// ═══════════════════════════════════════════════════════════════════
// SCHEMA — the canonical Compass Issuer Datapoint Register as a
// Knowledge Base + Knowledge Graph layer. Reads window.COMPASS_SCHEMA
// (268 datapoints · 4 tiers · 45 controlled vocabularies) and derives:
//   · the tier ontology (Issuer → Project → Asset → Offering)
//   · groups per tier · vocabulary join-nodes · cross-tier relations
//   · deterministic per-project capture state (the instance overlay)
// ═══════════════════════════════════════════════════════════════════
(function () {
  const SRC = window.COMPASS_SCHEMA || { meta: {}, DATAPOINTS: [] };
  const DP = SRC.DATAPOINTS;

  // ── tier ontology — the four typed nodes of the graph ─────────────
  const TIERS = [
    { id: "Issuer",   glyph: "◆", role: "Legal entity",   def: "The entity legally accountable to investors — identity, registration, ownership, standing." },
    { id: "Project",  glyph: "▣", role: "The venture",     def: "The business being financed — narrative, media, documents, financials, cap table, operations." },
    { id: "Asset",    glyph: "⬡", role: "What is tokenized", def: "The underlying value wrapped as a security — class, valuation, title, token economics, on-chain passport." },
    { id: "Offering", glyph: "◎", role: "The capital round", def: "The round itself — terms, eligibility, legal basis, window, documents, settlement." },
  ];
  const TIER_ORDER = TIERS.map((t) => t.id);
  const tierMeta = (id) => TIERS.find((t) => t.id === id) || { id, glyph: "·", role: "", def: "" };

  // ── cross-tier relationships — the edges that bind the tiers ──────
  const REL = [
    { from: "Project",  to: "Issuer",  label: "issued by",            basis: "—",     note: "Every project is run by exactly one accountable issuer." },
    { from: "Asset",    to: "Issuer",  label: "legally owned by",     basis: "A03",   note: "Legal owner resolves against the entity register." },
    { from: "Asset",    to: "Project", label: "developed under",      basis: "—",     note: "Assets belong to the project's business context." },
    { from: "Offering", to: "Asset",   label: "sells interest in",    basis: "OFF03", note: "Linked asset(s) resolve against the entity register." },
    { from: "Offering", to: "Project", label: "raises capital for",   basis: "—",     note: "The round funds the project's use of proceeds." },
  ];

  // ═══ THE WORLD MODEL ═══════════════════════════════════════════════
  // The register is PROPERTIES; the ontology is the SUBJECTS those
  // properties are about, plus the edges between them. Tiers are demoted
  // to property domains. Three structural rules shape the graph:
  //   1. roles live on EDGES, not nodes (one Party, role on the edge)
  //   2. an attributed relationship is REIFIED into a node (Holding, Valuation)
  //   3. a vocabulary value is a NODE iff an edge targets it (Jurisdiction, Chain)
  // ── subject categories (for the node legend) ──────────────────────
  const SUBJ_CATS = {
    core:    { label: "Core subject",   note: "owns a rich property bundle" },
    content: { label: "Content",        note: "artifacts & provenance" },
    reified: { label: "Reified edge",   note: "an attributed relationship with identity" },
    ref:     { label: "Reference node",  note: "a shared value other subjects point at" },
  };
  // ── SUBJECTS — the nodes of the property graph ────────────────────
  // `owns` = the register groups whose datapoints are properties OF this
  // subject (as [tier, group]); ref/reified nodes own little or nothing.
  const SUBJECTS = [
    { id: "Company",  glyph: "◆", cat: "core", def: "The legal / operating entity accountable to investors — identity, registration, standing. An SPV is a Company in the issuance-vehicle role.",
      owns: [["Issuer", "Issuer identity"], ["Issuer", "Registration, profile & contact"], ["Issuer", "Standing & advisors"]] },
    { id: "Project",  glyph: "▣", cat: "core", def: "The venture being financed — narrative, market, traction, online presence, and the financial model that underwrites it.",
      owns: [["Project", "Project basics"], ["Project", "Marketing & online presence"], ["Project", "Business financials"], ["Project", "Computed & live (project)"]] },
    { id: "Asset",    glyph: "⬡", cat: "core", def: "The real-world value being tokenized — identity, class, and the class-specific property set that varies by asset type.",
      owns: [["Asset", "Asset identity & class"], ["Asset", "Class fields — Real estate & farmland"], ["Asset", "Class fields — Company equity"], ["Asset", "Class fields — Debt / credit, revenue-share & receivables"], ["Asset", "Class fields — Fund / portfolio"], ["Asset", "Class fields — Commodities, metals & infrastructure"], ["Asset", "Class fields — Art, collectibles, equipment & IP"]] },
    { id: "Security", glyph: "⬢", cat: "core", def: "The token that wraps the asset — economics, on-chain passport, custody/ops, and live chain state.",
      owns: [["Asset", "Token economics & technology"], ["Asset", "On-chain & passport (asset)"], ["Asset", "Post-offering (asset)"], ["Asset", "Live on-chain (asset)"], ["Project", "Custody, ops & reporting"]] },
    { id: "Offering", glyph: "◎", cat: "core", def: "The capital round — terms, window, eligibility policy and settlement. The exemption is reified into its own legal-basis edge; distribution & settlement live on the Broker-Dealer it routes through.",
      owns: [["Offering", "Round identity"], ["Offering", "Terms"], ["Offering", "Structure & investor terms"], ["Offering", "Offering window & dates"], ["Offering", "Eligibility & compliance"], ["Offering", "Computed (offering)"]] },
    { id: "Document", glyph: "▤", cat: "content", def: "Every uploaded artifact — business & offering documents, governing docs, media — that evidences a fact. The provenance backbone; `evidences→` any subject.",
      owns: [["Project", "Business documents"], ["Project", "Media — images & video"], ["Offering", "Offering documents"]] },
    { id: "Holding",  glyph: "⊟", cat: "reified", def: "A reified ownership edge — a Party's quantified position in a Company (cap-table line, UBO stake) or subscription to an Offering. Carries %, class, amount.",
      owns: [["Issuer", "Ownership & governance"], ["Project", "Cap table & capital"]] },
    { id: "Valuation", glyph: "◔", cat: "reified", def: "A reified appraisal — the assertion of an Asset's value with method, date and appraiser. Re-done on a cadence, so it has its own identity.",
      owns: [["Asset", "Valuation & title"]] },
    { id: "Party",    glyph: "◐", cat: "core", def: "A core actor entity — any named person or organization (director, UBO, auditor, counsel, escrow agent, custodian, KYC provider, regulator, appraiser, capital-intro / channel partner). One node with its own identity; each role it plays is carried on the edge, never duplicated into a new node type. The broker-dealer is the one role promoted to its own entity node.",
      owns: [], refs: "E04 directors · E05 UBOs · E11 auditor · L03 counsel · OFF07 escrow · M10 KYC · E09b regulator" },
    { id: "Jurisdiction", glyph: "⌖", cat: "ref", def: "A country / regulatory regime. A node because Company, Asset and Offering all point at it (incorporation, location, offer & geo-gating).",
      owns: [], refs: "E02 · A12 · L01 · M03 / M04  (COUNTRY)" },
    { id: "Chain",    glyph: "⬙", cat: "ref", def: "The on-chain network the Security deploys to. A node because the Security points at it as shared infrastructure.",
      owns: [], refs: "T01  (CHAIN)" },
    { id: "Broker-Dealer", glyph: "▦", cat: "core", def: "A registered placement venue an Offering is distributed and cleared through — broker-dealer, funding portal or ATS. An entity with its own identity (firm, registration, the regimes it clears, fees); routing and acceptance point at it as the gate the offering must clear. Optional — a self-managed raise (OFF21) has none.",
      owns: [["Offering", "Broker, escrow & settlement (optional)"]] },
    { id: "Exemption", glyph: "§", cat: "reified", def: "A reified legal-basis edge — the regime an Offering is made under (Reg D 506(b)/(c) · Reg CF · Reg A+ · Reg S) scoped to a Jurisdiction, carrying its caps, investor eligibility, solicitation and filing duties. Reified because it is an attributed relationship with its own identity, selected by the EXEMPTION discriminator (L02).",
      owns: [["Offering", "Legal basis"]] },
  ];
  const subjMeta = (id) => SUBJECTS.find((s) => s.id === id) || { id, glyph: "·", cat: "ref", def: "", owns: [] };
  const SUBJ_ORDER = SUBJECTS.map((s) => s.id);

  // ── EDGES — the shape of the property graph ───────────────────────
  // kind: spine | struct | actor | reified | ref | provenance
  const EDGES = [
    { from: "Company",   to: "Project",      label: "runs",                 kind: "spine" },
    { from: "Project",   to: "Offering",     label: "raises capital via",   kind: "spine" },
    { from: "Offering",  to: "Security",     label: "sells",                kind: "spine", basis: "OFF03" },
    { from: "Security",  to: "Asset",        label: "wraps",                kind: "spine", basis: "C04" },
    { from: "Asset",     to: "Company",      label: "owned by",             kind: "spine", basis: "A03" },
    { from: "Asset",     to: "Project",      label: "developed under",      kind: "spine" },
    { from: "Company",   to: "Company",      label: "controls (SPV)",       kind: "struct", basis: "E03" },
    { from: "Company",   to: "Party",        label: "governed / audited by", kind: "actor", basis: "E04 · E11" },
    { from: "Party",     to: "Company",      label: "holds",                kind: "reified", via: "Holding", basis: "E05 · C01" },
    { from: "Party",     to: "Offering",     label: "subscribes",           kind: "reified", via: "Holding", basis: "S06" },
    { from: "Valuation", to: "Asset",        label: "appraises",            kind: "reified", basis: "A05 · A09" },
    { from: "Party",     to: "Valuation",    label: "appraiser",            kind: "actor" },
    { from: "Security",  to: "Chain",        label: "deployed on",          kind: "ref", basis: "T01" },
    { from: "Security",  to: "Party",        label: "custodied by",         kind: "actor", basis: "T04" },
    { from: "Company",   to: "Jurisdiction", label: "incorporated in",      kind: "ref", basis: "E02" },
    { from: "Asset",     to: "Jurisdiction", label: "located in",           kind: "ref", basis: "A12" },
    { from: "Offering",  to: "Jurisdiction", label: "offered in / geo-gated", kind: "ref", basis: "L01 · M03 / M04" },
    { from: "Offering",  to: "Party",        label: "counsel · escrow · KYC", kind: "actor", basis: "L03 · OFF07 · M10" },
    { from: "Offering",  to: "Broker-Dealer", label: "distributed via",      kind: "actor", basis: "OFF05" },
    { from: "Broker-Dealer", to: "Exemption", label: "clears",              kind: "ref" },
    { from: "Offering",  to: "Exemption",    label: "offered under",        kind: "reified", basis: "L02" },
    { from: "Exemption", to: "Jurisdiction", label: "scoped to",            kind: "ref" },
    { from: "Document",  to: "*",            label: "evidences",            kind: "provenance" },
  ];

  // ── derive groups per tier (ordered as first seen) ────────────────
  const groupsByTier = {};
  TIER_ORDER.forEach((t) => (groupsByTier[t] = []));
  DP.forEach((d) => {
    const arr = groupsByTier[d.tier] || (groupsByTier[d.tier] = []);
    let g = arr.find((x) => x.group === d.group);
    if (!g) { g = { group: d.group, codes: [] }; arr.push(g); }
    g.codes.push(d.code);
  });

  // ── EXTENDED ONTOLOGY: groups are FIRST-CLASS facets of their tier ──
  // The register is a two-level structure — Tier → Group → datapoints.
  // We promote the 33 groups to named structural nodes so the ontology
  // is the full Tier × Group decomposition, not just the four tiers.
  const slug = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
  const groupKey = (tier, group) => tier + "›" + group;
  const GROUPS = [];
  TIER_ORDER.forEach((t) => (groupsByTier[t] || []).forEach((g) => {
    GROUPS.push({ id: t.toLowerCase() + "/" + slug(g.group), key: groupKey(t, g.group), tier: t, group: g.group, codes: g.codes, n: g.codes.length });
  }));
  const groupMeta = (tier, group) => GROUPS.find((x) => x.tier === tier && x.group === group);
  const groupCount = GROUPS.length;

  // ── controlled vocabularies — the property value-domain layer ─────
  // Each vocabulary plays one of three structural roles:
  //   discriminator — selects a SUBJECT's variant / regime (it is schema)
  //   dimension     — a shared join key / external standard (sliceable axis)
  //   enum          — a local constrained field (validation only)
  const DISCRIMINATOR_VOCAB = {
    ASSET_CLASS:    { code: "A01",   target: "Asset",    label: "asset class",         note: "selects which of the six class-field bundles the Asset carries" },
    SECURITY_TYPE:  { code: "C04",   target: "Security", label: "token wrapper",       note: "selects the security wrapper / token type" },
    OFFERING_TYPE:  { code: "OFF02", target: "Offering", label: "round type",          note: "selects the round structure" },
    EXEMPTION:      { code: "L02",   target: "Offering", label: "legal regime",        note: "selects the exemption — also reified into the Exemption legal-basis edge; drives eligibility & transfer rules" },
    ENTITY_TYPE:    { code: "E01b",  target: "Company",  label: "legal form",          note: "selects the entity's legal form" },
    STRUCTURE_TYPE: { code: "E03",   target: "Company",  label: "ownership structure", note: "selects parent / SPV structure" },
  };
  const DIMENSION_VOCAB = {
    COUNTRY:         { node: "Jurisdiction", note: "promoted to the Jurisdiction node" },
    CHAIN:           { node: "Chain",        note: "promoted to the Chain node" },
    CURRENCY:        { note: "normalizes money across every subject" },
    ENTITY_REGISTER: { note: "the foreign-key axis — resolves Offering→Asset, Asset→owner" },
    TOKEN_STANDARD:  { note: "external standard — ERC-3643 / ERC-20 …" },
    REG_BODY:        { note: "joins to the regulator (a Party)" },
  };
  const vocabRole = (name) => DISCRIMINATOR_VOCAB[name] ? "discriminator" : DIMENSION_VOCAB[name] ? "dimension" : "enum";
  // the Asset's six variants — the bundles ASSET_CLASS chooses between
  const assetVariants = () => (groupsByTier["Asset"] || [])
    .filter((g) => g.group.indexOf("Class fields — ") === 0)
    .map((g) => ({ label: g.group.replace("Class fields — ", ""), group: g.group, codes: g.codes }));

  // ── controlled vocabularies — the shared dimension nodes ──────────
  const VOCAB = {};
  DP.forEach((d) => {
    if (!d.vocab) return;
    const v = VOCAB[d.vocab] || (VOCAB[d.vocab] = { name: d.vocab, codes: [], tiers: new Set() });
    v.codes.push(d.code); v.tiers.add(d.tier);
  });
  const VOCAB_LIST = Object.values(VOCAB)
    .map((v) => ({ name: v.name, codes: v.codes, tiers: TIER_ORDER.filter((t) => v.tiers.has(t)), role: vocabRole(v.name) }))
    .sort((a, b) => b.codes.length - a.codes.length || a.name.localeCompare(b.name));
  // vocabularies referenced from 2+ tiers are the graph's join keys
  const JOIN_VOCAB = VOCAB_LIST.filter((v) => v.tiers.length >= 2);

  // ── subject grounding — resolve each subject's `owns` to real groups,
  // so every subject carries the register properties it is the subject of ─
  const subjGroups = (s) => (s.owns || [])
    .map(([tier, group]) => ({ tier, group: (groupsByTier[tier] || []).find((g) => g.group === group) }))
    .filter((x) => x.group);
  const subjCodes = (s) => subjGroups(s).reduce((acc, x) => acc.concat(x.group.codes), []);
  const subjCount = (s) => subjCodes(s).length;
  // coverage of a subject = rollup over the groups it owns
  function subjCoverage(s, cov) {
    const o = { verified: 0, captured: 0, gap: 0, total: 0 };
    (s.owns || []).forEach(([tier, group]) => {
      const gc = cov.byGroup[tier + "›" + group];
      if (gc) { o.verified += gc.verified; o.captured += gc.captured; o.gap += gc.gap; o.total += gc.total; }
    });
    return o;
  }
  // edges touching a subject (out + in)
  const subjEdges = (id) => EDGES.filter((e) => e.from === id || e.to === id);

  const byCode = {}; DP.forEach((d) => (byCode[d.code] = d));

  // ── distributions ─────────────────────────────────────────────────
  function dist(key) { const m = {}; DP.forEach((d) => (m[d[key]] = (m[d[key]] || 0) + 1)); return m; }
  const tierCount = (t) => DP.filter((d) => d.tier === t).length;

  // ── deterministic per-project capture state (instance overlay) ────
  function hash(s) { let h = 2166136261; for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return (h >>> 0) / 4294967295; }
  const REQ_BOOST = { Required: 0.30, Recommended: 0.05, Optional: -0.14, Auto: 0.0, Internal: -0.22 };
  // status for one datapoint under a project of given completeness (0..1)
  function statusOf(pid, d, completeness) {
    const r = hash(pid + ":" + d.code);
    if (d.req === "Auto") {
      // computed/live fields only populate once the record is well advanced
      return completeness > 0.6 && r < (completeness - 0.15) ? "captured" : "gap";
    }
    const score = completeness + (REQ_BOOST[d.req] || 0);
    if (r >= score) return "gap";
    const v = hash(pid + ":" + d.code + ":v");
    const verified = d.vis === "Public" && v < (completeness - 0.22);
    return verified ? "verified" : "captured";
  }
  // rollups: { byTier:{Issuer:{verified,captured,gap,total}...}, byGroup:{...}, total:{...} }
  // classId (optional) BINDS the rollup to a project's asset class (A01): only the
  // bound class's `Class fields — …` bundle is in scope; the other six bundles are
  // marked "na" and excluded from every total. Omit classId (reference views) to
  // score the full register across all class bundles.
  function coverage(pid, completeness, classId) {
    const boundGroup = classId ? (assetClass(classId) || {}).group : null;
    const outOfClass = (d) => classId && d.group && d.group.indexOf("Class fields — ") === 0 && d.group !== boundGroup;
    const blank = () => ({ verified: 0, captured: 0, gap: 0, total: 0 });
    const out = { byTier: {}, byGroup: {}, total: blank(), perCode: {}, classId: classId || null };
    TIER_ORDER.forEach((t) => (out.byTier[t] = blank()));
    DP.forEach((d) => {
      if (outOfClass(d)) { out.perCode[d.code] = "na"; return; } // a different class's bundle — not applicable to this project
      const st = statusOf(pid, d, completeness);
      out.perCode[d.code] = st;
      const bump = (o) => { o[st]++; o.total++; };
      bump(out.byTier[d.tier]); bump(out.total);
      const gk = d.tier + "›" + d.group;
      bump(out.byGroup[gk] || (out.byGroup[gk] = blank()));
    });
    out.filled = out.total.verified + out.total.captured;
    out.pct = Math.round((out.filled / out.total.total) * 100);
    return out;
  }

  // ── canonical ASSET-CLASS registry — the ROOT of the asset-class axis.
  // Sourced from the A01 · ASSET_CLASS discriminator: one entry per Asset
  // class-field bundle. Identity (id · name · glyph) lives HERE; every other
  // spec layer (Datapoint Register, Report Schema, Workflow Templates, and
  // the Asset Classes index) builds on these ids and must not re-declare them.
  const ASSET_CLASS_META = [
    { id: "realEstate",     name: "Real Estate & Land",              glyph: "▦", group: "Class fields — Real estate & farmland" },
    { id: "equity",         name: "Company Equity",                  glyph: "❖", group: "Class fields — Company equity" },
    { id: "credit",         name: "Debt & Credit",                   glyph: "☲", group: "Class fields — Debt / credit, revenue-share & receivables" },
    { id: "fund",           name: "Fund / Portfolio",                glyph: "⬡", group: "Class fields — Fund / portfolio" },
    { id: "commodity",      name: "Commodities & Natural Resources", glyph: "⬗", group: "Class fields — Commodities, metals & resources" },
    { id: "infrastructure", name: "Infrastructure",                  glyph: "▥", group: "Class fields — Infrastructure" },
    { id: "collectible",    name: "Art, Collectibles & IP",          glyph: "◈", group: "Class fields — Art, collectibles, equipment & IP" },
  ];
  const _variantsByGroup = {}; assetVariants().forEach((v) => (_variantsByGroup[v.group] = v));
  const ASSET_CLASSES = ASSET_CLASS_META.map((m) => { const v = _variantsByGroup[m.group] || { codes: [] }; return { id: m.id, name: m.name, glyph: m.glyph, group: m.group, codes: v.codes, count: v.codes.length }; });
  const assetClass = (id) => ASSET_CLASSES.find((c) => c.id === id) || null;

  // ── per-project asset-class binding (assignment overlay) ───────────
  // The class an analyst assigns to a project. Roots on the registry above;
  // legacy ids are remapped, unknown ids fall back. Lives with the ontology
  // because the class taxonomy is owned here.
  window.AssetClass = (function () {
    const KEY = "intelPortal.assetClass";
    const LEGACY = { operatingCo: "equity", privateCredit: "credit", digital: "equity" };
    const valid = ASSET_CLASSES.map((c) => c.id);
    const norm = (id) => { const x = LEGACY[id] || id; return valid.indexOf(x) >= 0 ? x : "equity"; };
    const loadMap = () => { try { return JSON.parse(localStorage.getItem(KEY) || "{}"); } catch (e) { return {}; } };
    const derived = (proj) => (proj && proj.assetClass) || (proj && proj.tenant === "saxcap" ? "realEstate" : "equity");
    return {
      of(proj) { const m = loadMap(); return norm((proj && m[proj.id]) || derived(proj)); },
      assigned(proj) { const m = loadMap(); return !!(proj && m[proj.id]); },
      set(projId, cls) { const m = loadMap(); m[projId] = cls; localStorage.setItem(KEY, JSON.stringify(m)); },
    };
  })();

  window.ONTOLOGY = {
    meta: SRC.meta, DP, byCode,
    TIERS, TIER_ORDER, tierMeta, tierCount,
    groupsByTier, REL,
    GROUPS, groupKey, groupMeta, groupCount, slug,
    SUBJECTS, SUBJ_ORDER, SUBJ_CATS, subjMeta, EDGES,
    subjGroups, subjCodes, subjCount, subjCoverage, subjEdges,
    VOCAB_LIST, JOIN_VOCAB,
    DISCRIMINATOR_VOCAB, DIMENSION_VOCAB, vocabRole, assetVariants, ASSET_CLASSES, assetClass,
    dist, coverage, statusOf,
  };
  // Deprecated alias — this is the INTELLIGENCE ontology (the Compass World
  // Model). Prefer window.ONTOLOGY. Kept so the generated bundle and any
  // external consumer keep working. Distinct from the C2 Foundation ontology
  // (window.FOUNDATION) and from window.COMPASS_SCHEMA (raw datapoint register).
  window.SCHEMA = window.ONTOLOGY;
})();
