/**
 * Types for the series distillate (N11 — serie-nivå-vern / C-5).
 *
 * The gap this closes: every long-form gate agent sees ONE edition. At two
 * editions a week the fastest-growing defect class is not a bad edition — it is
 * a *repeated* one: the anecdote, the argument or the hook the reader already
 * met three editions ago. Structurally invisible today, and the specifics-bank
 * dedupe actively encourages re-surfacing the same material.
 *
 * So each locked edition leaves behind a distillate of the narrative units it
 * spent, and the Step 2.5 skeleton gate checks the NEXT skeleton against it —
 * before prose exists, which is the only point where changing course is cheap.
 *
 * Deterministic by design: the extraction of units from a locked edition is an
 * AI job (the command layer), but the store, the comparison and the verdict are
 * plain code — no model in the loop, no run-to-run variance.
 *
 * Scope note: this file is SERIES-scoped memory ("has this reader heard it?").
 * Cross-series re-use of the operator's raw material is the specifics-bank's
 * `usedIn` log — a different grain, deliberately a different store.
 */

export const SCHEMA_VERSION = 1;

/** The three narrative units a reader actually recognizes across editions. */
export type NarrativeKind = "anecdote" | "argument" | "hook";

/** What one locked edition spent. */
export interface DistillateEntry {
  /** Edition id within the series, e.g. "05". */
  editionId: string;
  title: string;
  /** ISO date the edition was locked. Supplied by the caller (CLI edge) — no clock in the pure core. */
  lockedAt: string;
  /** Stories told, in the operator's own phrasing. */
  anecdotes: string[];
  /** Claims argued. */
  arguments: string[];
  /** Opening moves used. */
  hooks: string[];
}

export interface SeriesDistillate {
  schemaVersion: number;
  /** Series slug — the folder the editions live in. */
  series: string;
  editions: DistillateEntry[];
}

/** The units a NEW skeleton proposes, checked before prose (Step 2.5). */
export interface SkeletonCandidate {
  anecdotes?: string[];
  arguments?: string[];
  hooks?: string[];
}

/** One proposed unit that resembles something the series already published. */
export interface ReuseHit {
  kind: NarrativeKind;
  /** The proposed text. */
  candidate: string;
  /** The already-published text it resembles. */
  matched: string;
  /** Which edition spent it. */
  editionId: string;
  /** 0..1 similarity; 1 = verbatim. */
  score: number;
}

export type ReuseVerdict = "CLEAR" | "REUSE";

/* ------------------------------------------------------------------ *
 * Editions register (N12 — A1-11 / A1-12)
 *
 * A second, orthogonal grain of series memory: the distillate above answers
 * "has the reader heard this before?", the register answers "what is in flight
 * right now, and how long has it taken?". At two editions a week the expensive
 * blind spot is not content — it is that the only record of an in-flight edition
 * lives inside its own `edition-state.json`, so seeing the week means opening
 * every series folder by hand, and lead time is never measured at all.
 *
 * The register is a MIRROR, never a source of truth: every row is derived from
 * an `edition-state.json` at a phase transition. Losing it costs telemetry, not
 * an edition — rebuild by re-running the transitions.
 * ------------------------------------------------------------------ */

export const REGISTER_SCHEMA_VERSION = 1;

export type EditionStatus = "active" | "complete";

/** One in-flight (or finished) edition, keyed by `(series, editionId)`. */
export interface EditionRow {
  /** Series slug — `series.slug` in edition-state.json. */
  series: string;
  /** Edition id within the series — `currentArticle`, e.g. "05". */
  editionId: string;
  title: string;
  /** Absolute path to the series root holding this edition. */
  path: string;
  /** Last completed phase, mirrored from `currentPhase`. */
  currentPhase: string;
  /** The one next concrete step, in the operator's words. */
  nextAction: string | null;
  /** Publishing slot, once one is claimed (N13 wires the slot layer). */
  slot: string | null;
  /** First transition seen — the lead-time anchor. Never moves. */
  startedAt: string;
  updatedAt: string;
  completedAt: string | null;
  status: EditionStatus;
}

export interface EditionRegister {
  schemaVersion: number;
  editions: EditionRow[];
}

/** What a phase transition supplies. Omitted optionals are PRESERVED, not blanked. */
export interface UpsertInput {
  series: string;
  editionId: string;
  path: string;
  currentPhase: string;
  title?: string;
  nextAction?: string;
  slot?: string;
}

/* ---- edition-state.json: the mirrored source of truth ---- */

/** One completed phase transition — the raw material for lead-time measurement. */
export interface PhaseLogEntry {
  phase: string;
  /** ISO timestamp. Supplied by the caller (CLI edge) — no clock in the pure core. */
  completedAt: string;
}

/** Structurally open: this package reads a few fields and must preserve the rest verbatim. */
export interface EditionArticle {
  title?: string;
  phase?: string;
  phaseLog?: PhaseLogEntry[];
  [key: string]: unknown;
}

export interface EditionStateFile {
  schemaVersion?: number;
  series?: { slug?: string; title?: string };
  currentArticle?: string;
  currentPhase?: string;
  articles: Record<string, EditionArticle>;
  [key: string]: unknown;
}

/** The fields the register mirrors out of an edition-state file. */
export interface EditionFacts {
  series: string;
  editionId: string;
  title: string;
  currentPhase: string;
}

/**
 * Advisory by design — this reports into the annotation gate the operator
 * already reads at Step 2.5; it never blocks. Re-use is sometimes the right
 * call (a deliberate callback); silence about it never is.
 */
export interface ReuseReport {
  verdict: ReuseVerdict;
  hits: ReuseHit[];
  threshold: number;
  /** How many locked editions the skeleton was compared against. */
  comparedAgainst: number;
}
