// =====================================================================
// forge-web · client/space/ledger-events.jsx
// ---------------------------------------------------------------------
// Aetherion Space tenant — signed event ledger primitives.
//
// Source of truth: tenant-spec/space.md §9 ("Ledger receipt schema").
// Twenty-two event types, ordered chronologically from RFQ intake to
// launch-manifest lock. Same shape as fixtures-pump.jsx:654 — every
// emitted entry carries { event_id, ts, type, mode:"forge", actor,
// summary, ref } and is wrapped in a hash-chained receipt envelope so
// the auditor ledger can verify ordering and detect tampering without
// pulling in a cryptography dependency.
//
// This file is metadata + a pure utility. No UI components. Other Wave
// A1 / Space agents should:
//   - read AETHERION_LEDGER_EVENTS for category, tone, icon, ITAR tag
//   - call emitAetherionLedger(eventName, payload) when constructing
//     fixture receipts so the chain head stays consistent
//
// Color tones map to the existing <Pill> contract in client/ui.jsx
// ("accent" | "warn" | "danger" | "ok" | "muted") and the §10 palette:
//   --space-accent  #4F8FFF  → tone "accent"
//   --space-slate   #5B6B82  → tone "muted"
//   warn / danger / ok use the cross-tenant tokens unchanged.
//
// ITAR segregation tags follow USML Category XV (Spacecraft and
// Related Articles). Events touching controlled technical data are
// flagged itar:"controlled"; events that physically pull a controlled
// drawing (ITAR_EXPORT_RECORDED) are flagged itar:"export". Everything
// else is itar:"unrestricted" but the segregation key is always
// present so downstream filters never have to coalesce.
// =====================================================================

// ---------------------------------------------------------------------
// 1 · Event catalog (22 entries, chronological)
// ---------------------------------------------------------------------
//
// Field reference:
//   name      uppercase snake_case identifier from spec §9
//   label     human title for ledger UIs
//   category  one of:
//               "contract"   — RFQ / LOI / customer-acceptance
//               "mission"    — baseline + mass/power budgets
//               "review"     — SRR / PDR / CDR formal gates
//               "trade"      — propulsion / supplier trade studies
//               "build"      — FAB release, station completion
//               "quality"    — TVAC, NCR, qual, EMC
//               "change"     — ECO raise / board / waiver
//               "export"     — ITAR / EAR record-keeping
//               "acceptance" — flight + customer acceptance
//               "launch"     — manifest lock
//   tone      Pill tone (accent | warn | danger | ok | muted)
//   icon      key in window.Icons (see icons.jsx)
//   itar      "unrestricted" | "controlled" | "export"
//   summary   default summary line, used when payload omits one
//
const AETHERION_LEDGER_EVENTS = [
  {
    name: "RFQ_RECEIVED",
    label: "RFQ received",
    category: "contract",
    tone: "muted",
    icon: "File",
    itar: "unrestricted",
    summary: "Aperture Earth RFQ for 12U EO bus",
  },
  {
    name: "LOI_SIGNED",
    label: "LOI signed",
    category: "contract",
    tone: "accent",
    icon: "Clipboard",
    itar: "unrestricted",
    summary: "Aperture Earth LOI · $4.2M FFP",
  },
  {
    name: "MISSION_BASELINED",
    label: "Mission baselined",
    category: "mission",
    tone: "accent",
    icon: "Satellite",
    itar: "controlled",
    summary: "KESTREL-3 mission baseline rev A locked",
  },
  {
    name: "SRR_CLOSED",
    label: "SRR closed",
    category: "review",
    tone: "ok",
    icon: "Check",
    itar: "controlled",
    summary: "SRR review passed · 7 actions tracked",
  },
  {
    name: "MASS_BUDGET_LOCKED",
    label: "Mass budget locked",
    category: "mission",
    tone: "accent",
    icon: "Layers",
    itar: "controlled",
    summary: "Mass budget 14.0 kg cap · margin policy 10%",
  },
  {
    name: "POWER_BUDGET_LOCKED",
    label: "Power budget locked",
    category: "mission",
    tone: "accent",
    icon: "Bolt",
    itar: "controlled",
    summary: "Power budget 70 W BOL · margin policy 5 W",
  },
  {
    name: "PDR_CLOSED",
    label: "PDR closed",
    category: "review",
    tone: "ok",
    icon: "Check",
    itar: "controlled",
    summary: "PDR passed · propulsion trade study attached",
  },
  {
    name: "PROPULSION_TRADE_DONE",
    label: "Propulsion trade complete",
    category: "trade",
    tone: "accent",
    icon: "Diff",
    itar: "controlled",
    summary: "Cold-gas R-236fa selected over Hall thruster",
  },
  {
    name: "CDR_CLOSED",
    label: "CDR closed",
    category: "review",
    tone: "ok",
    icon: "Check",
    itar: "controlled",
    summary: "CDR passed · mass margin 11.4%",
  },
  {
    name: "SUPPLIER_QUALIFIED",
    label: "Supplier qualified",
    category: "trade",
    tone: "ok",
    icon: "Users",
    itar: "controlled",
    summary: "Honeybee + ISIS + AAC Clyde + BCT cleared at PDR",
  },
  {
    name: "FAB_RELEASED",
    label: "Fabrication released",
    category: "build",
    tone: "accent",
    icon: "Factory",
    itar: "controlled",
    summary: "FAB-A WO-AET-001 released",
  },
  {
    name: "TVAC_TEST_RUN",
    label: "TVAC test run",
    category: "quality",
    tone: "accent",
    icon: "Wave",
    itar: "controlled",
    summary: "TVAC-AET-014 run on integrated vehicle",
  },
  {
    name: "NCR_LOGGED",
    label: "NCR logged",
    category: "quality",
    tone: "danger",
    icon: "Flag",
    itar: "controlled",
    summary: "NCR-AET-019 hinge fastener pre-load loss",
  },
  {
    name: "ECO_RAISED",
    label: "ECO raised",
    category: "change",
    tone: "warn",
    icon: "Wrench",
    itar: "controlled",
    summary: "ECO-AET-0042 raised against KESTREL-3.60.HG.03",
  },
  {
    name: "ECO_BOARD_REVIEW",
    label: "ECO board review",
    category: "change",
    tone: "warn",
    icon: "Users",
    itar: "controlled",
    summary: "Mech CCB + Q&MA signed",
  },
  {
    name: "WAIVER_GRANTED",
    label: "Waiver granted",
    category: "change",
    tone: "warn",
    icon: "Flag",
    itar: "controlled",
    summary: "WAIVER-AET-005 · single-point hinge inspection",
  },
  {
    name: "QUAL_PASSED",
    label: "Qual passed",
    category: "quality",
    tone: "ok",
    icon: "Check",
    itar: "controlled",
    summary: "Vibration qual at GEVS+3 dB envelope passed",
  },
  {
    name: "EMC_PASSED",
    label: "EMC passed",
    category: "quality",
    tone: "ok",
    icon: "Magnet",
    itar: "controlled",
    summary: "EMI / EMC per MIL-STD-461G passed",
  },
  {
    name: "ITAR_EXPORT_RECORDED",
    label: "ITAR export recorded",
    category: "export",
    tone: "danger",
    icon: "Eye",
    itar: "export",
    summary: "ITAR-controlled drawing pull logged for FAB-B",
  },
  {
    name: "ACC_PASSED",
    label: "Acceptance passed",
    category: "acceptance",
    tone: "ok",
    icon: "Check",
    itar: "controlled",
    summary: "Flight acceptance signed by Mission Director",
  },
  {
    name: "CUSTOMER_ACCEPTED",
    label: "Customer accepted",
    category: "acceptance",
    tone: "ok",
    icon: "Person",
    itar: "unrestricted",
    summary: "Aperture Earth pre-launch sign-off",
  },
  {
    name: "LAUNCH_MANIFEST_LOCKED",
    label: "Launch manifest locked",
    category: "launch",
    tone: "accent",
    icon: "Rocket",
    itar: "unrestricted",
    summary: "Falcon 9 Transporter-15 manifest confirmed",
  },
];

// Quick lookup index (name → entry). Frozen so consumers can't mutate
// the catalog by accident.
const AETHERION_LEDGER_EVENT_INDEX = Object.freeze(
  AETHERION_LEDGER_EVENTS.reduce((acc, ev) => {
    acc[ev.name] = ev;
    return acc;
  }, {})
);

// ---------------------------------------------------------------------
// 2 · Hash chain primitive
// ---------------------------------------------------------------------
//
// Browser fixtures cannot pull in node:crypto and shouldn't bring a
// 30 KB SHA shim for a demo ledger. We use a 64-bit FNV-1a-style mix
// over the canonical JSON encoding of the entry. Two 32-bit words give
// ~16 hex chars of output, prefixed "ae:" so it's visually distinct
// from real SHA-256 receipts and never confused for one in screenshots.
//
// Properties we actually need (and get):
//   - deterministic for identical input
//   - cheap (one pass, no allocations beyond the string)
//   - hash bound to prevHash → tamper-evident chain
// Properties we do NOT claim:
//   - cryptographic collision resistance
//   - preimage resistance
// The auditor view treats this as an integrity tag, not a signature.
//
function _fnv1aHex(str) {
  // 32-bit FNV-1a, run twice with different seeds to get 64 bits of
  // output. Plain JavaScript, no typed arrays needed.
  let h1 = 0x811c9dc5;
  let h2 = 0x01000193 ^ 0xdeadbeef;
  for (let i = 0; i < str.length; i++) {
    const c = str.charCodeAt(i);
    h1 ^= c;
    h1 = (h1 + ((h1 << 1) + (h1 << 4) + (h1 << 7) + (h1 << 8) + (h1 << 24))) >>> 0;
    h2 ^= c + i;
    h2 = (h2 + ((h2 << 1) + (h2 << 4) + (h2 << 7) + (h2 << 8) + (h2 << 24))) >>> 0;
  }
  const hex = (n) => n.toString(16).padStart(8, "0");
  return hex(h1) + hex(h2);
}

// Stable JSON encoder that sorts object keys. Two payloads with the
// same content but different key insertion order must produce the
// same hash, otherwise replays from a saved fixture won't verify.
function _stableStringify(value) {
  if (value === null || typeof value !== "object") return JSON.stringify(value);
  if (Array.isArray(value)) {
    return "[" + value.map(_stableStringify).join(",") + "]";
  }
  const keys = Object.keys(value).sort();
  return (
    "{" +
    keys.map((k) => JSON.stringify(k) + ":" + _stableStringify(value[k])).join(",") +
    "}"
  );
}

// Chain head. Module-scope so successive emitAetherionLedger calls
// without an explicit prevHash thread together automatically. The
// genesis prevHash is the conventional zero string so an auditor can
// verify the first entry the same way as the rest.
const AETHERION_LEDGER_GENESIS = "ae:0000000000000000";
let _aetherionChainHead = AETHERION_LEDGER_GENESIS;

// ---------------------------------------------------------------------
// 3 · emitAetherionLedger
// ---------------------------------------------------------------------
//
// Returns a receipt envelope:
//   {
//     ts, eventName, payload, prevHash, hash,
//     event_id, mode: "forge", category, tone, icon, itar
//   }
//
// `payload` is the per-event body — typically { actor, summary, ref }
// matching the existing fixtures-pump.jsx shape. Anything passed in
// `payload` is preserved verbatim and folded into the hash so a
// downstream verifier can re-derive the chain.
//
// `opts` is optional:
//   - prevHash    override the module chain head (use this when
//                 replaying a saved fixture deterministically)
//   - ts          ISO-8601 Zulu timestamp; defaults to Date.now()
//   - eventId     stable event_id (E-NNNN); auto-generated if absent
//   - chain       boolean (default true) — when true, advances the
//                 module chain head to the new hash
//
// The function is pure with respect to its inputs *except* for the
// optional chain advance, which is opt-out. Use { chain: false } when
// emitting in a hot path (e.g. table renders) where you don't want
// the head to drift.
//
let _aetherionEventCounter = 0;
function emitAetherionLedger(eventName, payload, opts) {
  const meta = AETHERION_LEDGER_EVENT_INDEX[eventName];
  if (!meta) {
    throw new Error(
      `emitAetherionLedger: unknown event "${eventName}". ` +
        `Expected one of: ${AETHERION_LEDGER_EVENTS.map((e) => e.name).join(", ")}`
    );
  }
  const o = opts || {};
  const ts = o.ts || new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
  const prevHash = o.prevHash != null ? o.prevHash : _aetherionChainHead;
  _aetherionEventCounter += 1;
  const eventId = o.eventId || `E-${String(_aetherionEventCounter).padStart(4, "0")}`;
  const body = payload && typeof payload === "object" ? payload : {};
  const summary = body.summary != null ? body.summary : meta.summary;

  // Canonical pre-image: only the fields that should be
  // tamper-evident. event_id is included so reordering events in a
  // saved fixture invalidates the chain.
  const preimage = _stableStringify({
    event_id: eventId,
    ts,
    type: eventName,
    mode: "forge",
    payload: { ...body, summary },
    prevHash,
    itar: meta.itar,
  });
  const hash = "ae:" + _fnv1aHex(preimage);

  if (o.chain !== false) _aetherionChainHead = hash;

  return {
    event_id: eventId,
    ts,
    eventName,
    type: eventName,
    mode: "forge",
    category: meta.category,
    tone: meta.tone,
    icon: meta.icon,
    itar: meta.itar,
    label: meta.label,
    payload: { ...body, summary },
    prevHash,
    hash,
  };
}

// Exposed helpers for tests and replay scenarios. Resetting the chain
// is occasionally useful when seeding a fixture deterministically —
// production ledgers, of course, never reset.
function resetAetherionLedgerChain(toHash) {
  _aetherionChainHead = toHash || AETHERION_LEDGER_GENESIS;
  _aetherionEventCounter = 0;
}
function getAetherionLedgerHead() {
  return _aetherionChainHead;
}

// ---------------------------------------------------------------------
// 4 · Window export
// ---------------------------------------------------------------------
Object.assign(window, {
  AETHERION_LEDGER_EVENTS,
  AETHERION_LEDGER_EVENT_INDEX,
  AETHERION_LEDGER_GENESIS,
  emitAetherionLedger,
  resetAetherionLedgerChain,
  getAetherionLedgerHead,
});
