/* graph-data.jsx — derive a layered dependency graph from existing TOKENS.
   Columns: 0 primitive · 1 semantic · 2 component token · 3 consumer component.
   Brand- & mode-aware edge rerouting. Pure helpers, no React. */

/* consumer components, derived from component-token prefixes */
const CONSUMER_DEFS = [
  { prefix:'button',   id:'cmp.button',   name:'Button',   icon:'component' },
  { prefix:'link',     id:'cmp.link',     name:'Link',     icon:'link' },
  { prefix:'switch',   id:'cmp.switch',   name:'Switch',   icon:'power' },
  { prefix:'badge',    id:'cmp.badge',    name:'Badge',    icon:'hash' },
  { prefix:'tabs',     id:'cmp.tabs',     name:'Tabs',     icon:'layers' },
  { prefix:'progress', id:'cmp.progress', name:'Progress', icon:'activity' },
  { prefix:'modal',    id:'cmp.modal',    name:'Modal',    icon:'copy' },
  { prefix:'drawer',   id:'cmp.drawer',   name:'Drawer',   icon:'external' },
];
function consumerFor(tokenId) {
  const head = tokenId.split('.')[0];
  return CONSUMER_DEFS.find(c => c.prefix === head) || null;
}

/* virtual brand-accent primitives (one per theme) — the reroute targets */
function accentPrims() {
  return THEMES.map(th => ({
    id: 'accent.' + th.id + '.500', virtual:true, brand: th.id, brandName: th.name,
    layer:'primitive', kind:'color', name:'accent.'+th.id, value: th.accent[500], status:'published',
  }));
}
const IS_BRAND_ACCENT = /^brand\.accent\./;

/* immediate left-parent of a token in the graph (brand/mode aware) */
function graphParent(tk, ctx) {
  const s = tk.source;
  if (!s) return null;
  if (s.type === 'ref')      return IS_BRAND_ACCENT.test(s.ref) ? ('accent.'+ctx.theme.id+'.500') : s.ref;
  if (s.type === 'modeRef')  return s[ctx.mode] || s.light;
  return null; // value / themeAccent → leaf primitive
}

/* node accessors that tolerate virtual accent prims */
function gNode(id, accents) {
  if (TOKEN_MAP[id]) return TOKEN_MAP[id];
  return accents.find(a => a.id === id) || null;
}
function gValue(id, ctx, accents) {
  const a = accents.find(x => x.id === id);
  if (a) return { value:a.value, kind:'color' };
  const r = resolve(id, ctx);
  return { value:r.value, lh:r.lh, kind:r.kind };
}
function gParentId(id, ctx, accents) {
  if (accents.find(x => x.id === id)) return null; // accent prim is a leaf
  const tk = TOKEN_MAP[id];
  if (!tk) return null;
  return graphParent(tk, ctx);
}

/* ---- build the full graph for one token kind ---- */
function buildGraph(kind, ctx) {
  const accents = kind === 'color' ? accentPrims() : [];
  const sem  = TOKENS.filter(t => t.layer==='semantic'  && t.kind===kind && t.status!=='deprecated');
  const comp = TOKENS.filter(t => t.layer==='component' && t.kind===kind && t.status!=='deprecated');

  const nodes = {};
  const edges = [];
  const add = (id, col, ref, kindOf) => { if (id && !nodes[id]) nodes[id] = { id, col, ref, layer:kindOf }; };

  accents.forEach(a => add(a.id, 0, a, 'primitive'));

  sem.forEach(s => {
    add(s.id, 1, s, 'semantic');
    const p = gParentId(s.id, ctx, accents);
    if (p) { const pn = gNode(p, accents); add(p, 0, pn, 'primitive'); edges.push({ from:p, to:s.id }); }
  });

  comp.forEach(c => {
    add(c.id, 2, c, 'component');
    const p = gParentId(c.id, ctx, accents);
    if (p) {
      const pn = gNode(p, accents);
      const col = pn && pn.layer==='primitive' ? 0 : 1;
      add(p, col, pn, col===0?'primitive':'semantic');
      edges.push({ from:p, to:c.id });
    }
    const cons = consumerFor(c.id);
    if (cons) {
      const cat = (typeof COMPONENT_MAP !== 'undefined' && COMPONENT_MAP[cons.prefix]) ? COMPONENT_MAP[cons.prefix].category : 'Foundation';
      add(cons.id, 3, { ...cons, category: cat }, 'consumer'); edges.push({ from:c.id, to:cons.id });
    }
  });

  // add EVERY catalog component as a consumer, linked to the tokens it consumes in this kind
  if (typeof COMPONENTS !== 'undefined') {
    COMPONENTS.forEach(c => {
      if (c.status === 'deprecated') return;
      const cid = 'cmp.' + c.id;
      if (nodes[cid]) return; // already added via a component token (wired component)
      const consumes = (typeof COMPONENT_CONSUMES !== 'undefined' ? COMPONENT_CONSUMES[c.id] : null) || [];
      const links = consumes.filter(tid => nodes[tid]);
      if (!links.length) return;
      add(cid, 3, { id: cid, name: c.name, icon: c.icon, category: c.category }, 'consumer');
      links.forEach(tid => edges.push({ from: tid, to: cid }));
    });
  }

  return layout(nodes, edges, accents, ctx);
}

/* ---- barycenter layout ---- */
const COL_X   = [0, 250, 500, 752];
const NODE_W  = 188;
const NODE_H  = 50;
const ROW_GAP = 24;

function layout(nodesMap, edges, accents) {
  const ids = Object.keys(nodesMap);
  const cols = [[],[],[],[]];
  ids.forEach(id => cols[nodesMap[id].col].push(id));

  const parents = {}, children = {};
  ids.forEach(id => { parents[id]=[]; children[id]=[]; });
  edges.forEach(e => { children[e.from].push(e.to); parents[e.to].push(e.from); });

  const y = {};
  cols.forEach(col => col.forEach((id,i) => { y[id] = i; }));
  const avg = arr => arr.reduce((a,b)=>a+b,0)/arr.length;

  for (let s=0; s<10; s++) {
    for (let c=1; c<4; c++) cols[c].forEach(id => { if (parents[id].length) y[id] = avg(parents[id].map(p=>y[p])); });
    for (let c=2; c>=0; c--) cols[c].forEach(id => { if (children[id].length) y[id] = avg(children[id].map(p=>y[p])); });
  }

  const pos = {};
  let maxH = 0;
  cols.forEach((col, ci) => {
    col.sort((a,b) => y[a]-y[b]);
    col.forEach((id,i) => { pos[id] = { x: COL_X[ci], y: i*(NODE_H+ROW_GAP) }; });
    maxH = Math.max(maxH, col.length*(NODE_H+ROW_GAP) - ROW_GAP);
  });
  cols.forEach(col => {
    const h = col.length*(NODE_H+ROW_GAP) - ROW_GAP;
    const off = (maxH - h)/2;
    col.forEach(id => { pos[id].y += off; });
  });

  const nodes = ids.map(id => ({ ...nodesMap[id], ...pos[id] }));
  ids.forEach(id => { nodesMap[id].x = pos[id].x; nodesMap[id].y = pos[id].y; });
  const width  = COL_X[3] + NODE_W;
  return { nodes, edges, nodesMap, parents, children, accents, width, height: Math.max(maxH, NODE_H), cols };
}

function ancestorsOf(id, parents) {
  const out = new Set(); const stack = [...(parents[id]||[])];
  while (stack.length) { const n = stack.pop(); if (!out.has(n)) { out.add(n); (parents[n]||[]).forEach(p=>stack.push(p)); } }
  return out;
}
function descendantsOf(id, children) {
  const out = new Set(); const stack = [...(children[id]||[])];
  while (stack.length) { const n = stack.pop(); if (!out.has(n)) { out.add(n); (children[n]||[]).forEach(p=>stack.push(p)); } }
  return out;
}
function subgraphIds(id, g) {
  const set = new Set([id]);
  ancestorsOf(id, g.parents).forEach(x=>set.add(x));
  descendantsOf(id, g.children).forEach(x=>set.add(x));
  return set;
}
function upstreamChain(id, ctx, accents) {
  const chain = []; const seen = new Set(); let cur = id;
  while (cur && !seen.has(cur)) { seen.add(cur); chain.push(cur); cur = gParentId(cur, ctx, accents); }
  return chain;
}

Object.assign(window, {
  CONSUMER_DEFS, consumerFor, accentPrims, graphParent, gNode, gValue, gParentId,
  buildGraph, ancestorsOf, descendantsOf, subgraphIds, upstreamChain,
  COL_X, NODE_W, NODE_H, ROW_GAP,
});
