/**
 * # fill/spot — SpotFillEngine: the instrument-keyed resting-order engine for a spot instrument
 *
 * The equity/crypto sibling of {@link ./engine.ts SimFillEngine} (ADR-0017). The options engine
 * keys its strict-cross match on `strike + right` and cash-settles filled inventory at
 * **intrinsic** off the final spot — both meaningless for a plain equity (no strike, no expiry).
 * This engine keeps the *same* conservative floor and the *same* survival/expected-$ accounting,
 * but:
 *
 * - **matches on `instrument` alone**, against the instrument's own quote/tape observation
 *   ({@link SpotQuote}) — the strict-cross primitive ({@link spotStrictCross}) is the exact
 *   instrument-general floor rule, with two arms sharing one strictness doctrine (a definite
 *   price-priority cross, never same-price):
 *     • QUOTE arm: a BUY fills iff the ask is present and strictly below the resting price;
 *       a SELL iff the bid is present and strictly above it. A dark side never crosses.
 *     • TRADE-THROUGH arm (kestrel-7pq6): a BUY fills iff a trade printed strictly below the
 *       resting price (`last < px`); a SELL iff strictly above. The derived/catalog tapes are
 *       quote-less SPOT prints — without this arm the spot floor was structurally inert (every
 *       resting order unfillable on every catalog scenario, the P&L axis measuring nothing).
 *       A print AT the limit is not a cross (queue position is unknowable — conservative floor).
 *   Either arm fills AT the resting price. The far-OTM directional guard / moneyness /
 *   covered-wing exemption are option concepts with no spot analogue and simply do not exist
 *   here (the symmetric floor).
 * - **settles mark-to-final-spot** ({@link SpotFillEngine.settle}): a filled lot marks at the
 *   final spot — `(settleSpot − px)·qty·mult` for a buy, the mirror for a sell — instead of
 *   intrinsic-at-expiry. Per-lot mark-to-spot **telescopes** for a closed round-trip (the spot
 *   cancels), so no inventory-netting is needed at the settle seam (ADR-0017).
 *
 * Pure and injected-time (RUNTIME §0): no wall clock, no RNG. **Same quote sequence ⇒ byte-
 * identical events + settle**, the determinism invariant. It emits the same {@link OrderPayload}
 * shape as the options engine but WITHOUT `strike`/`right` (both already optional on the bus) —
 * a spot instrument has neither, and a fictional strike is never written.
 *
 */

import type { NewBusEvent, OrderAction, OrderPayload } from "../bus/index.ts";
import type { FillSupport } from "../support/index.ts";

/** A spot instrument's own quote at one moment: a two-sided NBBO plus an optional last trade.
 * A missing side is `null` (dark), never zero — a one-sided book never crosses (fail-closed). */
export interface SpotQuote {
  readonly bid: number | null;
  readonly ask: number | null;
  /** The last trade price for this instrument, when one has printed. Read by the strict-cross
   * floor's trade-through arm (kestrel-7pq6): a print strictly through a resting limit is as
   * definite a price-priority fact as a crossing quote — and on the quote-less derived tapes the
   * catalog serves, it is the ONLY fill evidence that exists. */
  readonly last?: number;
}

/** A resting spot order handed to {@link SpotFillEngine.place}. No strike/right — a spot leg has
 * neither; the instrument alone locates it (ADR-0017). */
export interface SpotFillOrder {
  readonly ref: string;
  readonly plan?: string;
  readonly plan_instance?: string;
  readonly instrument: string;
  readonly side: "buy" | "sell";
  readonly qty: number;
  /** The resting limit price. A floor fill always fills at this price. */
  readonly px: number;
}

/** One spot order's settle outcome — the floor and the expectation, side by side (RUNTIME §6). */
export interface SpotOrderOutcome {
  readonly ref: string;
  readonly plan?: string;
  readonly plan_instance?: string;
  readonly instrument: string;
  readonly side: "buy" | "sell";
  readonly qty: number;
  readonly px: number;
  readonly floorFilled: boolean;
  readonly floorFillPx: number | null;
  readonly expectedFillProb: number;
  readonly support: FillSupport;
  /** The final spot the lot was marked to (mark-to-market, ADR-0017). */
  readonly markSpot: number;
  /** Realized $ under the strict-cross floor (0 for an unfilled order): mark-to-spot P&L. */
  readonly floorPnl: number;
  /** E[$] under `pFill`. */
  readonly expectedPnl: number;
}

/** The spot settle report — mark-to-final-spot, with the mark's source provenance (ADR-0017). */
export interface SpotSettleReport {
  readonly settleSpot: number;
  readonly fillModel: string;
  readonly multiplier: number;
  readonly outcomes: readonly SpotOrderOutcome[];
  readonly floorTotal: number;
  readonly expectedTotal: number;
  /** The `ts` of the quote/print that set the settle spot (the mark's source watermark). */
  readonly markAsOf: number;
  /** The settle clock `ts`. */
  readonly settleTs: number;
  /** `true` when the settle spot's source **predates** the settle instant — a stale mark
   * (provenance note, not a gate; related: kestrel-xwf staleness-taint). Applying a staleness
   * de-arm is the engine's job downstream; this records that the final mark was not concurrent. */
  readonly staleMark: boolean;
}

/**
 * The instrument-general floor rule (RUNTIME §6, ADR-0017), two arms under one strictness
 * doctrine — a definite price-priority cross, never same-price (`<`/`>`, never `≤`/`≥`):
 *
 *   • QUOTE arm: a resting BUY fills iff the ask is present and **strictly below** the resting
 *     price; a resting SELL iff the bid is present and **strictly above** it. A dark side
 *     (`null`) never crosses.
 *   • TRADE-THROUGH arm (kestrel-7pq6): a resting BUY fills iff a trade printed **strictly
 *     below** the resting price; a resting SELL iff strictly above. A print AT the limit is
 *     not a cross — queue position is unknowable, so the conservative floor refuses it. On the
 *     quote-less derived tapes the catalog serves (`SPOT` prints, no book), this arm is the
 *     only fill evidence that exists; without it the spot floor was structurally inert.
 *
 * **Same-price doctrine split vs options (kestrel-0gnb).** This spot arm REFUSES a same-price
 * trade print; its options sibling {@link ./model.ts strictCross} instead credits *at-or-through*
 * (`≤`/`≥`), because a fresh option-tape print AT the level is a real execution the passive order
 * shared, while the quote-less catalog tape here leaves queue position unknowable. Both stamp the
 * `strict-cross` judge name, so this spot floor is strictly the more conservative reading of it —
 * an option-vs-equity comparison under the shared stamp must key on the arm, not the name.
 *
 * Pure — the conservative floor a spot strategy must clear. Either arm fills at the resting price.
 */
export function spotStrictCross(order: { side: "buy" | "sell"; px: number }, quote: SpotQuote): boolean {
  const last = quote.last ?? null;
  if (order.side === "buy") {
    return (quote.ask !== null && quote.ask < order.px) || (last !== null && last < order.px);
  }
  return (quote.bid !== null && quote.bid > order.px) || (last !== null && last > order.px);
}

interface SpotResting {
  readonly order: SpotFillOrder;
  lastTs: number;
}

interface SpotFilledLot {
  readonly order: SpotFillOrder;
  readonly fillPx: number;
  readonly fillTs: number;
}

/** Construction options. `multiplier` scales per-share P&L to dollars (default `1` — equity). */
export interface SpotFillEngineOptions {
  readonly multiplier?: number;
}

/**
 * The spot resting-order state machine. Construct one per book/session; drive it with
 * `place` / `cancel` / `onQuote`, then `settle`. Every mutator returns the seq-less
 * {@link NewBusEvent}s it produced (the owner BusWriter stamps `seq`), also appended to the
 * cumulative {@link events} log. The floor judge only: `pFill ∈ {0, 1}` (strict-cross). A hazard
 * ceiling for spot instruments is a later slice; the floor is the honest first cut.
 */
export class SpotFillEngine {
  readonly #multiplier: number;
  readonly #resting = new Map<string, SpotResting>();
  readonly #filled: SpotFilledLot[] = [];
  readonly #seen = new Set<string>();
  readonly #events: NewBusEvent[] = [];
  readonly #lastQuote = new Map<string, { readonly quote: SpotQuote; readonly ts: number }>();
  #settled = false;

  constructor(opts: SpotFillEngineOptions = {}) {
    this.#multiplier = opts.multiplier ?? 1;
  }

  /** The cumulative typed bus events this engine has produced, in order. Read-only. */
  get events(): readonly NewBusEvent[] {
    return this.#events;
  }

  /** The refs of all currently-resting orders. */
  restingRefs(): readonly string[] {
    return [...this.#resting.keys()];
  }

  /** The latest quote observed for `instrument` via {@link onQuote}, paired with the injected `ts`
   * it arrived at — `undefined` until the first quote for that instrument. Read-only; the cockpit
   * data-health projects an equity vehicle's liquidity from THIS (the SAME NBBO the strict-cross
   * floor and `exec-fair-quote-v1` execute against, {@link ../engine/pricing.ts}), so HEALTH and
   * PRICING read one source and never disagree (kestrel-710). Pure — no wall clock. */
  currentQuote(instrument: string): { readonly quote: SpotQuote; readonly ts: number } | undefined {
    return this.#lastQuote.get(instrument);
  }

  /** Place a resting order (a re-used ref is refused loudly — fail-closed, RUNTIME §8). Emits a
   * `place` ORDER event. */
  place(order: SpotFillOrder, ts: number): string {
    if (this.#settled) throw new Error(`SpotFillEngine: place after settle (${order.ref})`);
    if (this.#seen.has(order.ref)) {
      throw new Error(`SpotFillEngine: duplicate order ref ${JSON.stringify(order.ref)}`);
    }
    this.#seen.add(order.ref);
    this.#resting.set(order.ref, { order, lastTs: ts });
    this.#emit("place", ts, order, { px: order.px });
    return order.ref;
  }

  /** Cancel a resting order, effective immediately. No-op (no event) if not currently resting.
   * Returns whether a resting order was removed. */
  cancel(ref: string, ts: number): boolean {
    const st = this.#resting.get(ref);
    if (st === undefined) return false;
    this.#resting.delete(ref);
    this.#emit("cancel", ts, st.order, { reason: "cancelled" });
    return true;
  }

  /**
   * Reassess every resting order on `instrument` against a new two-sided quote. A strict-cross is
   * a **floor fill**: the order leaves the book into inventory and a `fill` event is emitted.
   * Returns the events produced this call. Causal (RUNTIME §0): an order is eligible only against
   * quotes at or after its own placement — a rewound/stale quote can never fill it.
   */
  onQuote(instrument: string, quote: SpotQuote, ts: number): readonly NewBusEvent[] {
    if (this.#settled) throw new Error("SpotFillEngine: onQuote after settle");
    // Record the latest observed NBBO per instrument (latest-wins; ticks arrive in ts order) so the
    // cockpit data-health can read the SAME quote the floor executes against (kestrel-710). This is a
    // pure observation — it never changes a fill decision, so the emitted stream stays byte-identical.
    this.#lastQuote.set(instrument, { quote, ts });
    const before = this.#events.length;
    // Snapshot matches first: a fill mutates the map mid-iteration; insertion order stays stable.
    const matches: SpotResting[] = [];
    for (const st of this.#resting.values()) {
      if (st.order.instrument === instrument) matches.push(st);
    }
    for (const st of matches) {
      if (ts < st.lastTs) continue; // causal: never fill against a quote predating placement
      st.lastTs = ts;
      if (spotStrictCross(st.order, quote)) {
        this.#resting.delete(st.order.ref);
        this.#filled.push({ order: st.order, fillPx: st.order.px, fillTs: ts });
        this.#emit("fill", ts, st.order, { px: st.order.px, reason: "cross" });
      }
    }
    return this.#events.slice(before);
  }

  /**
   * Cash-settle at the final spot (RUNTIME §6, ADR-0017 mark-to-market): filled inventory marks
   * to `settleSpot` — `(settleSpot − px)` for a buy, `(px − settleSpot)` for a sell, ×qty×mult;
   * still-resting orders expire worthless-unfilled ($0 floor) and emit a `cancel` annotated
   * `expired-unfilled`. `markAsOf` is the `ts` of the print that set `settleSpot`; a mark whose
   * source predates `ts` is flagged `staleMark` (a provenance note). Idempotent after the first
   * call.
   */
  settle(settleSpot: number, ts: number, markAsOf: number): SpotSettleReport {
    const outcomes: SpotOrderOutcome[] = [];
    let floorTotal = 0;
    let expectedTotal = 0;

    for (const lot of this.#filled) {
      const oc = this.#outcome(lot.order, settleSpot, true, lot.fillPx, 1);
      outcomes.push(oc);
      floorTotal += oc.floorPnl;
      expectedTotal += oc.expectedPnl;
    }
    for (const st of this.#resting.values()) {
      this.#emit("cancel", ts, st.order, { reason: "expired-unfilled" });
      const oc = this.#outcome(st.order, settleSpot, false, null, 0);
      outcomes.push(oc);
      floorTotal += oc.floorPnl;
      expectedTotal += oc.expectedPnl;
    }
    this.#resting.clear();
    this.#settled = true;

    // Commit the settle accounting ON THE BUS (a57.1 / ADR-0011, mirrored from the options engine):
    // one settle-outcome TELEMETRY record per order, so the Blotter projector re-derives
    // totals{floor,expected} + orders[].support for a SPOT session from bus bytes alone — the same
    // no-engine-state-scrape guarantee the options judge gives. Emitted AFTER the expired-unfilled
    // cancels, in outcomes order (filled-first, then resting) for determinism.
    for (const oc of outcomes) {
      this.#events.push({
        ts,
        stream: "TELEMETRY",
        type: "settle",
        order_id: oc.ref,
        expected_fill_prob: oc.expectedFillProb,
        support: oc.support,
        floor_filled: oc.floorFilled,
        floor_pnl: oc.floorPnl,
        expected_pnl: oc.expectedPnl,
      });
    }

    return {
      settleSpot,
      fillModel: "spot-strict-cross/v1",
      multiplier: this.#multiplier,
      outcomes,
      floorTotal,
      expectedTotal,
      markAsOf,
      settleTs: ts,
      staleMark: markAsOf < ts,
    };
  }

  #outcome(
    order: SpotFillOrder,
    settleSpot: number,
    floorFilled: boolean,
    floorFillPx: number | null,
    expectedFillProb: number,
  ): SpotOrderOutcome {
    // Mark-to-market: a long earns (mark − entry), a short earns (entry − mark) — no intrinsic,
    // no strike. Telescopes for a closed round-trip (the mark cancels), ADR-0017.
    const perShare = order.side === "buy" ? settleSpot - order.px : order.px - settleSpot;
    const scaled = perShare * order.qty * this.#multiplier;
    return {
      ref: order.ref,
      ...(order.plan !== undefined ? { plan: order.plan } : {}),
      ...(order.plan_instance !== undefined ? { plan_instance: order.plan_instance } : {}),
      instrument: order.instrument,
      side: order.side,
      qty: order.qty,
      px: order.px,
      floorFilled,
      floorFillPx,
      expectedFillProb,
      // A strict-cross floor fill is a structural price-priority fact ⇒ calibrated support; an
      // unfilled order banks nothing (pFill 0) and is likewise structurally calibrated.
      support: "calibrated",
      markSpot: settleSpot,
      floorPnl: floorFilled ? scaled : 0,
      expectedPnl: expectedFillProb * scaled,
    };
  }

  /** Append a typed ORDER event (no strike/right — a spot leg has neither, ADR-0017). */
  #emit(action: OrderAction, ts: number, order: SpotFillOrder, extra: Partial<OrderPayload>): void {
    const payload: OrderPayload = {
      order_id: order.ref,
      ...(order.plan !== undefined ? { plan: order.plan } : {}),
      ...(order.plan_instance !== undefined ? { plan_instance: order.plan_instance } : {}),
      instrument: order.instrument,
      side: order.side,
      qty: order.qty,
      ...extra,
    };
    this.#events.push({ ts, stream: "ORDER", type: action, ...payload });
  }
}
