#!/usr/bin/env bun
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { spawn } from "node:child_process";
import path from "node:path";
import { getPiSpawnCommand } from "../extensions/_shared/pi-spawn.ts";
import { TERMINAL, TRANSITIONS, enforceGateSet, enforceRetrospective, failIfPlaceholder, ParsedState, Phase, readState, requireTruthy } from "../scripts/task-phase-lib.ts";

function usage(): never {
  console.error(`Usage:
  task-phase-run --task-dir /home/sebas/work/tasks/T-123 --repo /path/repo [--provider openai-codex] [--model gpt-5.4-mini] [--thinking low] [--allow-extensions]
`);
  process.exit(1);
}

function parseArgs() {
  const args = process.argv.slice(2);
  let taskDir = "";
  let repo = "";
  let provider = "openai-codex";
  let model = "gpt-5.4-mini";
  let thinking = "low";
  let allowExtensions = false;
  for (let i = 0; i < args.length; i++) {
    const arg = args[i];
    const next = args[i + 1];
    switch (arg) {
      case "--task-dir": taskDir = next || ""; i++; break;
      case "--repo": repo = next || ""; i++; break;
      case "--provider": provider = next || ""; i++; break;
      case "--model": model = next || ""; i++; break;
      case "--thinking": thinking = next || ""; i++; break;
      case "--allow-extensions": allowExtensions = true; break;
      case "-h":
      case "--help": usage();
      default:
        console.error(`Unknown arg: ${arg}`);
        usage();
    }
  }
  if (!taskDir || !repo) usage();
  return { taskDir: path.resolve(taskDir), repo: path.resolve(repo), provider, model, thinking, allowExtensions };
}

function preflight(state: ParsedState) {
  if (TERMINAL.has(state.phase)) throw new Error(`Task already in terminal phase: ${state.phase}`);
  failIfPlaceholder(state.values["Objective"], "Objective");
  failIfPlaceholder(state.values["Evidence location"], "Evidence location");
  failIfPlaceholder(state.values["Evidence link"], "Evidence link");
}

function validateTransition(before: ParsedState, after: ParsedState) {
  if (after.phase === before.phase) throw new Error(`Gate failed: phase did not advance (still ${after.phase})`);
  if (!TRANSITIONS[before.phase].includes(after.phase)) throw new Error(`Gate failed: illegal transition ${before.phase} -> ${after.phase}`);
  failIfPlaceholder(after.values["Last updated"], "Last updated");

  switch (`${before.phase}->${after.phase}`) {
    case "intake->shape":
      break;
    case "shape->implement":
      requireTruthy(after.values["Done gate"], "Done gate");
      requireTruthy(after.values["Not doing"], "Not doing");
      requireTruthy(after.values["Reality constraints"], "Reality constraints");
      requireTruthy(after.values["Validation plan"], "Validation plan");
      break;
    case "implement->verify":
      requireTruthy(after.values["Candidate done"], "Candidate done", ["yes"]);
      requireTruthy(after.values["Current result"], "Current result");
      break;
    case "verify->review":
      requireTruthy(after.values["Validator status"], "Validator status", ["pass"]);
      requireTruthy(after.values["Evidence location"], "Evidence location");
      requireTruthy(after.values["Evidence link"], "Evidence link");
      enforceGateSet(after);
      break;
    case "verify->repair":
      requireTruthy(after.values["Validator status"], "Validator status", ["fail"]);
      requireTruthy(after.values["Current result"], "Current result");
      break;
    case "review->done":
      requireTruthy(after.values["Candidate done"], "Candidate done", ["yes"]);
      requireTruthy(after.values["Validator status"], "Validator status", ["pass"]);
      if (after.values["Repair applied"] === "yes") {
        requireTruthy(after.values["Revalidation status"], "Revalidation status", ["pass"]);
      }
      enforceGateSet(after);
      break;
    case "review->repair":
      requireTruthy(after.values["Current result"], "Current result");
      break;
    case "repair->verify":
      requireTruthy(after.values["Repair applied"], "Repair applied", ["yes"]);
      requireTruthy(after.values["Retrospective required"], "Retrospective required", ["yes"]);
      break;
    case "done->retrospective":
    case "blocked->retrospective":
    case "needs_user_decision->retrospective":
      requireTruthy(after.values["Retrospective required"], "Retrospective required", ["yes"]);
      break;
    case "done->closed":
    case "blocked->closed":
    case "needs_user_decision->closed":
      requireTruthy(after.values["Retrospective required"], "Retrospective required", ["no"]);
      break;
    case "retrospective->closed":
      enforceRetrospective(after);
      break;
  }
}

function buildPrompt(taskDir: string, repo: string, state: ParsedState, runDir: string): string {
  const allowed = TRANSITIONS[state.phase];
  const gateNotes: Record<Phase, string[]> = {
    intake: [
      "Move only to shape, blocked, or needs_user_decision.",
      "Use this pass to prepare the task for deterministic shaping.",
    ],
    shape: [
      "You must lock Done gate, Not doing, Reality constraints, and Validation plan before leaving shape.",
      "Only move to implement, blocked, or needs_user_decision.",
    ],
    implement: [
      "Do the work. When and only when a candidate solution exists, move to verify.",
      "Set Candidate done: yes only if a real candidate exists.",
    ],
    verify: [
      "Run validation and record proof files.",
      "If validation passes, move to review.",
      "If bounded fixes are needed, move to repair.",
      "Do not jump back to implement.",
    ],
    review: [
      "Review quality/truthfulness only.",
      "If pass, move to done. If bounded issue, move to repair. If the work cannot ship, blocked is allowed.",
      "If this task had repair, failed validation, intent confusion, or other process pain, set Retrospective required: yes before leaving review.",
    ],
    repair: [
      "Apply only bounded fixes for known failures.",
      "After repair, move to verify. Never move directly to done.",
      "Set Retrospective required: yes when repair happened.",
    ],
    done: [
      "Main deliverable is finished.",
      "If Retrospective required: yes, move only to retrospective.",
      "If Retrospective required: no, you may move to closed.",
    ],
    blocked: [
      "Main deliverable is blocked.",
      "If Retrospective required: yes, move only to retrospective.",
      "If Retrospective required: no, you may move to closed.",
    ],
    needs_user_decision: [
      "Main deliverable needs user choice.",
      "If Retrospective required: yes, move only to retrospective.",
      "If Retrospective required: no, you may move to closed.",
    ],
    retrospective: [
      "Do not change the main deliverable here.",
      "Do not repair code or reopen the main task.",
      "Record what went wrong, what guardrails were missing, and what follow-up tasks should exist.",
      "Create follow-up tasks instead of fixing the system in place.",
      "Then move to closed.",
    ],
    closed: [],
  };

  return `You are running one deterministic phase-machine pass.

Task folder: ${taskDir}
Repo: ${repo}
Current phase: ${state.phase}
Allowed next phases: ${allowed.join(", ")}
Run artifact dir: ${runDir}

Read and use these files first:
- ${path.join(taskDir, "README.md")}
- ${path.join(taskDir, "STATE.md")}
- ${path.join(taskDir, "EVIDENCE.md")}
- ${path.join(taskDir, "RETROSPECTIVE.md")}

Gate profile: ${state.gateProfile}
Required gates listed in STATE.md:
${state.requiredGates.map((g) => `- ${g}`).join("\n") || "- (none beyond universal/profile gates)"}

Rules:
- Work only in ${repo} and files under ${taskDir}.
- Update STATE.md before exit.
- Update EVIDENCE.md when proof changes.
- Update RETROSPECTIVE.md during retrospective.
- Put generated proof under ${path.join(taskDir, "artifacts")}
- Keep the Evidence link in STATE.md truthful and clickable.
- For complex evidence bundles, update artifacts/evidence.html as the joinery page.
- Do not push, merge, or ask the user anything.
- Do not leave the phase unchanged.
- Do not choose a next phase outside: ${allowed.join(", ")}.
- If you are in retrospective, do not modify the main deliverable. Spawn follow-up tasks instead.
- If you create follow-up tasks, add them to docs/TASKS.md and create their workspace folders.

Phase-specific rules:
${gateNotes[state.phase].map((x) => `- ${x}`).join("\n")}

Required outcomes before exit:
1. Do the highest-value work for this phase.
2. Update STATE.md fields truthfully.
3. Set Phase to exactly one allowed next phase.
4. Update Last updated.
5. If you claim validation or evidence, leave inspectable files.
6. Update gate statuses in STATE.md truthfully.
7. Any required gate for review/done must be explicitly marked pass, not implied.
8. If retrospective is required, capture failures and spawn follow-up tasks before closing.
`;
}

async function runPi(prompt: string, cwd: string, provider: string, model: string, thinking: string, allowExtensions: boolean, outputFile: string) {
  const args = ["--provider", provider, "--model", model, "--thinking", thinking];
  if (!allowExtensions) args.push("--no-extensions");
  args.push("-p", prompt);
  const invocation = getPiSpawnCommand(args);

  await new Promise<number>((resolve, reject) => {
    const proc = spawn(invocation.command, invocation.args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
    let out = "";
    proc.stdout.on("data", (d) => { out += d.toString(); });
    proc.stderr.on("data", (d) => { out += d.toString(); });
    proc.on("error", reject);
    proc.on("close", (code) => {
      writeFileSync(outputFile, out);
      resolve(code ?? 1);
    });
  }).then((code) => {
    if (code !== 0) throw new Error(`Pi exited with code ${code}`);
  });
}

async function main() {
  const { taskDir, repo, provider, model, thinking, allowExtensions } = parseArgs();
  const stateFile = path.join(taskDir, "STATE.md");
  const evidenceFile = path.join(taskDir, "EVIDENCE.md");
  const retrospectiveFile = path.join(taskDir, "RETROSPECTIVE.md");
  const readmeFile = path.join(taskDir, "README.md");
  if (!existsSync(readmeFile) || !existsSync(stateFile) || !existsSync(evidenceFile) || !existsSync(retrospectiveFile)) {
    throw new Error("Task folder missing README.md, STATE.md, EVIDENCE.md, or RETROSPECTIVE.md");
  }
  if (!existsSync(repo)) throw new Error(`Repo not found: ${repo}`);

  const before = readState(stateFile);
  preflight(before);

  const stamp = new Date().toISOString().replace(/[:.]/g, "-");
  const runDir = path.join(taskDir, "artifacts", "phase-runs", stamp);
  mkdirSync(runDir, { recursive: true });
  const promptFile = path.join(runDir, "prompt.txt");
  const outputFile = path.join(runDir, "output.log");
  const metaFile = path.join(runDir, "run-meta.txt");
  const prompt = buildPrompt(taskDir, repo, before, runDir);
  writeFileSync(promptFile, prompt);
  writeFileSync(metaFile, `phase_before=${before.phase}\nprovider=${provider}\nmodel=${model}\nthinking=${thinking}\nallow_extensions=${allowExtensions}\nrepo=${repo}\ntask_dir=${taskDir}\n`);

  await runPi(prompt, repo, provider, model, thinking, allowExtensions, outputFile);

  const after = readState(stateFile);
  if (after.mtimeMs <= before.mtimeMs) throw new Error("Gate failed: STATE.md was not updated");
  validateTransition(before, after);

  console.log(`task_dir=${taskDir}`);
  console.log(`run_dir=${runDir}`);
  console.log(`phase_before=${before.phase}`);
  console.log(`phase_after=${after.phase}`);
  console.log(`evidence_link=${after.values["Evidence link"]}`);
}

main().catch((error) => {
  console.error(error instanceof Error ? error.message : String(error));
  process.exit(1);
});
