using System.Globalization;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Text.RegularExpressions;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Media.Animation;
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.Web.WebView2.Core;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.System;
using WinRT.Interop;
using YMhut.Box.Core.Api;
using YMhut.Box.Core.Data;
using YMhut.Box.Core.Logging;
using YMhut.Box.Core.Settings;
using YMhut.Box.Core.Tools;
using YMhut.Box.WinUI.Views;
using YMhut.Box.WinUI.Services;
using YMhut.Box.WinUI.ViewModels.Tools;

#pragma warning disable CS0162

namespace YMhut.Box.WinUI.Views.Tools;

public abstract partial class AdaptiveToolPage : ToolPageBase
{
    private readonly Action? _goBack;
    private readonly IApiManager _apiManager = AppServices.GetRequiredService<IApiManager>();
    private readonly IReferenceDataService _referenceDataService = AppServices.GetRequiredService<IReferenceDataService>();
    private readonly ILogService _logService = AppServices.GetRequiredService<ILogService>();
    private readonly ISettingsService _settingsService = AppServices.GetRequiredService<ISettingsService>();
    private readonly IToolWorkerService _workerService = AppServices.GetRequiredService<IToolWorkerService>();
    private readonly WebView2EnvironmentFactory _webViewEnvironmentFactory = AppServices.GetRequiredService<WebView2EnvironmentFactory>();
    private readonly ToolResultWebBridge _toolResultWebBridge = AppServices.GetRequiredService<ToolResultWebBridge>();
    private readonly ToolPageWebBridge _toolPageWebBridge = AppServices.GetRequiredService<ToolPageWebBridge>();
    private readonly IUiPerformanceCoordinator _uiPerformanceCoordinator = AppServices.GetRequiredService<IUiPerformanceCoordinator>();
    private readonly AdaptiveToolViewModel _viewModel;
    private readonly ToolPageSpec _spec;

    private readonly AutoSuggestBox _queryBox = new()
    {
        QueryIcon = new SymbolIcon(Symbol.Find),
        HorizontalAlignment = HorizontalAlignment.Stretch,
        Visibility = Visibility.Collapsed
    };

    private readonly TextBox _inputBox = new()
    {
        AcceptsReturn = true,
        TextWrapping = TextWrapping.Wrap,
        MinHeight = 132
    };

    private readonly ComboBox _parameterBox = new()
    {
        Header = AppLocalizer.T("参数", "Parameter"),
        PlaceholderText = AppLocalizer.T("选择固定选项", "Choose a fixed option"),
        HorizontalAlignment = HorizontalAlignment.Stretch,
        Visibility = Visibility.Collapsed
    };

    private readonly TextBox _outputBox = new()
    {
        AcceptsReturn = true,
        IsReadOnly = true,
        TextWrapping = TextWrapping.Wrap,
        MinHeight = 280,
        FontFamily = new FontFamily("Cascadia Mono"),
        Visibility = Visibility.Collapsed
    };

    private readonly AutoSuggestBox _resultSearchBox = new()
    {
        QueryIcon = new SymbolIcon(Symbol.Find),
        PlaceholderText = AppLocalizer.T("搜索结果", "Search results"),
        MinWidth = 240,
        MaxWidth = 420,
        HorizontalAlignment = HorizontalAlignment.Stretch
    };

    private readonly TextBlock _resultSearchStatus = ModernUi.Text(AppLocalizer.T("暂无结果", "No result"), 12, FontWeights.SemiBold, ModernUi.TextSecondary, maxLines: 1);
    private Button? _renderedResultButton;
    private Button? _rawResultButton;

    private readonly StackPanel _ruleControlsPanel = new()
    {
        Spacing = 12,
        Visibility = Visibility.Collapsed
    };

    private readonly Dictionary<string, string> _selectedRuleValues = new(StringComparer.OrdinalIgnoreCase);

    private readonly TextBox _auxBox = new()
    {
        Header = AppLocalizer.T("附加参数", "Extra parameter"),
        PlaceholderText = AppLocalizer.T("分隔符、关键词、前后缀或密钥", "Delimiter, keyword, prefix, suffix, or key"),
        Visibility = Visibility.Collapsed
    };

    private readonly NumberBox _numberBox = NumberInput(AppLocalizer.T("数值", "Value"), 1);
    private readonly NumberBox _secondaryNumberBox = NumberInput(AppLocalizer.T("数值 B", "Value B"), 1);
    private readonly NumberBox _thirdNumberBox = NumberInput(AppLocalizer.T("数值 C", "Value C"), 1);
    private readonly NumberBox _fourthNumberBox = NumberInput(AppLocalizer.T("数值 D", "Value D"), 1);

    private readonly CalendarDatePicker _startDatePicker = new()
    {
        Header = AppLocalizer.T("开始日期", "Start date"),
        Date = DateTimeOffset.Now,
        Visibility = Visibility.Collapsed
    };

    private readonly CalendarDatePicker _endDatePicker = new()
    {
        Header = AppLocalizer.T("结束日期", "End date"),
        Date = DateTimeOffset.Now.AddDays(1),
        Visibility = Visibility.Collapsed
    };

    private readonly ProgressRing _progressRing = new()
    {
        Width = 24,
        Height = 24,
        IsActive = false,
        Visibility = Visibility.Collapsed,
        VerticalAlignment = VerticalAlignment.Center
    };

    private readonly StackPanel _resultCards = new() { Spacing = 12 };
    private readonly ScrollViewer _renderedResultScroll = new()
    {
        VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
        HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled,
        MinHeight = 280
    };
    private readonly WebView2 _resultWebView = new()
    {
        Visibility = Visibility.Collapsed,
        HorizontalAlignment = HorizontalAlignment.Stretch,
        VerticalAlignment = VerticalAlignment.Stretch,
        MinHeight = 280
    };
    private readonly WebView2 _toolPageWebView = new()
    {
        Visibility = Visibility.Collapsed,
        HorizontalAlignment = HorizontalAlignment.Stretch,
        VerticalAlignment = VerticalAlignment.Stretch,
        MinHeight = 320
    };
    private readonly Grid _toolGrid = new() { ColumnSpacing = 16, RowSpacing = 16 };
    private readonly DispatcherTimer _arrangeDebounce = new() { Interval = TimeSpan.FromMilliseconds(120) };

    private bool _autoRefreshStarted;
    private bool _showRawResult;
    private bool _resultWebViewReady;
    private bool _resultWebViewFailed;
    private bool _toolPageWebViewReady;
    private bool _toolPageWebViewFailed;
    private bool _useToolPageWebView;
    private string _lastRawOutput = string.Empty;
    private ToolResultDocument? _lastToolPageDocument;
    private ToolResultRunState? _lastToolPageRunState;
    private CancellationTokenSource? _runCts;
    private long _runSequence;

    private enum ToolRunState
    {
        Loading,
        Success,
        Empty,
        Error,
        Canceled
    }

    protected AdaptiveToolPage(IToolModule module, AdaptiveToolViewModel viewModel, Action? goBack = null)
    {
        _goBack = goBack;
        _viewModel = viewModel;
        _spec = ToolPageSpecCatalog.For(module);
        Background = ModernUi.AppBackground;
        BindModule(module);

        _resultSearchBox.TextChanged += (_, _) => UpdateResultSearchStatus();
        _resultSearchBox.QuerySubmitted += async (_, _) => await LogResultSearchAsync();
        _renderedResultScroll.Content = _resultCards;
        _queryBox.QuerySubmitted += async (_, _) => await RunCurrentToolAsync();

        Content = BuildContent(module);
        Loaded += AdaptiveToolPage_Loaded;
        _settingsService.PropertyChanged += SettingsService_PropertyChanged;
        _uiPerformanceCoordinator.LightModeChanged += UiPerformanceCoordinator_LightModeChanged;
        Unloaded += (_, _) =>
        {
            _settingsService.PropertyChanged -= SettingsService_PropertyChanged;
            _uiPerformanceCoordinator.LightModeChanged -= UiPerformanceCoordinator_LightModeChanged;
            _arrangeDebounce.Stop();
            CancelRunningTool();
        };
        SizeChanged += (_, e) =>
        {
            UpdateAdaptiveWebViewHeights(e.NewSize.Height);
            ArrangeGalleryToolGrid();
        };
        _arrangeDebounce.Tick += (_, _) =>
        {
            _arrangeDebounce.Stop();
            ArrangeGalleryToolGridCore();
            UpdateAdaptiveWebViewHeights(ActualHeight);
        };
    }

    private void UiPerformanceCoordinator_LightModeChanged(object? sender, UiPerformanceModeChangedEventArgs e)
    {
        if (DispatcherQueue.HasThreadAccess)
        {
            SendPerformanceMode();
            return;
        }

        DispatcherQueue.TryEnqueue(SendPerformanceMode);
    }

    private void SettingsService_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (DispatcherQueue.HasThreadAccess)
        {
            SendToolPageSettings();
            return;
        }

        DispatcherQueue.TryEnqueue(SendToolPageSettings);
    }

    private void SendToolPageSettings()
    {
        try
        {
            if (_toolPageWebViewReady && _toolPageWebView.CoreWebView2 is not null)
            {
                _toolPageWebView.CoreWebView2.PostWebMessageAsJson(_toolPageWebBridge.SerializeTheme());
            }
        }
        catch (Exception exception)
        {
            _toolPageWebViewReady = false;
            if (exception is System.Runtime.InteropServices.COMException)
            {
                return;
            }

            CrashLog.Write(exception);
        }
    }

    private void SendPerformanceMode()
    {
        var json = JsonSerializer.Serialize(
            new { type = "performanceMode", lightMode = _uiPerformanceCoordinator.IsLightMode, level = _uiPerformanceCoordinator.IsLightMode ? "strong" : "normal" },
            new JsonSerializerOptions(JsonSerializerDefaults.Web));

        try
        {
            if (_toolPageWebViewReady && _toolPageWebView.CoreWebView2 is not null)
            {
                _toolPageWebView.CoreWebView2.PostWebMessageAsJson(json);
            }

            if (_resultWebViewReady && _resultWebView.CoreWebView2 is not null)
            {
                _resultWebView.CoreWebView2.PostWebMessageAsJson(json);
            }
        }
        catch (Exception exception)
        {
            if (exception is System.Runtime.InteropServices.COMException)
            {
                return;
            }

            CrashLog.Write(exception);
        }
    }

    private void UpdateAdaptiveWebViewHeights(double availableHeight)
    {
        if (availableHeight <= 0 || double.IsNaN(availableHeight))
        {
            return;
        }

        var bodyHeight = Math.Clamp(availableHeight - 180, 300, 1200);
        var resultHeight = Math.Clamp(availableHeight - 260, 280, 980);
        _toolPageWebView.MaxHeight = bodyHeight;
        _resultWebView.MaxHeight = resultHeight;
        _renderedResultScroll.MaxHeight = resultHeight;
        _outputBox.MaxHeight = resultHeight;
    }

    private async void AdaptiveToolPage_Loaded(object sender, RoutedEventArgs e)
    {
        if (Module is null)
        {
            return;
        }

        await _settingsService.RecordRecentToolAsync(Module.Id);
        ArrangeGalleryToolGrid();

        if (!_autoRefreshStarted && (_spec.AutoRunOnOpen || ShouldAutoRefresh(Module)))
        {
            _autoRefreshStarted = true;
            await RunCurrentToolAsync();
        }
    }

    private UIElement BuildContent(IToolModule module)
    {
        var root = new Grid();
        root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
        root.RowDefinitions.Add(new RowDefinition());
        root.Children.Add(BuildModernGalleryHeader(module));

        FrameworkElement body = module.Id switch
        {
            "safe_browser" => BuildNativeHost(new SafeBrowserLauncherPage()),
            "media_player" => BuildNativeHost(new MediaPlayerPage(shellMode: MediaPlayerShellMode.Embedded)),
            "system_tool" => BuildSystemLauncherHost(module),
            _ => BuildStandardHost(module)
        };

        Grid.SetRow(body, 1);
        root.Children.Add(body);
        return root;
    }

    private Border BuildHeader(IToolModule module)
    {
        var back = ModernUi.IconButton("\uE72B", AppLocalizer.T("返回工具箱", "Back to toolbox"), () => _goBack?.Invoke());
        var detailPanel = new StackPanel
        {
            Spacing = 4,
            Visibility = Visibility.Collapsed,
            Children =
            {
                ModernUi.Text($"{ToolText.CategoryLabel(module.Metadata.Category)} / {(_spec.AutoRunOnOpen ? AppLocalizer.T("自动加载", "auto load") : AppLocalizer.T("手动执行", "manual run"))}", 14, foreground: ModernUi.TextSecondary, maxLines: 1),
                ModernUi.Text(ToolText.Description(module), 14, foreground: ModernUi.TextSecondary, maxLines: 2)
            }
        };
        var toggleText = ModernUi.Text(AppLocalizer.T("展开", "Expand"), 14, FontWeights.SemiBold);
        var toggle = new Button
        {
            Padding = new Thickness(12, 7, 12, 7),
            CornerRadius = new CornerRadius(6),
            BorderBrush = ModernUi.Stroke,
            Background = ModernUi.SurfaceAlt,
            Content = toggleText
        };
        var expanded = false;
        toggle.Click += (_, _) =>
        {
            expanded = !expanded;
            detailPanel.Visibility = expanded ? Visibility.Visible : Visibility.Collapsed;
            toggleText.Text = expanded ? AppLocalizer.T("收缩", "Collapse") : AppLocalizer.T("展开", "Expand");
        };

        var grid = new Grid { ColumnSpacing = 14 };
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        grid.ColumnDefinitions.Add(new ColumnDefinition());
        grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        grid.Children.Add(back);

        var icon = ModernUi.IconTile(module.Metadata.IconGlyph, 48, ModernUi.AccentSoft, ModernUi.Accent, 21);
        Grid.SetColumn(icon, 1);
        grid.Children.Add(icon);

        var title = new StackPanel
        {
            Spacing = 4,
            VerticalAlignment = VerticalAlignment.Center,
            Children =
            {
                ModernUi.Text(ToolText.Name(module), 24, FontWeights.SemiBold, maxLines: 1),
                detailPanel
            }
        };
        Grid.SetColumn(title, 2);
        grid.Children.Add(title);

        var right = new StackPanel
        {
            Orientation = Orientation.Horizontal,
            Spacing = 10,
            VerticalAlignment = VerticalAlignment.Center,
            Children = { _progressRing, toggle }
        };
        Grid.SetColumn(right, 3);
        grid.Children.Add(right);

        return ModernUi.Card(grid, new Thickness(18), new Thickness(32, 28, 32, 12), radius: 8);
    }

    private static Grid BuildNativeHost(Page page)
    {
        return new Grid
        {
            Margin = new Thickness(32, 0, 32, 32),
            Children = { page }
        };
    }

    private FrameworkElement BuildColorWorkbench()
    {
        var colorInput = new TextBox
        {
            Header = AppLocalizer.T("颜色值", "Color value"),
            PlaceholderText = "#3399FF",
            Text = "#3399FF",
            HorizontalAlignment = HorizontalAlignment.Stretch
        };
        var red = ColorSlider(AppLocalizer.T("红色", "Red"), 51);
        var green = ColorSlider(AppLocalizer.T("绿色", "Green"), 153);
        var blue = ColorSlider(AppLocalizer.T("蓝色", "Blue"), 255);
        var preview = new Border
        {
            MinHeight = 220,
            CornerRadius = new CornerRadius(8),
            BorderBrush = ModernUi.Stroke,
            BorderThickness = new Thickness(1)
        };
        var dataPanel = new VariableSizedWrapGrid
        {
            Orientation = Orientation.Horizontal,
            ItemWidth = 190,
            ItemHeight = 76
        };
        var recentPanel = new VariableSizedWrapGrid
        {
            Orientation = Orientation.Horizontal,
            ItemWidth = 58,
            ItemHeight = 46
        };
        var suppressTextUpdate = false;

        void ApplyColor(byte r, byte g, byte b, bool updateText)
        {
            var hex = $"#{r:X2}{g:X2}{b:X2}";
            preview.Background = ModernUi.Brush(hex);
            dataPanel.Children.Clear();
            foreach (var row in ColorRows(r, g, b))
            {
                dataPanel.Children.Add(ModernUi.Card(new StackPanel
                {
                    Spacing = 2,
                    Children =
                    {
                        ModernUi.Text(row.Key, 12, FontWeights.SemiBold, ModernUi.TextSecondary, maxLines: 1),
                        ModernUi.Text(row.Value, 14, FontWeights.SemiBold, maxLines: 2)
                    }
                }, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt));
            }

            _lastRawOutput = string.Join(Environment.NewLine, ColorRows(r, g, b).Select(row => $"{row.Key}: {row.Value}"));
            _outputBox.Text = _lastRawOutput;
            _viewModel.Output = _lastRawOutput;

            if (updateText)
            {
                suppressTextUpdate = true;
                colorInput.Text = hex;
                suppressTextUpdate = false;
            }
        }

        void ApplyFromSliders()
        {
            ApplyColor((byte)Math.Round(red.Value), (byte)Math.Round(green.Value), (byte)Math.Round(blue.Value), updateText: true);
        }

        red.ValueChanged += (_, _) => ApplyFromSliders();
        green.ValueChanged += (_, _) => ApplyFromSliders();
        blue.ValueChanged += (_, _) => ApplyFromSliders();
        colorInput.TextChanged += (_, _) =>
        {
            if (suppressTextUpdate)
            {
                return;
            }

            if (!TryParseHexColor(colorInput.Text, out var r, out var g, out var b))
            {
                return;
            }

            red.Value = r;
            green.Value = g;
            blue.Value = b;
            ApplyColor(r, g, b, updateText: false);
        };

        foreach (var swatch in new[] { "#D13438", "#FFB900", "#107C10", "#0078D4", "#8764B8", "#E74856", "#00B7C3", "#2D7D9A", "#000000", "#FFFFFF" })
        {
            var button = new Button
            {
                Width = 46,
                Height = 34,
                Padding = new Thickness(0),
                CornerRadius = new CornerRadius(8),
                Background = ModernUi.Brush(swatch),
                BorderBrush = ModernUi.Stroke,
                BorderThickness = new Thickness(1)
            };
            ToolTipService.SetToolTip(button, swatch);
            button.Click += (_, _) => colorInput.Text = swatch;
            recentPanel.Children.Add(button);
        }

        ApplyColor(51, 153, 255, updateText: false);

        var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10 };
        actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("复制 HEX", "Copy HEX"), "\uE8C8", () => CopyColorValue(colorInput.Text)));
        actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("复制全部", "Copy all"), "\uE8C8", CopyResult));

        var root = new Grid { ColumnSpacing = 16, RowSpacing = 16 };
        root.ColumnDefinitions.Add(new ColumnDefinition());
        root.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(360) });
        root.Children.Add(ModernUi.Card(new StackPanel
        {
            Spacing = 16,
            Children =
            {
                ModernUi.Text(AppLocalizer.T("实时颜色预览", "Live color preview"), 18, FontWeights.SemiBold),
                preview,
                colorInput,
                red,
                green,
                blue
            }
        }, new Thickness(18), radius: 8));

        var side = ModernUi.Card(new StackPanel
        {
            Spacing = 16,
            Children =
            {
                ModernUi.Text(AppLocalizer.T("常用色块", "Color swatches"), 18, FontWeights.SemiBold),
                recentPanel,
                ModernUi.Text(AppLocalizer.T("色彩数据", "Color data"), 18, FontWeights.SemiBold),
                dataPanel,
                actions
            }
        }, new Thickness(18), radius: 8);
        Grid.SetColumn(side, 1);
        root.Children.Add(side);

        var scroll = new ScrollViewer
        {
            VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
            Content = root,
            Margin = new Thickness(32, 0, 0, 32),
            Padding = new Thickness(0, 0, 8, 0)
        };
        scroll.SizeChanged += (_, e) =>
        {
            root.Width = Math.Max(420, e.NewSize.Width - 28);
            var singleColumn = e.NewSize.Width < 980;
            root.ColumnDefinitions.Clear();
            root.RowDefinitions.Clear();
            root.ColumnDefinitions.Add(new ColumnDefinition());
            if (singleColumn)
            {
                root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
                root.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
                Grid.SetColumn(side, 0);
                Grid.SetRow(side, 1);
            }
            else
            {
                root.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(360) });
                Grid.SetColumn(side, 1);
                Grid.SetRow(side, 0);
            }
        };

        return scroll;
    }

    private FrameworkElement BuildStandardHost(IToolModule module)
    {
        ConfigureInputOutput(module);
        ConfigurePrimaryParameter(module);
        BuildRules(module);

        _toolGrid.Children.Clear();
        _toolGrid.Children.Add(BuildModernGalleryInputCard(module));
        _toolGrid.Children.Add(BuildGalleryResultCard());

        var scroll = new ScrollViewer
        {
            VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
            Content = _toolGrid,
            Margin = new Thickness(0),
            Padding = new Thickness(0, 0, 12, 0)
        };
        scroll.SizeChanged += (_, e) =>
        {
            _toolGrid.Width = Math.Max(420, e.NewSize.Width - 12);
            ArrangeGalleryToolGrid();
        };

        var host = new Grid
        {
            Margin = new Thickness(28, 0, 28, 28)
        };
        var loading = BuildToolPageLoadingPlaceholder(module);
        _toolPageWebView.Visibility = Visibility.Collapsed;
        scroll.Visibility = Visibility.Collapsed;
        host.Children.Add(scroll);
        host.Children.Add(loading);
        host.Children.Add(_toolPageWebView);
        _ = InitializeToolPageWebViewAsync(scroll, loading);
        return host;
    }

    private static UIElement BuildToolPageLoadingPlaceholder(IToolModule module)
    {
        var ring = new ProgressRing
        {
            Width = 28,
            Height = 28,
            IsActive = true,
            HorizontalAlignment = HorizontalAlignment.Left
        };
        var panel = new StackPanel
        {
            Spacing = 14,
            Width = 560,
            HorizontalAlignment = HorizontalAlignment.Left,
            VerticalAlignment = VerticalAlignment.Top,
            Children =
            {
                new StackPanel
                {
                    Orientation = Orientation.Horizontal,
                    Spacing = 12,
                    Children =
                    {
                        ring,
                        new StackPanel
                        {
                            Spacing = 2,
                            Children =
                            {
                                ModernUi.Text(ToolText.Name(module), 22, FontWeights.SemiBold, maxLines: 1),
                                ModernUi.Text(AppLocalizer.T("准备界面 · 加载资源 · 渲染结果区", "Preparing UI · Loading assets · Rendering result area"), 13, foreground: ModernUi.TextSecondary, maxLines: 1)
                            }
                        }
                    }
                },
                SkeletonLine(1),
                SkeletonLine(.82),
                SkeletonLine(.64)
            }
        };

        return ModernUi.Card(panel, new Thickness(24), radius: 8, background: ModernUi.Surface);
    }

    private static Border SkeletonLine(double widthFactor)
    {
        return new Border
        {
            Height = 12,
            Width = Math.Clamp(520 * widthFactor, 180, 520),
            CornerRadius = new CornerRadius(6),
            Background = ModernUi.SurfaceAlt
        };
    }

    private FrameworkElement BuildSystemLauncherHost(IToolModule module)
    {
        ConfigureInputOutput(module);
        ConfigurePrimaryParameter(module);
        BuildRules(module);

        var launcherGrid = new VariableSizedWrapGrid
        {
            Orientation = Orientation.Horizontal,
            ItemWidth = 190,
            ItemHeight = 92
        };

        foreach (var option in PrimaryParameterOptions(module))
        {
            var command = option.Input;
            var button = new Button
            {
                HorizontalAlignment = HorizontalAlignment.Stretch,
                VerticalAlignment = VerticalAlignment.Stretch,
                Padding = new Thickness(14),
                Background = ModernUi.SurfaceAlt,
                BorderBrush = ModernUi.Stroke,
                CornerRadius = new CornerRadius(8),
                Content = new StackPanel
                {
                    Spacing = 8,
                    Children =
                    {
                        ModernUi.Text(option.Label, 15, FontWeights.SemiBold, maxLines: 1),
                        ModernUi.Text(SystemToolHint(command), 12, foreground: ModernUi.TextSecondary, maxLines: 2)
                    }
                }
            };
            button.Click += async (_, _) =>
            {
                SelectParameter(command);
                await RunCurrentToolAsync();
            };
            launcherGrid.Children.Add(button);
        }

        _resultCards.Children.Clear();
        var content = new StackPanel
        {
            Spacing = 16,
            Children =
            {
                ModernUi.Card(new StackPanel
                {
                    Spacing = 12,
                    Children =
                    {
                        ModernUi.Text(AppLocalizer.T("系统入口", "System entries"), 18, FontWeights.SemiBold),
                        launcherGrid
                    }
                }, new Thickness(18), radius: 8),
                BuildGalleryResultCard()
            }
        };

        var scroll = new ScrollViewer
        {
            VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
            Content = content,
            Margin = new Thickness(32, 0, 32, 32)
        };
        scroll.SizeChanged += (_, e) =>
        {
            launcherGrid.ItemWidth = Math.Max(160, Math.Min(220, (e.NewSize.Width - 64) / Math.Max(1, Math.Floor(e.NewSize.Width / 220))));
        };
        return scroll;
    }

    private void SelectParameter(string input)
    {
        for (var index = 0; index < _parameterBox.Items.Count; index++)
        {
            if (_parameterBox.Items[index] is ComboBoxItem item && string.Equals(item.Tag as string, input, StringComparison.OrdinalIgnoreCase))
            {
                _parameterBox.SelectedIndex = index;
                return;
            }
        }
    }

    private static string SystemToolHint(string command)
    {
        return command switch
        {
            "status" => AppLocalizer.T("查看系统摘要", "View system summary"),
            "temp" => AppLocalizer.T("打开临时目录", "Open temporary folder"),
            "process" => AppLocalizer.T("查看进程摘要", "View process summary"),
            "env" or "path" => AppLocalizer.T("查看环境变量", "View environment variables"),
            _ => AppLocalizer.T("调用 Windows 系统工具", "Launch Windows system tool")
        };
    }

    private void ConfigureInputOutput(IToolModule module)
    {
        var multiline = _spec.PrimaryInput == ToolPrimaryInputKind.MultilineText;
        var expandableCodec = module.Id is "base64_codec" or "unicode_codec";
        var formatter = _spec.Layout == ToolLayoutKind.FormatterWorkbench;
        var numberBase = module.Id is "number_base" or "number_base_converter";
        _inputBox.AcceptsReturn = multiline || expandableCodec;
        _inputBox.TextWrapping = multiline || expandableCodec ? TextWrapping.Wrap : TextWrapping.NoWrap;
        _inputBox.MinHeight = formatter ? 180 : expandableCodec ? 72 : numberBase ? 44 : multiline ? 132 : 44;
        _inputBox.MaxHeight = formatter ? 360 : expandableCodec ? 220 : numberBase ? 72 : multiline ? 280 : 72;
        _inputBox.PlaceholderText = InputHint(module);
        _inputBox.Text = DefaultInput(module);

        _queryBox.Visibility = _spec.PrimaryInput is ToolPrimaryInputKind.Query or ToolPrimaryInputKind.Url
            ? Visibility.Visible
            : Visibility.Collapsed;
        _queryBox.PlaceholderText = InputHint(module);
        _queryBox.Text = DefaultInput(module);

        _startDatePicker.Visibility = _spec.PrimaryInput == ToolPrimaryInputKind.DateRange ? Visibility.Visible : Visibility.Collapsed;
        _endDatePicker.Visibility = _spec.PrimaryInput == ToolPrimaryInputKind.DateRange ? Visibility.Visible : Visibility.Collapsed;
        if (module.Id == "age_calculator" || module.Id == "shelf_life")
        {
            _endDatePicker.Visibility = Visibility.Collapsed;
        }

        var profile = ToolPageProfiles.For(module);
        _numberBox.Visibility = _spec.UsesNumberBox || profile.ShowNumber ? Visibility.Visible : Visibility.Collapsed;
        _secondaryNumberBox.Visibility = _spec.PrimaryInput is ToolPrimaryInputKind.NumberPair or ToolPrimaryInputKind.NumberTriple or ToolPrimaryInputKind.NumberQuad ? Visibility.Visible : Visibility.Collapsed;
        _thirdNumberBox.Visibility = _spec.PrimaryInput is ToolPrimaryInputKind.NumberTriple or ToolPrimaryInputKind.NumberQuad ? Visibility.Visible : Visibility.Collapsed;
        _fourthNumberBox.Visibility = _spec.PrimaryInput == ToolPrimaryInputKind.NumberQuad ? Visibility.Visible : Visibility.Collapsed;

        ConfigureNumericInputs(module);
        if (profile.ShowNumber && !UsesNumericPrimaryInput())
        {
            SetNumber(_numberBox, profile.NumberHeader, profile.NumberDefault, 0, 100000000);
        }

        _inputBox.Visibility = _spec.PrimaryInput is ToolPrimaryInputKind.None or ToolPrimaryInputKind.FixedOptions or ToolPrimaryInputKind.Query or ToolPrimaryInputKind.Url or ToolPrimaryInputKind.DateRange
            || UsesNumericPrimaryInput()
                ? Visibility.Collapsed
                : Visibility.Visible;
    }

    private Border BuildInputCard(IToolModule module)
    {
        var panel = new StackPanel { Spacing = _spec.PageDensity == ToolPageDensity.Compact ? 10 : 14 };

        if (_spec.ShowInputHeader)
        {
            panel.Children.Add(ModernUi.Text(InputTitle(module), 18, FontWeights.SemiBold));
        }

        if (_spec.ShowDescription)
        {
            panel.Children.Add(ModernUi.Text(InputDescription(module), 13, foreground: ModernUi.TextSecondary));
        }

        if (_parameterBox.Visibility == Visibility.Visible)
        {
            panel.Children.Add(_parameterBox);
        }

        if (_queryBox.Visibility == Visibility.Visible)
        {
            panel.Children.Add(_queryBox);
        }

        if (_startDatePicker.Visibility == Visibility.Visible)
        {
            panel.Children.Add(BuildDatePanel());
        }

        if (UsesNumericPrimaryInput())
        {
            panel.Children.Add(BuildNumberInputPanel());
        }

        if (_inputBox.Visibility == Visibility.Visible)
        {
            panel.Children.Add(_inputBox);
        }

        var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 10 };
        actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("执行", "Run"), "\uE768", async () => await RunCurrentToolAsync(), primary: true));
        if (_spec.RequiresFilePicker)
        {
            actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("选择文件", "Choose file"), "\uE8E5", BrowseInputFile));
        }
        actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("清空", "Clear"), "\uE74D", ClearInput));
        actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("复制结果", "Copy result"), "\uE8C8", CopyResult));
        ReplaceWithSemanticActions(actions);
        panel.Children.Add(actions);

        return ModernUi.Card(panel, new Thickness(18), radius: 8);
    }

    private void ReplaceWithSemanticActions(StackPanel actions)
    {
        actions.Children.Clear();
        AddSemanticActions(actions);
    }

    private void AddSemanticActions(StackPanel actions)
    {
        if (!string.IsNullOrWhiteSpace(_spec.PrimaryActionLabel))
        {
            actions.Children.Add(ModernUi.PillButton(LocalActionLabel(_spec.PrimaryActionLabel), "\uE768", async () => await RunCurrentToolAsync(), primary: true));
        }
        if (_spec.SecondaryActions.Contains("chooseFile"))
        {
            actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("选择文件", "Choose file"), "\uE8E5", BrowseInputFile));
        }
        if (_spec.SecondaryActions.Contains("clear"))
        {
            actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("清空", "Clear"), "\uE74D", ClearInput));
        }
        if (_spec.SecondaryActions.Contains("copyResult"))
        {
            actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("复制结果", "Copy result"), "\uE8C8", CopyResult));
        }
    }

    private static string LocalActionLabel(string action)
    {
        return action switch
        {
            "格式化" => AppLocalizer.T("格式化", "Format"),
            "转换" => AppLocalizer.T("转换", "Convert"),
            "生成" => AppLocalizer.T("生成", "Generate"),
            "查询" => AppLocalizer.T("查询", "Query"),
            "分析" => AppLocalizer.T("分析", "Analyze"),
            "预览" => AppLocalizer.T("预览", "Preview"),
            "扫描" => AppLocalizer.T("扫描", "Scan"),
            "打开" => AppLocalizer.T("打开", "Open"),
            "重新加载" => AppLocalizer.T("重新加载", "Reload"),
            "计算" => AppLocalizer.T("计算", "Calculate"),
            "处理" => AppLocalizer.T("处理", "Process"),
            _ => action
        };
    }

    private UIElement BuildDatePanel()
    {
        if (_endDatePicker.Visibility != Visibility.Visible)
        {
            return _startDatePicker;
        }

        var grid = new Grid { ColumnSpacing = 12 };
        grid.ColumnDefinitions.Add(new ColumnDefinition());
        grid.ColumnDefinitions.Add(new ColumnDefinition());
        grid.Children.Add(_startDatePicker);
        Grid.SetColumn(_endDatePicker, 1);
        grid.Children.Add(_endDatePicker);
        return grid;
    }

    private UIElement BuildNumberInputPanel()
    {
        var wrap = new VariableSizedWrapGrid
        {
            Orientation = Orientation.Horizontal,
            ItemWidth = 190,
            ItemHeight = 74
        };
        wrap.Children.Add(_numberBox);
        if (_secondaryNumberBox.Visibility == Visibility.Visible)
        {
            wrap.Children.Add(_secondaryNumberBox);
        }
        if (_thirdNumberBox.Visibility == Visibility.Visible)
        {
            wrap.Children.Add(_thirdNumberBox);
        }
        if (_fourthNumberBox.Visibility == Visibility.Visible)
        {
            wrap.Children.Add(_fourthNumberBox);
        }

        return wrap;
    }

    private StackPanel BuildAssistPanelContent(IToolModule module)
    {
        var panel = new StackPanel { Spacing = 14 };

        if (_spec.AssistPanelMode is ToolAssistPanelMode.SourceOnly or ToolAssistPanelMode.RulesAndSource)
        {
            panel.Children.Add(ModernUi.Text(RuleDescription(module), 13, foreground: ModernUi.TextSecondary));
        }

        if (_spec.AssistPanelMode is ToolAssistPanelMode.RulesOnly or ToolAssistPanelMode.RulesAndSource)
        {
            if (_ruleControlsPanel.Visibility == Visibility.Visible)
            {
                panel.Children.Add(_ruleControlsPanel);
            }
            if (_auxBox.Visibility == Visibility.Visible)
            {
                panel.Children.Add(_auxBox);
            }
            if (UsesSupplementalNumber(module))
            {
                panel.Children.Add(_numberBox);
            }
        }

        if (_spec.AssistPanelMode == ToolAssistPanelMode.ExamplesOnly)
        {
            panel.Children.Add(BuildExamplePanel(module));
        }

        if (_spec.AssistPanelMode is ToolAssistPanelMode.SourceOnly or ToolAssistPanelMode.RulesAndSource)
        {
            panel.Children.Add(BuildMetadataPanel(module));
        }

        return panel;
    }

    private string AssistPanelTitle()
    {
        return _spec.AssistPanelMode switch
        {
            ToolAssistPanelMode.RulesOnly => AppLocalizer.T("规则", "Rules"),
            ToolAssistPanelMode.SourceOnly => AppLocalizer.T("来源", "Source"),
            ToolAssistPanelMode.ExamplesOnly => AppLocalizer.T("示例", "Examples"),
            ToolAssistPanelMode.RulesAndSource => AppLocalizer.T("选项与来源", "Options and source"),
            _ => AppLocalizer.T("说明", "Info")
        };
    }

    private UIElement BuildExamplePanel(IToolModule module)
    {
        var examples = SampleInputs(module).ToArray();
        if (examples.Length == 0)
        {
            return ModernUi.Text(AppLocalizer.T("可直接执行默认配置，或按当前工具说明填写输入。", "Run with defaults or fill input according to this tool."), 13, foreground: ModernUi.TextSecondary);
        }

        var panel = new StackPanel { Spacing = 8 };
        panel.Children.Add(ModernUi.Text(AppLocalizer.T("示例", "Examples"), 13, FontWeights.SemiBold, ModernUi.TextSecondary));
        var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
        foreach (var example in examples)
        {
            var button = new Button
            {
                Content = example.Label,
                Padding = new Thickness(10, 4, 10, 5),
                CornerRadius = new CornerRadius(6),
                BorderBrush = ModernUi.Stroke,
                Background = ModernUi.SurfaceAlt
            };
            button.Click += (_, _) =>
            {
                _inputBox.Text = example.Input;
                _queryBox.Text = example.Input;
            };
            ToolTipService.SetToolTip(button, example.Input);
            row.Children.Add(button);
        }
        panel.Children.Add(row);
        return panel;
    }

    private UIElement BuildMetadataPanel(IToolModule module)
    {
        return new StackPanel
        {
            Orientation = Orientation.Horizontal,
            Spacing = 8,
            Children =
            {
                ModernUi.SmallBadge(module.Id, ModernUi.TextSecondary, ModernUi.SurfaceAlt),
                ModernUi.SmallBadge(_spec.Layout.ToString(), ModernUi.Accent, ModernUi.AccentSoft),
                ModernUi.SmallBadge(_spec.Result.ToString(), ModernUi.TextSecondary, ModernUi.SurfaceAlt)
            }
        };
    }

    private void ArrangeToolGrid()
    {
        if (_toolGrid.Children.Count is < 2 or > 3)
        {
            return;
        }

        _toolGrid.ColumnDefinitions.Clear();
        _toolGrid.RowDefinitions.Clear();
        _toolGrid.ColumnDefinitions.Add(new ColumnDefinition());

        var oneColumn = ActualWidth < 1060;
        if (ActualWidth > 0)
        {
            _toolGrid.Width = Math.Max(420, ActualWidth - 64);
        }

        if (oneColumn)
        {
            for (var index = 0; index < _toolGrid.Children.Count; index++)
            {
                _toolGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
                var element = (FrameworkElement)_toolGrid.Children[index];
                Grid.SetColumn(element, 0);
                Grid.SetRow(element, index);
            }
            return;
        }

        if (_toolGrid.Children.Count == 2)
        {
            _toolGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            _toolGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            Grid.SetColumn((FrameworkElement)_toolGrid.Children[0], 0);
            Grid.SetRow((FrameworkElement)_toolGrid.Children[0], 0);
            Grid.SetColumn((FrameworkElement)_toolGrid.Children[1], 0);
            Grid.SetRow((FrameworkElement)_toolGrid.Children[1], 1);
            return;
        }

        _toolGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(360) });
        _toolGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
        _toolGrid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
        Grid.SetColumn((FrameworkElement)_toolGrid.Children[0], 0);
        Grid.SetRow((FrameworkElement)_toolGrid.Children[0], 0);
        Grid.SetColumn((FrameworkElement)_toolGrid.Children[1], 1);
        Grid.SetRow((FrameworkElement)_toolGrid.Children[1], 0);
        Grid.SetColumn((FrameworkElement)_toolGrid.Children[2], 0);
        Grid.SetColumnSpan((FrameworkElement)_toolGrid.Children[2], 2);
        Grid.SetRow((FrameworkElement)_toolGrid.Children[2], 1);
    }

    private void BuildRules(IToolModule module)
    {
        _ruleControlsPanel.Children.Clear();
        _selectedRuleValues.Clear();

        foreach (var rule in _spec.Rules)
        {
            var control = BuildRuleControl(rule);
            if (control is not null)
            {
                _ruleControlsPanel.Children.Add(control);
            }
        }

        _ruleControlsPanel.Visibility = _ruleControlsPanel.Children.Count > 0 ? Visibility.Visible : Visibility.Collapsed;

        var profile = ToolPageProfiles.For(module);
        _auxBox.Visibility = profile.ShowAuxiliary ? Visibility.Visible : Visibility.Collapsed;
        if (profile.ShowAuxiliary)
        {
            _auxBox.Header = profile.AuxiliaryHeader;
            _auxBox.PlaceholderText = profile.AuxiliaryPlaceholder;
        }
    }

    private UIElement? BuildRuleControl(ToolRuleSpec rule)
    {
        var label = Localized(rule.LocalizedLabel, rule.Label);
        if (rule.Presentation == ToolRulePresentationKind.ToggleSwitch)
        {
            _selectedRuleValues[rule.Key] = "false";
            var toggle = new ToggleSwitch
            {
                Header = label,
                HorizontalAlignment = HorizontalAlignment.Stretch,
                IsOn = false
            };
            toggle.Toggled += (_, _) => _selectedRuleValues[rule.Key] = toggle.IsOn ? "true" : "false";
            return toggle;
        }

        if (rule.Options.Count == 0)
        {
            return null;
        }

        _selectedRuleValues[rule.Key] = rule.Options[0].Value;
        _viewModel.SelectedRule = rule.Options[0].Value;
        return rule.Presentation switch
        {
            ToolRulePresentationKind.ComboBox => BuildRuleComboBox(rule, label),
            ToolRulePresentationKind.SelectionCards => BuildRuleSelectionCards(rule, label),
            ToolRulePresentationKind.RadioButtons => BuildRuleRadioButtons(rule, label),
            _ => BuildRuleComboBox(rule, label)
        };
    }

    private UIElement BuildRuleComboBox(ToolRuleSpec rule, string label)
    {
        var box = new ComboBox
        {
            Header = label,
            HorizontalAlignment = HorizontalAlignment.Stretch
        };
        foreach (var option in rule.Options)
        {
            box.Items.Add(new ComboBoxItem
            {
                Content = Localized(option.LocalizedLabel, option.Label),
                Tag = option.Value
            });
        }
        box.SelectedIndex = 0;
        box.SelectionChanged += (_, _) =>
        {
            if (box.SelectedItem is ComboBoxItem item && item.Tag is string value)
            {
                _selectedRuleValues[rule.Key] = value;
                _viewModel.SelectedRule = value;
            }
        };
        return box;
    }

    private UIElement BuildRuleRadioButtons(ToolRuleSpec rule, string label)
    {
        var buttons = new RadioButtons { Header = label };
        foreach (var option in rule.Options)
        {
            buttons.Items.Add(new ComboBoxItem
            {
                Content = Localized(option.LocalizedLabel, option.Label),
                Tag = option.Value
            });
        }
        buttons.SelectedIndex = 0;
        buttons.SelectionChanged += (_, _) =>
        {
            if (buttons.SelectedItem is ComboBoxItem item && item.Tag is string value)
            {
                _selectedRuleValues[rule.Key] = value;
                _viewModel.SelectedRule = value;
            }
        };
        return buttons;
    }

    private UIElement BuildRuleSelectionCards(ToolRuleSpec rule, string label)
    {
        var panel = new StackPanel { Spacing = 8 };
        panel.Children.Add(ModernUi.Text(label, 13, FontWeights.SemiBold, ModernUi.TextSecondary, maxLines: 1));
        var row = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
        foreach (var option in rule.Options)
        {
            var value = option.Value;
            var button = new Button
            {
                Content = Localized(option.LocalizedLabel, option.Label),
                Padding = new Thickness(10, 5, 10, 6),
                CornerRadius = new CornerRadius(6),
                BorderBrush = ModernUi.Stroke,
                Background = ModernUi.SurfaceAlt,
                Tag = value
            };
            button.Click += (_, _) =>
            {
                _selectedRuleValues[rule.Key] = value;
                _viewModel.SelectedRule = value;
                foreach (var child in row.Children.OfType<Button>())
                {
                    child.Background = string.Equals(child.Tag as string, value, StringComparison.OrdinalIgnoreCase)
                        ? ModernUi.AccentSoft
                        : ModernUi.SurfaceAlt;
                    child.BorderBrush = string.Equals(child.Tag as string, value, StringComparison.OrdinalIgnoreCase)
                        ? ModernUi.Accent
                        : ModernUi.Stroke;
                }
            };
            if (string.Equals(value, _selectedRuleValues[rule.Key], StringComparison.OrdinalIgnoreCase))
            {
                button.Background = ModernUi.AccentSoft;
                button.BorderBrush = ModernUi.Accent;
            }
            row.Children.Add(button);
        }
        panel.Children.Add(row);
        return panel;
    }

    private void ConfigurePrimaryParameter(IToolModule module)
    {
        var options = PrimaryParameterOptions(module).ToArray();
        _parameterBox.Items.Clear();
        _parameterBox.Visibility = options.Length == 0 ? Visibility.Collapsed : Visibility.Visible;
        if (options.Length == 0)
        {
            return;
        }

        foreach (var option in options)
        {
            _parameterBox.Items.Add(new ComboBoxItem { Content = option.Label, Tag = option.Input });
        }

        _parameterBox.SelectedIndex = 0;
        SetInputFromSelectedParameter();
        _parameterBox.SelectionChanged += (_, _) => SetInputFromSelectedParameter();
    }

    private void SetInputFromSelectedParameter()
    {
        if (_parameterBox.SelectedItem is ComboBoxItem item && item.Tag is string input)
        {
            if (Module?.Id == "smart_search")
            {
                return;
            }

            _inputBox.Text = input;
            _queryBox.Text = input;
        }
    }

    private void ConfigureNumericInputs(IToolModule module)
    {
        HideSecondaryNumbers();
        switch (module.Id)
        {
            case "bmi_calculator":
                SetNumber(_numberBox, AppLocalizer.T("体重 (kg)", "Weight (kg)"), 70, 1, 500);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("身高 (m)", "Height (m)"), 1.75, 0.3, 3);
                break;
            case "percentage_calculator":
                SetNumber(_numberBox, AppLocalizer.T("部分", "Part"), 25);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("整体", "Whole"), 100);
                break;
            case "percentage_change_calculator":
                SetNumber(_numberBox, AppLocalizer.T("原值", "Old value"), 100);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("新值", "New value"), 120);
                break;
            case "discount_calculator":
                SetNumber(_numberBox, AppLocalizer.T("原价", "Original price"), 100, 0, 100000000);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("折扣 (%)", "Discount (%)"), 80, 0, 100);
                break;
            case "tax_calculator":
                SetNumber(_numberBox, AppLocalizer.T("价格", "Price"), 100, 0, 100000000);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("税率 (%)", "Tax rate (%)"), 13, 0, 100);
                break;
            case "ratio_simplifier":
                SetNumber(_numberBox, AppLocalizer.T("左值", "Left value"), 16, 1, 100000000);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("右值", "Right value"), 9, 1, 100000000);
                break;
            case "permutation_calculator":
            case "combination_calculator":
                SetNumber(_numberBox, "n", 10, 0, 170);
                SetNumber(_secondaryNumberBox, "r", 3, 0, 170);
                break;
            case "simple_interest_calculator":
                SetNumber(_numberBox, AppLocalizer.T("本金", "Principal"), 10000, 0, 100000000);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("年利率 (%)", "Annual rate (%)"), 3.5, 0, 100);
                SetNumber(_thirdNumberBox, AppLocalizer.T("年数", "Years"), 1, 0, 200);
                break;
            case "compound_interest_calculator":
                SetNumber(_numberBox, AppLocalizer.T("本金", "Principal"), 10000, 0, 100000000);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("年利率 (%)", "Annual rate (%)"), 3.5, 0, 100);
                SetNumber(_thirdNumberBox, AppLocalizer.T("年数", "Years"), 3, 0, 200);
                SetNumber(_fourthNumberBox, AppLocalizer.T("每年复利次数", "Times / year"), 12, 1, 365);
                break;
            case "loan_emi_calculator":
                SetNumber(_numberBox, AppLocalizer.T("本金", "Principal"), 100000, 0, 100000000);
                SetNumber(_secondaryNumberBox, AppLocalizer.T("年利率 (%)", "Annual rate (%)"), 4.5, 0, 100);
                SetNumber(_thirdNumberBox, AppLocalizer.T("月数", "Months"), 360, 1, 1200);
                break;
            case "shelf_life":
                SetNumber(_numberBox, AppLocalizer.T("保质期 (天)", "Shelf life (days)"), 180, 1, 3650);
                break;
            default:
                SetNumber(_numberBox, module.Id.Contains("converter", StringComparison.OrdinalIgnoreCase) ? AppLocalizer.T("数值", "Value") : AppLocalizer.T("数量 / 数值", "Count / value"), DefaultNumber(module), 0, 100000000);
                break;
        }

        _secondaryNumberBox.Visibility = _spec.PrimaryInput is ToolPrimaryInputKind.NumberPair or ToolPrimaryInputKind.NumberTriple or ToolPrimaryInputKind.NumberQuad ? Visibility.Visible : Visibility.Collapsed;
        _thirdNumberBox.Visibility = _spec.PrimaryInput is ToolPrimaryInputKind.NumberTriple or ToolPrimaryInputKind.NumberQuad ? Visibility.Visible : Visibility.Collapsed;
        _fourthNumberBox.Visibility = _spec.PrimaryInput == ToolPrimaryInputKind.NumberQuad ? Visibility.Visible : Visibility.Collapsed;
    }

    private static NumberBox NumberInput(string header, double value)
    {
        return new NumberBox
        {
            Header = header,
            Minimum = -100000000,
            Maximum = 100000000,
            Value = value,
            SpinButtonPlacementMode = NumberBoxSpinButtonPlacementMode.Compact,
            Visibility = Visibility.Collapsed
        };
    }

    private static void SetNumber(NumberBox box, string header, double value, double minimum = -100000000, double maximum = 100000000)
    {
        box.Header = header;
        box.Minimum = minimum;
        box.Maximum = maximum;
        box.Value = value;
    }

    private void HideSecondaryNumbers()
    {
        _secondaryNumberBox.Visibility = Visibility.Collapsed;
        _thirdNumberBox.Visibility = Visibility.Collapsed;
        _fourthNumberBox.Visibility = Visibility.Collapsed;
    }

    private Border BuildToolStateCard(ToolRunState state, IToolModule module, string message, long? durationMs = null)
    {
        var title = state switch
        {
            ToolRunState.Loading => AppLocalizer.T("正在请求并整理数据", "Requesting and shaping data"),
            ToolRunState.Empty => AppLocalizer.T("没有可展示的数据", "No displayable data"),
            ToolRunState.Error => AppLocalizer.T("加载失败", "Load failed"),
            ToolRunState.Canceled => AppLocalizer.T("已取消", "Canceled"),
            _ => AppLocalizer.T("已完成", "Completed")
        };

        var badgeText = state switch
        {
            ToolRunState.Loading => AppLocalizer.T("加载中", "Loading"),
            ToolRunState.Empty => AppLocalizer.T("空结果", "Empty"),
            ToolRunState.Error => AppLocalizer.T("错误", "Error"),
            ToolRunState.Canceled => AppLocalizer.T("取消", "Canceled"),
            _ => AppLocalizer.T("成功", "Success")
        };

        var badgeForeground = state == ToolRunState.Error ? ModernUi.Danger : state == ToolRunState.Loading ? ModernUi.Info : ModernUi.TextSecondary;
        var panel = new StackPanel { Spacing = 10 };
        panel.Children.Add(new StackPanel
        {
            Orientation = Orientation.Horizontal,
            Spacing = 8,
            Children =
            {
                ModernUi.SmallBadge(badgeText, badgeForeground, ModernUi.SurfaceAlt),
                ModernUi.SmallBadge(module.Metadata.OfflineCapable ? AppLocalizer.T("本地处理", "Local") : AppLocalizer.T("联网请求", "Online"), ModernUi.TextSecondary, ModernUi.SurfaceAlt),
                ModernUi.SmallBadge(ResultLabel(_spec.Result), ModernUi.TextSecondary, ModernUi.SurfaceAlt)
            }
        });
        panel.Children.Add(ModernUi.Text(title, 16, FontWeights.SemiBold));
        panel.Children.Add(ModernUi.Text(message, 13, foreground: ModernUi.TextSecondary, maxLines: 3));

        if (durationMs is not null)
        {
            panel.Children.Add(ModernUi.SmallBadge(AppLocalizer.T($"耗时 {durationMs.Value} ms", $"{durationMs.Value} ms"), ModernUi.TextSecondary, ModernUi.SurfaceAlt));
        }

        if (state == ToolRunState.Error)
        {
            panel.Children.Add(ModernUi.PillButton(AppLocalizer.T("重试", "Retry"), "\uE72C", async () => await RunCurrentToolAsync(), primary: true));
        }

        return ModernUi.Card(panel, new Thickness(14), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private static ToolResultDocument AddRuntimeMetadata(ToolResultDocument document, long durationMs, bool cached)
    {
        var metadata = new Dictionary<string, string>(document.Metadata, StringComparer.OrdinalIgnoreCase)
        {
            ["loadedAt"] = DateTimeOffset.Now.ToString("O"),
            ["durationMs"] = durationMs.ToString(CultureInfo.InvariantCulture),
            ["cached"] = cached ? "true" : "false",
            ["state"] = document.Blocks.Count == 0 ? "empty" : "success"
        };

        return document with { Metadata = metadata };
    }

    private Border BuildDocumentSummaryCard(ToolResultDocument document, int rawLineCount)
    {
        var metadata = document.Metadata;
        metadata.TryGetValue("durationMs", out var durationMs);
        metadata.TryGetValue("displayProfile", out var displayProfile);
        metadata.TryGetValue("sourceVisibility", out var sourceVisibility);
        metadata.TryGetValue("loadedAt", out var loadedAt);

        var chips = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
        chips.Children.Add(ModernUi.SmallBadge(AppLocalizer.T($"{document.Blocks.Count} 个结果块", $"{document.Blocks.Count} blocks"), ModernUi.TextSecondary, ModernUi.SurfaceAlt));
        chips.Children.Add(ModernUi.SmallBadge(AppLocalizer.T($"{rawLineCount} 行原始内容", $"{rawLineCount} raw lines"), ModernUi.TextSecondary, ModernUi.SurfaceAlt));
        if (!string.IsNullOrWhiteSpace(durationMs))
        {
            chips.Children.Add(ModernUi.SmallBadge(AppLocalizer.T($"耗时 {durationMs} ms", $"{durationMs} ms"), ModernUi.TextSecondary, ModernUi.SurfaceAlt));
        }
        if (!string.IsNullOrWhiteSpace(displayProfile))
        {
            chips.Children.Add(ModernUi.SmallBadge(displayProfile, ModernUi.Accent, ModernUi.AccentSoft));
        }
        if (!string.IsNullOrWhiteSpace(sourceVisibility))
        {
            chips.Children.Add(ModernUi.SmallBadge(sourceVisibility, ModernUi.TextSecondary, ModernUi.SurfaceAlt));
        }

        var panel = new StackPanel { Spacing = 8 };
        panel.Children.Add(chips);
        panel.Children.Add(ModernUi.Text(AppLocalizer.T("已按内容类型整理为摘要和详情，原始输出可在结果区上方直接切换查看。", "The result is shaped into summary and details; raw output is available from the result toggle."), 13, foreground: ModernUi.TextSecondary, maxLines: 2));
        if (!string.IsNullOrWhiteSpace(loadedAt))
        {
            panel.Children.Add(ModernUi.Text(AppLocalizer.T($"加载时间：{loadedAt}", $"Loaded at: {loadedAt}"), 12, foreground: ModernUi.TextSecondary, maxLines: 1));
        }

        return ModernUi.Card(panel, new Thickness(12), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private bool CanUseToolPageWeb()
        => _useToolPageWebView && _toolPageWebViewReady && _toolPageWebView.CoreWebView2 is not null;

    private void ReportToolPageState(string state, string message, string? error = null, long durationMs = 0)
    {
        var runState = new ToolResultRunState(state, message, error, durationMs);
        _lastToolPageRunState = runState;
        if (state is "running" or "error" or "canceled")
        {
            _lastToolPageDocument = null;
        }

        if (CanUseToolPageWeb())
        {
            _toolPageWebView.CoreWebView2.PostWebMessageAsJson(_toolPageWebBridge.SerializeState(runState));
        }
    }

    private void ReportToolPageResult(ToolResultDocument document, long durationMs)
    {
        var runState = new ToolResultRunState("success", AppLocalizer.T("已完成", "Completed"), DurationMs: durationMs);
        _lastToolPageDocument = document;
        _lastToolPageRunState = runState;
        if (CanUseToolPageWeb())
        {
            _toolPageWebView.CoreWebView2.PostWebMessageAsJson(_toolPageWebBridge.SerializeResult(
                document,
                runState,
                durationMs));
        }
    }

    private async Task RunCurrentToolAsync()
    {
        if (Module is null)
        {
            return;
        }

        var runId = Interlocked.Increment(ref _runSequence);
        CancelRunningTool(invalidateRun: false);
        var cts = new CancellationTokenSource();
        _runCts = cts;
        var stopwatch = Stopwatch.StartNew();
        _viewModel.IsBusy = true;
        _progressRing.Visibility = Visibility.Visible;
        _progressRing.IsActive = true;
        _resultCards.Children.Clear();
        _resultCards.Children.Add(ModernUi.SmallBadge(AppLocalizer.T("正在处理", "Processing"), ModernUi.Info, ModernUi.AccentSoft));
        _resultCards.Children.Add(BuildToolStateCard(
            ToolRunState.Loading,
            Module,
            AppLocalizer.T("工具正在执行，结果会在这里展开为结构化视图。", "The tool is running; results will be expanded into a structured view here.")));
        SetResultViewMode(showRaw: false);
        ReportToolPageState("running", AppLocalizer.T("工具正在执行，结果会在此页展开。", "The tool is running; results will appear on this page."));

        try
        {
            var input = PrepareInput(Module, CurrentInputText());
            var result = await _workerService.ExecuteToolAsync(
                Module,
                input,
                _apiManager,
                _referenceDataService,
                cancellationToken: _runCts.Token,
                language: _settingsService.Current.Language);

            stopwatch.Stop();
            if (runId != _runSequence)
            {
                return;
            }

            if (result.Ok)
            {
                _outputBox.Text = result.Output;
                _lastRawOutput = result.Output;
                _viewModel.Output = result.Output;
                var document = AddRuntimeMetadata(result.Document ?? ToolResultDocument.FromOutput(Module, result.Output), stopwatch.ElapsedMilliseconds, cached: false);
                ReportToolPageResult(document, stopwatch.ElapsedMilliseconds);
                RenderResultDocument(document, stopwatch.ElapsedMilliseconds);
                await WriteResultLogAsync("Information", "render", "success", $"blocks={document.Blocks.Count}; chars={result.Output.Length}; durationMs={stopwatch.ElapsedMilliseconds}");
                ToastService.Show(AppLocalizer.T($"{ToolText.Name(Module)} 已完成", $"{ToolText.Name(Module)} completed"), ToastKind.Success);
            }
            else
            {
                _outputBox.Text = result.Error ?? AppLocalizer.T("未知错误", "Unknown error");
                _lastRawOutput = _outputBox.Text;
                _viewModel.ErrorMessage = _outputBox.Text;
                _resultCards.Children.Clear();
                _resultCards.Children.Add(ModernUi.SmallBadge(AppLocalizer.T("失败", "Failed"), ModernUi.Danger, ModernUi.SurfaceAlt));
                _resultCards.Children.Add(BuildToolStateCard(
                    ToolRunState.Error,
                    Module,
                    AppLocalizer.SanitizeSensitiveText(_outputBox.Text, 220),
                    stopwatch.ElapsedMilliseconds));
                SetResultViewMode(showRaw: false);
                ReportToolPageState(
                    "error",
                    AppLocalizer.SanitizeSensitiveText(_outputBox.Text, 220),
                    AppLocalizer.SanitizeSensitiveText(_outputBox.Text, 220),
                    stopwatch.ElapsedMilliseconds);
                await WriteResultLogAsync("Error", "render", "failed", $"chars={_outputBox.Text.Length}; durationMs={stopwatch.ElapsedMilliseconds}; error={AppLocalizer.SanitizeSensitiveText(_outputBox.Text, 220)}");
                ToastService.Show(AppLocalizer.T($"{ToolText.Name(Module)} 执行失败", $"{ToolText.Name(Module)} failed"), ToastKind.Error);
            }
        }
        catch (OperationCanceledException)
        {
            stopwatch.Stop();
            _resultCards.Children.Clear();
            _resultCards.Children.Add(ModernUi.SmallBadge(AppLocalizer.T("已取消", "Canceled"), ModernUi.TextSecondary, ModernUi.SurfaceAlt));
            _resultCards.Children.Add(BuildToolStateCard(
                ToolRunState.Canceled,
                Module,
                AppLocalizer.T("本次执行已取消，没有写入新的结果。", "This run was canceled, so no new result was written."),
                stopwatch.ElapsedMilliseconds));
            SetResultViewMode(showRaw: false);
            ReportToolPageState(
                "canceled",
                AppLocalizer.T("本次执行已取消，没有写入新的结果。", "This run was canceled, so no new result was written."),
                durationMs: stopwatch.ElapsedMilliseconds);
            await WriteResultLogAsync("Information", "render", "canceled", $"durationMs={stopwatch.ElapsedMilliseconds}");
        }
        finally
        {
            _viewModel.IsBusy = false;
            _progressRing.IsActive = false;
            _progressRing.Visibility = Visibility.Collapsed;
            _runCts?.Dispose();
            _runCts = null;
        }
    }

    private void CancelRunningTool(bool invalidateRun = true)
    {
        if (invalidateRun)
        {
            Interlocked.Increment(ref _runSequence);
        }

        if (_runCts is null)
        {
            return;
        }

        if (!_runCts.IsCancellationRequested)
        {
            _runCts.Cancel();
        }

        _runCts.Dispose();
        _runCts = null;
    }

    private string CurrentInputText()
    {
        if (_spec.PrimaryInput is ToolPrimaryInputKind.Query or ToolPrimaryInputKind.Url)
        {
            return _queryBox.Text;
        }

        if (_spec.PrimaryInput == ToolPrimaryInputKind.DateRange)
        {
            var start = _startDatePicker.Date?.DateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) ?? DateTime.Today.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
            if (Module?.Id == "shelf_life")
            {
                return $"{start} {Math.Max(1, (int)_numberBox.Value)} days";
            }
            if (Module?.Id == "age_calculator")
            {
                return start;
            }

            var end = _endDatePicker.Date?.DateTime.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture) ?? DateTime.Today.AddDays(1).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
            return $"{start} {end}";
        }

        if (_spec.PrimaryInput == ToolPrimaryInputKind.Number)
        {
            return FormatNumber(_numberBox.Value);
        }

        if (_spec.PrimaryInput == ToolPrimaryInputKind.NumberPair)
        {
            return $"{FormatNumber(_numberBox.Value)} {FormatNumber(_secondaryNumberBox.Value)}";
        }

        if (_spec.PrimaryInput == ToolPrimaryInputKind.NumberTriple)
        {
            return $"{FormatNumber(_numberBox.Value)} {FormatNumber(_secondaryNumberBox.Value)} {FormatNumber(_thirdNumberBox.Value)}";
        }

        if (_spec.PrimaryInput == ToolPrimaryInputKind.NumberQuad)
        {
            return $"{FormatNumber(_numberBox.Value)} {FormatNumber(_secondaryNumberBox.Value)} {FormatNumber(_thirdNumberBox.Value)} {FormatNumber(_fourthNumberBox.Value)}";
        }

        if (_parameterBox.Visibility == Visibility.Visible && _parameterBox.SelectedItem is ComboBoxItem item && item.Tag is string input)
        {
            return input;
        }

        return _inputBox.Text;
    }

    private async Task InitializeToolPageWebViewAsync(UIElement nativeFallback, UIElement loadingPlaceholder)
    {
        if (Module is null || _toolPageWebViewReady || _toolPageWebViewFailed)
        {
            return;
        }

        try
        {
            var assetsRoot = AppServices.GetRequiredService<YMhut.Box.Core.App.AppPaths>().Assets;
            var resourceFolder = Path.Combine(assetsRoot, "tool-pages");
            if (!Directory.Exists(resourceFolder))
            {
                resourceFolder = Path.Combine(AppContext.BaseDirectory, "Assets", "tool-pages");
            }

            if (!Directory.Exists(resourceFolder))
            {
                _toolPageWebViewFailed = true;
                loadingPlaceholder.Visibility = Visibility.Collapsed;
                nativeFallback.Visibility = Visibility.Visible;
                return;
            }

            var environment = await _webViewEnvironmentFactory.CreateAsync("ToolPages").ConfigureAwait(true);
            await _toolPageWebView.EnsureCoreWebView2Async(environment);
            _toolPageWebView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
            _toolPageWebView.CoreWebView2.Settings.AreDevToolsEnabled = false;
            _toolPageWebView.CoreWebView2.Settings.AreHostObjectsAllowed = false;
            _toolPageWebView.CoreWebView2.Settings.IsStatusBarEnabled = false;
            _toolPageWebView.CoreWebView2.Settings.IsZoomControlEnabled = false;
            _toolPageWebView.CoreWebView2.WebMessageReceived += async (_, args) => await ToolPageWebView_WebMessageReceived(args);
            _toolPageWebView.CoreWebView2.SetVirtualHostNameToFolderMapping(
                "tool-pages.ymhut.local",
                resourceFolder,
                CoreWebView2HostResourceAccessKind.Allow);
            _toolPageWebView.NavigationCompleted += (_, args) =>
            {
                if (!args.IsSuccess)
                {
                    _toolPageWebViewFailed = true;
                    _useToolPageWebView = false;
                    _toolPageWebView.Visibility = Visibility.Collapsed;
                    loadingPlaceholder.Visibility = Visibility.Collapsed;
                    nativeFallback.Visibility = Visibility.Visible;
                    return;
                }

                _toolPageWebViewReady = true;
                _useToolPageWebView = true;
                _toolPageWebView.Visibility = Visibility.Visible;
                loadingPlaceholder.Visibility = Visibility.Collapsed;
                nativeFallback.Visibility = Visibility.Collapsed;
                SendToolPagePayload();
                SendPerformanceMode();
            };
            _toolPageWebView.Source = new Uri("https://tool-pages.ymhut.local/index.html");
        }
        catch (Exception exception)
        {
            _toolPageWebViewFailed = true;
            _useToolPageWebView = false;
            CrashLog.Write(exception);
            loadingPlaceholder.Visibility = Visibility.Collapsed;
            nativeFallback.Visibility = Visibility.Visible;
            _toolPageWebView.Visibility = Visibility.Collapsed;
            _ = InitializeResultWebViewAsync();
        }
    }

    private async Task ToolPageWebView_WebMessageReceived(CoreWebView2WebMessageReceivedEventArgs args)
    {
        if (Module is null)
        {
            return;
        }

        try
        {
            var message = ToolPageWebBridge.ParseMessage(args);
            switch (message.Action)
            {
                case "run":
                    ApplyWebInput(message.Value, message.Payload);
                    await RunCurrentToolAsync();
                    break;
                case "clear":
                    ClearInput();
                    _lastToolPageDocument = null;
                    _lastToolPageRunState = null;
                    SendToolPagePayload();
                    break;
                case "chooseFile":
                case "copy":
                case "copyResult":
                case "openFile":
                case "openFolder":
                case "openLink":
                case "openSystemBrowser":
                    var selected = await _toolPageWebBridge.HandleSystemActionAsync(message, Module, _lastRawOutput, FileTypeFilters(Module.Id).ToArray());
                    if (!string.IsNullOrWhiteSpace(selected))
                    {
                        _inputBox.Text = selected;
                        _queryBox.Text = selected;
                        _toolPageWebView.CoreWebView2?.PostWebMessageAsJson(JsonSerializer.Serialize(new { type = "fileSelected", path = selected }, new JsonSerializerOptions(JsonSerializerDefaults.Web)));
                    }
                    break;
            }
        }
        catch (Exception exception)
        {
            CrashLog.Write(exception);
        }
    }

    private void SendToolPagePayload(ToolResultDocument? result = null, ToolResultRunState? runState = null)
    {
        if (Module is null || !_toolPageWebViewReady || _toolPageWebView.CoreWebView2 is null)
        {
            return;
        }

        var payload = _toolPageWebBridge.CreatePayload(
            Module,
            _spec,
            BuildToolInputState(Module),
            result ?? _lastToolPageDocument,
            runState ?? _lastToolPageRunState);
        _toolPageWebView.CoreWebView2.PostWebMessageAsJson(_toolPageWebBridge.Serialize(payload));
    }

    private ToolInputState BuildToolInputState(IToolModule module)
    {
        var fields = new List<ToolInputField>();
        var rules = new Dictionary<string, string>(_selectedRuleValues, StringComparer.OrdinalIgnoreCase);
        var metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["hint"] = InputHint(module),
            ["inputTitle"] = InputTitle(module),
            ["inputDescription"] = InputDescription(module)
        };

        if (_parameterBox.Visibility == Visibility.Visible)
        {
            fields.Add(new ToolInputField(
                "preset",
                AppLocalizer.T("预设", "Preset"),
                "select",
                _parameterBox.SelectedItem is ComboBoxItem selected && selected.Tag is string tag ? tag : string.Empty,
                AppLocalizer.T("选择预设", "Choose preset"),
                Options: PrimaryParameterOptions(module).Select(option => new ToolInputOption(option.Label, option.Input)).ToArray()));
        }

        if (_spec.PrimaryInput is ToolPrimaryInputKind.Query or ToolPrimaryInputKind.Url)
        {
            fields.Add(new ToolInputField("query", InputTitle(module), _spec.PrimaryInput == ToolPrimaryInputKind.Url ? "url" : "text", _queryBox.Text, InputHint(module)));
        }
        else if (_spec.PrimaryInput == ToolPrimaryInputKind.FilePath)
        {
            fields.Add(new ToolInputField("path", InputTitle(module), "file", _inputBox.Text, InputHint(module)));
        }
        else if (_spec.PrimaryInput == ToolPrimaryInputKind.DateRange)
        {
            fields.Add(new ToolInputField("start", AppLocalizer.T("开始日期", "Start date"), "date", (_startDatePicker.Date ?? DateTimeOffset.Now).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)));
            if (_endDatePicker.Visibility == Visibility.Visible)
            {
                fields.Add(new ToolInputField("end", AppLocalizer.T("结束日期", "End date"), "date", (_endDatePicker.Date ?? DateTimeOffset.Now.AddDays(1)).ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)));
            }
            if (module.Id == "shelf_life")
            {
                fields.Add(NumberField("value", _numberBox));
            }
        }
        else if (UsesNumericPrimaryInput())
        {
            fields.Add(NumberField("valueA", _numberBox));
            if (_secondaryNumberBox.Visibility == Visibility.Visible) fields.Add(NumberField("valueB", _secondaryNumberBox));
            if (_thirdNumberBox.Visibility == Visibility.Visible) fields.Add(NumberField("valueC", _thirdNumberBox));
            if (_fourthNumberBox.Visibility == Visibility.Visible) fields.Add(NumberField("valueD", _fourthNumberBox));
        }
        else if (_spec.PrimaryInput != ToolPrimaryInputKind.None && _spec.PrimaryInput != ToolPrimaryInputKind.FixedOptions)
        {
            var kind = _spec.PrimaryInput switch
            {
                ToolPrimaryInputKind.MultilineText => "textarea",
                ToolPrimaryInputKind.Token => "token",
                ToolPrimaryInputKind.Color => "color",
                _ => "text"
            };
            fields.Add(new ToolInputField("input", InputTitle(module), kind, _inputBox.Text, InputHint(module)));
        }

        if (UsesSupplementalNumber(module) && fields.All(field => field.Key != "value"))
        {
            fields.Add(NumberField("optionNumber", _numberBox));
        }

        if (_auxBox.Visibility == Visibility.Visible)
        {
            fields.Add(new ToolInputField("aux", AppLocalizer.T("附加参数", "Extra parameter"), "text", _auxBox.Text, _auxBox.PlaceholderText));
        }

        return new ToolInputState(CurrentInputText(), fields, rules, metadata);
    }

    private static ToolInputField NumberField(string key, NumberBox box)
        => new(key, box.Header?.ToString() ?? key, "number", FormatNumber(box.Value), Minimum: box.Minimum, Maximum: box.Maximum);

    private void ApplyWebInput(string? input, JsonElement? payload)
    {
        if (payload is { ValueKind: JsonValueKind.Object } root)
        {
            if (root.TryGetProperty("fields", out var fields) && fields.ValueKind == JsonValueKind.Array)
            {
                ApplyWebFields(fields);
            }
            if (root.TryGetProperty("rules", out var rules) && rules.ValueKind == JsonValueKind.Object)
            {
                _selectedRuleValues.Clear();
                foreach (var rule in rules.EnumerateObject())
                {
                    _selectedRuleValues[rule.Name] = rule.Value.GetString() ?? string.Empty;
                }
            }
        }

        if (!string.IsNullOrWhiteSpace(input))
        {
            if (_spec.PrimaryInput is ToolPrimaryInputKind.Query or ToolPrimaryInputKind.Url)
            {
                _queryBox.Text = input;
            }
            else if (!UsesNumericPrimaryInput() && _spec.PrimaryInput != ToolPrimaryInputKind.DateRange)
            {
                _inputBox.Text = input;
            }
        }
    }

    private void ApplyWebFields(JsonElement fields)
    {
        foreach (var field in fields.EnumerateArray())
        {
            var key = field.TryGetProperty("key", out var keyElement) ? keyElement.GetString() : string.Empty;
            var value = field.TryGetProperty("value", out var valueElement) ? valueElement.GetString() ?? string.Empty : string.Empty;
            switch (key)
            {
                case "query":
                    _queryBox.Text = value;
                    break;
                case "path":
                case "input":
                    _inputBox.Text = value;
                    break;
                case "aux":
                    _auxBox.Text = value;
                    break;
                case "preset":
                    SelectParameter(value);
                    break;
                case "start":
                    if (DateTimeOffset.TryParse(value, out var start)) _startDatePicker.Date = start;
                    break;
                case "end":
                    if (DateTimeOffset.TryParse(value, out var end)) _endDatePicker.Date = end;
                    break;
                case "value":
                case "valueA":
                    SetNumberValue(_numberBox, value);
                    break;
                case "optionNumber":
                    SetNumberValue(_numberBox, value);
                    break;
                case "valueB":
                    SetNumberValue(_secondaryNumberBox, value);
                    break;
                case "valueC":
                    SetNumberValue(_thirdNumberBox, value);
                    break;
                case "valueD":
                    SetNumberValue(_fourthNumberBox, value);
                    break;
            }
        }
    }

    private static void SetNumberValue(NumberBox box, string value)
    {
        if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var parsed) ||
            double.TryParse(value, NumberStyles.Float, CultureInfo.CurrentCulture, out parsed))
        {
            box.Value = Math.Clamp(parsed, box.Minimum, box.Maximum);
        }
    }

    private async Task InitializeResultWebViewAsync()
    {
        try
        {
            var assetsRoot = AppServices.GetRequiredService<YMhut.Box.Core.App.AppPaths>().Assets;
            var resourceFolder = Path.Combine(assetsRoot, "tool-results");
            if (!Directory.Exists(resourceFolder))
            {
                resourceFolder = Path.Combine(AppContext.BaseDirectory, "Assets", "tool-results");
            }

            var environment = await _webViewEnvironmentFactory.CreateAsync("ToolResults").ConfigureAwait(true);
            await _resultWebView.EnsureCoreWebView2Async(environment);
            _resultWebView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
            _resultWebView.CoreWebView2.Settings.AreDevToolsEnabled = false;
            _resultWebView.CoreWebView2.Settings.AreHostObjectsAllowed = false;
            _resultWebView.CoreWebView2.Settings.IsStatusBarEnabled = false;
            _resultWebView.CoreWebView2.Settings.IsZoomControlEnabled = false;
            _resultWebView.CoreWebView2.WebMessageReceived += async (_, args) => await _toolResultWebBridge.HandleMessageAsync(args);
            _resultWebView.CoreWebView2.SetVirtualHostNameToFolderMapping(
                "tool-results.ymhut.local",
                resourceFolder,
                CoreWebView2HostResourceAccessKind.Allow);
            _resultWebView.NavigationCompleted += (_, args) =>
            {
                if (args.IsSuccess)
                {
                    SendPerformanceMode();
                }
            };
            _resultWebView.Source = new Uri("https://tool-results.ymhut.local/index.html");
            _resultWebViewReady = true;
            SendPerformanceMode();
        }
        catch (Exception exception)
        {
            _resultWebViewFailed = true;
            CrashLog.Write(exception);
        }
    }

    private bool TryRenderResultWebView(ToolResultDocument document, long durationMs)
    {
        if (Module is null || _resultWebViewFailed || !_resultWebViewReady || _resultWebView.CoreWebView2 is null)
        {
            return false;
        }

        try
        {
            _resultCards.Children.Clear();
            var payload = _toolResultWebBridge.CreatePayload(Module, document, durationMs);
            _resultWebView.CoreWebView2.PostWebMessageAsJson(_toolResultWebBridge.Serialize(payload));
            SendPerformanceMode();
            return true;
        }
        catch (Exception exception)
        {
            _resultWebViewFailed = true;
            CrashLog.Write(exception);
            return false;
        }
    }

    private void RenderResultDocument(ToolResultDocument document, long durationMs = 0)
    {
        if (TryRenderResultWebView(document, durationMs))
        {
            SetResultViewMode(showRaw: false);
            UpdateResultSearchStatus();
            return;
        }

        RenderNativeResultDocument(document);
    }

    private void RenderNativeResultDocument(ToolResultDocument document)
    {
        _resultCards.Children.Clear();
        var output = document.RawText;
        var lines = output.Replace("\r\n", "\n").Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

        _resultCards.Children.Add(BuildDocumentSummaryCard(document, lines.Length));
        _resultCards.Children.Add(ModernUi.SmallBadge(
            AppLocalizer.T($"结构化渲染 / {document.Blocks.Count} 个结果块 / {lines.Length} 行", $"Structured rendering / {document.Blocks.Count} result blocks / {lines.Length} lines"),
            ModernUi.TextSecondary,
            ModernUi.SurfaceAlt));

        if (document.Blocks.Count == 0)
        {
            try
            {
                var markdown = ToolMarkdownConverter.FromResult(Module!, new ToolExecutionResult(true, output, null, document), _settingsService.Current.Language);
                RenderMarkdownResult(markdown);
            }
            catch
            {
                _resultCards.Children.Add(BuildLineResultCard(AppLocalizer.T("暂无可展示的结果。", "There is no result to display.")));
            }
            SetResultViewMode(showRaw: false);
            UpdateResultSearchStatus();
            return;
        }

        var maxBlocks = _spec.UseVirtualizedResults ? 120 : 36;
        foreach (var block in document.Blocks.Take(maxBlocks))
        {
            _resultCards.Children.Add(BuildResultBlock(block));
        }
        if (document.Blocks.Count > maxBlocks)
        {
            _resultCards.Children.Add(ModernUi.Card(
                ModernUi.Text(AppLocalizer.T($"已渲染前 {maxBlocks} 个结果块，其余内容可切换到“原始输出”查看。", $"Rendered the first {maxBlocks} result blocks; switch to Raw output for the rest."), 13, foreground: ModernUi.TextSecondary),
                new Thickness(12),
                radius: 8,
                background: ModernUi.SurfaceAlt));
        }

        SetResultViewMode(showRaw: false);
        UpdateResultSearchStatus();
    }

    private UIElement BuildResultBlock(ToolResultBlock block)
    {
        return block.Kind switch
        {
            ToolResultBlockKind.KeyValue => BuildInfoGrid(block.Pairs.Select(pair => (pair.Key, pair.Value)).ToArray()),
            ToolResultBlockKind.Metric => BuildMetricCard(block),
            ToolResultBlockKind.LineChart => BuildLineChartCard(block),
            ToolResultBlockKind.Table => BuildTableCard(block),
            ToolResultBlockKind.CardList => BuildCardListBlock(block),
            ToolResultBlockKind.Timeline => BuildTimelineBlock(block),
            ToolResultBlockKind.RankedList or ToolResultBlockKind.NewsList or ToolResultBlockKind.Text or ToolResultBlockKind.Diff or ToolResultBlockKind.Status => BuildListBlockCard(block),
            ToolResultBlockKind.Image => BuildImageResultCard(block),
            ToolResultBlockKind.Media => BuildMediaResultCard(block),
            ToolResultBlockKind.Link => BuildLinkResultCard(block.Uri),
            ToolResultBlockKind.File => BuildFileResultCard(block),
            ToolResultBlockKind.Code or ToolResultBlockKind.Json or ToolResultBlockKind.JsonTree or ToolResultBlockKind.Raw => BuildCodeResultCard(block),
            ToolResultBlockKind.Color => BuildColorResultCard(block),
            _ => BuildLineResultCard(block.Text)
        };
    }

    private static Border BuildTableCard(ToolResultBlock block)
    {
        var panel = new StackPanel { Spacing = 8 };
        panel.Children.Add(ModernUi.Text(LocalTitle(block.Title, "表格", "Table"), 14, FontWeights.SemiBold, ModernUi.TextSecondary));
        var rows = block.Rows.Take(80).ToArray();
        var columnCount = rows.Length == 0 ? 0 : Math.Min(6, rows.Max(row => row.Length));
        var grid = new Grid { RowSpacing = 1, ColumnSpacing = 1, MinWidth = Math.Max(360, columnCount * 132) };
        for (var column = 0; column < columnCount; column++)
        {
            grid.ColumnDefinitions.Add(new ColumnDefinition { MinWidth = 120 });
        }

        for (var rowIndex = 0; rowIndex < rows.Length; rowIndex++)
        {
            grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
            for (var column = 0; column < columnCount; column++)
            {
                var text = column < rows[rowIndex].Length ? rows[rowIndex][column] : string.Empty;
                var cell = new Border
                {
                    Padding = new Thickness(10, 7, 10, 7),
                    Background = rowIndex == 0 ? ModernUi.AccentSoft : ModernUi.SurfaceAlt,
                    Child = ModernUi.Text(ModernUi.Ellipsize(text, 80), 13, rowIndex == 0 ? FontWeights.SemiBold : FontWeights.Normal, maxLines: 2)
                };
                Grid.SetRow(cell, rowIndex);
                Grid.SetColumn(cell, column);
                grid.Children.Add(cell);
            }
        }

        panel.Children.Add(new ScrollViewer
        {
            HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
            VerticalScrollBarVisibility = ScrollBarVisibility.Disabled,
            Content = grid
        });
        return ModernUi.Card(panel, new Thickness(10), radius: 8, background: ModernUi.Surface);
    }

    private static Border BuildMetricCard(ToolResultBlock block)
    {
        var panel = new StackPanel { Spacing = 10 };
        panel.Children.Add(ModernUi.Text(LocalTitle(block.Title, "摘要", "Summary"), 14, FontWeights.SemiBold, ModernUi.TextSecondary));
        var wrap = new VariableSizedWrapGrid
        {
            Orientation = Orientation.Horizontal,
            ItemWidth = 180,
            ItemHeight = 82
        };
        foreach (var pair in block.Pairs.Take(24))
        {
            wrap.Children.Add(ModernUi.Card(new StackPanel
            {
                Spacing = 4,
                Children =
                {
                    ModernUi.Text(pair.Key, 12, FontWeights.SemiBold, ModernUi.TextSecondary, maxLines: 1),
                    ModernUi.Text(pair.Value, 15, FontWeights.SemiBold, maxLines: 2)
                }
            }, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt));
        }
        panel.Children.Add(wrap);
        return ModernUi.Card(panel, new Thickness(10), radius: 8, background: ModernUi.Surface);
    }

    private static Border BuildLineChartCard(ToolResultBlock block)
    {
        var points = ExtractLineChartPoints(block).ToArray();
        if (points.Length < 2)
        {
            return BuildTableCard(block);
        }

        var panel = new StackPanel { Spacing = 10 };
        var unit = block.Metadata is not null && block.Metadata.TryGetValue("chartUnit", out var chartUnit)
            ? chartUnit
            : string.Empty;
        var header = new Grid
        {
            ColumnDefinitions =
            {
                new ColumnDefinition(),
                new ColumnDefinition { Width = GridLength.Auto }
            }
        };
        header.Children.Add(ModernUi.Text(LocalTitle(block.Title, "趋势", "Trend"), 14, FontWeights.SemiBold, ModernUi.TextSecondary));
        var unitBadge = ModernUi.SmallBadge(string.IsNullOrWhiteSpace(unit) ? AppLocalizer.T("实时数据", "Live data") : unit, ModernUi.TextSecondary, ModernUi.SurfaceAlt);
        Grid.SetColumn(unitBadge, 1);
        header.Children.Add(unitBadge);
        panel.Children.Add(header);

        var canvas = new Canvas
        {
            MinHeight = 224,
            Height = 224,
            Background = ModernUi.SurfaceAlt
        };
        canvas.SizeChanged += (_, e) => RenderLineChart(canvas, points, e.NewSize.Width, e.NewSize.Height);
        canvas.Loaded += (_, _) => RenderLineChart(canvas, points, Math.Max(360, canvas.ActualWidth), canvas.ActualHeight > 0 ? canvas.ActualHeight : 224);

        panel.Children.Add(new Border
        {
            CornerRadius = new CornerRadius(8),
            BorderBrush = ModernUi.Stroke,
            BorderThickness = new Thickness(1),
            Background = ModernUi.SurfaceAlt,
            Child = canvas
        });
        return ModernUi.Card(panel, new Thickness(10), radius: 8, background: ModernUi.Surface);
    }

    private static IEnumerable<(string Label, double Value)> ExtractLineChartPoints(ToolResultBlock block)
    {
        foreach (var row in block.Rows.Skip(1))
        {
            if (row.Length >= 2 && TryParseChartDouble(row[1], out var value))
            {
                yield return (row[0], value);
            }
        }
    }

    private static bool TryParseChartDouble(string value, out double number)
    {
        var cleaned = Regex.Replace(value ?? string.Empty, @"[^\d\.\,\-]", string.Empty).Replace(",", string.Empty, StringComparison.Ordinal);
        return double.TryParse(cleaned, NumberStyles.Float, CultureInfo.InvariantCulture, out number) ||
            double.TryParse(cleaned, NumberStyles.Float, CultureInfo.CurrentCulture, out number);
    }

    private static void RenderLineChart(Canvas canvas, IReadOnlyList<(string Label, double Value)> points, double width, double height)
    {
        canvas.Children.Clear();
        if (points.Count < 2 || width <= 0 || height <= 0)
        {
            return;
        }

        var left = 52d;
        var right = 18d;
        var top = 16d;
        var bottom = 34d;
        var plotWidth = Math.Max(120, width - left - right);
        var plotHeight = Math.Max(96, height - top - bottom);
        var min = points.Min(point => point.Value);
        var max = points.Max(point => point.Value);
        if (Math.Abs(max - min) < 0.0001)
        {
            max += 1;
            min -= 1;
        }

        for (var i = 0; i <= 4; i++)
        {
            var y = top + plotHeight * i / 4d;
            canvas.Children.Add(new Microsoft.UI.Xaml.Shapes.Line
            {
                X1 = left,
                X2 = left + plotWidth,
                Y1 = y,
                Y2 = y,
                Stroke = ModernUi.Stroke,
                StrokeThickness = 1,
                Opacity = 0.46
            });

            var value = max - (max - min) * i / 4d;
            var label = ModernUi.Text(value.ToString("0.##", CultureInfo.CurrentCulture), 10, foreground: ModernUi.TextSecondary, maxLines: 1);
            Canvas.SetLeft(label, 6);
            Canvas.SetTop(label, y - 9);
            canvas.Children.Add(label);
        }

        for (var i = 0; i <= 5; i++)
        {
            var x = left + plotWidth * i / 5d;
            canvas.Children.Add(new Microsoft.UI.Xaml.Shapes.Line
            {
                X1 = x,
                X2 = x,
                Y1 = top,
                Y2 = top + plotHeight,
                Stroke = ModernUi.Stroke,
                StrokeThickness = 1,
                Opacity = 0.22
            });
        }

        var polyline = new Microsoft.UI.Xaml.Shapes.Polyline
        {
            Stroke = ModernUi.Accent,
            StrokeThickness = 2.4,
            StrokeLineJoin = PenLineJoin.Round,
            Points = new PointCollection()
        };

        for (var i = 0; i < points.Count; i++)
        {
            var point = points[i];
            var x = left + plotWidth * i / (points.Count - 1);
            var y = top + plotHeight - ((point.Value - min) / (max - min) * plotHeight);
            polyline.Points.Add(new Windows.Foundation.Point(x, y));
        }

        canvas.Children.Add(polyline);
        var last = polyline.Points[^1];
        var marker = new Microsoft.UI.Xaml.Shapes.Ellipse
        {
            Width = 7,
            Height = 7,
            Fill = ModernUi.Accent,
            Stroke = ModernUi.Surface,
            StrokeThickness = 1
        };
        Canvas.SetLeft(marker, last.X - 3.5);
        Canvas.SetTop(marker, last.Y - 3.5);
        canvas.Children.Add(marker);

        var firstLabel = ModernUi.Text(ModernUi.Ellipsize(points[0].Label, 18), 10, foreground: ModernUi.TextSecondary, maxLines: 1);
        Canvas.SetLeft(firstLabel, left);
        Canvas.SetTop(firstLabel, top + plotHeight + 10);
        canvas.Children.Add(firstLabel);

        var lastLabel = ModernUi.Text(ModernUi.Ellipsize(points[^1].Label, 18), 10, foreground: ModernUi.TextSecondary, maxLines: 1);
        Canvas.SetLeft(lastLabel, Math.Max(left, left + plotWidth - 82));
        Canvas.SetTop(lastLabel, top + plotHeight + 10);
        canvas.Children.Add(lastLabel);
    }

    private static Border BuildCardListBlock(ToolResultBlock block)
    {
        var panel = new StackPanel { Spacing = 10 };
        panel.Children.Add(ModernUi.Text(LocalTitle(block.Title, "卡片列表", "Cards"), 14, FontWeights.SemiBold, ModernUi.TextSecondary));
        foreach (var item in block.Items.Take(60))
        {
            var title = string.IsNullOrWhiteSpace(item.Leading) ? item.Title : $"{item.Leading}. {item.Title}";
            var cardContent = new StackPanel
            {
                Spacing = 5,
                Children =
                {
                    ModernUi.Text(ModernUi.Ellipsize(title, 180), 14, FontWeights.SemiBold, maxLines: 2),
                    ModernUi.Text(ModernUi.Ellipsize(item.Subtitle, 240), 12, foreground: ModernUi.TextSecondary, maxLines: 3)
                }
            };
            if (Uri.TryCreate(item.Uri, UriKind.Absolute, out var uri))
            {
                cardContent.Children.Add(new HyperlinkButton
                {
                    Content = AppLocalizer.T("打开来源", "Open source"),
                    NavigateUri = uri,
                    Padding = new Thickness(0)
                });
            }

            panel.Children.Add(ModernUi.Card(cardContent, new Thickness(12), radius: 8, background: ModernUi.SurfaceAlt));
        }
        return ModernUi.Card(panel, new Thickness(10), radius: 8, background: ModernUi.Surface);
    }

    private static Border BuildTimelineBlock(ToolResultBlock block)
    {
        var panel = new StackPanel { Spacing = 8 };
        panel.Children.Add(ModernUi.Text(LocalTitle(block.Title, "时间线", "Timeline"), 14, FontWeights.SemiBold, ModernUi.TextSecondary));
        foreach (var item in block.Items.Take(40))
        {
            var row = new Grid { ColumnSpacing = 10 };
            row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
            row.ColumnDefinitions.Add(new ColumnDefinition());
            row.Children.Add(new Border
            {
                Width = 9,
                Height = 9,
                CornerRadius = new CornerRadius(5),
                Background = StatusBrush(item.Status),
                VerticalAlignment = VerticalAlignment.Center
            });
            var text = new StackPanel
            {
                Spacing = 2,
                Children =
                {
                    ModernUi.Text(item.Title, 13, FontWeights.SemiBold, maxLines: 2),
                    ModernUi.Text(item.Subtitle, 12, foreground: ModernUi.TextSecondary, maxLines: 2)
                }
            };
            Grid.SetColumn(text, 1);
            row.Children.Add(text);
            panel.Children.Add(row);
        }
        return ModernUi.Card(panel, new Thickness(12), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private static Border BuildListBlockCard(ToolResultBlock block)
    {
        var panel = new StackPanel { Spacing = 8 };
        panel.Children.Add(ModernUi.Text(LocalTitle(block.Title, "列表", "List"), 14, FontWeights.SemiBold, ModernUi.TextSecondary));
        foreach (var item in block.Items.Take(80))
        {
            var row = new Grid { ColumnSpacing = 10 };
            row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
            row.ColumnDefinitions.Add(new ColumnDefinition());
            var markerText = string.IsNullOrWhiteSpace(item.Leading) ? "•" : item.Leading;
            row.Children.Add(ModernUi.SmallBadge(markerText, StatusBrush(item.Status), ModernUi.Surface));
            var text = new StackPanel
            {
                Spacing = 2,
                Children =
                {
                    ModernUi.Text(ModernUi.Ellipsize(item.Title, 180), 13, FontWeights.SemiBold, maxLines: 2)
                }
            };
            if (!string.IsNullOrWhiteSpace(item.Subtitle))
            {
                text.Children.Add(ModernUi.Text(ModernUi.Ellipsize(item.Subtitle, 180), 12, foreground: ModernUi.TextSecondary, maxLines: 2));
            }

            Grid.SetColumn(text, 1);
            row.Children.Add(text);
            panel.Children.Add(ModernUi.Card(row, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt));
        }

        return ModernUi.Card(panel, new Thickness(10), radius: 8, background: ModernUi.Surface);
    }

    private static Border BuildMediaResultCard(ToolResultBlock block)
    {
        var row = new Grid { ColumnSpacing = 10 };
        row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        row.ColumnDefinitions.Add(new ColumnDefinition());
        row.Children.Add(ModernUi.IconTile("\uE714", 38, ModernUi.AccentSoft, ModernUi.Accent, 17));
        var text = new StackPanel
        {
            Spacing = 2,
            Children =
            {
                ModernUi.Text(AppLocalizer.T("媒体引用", "Media reference"), 13, FontWeights.SemiBold),
                ModernUi.Text(ModernUi.Ellipsize(block.Text, 110), 12, foreground: ModernUi.TextSecondary, maxLines: 2)
            }
        };
        Grid.SetColumn(text, 1);
        row.Children.Add(text);
        return ModernUi.Card(row, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private static Border BuildFileResultCard(ToolResultBlock block)
    {
        var row = new Grid { ColumnSpacing = 10 };
        row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        row.ColumnDefinitions.Add(new ColumnDefinition());
        row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        row.Children.Add(ModernUi.IconTile("\uE8A5", 38, ModernUi.AccentSoft, ModernUi.Accent, 17));
        var text = new StackPanel
        {
            Spacing = 2,
            Children =
            {
                ModernUi.Text(AppLocalizer.T("文件结果", "File result"), 13, FontWeights.SemiBold),
                ModernUi.Text(ModernUi.Ellipsize(block.Path, 110), 12, foreground: ModernUi.TextSecondary, maxLines: 2)
            }
        };
        Grid.SetColumn(text, 1);
        row.Children.Add(text);
        var open = new Button
        {
            Content = AppLocalizer.T("打开", "Open"),
            Padding = new Thickness(10, 4, 10, 5),
            CornerRadius = new CornerRadius(6),
            BorderBrush = ModernUi.Stroke,
            Background = ModernUi.SurfaceAlt
        };
        open.Click += async (_, _) =>
        {
            if (Uri.TryCreate(block.Path, UriKind.Absolute, out var uri))
            {
                if (uri.Scheme is "http" or "https")
                {
                    await AppServices.GetRequiredService<IToolLinkNavigationService>().OpenAsync(uri.AbsoluteUri);
                }
                else
                {
                    await Launcher.LaunchUriAsync(uri);
                }
            }
        };
        Grid.SetColumn(open, 2);
        row.Children.Add(open);
        return ModernUi.Card(row, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private static Border BuildCodeResultCard(ToolResultBlock block)
    {
        if (TryBuildJsonResultCard(LocalTitle(block.Title, "JSON", "JSON"), block.Text, out var jsonCard))
        {
            return jsonCard;
        }

        var text = new TextBox
        {
            Text = block.Text,
            IsReadOnly = true,
            AcceptsReturn = true,
            TextWrapping = TextWrapping.NoWrap,
            FontFamily = new FontFamily("Cascadia Mono"),
            MinHeight = 320,
            MaxHeight = 680
        };
        return ModernUi.Card(new StackPanel
        {
            Spacing = 10,
            Children =
            {
                ModernUi.Text(LocalTitle(block.Title, block.Kind == ToolResultBlockKind.Json ? "JSON" : "代码", block.Kind == ToolResultBlockKind.Json ? "JSON" : "Code"), 14, FontWeights.SemiBold, ModernUi.TextSecondary),
                text
            }
        }, new Thickness(10), radius: 8, background: ModernUi.Surface);
    }

    private static bool TryBuildJsonResultCard(string title, string json, out Border card)
    {
        card = null!;
        var trimmed = json.TrimStart();
        if (!trimmed.StartsWith('{') && !trimmed.StartsWith('['))
        {
            return false;
        }

        try
        {
            using var document = JsonDocument.Parse(json);
            var root = document.RootElement;
            var pretty = JsonSerializer.Serialize(root, new JsonSerializerOptions { WriteIndented = true });
            var nodeCount = 0;
            var tree = new TreeView
            {
                SelectionMode = TreeViewSelectionMode.None,
                MinHeight = 260,
                MaxHeight = 520
            };
            tree.RootNodes.Add(BuildJsonTreeNode("$", root, depth: 0, ref nodeCount));

            var rows = new List<(string Key, string Value)>
            {
                (AppLocalizer.T("类型", "Type"), root.ValueKind == JsonValueKind.Array ? "Array" : "Object"),
                (AppLocalizer.T("字符数", "Characters"), json.Length.ToString(CultureInfo.InvariantCulture)),
                (AppLocalizer.T("树节点", "Tree nodes"), nodeCount.ToString(CultureInfo.InvariantCulture))
            };
            if (root.ValueKind == JsonValueKind.Array)
            {
                rows.Insert(1, (AppLocalizer.T("项目数", "Items"), root.GetArrayLength().ToString(CultureInfo.InvariantCulture)));
            }
            else if (root.ValueKind == JsonValueKind.Object)
            {
                rows.Insert(1, (AppLocalizer.T("字段数", "Fields"), root.EnumerateObject().Count().ToString(CultureInfo.InvariantCulture)));
            }

            var prettyBox = new TextBox
            {
                Text = pretty,
                IsReadOnly = true,
                AcceptsReturn = true,
                TextWrapping = TextWrapping.NoWrap,
                FontFamily = new FontFamily("Cascadia Mono"),
                MinHeight = 260,
                MaxHeight = 560
            };

            var panel = new StackPanel
            {
                Spacing = 12,
                Children =
                {
                    ModernUi.Text(title, 15, FontWeights.SemiBold, ModernUi.TextSecondary),
                    BuildInfoGrid(rows),
                    ModernUi.Card(new StackPanel
                    {
                        Spacing = 8,
                        Children =
                        {
                            ModernUi.Text(AppLocalizer.T("JSON 树", "JSON tree"), 13, FontWeights.SemiBold, ModernUi.TextSecondary),
                            new ScrollViewer
                            {
                                VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
                                HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
                                MaxHeight = 540,
                                Content = tree
                            }
                        }
                    }, new Thickness(12), radius: 8, background: ModernUi.SurfaceAlt),
                    new Expander
                    {
                        Header = ModernUi.Text(AppLocalizer.T("Pretty / Raw JSON", "Pretty / Raw JSON"), 13, FontWeights.SemiBold, ModernUi.TextSecondary),
                        IsExpanded = true,
                        Content = prettyBox
                    }
                }
            };
            card = ModernUi.Card(panel, new Thickness(12), radius: 8, background: ModernUi.Surface);
            return true;
        }
        catch (JsonException)
        {
            return false;
        }
    }

    private static TreeViewNode BuildJsonTreeNode(string name, JsonElement element, int depth, ref int nodeCount)
    {
        const int MaxDepth = 8;
        const int MaxNodes = 600;
        nodeCount++;
        var node = new TreeViewNode
        {
            Content = BuildJsonNodeContent(name, element),
            IsExpanded = depth < 2
        };

        if (depth >= MaxDepth || nodeCount >= MaxNodes)
        {
            if (element.ValueKind is JsonValueKind.Array or JsonValueKind.Object)
            {
                node.Children.Add(new TreeViewNode
                {
                    Content = ModernUi.Text(AppLocalizer.T("内容较大，已折叠到 Raw JSON。", "Large content; inspect Raw JSON for the rest."), 12, foreground: ModernUi.TextSecondary)
                });
            }

            return node;
        }

        if (element.ValueKind == JsonValueKind.Object)
        {
            foreach (var property in element.EnumerateObject())
            {
                if (nodeCount >= MaxNodes)
                {
                    AddJsonLimitNode(node);
                    break;
                }

                node.Children.Add(BuildJsonTreeNode(property.Name, property.Value, depth + 1, ref nodeCount));
            }
        }
        else if (element.ValueKind == JsonValueKind.Array)
        {
            var index = 0;
            foreach (var item in element.EnumerateArray())
            {
                if (nodeCount >= MaxNodes)
                {
                    AddJsonLimitNode(node);
                    break;
                }

                node.Children.Add(BuildJsonTreeNode($"[{index++}]", item, depth + 1, ref nodeCount));
            }
        }

        return node;
    }

    private static UIElement BuildJsonNodeContent(string name, JsonElement element)
    {
        var value = element.ValueKind is JsonValueKind.Object or JsonValueKind.Array
            ? JsonPreviewValue(element)
            : element.ValueKind.ToString() + ": " + JsonPreviewValue(element);
        return new StackPanel
        {
            Orientation = Orientation.Horizontal,
            Spacing = 8,
            Children =
            {
                ModernUi.SmallBadge(element.ValueKind.ToString(), ModernUi.Accent, ModernUi.AccentSoft),
                ModernUi.Text(name, 13, FontWeights.SemiBold, maxLines: 1),
                ModernUi.Text(ModernUi.Ellipsize(value, 180), 12, foreground: ModernUi.TextSecondary, maxLines: 1)
            }
        };
    }

    private static void AddJsonLimitNode(TreeViewNode node)
    {
        node.Children.Add(new TreeViewNode
        {
            Content = ModernUi.Text(AppLocalizer.T("节点过多，已省略其余内容。", "Too many nodes; remaining content omitted."), 12, foreground: ModernUi.TextSecondary)
        });
    }

    private static Border BuildColorResultCard(ToolResultBlock block)
    {
        return ModernUi.Card(new StackPanel
        {
            Spacing = 8,
            Children =
            {
                new Border
                {
                    Height = 52,
                    CornerRadius = new CornerRadius(8),
                    Background = ModernUi.AccentSoft
                },
                ModernUi.Text(block.Text, 13, FontWeights.SemiBold, maxLines: 1)
            }
        }, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private static string LocalTitle(string value, string zh, string en)
    {
        return string.IsNullOrWhiteSpace(value) ? AppLocalizer.T(zh, en) : value;
    }

    private static Brush StatusBrush(string status)
    {
        return status.Equals("error", StringComparison.OrdinalIgnoreCase) || status.Equals("removed", StringComparison.OrdinalIgnoreCase)
            ? ModernUi.Danger
            : status.Equals("ok", StringComparison.OrdinalIgnoreCase) || status.Equals("added", StringComparison.OrdinalIgnoreCase)
                ? ModernUi.Success
                : ModernUi.Accent;
    }

    private void SetResultViewMode(bool showRaw)
    {
        _showRawResult = showRaw;
        _outputBox.Text = _lastRawOutput;
        var showWeb = !showRaw && _resultWebViewReady && !_resultWebViewFailed;
        _resultWebView.Visibility = showWeb ? Visibility.Visible : Visibility.Collapsed;
        _resultCards.Visibility = showRaw || showWeb ? Visibility.Collapsed : Visibility.Visible;
        _renderedResultScroll.Visibility = showRaw || showWeb ? Visibility.Collapsed : Visibility.Visible;
        _outputBox.Visibility = showRaw ? Visibility.Visible : Visibility.Collapsed;
        UpdateResultModeButtons();
        UpdateResultSearchStatus();
    }

    private string ResultModeText()
    {
        return _showRawResult
            ? AppLocalizer.T("查看渲染结果", "Show rendered result")
            : AppLocalizer.T("查看原始结果", "Show raw result");
    }

    private string ResultModeGlyph() => _showRawResult ? "\uE9D9" : "\uE8A5";

    private static Border BuildImageResultCard(ToolResultBlock block)
    {
        var metadata = block.Metadata ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        metadata.TryGetValue("contentType", out var contentType);
        metadata.TryGetValue("rawSvg", out var rawSvg);
        metadata.TryGetValue("pngBase64", out var pngBase64);
        metadata.TryGetValue("payload", out var payload);
        metadata.TryGetValue("suggestedFileName", out var suggestedName);

        var svgText = !string.IsNullOrWhiteSpace(rawSvg) ? rawSvg : block.Text;
        var isSvg = string.Equals(contentType, "image/svg+xml", StringComparison.OrdinalIgnoreCase) ||
            svgText.TrimStart().StartsWith("<svg", StringComparison.OrdinalIgnoreCase);

        ImageSource? source = null;
        var caption = block.Uri;
        if (isSvg)
        {
            var svgSource = new SvgImageSource();
            _ = SetSvgSourceAsync(svgSource, svgText);
            source = svgSource;
            caption = AppLocalizer.T("SVG 二维码图片", "SVG QR code image");
        }
        else if (TryCreateImageUri(block.Uri, out var bitmapUri))
        {
            source = new BitmapImage(bitmapUri);
        }

        var image = new Image
        {
            Source = source,
            Stretch = Stretch.Uniform,
            MinHeight = isSvg ? 260 : 140,
            MaxHeight = isSvg ? 420 : 260,
            HorizontalAlignment = HorizontalAlignment.Center
        };

        var actions = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 8 };
        if (!string.IsNullOrWhiteSpace(payload))
        {
            actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("复制内容", "Copy content"), "\uE8C8", () => CopyText(payload)));
        }
        if (isSvg)
        {
            actions.Children.Add(ModernUi.PillButton("SVG", "\uE8C8", () => CopyText(svgText)));
            actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("保存 SVG", "Save SVG"), "\uE74E", async () => await SaveTextFileAsync($"{suggestedName ?? "image"}.svg", svgText, ".svg", "SVG")));
            if (!string.IsNullOrWhiteSpace(pngBase64))
            {
                actions.Children.Add(ModernUi.PillButton(AppLocalizer.T("保存 PNG", "Save PNG"), "\uE74E", async () => await SaveBytesFileAsync($"{suggestedName ?? "image"}.png", Convert.FromBase64String(pngBase64), ".png", "PNG")));
            }
        }
        else if (Uri.TryCreate(block.Uri, UriKind.Absolute, out var openUri))
        {
            actions.Children.Add(new HyperlinkButton
            {
                Content = AppLocalizer.T("打开图片", "Open image"),
                NavigateUri = openUri,
                Padding = new Thickness(0)
            });
        }

        var panel = new StackPanel
        {
            Spacing = 10,
            Children =
            {
                ModernUi.Text(LocalTitle(block.Title, "图片预览", "Image preview"), 14, FontWeights.SemiBold, ModernUi.TextSecondary),
                new Border
                {
                    CornerRadius = new CornerRadius(8),
                    Background = isSvg ? ModernUi.Brush("#FFFFFFFF") : ModernUi.Surface,
                    BorderBrush = ModernUi.Stroke,
                    BorderThickness = new Thickness(1),
                    Padding = new Thickness(isSvg ? 18 : 0),
                    Child = image
                },
                ModernUi.Text(ModernUi.Ellipsize(caption, 90), 12, foreground: ModernUi.TextSecondary, maxLines: 2)
            }
        };
        if (actions.Children.Count > 0)
        {
            panel.Children.Add(new ScrollViewer
            {
                HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
                VerticalScrollBarVisibility = ScrollBarVisibility.Disabled,
                Content = actions
            });
        }

        return ModernUi.Card(panel, new Thickness(12), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private static Border BuildImageResultCard(string link)
    {
        var image = new Image
        {
            Source = Uri.TryCreate(link, UriKind.Absolute, out var uri) ? new BitmapImage(uri) : null,
            Stretch = Stretch.UniformToFill,
            Height = 120
        };
        var open = new HyperlinkButton
        {
            Content = AppLocalizer.T("打开图片", "Open image"),
            NavigateUri = Uri.TryCreate(link, UriKind.Absolute, out var openUri) ? openUri : null,
            Padding = new Thickness(0)
        };
        return ModernUi.Card(new StackPanel
        {
            Spacing = 8,
            Children =
            {
                new Border { CornerRadius = new CornerRadius(8), Background = ModernUi.Surface, Child = image },
                ModernUi.Text(ModernUi.Ellipsize(link, 54), 12, foreground: ModernUi.TextSecondary, maxLines: 2),
                open
            }
        }, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private static async Task SetSvgSourceAsync(SvgImageSource source, string svg)
    {
        var bytes = Encoding.UTF8.GetBytes(svg);
        using var stream = new InMemoryRandomAccessStream();
        using (var writer = new DataWriter(stream.GetOutputStreamAt(0)))
        {
            writer.WriteBytes(bytes);
            await writer.StoreAsync();
            await writer.FlushAsync();
        }

        stream.Seek(0);
        await source.SetSourceAsync(stream);
    }

    private static bool TryCreateImageUri(string value, out Uri uri)
    {
        if (File.Exists(value))
        {
            uri = new Uri(value);
            return true;
        }

        return Uri.TryCreate(value, UriKind.Absolute, out uri!);
    }

    private static void CopyText(string value)
    {
        var data = new DataPackage();
        data.SetText(value);
        Clipboard.SetContent(data);
        ToastService.Show(AppLocalizer.T("内容已复制", "Content copied"), ToastKind.Success);
    }

    private static async Task SaveTextFileAsync(string suggestedName, string content, string extension, string fileTypeName)
    {
        var file = await PickSaveFileAsync(suggestedName, extension, fileTypeName);
        if (file is null)
        {
            return;
        }

        await FileIO.WriteTextAsync(file, content);
        ToastService.Show(AppLocalizer.T("文件已保存", "File saved"), ToastKind.Success);
    }

    private static async Task SaveBytesFileAsync(string suggestedName, byte[] content, string extension, string fileTypeName)
    {
        var file = await PickSaveFileAsync(suggestedName, extension, fileTypeName);
        if (file is null)
        {
            return;
        }

        await FileIO.WriteBytesAsync(file, content);
        ToastService.Show(AppLocalizer.T("文件已保存", "File saved"), ToastKind.Success);
    }

    private static async Task<StorageFile?> PickSaveFileAsync(string suggestedName, string extension, string fileTypeName)
    {
        if (App.CurrentWindow is null)
        {
            return null;
        }

        var picker = new FileSavePicker
        {
            SuggestedFileName = Path.GetFileNameWithoutExtension(suggestedName)
        };
        InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(App.CurrentWindow));
        picker.FileTypeChoices.Add(fileTypeName, [extension]);
        return await picker.PickSaveFileAsync();
    }

    private static Border BuildLinkResultCard(string link)
    {
        var row = new Grid { ColumnSpacing = 10 };
        row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        row.ColumnDefinitions.Add(new ColumnDefinition());
        row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        row.Children.Add(ModernUi.IconTile("\uE774", 34, ModernUi.AccentSoft, ModernUi.Accent, 15));
        var text = ModernUi.Text(ModernUi.Ellipsize(link, 120), 13, foreground: ModernUi.TextSecondary, maxLines: 2);
        Grid.SetColumn(text, 1);
        row.Children.Add(text);
        var open = new HyperlinkButton
        {
            Content = AppLocalizer.T("打开", "Open"),
            NavigateUri = Uri.TryCreate(link, UriKind.Absolute, out var uri) ? uri : null
        };
        Grid.SetColumn(open, 2);
        row.Children.Add(open);
        return ModernUi.Card(row, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private static Border BuildInfoGrid(IReadOnlyList<(string Key, string Value)> rows)
    {
        var panel = new VariableSizedWrapGrid
        {
            Orientation = Orientation.Horizontal,
            ItemWidth = 230,
            ItemHeight = 78
        };

        foreach (var row in rows)
        {
            panel.Children.Add(ModernUi.Card(new StackPanel
            {
                Spacing = 2,
                Children =
                {
                    ModernUi.Text(row.Key, 12, FontWeights.SemiBold, ModernUi.TextSecondary, maxLines: 1),
                    ModernUi.Text(ModernUi.Ellipsize(row.Value, 72), 14, FontWeights.SemiBold, maxLines: 2)
                }
            }, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt));
        }

        return ModernUi.Card(panel, new Thickness(10), radius: 8, background: ModernUi.Surface);
    }

    private static Border BuildLineResultCard(string line)
    {
        var row = new Grid { ColumnSpacing = 10 };
        row.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
        row.ColumnDefinitions.Add(new ColumnDefinition());
        row.Children.Add(new Border
        {
            Width = 8,
            Height = 8,
            CornerRadius = new CornerRadius(4),
            Background = ModernUi.Accent,
            VerticalAlignment = VerticalAlignment.Center
        });

        var text = ModernUi.Text(ModernUi.Ellipsize(line, 180), 13, foreground: ModernUi.TextSecondary, maxLines: 3);
        Grid.SetColumn(text, 1);
        row.Children.Add(text);
        return ModernUi.Card(row, new Thickness(10), radius: 8, background: ModernUi.SurfaceAlt);
    }

    private string PrepareInput(IToolModule module, string input)
    {
        input = ApplySelectedRulesToInput(module, input);
        var aux = _auxBox.Text.Trim();
        if (module.Id == "hmac_generator" && !string.IsNullOrWhiteSpace(aux))
        {
            return $"{aux}{Environment.NewLine}{input}";
        }

        if (module.Id is "keyword_counter" or "text_filter" && !string.IsNullOrWhiteSpace(aux))
        {
            return $"{aux}{Environment.NewLine}---{Environment.NewLine}{input}";
        }

        if (module.Id == "prefix_suffix_batch" && !string.IsNullOrWhiteSpace(aux))
        {
            var parts = aux.Split('|', 2);
            var prefix = parts.ElementAtOrDefault(0) ?? string.Empty;
            var suffix = parts.ElementAtOrDefault(1) ?? string.Empty;
            return string.Join(Environment.NewLine, input.Replace("\r\n", "\n").Split('\n').Select(line => $"{prefix}{line}{suffix}"));
        }

        if (module.Id == "smart_search" && _parameterBox.SelectedItem is ComboBoxItem item && item.Tag is string channel)
        {
            return $"{channel}{Environment.NewLine}{input}";
        }

        return input;
    }

    private string ApplySelectedRulesToInput(IToolModule module, string input)
    {
        if (_selectedRuleValues.Count == 0)
        {
            return input;
        }

        var primary = _selectedRuleValues.FirstOrDefault(pair => pair.Value is not "false").Value;
        if (string.IsNullOrWhiteSpace(primary) || primary is "true")
        {
            return input;
        }

        return module.Id switch
        {
            "compression_codec" => $"{primary}{Environment.NewLine}{input}",
            "text_sorter" or "manifest_sorter" => $"{primary}{Environment.NewLine}{input}",
            "text_deduplicator" or "duplicate_detector" or "manifest_deduplicator" => $"{primary}{Environment.NewLine}{input}",
            "text_filter" or "keyword_counter" => input,
            "hash_generator" or "hmac_generator" or "hash_manifest_builder" or "hash_manifest_verify" => $"{primary}{Environment.NewLine}{input}",
            _ => input
        };
    }

    private static string Localized(ToolTextPair? text, string fallback)
    {
        return text?.ForLanguage(AppLocalizer.CurrentLanguage) ?? fallback;
    }

    private void ClearInput()
    {
        _inputBox.Text = string.Empty;
        _queryBox.Text = string.Empty;
        _outputBox.Text = string.Empty;
        _lastRawOutput = string.Empty;
        _resultCards.Children.Clear();
        SetResultViewMode(showRaw: false);
        ToastService.Show(AppLocalizer.T("内容已清空", "Content cleared"), ToastKind.Info);
    }

    private void CopyResult()
    {
        var data = new DataPackage();
        data.SetText(_lastRawOutput);
        Clipboard.SetContent(data);
        _ = WriteResultLogAsync("Information", "copy", "success", $"chars={_lastRawOutput.Length}");
        ToastService.Show(AppLocalizer.T("结果已复制", "Result copied"), ToastKind.Success);
    }

    private async Task ExportResultAsync()
    {
        if (string.IsNullOrWhiteSpace(_lastRawOutput))
        {
            ToastService.Show(AppLocalizer.T("暂无可导出的结果", "No result to export"), ToastKind.Info);
            await WriteResultLogAsync("Information", "export", "empty", "chars=0");
            return;
        }

        var suggestedName = $"{ResultFileBaseName()}-{DateTime.Now:yyyyMMdd-HHmmss}.txt";
        var file = await PickSaveFileAsync(suggestedName, _lastRawOutput.TrimStart().StartsWith('{') || _lastRawOutput.TrimStart().StartsWith('[') ? ".json" : ".txt", "Result");
        if (file is null)
        {
            await WriteResultLogAsync("Information", "export", "canceled", $"chars={_lastRawOutput.Length}");
            return;
        }

        await FileIO.WriteTextAsync(file, _lastRawOutput);
        await WriteResultLogAsync("Information", "export", "success", $"chars={_lastRawOutput.Length}; file={Path.GetFileName(file.Path)}");
        ToastService.Show(AppLocalizer.T("结果已导出", "Result exported"), ToastKind.Success);
    }

    private void UpdateResultSearchStatus()
    {
        var query = _resultSearchBox.Text?.Trim() ?? string.Empty;
        if (string.IsNullOrEmpty(_lastRawOutput))
        {
            _resultSearchStatus.Text = AppLocalizer.T("暂无结果", "No result");
            return;
        }

        if (string.IsNullOrWhiteSpace(query))
        {
            var lines = _lastRawOutput.Replace("\r\n", "\n").Split('\n').Length;
            _resultSearchStatus.Text = AppLocalizer.T($"{lines} 行 / {_lastRawOutput.Length} 字符", $"{lines} lines / {_lastRawOutput.Length} chars");
            return;
        }

        var count = CountOccurrences(_lastRawOutput, query);
        _resultSearchStatus.Text = count == 0
            ? AppLocalizer.T("无命中", "No matches")
            : AppLocalizer.T($"{count} 处命中", $"{count} matches");
    }

    private async Task LogResultSearchAsync()
    {
        var query = _resultSearchBox.Text?.Trim() ?? string.Empty;
        if (string.IsNullOrWhiteSpace(query) || string.IsNullOrEmpty(_lastRawOutput))
        {
            await WriteResultLogAsync("Information", "search", "empty", $"queryLen={query.Length}; chars={_lastRawOutput.Length}");
            return;
        }

        var index = _lastRawOutput.IndexOf(query, StringComparison.CurrentCultureIgnoreCase);
        var matches = CountOccurrences(_lastRawOutput, query);
        if (index >= 0)
        {
            SetResultViewMode(showRaw: true);
            _outputBox.Focus(FocusState.Programmatic);
            _outputBox.Select(index, query.Length);
        }

        await WriteResultLogAsync("Information", "search", index >= 0 ? "success" : "miss", $"queryLen={query.Length}; matches={matches}; chars={_lastRawOutput.Length}");
    }

    private async Task WriteResultLogAsync(string level, string operation, string result, string? detail = null)
    {
        var toolId = Module?.Id ?? "<unknown>";
        var payload = $"toolId={toolId}; operation={operation}; result={result}";
        if (!string.IsNullOrWhiteSpace(detail))
        {
            payload += "; " + AppLocalizer.SanitizeSensitiveText(detail, 360);
        }

        await _logService.WriteAsync(level, "tool-result", $"Tool result {result}: {operation}", payload);
    }

    private string ResultFileBaseName()
    {
        var name = Module?.Id ?? "result";
        foreach (var invalid in Path.GetInvalidFileNameChars())
        {
            name = name.Replace(invalid, '-');
        }

        return string.IsNullOrWhiteSpace(name) ? "result" : name;
    }

    private static int CountOccurrences(string text, string query)
    {
        if (string.IsNullOrEmpty(text) || string.IsNullOrEmpty(query))
        {
            return 0;
        }

        var count = 0;
        var index = 0;
        while ((index = text.IndexOf(query, index, StringComparison.CurrentCultureIgnoreCase)) >= 0)
        {
            count++;
            index += Math.Max(1, query.Length);
        }

        return count;
    }

    private async void BrowseInputFile()
    {
        if (Module is null || App.CurrentWindow is null)
        {
            return;
        }

        var picker = new FileOpenPicker();
        InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(App.CurrentWindow));
        foreach (var extension in FileTypeFilters(Module.Id))
        {
            picker.FileTypeFilter.Add(extension);
        }

        var file = await picker.PickSingleFileAsync();
        if (file is not null)
        {
            _inputBox.Text = file.Path;
            ToastService.Show(AppLocalizer.T("文件已选择", "File selected"), ToastKind.Info);
        }
    }

    private bool UsesNumericPrimaryInput()
    {
        return _spec.PrimaryInput is ToolPrimaryInputKind.Number
            or ToolPrimaryInputKind.NumberPair
            or ToolPrimaryInputKind.NumberTriple
            or ToolPrimaryInputKind.NumberQuad;
    }

    private bool UsesSupplementalNumber(IToolModule module)
    {
        return !UsesNumericPrimaryInput() && (_spec.UsesNumberBox || ToolPageProfiles.For(module).ShowNumber);
    }

    private static string FormatNumber(double value)
    {
        return value.ToString("0.########", CultureInfo.InvariantCulture);
    }

    private static Slider ColorSlider(string header, double value)
    {
        var slider = new Slider
        {
            Header = header,
            Minimum = 0,
            Maximum = 255,
            StepFrequency = 1,
            Value = value
        };
        return slider;
    }

    private static bool TryParseHexColor(string input, out byte r, out byte g, out byte b)
    {
        r = g = b = 0;
        var normalized = input.Trim().TrimStart('#');
        if (normalized.Length == 3)
        {
            normalized = string.Concat(normalized.Select(ch => new string(ch, 2)));
        }

        if (normalized.Length != 6 || !Regex.IsMatch(normalized, "^[0-9a-fA-F]{6}$"))
        {
            return false;
        }

        r = Convert.ToByte(normalized[..2], 16);
        g = Convert.ToByte(normalized.Substring(2, 2), 16);
        b = Convert.ToByte(normalized.Substring(4, 2), 16);
        return true;
    }

    private static IReadOnlyList<KeyValuePair<string, string>> ColorRows(byte r, byte g, byte b)
    {
        var (h, s, l) = RgbToHsl(r, g, b);
        var (hh, ss, vv) = RgbToHsv(r, g, b);
        var (c, m, y, k) = RgbToCmyk(r, g, b);
        var luminance = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255;
        var text = luminance > 0.55 ? AppLocalizer.T("建议深色文字", "Use dark text") : AppLocalizer.T("建议浅色文字", "Use light text");
        return
        [
            new("HEX", $"#{r:X2}{g:X2}{b:X2}"),
            new("RGB", $"{r}, {g}, {b}"),
            new("HSL", $"{h:0.#}, {s:0.#}%, {l:0.#}%"),
            new("HSV", $"{hh:0.#}, {ss:0.#}%, {vv:0.#}%"),
            new("CMYK", $"{c:0.#}%, {m:0.#}%, {y:0.#}%, {k:0.#}%"),
            new("ARGB", $"255, {r}, {g}, {b}"),
            new(AppLocalizer.T("可读性", "Readability"), text)
        ];
    }

    private static (double H, double S, double L) RgbToHsl(byte r, byte g, byte b)
    {
        var rf = r / 255d;
        var gf = g / 255d;
        var bf = b / 255d;
        var max = Math.Max(rf, Math.Max(gf, bf));
        var min = Math.Min(rf, Math.Min(gf, bf));
        var h = 0d;
        var l = (max + min) / 2d;
        var d = max - min;
        var s = d == 0 ? 0 : d / (1 - Math.Abs(2 * l - 1));
        if (d != 0)
        {
            h = max == rf ? 60 * (((gf - bf) / d) % 6) :
                max == gf ? 60 * (((bf - rf) / d) + 2) :
                60 * (((rf - gf) / d) + 4);
        }

        if (h < 0)
        {
            h += 360;
        }

        return (h, s * 100, l * 100);
    }

    private static (double H, double S, double V) RgbToHsv(byte r, byte g, byte b)
    {
        var rf = r / 255d;
        var gf = g / 255d;
        var bf = b / 255d;
        var max = Math.Max(rf, Math.Max(gf, bf));
        var min = Math.Min(rf, Math.Min(gf, bf));
        var delta = max - min;
        var h = 0d;
        if (delta != 0)
        {
            h = max == rf ? 60 * (((gf - bf) / delta) % 6) :
                max == gf ? 60 * (((bf - rf) / delta) + 2) :
                60 * (((rf - gf) / delta) + 4);
        }

        if (h < 0)
        {
            h += 360;
        }

        return (h, max == 0 ? 0 : delta / max * 100, max * 100);
    }

    private static (double C, double M, double Y, double K) RgbToCmyk(byte r, byte g, byte b)
    {
        var rf = r / 255d;
        var gf = g / 255d;
        var bf = b / 255d;
        var k = 1 - Math.Max(rf, Math.Max(gf, bf));
        if (k >= 1)
        {
            return (0, 0, 0, 100);
        }

        return ((1 - rf - k) / (1 - k) * 100, (1 - gf - k) / (1 - k) * 100, (1 - bf - k) / (1 - k) * 100, k * 100);
    }

    private static void CopyColorValue(string value)
    {
        var data = new DataPackage();
        data.SetText(value);
        Clipboard.SetContent(data);
        ToastService.Show(AppLocalizer.T("颜色值已复制", "Color value copied"), ToastKind.Success);
    }

    private static IEnumerable<string> ExtractLinks(IEnumerable<string> lines)
    {
        foreach (var line in lines)
        {
            foreach (Match match in Regex.Matches(line, @"https?://[^\s\]\)>'""]+", RegexOptions.IgnoreCase))
            {
                yield return match.Value.TrimEnd('.', ',', ';');
            }
        }
    }

    private static bool IsImageLink(string link)
    {
        if (!Uri.TryCreate(link, UriKind.Absolute, out var uri))
        {
            return false;
        }

        var path = uri.AbsolutePath.ToLowerInvariant();
        return path.EndsWith(".png", StringComparison.OrdinalIgnoreCase)
            || path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase)
            || path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase)
            || path.EndsWith(".webp", StringComparison.OrdinalIgnoreCase)
            || path.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)
            || uri.Host.Contains("qlogo.cn", StringComparison.OrdinalIgnoreCase);
    }

    private static (string Key, string Value)? ParseKeyValue(string line)
    {
        var normalized = line.Trim();
        var separator = normalized.IndexOf(':');
        if (separator <= 0 || separator >= normalized.Length - 1)
        {
            return null;
        }

        var key = normalized[..separator].Trim().Trim('-', '*', ' ');
        var value = normalized[(separator + 1)..].Trim();
        return key.Length is > 0 and < 48 && value.Length > 0 ? (key, value) : null;
    }

    private static string? ExtractLocalPath(string line)
    {
        var candidate = line.Replace("Path:", string.Empty, StringComparison.OrdinalIgnoreCase)
            .Replace("Output:", string.Empty, StringComparison.OrdinalIgnoreCase)
            .Trim()
            .Trim('"');
        return File.Exists(candidate) ? candidate : null;
    }

    private static bool ShouldAutoRefresh(IToolModule module)
    {
        return ToolPageSpecCatalog.For(module).AutoRunOnOpen
            || (!module.Metadata.OfflineCapable &&
                module.Id is not "safe_browser" and not "media_player" and not "ai_translation" and not "ai_latest_news" and not "city_route_query" and not "serial_terminal");
    }

    private static string InputTitle(IToolModule module)
    {
        return ToolPageSpecCatalog.For(module).PrimaryInput switch
        {
            ToolPrimaryInputKind.None => AppLocalizer.T("实时视图", "Live view"),
            ToolPrimaryInputKind.FixedOptions => AppLocalizer.T("选择项", "Choose option"),
            ToolPrimaryInputKind.Query or ToolPrimaryInputKind.Url => AppLocalizer.T("查询", "Query"),
            ToolPrimaryInputKind.DateRange => AppLocalizer.T("日期输入", "Date input"),
            ToolPrimaryInputKind.Number or ToolPrimaryInputKind.NumberPair or ToolPrimaryInputKind.NumberTriple or ToolPrimaryInputKind.NumberQuad => AppLocalizer.T("数字表单", "Numeric form"),
            ToolPrimaryInputKind.FilePath => AppLocalizer.T("文件输入", "File input"),
            _ => AppLocalizer.T("输入", "Input")
        };
    }

    private static string InputDescription(IToolModule module)
    {
        return ToolPageSpecCatalog.For(module).Layout switch
        {
            ToolLayoutKind.Dashboard => AppLocalizer.T("此页会加载并以卡片或列表展示实时数据。", "This page loads and renders data as cards or lists."),
            ToolLayoutKind.ReferenceBrowser => AppLocalizer.T("选择预设后浏览参考数据。", "Choose a preset to browse reference rows."),
            ToolLayoutKind.CalculatorForm or ToolLayoutKind.UnitConverter => AppLocalizer.T("填写原生表单字段后执行计算。", "Fill the native form fields and run the calculation."),
            ToolLayoutKind.FileWorkflow => AppLocalizer.T("选择文件或粘贴路径后执行本地流程。", "Choose a file or paste a path, then run the local workflow."),
            ToolLayoutKind.QueryDashboard => AppLocalizer.T("输入查询条件，在线工具使用共享 HTTP/API 服务。", "Enter a query. Online tools use the shared HTTP/API service."),
            _ => AppLocalizer.T("使用原生 WinUI 控件完成输入、规则选择和结果展示。", "Use native WinUI controls for input, rule selection, and rendered output.")
        };
    }

    private static string RuleDescription(IToolModule module)
    {
        var spec = ToolPageSpecCatalog.For(module);
        if (module.Id == "smart_search")
        {
            return AppLocalizer.T("输入关键词并选择需要打开的搜索渠道，结果只展示可跳转入口。", "Enter keywords and choose a search channel. Results show launchable entries only.");
        }

        if (module.Id == "system_tool")
        {
            return AppLocalizer.T("选择系统信息分组后重新加载，下方会以卡片展示本机摘要。", "Choose a system group and reload to render local summaries as cards.");
        }

        if (spec.Layout == ToolLayoutKind.CodecWorkbench)
        {
            return AppLocalizer.T("选择编码、解码或自动识别方向，短文本输入框会保持单行友好。", "Choose encode, decode, or auto mode. Short inputs stay single-line friendly.");
        }

        if (spec.Layout == ToolLayoutKind.FormatterWorkbench)
        {
            return AppLocalizer.T("粘贴文档或代码片段后选择格式化规则，结果会保留结构化预览。", "Paste documents or code, choose a formatting rule, and keep structured preview output.");
        }

        if (spec.Layout == ToolLayoutKind.ReferenceBrowser)
        {
            return AppLocalizer.T("选择分类或输入关键词即可浏览内置参考数据。", "Choose a category or enter keywords to browse built-in reference data.");
        }

        if (spec.Layout == ToolLayoutKind.QueryDashboard)
        {
            return AppLocalizer.T("输入查询条件后获取公开数据，界面只展示脱敏后的来源名称。", "Enter a query to fetch public data. The UI only shows sanitized source names.");
        }

        if (spec.Layout == ToolLayoutKind.FileWorkflow)
        {
            return AppLocalizer.T("选择文件或粘贴路径后执行处理，结果区展示状态、文件或预览。", "Choose a file or paste a path, then render status, files, or previews.");
        }

        if (spec.Layout is ToolLayoutKind.CalculatorForm or ToolLayoutKind.UnitConverter)
        {
            return AppLocalizer.T("填写数值参数后计算，结果会以表格或摘要卡片展示。", "Fill numeric parameters to calculate and render table or summary cards.");
        }

        return module.Metadata.OfflineCapable
            ? AppLocalizer.T("此工具会在本机完成处理，结果仅在当前页面展示。", "This tool processes input locally and renders results on this page.")
            : AppLocalizer.T("使用共享 API/HTTP 服务；不会记录敏感接口细节。", "Uses the shared API/HTTP service; sensitive endpoint details are not logged.");
    }

    private static string InputHint(IToolModule module)
    {
        return module.Id switch
        {
            "regex_tool" => AppLocalizer.T("第一行填写正则表达式，后续行填写待匹配文本。", "First line: regex. Remaining lines: text to match."),
            "replace_text" or "batch_text_replace" => AppLocalizer.T("第 1 行查找，第 2 行替换，剩余行为正文。", "Line 1: find. Line 2: replace. Remaining lines: text."),
            "train_query" => "2026-05-25 北京 上海",
            "qrcode_scanner" or "image_processor" or "image_metadata_inspector" or "archive_tool" => AppLocalizer.T("粘贴本地文件路径，或使用文件选择器。", "Paste a local file path or use the file picker."),
            _ when ToolPageSpecCatalog.For(module).PrimaryInput is ToolPrimaryInputKind.Query or ToolPrimaryInputKind.Url => AppLocalizer.T("输入关键词、URL、域名、IP 或城市。", "Enter keyword, URL, domain, IP, or city."),
            _ => AppLocalizer.T("输入此工具所需的文本或参数。", "Enter text or parameters for this tool.")
        };
    }

    private static string DefaultInput(IToolModule module)
    {
        return module.Id switch
        {
            "json_formatter" => "{\"name\":\"YMhut Box\",\"runtime\":\"WinUI 3\"}",
            "regex_tool" => "\\bWinUI\\b\nYMhut Box WinUI 3",
            "dns_query" => "example.com",
            "weather" => AppLocalizer.T("北京", "Beijing"),
            "smart_search" => AppLocalizer.T("YMhut Box", "YMhut Box"),
            "train_query" => $"{DateTime.Today.AddDays(1):yyyy-MM-dd} 北京 上海",
            "domain_price" or "rdap_domain_lookup" => "example.com",
            "rdap_ip_lookup" or "ip_info" or "ip_lookup" => "8.8.8.8",
            "http_diagnostic" or "url_inspector" or "url_redirect_trace" => "https://example.com",
            "compression_codec" => "gzip encode\nYMhut Box",
            "totp_generator" => "JBSWY3DPEHPK3PXP",
            "color_picker" => "#3399FF",
            "color_contrast_checker" => "#111827 #ffffff",
            "markdown_preview" => "# YMhut Box\n\n- WinUI 3\n- Toolbox",
            "number_base" or "number_base_converter" => "255",
            "timestamp_converter" => DateTimeOffset.Now.ToUnixTimeSeconds().ToString(CultureInfo.InvariantCulture),
            _ => string.Empty
        };
    }

    private static double DefaultNumber(IToolModule module)
    {
        return module.Id switch
        {
            "password_generator" or "random_generator" => 24,
            "uuid_generator" or "ulid_generator" => 1,
            _ => 1
        };
    }

    private static IEnumerable<string> RuleOptions(IToolModule module)
    {
        return module.Id switch
        {
            "json_formatter" => [AppLocalizer.T("格式化", "Format"), AppLocalizer.T("压缩", "Minify"), AppLocalizer.T("校验", "Validate")],
            "base64_codec" or "compression_codec" or "url_codec" or "html_entity" or "unicode_codec" or "punycode_codec" => [AppLocalizer.T("编码", "Encode"), AppLocalizer.T("解码", "Decode"), AppLocalizer.T("双向", "Both")],
            "text_sorter" => [AppLocalizer.T("升序", "Ascending"), AppLocalizer.T("降序", "Descending"), AppLocalizer.T("自然排序", "Natural")],
            "text_deduplicator" => [AppLocalizer.T("保留首次出现", "Keep first"), AppLocalizer.T("保留最后出现", "Keep last"), AppLocalizer.T("忽略大小写", "Case-insensitive")],
            "text_filter" => [AppLocalizer.T("包含", "Contains"), AppLocalizer.T("正则", "Regex"), AppLocalizer.T("排除", "Exclude")],
            "hash_generator" => ["MD5", "SHA1", "SHA256", "SHA512"],
            _ => []
        };
    }

    private static IEnumerable<(string Label, string Input)> SampleInputs(IToolModule module)
    {
        return module.Id switch
        {
            "json_formatter" => [("JSON", "{\"name\":\"YMhut Box\",\"runtime\":\"WinUI 3\"}")],
            "regex_tool" => [(AppLocalizer.T("正则", "Regex"), "\\bWinUI\\b\nYMhut Box WinUI 3")],
            "weather" => [(AppLocalizer.T("北京", "Beijing"), "北京"), (AppLocalizer.T("上海", "Shanghai"), "上海")],
            "dns_query" => [(AppLocalizer.T("A 记录", "A record"), "example.com A"), ("MX", "example.com MX")],
            "domain_price" => [(".com", "example.com"), (".net", "example.net")],
            "ip_info" or "ip_lookup" or "rdap_ip_lookup" => [("Google DNS", "8.8.8.8"), ("Cloudflare", "1.1.1.1")],
            "system_tool" => [(AppLocalizer.T("状态", "Status"), "status"), (AppLocalizer.T("临时目录", "Temp"), "temp"), (AppLocalizer.T("进程", "Process"), "process")],
            _ => []
        };
    }

    private static IEnumerable<(string Label, string Input)> PrimaryParameterOptions(IToolModule module)
    {
        return module.Id switch
        {
            "smart_search" =>
            [
                ("百度", "baidu"),
                ("Bing", "bing"),
                ("Google", "google"),
                ("DuckDuckGo", "duckduckgo"),
                ("搜狗", "sogou"),
                ("360", "so360"),
                ("知乎", "zhihu"),
                ("B 站", "bilibili"),
                ("微博", "weibo"),
                ("GitHub", "github"),
                ("Stack Overflow", "stackoverflow"),
                ("MDN", "mdn")
            ],
            "hotboard" =>
            [
                ("百度热搜", "百度"),
                ("哔哩哔哩", "哔哩哔哩"),
                ("知乎", "知乎"),
                ("微博", "微博"),
                ("抖音", "抖音"),
                ("今日头条", "今日头条"),
                ("36氪", "36氪"),
                ("少数派", "少数派"),
                ("GitHub", "GitHub"),
                ("V2EX", "V2EX")
            ],
            "system_tool" =>
            [
                (AppLocalizer.T("系统状态", "System status"), "status"),
                (AppLocalizer.T("临时目录", "Temporary folder"), "temp"),
                (AppLocalizer.T("进程", "Processes"), "process"),
                (AppLocalizer.T("环境变量", "Environment variables"), "env"),
                ("PATH", "path"),
                (AppLocalizer.T("远程桌面", "Remote Desktop"), "rdp"),
                (AppLocalizer.T("画图", "Paint"), "paint"),
                (AppLocalizer.T("计算器", "Calculator"), "calculator"),
                (AppLocalizer.T("控制面板", "Control Panel"), "control_panel"),
                (AppLocalizer.T("注册表编辑器", "Registry Editor"), "registry_editor"),
                (AppLocalizer.T("任务管理器", "Task Manager"), "task_manager"),
                (AppLocalizer.T("服务", "Services"), "services"),
                (AppLocalizer.T("设备管理器", "Device Manager"), "device_manager"),
                (AppLocalizer.T("磁盘管理", "Disk Management"), "disk_management"),
                (AppLocalizer.T("事件查看器", "Event Viewer"), "event_viewer"),
                (AppLocalizer.T("Windows 设置", "Windows Settings"), "windows_settings"),
                (AppLocalizer.T("文件资源管理器", "File Explorer"), "explorer"),
                (AppLocalizer.T("记事本", "Notepad"), "notepad"),
                (AppLocalizer.T("命令提示符", "Command Prompt"), "cmd"),
                ("PowerShell", "powershell")
            ],
            "http_status_lookup" => [(AppLocalizer.T("全部状态码", "All status codes"), ""), ("2xx", "2"), ("3xx", "3"), ("4xx", "4"), ("5xx", "5"), ("404", "404"), ("429", "429")],
            "port_lookup" => [(AppLocalizer.T("全部端口", "All ports"), ""), (AppLocalizer.T("Web", "Web"), "http"), (AppLocalizer.T("邮件", "Mail"), "mail"), ("SQL", "sql"), (AppLocalizer.T("远程", "Remote"), "remote"), ("RDP", "rdp")],
            "mime_lookup" => [(AppLocalizer.T("全部 MIME", "All MIME"), ""), (AppLocalizer.T("图片", "Image"), "image"), (AppLocalizer.T("音频", "Audio"), "audio"), (AppLocalizer.T("视频", "Video"), "video"), (AppLocalizer.T("文本", "Text"), "text"), ("JSON", "json"), ("PDF", "pdf"), ("Zip", "zip")],
            "dns_record_lookup" => [(AppLocalizer.T("全部记录", "All records"), ""), ("A", "A"), ("AAAA", "AAAA"), ("CNAME", "CNAME"), ("MX", "MX"), ("TXT", "TXT"), ("NS", "NS"), ("SOA", "SOA"), ("CAA", "CAA")],
            "unicode_block_lookup" => [(AppLocalizer.T("全部区段", "All blocks"), ""), ("Basic Latin", "Basic Latin"), ("CJK", "CJK"), ("Kana", "Kana"), (AppLocalizer.T("标点", "Punctuation"), "Punctuation"), ("Emoji", "Pictographs")],
            "charset_lookup" => [(AppLocalizer.T("全部字符集", "All charsets"), ""), ("UTF-8", "UTF-8"), ("GB18030", "GB18030"), ("GBK", "GBK"), ("Big5", "Big5"), ("Shift_JIS", "Shift_JIS")],
            "timezone_abbr_lookup" => [(AppLocalizer.T("全部时区", "All zones"), ""), ("UTC", "UTC"), ("CST", "CST"), ("JST", "JST"), ("EST", "EST"), ("PST", "PST"), ("CET", "CET")],
            "regex_preset_lookup" => [(AppLocalizer.T("全部预设", "All presets"), ""), (AppLocalizer.T("邮箱", "Email"), "email"), ("URL", "URL"), ("IPv4", "IPv4"), (AppLocalizer.T("日期", "Date"), "date")],
            "magic_number_lookup" => [(AppLocalizer.T("全部签名", "All signatures"), ""), ("PNG", "PNG"), ("JPEG", "JPEG"), ("GIF", "GIF"), ("PDF", "PDF"), ("ZIP", "ZIP"), ("EXE", "MZ"), ("MP4", "mp4")],
            "car_info" => [(AppLocalizer.T("全部", "All"), ""), (AppLocalizer.T("北京", "Beijing"), "Beijing"), (AppLocalizer.T("上海", "Shanghai"), "Shanghai"), (AppLocalizer.T("广东", "Guangdong"), "Guangdong"), (AppLocalizer.T("江苏", "Jiangsu"), "Jiangsu")],
            "sanguosha_skin" => [(AppLocalizer.T("前 80 条", "First 80"), ""), (AppLocalizer.T("刘备", "Liu Bei"), "Liu Bei"), (AppLocalizer.T("曹操", "Cao Cao"), "Cao Cao"), (AppLocalizer.T("孙权", "Sun Quan"), "Sun Quan"), (AppLocalizer.T("皮肤 ID", "Skin ID"), "100101")],
            _ => []
        };
    }

    private static IReadOnlyList<string> FileTypeFilters(string id)
    {
        return id switch
        {
            "qrcode_scanner" or "image_processor" or "image_metadata_inspector" => [".png", ".jpg", ".jpeg", ".bmp", ".gif", ".webp"],
            "archive_tool" => [".zip", ".tar", "*"],
            _ => ["*"]
        };
    }
}
