using Sdt.Config;
using Sdt.Core;
using Sdt.Core.Debug;
using Spectre.Console;

namespace Sdt.Tui;

internal sealed record MenuItem(string Display, string Value);

public enum AppExitReason { Quit, SwitchProject }
public sealed record AppResult(AppExitReason Reason, string? NewProjectRoot = null);

public sealed class App
{
    private DevToolConfig _config;
    private string _projectRoot;
    private readonly WorkspaceConfig? _workspace;
    private readonly string? _workspaceRoot;
    private List<WorkflowDefinition> _workflows;
    private readonly List<string> _warnings;
    private readonly IDebugProfileRunner _debugRunner;
    private readonly IDiagnosticsBundleService _diagnostics;
    private readonly IRequirementResolver _requirementResolver = new RequirementResolver();

    private readonly WorkflowExecutor _executor = new(
        new WorkflowPlanner(),
        new ToolProbeService(),
        new PrereqInstallerService(),
        new ActionRunner(),
        new RequirementResolver());

    private IReadOnlyDictionary<string, WorkflowDefinition> WorkflowMap =>
        _workflows.ToDictionary(t => t.Id, StringComparer.OrdinalIgnoreCase);

    public App(
        DevToolConfig config,
        string projectRoot,
        IReadOnlyList<string>? warnings = null,
        WorkspaceConfig? workspace = null,
        string? workspaceRoot = null)
    {
        _config = config;
        _projectRoot = projectRoot;
        _workspace = workspace;
        _workspaceRoot = workspaceRoot;
        var normalized = WorkflowModelBuilder.Normalize(config, ResolveLegacyMode(), _requirementResolver);
        _workflows = normalized.Workflows.ToList();
        _warnings = [];
        _debugRunner = new DebugProfileRunner(new ToolProbeService(), new PrereqInstallerService());
        _diagnostics = new DiagnosticsBundleService();
        if (warnings is not null)
            _warnings.AddRange(warnings);
        _warnings.AddRange(normalized.Warnings);
    }

    private static LegacyMode ResolveLegacyMode()
    {
        var raw = Environment.GetEnvironmentVariable("SDT_LEGACY_MODE");
        return string.Equals(raw, "compat", StringComparison.OrdinalIgnoreCase)
            ? LegacyMode.Compat
            : LegacyMode.Strict;
    }

    public async Task<AppResult> RunAsync()
    {
        while (true)
        {
            AnsiConsole.Clear();
            RenderBanner();

            if (_warnings.Count > 0)
            {
                foreach (var warning in _warnings.Distinct(StringComparer.OrdinalIgnoreCase))
                    AnsiConsole.MarkupLine(Theme.Warn("Config warning: " + warning));
                AnsiConsole.WriteLine();
            }

            var choice = ShowMainMenu();
            switch (choice)
            {
                case "__env__":
                    EditEnvironment();
                    break;

                case "__toolchains__":
                    await new ToolchainScreen(_config, _projectRoot).RunAsync();
                    break;

                case "__doctor__":
                    await RunConfigDoctorAsync();
                    break;

                case "__events__":
                    new EventsScreen(_projectRoot, _config.Name, _config.Version).Run();
                    break;

                case "__workspace__":
                    if (_workspace is not null && _workspaceRoot is not null)
                    {
                        var switcher = new WorkspaceScreen(_workspace, _workspaceRoot, _projectRoot);
                        var newRoot = switcher.SelectProject();
                        if (newRoot is not null)
                            return new AppResult(AppExitReason.SwitchProject, newRoot);
                    }
                    break;

                case "__migrate_legacy__":
                    ApplyLegacyMigration();
                    break;

                case "__quit__":
                    AnsiConsole.MarkupLine("\n" + Theme.Faint("Later.") + "\n");
                    return new AppResult(AppExitReason.Quit);

                default:
                    if (choice.StartsWith("__debugattach__:", StringComparison.Ordinal))
                    {
                        var profileId = choice["__debugattach__:".Length..];
                        ShowAttachInstructions(profileId);
                        break;
                    }

                    if (choice.StartsWith("__debug__:", StringComparison.Ordinal))
                    {
                        var parts = choice.Split(':', 3, StringSplitOptions.None);
                        if (parts.Length == 3)
                            await RunDebugProfileAsync(parts[1], string.Equals(parts[2], "verbose", StringComparison.OrdinalIgnoreCase));
                        break;
                    }

                    var workflowMap = WorkflowMap;
                    if (workflowMap.TryGetValue(choice, out var workflow))
                        await RunWorkflowAsync(workflow, workflowMap);
                    break;
            }

            if (choice != "__quit__")
            {
                AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to return to the menu..."));
                Console.ReadKey(intercept: true);
            }
        }
    }

    private void RenderBanner()
    {
        AnsiConsole.Write(new FigletText("SDT").Color(Theme.GreenColor));
        var wsInfo = _workspace is not null
            ? $" [{Theme.GreenDim}]∙ {Markup.Escape(_workspace.Name)}[/]"
            : string.Empty;

        AnsiConsole.Write(
            new Rule($"[bold {Theme.GreenBold}]{Markup.Escape(_config.Name)}[/]  [{Theme.GreenDim}]v{Markup.Escape(_config.Version)}[/]{wsInfo}")
                .RuleStyle(Theme.DimStyle));
        AnsiConsole.MarkupLine(Theme.Faint($"root: {_projectRoot}") + "\n");
    }

    private string ShowMainMenu()
    {
        var prompt = new SelectionPrompt<MenuItem>()
            .Title($"[{Theme.Green}]What would you like to do?[/]")
            .PageSize(28)
            .MoreChoicesText(Theme.Faint("(scroll to see more)"))
            .UseConverter(m => m.Display);

        var groups = _workflows
            .Where(t => !string.IsNullOrWhiteSpace(t.Label))
            .GroupBy(t => string.IsNullOrWhiteSpace(t.Group) ? "General" : t.Group);

        foreach (var group in groups)
        {
            var header = new MenuItem(
                $"[bold {Theme.Amber}]{Markup.Escape(group.Key.ToUpperInvariant())}[/]",
                "__group__");

            var items = group.Select(t => new MenuItem(
                $"[{Theme.Green}]{Markup.Escape(t.Label)}[/]  [{Theme.GreenDim}]{Markup.Escape(t.Description)}[/]",
                t.Id)).ToList();

            prompt.AddChoiceGroup(header, items);
        }

        var debugProfiles = _config.Debug?.Profiles ?? [];
        if (debugProfiles.Count > 0)
        {
            var debugItems = new List<MenuItem>();
            foreach (var profile in debugProfiles)
            {
                debugItems.Add(new MenuItem(
                    $"[{Theme.Green}]Run {Markup.Escape(profile.Label)}[/]  [{Theme.GreenDim}]debug profile[/]",
                    $"__debug__:{profile.Id}:normal"));
                debugItems.Add(new MenuItem(
                    $"[{Theme.Green}]Run {Markup.Escape(profile.Label)} (verbose)[/]  [{Theme.GreenDim}]stream full output[/]",
                    $"__debug__:{profile.Id}:verbose"));
                if (profile.Attach is not null)
                {
                    debugItems.Add(new MenuItem(
                        $"[{Theme.Green}]Attach instructions: {Markup.Escape(profile.Label)}[/]",
                        $"__debugattach__:{profile.Id}"));
                }
            }

            prompt.AddChoiceGroup(
                new MenuItem($"[bold {Theme.Amber}]DEBUG[/]", "__group__"),
                debugItems);
        }

        var systemItems = new List<MenuItem>
        {
            new($"[{Theme.Green}]🩺  Run config doctor[/]  [{Theme.GreenDim}]validate config, tools, paths[/]", "__doctor__"),
            new($"[{Theme.Green}]📜  View run events[/]  [{Theme.GreenDim}].sdt/events JSONL viewer[/]", "__events__"),
            new($"[{Theme.Green}]⚙  Edit environment variables[/]", "__env__"),
        };

        if (_config.Toolchains is not null)
            systemItems.Insert(0, new MenuItem(
                $"[{Theme.Green}]⬡  Toolchain management[/]  [{Theme.GreenDim}]python / node[/]",
                "__toolchains__"));

        if (_workspace is not null)
            systemItems.Insert(0, new MenuItem(
                $"[{Theme.Green}]⇄  Workspace projects[/]  [{Theme.GreenDim}]{Markup.Escape(_workspace.Name)}[/]",
                "__workspace__"));

        if (_config.Targets.Count > 0)
            systemItems.Insert(0, new MenuItem(
                $"[{Theme.Green}]⇪  Migrate legacy targets → workflows[/]  [{Theme.GreenDim}]writes devtool.json + backup[/]",
                "__migrate_legacy__"));

        systemItems.Add(new MenuItem($"[{Theme.GreenDim}]✗  Quit[/]", "__quit__"));

        prompt.AddChoiceGroup(
            new MenuItem($"[bold {Theme.Amber}]SYSTEM[/]", "__group__"),
            systemItems);

        return AnsiConsole.Prompt(prompt).Value;
    }

    private async Task RunWorkflowAsync(
        WorkflowDefinition workflow,
        IReadOnlyDictionary<string, WorkflowDefinition> workflowMap)
    {
        AnsiConsole.Clear();
        AnsiConsole.Write(Theme.SectionRule(workflow.Label));

        var plan = new WorkflowPlanner().ResolvePlan(workflow, workflowMap);
        if (plan.Count == 0)
        {
            AnsiConsole.MarkupLine(Theme.Warn("This workflow has no executable steps."));
            return;
        }

        if (plan.Count > 1)
        {
            AnsiConsole.MarkupLine(Theme.Faint($"Execution plan — {plan.Count} workflow(s):"));
            foreach (var item in plan)
                AnsiConsole.MarkupLine($"  [{Theme.GreenDim}]→[/] [{Theme.Green}]{Markup.Escape(item.Label)}[/]");
            AnsiConsole.WriteLine();
        }

        var outputLines = new List<string>();
        using var eventRecorder = RunEventJsonlRecorder.Create(_projectRoot, "workflow");

        var result = await _executor.ExecuteAsync(
            workflow,
            workflowMap,
            _config,
            _projectRoot,
            confirmInstallAsync: ConfirmInstallAsync,
            onOutput: (line, isErr) =>
            {
                outputLines.Add((isErr ? "ERR: " : "OUT: ") + line);
                var escaped = Markup.Escape(line);
                AnsiConsole.MarkupLine(isErr
                    ? $"[{Theme.Amber}]{escaped}[/]"
                    : $"[{Theme.Green}]{escaped}[/]");
            },
            onEvent: evt =>
            {
                eventRecorder.Write(evt);
                RenderRunEvent(evt);
            });

        AnsiConsole.Write(Theme.SectionRule());
        RenderStepSummary(result);
        RenderProbeDiagnosticsSummary(outputLines);
        if (result.Success)
        {
            var totalSeconds = result.Steps.Sum(s => s.Result.Elapsed.TotalSeconds);
            AnsiConsole.MarkupLine("\n" + Theme.Ok($"Done!  Total: {totalSeconds:F1}s"));
            AnsiConsole.MarkupLine(Theme.Faint($"Run events: {eventRecorder.FilePath}"));
            return;
        }

        AnsiConsole.MarkupLine("\n" + Theme.Fail($"{result.StopReason}: {result.Message}"));
        AnsiConsole.MarkupLine(Theme.Faint($"Run events: {eventRecorder.FilePath}"));
        await WriteWorkflowDiagnosticsAsync(workflow, workflowMap, result, outputLines);
    }

    private static void RenderStepSummary(WorkflowExecutionResult result)
    {
        if (result.Steps.Count == 0)
            return;

        var table = new Table()
            .Border(TableBorder.Rounded)
            .BorderStyle(Theme.DimStyle)
            .AddColumn(new TableColumn($"[{Theme.Amber}]Workflow[/]"))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Step[/]"))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Exit[/]").Width(8))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Seconds[/]").Width(10))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(12));

        foreach (var step in result.Steps)
        {
            var ok = step.Result.Success;
            table.AddRow(
                Theme.Faint(step.WorkflowId),
                Theme.G(step.StepLabel),
                Theme.Bold(step.Result.ExitCode.ToString()),
                Theme.Faint($"{step.Result.Elapsed.TotalSeconds:F1}"),
                ok ? Theme.Ok("ok") : Theme.Fail("failed"));
        }

        AnsiConsole.Write(table);
    }

    private async Task RunDebugProfileAsync(string profileId, bool verbose)
    {
        var profile = _config.Debug?.Profiles.FirstOrDefault(p => string.Equals(p.Id, profileId, StringComparison.OrdinalIgnoreCase));
        if (profile is null)
        {
            AnsiConsole.MarkupLine(Theme.Fail($"Debug profile not found: {profileId}"));
            return;
        }

        AnsiConsole.Clear();
        AnsiConsole.Write(Theme.SectionRule($"DEBUG — {profile.Label}"));
        using var eventRecorder = RunEventJsonlRecorder.Create(_projectRoot, "debug");

        if (profile.Attach is not null)
        {
            AnsiConsole.MarkupLine(Theme.Faint($"Attach: {profile.Attach.Kind}"));
            if (profile.Attach.Port is not null)
                AnsiConsole.MarkupLine(Theme.Faint($"Port: {profile.Attach.Port}"));
            if (!string.IsNullOrWhiteSpace(profile.Attach.ProcessName))
                AnsiConsole.MarkupLine(Theme.Faint($"Process: {profile.Attach.ProcessName}"));
            if (!string.IsNullOrWhiteSpace(profile.Attach.Note))
                AnsiConsole.MarkupLine(Theme.Faint(profile.Attach.Note!));
            AnsiConsole.WriteLine();
        }

        var result = await _debugRunner.RunAsync(
            profile,
            _config,
            _projectRoot,
            verbose,
            ConfirmInstallAsync,
            (line, isErr) =>
            {
                var escaped = Markup.Escape(line);
                AnsiConsole.MarkupLine(isErr
                    ? $"[{Theme.Amber}]{escaped}[/]"
                    : $"[{Theme.Green}]{escaped}[/]");
            },
            evt =>
            {
                eventRecorder.Write(evt);
                RenderRunEvent(evt);
            });

        if (result.Success)
        {
            var seconds = result.RunResult?.Elapsed.TotalSeconds ?? 0;
            AnsiConsole.MarkupLine("\n" + Theme.Ok($"Debug run completed in {seconds:F1}s"));
            AnsiConsole.MarkupLine(Theme.Faint($"Run events: {eventRecorder.FilePath}"));
            return;
        }

        AnsiConsole.MarkupLine("\n" + Theme.Fail($"{result.StopReason}: {result.Message}"));
        AnsiConsole.MarkupLine(Theme.Faint($"Run events: {eventRecorder.FilePath}"));

        var diagnostics = _config.Debug?.Diagnostics ?? new DebugDiagnosticsOptions();
        if (!diagnostics.Enabled || !diagnostics.BundleOnFailure)
            return;

        var bundle = await _diagnostics.WriteBundleAsync(
            new DiagnosticsBundleRequest(
                Category: "debug",
                ProjectRoot: _projectRoot,
                SummaryMessage: result.Message,
                OutputLines: result.OutputLines,
                WorkflowSteps: [],
                Probes: result.Probes,
                DiagnosticsOptions: diagnostics,
                Config: _config,
                StopReason: result.StopReason,
                DebugRun: result.RunResult,
                DebugProfile: result.Profile));

        if (bundle.Success)
            AnsiConsole.MarkupLine(Theme.Warn($"Diagnostics bundle: {bundle.BundleDirectory}"));
        else
            AnsiConsole.MarkupLine(Theme.Warn($"Diagnostics bundle failed: {bundle.Message}"));
    }

    private static void RenderProbeDiagnosticsSummary(IReadOnlyList<string> outputLines)
    {
        var diagnostics = new List<(string Tool, string Detail)>();
        foreach (var line in outputLines)
        {
            const string marker = "Probe detail [";
            var markerIndex = line.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
            if (markerIndex < 0)
                continue;

            var toolStart = markerIndex + marker.Length;
            var toolEnd = line.IndexOf(']', toolStart);
            if (toolEnd <= toolStart)
                continue;

            var tool = line[toolStart..toolEnd].Trim();
            var detailStart = line.IndexOf(':', toolEnd);
            var detail = detailStart >= 0 && detailStart + 1 < line.Length
                ? line[(detailStart + 1)..].Trim()
                : "";

            if (!string.IsNullOrWhiteSpace(tool))
                diagnostics.Add((tool, detail));
        }

        if (diagnostics.Count == 0)
            return;

        var table = new Table()
            .Border(TableBorder.Rounded)
            .BorderStyle(Theme.DimStyle)
            .AddColumn(new TableColumn($"[{Theme.Amber}]Probe Tool[/]").Width(14))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Resolver / Probe Detail[/]"))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Suggested Fix[/]"));

        foreach (var diag in diagnostics.Distinct())
        {
            table.AddRow(
                Theme.Warn(diag.Tool),
                Theme.Faint(diag.Detail),
                Theme.Faint(GetProbeFixHint(diag.Tool, diag.Detail)));
        }

        AnsiConsole.WriteLine();
        AnsiConsole.Write(Theme.SectionRule("PROBE DIAGNOSTICS"));
        AnsiConsole.Write(table);
    }

    private static string GetProbeFixHint(string tool, string detail)
    {
        if (detail.Contains("ConfiguredOverride", StringComparison.OrdinalIgnoreCase))
            return "Check tooling.tools[].executables paths.";
        if (detail.Contains("NodeAdjacentShim", StringComparison.OrdinalIgnoreCase))
            return "Node found; verify npm/yarn shim in node directory.";
        if (detail.Contains("Fallback", StringComparison.OrdinalIgnoreCase))
            return $"Add {tool} to PATH or set tooling.tools[].executables.";
        return $"Install/configure {tool} then rerun.";
    }

    private static void RenderRunEvent(RunEvent evt)
    {
        var shouldRender = evt.Type is
            RunEventType.WorkflowStarted or
            RunEventType.WorkflowStepStarted or
            RunEventType.WorkflowStepCompleted or
            RunEventType.ProbeFailed or
            RunEventType.InstallPlanPrepared or
            RunEventType.DebugStarted or
            RunEventType.DebugCommandStarted or
            RunEventType.DebugCommandCompleted or
            RunEventType.WorkflowCompleted or
            RunEventType.DebugCompleted;

        if (!shouldRender)
            return;

        var prefix = evt.Category.Equals("debug", StringComparison.OrdinalIgnoreCase) ? "DBG" : "RUN";
        var tone = evt.Success is false ? Theme.Amber : Theme.GreenDim;
        AnsiConsole.MarkupLine($"[{tone}]{prefix} {Markup.Escape(evt.Message)}[/]");
    }

    private void ShowAttachInstructions(string profileId)
    {
        var profile = _config.Debug?.Profiles.FirstOrDefault(p => string.Equals(p.Id, profileId, StringComparison.OrdinalIgnoreCase));
        if (profile?.Attach is null)
        {
            AnsiConsole.MarkupLine(Theme.Warn("No attach instructions configured for this profile."));
            return;
        }

        AnsiConsole.Clear();
        AnsiConsole.Write(Theme.SectionRule($"ATTACH — {profile.Label}"));
        AnsiConsole.MarkupLine(Theme.Faint($"Kind: {profile.Attach.Kind}"));
        if (profile.Attach.Port is not null)
            AnsiConsole.MarkupLine(Theme.Faint($"Port: {profile.Attach.Port}"));
        if (!string.IsNullOrWhiteSpace(profile.Attach.ProcessName))
            AnsiConsole.MarkupLine(Theme.Faint($"Process: {profile.Attach.ProcessName}"));
        if (!string.IsNullOrWhiteSpace(profile.Attach.Note))
            AnsiConsole.MarkupLine(Theme.G(profile.Attach.Note!));
    }

    private async Task WriteWorkflowDiagnosticsAsync(
        WorkflowDefinition workflow,
        IReadOnlyDictionary<string, WorkflowDefinition> workflowMap,
        WorkflowExecutionResult result,
        IReadOnlyList<string> outputLines)
    {
        var diagnostics = _config.Debug?.Diagnostics ?? new DebugDiagnosticsOptions();
        if (!diagnostics.Enabled || !diagnostics.BundleOnFailure)
            return;

        var probes = await SnapshotWorkflowToolsAsync(workflow, workflowMap);

        var bundle = await _diagnostics.WriteBundleAsync(
            new DiagnosticsBundleRequest(
                Category: "workflow",
                ProjectRoot: _projectRoot,
                SummaryMessage: result.Message,
                OutputLines: outputLines,
                WorkflowSteps: result.Steps,
                Probes: probes,
                DiagnosticsOptions: diagnostics,
                Config: _config,
                StopReason: result.StopReason));

        if (bundle.Success)
            AnsiConsole.MarkupLine(Theme.Warn($"Diagnostics bundle: {bundle.BundleDirectory}"));
        else
            AnsiConsole.MarkupLine(Theme.Warn($"Diagnostics bundle failed: {bundle.Message}"));
    }

    private async Task<IReadOnlyList<ProbeResult>> SnapshotWorkflowToolsAsync(
        WorkflowDefinition workflow,
        IReadOnlyDictionary<string, WorkflowDefinition> workflowMap)
    {
        var plan = new WorkflowPlanner().ResolvePlan(workflow, workflowMap);
        var tools = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var item in plan)
        {
            foreach (var step in item.Steps)
            {
                foreach (var req in _requirementResolver.Resolve(step))
                {
                    if (!string.IsNullOrWhiteSpace(req.Tool))
                        tools.Add(req.Tool);
                }
            }
        }

        var probeService = new ToolProbeService();
        var probes = new List<ProbeResult>();
        foreach (var tool in tools)
            probes.Add(await probeService.ProbeAsync(tool, _projectRoot, _config));
        return probes;
    }

    private Task<bool> ConfirmInstallAsync(string tool, InstallPlan plan)
    {
        AnsiConsole.MarkupLine(Theme.Warn($"Missing prerequisite: {tool}"));
        AnsiConsole.MarkupLine(Theme.Faint(plan.Summary));
        foreach (var cmd in plan.Commands)
            AnsiConsole.MarkupLine(Theme.Faint($"  $ {cmd.Command} {string.Join(" ", cmd.Args)}"));

        var allow = AnsiConsole.Confirm(
            $"[{Theme.Amber}]Run install commands for {Markup.Escape(tool)} now?[/]",
            defaultValue: false);
        return Task.FromResult(allow);
    }

    private async Task RunConfigDoctorAsync()
    {
        AnsiConsole.Clear();
        AnsiConsole.Write(Theme.SectionRule("CONFIG DOCTOR"));
        AnsiConsole.WriteLine();

        var service = new ConfigDoctorService(new ToolProbeService(), _requirementResolver);
        var report = await service.RunAsync(_config, _projectRoot);

        var table = new Table()
            .Border(TableBorder.Rounded)
            .BorderStyle(Theme.DimStyle)
            .AddColumn(new TableColumn($"[{Theme.Amber}]Check[/]").Width(26))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Status[/]").Width(10))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Detail[/]"))
            .AddColumn(new TableColumn($"[{Theme.Amber}]Fix[/]"));

        foreach (var check in report.Checks)
        {
            var statusText = check.Status switch
            {
                DoctorStatus.Pass => Theme.Ok("ok"),
                DoctorStatus.Warn => Theme.Warn("warn"),
                DoctorStatus.Fail => Theme.Fail("fail"),
                _ => Theme.Faint("n/a"),
            };

            table.AddRow(
                Theme.G(check.Name),
                statusText,
                Theme.Faint(check.Detail),
                string.IsNullOrWhiteSpace(check.Fix) ? Theme.Faint("-") : Theme.Faint(check.Fix));
        }

        AnsiConsole.Write(table);
        AnsiConsole.WriteLine();

        if (report.HasFailures)
            AnsiConsole.MarkupLine(Theme.Fail("Doctor found blocking issues."));
        else if (report.HasWarnings)
            AnsiConsole.MarkupLine(Theme.Warn("Doctor completed with warnings."));
        else
            AnsiConsole.MarkupLine(Theme.Ok("Doctor completed: no issues found."));

        if (!report.HasFailures && !report.HasWarnings)
            return;

        var applyFixes = AnsiConsole.Confirm(
            $"[{Theme.Amber}]Apply common autofixes now?[/]",
            defaultValue: false);
        if (!applyFixes)
            return;

        var fixer = new ConfigDoctorAutoFixService();
        var missingDirs = fixer.FindMissingWorkingDirectories(_config, _projectRoot);
        if (missingDirs.Count > 0)
        {
            var createDirs = AnsiConsole.Confirm(
                $"[{Theme.Amber}]Create {missingDirs.Count} missing working director{(missingDirs.Count == 1 ? "y" : "ies")}?[/]",
                defaultValue: true);
            if (createDirs)
            {
                var dirFix = fixer.CreateMissingWorkingDirectories(missingDirs);
                AnsiConsole.MarkupLine(dirFix.Success ? Theme.Ok(dirFix.Message) : Theme.Fail(dirFix.Message));
            }
        }

        if (_config.Targets.Count > 0)
        {
            var migrate = AnsiConsole.Confirm(
                $"[{Theme.Amber}]Migrate legacy targets to workflows now?[/]",
                defaultValue: true);
            if (migrate)
            {
                var migration = fixer.ApplyLegacyMigration(_projectRoot);
                if (migration.Success)
                {
                    AnsiConsole.MarkupLine(Theme.Ok("Legacy migration applied from doctor."));
                    if (!string.IsNullOrWhiteSpace(migration.BackupPath))
                        AnsiConsole.MarkupLine(Theme.Faint($"Backup: {migration.BackupPath}"));

                    var reloaded = ConfigLoader.FindAndLoad(_projectRoot);
                    if (reloaded is not null)
                    {
                        _config = reloaded.Config;
                        var normalized = WorkflowModelBuilder.Normalize(_config, ResolveLegacyMode(), _requirementResolver);
                        _workflows = normalized.Workflows.ToList();
                        _warnings.Clear();
                        _warnings.AddRange(reloaded.Warnings);
                        _warnings.AddRange(normalized.Warnings);
                    }
                }
                else
                {
                    AnsiConsole.MarkupLine(Theme.Fail(migration.Message));
                }
            }
        }
    }

    private void ApplyLegacyMigration()
    {
        if (_config.Targets.Count == 0)
        {
            AnsiConsole.MarkupLine(Theme.Warn("No legacy targets found in this config."));
            return;
        }

        var configPath = ConfigLoader.FindConfigPath(_projectRoot);
        if (string.IsNullOrWhiteSpace(configPath))
        {
            AnsiConsole.MarkupLine(Theme.Fail("Could not locate devtool.json to migrate."));
            return;
        }

        var confirm = AnsiConsole.Confirm(
            $"[{Theme.Amber}]Migrate legacy targets to workflows and overwrite devtool.json (with backup)?[/]",
            defaultValue: true);
        if (!confirm)
            return;

        var result = ConfigLoader.ApplyLegacyTargetMigration(configPath, createBackup: true);
        if (!result.Success)
        {
            AnsiConsole.MarkupLine(Theme.Fail(result.Message));
            return;
        }

        var reloaded = ConfigLoader.FindAndLoad(_projectRoot);
        if (reloaded is null)
        {
            AnsiConsole.MarkupLine(Theme.Fail("Migration wrote config, but reload failed."));
            return;
        }

        _config = reloaded.Config;
        var normalized = WorkflowModelBuilder.Normalize(_config, ResolveLegacyMode(), _requirementResolver);
        _workflows = normalized.Workflows.ToList();
        _warnings.Clear();
        _warnings.AddRange(reloaded.Warnings);
        _warnings.AddRange(normalized.Warnings);

        AnsiConsole.MarkupLine(Theme.Ok("Migration complete."));
        if (!string.IsNullOrWhiteSpace(result.BackupPath))
            AnsiConsole.MarkupLine(Theme.Faint($"Backup: {result.BackupPath}"));
    }

    private void EditEnvironment()
    {
        AnsiConsole.Clear();

        if (_config.Env.Count == 0)
        {
            AnsiConsole.MarkupLine(Theme.Warn("No environment variables defined in devtool.json."));
            AnsiConsole.MarkupLine("\n" + Theme.Faint("Press any key to go back..."));
            Console.ReadKey(intercept: true);
            return;
        }

        while (true)
        {
            AnsiConsole.Write(Theme.SectionRule("ENVIRONMENT"));

            var table = new Table()
                .Border(TableBorder.Rounded)
                .BorderStyle(Theme.DimStyle)
                .AddColumn(new TableColumn($"[{Theme.Amber}]Variable[/]"))
                .AddColumn(new TableColumn($"[{Theme.Amber}]Current Value[/]"))
                .AddColumn(new TableColumn($"[{Theme.Amber}]Description[/]"));

            foreach (var def in _config.Env)
            {
                var val = Environment.GetEnvironmentVariable(def.Key) ?? def.DefaultValue;
                table.AddRow(
                    Theme.Warn(def.Key),
                    Theme.Bold(val.Length > 0 ? val : "(not set)"),
                    Theme.Faint(def.Description));
            }

            AnsiConsole.Write(table);
            AnsiConsole.MarkupLine(Theme.Faint("Changes apply to this SDT session only.\n"));

            var choices = _config.Env
                .Select(e =>
                {
                    var curr = Environment.GetEnvironmentVariable(e.Key) ?? e.DefaultValue;
                    return new MenuItem(
                        $"[{Theme.Amber}]{Markup.Escape(e.Key)}[/]  [{Theme.GreenDim}]= {Markup.Escape(curr)}[/]",
                        e.Key);
                })
                .Append(new MenuItem(Theme.Faint("← Back"), "__back__"))
                .ToList();

            var selected = AnsiConsole.Prompt(
                new SelectionPrompt<MenuItem>()
                    .Title($"[{Theme.Green}]Select a variable to edit:[/]")
                    .UseConverter(m => m.Display)
                    .AddChoices(choices));

            if (selected.Value == "__back__") break;

            var envDef = _config.Env.First(e => e.Key == selected.Value);
            var current = Environment.GetEnvironmentVariable(envDef.Key) ?? envDef.DefaultValue;

            string newVal;
            if (envDef.Options.Count > 0)
            {
                newVal = AnsiConsole.Prompt(
                    new SelectionPrompt<string>()
                        .Title($"[{Theme.Amber}]{Markup.Escape(envDef.Key)}[/]  [{Theme.GreenDim}]current: {Markup.Escape(current)}[/]")
                        .AddChoices(envDef.Options));
            }
            else
            {
                newVal = AnsiConsole.Ask(
                    $"[{Theme.Amber}]{Markup.Escape(envDef.Key)}[/]", current);
            }

            Environment.SetEnvironmentVariable(envDef.Key, newVal);
            AnsiConsole.MarkupLine("\n" + Theme.Ok($"{envDef.Key} = {newVal}") + "\n");
            Thread.Sleep(500);
            AnsiConsole.Clear();
        }
    }
}
