import { mkdirSync, writeFileSync } from "node:fs";
import { dirname } from "node:path";
import { appendUltragoalLedger } from "./ledger.mjs";
import {
  ultragoalBriefPath,
  ultragoalGoalsPath,
  ultragoalLedgerPath,
  ultragoalLockDir,
} from "./paths.mjs";
import { readUltragoalState, UltragoalStateError, withUltragoalLock, writeUltragoalState } from "./state.mjs";

const subcommands = [
  ["create-goals", "Create durable goal records from a brief."],
  ["status", "Print the current ultragoal status."],
  ["criteria", "List or update success criteria."],
  ["record-evidence", "Record evidence as JSON."],
  ["checkpoint", "Write a continuation checkpoint."],
  ["steer", "Record a steering decision."],
  ["record-review-blockers", "Record review blockers."],
];

const helpText = `Usage: lazyclaude-ai ultragoal <command> [...args]
       lazyclaude-ai ultragoal --help

Commands:
${subcommands.map(([name, description]) => `  ${name.padEnd(22)} ${description}`).join("\n")}
`;

class UltragoalCliError extends Error {
  constructor(message, status = 64) {
    super(message);
    this.name = "UltragoalCliError";
    this.status = status;
  }
}

const nowIso = () => new Date().toISOString();

const hasFlag = (args, flag) => args.includes(flag);

const optionValue = (args, flag) => {
  const index = args.indexOf(flag);
  if (index === -1) return undefined;
  const value = args[index + 1];
  if (!value || value.startsWith("--")) return "";
  return value;
};

const requireOption = (args, flag, label = flag.slice(2)) => {
  const value = optionValue(args, flag);
  if (!value) throw new UltragoalCliError(`missing ${label}`);
  return value;
};

const parseEvidenceJson = (args) => {
  const index = args.indexOf("--json");
  if (index === -1) return {};
  const value = args[index + 1];
  if (!value || value.startsWith("--")) return {};
  try {
    return JSON.parse(value);
  } catch {
    throw new UltragoalCliError("invalid ultragoal JSON");
  }
};

const emit = (io, payload, outputJson) => {
  if (outputJson) {
    io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
    return;
  }
  io.stdout.write(`${payload.message ?? JSON.stringify(payload)}\n`);
};

const readState = (cwd) => readUltragoalState(ultragoalGoalsPath(cwd), null);

const requireState = (cwd) => {
  const state = readState(cwd);
  if (!state) throw new UltragoalStateError("ultragoal state not found");
  return state;
};

const appendLedger = (cwd, entry) => appendUltragoalLedger(ultragoalLedgerPath(cwd), entry);

const defaultCriterion = (brief) => ({
  id: "criterion-1",
  description: "Objective has observable verification evidence.",
  expectedEvidence: `Evidence that "${brief}" is complete.`,
  status: "pending",
  evidence: [],
});

const createGoals = (cwd, args) => {
  const brief = requireOption(args, "--brief", "brief").trim();
  if (!brief) throw new UltragoalCliError("missing brief");
  return withUltragoalLock(ultragoalLockDir(cwd), () => {
    const timestamp = nowIso();
    const state = {
      version: 1,
      objective: brief,
      status: "active",
      criteria: [defaultCriterion(brief)],
      blockers: [],
      steering: [],
      checkpoints: [],
      createdAt: timestamp,
      updatedAt: timestamp,
    };

    mkdirSync(dirname(ultragoalBriefPath(cwd)), { recursive: true });
    writeFileSync(ultragoalBriefPath(cwd), `${brief}\n`);
    writeUltragoalState(ultragoalGoalsPath(cwd), state);
    appendLedger(cwd, {
      event: "goal.created",
      objective: brief,
      status: state.status,
      criteria: state.criteria.map(({ id, status }) => ({ id, status })),
    });
    return state;
  });
};

const listCriteria = (cwd) => {
  const state = requireState(cwd);
  return { criteria: state.criteria ?? [] };
};

const updateCriterionWithEvidence = (cwd, args) => {
  const evidence = parseEvidenceJson(args);
  return withUltragoalLock(ultragoalLockDir(cwd), () => {
    const criterionId = requireOption(args, "--criterion", "criterion");
    const status = optionValue(args, "--status") || "pass";
    if (!["pass", "fail", "blocked"].includes(status)) {
      throw new UltragoalCliError("invalid evidence status");
    }
    const state = requireState(cwd);
    const criterion = state.criteria?.find(({ id }) => id === criterionId);
    if (!criterion) throw new UltragoalStateError("unknown criterion");

    const record = {
      status,
      ...evidence,
      recordedAt: nowIso(),
    };
    criterion.status = status;
    criterion.evidence = [...(criterion.evidence ?? []), record];
    state.updatedAt = record.recordedAt;
    writeUltragoalState(ultragoalGoalsPath(cwd), state);
    appendLedger(cwd, {
      event: "evidence.recorded",
      criterionId,
      status,
      evidence,
    });
    return { criterion };
  });
};

const checkpoint = (cwd, args) =>
  withUltragoalLock(ultragoalLockDir(cwd), () => {
    const state = requireState(cwd);
    const status = optionValue(args, "--status") || "active";
    if (!["active", "complete", "blocked"].includes(status)) {
      throw new UltragoalCliError("invalid checkpoint status");
    }
    const criteria = state.criteria ?? [];
    if (status === "complete" && !criteria.every((criterion) => criterion.status === "pass")) {
      throw new UltragoalStateError("criteria must pass before completion");
    }

    const record = {
      status,
      note: optionValue(args, "--note") || "",
      createdAt: nowIso(),
    };
    state.status = status;
    state.checkpoints = [...(state.checkpoints ?? []), record];
    state.updatedAt = record.createdAt;
    writeUltragoalState(ultragoalGoalsPath(cwd), state);
    appendLedger(cwd, { event: "checkpoint.recorded", ...record });
    return { status: state.status, checkpoint: record };
  });

const steer = (cwd, args) =>
  withUltragoalLock(ultragoalLockDir(cwd), () => {
    const state = requireState(cwd);
    const kind = requireOption(args, "--kind", "kind");
    if (!["scope", "priority", "blocker", "quality", "handoff"].includes(kind)) {
      throw new UltragoalCliError("invalid steering kind");
    }
    const record = {
      kind,
      note: requireOption(args, "--note", "note"),
      createdAt: nowIso(),
    };
    state.steering = [...(state.steering ?? []), record];
    state.updatedAt = record.createdAt;
    writeUltragoalState(ultragoalGoalsPath(cwd), state);
    appendLedger(cwd, { event: "steering.recorded", ...record });
    return record;
  });

const recordReviewBlockers = (cwd, args) =>
  withUltragoalLock(ultragoalLockDir(cwd), () => {
    const state = requireState(cwd);
    const blocker = optionValue(args, "--blocker") || optionValue(args, "--note") || "review blocker recorded";
    const record = {
      blocker,
      status: optionValue(args, "--status") || "blocked",
      createdAt: nowIso(),
    };
    state.blockers = [...(state.blockers ?? []), record];
    state.updatedAt = record.createdAt;
    writeUltragoalState(ultragoalGoalsPath(cwd), state);
    appendLedger(cwd, { event: "review_blocker.recorded", ...record });
    return record;
  });

const runCommand = (cwd, command, args) => {
  switch (command) {
    case "create-goals":
      return createGoals(cwd, args);
    case "status":
      return requireState(cwd);
    case "criteria":
      return listCriteria(cwd);
    case "record-evidence":
      return updateCriterionWithEvidence(cwd, args);
    case "checkpoint":
      return checkpoint(cwd, args);
    case "steer":
      return steer(cwd, args);
    case "record-review-blockers":
      return recordReviewBlockers(cwd, args);
    default:
      throw new UltragoalCliError(`Unknown ultragoal command: ${command}`);
  }
};

export const runUltragoalCli = (argv, io = { stdout: process.stdout, stderr: process.stderr }, cwd = process.cwd()) => {
  const [command, ...rest] = argv;

  if (!command || command === "--help" || command === "-h") {
    io.stdout.write(helpText);
    return 0;
  }

  if (!subcommands.some(([name]) => name === command)) {
    io.stderr.write(`Unknown ultragoal command: ${command}\n`);
    return 64;
  }

  try {
    const outputJson = hasFlag(rest, "--json");
    const result = runCommand(cwd, command, rest);
    emit(io, result, outputJson);
    return 0;
  } catch (error) {
    if (error instanceof UltragoalCliError || error instanceof UltragoalStateError) {
      io.stderr.write(`${error.message}\n`);
      return error.status;
    }
    io.stderr.write("ultragoal runtime error\n");
    return 1;
  }
};
