/**
 * # cli/backend — the ExecutionBackend seam (ADR-0010, ADR-0004).
 *
 * ONE interface both a LocalBackend (in-process engine + registry) and a
 * RemoteBackend (HTTP client vs kestrel.markets/api) implement. The control
 * plane is typed with the OSS protocol types (src/protocol); the domain payload
 * each verb carries is opaque to the protocol and rides in the payload T of
 * Gated<T>. Imports are type-only (erased) → this module and both backends stay
 * node-light. Local is the DEFAULT; --api/KESTREL_API selects remote.
 */

import type {
  TrialCapability,
  Operation,
  GradeResult,
  CertifiedGrade,
  Blotter,
} from "../../protocol/index.ts";
// type-only (erased at build) → no heavy runtime pulled:
import type { EpisodeReport, FillModelName, TimeOfDay } from "../heavy.ts";
import type { WakeRecord } from "../../session/day.ts";
import type { EpisodeSigmoidParams } from "../../fill/index.ts";
import type { KestrelNode } from "../../lang/index.ts";
import type { ViewSelection } from "../../frame/pane-catalog.ts";
import type { SeatId } from "../../frame/seat.ts";
import type { WireOfferResponse } from "./wire.ts";

/** A control-plane result that may be gated behind a 402/Offer. A LocalBackend
 *  never gates (`gated:false` always). Callers switch on `.gated`. The gated
 *  payload is the platform's STRUCTURED 402 body ({@link WireOfferResponse},
 *  snake_case + {@link ../backend/wire.ts Money}) surfaced as DATA (ADR-0004), not
 *  an exception and never a browser redirect. */
export type Gated<T> =
  | { readonly gated: false; readonly value: T }
  | { readonly gated: true; readonly payment: WireOfferResponse };

/* ---- domain payloads (opaque to the protocol; what renderers/registry read) ---- */

export type PerceptKind = "briefing" | "wake";

export interface PerceptArgs {
  /** Fixture path or `-`/undefined = stdin (local face); catalog selector (remote face). */
  readonly input?: string;
  readonly kind?: PerceptKind;
  /** Raw JSON already read (local face short-circuit); backend may ignore. */
  readonly raw?: string;
  /** Explicit View to render under (kestrel-wa0j.73.2, local face). Resolution is
   *  {@link ../../frame/pane-catalog.ts resolveView}'s strict precedence: explicit View >
   *  seat founder View > phase default — so when present, `seat` is never consulted.
   *  Unknown pane ids / args on arg-less panes REFUSE loudly (never a silent fall-through). */
  readonly view?: ViewSelection;
  /** Acting pod seat (ADR-0041 §2 / A1). When no `view` is given, a seat with a founder
   *  seed for the frame kind reads its founder View; a seat without one falls through to
   *  the phase default (honest absence). Absent ⇒ byte-identical to before this axis. */
  readonly seat?: SeatId;
}

/** The rendered percept frame (protocol has no Frame type; this is the domain body). */
export interface PerceptFrame {
  readonly kind: PerceptKind;
  readonly ascii: string;
}

export interface PlanResult {
  readonly ok: boolean;
  /** Byte-stable canonical text the registry hashes (`print`-joined). */
  readonly canonicalText: string;
  readonly documents: readonly KestrelNode[];
  readonly diagnostics: readonly string[];
  /** Control-plane identity of the submit. Absent on the remote face: `POST /validate`
   *  is pure and returns no Operation (a validated `artifact_id` is the only handle). */
  readonly operation?: Operation;
}

export interface SessionArgs {
  readonly busPath?: string;
  readonly documents: readonly KestrelNode[];
  readonly fillModel: FillModelName;
  readonly rUsd: number;
  readonly fairTauYears?: (now: number) => number | null;
  readonly makerFairParams?: EpisodeSigmoidParams;
  readonly out?: string;
}

export interface DayArgs extends SessionArgs {
  readonly dir: string;
  readonly wakes?: readonly TimeOfDay[];
  readonly structural?: boolean;
  readonly maxWaitSec?: number;
  /** `--await-author`: an external author WILL reply out-of-band — block up to `maxWaitSec` at each
   * handshake, then abandon loudly (kestrel-4fc9). Supplying `maxWaitSec` implies it. */
  readonly awaitAuthor?: boolean;
  /** `--no-author`: no external author will reply — the handshake documents are pre-staged in `dir`
   * and a miss is a prompt typed refusal instead of a `maxWaitSec` ride (kestrel-4fc9). Redundant with
   * the shipped default (which also refuses promptly); kept as an explicit assertion. */
  readonly noAuthor?: boolean;
}

/** What `run`/`day` GUARANTEE back on either face: the EpisodeReport (domain) + the
 *  authored text + the wake records. This is the {@link ExecutionBackend} seam's promise —
 *  it names NO control-plane receipts, so a caller holding the interface can never read a
 *  `blotter`/`operation` that a LocalBackend does not produce. The remote-only certified
 *  fields live on {@link RemoteRunResult}; a caller must hold the concrete RemoteBackend
 *  (or narrow) to reach them (arch-review A9). */
export interface SessionRunResult {
  readonly report: EpisodeReport; // domain payload → renderer + registry
  readonly plansText: string; // for recordGraded (day concatenates authored text)
  readonly wakes: readonly WakeRecord[]; // day only; [] for run
}

/** The LOCAL face's result: exactly the seam guarantee, nothing certified. The in-process
 *  engine mints no server {@link Operation} and no signed {@link Blotter}, so this is the
 *  bare {@link SessionRunResult} — the type states precisely what local promises. */
export type LocalRunResult = SessionRunResult;

/** The REMOTE face's result: the seam guarantee PLUS the control-plane receipts the platform
 *  certifies — the server {@link Operation} identity and the certified execution {@link Blotter}.
 *  BOTH are required (not `?`): the type asserts that a remote run always carries them, and the
 *  RemoteBackend fails closed if the stream omits either. Reachable only through a concrete
 *  RemoteBackend-typed handle (arch-review A9). */
export interface RemoteRunResult extends SessionRunResult {
  readonly operation: Operation; // server identity of the run
  readonly blotter: Blotter; // certified execution record
}

export interface GradeArgs {
  readonly fillModel: FillModelName;
  readonly corpus?: readonly Blotter[]; // additional subject blotters
  readonly certified?: boolean; // request the signed CertifiedGrade form
}

/** Scopes the CLI opens a session under. `sim` = the free deterministic path (trial-covered);
 *  `paper` is beyond trial → remote returns a 402/Offer. */
export type SessionScope = "sim" | "paper";

export interface ExecutionBackend {
  readonly kind: "local" | "remote";

  /** Anonymous, rate-limited trial capability (ADR-0002). Local returns a synthetic
   *  always-valid trial cap; remote mints one via `POST /capabilities/trial`.
   *  Idempotent (cached after first call). */
  bootstrapCapability(): Promise<TrialCapability>;

  /** The View/briefing frame. Local renders from a fixture. NOTE (kestrel-5rb):
   *  the M1 platform contract has NO percept/View route, so the RemoteBackend FAILS
   *  CLOSED here rather than fabricate one — a remote View projection is an owner
   *  decision (local-only face vs a new platform op). */
  getPercept(args: PerceptArgs): Promise<Gated<PerceptFrame>>;

  /** `POST /validate` — the real deterministic parse/validate, fail-closed. Pure:
   *  the contract returns a ValidateResult, NO Operation (remote leaves
   *  {@link PlanResult.operation} undefined). */
  submitPlan(documents: readonly KestrelNode[]): Promise<Gated<PlanResult>>;

  /** `POST /sim` — create an Operation and run the deterministic Session (the report
   *  arrives over the operation's SSE stream). May `402` (paid dataset boundary). */
  openSession(scope: SessionScope, args: SessionArgs): Promise<Gated<SessionRunResult>>;

  /** `POST /sim` (stepped/wake variant). */
  openDaySession(scope: SessionScope, args: DayArgs): Promise<Gated<SessionRunResult>>;

  /** `POST /grade` — create an Operation grading one or more Blotter artifacts; the
   *  certified GradeResult arrives over the operation's SSE stream. (Local derives a
   *  portable GradeResult from a EpisodeReport.) */
  grade(subject: Blotter | EpisodeReport, opts: GradeArgs): Promise<Gated<GradeResult | CertifiedGrade>>;
}
