// Claude CLI flags (verified 2026-04-23 via Context7):
//   Thinking budget:   env var MAX_THINKING_TOKENS=20000 (no CLI flag exists)
//   Allowed-tools:     --allowedTools (camelCase), comma-separated tokens
//   System prompt:     --append-system-prompt-file <path>  (file form)
//   Session ID:        no pre-assign flag; resume with --resume <id>
// Launch model: wt.exe -> powershell -> claude.exe (UseShellExecute=false).
//   wt.exe treats ';' as a tab/command delimiter in EVERY argument, regardless of
//   quoting, and there is no escape that survives (microsoft/terminal#13264). So the
//   free-text prompt must never appear on the wt command line. We hand it to PowerShell
//   out-of-band via an environment variable and reference it as $env:VAR — PowerShell
//   binds a variable's value as a single argument without re-tokenizing it, so the prompt
//   is robust to ';', '&', quotes, and newlines. All other (controlled) tokens are
//   single-quoted. No cmd shim: cmd re-parses %VAR% and would re-introduce the problem.

using System.Diagnostics;
using System.Text;
using ClaudeDo.Data.Models;

namespace ClaudeDo.Worker.Planning;

// Spawns the Claude CLI inside a visible Windows Terminal window. Used for every
// human-driven session: an interactive planning session (start/resume) and the ad-hoc
// "Run interactively" action. Headless task execution does NOT come through here — that
// path is ClaudeProcess (prompt over stdin, no terminal).
public sealed class WindowsTerminalLauncher : ITerminalLauncher
{
    private const string AllowedTools = "mcp__claudedo__*,Read,Grep,Glob,WebFetch,WebSearch,Skill";
    private const string Model = ModelRegistry.PlanningAlias;

    // Carries the free-text initial prompt to PowerShell out-of-band (never on the
    // command line) so wt.exe cannot split it on ';'.
    private const string PromptEnvVar = "CLAUDEDO_LAUNCH_PROMPT";

    private readonly string _wtPath;
    private readonly string _claudePath;

    public WindowsTerminalLauncher(string wtPath, string claudePath)
    {
        _wtPath = wtPath;
        _claudePath = claudePath;
    }

    public Task LaunchPlanningStartAsync(PlanningSessionStartContext ctx, CancellationToken cancellationToken)
    {
        if (!Directory.Exists(ctx.WorkingDir))
            throw new TerminalLaunchException($"Working directory does not exist: {ctx.WorkingDir}");

        if (!File.Exists(ctx.Files.SystemPromptPath))
            throw new TerminalLaunchException($"System prompt file not found: {ctx.Files.SystemPromptPath}");
        if (!File.Exists(ctx.Files.InitialPromptPath))
            throw new TerminalLaunchException($"Initial prompt file not found: {ctx.Files.InitialPromptPath}");

        var resolvedWt = ResolveWtOrThrow();
        var resolvedClaude = ResolveClaudeOrThrow();

        // Arg order: --allowedTools is variadic (space-separated). The positional prompt
        // must follow a single-value flag, or it will be swallowed —
        // --append-system-prompt-file serves as that buffer.
        var command = BuildPwshCommand(resolvedClaude, new[]
        {
            "--model", Model,
            "--permission-mode", "plan",
            "--allowedTools", AllowedTools,
            "--append-system-prompt-file", ctx.Files.SystemPromptPath,
        }, appendPrompt: true);

        StartInWindowsTerminal(resolvedWt, ctx.WorkingDir, command, env =>
        {
            env["MAX_THINKING_TOKENS"] = "20000";
            env["CLAUDEDO_PLANNING_TOKEN"] = ctx.Token;
            env[PromptEnvVar] = File.ReadAllText(ctx.Files.InitialPromptPath);
        });

        return Task.CompletedTask;
    }

    public Task LaunchInteractiveAsync(InteractiveLaunchContext ctx, CancellationToken cancellationToken)
    {
        if (!Directory.Exists(ctx.WorkingDir))
            throw new TerminalLaunchException($"Working directory does not exist: {ctx.WorkingDir}");

        var resolvedWt = ResolveWtOrThrow();
        var resolvedClaude = ResolveClaudeOrThrow();

        var command = BuildPwshCommand(resolvedClaude, new[]
        {
            "--model", Model,
            "--permission-mode", "auto",
        }, appendPrompt: true);

        StartInWindowsTerminal(resolvedWt, ctx.WorkingDir, command, env =>
        {
            env["MAX_THINKING_TOKENS"] = "20000";
            env[PromptEnvVar] = ctx.InitialPrompt;
        });

        return Task.CompletedTask;
    }

    public Task LaunchPlanningResumeAsync(PlanningSessionResumeContext ctx, CancellationToken cancellationToken)
    {
        if (!Directory.Exists(ctx.WorkingDir))
            throw new TerminalLaunchException($"Working directory does not exist: {ctx.WorkingDir}");

        var resolvedWt = ResolveWtOrThrow();
        var resolvedClaude = ResolveClaudeOrThrow();

        var command = BuildPwshCommand(resolvedClaude, new[]
        {
            "--permission-mode", "plan",
            "--resume", ctx.ClaudeSessionId,
        }, appendPrompt: false);

        StartInWindowsTerminal(resolvedWt, ctx.WorkingDir, command, env =>
        {
            env["CLAUDEDO_PLANNING_TOKEN"] = ctx.Token;
        });

        return Task.CompletedTask;
    }

    private string ResolveWtOrThrow() =>
        Resolve(_wtPath) ?? throw new TerminalLaunchException($"Windows Terminal not found: {_wtPath}");

    private string ResolveClaudeOrThrow() =>
        Resolve(_claudePath) ?? throw new TerminalLaunchException($"claude executable not found: {_claudePath}");

    // Builds the PowerShell command that invokes claude with the given (controlled)
    // arguments, optionally appending the free-text prompt from $env:CLAUDEDO_LAUNCH_PROMPT.
    // The prompt is referenced as a variable so PowerShell binds its value as ONE argument
    // (never re-tokenized). The `-replace '"','\"'` works around Windows PowerShell 5.1's
    // native-argument quirk, which otherwise strips embedded double-quotes before the child
    // sees them; all other characters (';', '&', spaces, backslashes, newlines) pass through.
    private static string BuildPwshCommand(string claudePath, IReadOnlyList<string> args, bool appendPrompt)
    {
        var sb = new StringBuilder();
        sb.Append("& ").Append(PwshQuote(claudePath));
        foreach (var a in args)
            sb.Append(' ').Append(PwshQuote(a));
        if (appendPrompt)
            sb.Append(" ($env:").Append(PromptEnvVar).Append(" -replace '\"','\\\"')");
        return sb.ToString();
    }

    // Single-quote for PowerShell: only the single quote itself is special, escaped by doubling.
    private static string PwshQuote(string value) => "'" + value.Replace("'", "''") + "'";

    private static void StartInWindowsTerminal(
        string resolvedWt, string workingDir, string pwshCommand, Action<IDictionary<string, string?>> configureEnv)
    {
        var psi = new ProcessStartInfo
        {
            FileName = resolvedWt,
            UseShellExecute = false,
            CreateNoWindow = false,
        };

        configureEnv(psi.Environment);

        psi.ArgumentList.Add("-d");
        psi.ArgumentList.Add(workingDir);
        psi.ArgumentList.Add("powershell");
        psi.ArgumentList.Add("-NoProfile");
        psi.ArgumentList.Add("-NoLogo");
        psi.ArgumentList.Add("-Command");
        psi.ArgumentList.Add(pwshCommand);

        _ = Process.Start(psi)
            ?? throw new TerminalLaunchException("Failed to start Windows Terminal process.");
    }

    private static string? Resolve(string pathOrName)
    {
        if (File.Exists(pathOrName))
            return pathOrName;

        // Try PATH resolution
        var envPath = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
        var extensions = new[] { "", ".exe", ".cmd", ".bat" };
        foreach (var dir in envPath.Split(Path.PathSeparator))
        {
            foreach (var ext in extensions)
            {
                var candidate = Path.Combine(dir, pathOrName + ext);
                if (File.Exists(candidate))
                    return candidate;
            }
        }

        return null;
    }
}
