// 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)
//   Extra read roots:  --add-dir <dir...>  (variadic)
//   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 (microsoft/terminal#13264), so nothing containing ';' may appear on the wt
//   command line. Every token we place there is a controlled constant or a filesystem
//   path under ~/.claudeDo/sessions/<taskId> — none can contain ';' — and each is
//   single-quoted for PowerShell.
//
//   The free-text task brief is NEVER passed as an argument. An interactive `claude`
//   session auto-submits its positional prompt, and a newline in that prompt ends the
//   first line — a multi-line brief was silently truncated at the first newline. So we
//   hand claude a single-line kickoff that points it at the brief FILE (written by
//   PlanningSessionManager) and expose that file's directory via --add-dir so the Read
//   tool can open it. The full multi-line brief reaches claude intact via the file.

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

namespace ClaudeDo.Worker.Planning;

// Spawns the Claude CLI inside a visible Windows Terminal window for an interactive
// planning session (start/resume). Headless task execution does NOT come through here —
// that path is ClaudeProcess (prompt over stdin, no terminal) — nor does an embedded
// ConPTY interactive session (InteractiveLaunchSpecService), which reuses this class's
// arg-building helpers (BuildResumeArgs, BuildPlanningStart/ResumeArgs) for a bare
// Exe/Args pair instead of a wrapped pwsh command line.
public sealed class WindowsTerminalLauncher : ITerminalLauncher
{
    private const string AllowedTools = "mcp__claudedo__*,Read,Grep,Glob,WebFetch,WebSearch,Skill";
    private const string Model = ModelRegistry.PlanningAlias;

    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();

        var command = BuildPlanningStartCommand(resolvedClaude, ctx);

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

        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, BuildPlanningResumeArgs(ctx.ClaudeSessionId));

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

        return Task.CompletedTask;
    }

    // The raw claude CLI args for a --resume launch, shared with InteractiveLaunchSpecService
    // (which needs the bare Exe/Args pair for a ConPTY host, not a wrapped pwsh command line).
    internal static IReadOnlyList<string> BuildResumeArgs(string claudeSessionId) =>
        new[] { "--resume", claudeSessionId };

    // Builds the PowerShell command that launches an interactive planning session.
    // Arg order matters: variadic flags (--allowedTools, --add-dir) come first; the
    // single-line kickoff prompt is positional, so it must follow a single-value flag
    // (--append-system-prompt-file) or a variadic flag would swallow it.
    internal static string BuildPlanningStartCommand(string claudePath, PlanningSessionStartContext ctx) =>
        BuildPwshCommand(claudePath, BuildPlanningStartArgs(ctx));

    // The raw claude CLI args for an interactive planning START, shared with the embedded-ConPTY
    // planning path (InteractiveLaunchSpecService), which needs the bare Exe/Args pair rather than
    // a pwsh-wrapped command line. Arg order matters: variadic flags (--allowedTools, --add-dir)
    // come first; the single-line kickoff prompt is positional, so it must follow a single-value
    // flag (--append-system-prompt-file) or a variadic flag would swallow it.
    internal static IReadOnlyList<string> BuildPlanningStartArgs(PlanningSessionStartContext ctx)
    {
        var kickoff =
            $"Read the file {ctx.Files.InitialPromptPath} first. It contains the task you must plan. " +
            "After reading it, begin the planning session as your instructions describe.";

        return new[]
        {
            "--model", Model,
            // NOT --permission-mode plan: plan mode gates EVERY MCP tool call regardless of
            // --allowedTools, so the session would prompt the user on the first CreateChildTask
            // (the whole point of a planning session). Default mode honours the allowlist below,
            // and file edits stay blocked anyway — the planning system prompt forbids them and
            // AllowedTools omits Write/Edit/Bash (an unexpected edit would prompt, not run silently).
            "--permission-mode", "default",
            "--allowedTools", AllowedTools,
            "--add-dir", ctx.Files.SessionDirectory,
            "--append-system-prompt-file", ctx.Files.SystemPromptPath,
            kickoff,
        };
    }

    // The raw claude CLI args for an interactive planning RESUME, shared with the embedded-ConPTY
    // planning path. Re-passes the planning allowlist in default mode (not --resume alone): the CLI
    // does not restore --allowedTools across a resume, and plan mode would gate the MCP tools.
    internal static IReadOnlyList<string> BuildPlanningResumeArgs(string claudeSessionId) =>
        new[] { "--permission-mode", "default", "--allowedTools", AllowedTools, "--resume", claudeSessionId };

    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 tokens. Each token
    // is single-quoted, so ';', '&', spaces, quotes, and backslashes pass through to the
    // child process untouched. No cmd shim: cmd would re-parse the arguments.
    private static string BuildPwshCommand(string claudePath, IReadOnlyList<string> args)
    {
        var sb = new StringBuilder();
        sb.Append("& ").Append(PwshQuote(claudePath));
        foreach (var a in args)
            sb.Append(' ').Append(PwshQuote(a));
        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.");
    }

    // Not private: reused by InteractiveLaunchSpecService to resolve the claude executable
    // for a ConPTY-hosted launch spec the same way a wt-launched terminal resolves it.
    internal 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;
    }
}
