#!/usr/bin/env node
import { execFile, execFileSync, spawn } from "node:child_process";
import { existsSync, realpathSync } from "node:fs";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import {
  proofUnits,
  semanticReadiness,
  validateCriterionRegistry,
  type ProofUnit,
} from "./criterion-registry.js";
import { loadJourneyManifestAtLeast, type JourneyManifest } from "./journey-manifest.js";
import { loadCompactSelfProjectVision } from "./self-project-vision.js";
import {
  acceptanceState,
  loadProofReceiptLedger,
  persistProofReceipts,
  receiptLedgerPath,
  reconcileAcceptanceFile,
  type ProofReceiptLedger,
} from "./acceptance-state.js";
import {
  authoritativeRemoteHead,
  canonicalRepositoryParts,
  canonicalGitHubRepository,
  commandOutput,
} from "./repository-identity.js";
import {
  approveOwnerJourney,
  authorOwnerJourney,
  ensureRecoveredOwnerApprovalUi,
  ensureRecoveredOwnerAuthoringUi,
  ownerApprovalProof,
  ownerAuthoringProof,
  type OwnerEvidence,
} from "./04-owner-lifecycle.spec.js";
import {
  journeyScopedOwnerLabels,
  manifestFingerprint,
  reconcilePersistedClosure,
  transitionToApproved,
  transitionToOwnerCreated,
  type ApprovalIdentity,
  type OwnerCreatedIdentity,
  type PersistedClosureState,
} from "./journey-manifest.js";

export interface ExecutionStep {
  readonly unitId: string;
  readonly startBarrier: string;
  readonly awaitBarrier: string;
  readonly criterionIds: readonly string[];
  readonly requiredManifestStage: ProofUnit["requiredManifestStage"];
  readonly testFile: string;
}
export function validateStructure(root = process.cwd()): void {
  validateCriterionRegistry();
  for (const unit of proofUnits)
    if (!existsSync(resolve(root, unit.file)))
      throw new Error(`${unit.unitId} proof does not exist: ${unit.file}`);
}
export function validateSemanticReadiness(): void {
  const status = semanticReadiness();
  if (status.pending.length)
    throw new Error(
      `semantic readiness refused: pending migrations: ${status.pending.flatMap((u) => u.criterionIds).join(", ")}`,
    );
}
function barrierRank(value: string): number {
  if (value === "owner-created") return 1.5;
  if (value === "approved") return 3.5;
  const match = /^P(\d+)(-complete)?$/u.exec(value);
  if (!match) throw new Error(`unknown GOLIVE barrier ${value}`);
  return Number(match[1]) + (match[2] ? 0.5 : 0);
}

export function validateBarrierTopology(units: readonly ProofUnit[] = proofUnits): void {
  const byId = new Map(units.map((unit) => [unit.unitId, unit] as const));
  for (const unit of units) {
    const start = barrierRank(unit.startBarrier);
    const awaited = barrierRank(unit.awaitBarrier);
    if (start > awaited) {
      throw new Error(
        `${unit.unitId} awaits ${unit.awaitBarrier} before start ${unit.startBarrier}`,
      );
    }
    for (const prerequisiteId of unit.prerequisites) {
      const prerequisite = byId.get(prerequisiteId);
      if (!prerequisite)
        throw new Error(`${unit.unitId} has unknown prerequisite ${prerequisiteId}`);
      if (barrierRank(prerequisite.startBarrier) > start) {
        throw new Error(`${unit.unitId} starts before prerequisite ${prerequisiteId}`);
      }
    }
  }
}

export function validateExecutionPlan(root = process.cwd()): void {
  validateStructure(root);
  validateSemanticReadiness();
  validateBarrierTopology();
  for (const u of proofUnits)
    if (!u.scope.trim() || !u.inputSelectors)
      throw new Error(`${u.unitId} is unanchored or missing declared inputs`);
}
export function statusSummary(): string {
  const r = semanticReadiness();
  return `registry structure: 30/30\nsemantic readiness: ${r.ready}/30\nowner acceptance: 0/30`;
}
export function executionSchedule(): readonly ExecutionStep[] {
  return Object.freeze(
    proofUnits.map((u) =>
      Object.freeze({
        unitId: u.unitId,
        startBarrier: u.startBarrier,
        awaitBarrier: u.awaitBarrier,
        criterionIds: u.criterionIds,
        requiredManifestStage: u.requiredManifestStage,
        testFile: u.file,
      }),
    ),
  );
}
export function executionFiles(): readonly string[] {
  return executionSchedule().map((s) => s.testFile);
}
const necessities = ["PATH", "HOME", "TMPDIR", "TMP", "TEMP", "LANG", "LC_ALL"] as const;
const selectorEnv: Record<string, string> = {
  "repository.remoteUrl": "AWP_REPOSITORY_REMOTE_URL",
  "repository.checkoutPath": "AWP_REPOSITORY_CHECKOUT_PATH",
  "repository.owner": "AWP_REPOSITORY_OWNER",
  "repository.name": "AWP_REPOSITORY_NAME",
  "repository.defaultBranch": "AWP_REPOSITORY_DEFAULT_BRANCH",
  agentAccountId: "AWP_AGENT_ACCOUNT_ID",
  agentModel: "AWP_AGENT_MODEL",
  projectId: "AWP_PROJECT_ID",
  planId: "AWP_PLAN_ID",
  planRevisionId: "AWP_PLAN_REVISION_ID",
  taskIds: "AWP_TASK_IDS",
  factoryRunId: "AWP_FACTORY_RUN_ID",
  firstTaskId: "AWP_FIRST_TASK_ID",
};
function select(manifest: JourneyManifest, selector: string): string | undefined {
  if (selector.startsWith("repository."))
    return manifest.repository[selector.slice(11) as keyof typeof manifest.repository];
  const value = (manifest as unknown as Record<string, unknown>)[selector];
  return Array.isArray(value) ? value.join(",") : typeof value === "string" ? value : undefined;
}
export function proofEnvironment(
  unit: ProofUnit,
  manifest: JourneyManifest,
  runtime: NodeJS.ProcessEnv = {},
  live = false,
): NodeJS.ProcessEnv {
  const env: NodeJS.ProcessEnv = {};
  for (const key of necessities) if (process.env[key]) env[key] = process.env[key];
  for (const selector of unit.inputSelectors) {
    const value = select(manifest, selector);
    if (!value) throw new Error(`${unit.unitId} missing declared input ${selector}`);
    env[selectorEnv[selector] ?? selector] = value;
  }
  for (const key of unit.runtimeInputs) {
    const value = runtime[key];
    if (!value) throw new Error(`${unit.unitId} missing runtime input ${key}`);
    env[key] = value;
  }
  if (live) env.AWP_GOLIVE_EXECUTION = "1";
  return env;
}
export function manifestEnvironment(manifest: JourneyManifest): NodeJS.ProcessEnv {
  const synthetic: ProofUnit = { ...proofUnits[0]!, inputSelectors: Object.keys(selectorEnv) };
  return proofEnvironment(synthetic, manifest);
}

export type LifecycleHandler = (
  manifestPath: string,
  runtime: NodeJS.ProcessEnv,
) => Promise<readonly OwnerEvidence[]>;

interface JourneyMutationLockHandle {
  readonly release: () => Promise<void>;
}

async function acquireJourneyMutationLock(lockPath: string): Promise<JourneyMutationLockHandle> {
  const child = spawn(
    "flock",
    ["--exclusive", "--nonblock", lockPath, "sh", "-c", 'printf "READY\n"; cat >/dev/null'],
    { stdio: ["pipe", "pipe", "pipe"] },
  );
  child.stdout.setEncoding("utf8");
  child.stderr.setEncoding("utf8");
  let stdout = "";
  let stderr = "";
  let acquired = false;

  await new Promise<void>((resolveLock, rejectLock) => {
    const fail = (message: string, cause?: unknown) => {
      if (acquired) return;
      acquired = true;
      rejectLock(new Error(message, cause === undefined ? undefined : { cause }));
    };
    child.once("error", (error) => fail("cannot start owner lifecycle kernel lock", error));
    child.stderr.on("data", (chunk: string) => {
      stderr += chunk;
    });
    child.stdout.on("data", (chunk: string) => {
      stdout += chunk;
      if (!acquired && stdout.includes("READY\n")) {
        acquired = true;
        resolveLock();
      }
    });
    child.once("exit", (code, signal) => {
      if (acquired) return;
      fail(
        code === 1
          ? "owner lifecycle mutation is already in progress for this journey"
          : `owner lifecycle kernel lock exited before acquisition (code=${String(code)}, signal=${String(signal)}): ${stderr.trim()}`,
      );
    });
  });

  return {
    release: async () => {
      if (!child.stdin.destroyed) child.stdin.end();
      if (child.exitCode !== null) return;
      await new Promise<void>((resolveExit, rejectExit) => {
        child.once("error", rejectExit);
        child.once("exit", (code, signal) => {
          if (code === 0) resolveExit();
          else
            rejectExit(
              new Error(
                `owner lifecycle kernel lock release failed (code=${String(code)}, signal=${String(signal)})`,
              ),
            );
        });
      });
    },
  };
}

export async function withJourneyMutationLock<T>(
  manifestPath: string,
  callback: () => Promise<T>,
): Promise<T> {
  const lock = await acquireJourneyMutationLock(`${manifestPath}.owner-lifecycle.lock`);
  try {
    return await callback();
  } finally {
    await lock.release();
  }
}

function sql(value: string): string {
  return `'${value.replaceAll("'", "''")}'`;
}

export function hasExactDependencyChain(
  taskIds: readonly string[],
  edges: readonly (readonly [string, string])[],
): boolean {
  return (
    taskIds.length === 3 &&
    edges.length === 2 &&
    edges[0]?.[0] === taskIds[1] &&
    edges[0]?.[1] === taskIds[0] &&
    edges[1]?.[0] === taskIds[2] &&
    edges[1]?.[1] === taskIds[1]
  );
}

export function isPristinePreApproval(
  planStatus: string,
  taskStates: readonly (readonly [string, string])[],
  taskIds: readonly string[],
  edges: readonly (readonly [string, string])[],
): boolean {
  return (
    planStatus === "draft" &&
    taskStates.length === 3 &&
    taskStates.every(
      ([id, status], index) =>
        id === taskIds[index] && status === (index === 0 ? "planned" : "blocked"),
    ) &&
    hasExactDependencyChain(taskIds, edges)
  );
}
export function isCausallyAdvancedApproval(
  planStatus: string,
  runStatus: string,
  runReason: string,
  taskStates: readonly (readonly [string, string])[],
  taskIds: readonly string[],
): boolean {
  const exactIds =
    taskStates.length === 3 && taskStates.every(([id], index) => id === taskIds[index]);
  if (!exactIds) return false;
  if (runStatus === "queued") {
    return (
      planStatus === "approved" &&
      runReason === "Automatic dependency-legal dispatch after Plan approval" &&
      taskStates[0]?.[1] === "dispatched" &&
      taskStates[1]?.[1] === "blocked" &&
      taskStates[2]?.[1] === "blocked"
    );
  }
  const advancedRuns = new Set([
    "starting",
    "running",
    "waiting",
    "blocked",
    "cancelling",
    "cancelled",
    "failed",
    "retryable",
    "resolving",
    "completed",
  ]);
  const firstTaskStates = new Set([
    "dispatched",
    "queued",
    "executing",
    "waiting",
    "review",
    "correction",
    "completed",
    "cancelled",
    "failed",
  ]);
  const dependentTaskStates = new Set(["blocked", "ready", ...firstTaskStates]);
  if (!advancedRuns.has(runStatus)) return false;
  if (!firstTaskStates.has(taskStates[0]![1])) return false;
  if (!dependentTaskStates.has(taskStates[1]![1]) || !dependentTaskStates.has(taskStates[2]![1]))
    return false;
  if (runStatus === "completed" || planStatus === "completed")
    return (
      runStatus === "completed" &&
      planStatus === "completed" &&
      taskStates.every(([, status]) => status === "completed")
    );
  return planStatus === "approved";
}

function postgresRows(runtime: NodeJS.ProcessEnv, statement: string): string[][] {
  const databaseUrl = runtime.AWP_TEST_POSTGRES_URL;
  if (!databaseUrl) throw new Error("AWP_TEST_POSTGRES_URL is required");
  const output = commandOutput(
    execFileSync("psql", [databaseUrl, "-At", "-F", "\t", "-c", statement], {
      encoding: "utf8",
    }),
  );
  return output ? output.split("\n").map((row) => row.split("\t")) : [];
}

/** Crash-recovery query contract shared with the owner lifecycle proof. */
export const persistedOwnerClosure = Object.freeze({
  authoring(
    manifest: Extract<JourneyManifest, { stage: "seed" }>,
    runtime: NodeJS.ProcessEnv,
  ): readonly PersistedClosureState<OwnerCreatedIdentity>[] {
    const labels = journeyScopedOwnerLabels(manifest.journeyKey);
    const projects = postgresRows(
      runtime,
      `select id, repository_url from projects where name=${sql(labels.projectName)} order by id`,
    );
    if (!projects.length) return [];
    if (projects.length !== 1)
      return projects.map(() => ({ status: "mismatched", reason: "duplicate journey project" }));
    const [projectId, repositoryUrl] = projects[0]!;
    if (repositoryUrl !== manifest.repository.remoteUrl)
      return [{ status: "mismatched", reason: "repository URL differs from seed" }];
    const rows = postgresRows(
      runtime,
      `select p.id, r.id, v.id, v.summary, r.sequence, (select count(*) from goals g where g.project_id=p.project_id), (select min(g.title) from goals g where g.project_id=p.project_id), (select min(g.success_criteria->>0) from goals g where g.project_id=p.project_id), coalesce(string_agg(t.id, ',' order by t.position), ''), coalesce(string_agg(t.title, '|' order by t.position), ''), coalesce(string_agg(t.status, '|' order by t.position), '') from plans p join plan_revisions r on r.plan_id=p.id and r.project_id=p.project_id join project_vision_versions v on v.id=r.project_vision_version_id and v.project_id=p.project_id left join tasks t on t.plan_revision_id=r.id and t.project_id=p.project_id where p.project_id=${sql(projectId!)} and p.title=${sql(labels.planTitle)} group by p.id,r.id,v.id,v.summary,r.sequence order by p.id,r.id`,
    );
    if (rows.length !== 1)
      return [
        { status: "partial", reason: `expected one Plan/Revision closure, found ${rows.length}` },
      ];
    const [
      planId,
      planRevisionId,
      visionVersionId,
      visionSummary,
      revisionSequence,
      goalCount,
      persistedGoalTitle,
      persistedCriterion,
      taskList,
      titles,
      taskStatuses,
    ] = rows[0]!;
    const taskIds = taskList!.split(",").filter(Boolean);
    const dependencyEdges = postgresRows(
      runtime,
      `select d.task_id, d.prerequisite_task_id from task_dependencies d join tasks t on t.id=d.task_id and t.project_id=${sql(projectId!)} and t.plan_revision_id=${sql(planRevisionId!)} order by t.position, d.prerequisite_task_id`,
    ) as [string, string][];
    const expectedVision = loadCompactSelfProjectVision(manifest.repository.checkoutPath);
    const expectedGoalTitle = "Launch the first complete journey";
    const expectedCriterion = "Project, execution, review, and trusted merge are proven.";
    const expectedTitles = [
      "Document the canonical owner journey",
      "Add owner journey verification notes",
      "Review owner journey documentation",
    ];
    if (
      visionSummary !== expectedVision ||
      revisionSequence !== "1" ||
      goalCount !== "1" ||
      persistedGoalTitle !== expectedGoalTitle ||
      persistedCriterion !== expectedCriterion ||
      taskIds.length !== 3 ||
      titles!.split("|").join("|") !== expectedTitles.join("|") ||
      taskStatuses !== "planned|blocked|blocked" ||
      !hasExactDependencyChain(taskIds, dependencyEdges)
    )
      return [{ status: "partial", reason: "tasks or dependencies are incomplete" }];
    return [
      {
        status: "complete",
        value: {
          projectId: projectId!,
          planId: planId!,
          planRevisionId: planRevisionId!,
          taskIds,
          authoringReceipt: manifestFingerprint({
            projectId,
            projectName: labels.projectName,
            repositoryUrl,
            visionVersionId,
            goalTitle: persistedGoalTitle,
            criterion: persistedCriterion,
            planId,
            planRevisionId,
            taskIds,
            taskTitles: expectedTitles,
            dependencyCount: 2,
          }),
        },
      },
    ];
  },
  approval(
    manifest: Extract<JourneyManifest, { stage: "owner-created" }>,
    runtime: NodeJS.ProcessEnv,
  ): readonly PersistedClosureState<ApprovalIdentity>[] {
    const planRows = postgresRows(
      runtime,
      `select status from plans where id=${sql(manifest.planId)} and project_id=${sql(manifest.projectId)}`,
    );
    const taskStates = postgresRows(
      runtime,
      `select id, status from tasks where project_id=${sql(manifest.projectId)} and plan_revision_id=${sql(manifest.planRevisionId)} order by position`,
    ) as [string, string][];
    const dependencyEdges = postgresRows(
      runtime,
      `select d.task_id, d.prerequisite_task_id from task_dependencies d join tasks t on t.id=d.task_id and t.project_id=${sql(manifest.projectId)} and t.plan_revision_id=${sql(manifest.planRevisionId)} order by t.position, d.prerequisite_task_id`,
    ) as [string, string][];
    if (planRows.length !== 1 || taskStates.length !== 3)
      return [{ status: "mismatched", reason: "owner-created Plan/Task identity is missing" }];
    const planStatus = planRows[0]![0]!;
    const rows = postgresRows(
      runtime,
      `select f.id, f.account_id, f.model, f.status, f.reason, p.status, t.status, f.task_id from factory_runs f join plans p on p.id=${sql(manifest.planId)} and p.project_id=f.project_id join tasks t on t.id=${sql(manifest.taskIds[0]!)} and t.project_id=f.project_id and t.plan_revision_id=f.plan_revision_id where f.project_id=${sql(manifest.projectId)} and f.plan_revision_id=${sql(manifest.planRevisionId)} order by f.id`,
    );
    if (!rows.length) {
      if (!isPristinePreApproval(planStatus, taskStates, manifest.taskIds, dependencyEdges))
        return [{ status: "partial", reason: "approval closure mutated without its FactoryRun" }];
      return [];
    }
    if (rows.length !== 1)
      return rows.map(() => ({ status: "mismatched", reason: "multiple FactoryRuns" }));
    const [
      factoryRunId,
      accountId,
      model,
      runStatus,
      runReason,
      persistedPlanStatus,
      firstTaskStatus,
      factoryTaskId,
    ] = rows[0]!;
    if (
      accountId !== manifest.agentAccountId ||
      model !== manifest.agentModel ||
      firstTaskStatus !== taskStates[0]?.[1] ||
      factoryTaskId !== manifest.taskIds[0] ||
      !isCausallyAdvancedApproval(
        persistedPlanStatus!,
        runStatus!,
        runReason ?? "",
        taskStates,
        manifest.taskIds,
      ) ||
      !hasExactDependencyChain(manifest.taskIds, dependencyEdges)
    )
      return [
        { status: "mismatched", reason: "FactoryRun or approval state differs from journey" },
      ];
    return [
      {
        status: "complete",
        value: {
          factoryRunId: factoryRunId!,
          firstTaskId: manifest.taskIds[0]!,
          approvalReceipt: manifestFingerprint({
            projectId: manifest.projectId,
            planId: manifest.planId,
            planRevisionId: manifest.planRevisionId,
            planStatus: persistedPlanStatus,
            firstTaskId: manifest.taskIds[0],
            firstTaskStatus,
            factoryRunId,
            factoryTaskId,
            accountId,
            model,
            runStatus,
            runReason,
          }),
        },
      },
    ];
  },
});

function recoveredEvidence(
  manifest: JourneyManifest,
  proof: typeof ownerAuthoringProof | typeof ownerApprovalProof,
): readonly OwnerEvidence[] {
  return proof.criterionIds.map((criterionId) => ({
    criterionId,
    proofId: proof.proofId,
    outcome: "passed",
    generation: manifest.generation,
    seedFingerprint: manifest.seedFingerprint,
    entityIds: {
      ...(manifest.stage === "seed"
        ? {}
        : {
            projectId: manifest.projectId,
            planId: manifest.planId,
            planRevisionId: manifest.planRevisionId,
            taskIds: manifest.taskIds,
          }),
      ...(manifest.stage === "approved" ? { factoryRunId: manifest.factoryRunId } : {}),
    },
    evidenceSource: "ui+postgres",
    timestamp: new Date().toISOString(),
  }));
}

export const ownerLifecycleHandlers: ReadonlyMap<
  "owner-authoring" | "owner-approval",
  LifecycleHandler
> = new Map([
  [
    "owner-authoring",
    async (manifestPath, runtime) =>
      withJourneyMutationLock(manifestPath, async () => {
        const manifest = loadJourneyManifestAtLeast(manifestPath, "seed");
        if (manifest.stage !== "seed") {
          return recoveredEvidence(manifest, ownerAuthoringProof);
        }
        const unit = proofUnits.find((candidate) => candidate.unitId === "owner-authoring")!;
        const environment = proofEnvironment(
          unit,
          manifest,
          { ...runtime, AWP_JOURNEY_MANIFEST_PATH: manifestPath },
          true,
        );
        const recovered = reconcilePersistedClosure(
          "owner authoring",
          persistedOwnerClosure.authoring(manifest, environment),
        );
        if (recovered) {
          await ensureRecoveredOwnerAuthoringUi(manifest, recovered, environment);
          const checkpoint = transitionToOwnerCreated(manifestPath, manifest, recovered);
          return recoveredEvidence(checkpoint, ownerAuthoringProof);
        }
        return (await authorOwnerJourney(manifest, environment)).evidence;
      }),
  ],
  [
    "owner-approval",
    async (manifestPath, runtime) =>
      withJourneyMutationLock(manifestPath, async () => {
        const manifest = loadJourneyManifestAtLeast(manifestPath, "owner-created");
        if (manifest.stage === "approved") {
          return recoveredEvidence(manifest, ownerApprovalProof);
        }
        const unit = proofUnits.find((candidate) => candidate.unitId === "owner-approval")!;
        const environment = proofEnvironment(
          unit,
          manifest,
          { ...runtime, AWP_JOURNEY_MANIFEST_PATH: manifestPath },
          true,
        );
        const recovered = reconcilePersistedClosure(
          "owner approval",
          persistedOwnerClosure.approval(manifest, environment),
        );
        if (recovered) {
          await ensureRecoveredOwnerApprovalUi(manifest, recovered, environment);
          const checkpoint = transitionToApproved(manifestPath, manifest, recovered);
          return recoveredEvidence(checkpoint, ownerApprovalProof);
        }
        return (await approveOwnerJourney(manifest, environment)).evidence;
      }),
  ],
]);

export function lifecycleHandlerFor(unitId: string): LifecycleHandler | undefined {
  return ownerLifecycleHandlers.get(unitId as "owner-authoring" | "owner-approval");
}
export interface RepositoryPreflight {
  checkoutPath: string;
  originUrl: string;
  defaultBranchRef: string;
  preRunTip: string;
}
export function validateRepository(manifest: JourneyManifest): RepositoryPreflight {
  const checkout = realpathSync(manifest.repository.checkoutPath);
  const git = (args: string[]) =>
    commandOutput(execFileSync("git", ["-C", checkout, ...args], { encoding: "utf8" }));
  const top = realpathSync(git(["rev-parse", "--show-toplevel"]));
  if (top !== checkout) throw new Error("repository checkoutPath does not equal Git toplevel");
  const origin = git(["remote", "get-url", "origin"]);
  const seededRemote = canonicalRepositoryParts(manifest.repository.remoteUrl);
  if (
    seededRemote.owner !== manifest.repository.owner.toLowerCase() ||
    seededRemote.repository !== manifest.repository.name.toLowerCase() ||
    canonicalGitHubRepository(origin) !== canonicalGitHubRepository(manifest.repository.remoteUrl)
  ) {
    throw new Error("repository origin/seed host, owner, and name identity mismatch");
  }
  const remoteHead = authoritativeRemoteHead(git(["ls-remote", "--symref", "origin", "HEAD"]));
  const authoritativeBranch = remoteHead.branchRef.slice("refs/heads/".length);
  if (authoritativeBranch !== manifest.repository.defaultBranch) {
    throw new Error(
      `repository default branch mismatch: origin=${authoritativeBranch}, seed=${manifest.repository.defaultBranch}`,
    );
  }
  const ref = `refs/remotes/origin/${authoritativeBranch}`;
  git(["show-ref", "--verify", ref]);
  return {
    checkoutPath: checkout,
    originUrl: origin,
    defaultBranchRef: ref,
    preRunTip: remoteHead.oid,
  };
}
const execFileAsync = promisify(execFile);

const barrierPlan = Object.freeze([
  { start: "P0", await: "P0" },
  { start: "P1", await: "owner-created" },
  { start: "P2" },
  { start: "P3", await: "approved" },
  { start: "P4", await: "P4-complete" },
  { start: "P5", await: "P5-complete" },
  { start: "P6", await: "P6-complete" },
  { start: "P7", await: "P7-complete" },
  { start: "P8", await: "P8-complete" },
  { start: "P9", await: "P9-complete" },
  { start: "P10", await: "P10" },
] as const);

export async function executeBarrierSchedule<T>(
  units: readonly ProofUnit[],
  runUnit: (unit: ProofUnit) => Promise<T>,
  completeUnit: (unit: ProofUnit, value: T) => Promise<void>,
): Promise<void> {
  const running = new Map<string, { unit: ProofUnit; promise: Promise<T> }>();
  for (const barrier of barrierPlan) {
    const starting = units.filter((unit) => unit.startBarrier === barrier.start);
    for (const unit of starting) {
      if (unit.awaitBarrier === barrier.start) {
        const value = await runUnit(unit);
        await completeUnit(unit, value);
      } else {
        running.set(unit.unitId, { unit, promise: runUnit(unit) });
      }
    }
    if (!barrier.await || barrier.await === barrier.start) continue;
    const due = [...running.values()].filter((entry) => entry.unit.awaitBarrier === barrier.await);
    const values = await Promise.all(
      due.map(async (entry) => ({ entry, value: await entry.promise })),
    );
    for (const { entry, value } of values) {
      await completeUnit(entry.unit, value);
      running.delete(entry.unit.unitId);
    }
  }
  if (running.size > 0) {
    throw new Error(
      `GOLIVE controller ended with unawaited proofs: ${[...running.values()]
        .map(({ unit }) => `${unit.unitId}->${unit.awaitBarrier}`)
        .join(", ")}`,
    );
  }
}

interface ProofRunResult {
  readonly output: string;
  readonly reused: boolean;
}

function unitHasDurableReceipts(ledger: ProofReceiptLedger, unit: ProofUnit): boolean {
  let complete = true;
  for (const criterionId of unit.criterionIds) {
    const receipt = ledger.receipts[criterionId];
    if (!receipt) {
      complete = false;
      continue;
    }
    if (receipt.unitId !== unit.unitId || receipt.proofId !== unit.proofId) {
      throw new Error(`${criterionId} durable receipt is bound to a different proof`);
    }
  }
  return complete;
}

async function executeProofUnit(
  unit: ProofUnit,
  manifestPath: string,
  root: string,
  runtime: NodeJS.ProcessEnv,
  ledgerPath: string,
  signal: AbortSignal,
): Promise<ProofRunResult> {
  const manifest = loadJourneyManifestAtLeast(manifestPath, unit.requiredManifestStage);
  if (unitHasDurableReceipts(loadProofReceiptLedger(ledgerPath, manifest), unit)) {
    return { output: `reused durable receipts for ${unit.criterionIds.join(",")}`, reused: true };
  }
  const lifecycle = lifecycleHandlerFor(unit.unitId);
  if (lifecycle) {
    const evidence = await lifecycle(manifestPath, runtime);
    const evidenceIds = new Set<string>(evidence.map((item) => item.criterionId));
    if (unit.criterionIds.some((criterionId) => !evidenceIds.has(criterionId))) {
      throw new Error(`${unit.unitId} lifecycle proof did not return every declared criterion`);
    }
    return { output: JSON.stringify(evidence), reused: false };
  }

  const environment = proofEnvironment(unit, manifest, runtime, true);
  const commandArgs = unit.file.endsWith(".test.ts")
    ? ["exec", "vitest", "run", unit.file]
    : ["exec", "tsx", unit.file];
  const { stdout, stderr } = await execFileAsync("pnpm", commandArgs, {
    cwd: root,
    env: environment,
    maxBuffer: 32 * 1024 * 1024,
    signal,
  });
  const output = [stdout, stderr].filter(Boolean).join("\n").trim();
  return { output, reused: false };
}

function canonicalAcceptanceFile(root: string, runtime: NodeJS.ProcessEnv): string {
  const configured = runtime.AWP_ACCEPTANCE_STATE_FILE;
  if (!configured)
    throw new Error("AWP_ACCEPTANCE_STATE_FILE is required for live GOLIVE execution");
  const expected = realpathSync(resolve(root, "GOLIVE.md"));
  const actual = realpathSync(resolve(configured));
  if (actual !== expected) {
    throw new Error("live GOLIVE acceptance state must be the repository-root GOLIVE.md");
  }
  return actual;
}

export async function executeJourney(
  manifestPath: string,
  root = process.cwd(),
  runtime: NodeJS.ProcessEnv = process.env,
): Promise<void> {
  validateExecutionPlan(root);
  const initialManifest = loadJourneyManifestAtLeast(manifestPath, "seed");
  const preflight = validateRepository(initialManifest);
  if (realpathSync(root) !== preflight.checkoutPath) {
    throw new Error("GOLIVE controller root must equal the manifest repository checkout");
  }
  const acceptanceFile = canonicalAcceptanceFile(root, runtime);
  const ledgerPath = receiptLedgerPath(manifestPath);
  let ledger = loadProofReceiptLedger(ledgerPath, initialManifest);
  reconcileAcceptanceFile(acceptanceFile, ledger);

  const abortController = new AbortController();
  try {
    await executeBarrierSchedule(
      proofUnits,
      (unit) =>
        executeProofUnit(
          unit,
          manifestPath,
          root,
          { ...runtime, AWP_JOURNEY_MANIFEST_PATH: manifestPath },
          ledgerPath,
          abortController.signal,
        ),
      async (unit, result) => {
        const manifest = loadJourneyManifestAtLeast(manifestPath, unit.requiredManifestStage);
        ledger = persistProofReceipts(ledgerPath, manifest, {
          unitId: unit.unitId,
          proofId: unit.proofId,
          criterionIds: unit.criterionIds,
          output: result.output || `${unit.unitId} passed without textual output`,
        });
        reconcileAcceptanceFile(acceptanceFile, ledger);
        if (result.output) console.log(result.output);
        console.log(
          `${result.reused ? "REUSED" : "PASS"} ${unit.unitId}: ${unit.criterionIds.join(", ")}`,
        );
      },
    );
  } catch (error) {
    abortController.abort();
    throw error;
  }

  const finalState = acceptanceState(execFileSync("cat", [acceptanceFile], { encoding: "utf8" }));
  if (finalState.checked.size !== finalState.total || finalState.total !== 30) {
    throw new Error(
      `GOLIVE controller completed without full acceptance: ${finalState.checked.size}/${finalState.total}`,
    );
  }
  console.log("registry structure: 30/30");
  console.log("semantic readiness: 30/30");
  console.log("owner acceptance: 30/30");
}

export function main(argv = process.argv.slice(2)): void | Promise<void> {
  const validation = argv.includes("--validate") || argv.includes("--validate-bindings");
  const list = argv.includes("--list");
  const execute = argv.includes("--execute");
  if (execute && (validation || list))
    throw new Error("validation/listing and live execution are separate commands");
  validateStructure();
  console.log(statusSummary());
  if (validation || list) {
    if (list)
      for (const u of proofUnits)
        console.log(
          `${u.unitId} ${u.migrationStatus} ${u.criterionIds.join(",")} ${u.startBarrier}->${u.awaitBarrier} ${u.file}`,
        );
    return;
  }
  if (!execute) throw new Error("refusing to start live journey without explicit --execute");
  const i = argv.indexOf("--manifest");
  if (i < 0 || !argv[i + 1]) throw new Error("--execute requires --manifest <persisted-json-path>");
  return executeJourney(resolve(argv[i + 1]!));
}
const invoked = process.argv[1] ? resolve(process.argv[1]) : undefined;
if (invoked === resolve(fileURLToPath(import.meta.url))) {
  Promise.resolve(main()).catch((error) => {
    console.error(error instanceof Error ? error.message : error);
    process.exitCode = 1;
  });
}
