/**
 * cli:compute-page-diff — disk-drift.ts  (Phase 3a orchestrator guard)
 *
 * `compute-page-diff` stays a PURE spec-diff CLI (AC-F4.1-011). This module is
 * the orchestrator-side guard that closes the **silent-skip class**: a pagespec
 * whose hash is unchanged can still have a drifted `.tsx` on disk — deleted,
 * hand-edited into an error state, broken i18n / import. Left alone, an empty
 * spec-diff would skip Phase 3a and let a broken page through, exactly the
 * failure the backend `pre_entry_coverage_check` closes for Phases 0-2
 * (`references/gates.md`).
 *
 * The orchestrator (model-driven, no `orchestrator.ts`) executes this contract
 * by running `compute-page-diff` then `validate-page` on each `unchanged` page.
 * These pure helpers are the tested reference of that union — the file
 * existence + `validate-page` err-count are INJECTED, so the union logic is
 * unit-testable without a real web project.
 */

export interface DiskDriftDeps {
  /** True when the page's generated `.tsx` exists on disk. */
  exists: (key: string) => boolean
  /** Count of `err`-severity violations `validate-page` reports for the page. */
  errCount: (key: string) => number
}

/**
 * Given the `unchanged` set from `compute-page-diff`, return the subset whose
 * `.tsx` is **missing or invalid** on disk. Only `unchanged` pages are checked:
 * `added` / `modified` already regenerate, and `removed` pages no longer exist.
 *
 * Note: `validate-page` itself returns an `err` for a missing file, so the
 * `exists` check is belt-and-suspenders — it lets a caller short-circuit
 * `validate-page` for pages it already knows are gone.
 */
export function computeDiskDrift(unchanged: string[], deps: DiskDriftDeps): string[] {
  const drift: string[] = []
  for (const key of unchanged) {
    if (!deps.exists(key)) {
      drift.push(key) // missing
      continue
    }
    if (deps.errCount(key) > 0) drift.push(key) // invalid on disk
  }
  return drift
}

/**
 * Final Phase 3a regeneration set = spec-diff (`added ∪ modified`) ∪ disk-drift.
 * Disk-drift keys outside the spec-diff are folded in; the result is sorted and
 * de-duplicated. Phase 3a is skipped **only** when this is empty — i.e. spec
 * AND disk are both clean.
 */
export function selectPagesToRegenerate(specToRegenerate: string[], diskDrift: string[]): string[] {
  return [...new Set([...specToRegenerate, ...diskDrift])].sort()
}
