using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using System.Text.Json;

namespace Zcbot.WindowsNode;

internal sealed class ConfigurationForm : Form
{
    private readonly TextBox server = CreateTextBox("http://127.0.0.1:8765");
    private readonly TextBox nodeName = CreateTextBox(Environment.MachineName.ToLowerInvariant());
    private readonly TextBox enrollmentCode = CreateTextBox(usePassword: true);
    private readonly Button register = CreateButton("注册并连接", 128, primary: true);
    private readonly Button reconnect = CreateButton("立即重连", 112, primary: true);
    private readonly Button resetIdentity = CreateButton("清除本机身份", 132, danger: true);
    private readonly Button copyDiagnostics = CreateButton("复制诊断信息", 132);
    private readonly TextBox dataRoot = CreateTextBox();
    private readonly Button changeDataRoot = CreateButton("更改并迁移", 124, primary: true);
    private readonly Button openDataRoot = CreateButton("打开目录", 104);
    private readonly Label dataRootStatus = CreateBodyLabel();
    private readonly Button runAnsysAcceptance = CreateButton("运行内置基准验收", 180, primary: true);
    private readonly Button enableAnsysGate = CreateButton("开启 ANSYS 执行门", 164);
    private readonly CheckBox startAtLogin = new()
    {
        Text = "登录 Windows 后自动启动节点",
        AutoSize = true,
        Margin = new Padding(0, 4, 0, 8),
    };
    private readonly Label state = new()
    {
        AutoSize = true,
        Anchor = AnchorStyles.Right,
        Font = new Font("Microsoft YaHei UI", 10, FontStyle.Bold),
        Margin = new Padding(12, 2, 0, 2),
        Padding = new Padding(10, 5, 10, 5),
    };
    private readonly Label detail = CreateBodyLabel();
    private readonly Label identity = CreateBodyLabel();
    private readonly Label capabilitySummary = CreateBodyLabel();
    private readonly Label ansysAcceptanceStatus = CreateBodyLabel();
    private readonly ProgressBar ansysAcceptanceProgress = new()
    {
        Style = ProgressBarStyle.Marquee,
        MarqueeAnimationSpeed = 30,
        Height = 8,
        Dock = DockStyle.Top,
        Visible = false,
        Margin = new Padding(0, 8, 0, 5),
    };
    private readonly Label jobSummary = CreateBodyLabel();
    private readonly Label jobDetail = CreateBodyLabel();
    private readonly DataGridView jobGrid = CreateJobGrid();
    private readonly JobInboxStore jobInbox = new(NodePaths.ForCurrentMachine().JobsDirectory);
    private readonly NodeAdapterRegistry adapters;
    private readonly Dictionary<string, SoftwareControls> softwareControls = new(StringComparer.Ordinal);
    private readonly System.Windows.Forms.Timer jobRefreshTimer = new() { Interval = 1000 };
    private readonly TableLayoutPanel registrationCard;
    private readonly TableLayoutPanel ansysAcceptanceCard;
    private bool changingStartup;
    private NodeConfig? currentConfig;
    private string? passedAcceptanceReport;
    private CancellationTokenSource? acceptanceStop;
    private CancellationTokenSource? runtimeInstallStop;
    private bool dataRootMigrationInProgress;

    internal event Func<EnrollOptions, Task>? RegisterRequested;
    internal event Action? ReconnectRequested;
    internal event Action? ResetIdentityRequested;
    internal event Func<string, Task<NodeDataMigrationResult>>? DataRootMigrationRequested;

    internal ConfigurationForm()
    {
        adapters = NodeAdapterRegistry.CreateDefault(jobInbox);
        Text = "zcbot Windows Node";
        AutoScaleMode = AutoScaleMode.Dpi;
        ClientSize = new Size(1080, 1050);
        MinimumSize = new Size(760, 560);
        StartPosition = FormStartPosition.CenterScreen;
        Font = new Font("Microsoft YaHei UI", 9);
        FormBorderStyle = FormBorderStyle.Sizable;
        BackColor = Color.White;

        var shell = new TableLayoutPanel
        {
            Dock = DockStyle.Fill,
            ColumnCount = 1,
            RowCount = 1,
            Padding = new Padding(24, 20, 24, 20),
            BackColor = BackColor,
        };
        shell.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
        shell.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
        Controls.Add(shell);

        var page = new TableLayoutPanel
        {
            Dock = DockStyle.Fill,
            ColumnCount = 1,
            RowCount = 2,
            BackColor = BackColor,
        };
        page.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
        page.RowStyles.Add(new RowStyle(SizeType.AutoSize));
        page.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
        shell.Controls.Add(page, 0, 0);

        var heading = new TableLayoutPanel
        {
            AutoSize = true,
            Dock = DockStyle.Top,
            ColumnCount = 2,
            Margin = new Padding(2, 0, 2, 14),
        };
        heading.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
        heading.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
        heading.Controls.Add(new Label
        {
            Text = "zcbot Windows Node",
            AutoSize = true,
            Anchor = AnchorStyles.Left,
            Font = new Font("Microsoft YaHei UI", 16, FontStyle.Bold),
            ForeColor = Color.FromArgb(15, 23, 42),
        }, 0, 0);
        heading.Controls.Add(state, 1, 0);
        page.Controls.Add(heading);

        var tabs = new NavigationTabControl
        {
            Dock = DockStyle.Fill,
            Margin = new Padding(0),
            Font = new Font("Microsoft YaHei UI", 9, FontStyle.Bold),
            Padding = new Point(14, 6),
            SizeMode = TabSizeMode.FillToRight,
        };
        var overviewPage = AddTab(tabs, "节点概览");
        var softwarePage = AddTab(tabs, "专业软件");
        var jobsPage = AddTab(tabs, "本机任务");
        var settingsPage = AddTab(tabs, "运行设置");
        page.Controls.Add(tabs, 0, 1);

        var statusCard = CreateSection();
        statusCard.Controls.Add(CreateSectionTitle("节点信息"));
        statusCard.Controls.Add(detail);
        statusCard.Controls.Add(identity);
        statusCard.Controls.Add(CreateDivider());
        statusCard.Controls.Add(CreateSubsectionTitle("可用软件"));
        statusCard.Controls.Add(capabilitySummary);
        var resetActions = CreateActions();
        resetActions.Controls.Add(reconnect);
        resetActions.Controls.Add(copyDiagnostics);
        resetActions.Controls.Add(resetIdentity);
        statusCard.Controls.Add(resetActions);
        overviewPage.Controls.Add(statusCard);

        var softwareCard = CreateSection();
        softwareCard.Controls.Add(CreateSectionTitle("专业软件"));
        softwareCard.Controls.Add(CreateHint(
            "只需配置这台电脑实际拥有的软件。应用位置和运行环境彼此独立，不会影响其他软件。"));
        foreach (var definition in SoftwareRuntimeManager.Definitions)
        {
            softwareCard.Controls.Add(CreateSoftwarePanel(definition));
        }
        softwarePage.Controls.Add(softwareCard);

        ansysAcceptanceCard = CreateSection();
        ansysAcceptanceCard.Controls.Add(CreateDivider());
        ansysAcceptanceCard.Controls.Add(CreateSectionTitle("ANSYS Mechanical 真机验收"));
        ansysAcceptanceCard.Controls.Add(CreateHint(
            "执行门关闭时也可本地验收。程序使用自带的标准试件，自动建立 fixed_face 和 load_face；先验证完整进程树取消，再连续求解 20 次并检查结果与进程释放。"));
        ansysAcceptanceCard.Controls.Add(ansysAcceptanceStatus);
        ansysAcceptanceCard.Controls.Add(ansysAcceptanceProgress);
        var acceptanceActions = CreateActions();
        acceptanceActions.Controls.Add(runAnsysAcceptance);
        acceptanceActions.Controls.Add(enableAnsysGate);
        ansysAcceptanceCard.Controls.Add(acceptanceActions);
        softwarePage.Controls.Add(ansysAcceptanceCard);

        var jobsCard = CreateSection();
        jobsCard.Controls.Add(CreateSectionTitle("本机任务"));
        jobsCard.Controls.Add(CreateHint(
            "仅显示已经派发到本机的任务；状态来自本地持久化记录，断线或重启后仍可查看。"));
        jobsCard.Controls.Add(jobSummary);
        jobsCard.Controls.Add(jobGrid);
        jobsCard.Controls.Add(jobDetail);
        jobsPage.Controls.Add(jobsCard);

        registrationCard = CreateSection();
        registrationCard.Controls.Add(CreateDivider());
        registrationCard.Controls.Add(CreateSectionTitle("首次注册"));
        registrationCard.Controls.Add(CreateHint(
            "从 zcbot 管理后台生成注册码。注册码默认 10 分钟有效，成功注册一次后立即失效。"));
        AddField(registrationCard, "zcbot 服务地址", server, "云端填写站点根地址；本机测试使用 http://127.0.0.1:8765");
        AddField(registrationCard, "节点名称", nodeName, "必须与注册码限定的节点名称完全一致");
        AddField(registrationCard, "一次性注册码", enrollmentCode, "格式类似 ZCN-…；仅用于首次注册，不会长期保存");
        var registerActions = CreateActions();
        registerActions.Controls.Add(register);
        registrationCard.Controls.Add(registerActions);
        overviewPage.Controls.Add(registrationCard);

        var runtimeCard = CreateSection();
        runtimeCard.Controls.Add(CreateSectionTitle("运行设置"));
        runtimeCard.Controls.Add(startAtLogin);
        AddField(
            runtimeCard,
            "数据目录",
            dataRoot,
            "保存节点身份、任务、Workspace 和受管 runtime；迁移只支持本机固定磁盘。环境变量优先于界面设置。");
        var dataRootActions = CreateActions();
        dataRootActions.Controls.Add(changeDataRoot);
        dataRootActions.Controls.Add(openDataRoot);
        runtimeCard.Controls.Add(dataRootActions);
        runtimeCard.Controls.Add(dataRootStatus);
        runtimeCard.Controls.Add(CreateHint(
            "关闭此窗口后，节点仍在系统托盘运行。右键托盘图标可以立即重连或退出。"));
        settingsPage.Controls.Add(runtimeCard);

        var securityNote = CreateHint(
            "安全说明：Node Token 由 Windows DPAPI 加密保存，不在界面显示，也不会写入日志。");
        securityNote.Margin = new Padding(4, 8, 4, 0);
        settingsPage.Controls.Add(securityNote);

        register.Click += async (_, _) => await RegisterAsync();
        reconnect.Click += (_, _) => ReconnectRequested?.Invoke();
        resetIdentity.Click += (_, _) => ResetIdentity();
        copyDiagnostics.Click += (_, _) => CopyDiagnostics();
        runAnsysAcceptance.Click += async (_, _) => await RunAnsysAcceptanceAsync();
        enableAnsysGate.Click += (_, _) => EnableAnsysGate();
        startAtLogin.CheckedChanged += (_, _) => ToggleStartup();
        changeDataRoot.Click += async (_, _) => await ChangeDataRootAsync();
        openDataRoot.Click += (_, _) => OpenDataRoot();
        FormClosing += (_, eventArgs) =>
        {
            if (eventArgs.CloseReason == CloseReason.UserClosing)
            {
                eventArgs.Cancel = true;
                Hide();
            }
        };
        startAtLogin.Checked = StartupRegistration.IsEnabled;
        dataRoot.ReadOnly = true;
        RefreshDataRoot();
        jobGrid.SelectionChanged += (_, _) => ShowSelectedJob();
        jobRefreshTimer.Tick += (_, _) => RefreshJobs();
        jobRefreshTimer.Start();
        Disposed += (_, _) =>
        {
            acceptanceStop?.Cancel();
            acceptanceStop?.Dispose();
            runtimeInstallStop?.Cancel();
            runtimeInstallStop?.Dispose();
            jobRefreshTimer.Dispose();
        };
        var ansysAdapter = adapters.Find("ansys.mechanical.static_structural@v2");
        ansysAcceptanceCard.Visible = ansysAdapter?.SupportsLocalAcceptance == true;
        enableAnsysGate.Enabled = false;
        UpdateAnsysAcceptanceStatus();
        RefreshSoftwareCards();
        RefreshJobs();
        ApplyStatus(NodeStatus.Create(NodeState.NotRegistered, "尚未注册"), null);
    }

    internal void ApplyStatus(NodeStatus status, NodeConfig? config)
    {
        currentConfig = config;
        state.Text = status.State switch
        {
            NodeState.Online => "● 在线",
            NodeState.Connecting => "● 正在连接",
            NodeState.NotRegistered => "● 尚未注册",
            NodeState.AuthenticationRequired => "● 需要重新注册",
            NodeState.Offline => "● 离线",
            _ => "● 已停止",
        };
        state.ForeColor = status.State switch
        {
            NodeState.Online => Color.FromArgb(21, 128, 61),
            NodeState.Connecting => Color.FromArgb(180, 83, 9),
            NodeState.NotRegistered or NodeState.AuthenticationRequired => Color.FromArgb(185, 28, 28),
            _ => Color.FromArgb(71, 85, 105),
        };
        state.BackColor = status.State switch
        {
            NodeState.Online => Color.FromArgb(240, 253, 244),
            NodeState.Connecting => Color.FromArgb(255, 251, 235),
            NodeState.NotRegistered or NodeState.AuthenticationRequired => Color.FromArgb(254, 242, 242),
            _ => Color.FromArgb(241, 245, 249),
        };
        detail.Text = $"{status.Message}　{status.ChangedAt:HH:mm:ss}";
        identity.Text = config is null
            ? "尚未建立节点身份"
            : $"节点：{config.NodeName}\nNode ID：{config.NodeId}\n服务：{config.ServerUrl}";
        capabilitySummary.Text = config is null
            ? "注册后启用"
            : FormatCapabilitySummary(adapters.All);
        copyDiagnostics.Enabled = config is not null;
        UpdateAnsysAcceptanceStatus();

        var registered = config is not null;
        registrationCard.Visible = !registered;
        reconnect.Visible = registered;
        resetIdentity.Visible = registered;
        register.Enabled = !registered && status.State != NodeState.Connecting;
        if (registered)
        {
            server.Text = config!.ServerUrl.AbsoluteUri.TrimEnd('/');
            nodeName.Text = config.NodeName;
            enrollmentCode.Clear();
        }
    }

    private static string FormatCapabilitySummary(IEnumerable<INodeAdapter> values)
    {
        var entries = values.Select(adapter =>
        {
            var runtime = adapter.DetectRuntime();
            var softwareName = runtime.Software == "OriginPro" ? "Origin" : runtime.Software;
            return new CapabilitySummaryEntry(
                softwareName,
                runtime.SoftwareVersion,
                ShortCapabilityName(adapter.DisplayName, softwareName),
                runtime.Health == "ready");
        }).ToArray();
        return string.Join("\n\n", entries
            .GroupBy(entry => entry.SoftwareName, StringComparer.Ordinal)
            .Select(group =>
            {
                var versions = group
                    .Select(entry => entry.SoftwareVersion)
                    .Where(version => !string.IsNullOrWhiteSpace(version))
                    .Distinct(StringComparer.Ordinal)
                    .ToArray();
                var heading = versions.Length == 0
                    ? $"{group.Key} · 版本未知"
                    : $"{group.Key} {string.Join(" / ", versions)}";
                var capabilities = group
                    .GroupBy(entry => entry.CapabilityName, StringComparer.Ordinal)
                    .Select(capability =>
                    {
                        var state = capability.All(entry => entry.Ready)
                            ? "可用"
                            : capability.Any(entry => entry.Ready) ? "部分可用" : "需配置";
                        return $"  {capability.Key}　{state}";
                    });
                return string.Join("\n", capabilities.Prepend(heading));
            }));
    }

    private static string ShortCapabilityName(string displayName, string softwareName) =>
        displayName.StartsWith(softwareName + " ", StringComparison.OrdinalIgnoreCase)
            ? displayName[(softwareName.Length + 1)..]
            : displayName;

    private Control CreateSoftwarePanel(SoftwareDefinition definition)
    {
        var panel = new BorderedTableLayoutPanel
        {
            AutoSize = true,
            AutoSizeMode = AutoSizeMode.GrowAndShrink,
            Dock = DockStyle.Top,
            ColumnCount = 2,
            Padding = new Padding(14, 12, 14, 12),
            Margin = new Padding(0, 8, 0, 0),
            BackColor = Color.FromArgb(248, 250, 252),
            BorderColor = Color.FromArgb(226, 232, 240),
        };
        panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
        panel.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
        var title = new Label
        {
            Text = definition.DisplayName,
            AutoSize = true,
            Font = new Font("Microsoft YaHei UI", 10, FontStyle.Bold),
            ForeColor = Color.FromArgb(15, 23, 42),
            Margin = new Padding(0, 0, 0, 4),
        };
        panel.Controls.Add(title, 0, 0);
        var path = CreateTextBox();
        path.ReadOnly = true;
        path.Dock = DockStyle.Fill;
        path.Margin = new Padding(0, 7, 0, 10);
        path.BackColor = Color.White;
        panel.Controls.Add(path, 0, 1);
        panel.SetColumnSpan(path, 2);
        var status = CreateBodyLabel();
        status.Dock = DockStyle.Fill;
        status.Margin = new Padding(0, 3, 0, 0);
        panel.Controls.Add(status, 0, 2);
        panel.SetColumnSpan(status, 2);
        var actions = CreateActions();
        actions.Margin = new Padding(0, 10, 0, 0);
        var automatic = CreateButton("自动检测", 92);
        Button? choose = null;
        if (definition.PathKind != SoftwarePathKind.None)
        {
            choose = CreateButton("选择位置", 92);
            actions.Controls.Add(choose);
        }
        var install = CreateButton("安装 / 更新环境", 150, primary: true);
        actions.Controls.Add(automatic);
        actions.Controls.Add(install);
        panel.Controls.Add(actions, 0, 3);
        panel.SetColumnSpan(actions, 2);
        var controls = new SoftwareControls(path, status, automatic, choose, install);
        softwareControls.Add(definition.Id, controls);
        automatic.Click += (_, _) => ResetSoftwareLocation(definition);
        if (choose is not null)
        {
            choose.Click += (_, _) => ChooseSoftwareLocation(definition);
        }
        install.Click += async (_, _) => await InstallSoftwareRuntimeAsync(definition);
        return panel;
    }

    private void RefreshSoftwareCards()
    {
        foreach (var definition in SoftwareRuntimeManager.Definitions)
        {
            RefreshSoftwareCard(definition);
        }
    }

    private void RefreshSoftwareCard(SoftwareDefinition definition)
    {
        var controls = softwareControls[definition.Id];
        var location = SoftwareRuntimeManager.ResolveLocation(definition);
        controls.Path.Text = location.Path ?? "未检测到应用位置";
        controls.Path.ForeColor = location.Available ? Color.FromArgb(51, 65, 85) : Color.Firebrick;
        var runtime = Path.Combine(
            NodePaths.ForCurrentMachine().RootDirectory,
            "runtimes",
            definition.RuntimeId,
            "Scripts",
            "python.exe");
        var runtimeState = File.Exists(runtime) ? "运行环境已安装" : "运行环境尚未安装";
        controls.Status.Text = location.Available
            ? $"{runtimeState}　·　{location.Source}"
            : $"{runtimeState}　·　{location.Detail}";
        controls.Status.ForeColor = location.Available ? Color.FromArgb(71, 85, 105) : Color.Firebrick;
        controls.Install.Enabled = location.Available && runtimeInstallStop is null;
        controls.Automatic.Enabled = runtimeInstallStop is null;
        if (controls.Choose is not null) controls.Choose.Enabled = runtimeInstallStop is null;
    }

    private void ResetSoftwareLocation(SoftwareDefinition definition)
    {
        try
        {
            SoftwareRuntimeManager.ClearConfiguredPath(definition);
            adapters.InvalidateRuntime(definition.RuntimeId);
            RefreshSoftwareCard(definition);
            RefreshCapabilitySummary();
        }
        catch (Exception exception) when (
            exception is IOException or SecurityException or UnauthorizedAccessException)
        {
            MessageBox.Show(exception.Message, "自动检测失败",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    private void ChooseSoftwareLocation(SoftwareDefinition definition)
    {
        string? selected = null;
        if (definition.PathKind == SoftwarePathKind.Executable)
        {
            using var dialog = new OpenFileDialog
            {
                Title = $"选择 {definition.DisplayName} 可执行文件",
                Filter = $"{definition.RequiredRelativePath}|{definition.RequiredRelativePath}|可执行文件 (*.exe)|*.exe",
                CheckFileExists = true,
                Multiselect = false,
            };
            if (dialog.ShowDialog(this) == DialogResult.OK) selected = dialog.FileName;
        }
        else if (definition.PathKind == SoftwarePathKind.Directory)
        {
            using var dialog = new FolderBrowserDialog
            {
                Description = $"选择 {definition.DisplayName} 安装根目录",
                UseDescriptionForTitle = true,
                ShowNewFolderButton = false,
            };
            if (dialog.ShowDialog(this) == DialogResult.OK) selected = dialog.SelectedPath;
        }
        if (selected is null) return;
        try
        {
            SoftwareRuntimeManager.SaveConfiguredPath(definition, selected);
            adapters.InvalidateRuntime(definition.RuntimeId);
            RefreshSoftwareCard(definition);
            RefreshCapabilitySummary();
        }
        catch (Exception exception) when (
            exception is ArgumentException
                or IOException
                or InvalidDataException
                or InvalidOperationException
                or SecurityException
                or UnauthorizedAccessException)
        {
            MessageBox.Show(exception.Message, "应用位置无效",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    private async Task InstallSoftwareRuntimeAsync(SoftwareDefinition definition)
    {
        if (adapters.All.Any(item =>
                definition.RuntimeId.Equals(item.RuntimeId, StringComparison.Ordinal)
                && item.HasActiveJobs))
        {
            MessageBox.Show(
                $"{definition.DisplayName} 仍有任务在执行，暂不能更换运行环境。",
                "暂不能安装", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            return;
        }
        var answer = MessageBox.Show(
            $"将为 {definition.DisplayName} 创建独立 Python 3.12 运行环境并安装固定依赖。是否继续？",
            "安装专业软件运行环境", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
            MessageBoxDefaultButton.Button2);
        if (answer != DialogResult.Yes) return;

        runtimeInstallStop = new CancellationTokenSource();
        RefreshSoftwareCards();
        var controls = softwareControls[definition.Id];
        controls.Status.ForeColor = Color.DarkOrange;
        var progress = new Progress<string>(line => controls.Status.Text = line);
        try
        {
            var result = await SoftwareRuntimeManager.InstallRuntimeAsync(
                definition,
                progress,
                runtimeInstallStop.Token);
            adapters.InvalidateRuntime(definition.RuntimeId);
            controls.Status.ForeColor = Color.ForestGreen;
            controls.Status.Text = result.Detail;
            MessageBox.Show(
                $"{result.Detail}\n{result.RuntimePath}",
                "安装完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        catch (OperationCanceledException)
        {
            controls.Status.ForeColor = Color.DimGray;
            controls.Status.Text = "安装已取消，原运行环境保持不变。";
        }
        catch (Exception exception) when (
            exception is IOException
                or InvalidDataException
                or InvalidOperationException
                or SecurityException
                or UnauthorizedAccessException
                or System.ComponentModel.Win32Exception)
        {
            controls.Status.ForeColor = Color.Firebrick;
            controls.Status.Text = $"安装失败：{exception.Message}";
            MessageBox.Show(exception.Message, "安装失败",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
        finally
        {
            runtimeInstallStop.Dispose();
            runtimeInstallStop = null;
            RefreshSoftwareCards();
            RefreshCapabilitySummary();
        }
    }

    private void RefreshCapabilitySummary()
    {
        capabilitySummary.Text = currentConfig is null
            ? "注册后启用"
            : FormatCapabilitySummary(adapters.All);
    }

    private void CopyDiagnostics()
    {
        if (currentConfig is null)
        {
            return;
        }
        try
        {
            Clipboard.SetText(BuildDiagnosticText(currentConfig));
            MessageBox.Show(
                "诊断信息已复制，可直接粘贴到 Codex 对话。内容不包含 Node Token。",
                "已复制", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
        catch (ExternalException exception)
        {
            MessageBox.Show(
                $"无法写入剪贴板：{exception.Message}",
                "复制失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    private string BuildDiagnosticText(NodeConfig config)
    {
        var paths = NodePaths.ForCurrentMachine();
        var builder = new StringBuilder()
            .AppendLine("zcbot Windows Node diagnostics")
            .AppendLine($"Node: {config.NodeName}")
            .AppendLine($"Node ID: {config.NodeId}")
            .AppendLine($"Server: {config.ServerUrl}")
            .AppendLine($"Node version: {Application.ProductVersion}")
            .AppendLine($"OS: {Environment.OSVersion.VersionString}")
            .AppendLine($"Data root: {paths.RootDirectory}")
            .AppendLine($"ANSYS gate: {AnsysGateState()}");
        foreach (var adapter in adapters.All.OrderBy(item => item.Capability, StringComparer.Ordinal))
        {
            try
            {
                var runtime = adapter.DetectRuntime();
                builder.AppendLine()
                    .AppendLine($"Capability: {adapter.Capability}")
                    .AppendLine($"Adapter: {adapter.AdapterVersion}")
                    .AppendLine($"Software: {runtime.Software} {runtime.SoftwareVersion ?? "unknown"}")
                    .AppendLine($"Health: {runtime.Health}")
                    .AppendLine($"Detail: {runtime.Detail}")
                    .AppendLine($"Runtime: {adapter.RuntimePath}")
                    .AppendLine($"Contract: {adapter.ContractPath}")
                    .AppendLine($"Contract SHA-256: {adapter.ContractSha256}")
                    .AppendLine(
                        $"Workspace protocol: {(adapter.WorkspaceStateFilename is null ? "disabled" : "v1")}")
                    .AppendLine(
                        $"Workspace state: {adapter.WorkspaceStateFilename ?? "none"}");
            }
            catch (Exception exception) when (
                exception is IOException
                    or InvalidOperationException
                    or UnauthorizedAccessException)
            {
                builder.AppendLine()
                    .AppendLine($"Capability: {adapter.Capability}")
                    .AppendLine($"Adapter: {adapter.AdapterVersion}")
                    .AppendLine($"Diagnostic error: {exception.Message}");
            }
        }
        return builder.ToString();
    }

    private async Task RunAnsysAcceptanceAsync()
    {
        var adapter = adapters.Find("ansys.mechanical.static_structural@v2");
        if (adapter?.SupportsLocalAcceptance != true)
        {
            MessageBox.Show(
                "当前安装包没有 ANSYS 验收工具。请先安装新版完整 Node 包。",
                "无法验收", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        using var outputDialog = new FolderBrowserDialog
        {
            Description = "选择保存 ANSYS 验收报告的父目录",
            UseDescriptionForTitle = true,
            ShowNewFolderButton = true,
        };
        if (outputDialog.ShowDialog(this) != DialogResult.OK)
        {
            return;
        }
        var answer = MessageBox.Show(
            "程序会使用内置标准试件执行一次进程树取消和连续 20 次真实静力求解，期间会获取并释放 ANSYS 许可证，可能耗时较长。确认开始？",
            "开始 ANSYS 真机验收", MessageBoxButtons.YesNo, MessageBoxIcon.Warning,
            MessageBoxDefaultButton.Button2);
        if (answer != DialogResult.Yes)
        {
            return;
        }

        var workRoot = Path.Combine(
            outputDialog.SelectedPath,
            $"zcbot-ansys-acceptance-{DateTime.Now:yyyyMMdd-HHmmss}");
        acceptanceStop?.Dispose();
        acceptanceStop = new CancellationTokenSource();
        runAnsysAcceptance.Enabled = false;
        enableAnsysGate.Enabled = false;
        ansysAcceptanceProgress.Visible = true;
        ansysAcceptanceStatus.ForeColor = Color.DarkOrange;
        ansysAcceptanceStatus.Text = "正在启动真机验收…";
        var progress = new Progress<string>(line =>
        {
            ansysAcceptanceStatus.Text = line;
        });
        try
        {
            var result = await adapter.RunLocalAcceptanceAsync(
                workRoot,
                progress,
                acceptanceStop.Token);
            passedAcceptanceReport = result.Passed ? result.ReportPath : null;
            enableAnsysGate.Enabled = result.Passed && AnsysGateState() != "enabled";
            ansysAcceptanceStatus.ForeColor = result.Passed ? Color.ForestGreen : Color.Firebrick;
            ansysAcceptanceStatus.Text = result.Passed
                ? $"验收通过。报告：{result.ReportPath}\n请确认许可证管理端席位已释放，再开启执行门。"
                : $"验收未通过：{result.Detail}\n报告：{result.ReportPath}";
        }
        catch (OperationCanceledException)
        {
            passedAcceptanceReport = null;
            ansysAcceptanceStatus.ForeColor = Color.DimGray;
            ansysAcceptanceStatus.Text = "验收已停止，执行门保持关闭。";
        }
        catch (Exception exception) when (
            exception is IOException
                or InvalidDataException
                or InvalidOperationException
                or JsonException
                or UnauthorizedAccessException)
        {
            passedAcceptanceReport = null;
            ansysAcceptanceStatus.ForeColor = Color.Firebrick;
            ansysAcceptanceStatus.Text = $"验收启动或执行失败：{exception.Message}";
        }
        finally
        {
            ansysAcceptanceProgress.Visible = false;
            runAnsysAcceptance.Enabled = true;
        }
    }

    private void EnableAnsysGate()
    {
        if (passedAcceptanceReport is null || !AcceptanceReportPassed(passedAcceptanceReport))
        {
            MessageBox.Show(
                "没有可验证的本次验收通过报告，执行门不会开启。",
                "无法开启", MessageBoxButtons.OK, MessageBoxIcon.Warning);
            return;
        }
        var answer = MessageBox.Show(
            "请先在许可证管理端确认正常求解和取消后的席位均已释放。开启后需要完全退出并重新启动 Node 才会进入实际调度。确认开启机器级执行门？",
            "开启 ANSYS 执行门", MessageBoxButtons.YesNo, MessageBoxIcon.Warning,
            MessageBoxDefaultButton.Button2);
        if (answer != DialogResult.Yes)
        {
            return;
        }
        try
        {
            Environment.SetEnvironmentVariable(
                "ZCBOT_ANSYS_242_VALIDATED", "1", EnvironmentVariableTarget.Machine);
            enableAnsysGate.Enabled = false;
            ansysAcceptanceStatus.ForeColor = Color.ForestGreen;
            ansysAcceptanceStatus.Text =
                "机器级执行门已写入。请从托盘退出 Node 后重新启动；重启后 ANSYS 应显示可用。";
        }
        catch (Exception exception) when (
            exception is SecurityException or UnauthorizedAccessException)
        {
            MessageBox.Show(
                $"写入机器级环境变量需要管理员权限：{exception.Message}\n请退出 Node，以管理员身份启动后再次点击。",
                "权限不足", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    private static bool AcceptanceReportPassed(string path)
    {
        try
        {
            using var document = JsonDocument.Parse(File.ReadAllBytes(path));
            return document.RootElement.TryGetProperty("passed", out var passed)
                && passed.ValueKind == JsonValueKind.True;
        }
        catch (Exception exception) when (
            exception is IOException or JsonException or UnauthorizedAccessException)
        {
            return false;
        }
    }

    private void UpdateAnsysAcceptanceStatus()
    {
        if (!ansysAcceptanceCard.Visible || ansysAcceptanceProgress.Visible)
        {
            return;
        }
        var gate = AnsysGateState();
        if (passedAcceptanceReport is not null
            && AcceptanceReportPassed(passedAcceptanceReport))
        {
            ansysAcceptanceStatus.ForeColor = Color.ForestGreen;
            ansysAcceptanceStatus.Text = gate == "enabled"
                ? "当前执行门：已开启。请完全退出并重新启动 Node，使新环境生效。"
                : $"验收通过。报告：{passedAcceptanceReport}\n请确认许可证管理端席位已释放，再开启执行门。";
            enableAnsysGate.Enabled = gate != "enabled";
            return;
        }
        ansysAcceptanceStatus.ForeColor = gate == "enabled"
            ? Color.ForestGreen
            : Color.FromArgb(71, 85, 105);
        ansysAcceptanceStatus.Text = gate == "enabled"
            ? "当前执行门：已开启。Node 重启并且 probe 正常后可接收 ANSYS 任务。"
            : "当前执行门：关闭。安装成功不代表验收通过，请先运行本页验收。";
        enableAnsysGate.Enabled = false;
    }

    private static string AnsysGateState() =>
        Environment.GetEnvironmentVariable(
            "ZCBOT_ANSYS_242_VALIDATED", EnvironmentVariableTarget.Machine) == "1"
            ? "enabled"
            : "disabled";

    private void RefreshJobs()
    {
        Guid? selectedId = jobGrid.SelectedRows.Count > 0
            && jobGrid.SelectedRows[0].Tag is JobDisplaySnapshot selected
                ? selected.JobId
                : null;
        IReadOnlyList<JobDisplaySnapshot> snapshots;
        try
        {
            snapshots = jobInbox.ReadJobSnapshots();
        }
        catch (Exception exception) when (
            exception is IOException or UnauthorizedAccessException)
        {
            jobSummary.Text = $"无法读取本机任务：{exception.Message}";
            return;
        }
        jobGrid.Rows.Clear();
        DataGridViewRow? rowToSelect = null;
        foreach (var snapshot in snapshots)
        {
            var index = jobGrid.Rows.Add(
                FormatJobStage(snapshot.Stage),
                snapshot.Title,
                snapshot.InputFilename,
                FormatProgress(snapshot),
                snapshot.AcceptedAt.LocalDateTime.ToString("MM-dd HH:mm"),
                snapshot.JobId.ToString("N")[..8]);
            var row = jobGrid.Rows[index];
            row.Tag = snapshot;
            row.DefaultCellStyle.ForeColor = snapshot.Stage switch
            {
                "failed" => Color.Firebrick,
                "cloud_terminal" => Color.Firebrick,
                "cancelled" => Color.DimGray,
                "succeeded" => Color.ForestGreen,
                _ => Color.FromArgb(30, 41, 59),
            };
            if (snapshot.JobId == selectedId)
            {
                rowToSelect = row;
            }
        }
        var active = snapshots.Count(item => item.IsActive);
        jobSummary.Text = snapshots.Count == 0
            ? "暂无本机任务"
            : $"活动任务 {active} 个 · 最近记录 {snapshots.Count} 条";
        if (rowToSelect is not null)
        {
            rowToSelect.Selected = true;
            jobGrid.CurrentCell = rowToSelect.Cells[0];
        }
        else if (jobGrid.Rows.Count > 0)
        {
            jobGrid.Rows[0].Selected = true;
            jobGrid.CurrentCell = jobGrid.Rows[0].Cells[0];
        }
        else
        {
            jobDetail.Text = "选择任务后可查看执行阶段、更新时间和完整 Job ID。";
        }
        ShowSelectedJob();
    }

    private void ShowSelectedJob()
    {
        if (jobGrid.SelectedRows.Count == 0
            || jobGrid.SelectedRows[0].Tag is not JobDisplaySnapshot snapshot)
        {
            return;
        }
        jobDetail.Text = string.Join("\n", [
            $"{FormatJobStage(snapshot.Stage)} · {snapshot.Detail}",
            $"能力：{snapshot.Capability}",
            $"Job ID：{snapshot.JobId}",
            $"更新时间：{snapshot.UpdatedAt.LocalDateTime:yyyy-MM-dd HH:mm:ss}",
        ]);
    }

    private static string FormatProgress(JobDisplaySnapshot snapshot) =>
        snapshot.Stage == "software_running"
            ? $"已执行 {FormatElapsed(DateTimeOffset.UtcNow - snapshot.UpdatedAt)}"
            : $"{snapshot.Progress}%";

    private static string FormatElapsed(TimeSpan elapsed) => elapsed.TotalHours >= 1
        ? $"{(int)elapsed.TotalHours}:{elapsed.Minutes:00}:{elapsed.Seconds:00}"
        : $"{elapsed.Minutes:00}:{elapsed.Seconds:00}";

    private static string FormatJobStage(string stage) => stage switch
    {
        "accepted" => "等待处理",
        "downloading_inputs" => "下载输入",
        "ready_to_run" => "准备执行",
        "software_running" => "软件执行中",
        "uploading_outputs" => "上传结果",
        "succeeded" => "成功",
        "failed" => "失败",
        "cloud_terminal" => "云端已终止",
        "cancelled" => "已取消",
        _ => stage,
    };

    private void ResetIdentity()
    {
        var answer = MessageBox.Show(
            "这会删除本机加密身份，随后需要使用新注册码重新注册。请先在管理后台删除或禁用云端旧节点。是否继续？",
            "清除本机节点身份", MessageBoxButtons.YesNo, MessageBoxIcon.Warning,
            MessageBoxDefaultButton.Button2);
        if (answer == DialogResult.Yes)
        {
            ResetIdentityRequested?.Invoke();
        }
    }

    private async Task RegisterAsync()
    {
        if (RegisterRequested is null)
        {
            return;
        }
        try
        {
            register.Enabled = false;
            detail.Text = "正在注册…";
            var options = EnrollOptions.Parse([
                "--server", server.Text, "--name", nodeName.Text, "--code", enrollmentCode.Text]);
            await RegisterRequested(options);
        }
        catch (Exception exception) when (exception is NodeConfigurationException or HttpRequestException)
        {
            MessageBox.Show(exception.Message, "注册失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
            register.Enabled = true;
        }
    }

    private void ToggleStartup()
    {
        if (changingStartup)
        {
            return;
        }
        try
        {
            StartupRegistration.SetEnabled(startAtLogin.Checked);
        }
        catch (Exception exception) when (exception is UnauthorizedAccessException or IOException)
        {
            MessageBox.Show(exception.Message, "自动启动设置失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
            changingStartup = true;
            try
            {
                startAtLogin.Checked = StartupRegistration.IsEnabled;
            }
            finally
            {
                changingStartup = false;
            }
        }
    }

    private static TableLayoutPanel CreateSection()
    {
        var section = new TableLayoutPanel
        {
            AutoSize = true,
            AutoSizeMode = AutoSizeMode.GrowAndShrink,
            Dock = DockStyle.Top,
            ColumnCount = 1,
            Padding = new Padding(4, 16, 4, 12),
            Margin = new Padding(0),
            BackColor = Color.White,
        };
        section.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
        return section;
    }

    private static TableLayoutPanel AddTab(TabControl tabs, string text)
    {
        var tab = new TabPage
        {
            Text = text,
            AutoScroll = true,
            BackColor = Color.White,
            Padding = new Padding(12, 0, 12, 0),
            UseVisualStyleBackColor = false,
        };
        var content = new TableLayoutPanel
        {
            AutoSize = true,
            AutoSizeMode = AutoSizeMode.GrowAndShrink,
            Dock = DockStyle.Top,
            ColumnCount = 1,
            BackColor = tab.BackColor,
            Margin = new Padding(0),
        };
        content.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
        tab.Controls.Add(content);
        tabs.TabPages.Add(tab);
        return content;
    }

    private static Label CreateSectionTitle(string text) => new()
    {
        Text = text,
        AutoSize = true,
        Font = new Font("Microsoft YaHei UI", 11, FontStyle.Bold),
        ForeColor = Color.FromArgb(30, 41, 59),
        Margin = new Padding(0, 0, 0, 8),
    };

    private static Label CreateSubsectionTitle(string text) => new()
    {
        Text = text,
        AutoSize = true,
        Font = new Font("Microsoft YaHei UI", 9, FontStyle.Bold),
        ForeColor = Color.FromArgb(51, 65, 85),
        Margin = new Padding(0, 0, 0, 7),
    };

    private static Label CreateBodyLabel() => new()
    {
        AutoSize = true,
        Dock = DockStyle.Fill,
        ForeColor = Color.FromArgb(71, 85, 105),
        Margin = new Padding(0, 2, 0, 5),
    };

    private static Label CreateHint(string text) => new()
    {
        Text = text,
        AutoSize = true,
        Dock = DockStyle.Fill,
        ForeColor = Color.FromArgb(100, 116, 139),
        Margin = new Padding(0, 2, 0, 4),
    };

    private static DataGridView CreateJobGrid()
    {
        var grid = new DataGridView
        {
            Height = 238,
            Dock = DockStyle.Top,
            Margin = new Padding(0, 8, 0, 8),
            BackgroundColor = Color.White,
            BorderStyle = BorderStyle.FixedSingle,
            AllowUserToAddRows = false,
            AllowUserToDeleteRows = false,
            AllowUserToResizeRows = false,
            AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None,
            ColumnHeadersHeight = 34,
            EnableHeadersVisualStyles = false,
            MultiSelect = false,
            ReadOnly = true,
            RowHeadersVisible = false,
            RowTemplate = { Height = 34 },
            SelectionMode = DataGridViewSelectionMode.FullRowSelect,
        };
        grid.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(241, 245, 249);
        grid.ColumnHeadersDefaultCellStyle.ForeColor = Color.FromArgb(51, 65, 85);
        grid.DefaultCellStyle.SelectionBackColor = Color.FromArgb(219, 234, 254);
        grid.DefaultCellStyle.SelectionForeColor = Color.FromArgb(30, 64, 175);
        grid.Columns.Add(new DataGridViewTextBoxColumn
        {
            HeaderText = "状态",
            Width = 105,
        });
        grid.Columns.Add(new DataGridViewTextBoxColumn
        {
            HeaderText = "任务",
            AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
            MinimumWidth = 170,
        });
        grid.Columns.Add(new DataGridViewTextBoxColumn
        {
            HeaderText = "输入",
            Width = 125,
        });
        grid.Columns.Add(new DataGridViewTextBoxColumn
        {
            HeaderText = "进度",
            Width = 95,
        });
        grid.Columns.Add(new DataGridViewTextBoxColumn
        {
            HeaderText = "接收时间",
            Width = 100,
        });
        grid.Columns.Add(new DataGridViewTextBoxColumn
        {
            HeaderText = "Job ID",
            Width = 76,
        });
        return grid;
    }

    private void RefreshDataRoot()
    {
        var selection = NodeDataRootSettings.Resolve();
        dataRoot.Text = selection.RootDirectory;
        changeDataRoot.Enabled = !selection.IsEnvironmentManaged && !dataRootMigrationInProgress;
        dataRootStatus.Text = selection.IsEnvironmentManaged
            ? $"由机器环境变量 {NodeDataRootSettings.EnvironmentVariableName} 管理，界面不可修改。"
            : selection.Source == "user"
                ? "使用当前 Windows 专用账号保存的自定义目录。"
                : "使用默认目录。";
    }

    private async Task ChangeDataRootAsync()
    {
        if (DataRootMigrationRequested is null)
        {
            return;
        }
        try
        {
            if (jobInbox.HasPendingJobs)
            {
                MessageBox.Show(
                    "本机仍有未完成任务，暂不能迁移数据目录。请等待任务结束后重试。",
                    "暂不能迁移", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }
        }
        catch (Exception exception) when (exception is IOException or UnauthorizedAccessException)
        {
            MessageBox.Show(
                $"无法确认本机任务状态，数据目录不会迁移：{exception.Message}",
                "暂不能迁移", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        using var dialog = new FolderBrowserDialog
        {
            Description = "选择新的 zcbot Windows Node 数据目录（必须为空）",
            UseDescriptionForTitle = true,
            SelectedPath = dataRoot.Text,
            ShowNewFolderButton = true,
        };
        if (dialog.ShowDialog(this) != DialogResult.OK)
        {
            return;
        }

        string target;
        try
        {
            target = NodeDataRootSettings.Normalize(dialog.SelectedPath);
        }
        catch (NodeConfigurationException exception)
        {
            MessageBox.Show(exception.Message, "目录无效", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }
        if (string.Equals(target, dataRoot.Text, StringComparison.OrdinalIgnoreCase))
        {
            MessageBox.Show("所选目录与当前数据目录相同。", "无需迁移",
                MessageBoxButtons.OK, MessageBoxIcon.Information);
            return;
        }
        var answer = MessageBox.Show(
            $"Node 将先停止接收任务，把现有数据复制并校验到：\n{target}\n\n"
            + "校验成功后切换目录并重启；旧目录不会自动删除。是否继续？",
            "迁移数据目录", MessageBoxButtons.YesNo, MessageBoxIcon.Warning,
            MessageBoxDefaultButton.Button2);
        if (answer != DialogResult.Yes)
        {
            return;
        }

        try
        {
            dataRootMigrationInProgress = true;
            changeDataRoot.Enabled = false;
            dataRootStatus.Text = "正在停止调度并复制、校验数据，请勿退出 Node…";
            var result = await DataRootMigrationRequested(target);
            MessageBox.Show(
                $"数据目录迁移完成：{result.FileCount} 个文件，{FormatBytes(result.TotalBytes)}。\n"
                + $"新目录：{result.TargetDirectory}\n\n旧目录仍保留：{result.SourceDirectory}\n"
                + "确认新节点运行正常后，可由管理员手工清理旧目录。Node 现在将重启。",
                "迁移完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
            Application.Restart();
        }
        catch (Exception exception) when (
            exception is IOException
                or NodeConfigurationException
                or SecurityException
                or UnauthorizedAccessException)
        {
            dataRootMigrationInProgress = false;
            RefreshDataRoot();
            MessageBox.Show(
                $"数据目录未切换，原目录继续有效：{exception.Message}",
                "迁移失败", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    private void OpenDataRoot()
    {
        try
        {
            Directory.CreateDirectory(dataRoot.Text);
            var startInfo = new ProcessStartInfo
            {
                FileName = Path.Combine(
                    Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"),
                UseShellExecute = false,
            };
            startInfo.ArgumentList.Add(dataRoot.Text);
            Process.Start(startInfo);
        }
        catch (Exception exception) when (
            exception is IOException or UnauthorizedAccessException or InvalidOperationException)
        {
            MessageBox.Show(exception.Message, "无法打开数据目录",
                MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    private static string FormatBytes(long bytes) => bytes >= 1024L * 1024 * 1024
        ? $"{bytes / (1024d * 1024 * 1024):F2} GiB"
        : bytes >= 1024L * 1024
            ? $"{bytes / (1024d * 1024):F1} MiB"
            : $"{bytes / 1024d:F1} KiB";

    private static Panel CreateDivider() => new()
    {
        Height = 1,
        Dock = DockStyle.Top,
        BackColor = Color.FromArgb(226, 232, 240),
        Margin = new Padding(0, 10, 0, 12),
    };

    private static TextBox CreateTextBox(string text = "", bool usePassword = false) => new()
    {
        Text = text,
        UseSystemPasswordChar = usePassword,
        AutoSize = false,
        Height = 34,
        BorderStyle = BorderStyle.FixedSingle,
        Font = new Font("Segoe UI", 10),
    };

    private static Button CreateButton(
        string text,
        int width,
        bool primary = false,
        bool danger = false)
    {
        var button = new Button
        {
            Text = text,
            AutoSize = false,
            Size = new Size(width, 38),
            FlatStyle = FlatStyle.Flat,
            BackColor = primary ? Color.FromArgb(37, 99, 235) : Color.White,
            ForeColor = primary
                ? Color.White
                : danger ? Color.FromArgb(185, 28, 28) : Color.FromArgb(51, 65, 85),
            Font = new Font("Microsoft YaHei UI", 9),
            Margin = new Padding(0, 0, 10, 0),
            Cursor = Cursors.Hand,
        };
        button.FlatAppearance.BorderColor = primary
            ? Color.FromArgb(37, 99, 235)
            : danger ? Color.FromArgb(254, 202, 202) : Color.FromArgb(203, 213, 225);
        button.FlatAppearance.MouseOverBackColor = primary
            ? Color.FromArgb(29, 78, 216)
            : danger ? Color.FromArgb(254, 242, 242) : Color.FromArgb(248, 250, 252);
        return button;
    }

    private static FlowLayoutPanel CreateActions() => new()
    {
        AutoSize = true,
        FlowDirection = FlowDirection.LeftToRight,
        WrapContents = true,
        Dock = DockStyle.Fill,
        Margin = new Padding(0, 10, 0, 0),
    };

    private static void AddField(
        TableLayoutPanel layout, string label, Control control, string hint)
    {
        layout.Controls.Add(new Label
        {
            Text = label,
            AutoSize = true,
            Font = new Font("Microsoft YaHei UI", 9, FontStyle.Bold),
            ForeColor = Color.FromArgb(51, 65, 85),
            Margin = new Padding(0, 8, 0, 4),
        });
        control.Dock = DockStyle.Top;
        control.Margin = new Padding(0, 0, 0, 2);
        layout.Controls.Add(control);
        layout.Controls.Add(CreateHint(hint));
    }

    private sealed record SoftwareControls(
        TextBox Path,
        Label Status,
        Button Automatic,
        Button? Choose,
        Button Install);

    private sealed record CapabilitySummaryEntry(
        string SoftwareName,
        string? SoftwareVersion,
        string CapabilityName,
        bool Ready);

    private sealed class BorderedTableLayoutPanel : TableLayoutPanel
    {
        internal Color BorderColor = Color.FromArgb(226, 232, 240);

        internal BorderedTableLayoutPanel()
        {
            DoubleBuffered = true;
        }

        protected override void OnPaint(PaintEventArgs eventArgs)
        {
            base.OnPaint(eventArgs);
            using var pen = new Pen(BorderColor);
            eventArgs.Graphics.DrawRectangle(
                pen,
                0,
                0,
                Math.Max(0, ClientSize.Width - 1),
                Math.Max(0, ClientSize.Height - 1));
        }
    }

    private sealed class NavigationTabControl : TabControl
    {
        internal NavigationTabControl()
        {
            Appearance = TabAppearance.FlatButtons;
            DrawMode = TabDrawMode.OwnerDrawFixed;
            HotTrack = true;
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        }

        protected override void OnDrawItem(DrawItemEventArgs eventArgs)
        {
            var selected = eventArgs.Index == SelectedIndex;
            var bounds = eventArgs.Bounds;
            using var background = new SolidBrush(Color.White);
            eventArgs.Graphics.FillRectangle(background, bounds);
            TextRenderer.DrawText(
                eventArgs.Graphics,
                TabPages[eventArgs.Index].Text,
                Font,
                bounds,
                selected ? Color.FromArgb(29, 78, 216) : Color.FromArgb(71, 85, 105),
                TextFormatFlags.HorizontalCenter
                    | TextFormatFlags.VerticalCenter
                    | TextFormatFlags.SingleLine);
            if (selected)
            {
                using var accent = new SolidBrush(Color.FromArgb(37, 99, 235));
                eventArgs.Graphics.FillRectangle(
                    accent,
                    bounds.Left + 12,
                    bounds.Bottom - 3,
                    Math.Max(0, bounds.Width - 24),
                    3);
            }
        }
    }
}
