using System.Windows;
using System.Windows.Input;
using SheetMe.Designer.Controls;
using SheetMe.Designer.Services;
namespace SheetMe.Designer.Views;
///
/// 알림·확인 대화상자 — 순정 Win32 MessageBox 대체.
///
/// 기각 의미론은 MessageBox 를 그대로 따른다: 닫기(X)·Alt+F4·Esc 는
/// 확인 1개면 '확인', 예/아니오면 '아니오', 예/아니오/취소면 '취소'로 돌아간다.
///
/// 를 쓰지 않는다. Show() 로 띄운 창에 대입하면
/// InvalidOperationException 이고, 진단 렌더러(DialogShots)가 정확히 그 경로로 이 창을 찍는다.
/// 같은 이유로 Closing 에서 e.Cancel 로 닫기를 막지도 않는다 —
/// 렌더러가 Close() 로만 창을 정리하므로 취소되면 화면 밖 창이 살아남는다.
///
public partial class MessageDialogView : Window
{
#region Member Fields
private readonly string caption;
private readonly string head;
private readonly string? detail;
private bool? answer;
#endregion
#region Properties
/// 사용자 응답 — 예=true / 아니오=false / 취소=null
public bool? Answer => answer;
#endregion
#region Constructors
///
/// 공개 생성자 — 정적 헬퍼로 감싸지 말고 이 형태를 유지할 것.
/// 진단 렌더러가 팩토리로 new 해서 라이트·다크 스냅샷을 찍는다(private 생성자면 편입 불가).
///
public MessageDialogView(DialogKind kind, string caption, string head, string? detail,
DialogButtons buttons, string yes = "예", string no = "아니오", bool destructive = false)
{
InitializeComponent();
this.caption = caption;
this.head = head;
this.detail = detail;
// 노트북 해상도에서 창이 화면을 덮지 않도록 한 번 더 조인다
MaxHeight = Math.Min(620, SystemParameters.WorkArea.Height * 0.85);
// 뒤를 어둡게 할 자격 — 지금 멈추고 결정해야 하는 것만.
// 물음(되돌릴 수 없는 쓰기의 확인)과 오류(진행이 정의되지 않는 차단)가 그것이다.
// 알림·경고는 읽고 지나가는 것이라 뒤를 가리지 않는다.
Services.ModalScrim.SetDimBehind(this, kind is DialogKind.Question or DialogKind.Error);
CaptionText.Text = caption;
HeadText.Text = head;
DetailText.Text = detail ?? string.Empty;
DetailText.Visibility = string.IsNullOrEmpty(detail) ? Visibility.Collapsed : Visibility.Visible;
var (icon, brushKey) = Visual(kind);
// 색은 반드시 '키'로 넘긴다 — Brush 인스턴스를 대입하면 그 시점 값으로 굳어 테마 전환을 못 따라간다
SeverityIcon.Content = LucideIcons.Icon(icon, 22, brushKey);
// 오류만 복사 버튼을 노출한다 — 오류 코드를 전화로 불러 주던 실사용 경로
CopyButton.Visibility = kind == DialogKind.Error ? Visibility.Visible : Visibility.Collapsed;
Configure(buttons, yes, no, destructive);
}
#endregion
#region Methods
/// 성격 → (아이콘, 색 토큰)
private static (string Icon, string BrushKey) Visual(DialogKind kind) => kind switch
{
DialogKind.Error => ("circle-alert", "B.Danger"),
DialogKind.Warning => ("triangle-alert", "B.Warning"),
DialogKind.Question => ("circle-help", "B.AccentText"),
_ => ("info", "B.AccentText"),
};
/// 버튼 구성·기본 응답·초기 포커스
private void Configure(DialogButtons buttons, string yes, string no, bool destructive)
{
switch (buttons)
{
case DialogButtons.YesNo:
answer = false;
AffirmButton.Content = yes;
DenyButton.Content = no;
DenyButton.Visibility = Visibility.Visible;
DenyButton.IsCancel = true;
break;
case DialogButtons.YesNoCancel:
answer = null;
AffirmButton.Content = yes;
DenyButton.Content = no;
DenyButton.Visibility = Visibility.Visible;
CancelButton.Visibility = Visibility.Visible;
CancelButton.IsCancel = true;
break;
default:
answer = true;
AffirmButton.Content = "확인";
AffirmButton.IsCancel = true;
break;
}
// 되돌리기 어려운 확인에서는 기본 버튼을 부정 쪽에 둔다 — Enter 한 번에 페이지가 지워지면 안 된다
var defaultButton = destructive && DenyButton.Visibility == Visibility.Visible
? DenyButton
: AffirmButton;
defaultButton.IsDefault = true;
Loaded += (_, _) => defaultButton.Focus();
}
private void OnAffirmClick(object sender, RoutedEventArgs e)
{
answer = true;
Close();
}
private void OnDenyClick(object sender, RoutedEventArgs e)
{
answer = false;
Close();
}
/// 닫기(X)·취소 — 생성자에서 정한 기각 기본값을 그대로 두고 닫는다
private void OnDismissClick(object sender, RoutedEventArgs e) => Close();
/// 본문이 넘칠 때만 버튼 줄 위에 구분선 — 창이 최대 높이에 걸렸다는 유일한 신호
private void OnBodyScrollChanged(object sender, System.Windows.Controls.ScrollChangedEventArgs e)
=> Footer.BorderThickness = new Thickness(0, BodyScroll.ScrollableHeight > 0.5 ? 1 : 0, 0, 0);
/// 내용 복사 — MessageBox 의 Ctrl+C 동작 보존(오류 코드를 옮겨 적던 경로)
private void OnCopyClick(object sender, RoutedEventArgs e) => CopyToClipboard();
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.C && Keyboard.Modifiers == ModifierKeys.Control)
{
CopyToClipboard();
e.Handled = true;
return;
}
base.OnKeyDown(e);
}
private void CopyToClipboard()
{
var text = string.IsNullOrEmpty(detail)
? $"{caption}\n---\n{head}"
: $"{caption}\n---\n{head}\n\n{detail}";
try
{
Clipboard.SetText(text);
}
catch (Exception ex)
{
// 클립보드는 다른 프로세스가 잠글 수 있다 — 복사 실패가 알림 자체를 막으면 안 된다
AppLog.Warn($"알림 내용 복사 실패: {ex.Message}");
}
}
#endregion
}