/**
 * # sdk/types — the ONE typed Kestrel SDK contract (kestrel-djm.5)
 *
 * The transport-neutral surface a caller writes ONCE and runs LOCAL or REMOTE. Every method returns a
 * djm.2 PROTOCOL object (never a face-local success/failure state): catalog discovery yields
 * {@link CatalogEntry}[]; validation yields the client's {@link ValidateOutcome}; a Session interaction
 * yields the djm.4 {@link Delivery} / {@link SessionResponse} / {@link SessionTranscript}; finalize yields a
 * protocol {@link Blotter} + tip + conformance root + artifact refs; grade yields the portable
 * {@link GradeOutcome}. The 402/Offer boundary is DATA on {@link Gated} (the client's exact shape), and an
 * unknown/off-contract subject is a TYPED refusal (fail-closed) — the SDK speaks the protocol, it does not
 * invent a vocabulary.
 *
 * ZERO-WEIGHT: this module carries NO value import of the local runtime — every djm.2/djm.4 shape is a
 * TYPE-ONLY import (erased at build), so `src/sdk/types.ts`, `src/sdk/facade.ts`, and the HTTP transport
 * (`src/sdk/remote.ts`) that consume it stay light (the djm.3 light/heavy seam, pinned by
 * tests/package.boundary.test.ts). {@link Gated}/{@link GradeOutcome}/{@link ValidateOutcome} are RE-EXPORTED
 * from the 109 client — one definition, never a fork.
 *
 * ── DESIGN FORK LOGGED (hard rules forbid .beads writes — recorded here like djm.4's 7dv.4 note) ──
 *  (1) ONE client interface, TRANSPORT via factory. `createSdk(transport)` (facade.ts) wires a low-level
 *      {@link Transport} seam into the {@link KestrelSdk} facade; `localTransport()` (local.ts, heavy) binds
 *      the djm.4 controller + djm.8 catalog + engine grade in-process; `remoteTransport({ baseUrl, fetch })`
 *      (remote.ts, light) binds the 109 client over the 5rb wire. Minimal shape consistent with djm.4's
 *      `openSession(spec)` factory and 109's `new KestrelClient(opts)` — no third contract.
 *  (2) PROTOCOL OBJECTS, no face-local states (this file's whole point).
 *  (3) SdkFinalized ADDS `conformanceRoot` (= `blotter.session.bus.sha256` = the catalog's pinned root) and
 *      `artifacts` to djm.4's `SessionFinalized`, and narrows `blotter` to the djm.2 PROTOCOL {@link Blotter}
 *      (the generic public view) — the SESSION's rich engine Blotter stays on the local path. Minimal shape
 *      consistent with protocol/session.ts's `FinalizeMessage` (`tipHash` + `artifacts`).
 */

import type { CatalogEntry, CatalogListing, CatalogPage } from "../protocol/catalog.ts";
import type { Blotter, EventCursor } from "../protocol/index.ts";
import type { SessionId } from "../protocol/session.ts";
import type {
  AuthoredResponse,
  BoundResponse,
  Delivery,
  SessionResponse,
  SessionTranscript,
} from "../session/controller-types.ts";

// The 402/Offer + verdict + validation shapes the SDK speaks — RE-EXPORTED from the 109 client so both
// transports return the SAME types (never forked). These are type-only edges (erased): they add no weight.
export type { Gated, GradeOutcome, ValidateOutcome } from "../client/index.ts";
// The djm.4 Session shapes the SDK REUSES verbatim on the incremental surface (also type-only).
export type { Delivery, SessionResponse, SessionTranscript, AuthoredResponse, BoundResponse } from "../session/controller-types.ts";

import type { Gated, GradeOutcome, ValidateOutcome } from "../client/index.ts";

/**
 * WHAT to run: a content-addressed CATALOG subject (the djm.8 pinned entry, resolved by the LOCAL loader or
 * the server's dataset artifact) or a RAW dataset (a plan `source` over a `dataset` artifact — the paid
 * boundary rides here). A discriminated union so an unknown subject is a typed refusal, never a silent load.
 */
export type SessionRef =
  | { readonly kind: "catalog"; readonly id: string }
  | { readonly kind: "dataset"; readonly source: string; readonly dataset: string };

/**
 * The sealed Session (djm.5 finalize): the protocol {@link Blotter} + the `tipHash` (tail of the pentad
 * chain) + the `conformanceRoot` (= the graded-bus sha256 = `SimRunId`, the catalog-pinned digest) + the
 * content-addressed `artifacts`. Narrower than djm.4's `SessionFinalized` (which carries the rich engine
 * Blotter + captured turns): the SDK surface is the PROTOCOL view.
 */
export interface SdkFinalized {
  readonly blotter: Blotter;
  readonly sessionId: SessionId;
  readonly tipHash: string;
  /** The graded-bus sha256 (`blotter.session.bus.sha256` = `SimRunId`); the catalog's pinned root. */
  readonly conformanceRoot: string;
  /** Content-addressed handles to the delivered Blotter/Grade artifacts (djm.2 FinalizeMessage.artifacts). */
  readonly artifacts: readonly string[];
}

/**
 * The incremental Session surface (djm.4 verbs, transport-neutral): `start` → `advance`/`revise`/`submit`
 * → `resume` → `finalize`. Every verb returns a djm.2/djm.4 protocol object. Identical shape on LOCAL (wraps
 * the djm.4 controller) and HTTP (replays the server-computed, catalog-pinned transcript).
 */
export interface KestrelSession {
  /** The deterministic Session identity (= genesis hash). Available immediately. */
  readonly sessionId: SessionId;
  /** START: deliver the date-blind OPEN Frame (ordinal OPEN_ORDINAL). */
  start(): Promise<Delivery>;
  /** AUTHORED-OR-STAND-DOWN: commit the pending slot, deliver the next eligible Wake (`next: null` at settle). */
  advance(response: AuthoredResponse): Promise<SessionResponse>;
  /** REVISION: author a superseding document at the current slot (behaviourally identical to advance). */
  revise(response: AuthoredResponse): Promise<SessionResponse>;
  /** EXPLICIT-BINDING turn: reconcile a fully-bound response against the recorded slot (idempotent/typed-refusal). */
  submit(response: BoundResponse): Promise<SessionResponse>;
  /** RESUME: re-derive the committed pentad-chained transcript (nothing to restore). */
  resume(after?: EventCursor): Promise<SessionTranscript>;
  /** FINALIZE: seal the chain → protocol Blotter + tip + conformance root + artifacts. */
  finalize(): Promise<SdkFinalized>;
}

/** A grade over one or more Blotter artifacts (a Blotter's `sessionId` is its artifact id). */
export interface GradeRequest {
  readonly blotters: readonly string[];
}

/** A resolved artifact reference: its `ref`, its `kind`, and the resolved protocol payload. */
export interface ArtifactResult {
  readonly ref: string;
  readonly kind: string;
  readonly value: unknown;
}

/** Address a durable, resumable control-plane Operation (ADR-0004) for {@link KestrelSdk.resumeOperation}. */
export interface OperationRef {
  readonly operationId: string;
  /** Resume strictly after this opaque cursor (absent ⇒ from the beginning). */
  readonly after?: EventCursor;
}

/** The re-derived tail of an Operation: the terminal cursor + the accumulated protocol payload. */
export interface OperationResumption {
  readonly operationId: string;
  readonly cursor: EventCursor | null;
  readonly payload: Readonly<Record<string, unknown>>;
}

/**
 * The ONE typed Kestrel client. Covers catalog discovery, validation, incremental Session interaction,
 * Grade, artifacts, and Operation resume. Transport-agnostic — the SAME object shape whether it wraps the
 * LOCAL controller or the HTTP client. Its `transportKind` / `version` / `transportVersion` are exposed for
 * experimental evidence (djm.5 AC5).
 */
export interface KestrelSdk {
  /** Which transport backs this client. */
  readonly transportKind: Transport["kind"];
  /** The SDK contract version (protocol-anchored). */
  readonly version: string;
  /** The bound transport's version tag. */
  readonly transportVersion: string;
  /** Catalog discovery — the canonical {@link CatalogPage} (kestrel-adge): the byte-identical
   *  {@link CatalogListing}[] menu (kestrel-dnxq) PLUS the optional ex-ante {@link CatalogPricing} block.
   *  Returning the WRAPPED page (not a bare array) makes this return type match the served `{ entries,
   *  pricing }` runtime shape — `pricing` is surfaced when the producer serves one, else absent. The listing
   *  CONTENTS are transport-specific (offline sample vs managed catalog); the reproducibility-complete
   *  {@link CatalogEntry} surface resolves at `openSession`/grade, not here. */
  catalog(): Promise<CatalogPage>;
  /** Pure validation → `{ ok, diagnostics }` (no face-local verdict). */
  validate(document: string): Promise<ValidateOutcome>;
  /**
   * Open an incremental Session over a subject; the 402/Offer boundary is DATA even here. An optional
   * `document` supplies the customer strategy (Kestrel plan text) the managed backend's author-no-strategy
   * fence requires (ADR-0012, kestrel-88u2) — the same role the sim verb's `--plans` plays. On the HTTP
   * transport a catalog subject WITH a document sends it as the `POST /sim` source; a bare openSession keeps
   * today's behavior (the platform's fence answers — now a legible refusal). The LOCAL transport already
   * carries the catalog recipe's strategy, so it ignores the argument.
   */
  openSession(subject: SessionRef, document?: string): Promise<Gated<KestrelSession>>;
  /** Grade one or more Blotter artifacts → the portable {@link GradeOutcome} (may gate). */
  grade(request: GradeRequest): Promise<Gated<GradeOutcome>>;
  /** Resolve a content-addressed artifact reference. */
  artifact(ref: string): Promise<ArtifactResult>;
  /** Resume a durable Operation from its cursor. */
  resumeOperation(ref: OperationRef): Promise<OperationResumption>;
}

/**
 * The low-level TRANSPORT seam a concrete transport implements and `createSdk` wraps. Identical method set
 * to {@link KestrelSdk} minus the cross-cutting version fields — the facade adds those. LOCAL and HTTP
 * transports are behaviourally indistinguishable at this seam (same protocol objects, same digests).
 */
export interface Transport {
  readonly kind: "local" | "http";
  readonly version: string;
  catalog(): Promise<CatalogPage>;
  validate(document: string): Promise<ValidateOutcome>;
  openSession(subject: SessionRef, document?: string): Promise<Gated<KestrelSession>>;
  grade(request: GradeRequest): Promise<Gated<GradeOutcome>>;
  artifact(ref: string): Promise<ArtifactResult>;
  resumeOperation(ref: OperationRef): Promise<OperationResumption>;
}
