export type AcceptanceCriterionId = `AC-${string}`;

export const EXPECTED_CRITERION_IDS = Object.freeze(
  Array.from(
    { length: 30 },
    (_, index) => `AC-${String(index + 1).padStart(2, "0")}` as AcceptanceCriterionId,
  ),
);

export type MigrationStatus = "planned" | "pending";
export type LifecycleRole =
  | "gate"
  | "owner-authoring"
  | "owner-approval"
  | "observer"
  | "work"
  | "publication"
  | "merge"
  | "completion"
  | "terminal"
  | "closure";

export interface ProofUnit {
  readonly unitId: string;
  readonly proofId: string;
  readonly file: `tests/golive/${string}`;
  readonly criterionIds: readonly AcceptanceCriterionId[];
  readonly lifecycleRole: LifecycleRole;
  readonly requiredManifestStage: "seed" | "owner-created" | "approved";
  readonly startBarrier: string;
  readonly awaitBarrier: string;
  readonly inputSelectors: readonly string[];
  readonly runtimeInputs: readonly string[];
  readonly outputs: readonly string[];
  readonly scope: string;
  readonly prerequisites: readonly string[];
  readonly migrationStatus: MigrationStatus;
  readonly pendingReason: string;
  readonly sharedProofJustification?: string;
}

type Definition = Omit<ProofUnit, "file" | "criterionIds" | "outputs"> & {
  readonly file: string;
  readonly criteria: readonly number[];
};

const approvedIdentitySelectors = Object.freeze([
  "projectId",
  "planId",
  "planRevisionId",
  "taskIds",
  "factoryRunId",
  "firstTaskId",
]);
const ownerCreatedIdentitySelectors = Object.freeze([
  "projectId",
  "planId",
  "planRevisionId",
  "taskIds",
]);
const authenticatedProofFiles = new Set([
  "01-ui-loads.spec.ts",
  "04-owner-lifecycle.spec.ts",
  "17-attempt-retry.spec.ts",
  "19-review-merge.spec.ts",
  "23-live-transitions.spec.ts",
  "24-tool-log-diff.spec.ts",
  "25-failure-state.spec.ts",
  "28-self-project.test.ts",
  "30-golive-progress.spec.ts",
]);

const definition = (
  file: string,
  criteria: readonly number[],
  unitId: string,
  lifecycleRole: LifecycleRole,
  startBarrier: string,
  awaitBarrier: string,
  inputSelectors: readonly string[],
  runtimeInputs: readonly string[],
  prerequisites: readonly string[] = [],
  migrationStatus: MigrationStatus = "pending",
): Definition => {
  const requiredManifestStage =
    lifecycleRole === "owner-authoring" || lifecycleRole === "gate"
      ? "seed"
      : lifecycleRole === "owner-approval" || unitId === "live-transitions"
        ? "owner-created"
        : "approved";
  const identitySelectors =
    requiredManifestStage === "approved"
      ? approvedIdentitySelectors
      : requiredManifestStage === "owner-created"
        ? ownerCreatedIdentitySelectors
        : [];
  const declaredInputs = [...new Set([...inputSelectors, ...identitySelectors])];
  const declaredRuntime = [
    ...new Set([
      ...runtimeInputs,
      ...(authenticatedProofFiles.has(file) ? ["AWP_OPERATOR_PASSWORD"] : []),
    ]),
  ];
  return {
    unitId,
    proofId: `${unitId}-ac-${criteria.map((id) => String(id).padStart(2, "0")).join("-")}`,
    file,
    criteria,
    lifecycleRole,
    requiredManifestStage,
    startBarrier,
    awaitBarrier,
    inputSelectors: declaredInputs,
    runtimeInputs: declaredRuntime,
    scope: "one exact persisted journey lineage; zero or multiple matches fail",
    prerequisites,
    migrationStatus,
    pendingReason:
      migrationStatus === "planned"
        ? "proof contract is planned but has not been migrated to exact journey-scoped receipts"
        : "proof contract is migrated but has not produced an owner-accepted semantic receipt",
  };
};

const definitions: readonly Definition[] = [
  definition(
    "01-ui-loads.spec.ts",
    [1],
    "ui-loads",
    "gate",
    "P0",
    "P0",
    ["repository.remoteUrl", "repository.checkoutPath", "repository.defaultBranch"],
    ["AWP_WEB_URL", "AWP_PLAYWRIGHT_MODULE"],
    [],
    "pending",
  ),
  definition(
    "02-control-plane-postgres.test.ts",
    [2],
    "control-plane-postgres",
    "gate",
    "P0",
    "P0",
    [],
    ["AWP_CONTROL_PLANE_URL", "AWP_TEST_POSTGRES_URL", "AWP_WEB_URL"],
  ),
  definition(
    "03-restart-snapshot.spec.ts",
    [3],
    "restart-snapshot",
    "terminal",
    "P9",
    "P9-complete",
    ["factoryRunId"],
    [
      "AWP_CONTROL_PLANE_HOST",
      "AWP_CONTROL_PLANE_SERVICE",
      "AWP_CONTROL_PLANE_URL",
      "AWP_GOLIVE_TIMEOUT_MS",
      "AWP_TEST_POSTGRES_URL",
    ],
    ["plan-completion"],
  ),
  definition(
    "04-owner-lifecycle.spec.ts",
    [4, 5, 6, 7, 8, 9],
    "owner-authoring",
    "owner-authoring",
    "P1",
    "owner-created",
    ["journeyKey", "repository.remoteUrl", "agentAccountId", "agentModel"],
    ["AWP_WEB_URL", "AWP_PLAYWRIGHT_MODULE", "AWP_TEST_POSTGRES_URL", "AWP_JOURNEY_MANIFEST_PATH"],
    ["ui-loads", "control-plane-postgres"],
    "pending",
  ),
  definition(
    "04-owner-lifecycle.spec.ts",
    [10, 11],
    "owner-approval",
    "owner-approval",
    "P3",
    "approved",
    ["projectId", "planId", "planRevisionId", "taskIds"],
    ["AWP_WEB_URL", "AWP_PLAYWRIGHT_MODULE", "AWP_TEST_POSTGRES_URL", "AWP_JOURNEY_MANIFEST_PATH"],
    ["owner-authoring"],
    "pending",
  ),
  definition(
    "12-k3s-agent-run.spec.ts",
    [12],
    "k3s-agent-run",
    "work",
    "P4",
    "P4-complete",
    ["factoryRunId", "firstTaskId"],
    [
      "AWP_CLEAN_WORKER_NODE",
      "AWP_KUBECONFIG",
      "AWP_TEST_POSTGRES_URL",
      "AWP_WORKSPACE_IMAGE",
      "AWP_WORKSPACE_NAMESPACE",
    ],
    ["owner-approval"],
  ),
  definition(
    "13-dependency-order.spec.ts",
    [13],
    "dependency-order",
    "observer",
    "P8",
    "P8-complete",
    ["planRevisionId", "taskIds", "factoryRunId"],
    ["AWP_GOLIVE_TIMEOUT_MS", "AWP_TEST_POSTGRES_URL"],
    ["owner-approval"],
  ),
  definition(
    "14-artifact-cleanup.spec.ts",
    [14],
    "artifact-cleanup",
    "closure",
    "P7",
    "P7-complete",
    ["factoryRunId", "firstTaskId"],
    ["AWP_GOLIVE_TIMEOUT_MS", "AWP_KUBECONFIG", "AWP_TEST_POSTGRES_URL", "AWP_WORKSPACE_NAMESPACE"],
    ["k3s-agent-run"],
  ),
  definition(
    "15-changeset.spec.ts",
    [15, 16],
    "changeset",
    "work",
    "P4",
    "P5-complete",
    [
      "projectId",
      "planRevisionId",
      "factoryRunId",
      "repository.remoteUrl",
      "repository.checkoutPath",
      "repository.defaultBranch",
    ],
    ["AWP_GOLIVE_TIMEOUT_MS", "AWP_TEST_POSTGRES_URL"],
    ["k3s-agent-run"],
  ),
  definition(
    "17-attempt-retry.spec.ts",
    [17],
    "attempt-retry",
    "work",
    "P4",
    "P4-complete",
    ["factoryRunId", "firstTaskId"],
    ["AWP_GOLIVE_TIMEOUT_MS", "AWP_PLAYWRIGHT_MODULE", "AWP_TEST_POSTGRES_URL", "AWP_WEB_URL"],
    ["k3s-agent-run"],
  ),
  definition(
    "18-agent-cannot-push.spec.ts",
    [18],
    "agent-cannot-push",
    "work",
    "P4",
    "P4-complete",
    ["factoryRunId", "firstTaskId", "repository.checkoutPath"],
    ["AWP_KUBECONFIG", "AWP_TEST_POSTGRES_URL", "AWP_WORKSPACE_NAMESPACE"],
    ["k3s-agent-run"],
  ),
  definition(
    "19-review-merge.spec.ts",
    [19, 20, 21],
    "review-merge",
    "merge",
    "P6",
    "P6-complete",
    [
      "factoryRunId",
      "firstTaskId",
      "planRevisionId",
      "taskIds",
      "repository.checkoutPath",
      "repository.defaultBranch",
    ],
    ["AWP_CONTROL_PLANE_URL", "AWP_PLAYWRIGHT_MODULE", "AWP_TEST_POSTGRES_URL", "AWP_WEB_URL"],
    ["changeset"],
  ),
  definition(
    "22-plan-completion.spec.ts",
    [22],
    "plan-completion",
    "completion",
    "P8",
    "P8-complete",
    [
      "agentAccountId",
      "agentModel",
      "projectId",
      "planId",
      "planRevisionId",
      "factoryRunId",
      "repository.remoteUrl",
      "taskIds",
    ],
    ["AWP_GOLIVE_TIMEOUT_MS", "AWP_TEST_POSTGRES_URL"],
    ["review-merge"],
  ),
  definition(
    "23-live-transitions.spec.ts",
    [23],
    "live-transitions",
    "observer",
    "P2",
    "P8-complete",
    ["projectId", "planRevisionId", "taskIds"],
    ["AWP_GOLIVE_TIMEOUT_MS", "AWP_PLAYWRIGHT_MODULE", "AWP_TEST_POSTGRES_URL", "AWP_WEB_URL"],
    ["owner-authoring"],
  ),
  definition(
    "24-tool-log-diff.spec.ts",
    [24],
    "tool-log-diff",
    "work",
    "P4",
    "P4-complete",
    ["factoryRunId", "firstTaskId"],
    ["AWP_GOLIVE_TIMEOUT_MS", "AWP_PLAYWRIGHT_MODULE", "AWP_TEST_POSTGRES_URL", "AWP_WEB_URL"],
    ["k3s-agent-run"],
  ),
  definition(
    "25-failure-state.spec.ts",
    [25],
    "failure-state",
    "work",
    "P4",
    "P4-complete",
    ["factoryRunId", "firstTaskId"],
    ["AWP_GOLIVE_TIMEOUT_MS", "AWP_PLAYWRIGHT_MODULE", "AWP_TEST_POSTGRES_URL", "AWP_WEB_URL"],
    ["k3s-agent-run"],
  ),
  definition(
    "26-control-plane-restart.spec.ts",
    [26],
    "control-plane-restart",
    "work",
    "P4",
    "P5-complete",
    ["factoryRunId", "firstTaskId"],
    [
      "AWP_CONTROL_PLANE_HOST",
      "AWP_CONTROL_PLANE_SERVICE",
      "AWP_CONTROL_PLANE_URL",
      "AWP_GOLIVE_TIMEOUT_MS",
      "AWP_KUBECONFIG",
      "AWP_TEST_POSTGRES_URL",
      "AWP_WORKSPACE_NAMESPACE",
    ],
    ["k3s-agent-run"],
  ),
  definition(
    "27-merge-restart.spec.ts",
    [27],
    "merge-restart",
    "publication",
    "P7",
    "P7-complete",
    [
      "factoryRunId",
      "taskIds",
      "repository.checkoutPath",
      "repository.remoteUrl",
      "repository.defaultBranch",
    ],
    [
      "AWP_CONTROL_PLANE_HOST",
      "AWP_CONTROL_PLANE_SERVICE",
      "AWP_CONTROL_PLANE_URL",
      "AWP_GOLIVE_TIMEOUT_MS",
      "AWP_TEST_POSTGRES_URL",
    ],
    ["review-merge"],
  ),
  definition(
    "28-self-project.test.ts",
    [28],
    "self-project",
    "closure",
    "P10",
    "P10",
    [
      "projectId",
      "repository.remoteUrl",
      "repository.checkoutPath",
      "repository.owner",
      "repository.name",
      "repository.defaultBranch",
    ],
    ["AWP_CONTROL_PLANE_URL", "AWP_TEST_POSTGRES_URL"],
    ["plan-completion"],
  ),
  definition(
    "29-self-change.spec.ts",
    [29],
    "self-change",
    "closure",
    "P10",
    "P10",
    ["factoryRunId", "repository.checkoutPath", "repository.remoteUrl", "repository.defaultBranch"],
    ["AWP_TEST_POSTGRES_URL"],
    ["self-project", "review-merge"],
  ),
  definition(
    "30-golive-progress.spec.ts",
    [30],
    "golive-progress",
    "closure",
    "P10",
    "P10",
    ["factoryRunId", "repository.remoteUrl"],
    ["AWP_ACCEPTANCE_STATE_FILE", "AWP_PLAYWRIGHT_MODULE", "AWP_TEST_POSTGRES_URL", "AWP_WEB_URL"],
    ["plan-completion"],
  ),
];

export const proofUnits: readonly ProofUnit[] = Object.freeze(
  definitions.map(({ criteria, file, ...unit }) => {
    const criterionIds = criteria.map(
      (number) => `AC-${String(number).padStart(2, "0")}` as AcceptanceCriterionId,
    );
    return Object.freeze({
      ...unit,
      file: `tests/golive/${file}` as const,
      criterionIds: Object.freeze(criterionIds),
      inputSelectors: Object.freeze([...unit.inputSelectors]),
      runtimeInputs: Object.freeze([...unit.runtimeInputs]),
      prerequisites: Object.freeze([...unit.prerequisites]),
      outputs: Object.freeze(criterionIds.map((id) => `receipt:${id}`)),
      ...(criterionIds.length > 1
        ? {
            sharedProofJustification:
              "One journey-scoped lifecycle event emits a distinct receipt for each criterion.",
          }
        : {}),
    });
  }),
);

export const criterionRegistry = Object.freeze(
  proofUnits.flatMap((unit) =>
    unit.criterionIds.map((id) =>
      Object.freeze({
        id,
        unitId: unit.unitId,
        testFile: unit.file,
        migrationStatus: unit.migrationStatus,
      }),
    ),
  ),
);

export const criterionReverseIndex = Object.freeze(
  Object.fromEntries(criterionRegistry.map((entry) => [entry.id, entry.unitId])) as Record<
    AcceptanceCriterionId,
    string
  >,
);

export function validateCriterionRegistry(units: readonly ProofUnit[] = proofUnits): void {
  const ids = units.flatMap((unit) => unit.criterionIds);
  const counts = new Map(ids.map((id) => [id, ids.filter((candidate) => candidate === id).length]));
  const missing = EXPECTED_CRITERION_IDS.filter((id) => !counts.has(id));
  const duplicate = [...counts].filter(([, count]) => count !== 1).map(([id]) => id);
  const unknown = [...counts.keys()].filter((id) => !EXPECTED_CRITERION_IDS.includes(id));
  if (missing.length || duplicate.length || unknown.length) {
    throw new Error(
      `invalid criterion registry (missing: ${missing.join(", ") || "none"}; duplicate: ${duplicate.join(", ") || "none"}; unknown: ${unknown.join(", ") || "none"})`,
    );
  }
  if (ids.some((id, index) => id !== EXPECTED_CRITERION_IDS[index])) {
    throw new Error("criterion order mismatch");
  }

  const unitIds = new Set(units.map((unit) => unit.unitId));
  if (unitIds.size !== units.length) throw new Error("proof unit IDs must be unique and stable");
  for (const unit of units) {
    if (!unit.proofId || !unit.scope || !unit.startBarrier || !unit.awaitBarrier) {
      throw new Error(`incomplete proof contract ${unit.unitId}`);
    }
    if (unit.file.startsWith("tests/smoke/")) {
      throw new Error(`destructive smoke proof ${unit.file}`);
    }
    if (unit.criterionIds.length > 1 && !unit.sharedProofJustification?.trim()) {
      throw new Error(`proof ${unit.file} is reused without an explicit justification`);
    }
    if (!unit.pendingReason.trim()) throw new Error(`${unit.unitId} lacks truthful pending status`);
    for (const prerequisite of unit.prerequisites) {
      if (!unitIds.has(prerequisite)) {
        throw new Error(`${unit.unitId} has unknown prerequisite ${prerequisite}`);
      }
    }
  }
}

export function semanticReadiness(units: readonly ProofUnit[] = proofUnits) {
  return {
    ready: 0,
    total: EXPECTED_CRITERION_IDS.length,
    pending: units,
  };
}
