/**
 * # bus/anchor-ledger — the SPOT-anchor UNKNOWN-window ledger + fail-closed under-report validator (ven.4)
 *
 * The de-arm PRIMITIVE already lives in the series layer: an unresolvable value anchor reads
 * UNKNOWN (`CanonicalState.spot: number | Unknown`) and any statement/detector referencing it
 * de-arms with a logged reason — it can never fire on a value it does not have (fail-closed,
 * RUNTIME §3/§8). What was missing is a **LEDGER** of those value gaps: a past-only record a
 * consumer can read so it is never misled into thinking a gapped/dead-feed window carried a real
 * value.
 *
 * ## ONE TRUTH — the ledger records EXACTLY the de-arm's windows (never a forked concept)
 * The value anchor is UNKNOWN precisely while no SPOT for the signal instrument has been folded —
 * the exact condition `CanonicalState.applyEvent` uses to set `spotVal` (a `TICK`/`SPOT` event for
 * the matching instrument; RUNTIME §2). This module re-derives THAT SAME predicate at the bus layer
 * — it does not import the series layer (the module spine is `series → bus`, never the reverse), so
 * the equivalence is proved event-by-event by the ven.4 suite rather than by a cross-layer call.
 * The ledger's covered seqs therefore equal the seqs where `CanonicalState.spot === UNKNOWN`: the
 * detectors de-arm across exactly the windows the ledger records. One truth, not two.
 *
 * ## PAST-ONLY / NO-LOOKAHEAD (RUNTIME §0)
 * A window is derived by a single forward replay; every `{start,end,dur}` bound is an
 * already-elapsed event. A window that resolves ends at the LAST still-UNKNOWN event — the event
 * *before* the resolving SPOT — never the resolver and never a synthetic future sentinel. A window
 * still open at end-of-tape ends at the last elapsed event. `dur = end.ts - start.ts`. No wall
 * clock, no RNG: same bus ⇒ byte-identical ledger (the determinism invariant).
 *
 * ## NO UNDER-REPORT — honest BY CONSTRUCTION (kestrel-voy9)
 * The ledger can never under-report the gaps, because {@link spotAnchorUnknownWindows} IS the sole
 * derivation: it replays the de-arm predicate forward and records EXACTLY the seqs the anchor read
 * UNKNOWN (a dropped window, a later start, an earlier end, a shortened `dur` are all unrepresentable
 * — the derivation emits the real span or nothing). The ONE-TRUTH property is proved end-to-end in
 * the ven.4 suite by driving this real derivation and asserting its covered seqs BYTE-MATCH the
 * series-layer `CanonicalState.spot === UNKNOWN` primitive, event-by-event, across a resolving, a
 * never-resolving, and a px-blind bus. There is NO standalone under-report validator: a guard that
 * only ever re-checks this same constructor's own honest output is a tautology (it can never fire),
 * and no production path SUPPLIES a foreign ledger to validate — so the honesty lives in the
 * construction, not in an unreachable export.
 */

import type { BusEvent } from "./types.ts";

/** A `(seq, ts)` reading — an already-elapsed bus event, referenced by both the writer's monotonic
 * `seq` and its injected `ts` (RUNTIME §0/§1). */
export interface SeqTs {
  readonly seq: number;
  readonly ts: number;
}

/**
 * One window the SPOT/value anchor was UNKNOWN, with PAST-ONLY bounds. `start` is the first event at
 * which the anchor read UNKNOWN (session open / META — the LEADING pre-first-spot span); `end` is the
 * LAST still-UNKNOWN event (never the resolving SPOT, never a future sentinel); `dur` is the elapsed
 * span `end.ts - start.ts`. The value anchor is STICKY (see {@link spotAnchorUnknownWindows}), so a
 * well-formed session yields exactly ONE such window — the leading one — never a mid-session re-lapse.
 */
export interface AnchorWindow {
  readonly start: SeqTs;
  readonly end: SeqTs;
  readonly dur: number;
}

/**
 * The de-arm predicate, re-derived at the bus layer to be BYTE-EQUAL to `CanonicalState.spot`'s fold
 * condition (ONE TRUTH — not a forked concept that could diverge). The series primitive folds a value
 * into the anchor via `onSpot(ev.ts, ev.px)` — reached only for a `TICK`/`SPOT` of the matching signal
 * instrument — and then reads `spot === spotVal ?? UNKNOWN`. So the anchor RESOLVES iff such a SPOT
 * carries a `px` that is neither `undefined` nor `null` (`spotVal ?? UNKNOWN` stays UNKNOWN when
 * `spotVal` is nullish; a `NaN` or a real number both fold to a non-nullish `spotVal` and read known).
 * The `ev.px != null` guard reproduces that exactly: a reader-accepted SPOT whose `px` is missing/null
 * (a torn/partial TICK the envelope validator lets through — {@link ../bus/read.ts}) leaves the window
 * OPEN, matching the primitive that stays UNKNOWN there; only a `px`-bearing (NaN/number) SPOT closes
 * it. A SPOT for any OTHER instrument never reaches `onSpot` and so never resolves this anchor (it is
 * instrument-scoped) — the window stays open, exactly as the series-layer primitive behaves.
 */
function isAnchorSpot(ev: BusEvent, instrument: string): boolean {
  return (
    ev.stream === "TICK" &&
    ev.type === "SPOT" &&
    ev.instrument === instrument &&
    ev.px != null
  );
}

/**
 * Derive the SPOT-anchor UNKNOWN-window ledger from a bus, purely and deterministically
 * (RUNTIME §0). A single forward replay: the anchor is UNKNOWN until the first px-bearing SPOT for
 * `instrument` is folded, at which point it resolves.
 *
 * The value anchor is **STICKY**: `CanonicalState.spotVal` is set once by `onSpot` and, on a
 * well-formed tape, never reverts (barring a `CanonicalState` reset, which this replay does not
 * model), so the `anchorKnown` latch below likewise never resets. This replay therefore records
 * **exactly the LEADING window** — the pre-first-spot gap — and, when no SPOT ever resolves the
 * anchor, the whole tape as one window. It does NOT record a mid-session lapse back to UNKNOWN: that
 * "gapped/dead-feed mid-session staleness" is a SEPARATE primitive concern (a staleness/heartbeat
 * backstop over `spotTs`, not the value-anchor de-arm this ledger mirrors) and is out of scope here —
 * mirroring it would be forking a second window concept, which ONE TRUTH forbids. Every bound is
 * past-only. The input is never mutated.
 *
 * @param events     the bus, in `seq` order (as the writer emits it).
 * @param instrument the signal instrument whose value anchor is tracked (mirrors
 *                   `CanonicalStateConfig.instrument`).
 * @returns the leading window the anchor was UNKNOWN (0 or 1 windows), in `seq` order — the same span
 *          the value-anchor de-arm covers.
 */
export function spotAnchorUnknownWindows(
  events: readonly BusEvent[],
  instrument: string,
): readonly AnchorWindow[] {
  const windows: AnchorWindow[] = [];
  let start: SeqTs | undefined; // the first event of the current open UNKNOWN run
  let last: SeqTs | undefined; //  the most recent still-UNKNOWN event of that run
  let anchorKnown = false; //       has a px-bearing SPOT for `instrument` been folded? (spotVal != null)

  const close = (): void => {
    if (start !== undefined && last !== undefined) {
      windows.push({ start, end: last, dur: last.ts - start.ts });
    }
    start = undefined;
    last = undefined;
  };

  for (const ev of events) {
    // Fold this event FIRST — the resolving SPOT resolves the anchor AT its own seq, so it is KNOWN
    // there and the window ended at the previous (last still-UNKNOWN) event. This matches the series
    // primitive, where `state.spot` is already known once `applyEvent(spot)` returns.
    if (isAnchorSpot(ev, instrument)) anchorKnown = true;

    if (anchorKnown) {
      close(); // the anchor is known at ev → close any run that was open through the previous event.
    } else {
      const at: SeqTs = { seq: ev.seq, ts: ev.ts };
      if (start === undefined) start = at;
      last = at;
    }
  }
  // A run still open at end-of-tape never resolved — it ends at the last ELAPSED event (no future
  // sentinel; past-only). A fully-UNKNOWN session is thus one window spanning the whole tape.
  close();

  return windows;
}
