/* FORGE — Mittelstand ledger event registry + hash-chained emitter.

   Wave A1 / Mittelstand / Agent #18 ledger-receipts.

   Twenty-two event types per tenant-spec/mittelstand.md §9. Each event
   carries a category (intake / build / qual / ship / govern), a color
   tone keyed off the §10 palette (--mittel-* tokens), and an icon name
   from the global `Icons` set (icons.jsx).

   This file is pure data + functions. No React, no UI atoms. Acts and
   ledger panels import via `window.MITTELSTAND_LEDGER_EVENTS` and
   `window.emitMittelstandLedger`.

   Loaded as <script type="text/babel"> after icons.jsx (for the Icons
   names this file references symbolically — the icons themselves are
   resolved at render time by the consumer).
*/

// ─────────────────────────────────────────────────────────────────────
// 1 · Event registry — 22 entries, ordered by arc beat (RFQ → close).
// ─────────────────────────────────────────────────────────────────────
//
// category band:
//   intake → RFQ → frame contract booked         (5)
//   build  → ECO + routing + WO + receiving      (7)
//   qual   → inspection / NCR / CAPA / dims      (5)
//   ship   → subassy / FAI / customer / close    (5)
//   govern → cross-cutting compliance markers     —  (folded into qual+ship)
//
// tone keys map to --mittel-* CSS variables in cross-cutting.jsx:
//   "ink"    → anthracite, neutral beat            (--mittel-ink)
//   "ink-2"  → quieter neutral                     (--mittel-ink-2)
//   "steel"  → accent / decision moment            (--mittel-accent)
//   "amber"  → tolerance / out-of-band watch       (--mittel-accent-warn)
//   "bad"    → NCR / CAPA / failure                (--mittel-accent-bad)
//   "good"   → pass / signed / closed              (--mittel-good, fallback to steel)
//
// icon names match keys in window.Icons (icons.jsx). If a name is not
// present at consumer time, fall back to "Dot".

const MITTELSTAND_LEDGER_EVENTS = [
  // ── intake ────────────────────────────────────────────────────────
  { name: "RFQ_RECEIVED",          category: "intake", tone: "ink-2", icon: "File",
    label: "RFQ received",         beat: "MAHLE call-off entered intake" },
  { name: "DESIGN_MATCH",          category: "intake", tone: "ink",   icon: "Cube",
    label: "Design matched",       beat: "MTL-220 Rev D matched to MAHLE spec" },
  { name: "CARRYOVER_DIFF",        category: "intake", tone: "ink-2", icon: "Diff",
    label: "Carryover diff",       beat: "Rev C → Rev D delta computed" },
  { name: "QUOTE_SENT",            category: "intake", tone: "steel", icon: "ArrowR",
    label: "Quote sent",           beat: "24-mill quote w/ ECO-MS-0017 cost line" },
  { name: "FRAME_CONTRACT_SIGNED", category: "intake", tone: "good",  icon: "Check",
    label: "Frame contract signed",beat: "Annual frame booked, two-batch schedule locked" },

  // ── build ─────────────────────────────────────────────────────────
  { name: "ECO_RAISED",            category: "build",  tone: "amber", icon: "Flag",
    label: "ECO raised",           beat: "ECO-MS-0017 opened against 10.01, 10.02" },
  { name: "ECO_APPROVED",          category: "build",  tone: "good",  icon: "Check",
    label: "ECO approved",         beat: "Engineering + Manufacturing approvals captured" },
  { name: "ROUTING_REVISED",       category: "build",  tone: "steel", icon: "Route",
    label: "Routing revised",      beat: "Granite-cell routing re-published with re-grind op" },
  { name: "GATES_PUBLISHED",       category: "build",  tone: "ink",   icon: "Layers",
    label: "Gates published",      beat: "Quality gates 1–5 frozen for Rev D batch" },
  { name: "LONG_LEAD_PO_RELEASED", category: "build",  tone: "amber", icon: "Clock",
    label: "Long-lead PO released",beat: "GMN spindle 180 d + Heidenhain TNC 640 45 d" },
  { name: "WO_RELEASED",           category: "build",  tone: "steel", icon: "Stack",
    label: "Work orders released", beat: "WO-MTL-2401..2424 pushed to SAP B1" },
  { name: "RECEIVING_LOGGED",      category: "build",  tone: "ink-2", icon: "DB",
    label: "Receiving logged",     beat: "Vendor part booked into stores with COC hash" },

  // ── qual ──────────────────────────────────────────────────────────
  { name: "INCOMING_INSPECTION_PASS", category: "qual", tone: "good",  icon: "Check",
    label: "Incoming inspection",  beat: "Vendor part cleared at Station 1" },
  { name: "NCR_OPENED",               category: "qual", tone: "bad",   icon: "X",
    label: "NCR opened",           beat: "Inspection bust raises non-conformance" },
  { name: "CAPA_LINKED",              category: "qual", tone: "amber", icon: "Link",
    label: "CAPA linked",          beat: "NCR linked to corrective action plan" },
  { name: "ROUTING_PASS",             category: "qual", tone: "good",  icon: "Check",
    label: "Routing op signed",    beat: "One routing op signed off at machine shop" },
  { name: "DIM_INSPECTED",            category: "qual", tone: "ink",   icon: "Mesh",
    label: "Dimensions inspected", beat: "CMM / ballbar / laser tracker measurement captured" },

  // ── ship ──────────────────────────────────────────────────────────
  { name: "SUBASSY_COMPLETED",        category: "ship", tone: "steel", icon: "Box",
    label: "Subassy completed",    beat: "Spindle module aligned, signed, advanced" },
  { name: "FAI_PACKAGE_GENERATED",    category: "ship", tone: "ink",   icon: "Clipboard",
    label: "FAI package generated",beat: "AS9102 / IATF FAI documents bundled" },
  { name: "FAI_PASSED",               category: "ship", tone: "good",  icon: "Check",
    label: "FAI passed",           beat: "First article cleared all five gates" },
  { name: "CUSTOMER_WITNESSED",       category: "ship", tone: "steel", icon: "Person",
    label: "Customer witnessed",   beat: "MAHLE rep signature captured at Station 4" },
  { name: "ACCEPTANCE_SIGNED",        category: "ship", tone: "good",  icon: "Check",
    label: "Acceptance signed",    beat: "Traveler closed, unit released; ledger arc closes" },
];

// Cardinality guard — the spec is locked at 22; if a future edit changes
// this without touching §9 of the spec, downstream screens will surface
// the mismatch via this assertion in dev builds.
if (MITTELSTAND_LEDGER_EVENTS.length !== 22) {
  // Soft warn — never throw in browser demo runtime.
  if (typeof console !== "undefined" && console.warn) {
    console.warn(
      "[mittelstand/ledger-events] expected 22 events per spec §9, got " +
      MITTELSTAND_LEDGER_EVENTS.length
    );
  }
}

// Fast lookup map — name → entry. Built once at script load.
const MITTELSTAND_LEDGER_EVENT_BY_NAME = (function () {
  const m = {};
  for (let i = 0; i < MITTELSTAND_LEDGER_EVENTS.length; i++) {
    const e = MITTELSTAND_LEDGER_EVENTS[i];
    m[e.name] = e;
  }
  return m;
})();

// Tone → CSS variable resolver. Consumers read this to paint chips.
const MITTELSTAND_TONE_VARS = {
  "ink":    "var(--mittel-ink)",
  "ink-2":  "var(--mittel-ink-2)",
  "steel":  "var(--mittel-accent)",
  "amber":  "var(--mittel-accent-warn)",
  "bad":    "var(--mittel-accent-bad)",
  "good":   "var(--mittel-good, var(--mittel-accent))",
};

// Category → display label (for filter chips, ledger band headers).
const MITTELSTAND_CATEGORY_LABELS = {
  intake: "Intake",
  build:  "Build",
  qual:   "Quality",
  ship:   "Ship",
  govern: "Govern",
};

// ─────────────────────────────────────────────────────────────────────
// 2 · Hash primitive — synchronous, deterministic, hex output.
// ─────────────────────────────────────────────────────────────────────
//
// We use a 64-bit FNV-1a variant (split into two 32-bit lanes) for a
// 16-char hex hash. Browser-side WebCrypto is async and overkill for a
// demo ledger; FNV-1a is stable across reloads and good enough to chain
// receipts without collision in any realistic demo run.

function _fnv1a64Hex(str) {
  // Two interleaved 32-bit FNV-1a accumulators with different primes,
  // concatenated into a 16-char hex digest.
  let h1 = 0x811c9dc5 >>> 0; // FNV offset basis
  let h2 = 0x01000193 >>> 0; // seeded from FNV prime to decorrelate lanes
  const P1 = 0x01000193;
  const P2 = 0x811c9dc5;
  for (let i = 0; i < str.length; i++) {
    const c = str.charCodeAt(i);
    h1 ^= c;
    h1 = Math.imul(h1, P1) >>> 0;
    h2 ^= (c + i) & 0xff;
    h2 = Math.imul(h2, P2) >>> 0;
  }
  // Stir lanes into each other so the digest reflects whole input.
  h1 = Math.imul(h1 ^ (h2 >>> 13), P1) >>> 0;
  h2 = Math.imul(h2 ^ (h1 >>> 11), P2) >>> 0;
  return _u32hex(h1) + _u32hex(h2);
}

function _u32hex(n) {
  let s = (n >>> 0).toString(16);
  while (s.length < 8) s = "0" + s;
  return s;
}

// Deterministic JSON for hashing — keys sorted, no whitespace.
function _stableStringify(v) {
  if (v === null || v === undefined) return "null";
  const t = typeof v;
  if (t === "number" || t === "boolean") return JSON.stringify(v);
  if (t === "string") return JSON.stringify(v);
  if (Array.isArray(v)) {
    const parts = [];
    for (let i = 0; i < v.length; i++) parts.push(_stableStringify(v[i]));
    return "[" + parts.join(",") + "]";
  }
  if (t === "object") {
    const keys = Object.keys(v).sort();
    const parts = [];
    for (let i = 0; i < keys.length; i++) {
      const k = keys[i];
      parts.push(JSON.stringify(k) + ":" + _stableStringify(v[k]));
    }
    return "{" + parts.join(",") + "}";
  }
  return "null";
}

// ─────────────────────────────────────────────────────────────────────
// 3 · Receipt emitter — pure, hash-chained.
// ─────────────────────────────────────────────────────────────────────
//
// emitMittelstandLedger(eventName, payload, opts?)
//   → { ts, eventName, payload, prevHash, hash }
//
// `opts.prevHash` chains to a prior receipt; default is "0".repeat(16).
// `opts.ts` overrides the timestamp (ISO string); default is now().
// `opts.meta` (optional) is included in the hash but stored alongside
// the receipt — useful for {wo, part, station} traceability tuples.
//
// Pure function: same inputs → same output. The hash covers ts +
// eventName + payload + prevHash + meta, so any tampering breaks the
// chain.

const MITTELSTAND_LEDGER_GENESIS_HASH = "0000000000000000";

function emitMittelstandLedger(eventName, payload, opts) {
  const o = opts || {};
  const ts = o.ts || new Date().toISOString();
  const prevHash = o.prevHash || MITTELSTAND_LEDGER_GENESIS_HASH;
  const meta = o.meta || null;

  // Validate event name against the registry; unknown events are
  // accepted but flagged so consumer screens can render an "unknown"
  // chip rather than crash.
  const known = !!MITTELSTAND_LEDGER_EVENT_BY_NAME[eventName];

  const body = {
    ts: ts,
    eventName: eventName,
    payload: payload === undefined ? null : payload,
    prevHash: prevHash,
    meta: meta,
  };
  const hash = _fnv1a64Hex(_stableStringify(body));

  const receipt = {
    ts: ts,
    eventName: eventName,
    payload: body.payload,
    prevHash: prevHash,
    hash: hash,
  };
  if (meta) receipt.meta = meta;
  if (!known) receipt.unknown = true;
  return receipt;
}

// Convenience: chain a sequence of {eventName, payload, ts?, meta?}
// entries. Each receipt's prevHash is the prior receipt's hash.
function chainMittelstandLedger(entries, opts) {
  const o = opts || {};
  const out = [];
  let prev = o.prevHash || MITTELSTAND_LEDGER_GENESIS_HASH;
  for (let i = 0; i < entries.length; i++) {
    const e = entries[i];
    const r = emitMittelstandLedger(e.eventName, e.payload, {
      ts: e.ts,
      prevHash: prev,
      meta: e.meta,
    });
    out.push(r);
    prev = r.hash;
  }
  return out;
}

// Verify a chain — recomputes each hash and confirms prevHash linkage.
// Returns { ok: bool, brokenAt: index|null, reason: string|null }.
function verifyMittelstandLedger(receipts) {
  if (!Array.isArray(receipts) || receipts.length === 0) {
    return { ok: true, brokenAt: null, reason: null };
  }
  let prev = MITTELSTAND_LEDGER_GENESIS_HASH;
  for (let i = 0; i < receipts.length; i++) {
    const r = receipts[i];
    if (r.prevHash !== prev) {
      return { ok: false, brokenAt: i, reason: "prevHash mismatch" };
    }
    const body = {
      ts: r.ts,
      eventName: r.eventName,
      payload: r.payload === undefined ? null : r.payload,
      prevHash: r.prevHash,
      meta: r.meta || null,
    };
    const expect = _fnv1a64Hex(_stableStringify(body));
    if (expect !== r.hash) {
      return { ok: false, brokenAt: i, reason: "hash mismatch" };
    }
    prev = r.hash;
  }
  return { ok: true, brokenAt: null, reason: null };
}

// ─────────────────────────────────────────────────────────────────────
// 4 · Display helpers.
// ─────────────────────────────────────────────────────────────────────

function truncateHash(h, n) {
  const k = (typeof n === "number" && n > 0) ? n : 6;
  if (typeof h !== "string") return "";
  if (h.length <= k) return h;
  return h.slice(0, k);
}

// Resolve an event's display metadata. Returns a stable shape so
// callers can render without null-checks.
function getMittelstandLedgerEvent(eventName) {
  const e = MITTELSTAND_LEDGER_EVENT_BY_NAME[eventName];
  if (e) return e;
  return {
    name: eventName,
    category: "govern",
    tone: "ink-2",
    icon: "Dot",
    label: eventName,
    beat: "",
  };
}

// Resolve a tone key to a CSS variable string.
function mittelstandToneVar(tone) {
  return MITTELSTAND_TONE_VARS[tone] || MITTELSTAND_TONE_VARS["ink-2"];
}

// ─────────────────────────────────────────────────────────────────────
// 5 · Window attachments — make the registry + emitter globally
// available, matching the rest of the demo's script-tag loading model.
// ─────────────────────────────────────────────────────────────────────

if (typeof window !== "undefined") {
  Object.assign(window, {
    MITTELSTAND_LEDGER_EVENTS: MITTELSTAND_LEDGER_EVENTS,
    MITTELSTAND_LEDGER_EVENT_BY_NAME: MITTELSTAND_LEDGER_EVENT_BY_NAME,
    MITTELSTAND_TONE_VARS: MITTELSTAND_TONE_VARS,
    MITTELSTAND_CATEGORY_LABELS: MITTELSTAND_CATEGORY_LABELS,
    MITTELSTAND_LEDGER_GENESIS_HASH: MITTELSTAND_LEDGER_GENESIS_HASH,
    emitMittelstandLedger: emitMittelstandLedger,
    chainMittelstandLedger: chainMittelstandLedger,
    verifyMittelstandLedger: verifyMittelstandLedger,
    truncateHash: truncateHash,
    getMittelstandLedgerEvent: getMittelstandLedgerEvent,
    mittelstandToneVar: mittelstandToneVar,
  });
}
