/**
 * # ast — the typed object model IS the language (ADR-0004)
 *
 * Kestrel's canonical representation is this discriminated-union family, not the text.
 * `parse(text) -> objects` (phase 3) and `print(objects) -> text` (this phase, in
 * `print.ts`) are inverse projections with a byte-stable round-trip. Every node carries a
 * literal `kind` tag; there are no classes (data is plain readonly objects) and no `any`.
 *
 * One language, four statement kinds (ADR-0001): {@link ViewStatement},
 * {@link WakeStatement}, {@link PlanStatement}, {@link GradeStatement} — plus the org
 * nodes {@link PodStatement} / {@link BookStatement} (ADR-0002). All four surfaces share
 * one lexical core: the {@link Trigger} algebra, {@link SeriesRef} operands, and
 * {@link PriceExpr} price expressions.
 *
 * The AST is ALWAYS fully qualified — `USING` defaults are resolved at parse time, and
 * the printer elides whatever matches the ambient `USING` (ADR-0004; ARCHITECTURE §2).
 */

// ─────────────────────────────────────────────────────────────────────────────
// Comment trivia — the byte-stable "why" that travels WITH the action (ADR-0033)
// ─────────────────────────────────────────────────────────────────────────────
//
// Comments are NOT executable (they carry no runtime semantics); they are authored reasoning
// that MUST survive `print(parse(text))` byte-for-byte (ADR-0004 round-trip; ADR-0013:63-65
// depends on it; ADR-0031 journals through it). They are modeled as a cross-cutting SIDECAR
// on the block nodes that print as one or more lines — never fields on the semantic clause
// nodes — so the object model of triggers/prices/legs stays comment-free and every existing
// builder and deep-equality test is untouched (the sidecar is simply absent on comment-free
// documents).
//
// Two kinds, by position:
//   • OWN-LINE (leading/tail): a line whose first non-space char is `#`. Printed on its own
//     line at the block/clause indentation, ABOVE the line it precedes (leading) or after the
//     block's last line (tail).
//   • TRAILING-INLINE: a `#…` after the statement/clause tokens on the same line. Printed
//     after the line content with the canonical single space before `#`.
//
// A comment's `text` is captured VERBATIM: everything after the opening `#` to end of line,
// the `#` itself excluded (so `# why` stores " why" and reprints as `# why`, byte-identical).
// The line INDENT and the single space before a trailing `#` are the only things canonicalized.

/** One printed line's comment trivia. `at` is the line's ordinal WITHIN its owning block, in
 * canonical print order: `0` = the block's own header line; `1..N` = the block's direct
 * interior lines (USING/WHEN/BECAUSE/clauses/panes/risk/directives). Child blocks own their
 * own trivia, so they never consume an ordinal here. */
export interface LineComment {
  readonly at: number;
  /** Own-line comments printed on their own line(s) ABOVE line `at`, verbatim, `#` excluded. */
  readonly leading?: readonly string[];
  /** An inline comment printed after line `at`'s content, one canonical space before `#`. */
  readonly trailing?: string;
}

/** The comment sidecar for a block node (a statement, or the module). Absent entirely on a
 * comment-free document, so it never perturbs deep-equality against builder output. */
export interface CommentLayer {
  /** Comments bound to specific printed lines of this block, by ordinal. */
  readonly lines?: readonly LineComment[];
  /** Own-line comments trailing the block's last line, at the block's interior indentation. */
  readonly tail?: readonly string[];
}

// ─────────────────────────────────────────────────────────────────────────────
// Primitive lexical units
// ─────────────────────────────────────────────────────────────────────────────

/** Timescale unit for windows and durations. There is no absolute shock: magnitude is
 * judged at a window (CONTEXT: Window). */
export type TimeUnit = "s" | "m" | "h" | "d" | "w" | "mo" | "q" | "y";

/** The timescale parameter a rate/magnitude series carries — `velocity(1m)`. */
export interface Window {
  readonly kind: "window";
  readonly value: number;
  readonly unit: TimeUnit;
}

/** A span of time used by `held`, `within`, `esc`, and relative `ttl`. */
export interface Duration {
  readonly kind: "duration";
  readonly value: number;
  readonly unit: TimeUnit;
}

/** A wall-clock time of day (session calendar), `HH:MM`. */
export interface TimeOfDay {
  readonly kind: "time-of-day";
  readonly hour: number;
  readonly minute: number;
}

/** A numeric quantity with an optional unit. Plain number, risk-fraction `R`, cents `c`,
 * percent `%`, or basis points `bp`. Used as a trigger operand and in offsets. */
export interface Quantity {
  readonly kind: "quantity";
  readonly value: number;
  readonly unit?: "R" | "c" | "%" | "bp";
}

/** A baseline-relative threshold — the portable, cross-instrument form of "big"
 * (CONTEXT: Window). `p99` = 99th percentile of the window's own trailing baseline;
 * `3sigma` = three standard deviations. */
export interface Baseline {
  readonly kind: "baseline";
  readonly stat: "p" | "sigma";
  readonly n: number;
}

// ─────────────────────────────────────────────────────────────────────────────
// Series references — the universal operand (CONTEXT: Series, Registry)
// ─────────────────────────────────────────────────────────────────────────────

/** A selector on a path segment: an org quantifier (`children(any)`) or a named target
 * (`plan(chase-urgent)`). */
export type PathSelector =
  | { readonly kind: "sel-quant"; readonly q: "any" | "all" }
  | { readonly kind: "sel-name"; readonly name: string };

/** One dotted segment of a series path, optionally selected. */
export interface PathSegment {
  readonly name: string;
  readonly selector?: PathSelector;
}

/**
 * A named series whose value changes over time — the universal operand.
 *
 * Two kinds and only two (CONTEXT: Series): **market facts** (ambient per signal
 * instrument, spectator-visible: `spot`, `vwap`, `velocity(1m)`) and **org facts**
 * (path-scoped in the pod tree, acting-sessions only: `pnl`, `alpha.pnl`,
 * `fills.avg_px`, `plan(chase-urgent).state`). The market/org distinction is **registry
 * resolution metadata, not a syntactic property** — canonical text does not mark it (a
 * dotted path prints identically for both), so it is NOT a field on this node; the
 * registry (a later phase) resolves a path to its space. A bare symbolic value like a
 * plan-state literal (`armed`) is a single-segment series — indistinguishable in text
 * from any other name, and resolved by the registry (ADR-0004: the node is the syntactic
 * identity; round-trip is mechanical, so nothing un-printable lives here).
 */
export interface SeriesRef {
  readonly kind: "series";
  readonly segments: readonly PathSegment[];
  readonly window?: Window;
}

/** Any leaf value that a comparison or cross can stand on. A bare identifier operand (a
 * plan-state literal like `armed`) is a single-segment {@link SeriesRef} — text cannot
 * distinguish it from a series, and ADR-0004 forbids un-round-trippable distinctions. */
export type Operand = SeriesRef | Quantity | Baseline;

// ─────────────────────────────────────────────────────────────────────────────
// Trigger algebra — the shared WHEN language (CONTEXT: Trigger algebra)
// ─────────────────────────────────────────────────────────────────────────────

export type CompareOp = "gt" | "ge" | "lt" | "le" | "eq" | "ne";

/** `left OP right` — a level/series comparison. */
export interface Comparison {
  readonly kind: "cmp";
  readonly op: CompareOp;
  readonly left: Operand;
  readonly right: Operand;
}

/**
 * `left crosses above|below right` — an edge event, not a level.
 *
 * Two hardening dials, both learned from a graded-sim failure where a bare cross phantom-
 * fired 10/12 on a single anomalous print:
 *
 * - **`touch`** picks the boundary predicate. Omitted (the default) is a **strict** cross:
 *   the series must move strictly past `right` (`>`/`<`). Present (`touches above|below`) is
 *   an **at-or-touch** cross: reaching *or* touching the level fires (`>=`/`<=`).
 * - **`band`** is a **re-arm** width. With a band, after the cross fires the series must
 *   clear back past `right ∓ band` (below `right−band` for an `above` cross, above
 *   `right+band` for `below`) and **re-arm** before it can fire again — so a lone spurious
 *   tick cannot phantom-fire. Omitted = a bare, single-shot cross (the fragile form). A
 *   positive magnitude; a zero/negative band cannot re-arm and is refused at parse time.
 *
 * Doctrine (fail-closed, all surfaces): a cross is an edge, not a level, so it may **not**
 * be wrapped in `held` — sustaining an edge for a duration is the anti-pattern that phantom-
 * fired; the parser rejects it, the builder refuses to construct it, the printer throws on
 * it. Robustness comes from `band` (re-arm), not from holding the edge. (`within` over a
 * cross stays legal: "the cross occurred inside this window" is a well-formed question.)
 */
export interface CrossEvent {
  readonly kind: "cross";
  readonly left: Operand;
  readonly dir: "above" | "below";
  readonly right: Operand;
  readonly touch?: boolean;
  readonly band?: Quantity;
}

/** Break-and-hold: `inner held <dur>` — the inner condition sustained for a duration. */
export interface BreakHold {
  readonly kind: "break-hold";
  readonly inner: Trigger;
  readonly dur: Duration;
}

/** `inner within <dur>` — the inner event occurred inside a rolling window. */
export interface WithinWindow {
  readonly kind: "within";
  readonly inner: Trigger;
  readonly dur: Duration;
}

/** `inner until <clock>` — the inner expectation is in force up to a wall-clock time; the
 * thesis temporal envelope (kestrel-rtf). A postfix combinator over the SAME predicate
 * surface as {@link WithinWindow} — `until`/`at` are siblings of `within`, not a second
 * predicate language — each carrying a {@link TimeOfDay}. */
export interface UntilWindow {
  readonly kind: "until";
  readonly inner: Trigger;
  readonly at: TimeOfDay;
}

/** `inner at <clock>` — the inner expectation is evaluated at a wall-clock time; the thesis
 * temporal envelope (kestrel-rtf). A postfix combinator sibling of {@link WithinWindow}. */
export interface AtWindow {
  readonly kind: "at";
  readonly inner: Trigger;
  readonly at: TimeOfDay;
}

/**
 * `held <dur>` in EXIT/lead position — a 0DTE **time-held stop**: the position has been HELD
 * for a duration (measured from the plan's first acquiring fill), independent of any market
 * predicate. This is the in-grammar form for "exit N minutes after entry" the FOMC-options
 * single-model runs reached for out-of-grammar (`EXIT held 90m`, `EXIT held 120m`) and
 * parse-escaped on, de-arming the whole plan (docs/results/fomc-options-axis/report.md).
 *
 * It is NOT a {@link BreakHold}: break-hold sustains an INNER predicate for a duration (any
 * flicker resets the clock); a `held-stop` has no inner predicate — it is a bare hold-clock on
 * the acquired inventory. As a general {@link Trigger} it only has a referent inside a clause
 * that manages a held position (EXIT); read in any other position it fails closed (UNKNOWN),
 * exactly like {@link UntilWindow}/{@link AtWindow}. The engine fires it at the leg's own basis,
 * respecting never-naked / covered-close, through the SAME {@link ExitClause} path.
 */
export interface TimeHeldStop {
  readonly kind: "held-stop";
  readonly dur: Duration;
}

/**
 * `clockET <clock>` — a 0DTE **wall-clock time-stop**: fire at or after a wall-clock ET time
 * (the theta-into-the-close cut the FOMC-options runs authored out-of-grammar as
 * `EXIT clockET 15:40`). Semantically a sibling of `time after <clock>`, but named `clockET`
 * because that is the surface the single-model authors emitted (mirroring the watcher's
 * `scheduleWake at {kind:"atClockET"}` vocabulary). Carries a {@link TimeOfDay}.
 */
export interface ClockStop {
  readonly kind: "clock-stop";
  readonly at: TimeOfDay;
}

/** The Nth occurrence of an event: `second failed-break of HOD`. */
export interface NthEvent {
  readonly kind: "nth";
  readonly ordinal: number;
  readonly event: Trigger;
}

/** A named structural market event, optionally of a level: `failed-break of HOD`. */
export interface StructuralEvent {
  readonly kind: "event";
  readonly name: string;
  readonly of?: Operand;
}

/** A session-calendar phase event: `phase open`. */
export interface PhaseEvent {
  readonly kind: "phase";
  readonly phase: string;
}

/** A wall-clock window: `time 09:30..10:00`, `time after 15:45`, `time before 10:00`.
 * At least one of `from` / `to` must be present. */
export interface TimeWindowTrig {
  readonly kind: "time-window";
  readonly from?: TimeOfDay;
  readonly to?: TimeOfDay;
}

/** A fill-lifecycle event (fill telemetry as a trigger). */
export interface FillEvent {
  readonly kind: "fill";
  readonly event: "filled" | "partial-fill" | "unfilled" | "rejected" | "cancelled";
  readonly leg?: number;
}

export interface And {
  readonly kind: "and";
  readonly terms: readonly Trigger[];
}
export interface Or {
  readonly kind: "or";
  readonly terms: readonly Trigger[];
}
export interface Not {
  readonly kind: "not";
  readonly term: Trigger;
}

/** The shared WHEN expression, used by Wake, Plan, and Grade filters alike. */
export type Trigger =
  | Comparison
  | CrossEvent
  | BreakHold
  | WithinWindow
  | UntilWindow
  | AtWindow
  | TimeHeldStop
  | ClockStop
  | NthEvent
  | StructuralEvent
  | PhaseEvent
  | TimeWindowTrig
  | FillEvent
  | And
  | Or
  | Not;

// ─────────────────────────────────────────────────────────────────────────────
// Price expressions (CONTEXT / ARCHITECTURE: ExecutionFair, the @fair anchor)
// ─────────────────────────────────────────────────────────────────────────────

/** A price anchor. `fair` = ExecutionFair (the honest MM fill price); `mid` is an
 * authoring resting-price, never a fair-value source (ARCHITECTURE §4). */
export type AnchorName =
  | "fair"
  | "intrinsic"
  | "basis"
  | "bid"
  | "ask"
  | "mid"
  | "last"
  | "join"
  | "improve"
  | "stub"
  // The underlying spot as a PRICE anchor (emergent grammar, ADR-0030 / kestrel-ipc). `spot` is
  // already a first-class SERIES name (`WHEN spot > 499`); admitting it in the `@ price` position
  // makes the anchor set a strict superset — no previously-parsed form changes meaning. It resolves
  // to the canonical underlying spot for an equity/spot leg and is REFUSED for an option leg
  // (the mirror image of `intrinsic`/`basis`, which parse always but are refused on a spot leg —
  // ADR-0017). The two readings of `spot` (series vs anchor) read the SAME underlying value.
  | "spot";

export interface Anchor {
  readonly kind: "anchor";
  readonly name: AnchorName;
}

/** An absolute limit price, e.g. `4.20`. */
export interface AbsolutePrice {
  readonly kind: "price-abs";
  readonly value: number;
}

/** `base ± N(c|%)` — a tight offset from a base price, e.g. `fair-3c`. */
export interface PriceOffset {
  readonly kind: "price-offset";
  readonly base: PriceExpr;
  readonly sign: "+" | "-";
  readonly amount: number;
  readonly unit: "c" | "%";
}

/** `lean(a, b, x)` — blend between two prices by fraction x (lean-to-MM). */
export interface Lean {
  readonly kind: "lean";
  readonly a: PriceExpr;
  readonly b: PriceExpr;
  readonly x: number;
}

/** `min(...)` / `max(...)` over price expressions. */
export interface PriceMinMax {
  readonly kind: "price-fn";
  readonly fn: "min" | "max";
  readonly args: readonly PriceExpr[];
}

export type PriceExpr = Anchor | AbsolutePrice | PriceOffset | Lean | PriceMinMax;

/** One rung of an escalation ladder: escalate the resting price `to` after `after`. */
export interface EscStage {
  readonly kind: "esc-stage";
  readonly to: PriceExpr;
  readonly after: Duration;
}

/** The resting-order execution policy: peg/fix, an esc ladder, cap/floor lists, a
 * per-order cancel-if guard, and gtc. Distinct from *which* price (that is a
 * {@link PriceExpr}). */
export interface OrderPolicy {
  readonly kind: "order-policy";
  readonly pricing?: "peg" | "fix";
  readonly esc?: readonly EscStage[];
  readonly caps?: readonly PriceExpr[];
  readonly floors?: readonly PriceExpr[];
  readonly cancelIf?: Trigger;
  readonly gtc?: boolean;
}

// ─────────────────────────────────────────────────────────────────────────────
// Order legs (CONTEXT: Book; ADR-0005 Plan v1)
// ─────────────────────────────────────────────────────────────────────────────

/** How a strike is chosen: relative to the anchor grid (`+1`), an absolute strike
 * (`450`), at-the-money (`atm`), or delta-targeted (`25d`). */
export type StrikeSpec =
  | { readonly kind: "strike-rel"; readonly steps: number }
  | { readonly kind: "strike-abs"; readonly strike: number }
  | { readonly kind: "strike-atm" }
  | { readonly kind: "strike-delta"; readonly delta: number };

/** One option leg: `buy 2 +1 C`.
 *
 * A SELL leg is legal ONLY as a **covered** close of inventory this plan actually holds — never-naked
 * applies to OPTION legs exactly as it does to an {@link EquityLeg} (ADR-0017). Coverage is enforced
 * per `(strike, right)` (`PlanEngine#sellCovered`: net-held-of-that-leg minus its resting sells) on each
 * surface that can emit a sell: entry, ALSO, RELOAD, flatten, **TAKE-PROFIT**, and **EXIT**.
 *
 * The last two are called out because they were the hole (kestrel-h5nx). Both sized off the PLAN-TOTAL
 * net-held while binding the sell to a SINGLE leg, so any plan holding more than one leg (a straddle, a
 * two-strike ladder) oversold that leg and went NET SHORT an uncovered option. On a one-leg plan the two
 * quantities coincide, which is why it survived for so long — and why a contaminated result had already
 * been pinned into the committed golden fixtures. Multi-leg sells now ALLOCATE across the held legs, each
 * capped by and named for the leg that covers it. */
export interface OptionLeg {
  readonly kind: "leg";
  readonly side: "buy" | "sell";
  readonly qty: number;
  readonly strike: StrikeSpec;
  readonly right: "C" | "P";
  /**
   * The leg's own tenor (kestrel theta-cell seam a; AUTHORABLE since kestrel-ih5h seam 1).
   *
   * `undefined` means **inherit the ambient execution tenor** — the expiry authored once on the
   * execution instrument (`USING exec … 0dte`, {@link ExpirySelector} on {@link Instrument}). It is
   * never a silent 0dte: the inheritance is resolved at execution (`#execForLeg` in
   * `src/engine/plans.ts`), and an expiry that cannot be resolved fails closed rather than defaulting.
   *
   * Authored per-leg with the `exp` marker — `buy 2 +1 C exp 0dte`, `sell 1 atm P exp 2026-07-17` —
   * reusing the SAME selector vocabulary as the instrument form. The marker is required because a leg
   * is followed by `,` or `@` and a bare tag selector would swallow the continuation; the instrument
   * form can be positional only because a stop-set holds it apart. Legs written without `exp` print
   * byte-identically to before this syntax existed (ADR-0004; the tail is additive).
   *
   * It is also what lets a leg RECONSTRUCTED from a held/marked position (not authored — e.g. the
   * per-wake mark-to-model folding a held straddle back out of the tape's option book, whose
   * `BookState` carries one expiry) carry its own tenor with it.
   *
   * Note the execution BOOK is still keyed by SYMBOL alone (`#books` in `src/engine/plans.ts`,
   * `foldBook` in `src/bus/types.ts` keeps ONE expiry per instrument). A leg expiry therefore reaches
   * execution as a CHECK, not a selector: `#bookFor` refuses a book whose expiry contradicts the
   * authored one (fail-closed) rather than pricing against the wrong tenor. Re-keying the book by
   * `(symbol, expiry)` — what a genuine multi-expiry / calendar position needs — is the other half of
   * this seam and is deliberately NOT made here.
   */
  readonly expiry?: ExpirySelector;
}

/** One equity/spot leg: `buy 100 shares` (ADR-0017). A spot instrument has neither a
 * strike nor a right, so an equity leg carries only side + quantity + the `shares` marker;
 * the tradeable symbol is the ambient `USING exec <SYMBOL>` with no expiry. `qty` is a share
 * count. Deliberately long-biased in v1: an uncovered equity SELL is a naked short (unbounded
 * risk) and is refused downstream (never-naked, ADR-0017). The SAME boundary governs an
 * {@link OptionLeg} sell — never-naked is not an equity-only rule (kestrel-h5nx). */
export interface EquityLeg {
  readonly kind: "equity-leg";
  readonly side: "buy" | "sell";
  readonly qty: number;
}

/** An order leg — an {@link OptionLeg} (`buy 2 +1 C`) or an {@link EquityLeg} (`buy 100
 * shares`, ADR-0017). A discriminated union on `kind`; every consumer narrows, so the
 * compiler enumerates the sites the instrument-general execution core must handle. */
export type Leg = OptionLeg | EquityLeg;

// ─────────────────────────────────────────────────────────────────────────────
// Plan clauses (ADR-0005: WHEN / DO / ALSO / RELOAD / TP / EXIT / INVALIDATE /
// CANCEL-IF / ARM; inventory binding)
// ─────────────────────────────────────────────────────────────────────────────

/** A held-inventory quantifier for clauses that act on pre-existing positions
 * (ADR-0005 inventory binding). */
export type HeldQuantifier =
  | { readonly kind: "held-foreach" } // FOREACH held leg
  | { readonly kind: "held-any" }; //     any held leg

/** `DO <legs> @ <price> <policy>` — the primary order ticket. `atomic` is reserved and
 * refused symmetrically on every surface (ADR-0005): the parser REJECTS the keyword, the
 * builders REFUSE to construct it, and the printer THROWS on it. The AST keeps the field
 * anyway — the parser needs the vocabulary to reject it loudly, and the type documents the
 * future surface (a whole-structure preflight + an atomic execution adapter) — but no valid
 * v1 ticket ever carries it, so `parse(print(x))` round-trips (ADR-0004). Never faked. */
export interface DoTicket {
  readonly kind: "do";
  readonly legs: readonly Leg[];
  readonly price: PriceExpr;
  readonly policy?: OrderPolicy;
  readonly atomic?: boolean;
}

/** `ALSO <legs> @ <price> <policy>` — an additional co-armed ticket. */
export interface AlsoTicket {
  readonly kind: "also";
  readonly legs: readonly Leg[];
  readonly price: PriceExpr;
  readonly policy?: OrderPolicy;
  readonly atomic?: boolean;
}

/** `RELOAD [WHEN <trig>] <legs> @ <price> <policy>` — buy more on adverse movement. */
export interface ReloadClause {
  readonly kind: "reload";
  readonly when?: Trigger;
  readonly legs: readonly Leg[];
  readonly price: PriceExpr;
  readonly policy?: OrderPolicy;
  readonly atomic?: boolean;
}

/** What target a take-profit rests against. */
export type TpTarget =
  | { readonly kind: "tp-pct"; readonly pct: number } // +100%
  | { readonly kind: "tp-mult"; readonly mult: number } // 2x
  | { readonly kind: "tp-price"; readonly price: PriceExpr };

/** `TP <target> [frac F] [<held-quant>] [@ <price>] <policy>` — resting take-profit. */
export interface TpClause {
  readonly kind: "tp";
  readonly target: TpTarget;
  readonly frac?: number;
  readonly over?: HeldQuantifier;
  readonly price?: PriceExpr;
  readonly policy?: OrderPolicy;
}

/** `EXIT <trig> [<held-quant>] [@ <price>] <policy>` — get out when the thesis breaks. */
export interface ExitClause {
  readonly kind: "exit";
  readonly when: Trigger;
  readonly over?: HeldQuantifier;
  readonly price?: PriceExpr;
  readonly policy?: OrderPolicy;
}

/** `INVALIDATE <trig>` — thesis dead: stop active management, ride the tail. */
export interface InvalidateClause {
  readonly kind: "invalidate";
  readonly when: Trigger;
}

/** `CANCEL-IF <trig>` — cancel resting orders (plan-scope, vs the per-order guard). */
export interface CancelIfClause {
  readonly kind: "cancel-if";
  readonly when: Trigger;
}

/** `ARM [WHEN <trig>] [basis <price>] [<held-quant>]` — the on-arm trigger + inventory
 * binding (ADR-0005): the basis price anchor and a held-leg quantifier. */
export interface ArmClause {
  readonly kind: "arm";
  readonly when?: Trigger;
  readonly basis?: PriceExpr;
  readonly over?: HeldQuantifier;
}

export type PlanClause =
  | DoTicket
  | AlsoTicket
  | ReloadClause
  | TpClause
  | ExitClause
  | InvalidateClause
  | CancelIfClause
  | ArmClause;

// ─────────────────────────────────────────────────────────────────────────────
// Instruments, USING, provenance
// ─────────────────────────────────────────────────────────────────────────────

/** An expiry selector for an execution instrument. */
export type ExpirySelector =
  | { readonly kind: "expiry-dte"; readonly dte: number } // 0dte, 1dte
  | { readonly kind: "expiry-date"; readonly date: string } // 2026-07-17
  | { readonly kind: "expiry-tag"; readonly tag: string }; // weekly, monthly

/** A tradeable instrument, optionally with an expiry (execution side). */
export interface Instrument {
  readonly kind: "instrument";
  readonly symbol: string;
  readonly expiry?: ExpirySelector;
}

/** Scoped defaults: a signal-space instrument (where WHEN reads) and an execution-space
 * instrument + expiry (where DO acts). The only v1 customization point for imports
 * (ADR-0003). */
export interface Using {
  readonly kind: "using";
  readonly signal?: Instrument;
  readonly exec?: Instrument;
}

export type ProvenanceTier = "vetted" | "candidate" | "unvetted";

/** Authorship + trust metadata. A declared provenance can only *narrow* the channel's
 * authority (ARCHITECTURE §6 provenance ceiling). */
export interface Provenance {
  readonly kind: "provenance";
  readonly tier?: ProvenanceTier;
  readonly author?: string;
  readonly origin?: string;
  readonly replay?: string;
}

// ─────────────────────────────────────────────────────────────────────────────
// VIEW (CONTEXT: View, Pane)
// ─────────────────────────────────────────────────────────────────────────────

/** A pane argument, carrying only its **syntactic supersort** — the class the lexer/parser assigns
 * with ZERO catalog knowledge (ADR-0041 §1: the `wf` rung is catalog-independent, so the parser
 * never picks a pane's semantic sort). Five supersorts: a bare **ident** (`skyline`, `vwap`), a
 * **window** (`5m` — a numeral + a time unit), a bare **numeral count** (`12` — a numeral with
 * NO unit, e.g. `chain 12`), a **session ordinal** (`d-0`, `d-1` — the `d-` prefix + a numeral,
 * Train 1B), and an **expiry ordinal** (`e-0`, `e-1` — the `e-` prefix + a numeral, Train 1B). A
 * catalog `ParamSlot` REFINES a supersort to a semantic sort
 * (`Instrument`/`LevelName`/`Window`/`Count`/`SessionOrdinal`/`ExpiryOrdinal`) at materialization
 * (`src/frame/pane-catalog.ts`). Each supersort has ONE canonical printed form so `print(parse(text))`
 * stays byte-stable (ADR-0004): an ident prints its name, a window `<n><unit>`, a count its bare
 * numeral, a session ordinal `d-<n>`, an expiry ordinal `e-<n>`. Panes are references + args only —
 * a View never computes (no application node, ADR-0041 §1). */
export type PaneArg =
  | { readonly kind: "arg-ident"; readonly name: string }
  | { readonly kind: "arg-window"; readonly window: Window }
  | { readonly kind: "arg-count"; readonly count: number }
  | { readonly kind: "arg-ordinal"; readonly ordinal: number }
  | { readonly kind: "arg-expiry"; readonly expiry: number };

/** One named block of a View. */
export interface PaneRef {
  readonly kind: "pane";
  readonly name: string;
  readonly args: readonly PaneArg[];
}

export interface ViewStatement {
  readonly kind: "view";
  readonly name: string;
  readonly budget?: number; // token budget
  readonly panes: readonly PaneRef[];
  /** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
  readonly comments?: CommentLayer;
}

// ─────────────────────────────────────────────────────────────────────────────
// Citation — the `because` pre-registration clause (kestrel-rtf)
// ─────────────────────────────────────────────────────────────────────────────

/**
 * A content-hash reference to a platform-side Thesis artifact — the `because` clause
 * (kestrel-rtf). It carries ONLY the digest of the fixed shape `sha256:<64 hex>`; the thesis
 * body lives platform-side, never inline (a `because` with an inline body is a parse refusal).
 *
 * Because the clause prints byte-stably like any other canonical text, it binds into the
 * armed-document hash (`sha256(print(module))`) automatically — so pre-registration is
 * tamper-evident once the plan is armed. It is an OPTIONAL field on {@link PlanStatement} and
 * {@link WakeStatement}, NOT a fifth statement kind: the statement algebra stays View/Wake/
 * Plan/Grade (ADR-0001), and `because` attached to a View or Grade is a fail-closed refusal.
 */
export interface Citation {
  readonly kind: "citation";
  readonly algo: "sha256";
  /** The 64-lowercase-hex content hash of the platform-side Thesis (no inline body). */
  readonly hash: string;
}

// ─────────────────────────────────────────────────────────────────────────────
// WAKE (CONTEXT: Wake, Scan, Wake delivery)
// ─────────────────────────────────────────────────────────────────────────────

/** `DELIVER <view> [MANDATORY] [KEYFRAME]` — the wake decides *when*, the View *what*.
 * Delta frame by default; keyframe on request. */
export interface DeliverSpec {
  readonly kind: "deliver";
  readonly view: string;
  readonly mandatory?: boolean;
  readonly keyframe?: boolean;
}

/** A wake's attention budget: wakes/day and/or tokens/day. */
export interface WakeBudget {
  readonly kind: "wake-budget";
  readonly wakesPerDay?: number;
  readonly tokensPerDay?: number;
}

/** A Scan's universe scope (`all-listed`, `nyse`) — wide and slow, vs a narrow coverage
 * wake. */
export interface UniverseScope {
  readonly kind: "universe";
  readonly universe: string;
}

export interface WakeStatement {
  readonly kind: "wake";
  readonly name: string;
  readonly when: Trigger;
  /** Optional `because sha256:<64 hex>` pre-registration citation (kestrel-rtf). */
  readonly because?: Citation;
  readonly deliver?: DeliverSpec;
  readonly priority?: number;
  readonly coalesce?: string;
  readonly budget?: WakeBudget;
  readonly universe?: UniverseScope;
  /** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
  readonly comments?: CommentLayer;
}

// ─────────────────────────────────────────────────────────────────────────────
// PLAN (ARCHITECTURE §2; ADR-0005)
// ─────────────────────────────────────────────────────────────────────────────

/** Budget as a risk fraction, `0.2R`. `size × max_loss ≤ budget` (ARCHITECTURE §6). */
export interface Budget {
  readonly kind: "budget";
  readonly value: number;
  readonly unit: "R";
}

/** Time-to-live: relative (`+30m`) or an absolute clock (`16:00`, hold-to-close). */
export type Ttl =
  | { readonly kind: "ttl-rel"; readonly dur: Duration }
  | { readonly kind: "ttl-at"; readonly at: TimeOfDay };

/** One tag binding of a regime gate: `intraday: trend`. */
export interface RegimeTagBinding {
  readonly scope: string;
  readonly value: string;
}

/** `regime {intraday: trend}` — an open-vocabulary tag gate (ADR-0005). */
export interface RegimeGate {
  readonly kind: "regime-gate";
  readonly tags: readonly RegimeTagBinding[];
}

/** The lifecycle standing of a standing statement (CONTEXT: Standing). */
export type Standing = "authored" | "armed" | "versioned" | "superseded";

export interface PlanStatement {
  readonly kind: "plan";
  readonly name: string;
  readonly budget?: Budget;
  readonly ttl?: Ttl;
  readonly regime?: RegimeGate;
  readonly priority?: number;
  readonly standing?: Standing;
  readonly provenance?: Provenance;
  readonly using?: Using;
  readonly when?: Trigger;
  /** Optional `because sha256:<64 hex>` pre-registration citation (kestrel-rtf). */
  readonly because?: Citation;
  readonly clauses: readonly PlanClause[];
  readonly atomic?: boolean;
  /** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
  readonly comments?: CommentLayer;
}

// ─────────────────────────────────────────────────────────────────────────────
// GRADE (ADR-0006)
// ─────────────────────────────────────────────────────────────────────────────

/** What is being graded — everything authored is gradable (ADR-0006). */
export interface GradeSubject {
  readonly kind: "grade-subject";
  readonly what: "plan" | "wake" | "view" | "pod" | "tag";
  readonly name: string;
}

/** A counterfactual — syntax, not a bespoke script (ADR-0006). The structural null and
 * bracket cross the SAME fill model. */
export type Counterfactual =
  | { readonly kind: "cf-ungated" }
  | { readonly kind: "cf-null" }
  | { readonly kind: "cf-bracket" };

/** The corpus range a grade replays over: `2025-01..2026-06`. Opaque date tokens. */
export interface CorpusRange {
  readonly kind: "corpus-range";
  readonly from: string;
  readonly to: string;
}

/** A `BY` stratification dimension — cells, not pools (ADR-0006). */
export type GradeDimension =
  | { readonly kind: "dim-series"; readonly series: SeriesRef }
  | { readonly kind: "dim-vehicle" }
  | { readonly kind: "dim-name" }
  | { readonly kind: "dim-lineage" };

export interface GradeStatement {
  readonly kind: "grade";
  readonly subject: GradeSubject;
  readonly over?: CorpusRange;
  readonly fill?: string; // named, versioned fill model
  readonly versus: readonly Counterfactual[];
  readonly by: readonly GradeDimension[];
  /** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
  readonly comments?: CommentLayer;
}

// ─────────────────────────────────────────────────────────────────────────────
// POD / BOOK / RISK (ADR-0002)
// ─────────────────────────────────────────────────────────────────────────────

/** Instruments assigned to a Trader PLUS the thesis for why (CONTEXT: Coverage). */
export interface Coverage {
  readonly kind: "coverage";
  readonly instruments: readonly Instrument[];
  readonly thesis: string;
}

/** How a Book's concurrent plans compete for its envelope (open-vocabulary policy). */
export interface Arbitration {
  readonly kind: "arbitration";
  readonly policy: string;
  readonly concurrency?: number;
}

/** One risk line: `RISK day-loss 2R -> halt`. The L0 envelope may clamp/veto, never open
 * risk (ARCHITECTURE §3). */
export interface RiskLine {
  readonly kind: "risk";
  readonly metric: string;
  readonly threshold: Quantity;
  readonly action: string;
}

/** The leaf of the org tree — the only place positions and orders live (ADR-0002). */
export interface BookStatement {
  readonly kind: "book";
  readonly name: string;
  readonly budget?: Budget;
  readonly coverage?: Coverage;
  readonly arbitration?: Arbitration;
  readonly risk?: readonly RiskLine[];
  /** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
  readonly comments?: CommentLayer;
}

/** The recursive org node: an allocating PM role + a risk envelope + children (each a
 * Book or another Pod). Depth is unbounded; the org is data (ADR-0002). */
export interface PodStatement {
  readonly kind: "pod";
  readonly name: string;
  readonly risk: readonly RiskLine[];
  readonly children: readonly (PodStatement | BookStatement)[];
  /** Byte-stable comment trivia (ADR-0033). Absent on comment-free documents. */
  readonly comments?: CommentLayer;
}

// ─────────────────────────────────────────────────────────────────────────────
// Module / Document (ADR-0003)
// ─────────────────────────────────────────────────────────────────────────────

/** An ESM-like import of named statements from another module (ADR-0003). */
export interface ImportDecl {
  readonly kind: "import";
  readonly names: readonly string[];
  readonly from: string;
}

/** Any top-level statement of a module. */
export type Statement =
  | ViewStatement
  | WakeStatement
  | PlanStatement
  | GradeStatement
  | PodStatement
  | BookStatement;

/** A Kestrel document: imports + optional USING defaults + statements + provenance. */
export interface Module {
  readonly kind: "module";
  readonly imports: readonly ImportDecl[];
  readonly using?: Using;
  readonly statements: readonly Statement[];
  readonly provenance?: Provenance;
  /** Byte-stable comment trivia (ADR-0033) for the module's own directive lines (IMPORT /
   * PROVENANCE / USING, ordinal 0..k-1) and its tail. Comments before/on a statement live on
   * that statement, not here. Absent on comment-free documents. */
  readonly comments?: CommentLayer;
}

/** Any node the printer accepts at the top level. */
export type KestrelNode = Module | Statement;
