using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Effects;
namespace AxCopilot.Views;
///
/// 도구 실행 승인을 위한 간결한 별도 다이얼로그 창.
/// PlanViewerV2(전체 계획 뷰어)와 분리하여 도구 단위 승인에 사용합니다.
///
internal sealed class ToolApprovalWindow : Window
{
private string? _result;
private ToolApprovalWindow(string message, List options)
{
Width = 500;
MinWidth = 400;
MaxWidth = 600;
SizeToContent = SizeToContent.Height;
WindowStartupLocation = WindowStartupLocation.CenterScreen;
ResizeMode = ResizeMode.NoResize;
WindowStyle = WindowStyle.None;
AllowsTransparency = true;
Background = Brushes.Transparent;
ShowInTaskbar = false;
Topmost = true;
var bg = Application.Current.TryFindResource("LauncherBackground") as Brush
?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E));
var primary = Application.Current.TryFindResource("PrimaryText") as Brush ?? Brushes.White;
var secondary = Application.Current.TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
var accent = Application.Current.TryFindResource("AccentColor") as Brush
?? new SolidColorBrush(Color.FromRgb(0x4B, 0x5E, 0xFC));
var border = Application.Current.TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
var itemBg = Application.Current.TryFindResource("ItemBackground") as Brush
?? new SolidColorBrush(Color.FromRgb(0x2A, 0x2B, 0x40));
var errorBrush = Application.Current.TryFindResource("ErrorColor") as Brush
?? new SolidColorBrush(Color.FromRgb(0xDC, 0x26, 0x26));
var hoverBg = Application.Current.TryFindResource("ItemHoverBackground") as Brush
?? new SolidColorBrush(Color.FromArgb(0x20, 0xFF, 0xFF, 0xFF));
var root = new Border
{
Background = bg,
BorderBrush = border,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(14),
Padding = new Thickness(20, 16, 20, 16),
Effect = new DropShadowEffect
{
BlurRadius = 20,
ShadowDepth = 4,
Opacity = 0.35,
Color = Colors.Black,
},
};
var stack = new StackPanel();
// Header
var header = new Grid { Margin = new Thickness(0, 0, 0, 12) };
header.MouseLeftButtonDown += (_, _) => { try { DragMove(); } catch { } };
header.Children.Add(new StackPanel
{
Orientation = Orientation.Horizontal,
Children =
{
new TextBlock
{
Text = "\uE946", // Shield icon
FontFamily = new FontFamily("Segoe MDL2 Assets"),
FontSize = 15,
Foreground = accent,
Margin = new Thickness(0, 0, 8, 0),
VerticalAlignment = VerticalAlignment.Center,
},
new TextBlock
{
Text = "실행 확인",
FontSize = 13.5,
FontWeight = FontWeights.SemiBold,
Foreground = primary,
VerticalAlignment = VerticalAlignment.Center,
}
}
});
// Close button
var close = new Border
{
Width = 26,
Height = 26,
CornerRadius = new CornerRadius(7),
Background = Brushes.Transparent,
Cursor = Cursors.Hand,
HorizontalAlignment = HorizontalAlignment.Right,
VerticalAlignment = VerticalAlignment.Center,
Child = new TextBlock
{
Text = "\uE8BB",
FontFamily = new FontFamily("Segoe MDL2 Assets"),
FontSize = 10,
Foreground = secondary,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
},
};
close.MouseLeftButtonUp += (_, _) => { _result = "취소"; Close(); };
close.MouseEnter += (_, _) => close.Background = hoverBg;
close.MouseLeave += (_, _) => close.Background = Brushes.Transparent;
header.Children.Add(close);
stack.Children.Add(header);
// Message content
var msgBorder = new Border
{
Background = itemBg,
CornerRadius = new CornerRadius(12),
Padding = new Thickness(14, 11, 14, 11),
Margin = new Thickness(0, 0, 0, 14),
};
var msgText = new TextBlock
{
Text = message,
FontSize = 13,
FontFamily = new FontFamily("Segoe UI"),
Foreground = primary,
TextWrapping = TextWrapping.Wrap,
LineHeight = 20,
};
msgBorder.Child = msgText;
stack.Children.Add(msgBorder);
// Buttons
var btnPanel = new StackPanel
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Right,
};
foreach (var option in options)
{
var btn = CreateOptionButton(option, primary, secondary, accent, bg, errorBrush);
btn.MouseLeftButtonUp += (_, _) => { _result = option; Close(); };
btnPanel.Children.Add(btn);
}
stack.Children.Add(btnPanel);
root.Child = stack;
Content = root;
// Entrance animation
root.Opacity = 0;
root.RenderTransformOrigin = new Point(0.5, 0.5);
root.RenderTransform = new ScaleTransform(0.96, 0.96);
Loaded += (_, _) =>
{
root.BeginAnimation(OpacityProperty, new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(140)));
var sx = new DoubleAnimation(0.96, 1, TimeSpan.FromMilliseconds(180))
{
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut },
};
var sy = new DoubleAnimation(0.96, 1, TimeSpan.FromMilliseconds(180))
{
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseOut },
};
((ScaleTransform)root.RenderTransform).BeginAnimation(ScaleTransform.ScaleXProperty, sx);
((ScaleTransform)root.RenderTransform).BeginAnimation(ScaleTransform.ScaleYProperty, sy);
};
// ESC to cancel
KeyDown += (_, e) =>
{
if (e.Key == Key.Escape)
{
_result = "취소";
Close();
}
};
}
private Border CreateOptionButton(string label, Brush primary, Brush secondary, Brush accent, Brush bg, Brush errorBrush)
{
Brush foreground, background, borderBrush;
switch (label)
{
case "확인":
case "승인":
foreground = Brushes.White;
background = accent;
borderBrush = accent;
break;
case "취소":
case "중단":
foreground = errorBrush;
background = Brushes.Transparent;
borderBrush = errorBrush;
break;
default:
foreground = primary;
background = Brushes.Transparent;
borderBrush = secondary;
break;
}
var isDestructive = label == "취소" || label == "중단";
// Build inner content: optional left accent bar + label
FrameworkElement child;
if (isDestructive)
{
var grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
var accentBar = new Border
{
Width = 4,
CornerRadius = new CornerRadius(2),
Background = errorBrush,
Margin = new Thickness(0, 4, 8, 4),
VerticalAlignment = VerticalAlignment.Stretch,
};
Grid.SetColumn(accentBar, 0);
grid.Children.Add(accentBar);
var txt = new TextBlock
{
Text = label,
FontSize = 12,
FontWeight = FontWeights.SemiBold,
Foreground = foreground,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
Grid.SetColumn(txt, 1);
grid.Children.Add(txt);
child = grid;
}
else
{
child = new TextBlock
{
Text = label,
FontSize = 12,
FontWeight = FontWeights.SemiBold,
Foreground = foreground,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
}
var btn = new Border
{
MinWidth = 84,
Height = 36,
CornerRadius = new CornerRadius(10),
Background = background,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
Padding = new Thickness(14, 0, 14, 0),
Margin = new Thickness(10, 0, 0, 0),
Cursor = Cursors.Hand,
Child = child,
};
btn.MouseEnter += (_, _) => btn.Opacity = 0.85;
btn.MouseLeave += (_, _) => btn.Opacity = 1.0;
return btn;
}
/// 도구 승인 다이얼로그를 표시하고 결과를 반환합니다.
internal static string? Show(Window? owner, string message, List options)
=> Show(owner, message, options, CancellationToken.None);
///
/// 도구 승인 다이얼로그를 표시합니다. cancellationToken이 트리거되면 창을 자동으로 닫고 null을 반환합니다.
/// 장시간 미응답 시 타임아웃으로 에이전트 루프가 멈추는 것을 방지하기 위해 사용합니다.
///
internal static string? Show(Window? owner, string message, List options, CancellationToken cancellationToken)
{
var dialog = new ToolApprovalWindow(message, options);
if (owner != null && IsWindowAlive(owner))
{
dialog.WindowStartupLocation = WindowStartupLocation.CenterOwner;
dialog.Owner = owner;
}
CancellationTokenRegistration reg = default;
if (cancellationToken.CanBeCanceled)
{
reg = cancellationToken.Register(() =>
{
// UI 스레드에서 안전하게 닫기
dialog.Dispatcher.BeginInvoke(new Action(() =>
{
try { if (dialog.IsVisible) dialog.Close(); }
catch { /* 이미 닫혀있거나 파괴 중 — 무시 */ }
}));
});
}
try
{
dialog.ShowDialog();
}
finally
{
reg.Dispose();
}
return dialog._result;
}
private static bool IsWindowAlive(Window? w)
{
if (w == null) return false;
try { var _ = w.IsVisible; return true; }
catch { return false; }
}
}