/**
 * # react/frame-fields — reading the json Rendering HONESTLY (ADR-0052 §3/§4)
 *
 * Shared helpers for the `/react` components. They read the typed Frame `json` Rendering
 * ({@link ../frame/render-json.ts}) the way a Rendering must: an UNKNOWN Field is UNKNOWN (never a
 * zero, never interpolated), and a `MODEL` Field must PROVE its receipt (source + model version +
 * confidence) or it is REFUSED — the honest-chart claim is that the chart invents nothing and hides
 * no provenance, so a `MODEL` Field arriving without its receipt is a fail-closed error, not a badge
 * we quietly drop.
 *
 * ## Isolation (ADR-0052 §3, a HARD requirement — the trust substrate ships this)
 * Every engine import here is `import type` — ERASED at build. The `/react` runtime graph reaches
 * NOTHING in the engine (no tokenizer, no sha256, no Bun/node built-in); the only runtime coupling is
 * the optional `react` peer, resolved by the components, not this module. The two runtime CONSTANTS
 * that must agree with the engine — the {@link UNKNOWN} marker and the arm-id registry
 * ({@link ./arms.ts}) — are declared locally and pinned to the engine's TYPES at typecheck, so a
 * drift reds `bun run typecheck` rather than shipping a browser bundle that disagrees with the wire.
 */

import type { FieldJson, JsonUnknown, Maybe } from "../frame/render-json.ts";

/**
 * The explicit UNKNOWN marker, pinned to the engine's {@link JsonUnknown} at typecheck. The engine
 * carries a null/absent Field across the `json` wire as this literal (never an omitted key, never a
 * defaulted `0`); the components compare against THIS constant to decide "gap, not candle". The
 * annotation is the drift guard: if the engine ever changes its marker string, `"UNKNOWN"` stops
 * being assignable to {@link JsonUnknown} and this line reds — a type-only coupling, zero runtime edge.
 */
export const UNKNOWN: JsonUnknown = "UNKNOWN";

/** Is this {@link Maybe} value the explicit UNKNOWN marker (⇒ render a GAP, never a value)? */
export function isUnknown<T>(v: Maybe<T>): v is JsonUnknown {
  return v === UNKNOWN;
}

/** The finite value of a {@link Maybe}, or `null` when it is UNKNOWN — a `null` is the caller's cue to
 * leave a GAP (never substitute `0`, never interpolate a neighbour). */
export function valueOr<T>(v: Maybe<T>, fallback: null = null): T | null {
  return isUnknown(v) ? fallback : v;
}

/** Raised when a `MODEL` Field arrives WITHOUT its receipt — the honest chart refuses to draw a
 * model-derived value it cannot attribute (fail-closed, ADR-0052 §3). */
export class HonestChartError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "HonestChartError";
  }
}

/** A `MODEL` Field's receipt, extracted once it has been proven honest. */
export interface FieldReceipt {
  readonly source: string;
  readonly modelVer: string;
  readonly confidence: number;
}

/**
 * Prove a `MODEL` {@link FieldJson} carries its full receipt (source + modelVer + confidence), or
 * THROW — mirroring the engine's seam-side honesty gate ({@link ../frame/types.ts assertClaimHonest})
 * on the reading side, because a `json` Rendering can be hand-built by a consumer and arrive
 * degraded. Returns the receipt so the badge renders it. Fail-closed: a `MODEL` value with no receipt
 * is never silently rendered as if honest.
 */
export function assertModelReceipt(field: FieldJson, label: string): FieldReceipt {
  if (field.attribution !== "MODEL") {
    throw new HonestChartError(
      `${label} is attributed ${field.attribution}, not MODEL — only a MODEL Field carries a receipt`,
    );
  }
  const { source, modelVer, confidence } = field;
  if (typeof source !== "string" || source === "") {
    throw new HonestChartError(`MODEL ${label} is missing its \`source\` receipt`);
  }
  if (typeof modelVer !== "string" || modelVer === "") {
    throw new HonestChartError(`MODEL ${label} is missing its \`modelVer\` receipt`);
  }
  if (typeof confidence !== "number" || !Number.isFinite(confidence)) {
    throw new HonestChartError(`MODEL ${label} is missing a finite \`confidence\` receipt`);
  }
  return { source, modelVer, confidence };
}
