/* OM Group ERP — Enterprise Analytics · Domain Workspace
   The universal view over any declared domain in window.IntelDomains. One
   renderer, thirteen workspaces: every section below reads only the pack
   returned by IntelDomains.build(), which is re-derived from Store on every
   ERP mutation. Nothing here holds domain knowledge — add a domain to the
   engine and it appears in the rail with the full capability set.          */
const { useState: idSt, useMemo: idMemo, useEffect: idEf, useRef: idRef } = React;
const IDx = () => window.IntelDomains;

function idFmt(v, k) { return window.IntelDomains.fmtVal(v, k); }
const ID_SEV = { 1: '#DC2626', 2: '#F97316', 3: '#2563EB' };
const ID_KIND = { risk: '#DC2626', action: '#F97316', opportunity: '#16A34A', info: '#2563EB' };

/* ── domain rail ───────────────────────────────────────────────────────────
   The workspace selector. Nothing about it is bound to the current domain
   list — it measures itself, so any number of future workspaces behaves the
   same. A wheel anywhere over the rail scrolls it horizontally with inertia
   and only hands the gesture back to the page at the ends; trackpad and
   touch keep their own native momentum; it is a roving-tabindex tablist
   driven by ←/→/Home/End/PageUp/PageDown; and the active tab always glides
   back to the middle of the visible region so it can never hide.          */
function IDomainRail({ value, onChange }) {
  const rail = IDx().RAIL;
  const wrap = idRef(null);
  const eng = idRef(null);
  const hold = idRef(null);
  const [edge, setEdge] = idSt({ s: true, e: true, over: false });
  const [ind, setInd] = idSt(null);

  /* scroll engine — one rAF loop easing scrollLeft toward a target */
  idEf(() => {
    const el = wrap.current; if (!el) return;
    const calm = window.matchMedia && window.matchMedia('(prefers-reduced-motion:reduce)').matches;
    const S = { target: el.scrollLeft, running: false, raf: 0 };
    const max = () => Math.max(0, el.scrollWidth - el.clientWidth);
    const report = () => {
      const m = max();
      const n = { s: el.scrollLeft <= 1, e: el.scrollLeft >= m - 1, over: m > 2 };
      setEdge(p => (p.s === n.s && p.e === n.e && p.over === n.over) ? p : n);
      const on = el.querySelector('button[data-on="1"]');
      if (on) setInd(p => (p && p.x === on.offsetLeft && p.w === on.offsetWidth) ? p : { x: on.offsetLeft, w: on.offsetWidth });
    };
    const tick = () => {
      const cur = el.scrollLeft, d = S.target - cur;
      if (Math.abs(d) < .6) { el.scrollLeft = S.target; S.running = false; report(); return; }
      el.scrollLeft = cur + d * (calm ? 1 : .19);
      report();
      S.raf = requestAnimationFrame(tick);
    };
    const glide = (v, absolute) => {
      S.target = Math.max(0, Math.min(max(), absolute ? v : S.target + v));
      if (!S.running) { S.running = true; S.raf = requestAnimationFrame(tick); }
    };
    const centre = btn => { if (btn) glide(btn.offsetLeft - (el.clientWidth - btn.offsetWidth) / 2, true); };
    const onWheel = e => {
      const m = max(); if (m <= 2) return;
      if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) { S.target = el.scrollLeft; return; } /* native horizontal wheel / trackpad */
      const unit = e.deltaMode === 1 ? 18 : e.deltaMode === 2 ? el.clientWidth : 1;
      const delta = e.deltaY * unit * (e.shiftKey ? 2.6 : 1.2);
      if ((delta < 0 && S.target <= .5) || (delta > 0 && S.target >= m - .5)) return; /* at the end — let the page scroll */
      e.preventDefault();
      glide(delta);
    };
    const onScroll = () => { if (!S.running) S.target = el.scrollLeft; report(); };
    el.addEventListener('wheel', onWheel, { passive: false });
    el.addEventListener('scroll', onScroll, { passive: true });
    let ro = null;
    if (window.ResizeObserver) { ro = new ResizeObserver(report); ro.observe(el); }
    const mo = new MutationObserver(report);
    mo.observe(el, { childList: true, subtree: true, characterData: true });
    eng.current = { glide, centre, report, width: () => el.clientWidth };
    report();
    const t = setTimeout(report, 240); /* after fonts settle */
    return () => {
      clearTimeout(t); cancelAnimationFrame(S.raf);
      el.removeEventListener('wheel', onWheel); el.removeEventListener('scroll', onScroll);
      if (ro) ro.disconnect(); mo.disconnect(); eng.current = null;
    };
  }, []);

  /* the selected workspace is never allowed to sit outside the viewport */
  idEf(() => {
    const el = wrap.current, E = eng.current; if (!el || !E) return;
    E.centre(el.querySelector('button[data-on="1"]'));
    E.report();
  }, [value]);

  const ix = Math.max(0, rail.findIndex(d => d.id === value));
  const focusActive = () => requestAnimationFrame(() => {
    const el = wrap.current, b = el && el.querySelector('button[data-on="1"]');
    if (b) b.focus({ preventScroll: true });
  });
  const go = i => { const n = rail.length; const j = ((i % n) + n) % n; if (rail[j].id !== value) onChange(rail[j].id); focusActive(); };
  const onKey = e => {
    const k = e.key;
    if (k === 'ArrowRight') { e.preventDefault(); go(ix + 1); }
    else if (k === 'ArrowLeft') { e.preventDefault(); go(ix - 1); }
    else if (k === 'Home') { e.preventDefault(); go(0); }
    else if (k === 'End') { e.preventDefault(); go(rail.length - 1); }
    else if (k === 'PageDown') { e.preventDefault(); go(Math.min(rail.length - 1, ix + 5)); }
    else if (k === 'PageUp') { e.preventDefault(); go(Math.max(0, ix - 5)); }
    else if (k === 'Enter' || k === ' ' || k === 'Spacebar') { e.preventDefault(); onChange(rail[ix].id); }
  };

  /* edge buttons: a page nudge on press, gently accelerating if held */
  const stopHold = () => { if (hold.current) { hold.current(); hold.current = null; } };
  const startHold = dir => {
    const E = eng.current; if (!E) return;
    E.glide(dir * Math.max(180, E.width() * .68));
    let v = 7, raf = 0;
    const timer = setTimeout(function run() {
      v = Math.min(42, v * 1.07 + .6); E.glide(dir * v);
      raf = requestAnimationFrame(run);
    }, 320);
    hold.current = () => { clearTimeout(timer); cancelAnimationFrame(raf); };
  };
  idEf(() => stopHold, []);

  const arrow = dir => (
    <button className={'i-dom-arw ' + (dir < 0 ? 'l' : 'r')} tabIndex={-1} aria-hidden="true"
      onPointerDown={e => { e.preventDefault(); startHold(dir); }} onPointerUp={stopHold}
      onPointerLeave={stopHold} onPointerCancel={stopHold}>
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" width="13" height="13">
        <path d={dir < 0 ? 'M15 5l-7 7 7 7' : 'M9 5l7 7-7 7'} />
      </svg>
    </button>
  );

  let lastGroup = null;
  return (
    <div className={'i-dom-nav' + (edge.over ? ' over' : '') + (edge.s ? ' at-s' : '') + (edge.e ? ' at-e' : '')}>
      {arrow(-1)}
      <div className="i-dom-rail" ref={wrap} role="tablist" aria-label="Analytics workspace" aria-orientation="horizontal" onKeyDown={onKey}>
        {ind && <span className="i-dom-ind" style={{ transform: 'translateX(' + ind.x + 'px)', width: ind.w }} aria-hidden="true"></span>}
        {rail.map(d => {
          const head = d.group !== lastGroup ? d.group : null;
          lastGroup = d.group;
          const on = value === d.id;
          return (
            <React.Fragment key={d.id}>
              {head && <span className="i-dom-grp" aria-hidden="true">{head}</span>}
              <button className={on ? 'on' : ''} style={{ '--dc': d.color }} role="tab" id={'i-dom-tab-' + d.id}
                aria-selected={on} aria-controls={'i-dom-panel-' + d.id} tabIndex={on ? 0 : -1} data-on={on ? '1' : '0'}
                onClick={() => onChange(d.id)}
                onFocus={() => { const E = eng.current, el = wrap.current; if (E && el) E.centre(el.querySelector('button[data-on="1"]')); }}>
                {window.OMIcon ? <window.OMIcon name={d.icon} size={14} /> : <i></i>}{d.label}
              </button>
            </React.Fragment>
          );
        })}
      </div>
      {arrow(1)}
    </div>
  );
}

/* ── delta pill (shares the Intelligence Center language) ──────────────── */
function IDDelta({ cur, prev, good }) {
  if (prev == null) return <span className="i-kpi-cmp">no comparison</span>;
  const d = prev !== 0 ? (cur - prev) / Math.abs(prev) * 100 : (cur > 0 ? 100 : 0);
  const flat = Math.abs(d) < 0.05 || good === 'flat';
  const better = good === 'down' ? d < 0 : d > 0;
  const arrow = d > 0
    ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><path d="M7 17L17 7M17 7H9M17 7v8" /></svg>
    : d < 0 ? <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><path d="M7 7l10 10M17 17H9M17 17V9" /></svg>
      : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3"><path d="M5 12h14" /></svg>;
  return <span className={'i-delta ' + (flat ? 'fl' : better ? 'up' : 'dn')}>{arrow}{Math.abs(d) > 999 ? '>999' : Math.abs(d).toFixed(1)}%</span>;
}

/* ── KPI rail ──────────────────────────────────────────────────────────────
   Every card in every workspace is a traceable intelligence surface: it opens
   the KPI sheet for its own metric, which re-derives the same metric function
   over the same live records the card was rendered from. A metric that cannot
   be formatted shows a contained error state — the rail keeps working.     */
const IDK_ARROW = (
  <span className="i-kpi-more" aria-hidden="true">
    <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.6" strokeLinecap="round" strokeLinejoin="round"><path d="M7 17L17 7M17 7H9M17 7v8" /></svg>
  </span>
);
function IDKpiCard({ pack, mkey, meta, color, index, onOpen }) {
  const [err, setErr] = idSt(0);
  const raw = pack.cur[mkey];
  const nul = raw == null || !isFinite(Number(raw));
  let shown = null, broke = false;
  try { shown = nul ? '—' : idFmt(raw, meta.fmt); } catch (e) { broke = true; }
  const spark = pack.points.map(p => Number(p.m[mkey]) || 0);
  if (broke) return (
    <div className="i-kpi" data-err="1" style={{ '--kc': '#DC2626' }}>
      <div className="i-kpi-k">{meta.label}</div>
      <div className="i-kpi-err">Unable to calculate</div>
      <button className="i-kpi-retry" onClick={() => setErr(e => e + 1)}>Retry</button>
    </div>
  );
  const open = () => { if (!nul || pack.rows.length) onOpen(mkey); };
  return (
    <div className="i-kpi" role="button" tabIndex={0}
      aria-label={'Open ' + meta.label + ' detailed analysis — ' + shown}
      style={{ '--kc': color, animation: 'iRow .5s ' + (index * 45) + 'ms cubic-bezier(.16,1,.3,1) both' }}
      onClick={open} onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar') { e.preventDefault(); open(); } }}
      title={'Open ' + meta.label + ' — calculation, contributors and source records'}>
      <div className="i-kpi-k">{meta.label}</div>
      <div className="i-kpi-v" style={nul ? { color: 'var(--iInk3)' } : null}>{shown}</div>
      <div className="i-kpi-ft">
        <IDDelta cur={Number(raw) || 0} prev={pack.prev ? Number(pack.prev[mkey]) || 0 : null} good={meta.good} />
        <window.IC.Spark values={spark} color={color} width={62} height={22} area />
      </div>
      {IDK_ARROW}
    </div>
  );
}

/* one sheet host per workspace — used by the rail, the subgroups and the
   comparison table, so there is a single drill-down implementation */
function IDKpiSheet({ pack, mkey, onClose, onMetric }) {
  const app = React.useContext(window.AppCtx) || {};
  if (!window.KpiSheetGuard || !window.KpiIntel) return null;
  return (
    <window.KpiSheetGuard metricKey={mkey} build={() => window.KpiIntel.model(mkey, pack)}
      onClose={onClose} onMetric={onMetric}
      onNavigate={page => { onClose(); app.navigate && app.navigate(page); }} />
  );
}

function IDKpis({ pack, onPick }) {
  const dom = pack.dom;
  const [drill, setDrill] = idSt(null);
  return (
    <div className="i-kpis">
      {dom.kpis.map((k, i) => {
        const meta = dom.mdefs[k]; if (!meta) return null;
        const kc = i === 0 ? dom.color : meta.good === 'down' ? '#2563EB' : meta.good === 'flat' ? '#6B7068' : '#16A34A';
        return <IDKpiCard key={k} pack={pack} mkey={k} meta={meta} color={kc} index={i} onOpen={setDrill} />;
      })}
      {drill && <IDKpiSheet pack={pack} mkey={drill} onClose={() => setDrill(null)} onMetric={setDrill} />}
    </div>
  );
}

/* ── declared metric subgroup ──────────────────────────────────────────────
   Diesel Recovery inside Financial Intelligence. The spec lives on the domain;
   this reads only pack.cur / pack.prev / pack.points, so it re-derives with the
   period, the company, the cross-filters and every ERP write exactly like the
   rest of the workspace. A metric the records cannot support renders as an em
   dash with the reason — never as a fabricated 100%.                        */
function IDSubgroup({ pack, spec, onDrill }) {
  const count = Number(pack.cur[spec.countKey]) || 0;
  const recoverable = Number(pack.cur.dieselRecoverable) || 0;
  const [kpiDrill, setKpiDrill] = idSt(null);
  const rows = pack.points.map(p => {
    const o = { label: p.label };
    spec.trend.forEach(k => { o[k] = Number(p.m[k]) || 0; });
    return o;
  });
  /* series visibility is a view flag only — the metrics behind a hidden
     series stay in the pack and in every calculation. */
  const [hidden, setHidden] = idSt({});
  /* the selected period is remembered by its own date range, not by index, so
     a filter change, a re-bucketing or an ERP write can never point it at a
     different period; if the range disappears the selection simply lapses. */
  const [selKey, setSelKey] = idSt(null);
  const [sheet, setSheet] = idSt(false);
  const [wholeSheet, setWholeSheet] = idSt(false);
  const keys = spec.trend.map((k, i) => ({
    key: k, label: (spec.metrics.find(m => m.key === k) || {}).label || k,
    color: spec.series[i % spec.series.length], off: !!hidden[k],
  }));
  const pkey = p => p.period.from + '|' + p.period.to;
  const selIdx = selKey ? pack.points.findIndex(p => pkey(p) === selKey) : -1;
  const DR = !!(window.DRTrend && window.DRPeriodSheet);
  const pick = i => { const p = pack.points[i]; if (!p) return; setSelKey(pkey(p)); setSheet(true); };
  const nav = d => { const i = selIdx + d; if (i >= 0 && i < pack.points.length) setSelKey(pkey(pack.points[i])); };
  idEf(() => { if (selIdx < 0 && sheet) setSheet(false); }, [selIdx, sheet]);
  return (
    <window.ISection title={spec.title} anchor={'i-dom-' + spec.id} icon={spec.icon} sub={spec.sub}
      right={<span className="i-card-n">{count} diesel record{count === 1 ? '' : 's'}</span>}>
      {!count ? (
        <div className="i-card">
          <window.IC.Empty msg={spec.empty.msg} h={140} />
          <div style={{ textAlign: 'center', fontSize: 11.5, color: 'var(--txt3)', padding: '0 16px 14px', maxWidth: 520, margin: '0 auto', textWrap: 'pretty' }}>{spec.empty.hint}</div>
        </div>
      ) : (
        <div className="dr-stack">
          <div className="i-kpis">
            {spec.metrics.map((m, i) => {
              const raw = pack.cur[m.key];
              const nul = raw == null || !isFinite(Number(raw));
              const pv = pack.prev ? pack.prev[m.key] : null;
              const vals = pack.points.map(p => p.m[m.key]);
              const sparkOk = !nul && vals.every(v => v != null && isFinite(Number(v)));
              const kc = spec.series[i] || spec.color;
              return (
                <div className="i-kpi" key={m.key} role="button" tabIndex={0}
                  aria-label={'Open ' + m.label + ' detailed analysis'}
                  style={{ '--kc': kc, animation: 'iRow .5s ' + (i * 45) + 'ms cubic-bezier(.16,1,.3,1) both' }}
                  onClick={() => setKpiDrill(m.key)}
                  onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setKpiDrill(m.key); } }}
                  title={'Open ' + m.label + ' — calculation, contributors and source records'}>
                  <div className="i-kpi-k">{m.label}</div>
                  <div className="i-kpi-v" style={nul ? { color: 'var(--txt3)' } : null}>{nul ? '—' : idFmt(raw, m.fmt)}</div>
                  <div className="i-kpi-ft">
                    {nul
                      ? <span className="i-kpi-cmp">{m.nullNote || 'Not measurable'}</span>
                      : (pv == null || !isFinite(Number(pv)))
                        ? <span className="i-kpi-cmp">{m.note}</span>
                        : <IDDelta cur={Number(raw)} prev={Number(pv)} good={m.good} />}
                    {sparkOk && <window.IC.Spark values={vals.map(Number)} color={kc} width={62} height={22} area />}
                  </div>
                  {IDK_ARROW}
                </div>
              );
            })}
          </div>
          <div className="dr-viz">
            <div className="dr-band">
              <div className="dr-band-t"><i style={{ background: spec.color }}></i>Recovery analytics
                <em>{DR ? 'Click any period on the trend for its full breakdown' : ''}</em></div>
              {DR && selIdx >= 0 && (
                <div className="dr-selchip">
                  <button onClick={() => setSheet(true)} title="Reopen this period's breakdown">
                    <i style={{ background: spec.color }}></i>{window.drTitle(pack.points[selIdx], pack.grain)}<span>selected</span></button>
                  <button className="x" onClick={() => { setSelKey(null); setSheet(false); }} title="Clear selection" aria-label="Clear selected period">×</button>
                </div>
              )}
            </div>
            <div className="i-grid i-g23">
            <div className="i-card hov">
              <div className="i-card-hd">
                <div><div className="i-card-t">Recovery trend</div><div className="i-card-s">Cost, recoverable, recovered and unrecovered per {pack.grain} · {pack.points.length} point{pack.points.length === 1 ? '' : 's'}</div></div>
                <div className="dr-legend">
                  {keys.map(k => (
                    <button key={k.key} className={'dr-leg' + (k.off ? ' off' : '')} aria-pressed={!k.off}
                      onClick={() => setHidden(h => Object.assign({}, h, { [k.key]: !h[k.key] }))}
                      title={(k.off ? 'Show ' : 'Hide ') + k.label + ' — visibility only, the calculation is untouched'}>
                      <i style={{ background: k.color }}></i>{k.label}</button>
                  ))}
                </div>
              </div>
              {DR
                ? <window.DRTrend points={pack.points} keys={keys} height={268} accent={spec.color}
                  grain={pack.grain} selIdx={selIdx >= 0 ? selIdx : null} onSelect={pick} />
                : <window.IC.Bars height={268} yKind="short" rows={rows} keys={keys.filter(k => !k.off)} onClick={() => onDrill && onDrill()} />}
            </div>
            <div className="i-card hov">
              <div className="i-card-hd"><div><div className="i-card-t">Recoverable base</div>
                <div className="i-card-s">{recoverable > 0 ? 'Settled against still open' : 'Nothing raised against the diesel in scope'}</div></div></div>
              {recoverable > 0 ? (
                <div className="dr-donut-wrap">
                  <window.IC.Donut size={196} thickness={26} valueKind="cur" onClick={() => setWholeSheet(true)}
                    slices={[{ label: 'Recovered', value: Number(pack.cur.dieselRecovered) || 0, color: '#16A34A' },
                    { label: 'Unrecovered', value: Number(pack.cur.dieselUnrecovered) || 0, color: '#DC2626' }]}
                    center={window.IC.short(recoverable)} sub="RECOVERABLE" />
                </div>
              ) : (
                <React.Fragment>
                  <window.IC.Empty msg="No deduction raised against this diesel" h={150} />
                  <div style={{ textAlign: 'center', fontSize: 11.5, color: 'var(--txt3)', padding: '0 16px 12px', textWrap: 'pretty' }}>
                    Recovery cannot be measured without a recoverable base. Allocate these entries to a transporter or vendor settlement and the ratio becomes real.
                  </div>
                </React.Fragment>
              )}
            </div>
            </div>
          </div>
          {DR && sheet && selIdx >= 0 && (
            <window.DRPeriodSheet pack={pack} spec={spec} idx={selIdx} grain={pack.grain}
              onClose={() => setSheet(false)} onNav={nav} onDrill={onDrill} />
          )}
          {DR && wholeSheet && (
            <window.DRPeriodSheet pack={pack} spec={spec} idx={null} grain={pack.grain} wholePeriod
              overridePt={{ period: pack.period, label: pack.period.label, sub: pack.period.label, m: pack.cur, rows: pack.rows }}
              onClose={() => setWholeSheet(false)} onNav={() => {}} onDrill={onDrill} />
          )}
          {kpiDrill && <IDKpiSheet pack={pack} mkey={kpiDrill} onClose={() => setKpiDrill(null)} onMetric={setKpiDrill} />}
        </div>
      )}
    </window.ISection>
  );
}

/* ── trend ─────────────────────────────────────────────────────────────── */
const ID_GRAINS = [['auto', 'Auto'], ['day', 'Daily'], ['week', 'Weekly'], ['cycle', 'Cycle'], ['month', 'Monthly'], ['quarter', 'Quarterly']];
function IDTrend({ pack, grain, setGrain, onPoint, onFocus }) {
  const dom = pack.dom;
  const [metric, setMetric] = idSt(dom.trend[0]);
  const [wfDrill, setWfDrill] = idSt(null);
  idEf(() => { setMetric(dom.trend[0]); }, [dom.id]);
  const meta = dom.mdefs[metric] || dom.mdefs[dom.trend[0]];
  const series = [{ key: 'cur', label: pack.period.label, color: dom.color, values: pack.points.map(p => Number(p.m[metric]) || 0) }];
  const main = pack.splits[0];
  return (
    <window.ISection title="Business Performance" anchor={'i-dom-perf'} icon="wave"
      sub={'How ' + dom.label.toLowerCase() + ' moved across ' + pack.period.label.toLowerCase() + '. Click any point to open that window; every bucket is re-derived from the live ERP, not cached.'}
      right={<div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>{ID_GRAINS.map(g => (
        <button key={g[0]} className={'i-ctl sm' + (grain === g[0] ? ' on' : '')} onClick={() => setGrain(g[0])}>{g[1]}</button>))}</div>}>
      <div className="i-grid i-g23">
        <div className="i-card hov">
          <div className="i-card-hd">
            <div><div className="i-card-t" style={{ whiteSpace: 'nowrap' }}>{meta.label}</div><div className="i-card-s">Over time · {pack.grain} buckets · {pack.points.length} points</div></div>
            <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>{dom.trend.map(o => (
              <button key={o} className={'i-ctl sm' + (metric === o ? ' on' : '')} onClick={() => setMetric(o)}>{dom.mdefs[o].label}</button>))}</div>
          </div>
          <window.IC.Area height={272} yKind={meta.fmt === 'days' || meta.fmt === 'ltr' || meta.fmt === 'num' ? 'int' : meta.fmt}
            labels={pack.points.map(p => p.label)} subLabels={pack.points.map(p => p.sub)} series={series}
            onPointClick={i => onPoint && onPoint(pack.points[i])} />
        </div>
        <div className="i-card hov">
          <div className="i-card-hd"><div><div className="i-card-t">{dom.waterfall ? 'Profit waterfall' : main ? main.title : 'Distribution'}</div>
            <div className="i-card-s">{dom.waterfall ? 'Single source of truth — Profit Engine' : main ? main.sub : ''}</div></div></div>
          {dom.waterfall
            ? <window.IC.Waterfall height={272} steps={[
              { label: 'Revenue', short: 'Revenue', value: pack.cur.revenue, color: '#16A34A' },
              { label: 'Purchase', short: 'Purchase', value: -pack.cur.purchaseValue, color: '#DC2626' },
              { label: 'Transport', short: 'Transport', value: -pack.cur.transportCost, color: '#DC2626' },
              { label: 'Diesel Margin', short: 'Diesel', value: pack.cur.dieselMargin, color: '#F97316' },
              { label: 'Net Profit', short: 'Net', value: pack.cur.netProfit, color: pack.cur.netProfit >= 0 ? '#15803D' : '#DC2626', total: true },
            ]} onClick={b => setWfDrill(b.label === 'Diesel Margin' ? { kind: 'diesel' } : { kind: 'finance', step: b })} />
            : main ? <window.IC.Rank limit={8} valueKind={main.valueKind}
              rows={main.rows.slice(0, 8).map(r => ({ label: r.label, value: Math.abs(r.value), key: r.key, sub: r.count + ' record' + (r.count === 1 ? '' : 's') }))}
              onClick={r => onFocus && onFocus({ splitId: main.id, key: r.key, label: r.label })} /> : null}
        </div>
      </div>
      {wfDrill && wfDrill.kind === 'diesel' && (
        <window.DRPeriodSheet pack={pack} spec={{ color: '#F97316' }} idx={null} grain={pack.grain} wholePeriod
          overridePt={{ period: pack.period, label: pack.period.label, sub: pack.period.label, m: pack.cur, rows: pack.rows }}
          onClose={() => setWfDrill(null)} onNav={() => {}}
          onDrill={() => { onFocus && onFocus({ splitId: 'head', key: 'Diesel', label: 'Diesel' }); }} />
      )}
      {wfDrill && wfDrill.kind === 'finance' && (
        <window.AFinanceDrill {...window.buildWaterfallDrill(wfDrill.step, pack)} onClose={() => setWfDrill(null)} />
      )}
    </window.ISection>
  );
}

/* ── comparison engine ─────────────────────────────────────────────────── */
function IDCompare({ pack }) {
  const dom = pack.dom;
  const keys = Object.keys(dom.mdefs);
  const [drill, setDrill] = idSt(null);
  if (!pack.prev) return (
    <window.ISection title="Comparison Engine" anchor="i-dom-cmp" icon="compare"
      sub="Pick a comparison window in the control bar to unlock a full side-by-side breakdown of every metric in this workspace.">
      <div className="i-card"><window.IC.Empty msg="No comparison period selected" h={120} /></div>
    </window.ISection>
  );
  return (
    <window.ISection title="Comparison Engine" anchor="i-dom-cmp" icon="compare"
      sub={pack.period.label + ' versus ' + pack.cmpPeriod.label + ' — every metric this workspace computes, aligned date-for-date. Click any row to trace it back to its records.'}>
      <div className="i-card" style={{ overflowX: 'auto' }}>
        <table className="i-cmp">
          <thead><tr><th>Metric</th><th>{pack.period.label}</th><th>{pack.cmpPeriod.label}</th><th>Share</th><th>Change</th></tr></thead>
          <tbody>
            {keys.map(k => {
              const meta = dom.mdefs[k];
              const a = Number(pack.cur[k]) || 0, b = Number(pack.prev[k]) || 0;
              const top = Math.max(Math.abs(a), Math.abs(b)) || 1;
              return (
                <tr key={k} className="kx-clickrow" tabIndex={0} onClick={() => setDrill(k)}
                  onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); setDrill(k); } }}
                  title={'Open ' + meta.label + ' detailed analysis'}>
                  <td>{meta.label}</td>
                  <td>{idFmt(a, meta.fmt)}</td>
                  <td style={{ color: 'var(--txt2)', fontWeight: 600 }}>{idFmt(b, meta.fmt)}</td>
                  <td><div className="i-cmp-bar"><i style={{ width: (Math.abs(a) / top * 100) + '%', background: dom.color }}></i></div></td>
                  <td><IDDelta cur={a} prev={b} good={meta.good} /></td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
      {drill && <IDKpiSheet pack={pack} mkey={drill} onClose={() => setDrill(null)} onMetric={setDrill} />}
    </window.ISection>
  );
}

/* ── distribution / contributors ───────────────────────────────────────── */
function IDSplits({ pack, focus, onFocus }) {
  const dom = pack.dom;
  const kindOf = s => s.valueKind === 'ton' ? 'ton' : s.valueKind === 'int' ? 'int' : 'short';
  return (
    <window.ISection title="Contribution & Distribution" anchor="i-dom-split" icon="layers"
      sub="Every visual is a filter: click a bar, slice or block to pin the records behind it in the ledger below."
      right={focus ? <button className="i-chip-clear" onClick={() => onFocus(null)}>Clear selection</button> : null}>
      <div className="i-grid i-g2">
        {pack.splits.map(s => {
          const rows = s.rows.filter(r => Math.abs(r.value) > 0 || r.count > 0);
          const pick = r => onFocus({ splitId: s.id, key: r.key, label: r.label });
          const on = focus && focus.splitId === s.id;
          return (
            <div className={'i-card hov' + (on ? ' i-card-on' : '')} key={s.id}>
              <div className="i-card-hd">
                <div><div className="i-card-t">{s.title}</div><div className="i-card-s">{s.sub}</div></div>
                <span className="i-card-n">{rows.length}</span>
              </div>
              {!rows.length ? <window.IC.Empty msg="No records in this window" h={140} /> :
                s.kind === 'donut' ? (
                  <div style={{ display: 'flex', justifyContent: 'center', padding: '4px 0 2px' }}>
                    <window.IC.Donut slices={rows.slice(0, 7).map(r => ({ label: r.label, value: Math.abs(r.value) }))}
                      size={196} valueKind={kindOf(s)} onClick={r => { const m = rows.find(x => x.label === r.label); if (m) pick(m); }}
                      center={idFmt(rows.reduce((a, r) => a + Math.abs(r.value), 0), kindOf(s) === 'ton' ? 'ton' : kindOf(s) === 'int' ? 'int' : 'short')} sub={s.title} />
                  </div>
                ) : s.kind === 'tree' ? (
                  <window.IC.Tree rows={rows.slice(0, 12).map(r => ({ label: r.label, value: Math.abs(r.value) }))} height={230}
                    valueKind={kindOf(s)} onClick={r => { const m = rows.find(x => x.label === r.label); if (m) pick(m); }} />
                ) : s.kind === 'bars' ? (
                  <window.IC.Bars height={230} yKind={kindOf(s)} rows={rows.slice(0, 12).map(r => ({ label: r.label, v: Math.abs(r.value) }))}
                    keys={[{ key: 'v', label: s.title, color: dom.color }]}
                    onClick={r => { const m = rows.find(x => x.label === r.label); if (m) pick(m); }} />
                ) : (
                  <window.IC.Rank rows={rows.slice(0, 9).map(r => ({ label: r.label, value: Math.abs(r.value), key: r.key }))} limit={9}
                    valueKind={kindOf(s)} onClick={r => { const m = rows.find(x => x.key === r.key); if (m) pick(m); }} />
                )}
            </div>
          );
        })}
      </div>
    </window.ISection>
  );
}

/* ── aging + heatmap + forecast ────────────────────────────────────────── */
function IDRhythm({ pack, onFocus }) {
  const dom = pack.dom;
  const fcMeta = dom.mdefs[dom.fcKey] || { label: dom.fcKey, fmt: 'cur' };
  const hist = pack.points.map(p => Number(p.m[dom.fcKey]) || 0);
  const fcSeries = [
    { key: 'h', label: 'Actual', color: dom.color, values: hist.concat(pack.fc.points.map(() => null)) },
    { key: 'f', label: 'Forecast', color: '#A8A4A0', dashed: true, values: hist.map((v, i) => i === hist.length - 1 ? v : null).concat(pack.fc.points.map(p => p.value)) },
  ];
  const fcLabels = pack.points.map(p => p.label).concat(pack.fc.points.map((p, i) => '+' + (i + 1)));
  return (
    <window.ISection title="Rhythm, Aging & Forecast" anchor="i-dom-rhythm" icon="calendarBars"
      sub="Where the activity clusters, how long the open items have been open, and what the same pattern projects forward.">
      <div className="i-grid i-g2">
        {pack.aging && (
          <div className="i-card hov">
            <div className="i-card-hd"><div><div className="i-card-t">{pack.aging.title}</div><div className="i-card-s">{pack.aging.sub}</div></div></div>
            <window.IC.Bars height={220} yKind="short"
              rows={pack.aging.rows.map(r => ({ label: r.label, v: r.value }))}
              keys={[{ key: 'v', label: 'Open value', color: dom.color }]}
              onClick={r => onFocus({ splitId: '__age', key: r.label, label: r.label })} />
            <div className="i-age-legend">
              {pack.aging.rows.map(r => (
                <span key={r.id}><b>{r.count}</b>{r.label} · {idFmt(r.value, 'short')}</span>
              ))}
            </div>
          </div>
        )}
        <div className="i-card hov">
          <div className="i-card-hd"><div><div className="i-card-t">Activity heatmap</div><div className="i-card-s">Daily density across {pack.period.label.toLowerCase()}</div></div></div>
          {pack.heat.length ? <window.IC.Heat cells={pack.heat} valueKind="short" /> : <window.IC.Empty msg="No dated activity in this window" h={160} />}
        </div>
        <div className="i-card hov" style={pack.aging ? null : { gridColumn: '1 / -1' }}>
          <div className="i-card-hd">
            <div><div className="i-card-t">{fcMeta.label} forecast</div><div className="i-card-s">{pack.fc.method}</div></div>
            <span className="i-card-n">{pack.fc.confidence}% confidence</span>
          </div>
          <window.IC.Area height={230} yKind={fcMeta.fmt === 'days' || fcMeta.fmt === 'ltr' ? 'int' : fcMeta.fmt} labels={fcLabels} series={fcSeries} />
          <div className="i-fc-rows">
            {pack.fc.points.map((p, i) => (
              <div className="i-fc-row" key={i}>
                <span>Next {pack.grain} +{i + 1}</span>
                <b>{idFmt(p.value, fcMeta.fmt)}</b>
                <em>{idFmt(p.lo, fcMeta.fmt)} – {idFmt(p.hi, fcMeta.fmt)}</em>
              </div>
            ))}
          </div>
        </div>
      </div>
    </window.ISection>
  );
}

/* ── AI insight engine ─────────────────────────────────────────────────── */
function IDInsights({ pack }) {
  const [open, setOpen] = idSt(null);
  return (
    <window.ISection title="Intelligence" anchor="i-dom-ins" icon="brain"
      sub={'Generated live from the ' + pack.rows.length + ' record' + (pack.rows.length === 1 ? '' : 's') + ' in scope — every sentence below is recomputed when the underlying ERP data changes.'}>
      <div className="i-grid i-g3">
        {pack.insights.map((ins, i) => {
          const c = ID_KIND[ins.kind] || ID_SEV[ins.severity];
          const isOpen = open === i;
          const reasons = ins.reasons || [];
          return (
            <div className="i-ins" key={i} style={{ '--ic': c, animation: 'iRow .5s ' + (i * 40) + 'ms cubic-bezier(.16,1,.3,1) both' }}
              onClick={() => setOpen(isOpen ? null : i)}>
              <span className="i-ins-k">{ins.kind}</span>
              <div className="i-ins-t">{ins.title}</div>
              <div className="i-ins-d">{ins.detail}</div>
              {reasons.length > 0 && <ul className="i-ins-r">{(isOpen ? reasons : reasons.slice(0, 2)).map((r, j) => <li key={j}>{r}</li>)}</ul>}
              {reasons.length > 2 && <div className="i-ins-more">{isOpen ? 'Show less' : 'Show all ' + reasons.length + ' reasons →'}</div>}
            </div>
          );
        })}
      </div>
    </window.ISection>
  );
}

/* ── drill-down ledger ─────────────────────────────────────────────────── */
const ID_PAGE = 40;
function IDLedger({ pack, focus, onFocus }) {
  const dom = pack.dom;
  const [q, setQ] = idSt('');
  const [sort, setSort] = idSt({ k: 'date', dir: -1 });
  const [page, setPage] = idSt(1);
  const [openRow, setOpenRow] = idSt(null);
  idEf(() => { setPage(1); }, [focus, q, pack.id, pack.period.from, pack.period.to]);

  const rows = idMemo(() => {
    let src = pack.rows;
    if (focus) {
      if (focus.splitId === '__age') {
        const b = (pack.aging ? pack.aging.rows.find(r => r.label === focus.key) : null);
        src = b ? b.recs : src;
      } else {
        const s = pack.splits.find(x => x.id === focus.splitId);
        const g = s && s.rows.find(r => r.key === focus.key);
        src = g ? g.recs : src;
      }
    }
    const shaped = src.map(r => ({ _r: r, v: dom.table.row(r) }));
    const s = q.trim().toLowerCase();
    const filt = s ? shaped.filter(x => dom.table.cols.some(c => String(x.v[c[0]] == null ? '' : x.v[c[0]]).toLowerCase().indexOf(s) >= 0)) : shaped;
    const k = sort.k;
    return filt.sort((a, b) => {
      const av = a.v[k], bv = b.v[k];
      if (typeof av === 'number' && typeof bv === 'number') return (av - bv) * sort.dir;
      return String(av == null ? '' : av).localeCompare(String(bv == null ? '' : bv)) * sort.dir;
    });
  }, [pack, focus, q, sort]);

  const total = rows.length;
  const shown = rows.slice(0, page * ID_PAGE);
  const sums = idMemo(() => {
    const o = {};
    dom.table.cols.forEach(c => { if (['cur', 'ton', 'ltr', 'num'].indexOf(c[2]) >= 0) o[c[0]] = rows.reduce((s, x) => s + (Number(x.v[c[0]]) || 0), 0); });
    return o;
  }, [rows]);

  function exportCsv() {
    const head = dom.table.cols.map(c => c[1]);
    const esc = v => '"' + String(v == null ? '' : v).replace(/"/g, '""') + '"';
    const body = rows.map(x => dom.table.cols.map(c => esc(x.v[c[0]])).join(','));
    const txt = [head.map(esc).join(',')].concat(body).join('\n');
    const a = document.createElement('a');
    a.href = URL.createObjectURL(new Blob(['\ufeff' + txt], { type: 'text/csv;charset=utf-8' }));
    a.download = 'OM-Analytics-' + dom.id + '-' + pack.period.from + '_' + pack.period.to + '.csv';
    document.body.appendChild(a); a.click(); setTimeout(() => { URL.revokeObjectURL(a.href); a.remove(); }, 400);
    window.toast && window.toast('Ledger exported', 'ok');
  }

  return (
    <window.ISection title="Drill-Down Ledger" anchor="i-dom-ledger" icon="doc"
      sub={'Every record behind every number on this page. ' + total + ' row' + (total === 1 ? '' : 's') + ' in scope' + (focus ? ' · filtered to ' + focus.label : '') + '.'}
      right={<div className="i-ldg-tools">
        {focus && <span className="i-chip"><span>Selection</span><b>{focus.label}</b><button onClick={() => onFocus(null)} aria-label="Clear">×</button></span>}
        <input className="i-ldg-search" value={q} placeholder="Search this ledger…" onChange={e => setQ(e.target.value)} />
        <button className="i-ctl sm" onClick={exportCsv}>Export CSV</button>
      </div>}>
      <div className="i-card" style={{ padding: 0, overflow: 'hidden' }}>
        <div className="i-ldg-wrap">
          <table className="i-ldg">
            <thead><tr>
              <th style={{ width: 22 }}></th>
              {dom.table.cols.map(c => (
                <th key={c[0]} className={['cur', 'ton', 'ltr', 'num', 'rate', 'days'].indexOf(c[2]) >= 0 ? 'r' : ''}
                  onClick={() => setSort(s => ({ k: c[0], dir: s.k === c[0] ? -s.dir : -1 }))}>
                  {c[1]}{sort.k === c[0] ? <em>{sort.dir < 0 ? ' ↓' : ' ↑'}</em> : null}
                </th>
              ))}
            </tr></thead>
            <tbody>
              {!shown.length && <tr><td colSpan={dom.table.cols.length + 1} className="i-ldg-empty">No records match the current period, filters and search.</td></tr>}
              {shown.map((x, i) => {
                const isOpen = openRow === x._r.id;
                return (
                  <React.Fragment key={x._r.id || i}>
                    <tr className={isOpen ? 'on' : ''} onClick={() => setOpenRow(isOpen ? null : x._r.id)}>
                      <td className="i-ldg-caret"><span style={{ transform: isOpen ? 'rotate(90deg)' : 'none' }}>▸</span></td>
                      {dom.table.cols.map(c => {
                        const v = x.v[c[0]];
                        const numeric = ['cur', 'ton', 'ltr', 'num', 'rate', 'days'].indexOf(c[2]) >= 0;
                        return (
                          <td key={c[0]} className={numeric ? 'r' : ''}>
                            {c[2] === 'badge' ? <span className="i-ldg-badge">{v || '—'}</span>
                              : c[2] === 'date' ? (v ? window.IntelEngine.util.fmtD(v) : '—')
                                : numeric ? idFmt(v, c[2]) : (v == null || v === '' ? '—' : String(v))}
                          </td>
                        );
                      })}
                    </tr>
                    {isOpen && (
                      <tr className="i-ldg-detail"><td colSpan={dom.table.cols.length + 1}>
                        <div className="i-ldg-kv">
                          {Object.keys(x._r.raw || {}).filter(k => k[0] !== '_' && typeof x._r.raw[k] !== 'object').slice(0, 24).map(k => (
                            <div key={k}><span>{k.replace(/([A-Z])/g, ' $1').replace(/^./, m => m.toUpperCase())}</span><b>{String(x._r.raw[k] === '' ? '—' : x._r.raw[k])}</b></div>
                          ))}
                        </div>
                      </td></tr>
                    )}
                  </React.Fragment>
                );
              })}
            </tbody>
            {total > 0 && (
              <tfoot><tr>
                <td></td>
                {dom.table.cols.map((c, i) => (
                  <td key={c[0]} className={['cur', 'ton', 'ltr', 'num', 'rate', 'days'].indexOf(c[2]) >= 0 ? 'r' : ''}>
                    {i === 0 ? total + ' records' : (sums[c[0]] != null ? idFmt(sums[c[0]], c[2]) : '')}
                  </td>
                ))}
              </tr></tfoot>
            )}
          </table>
        </div>
        {shown.length < total && (
          <button className="i-ldg-more" onClick={() => setPage(p => p + 1)}>Show {Math.min(ID_PAGE, total - shown.length)} more · {total - shown.length} remaining</button>
        )}
      </div>
    </window.ISection>
  );
}

/* ── the workspace ─────────────────────────────────────────────────────── */
function IDomainWorkspace({ id, companyId, period, cmpPeriod, cf, grain, setGrain, setPeriod, ver }) {
  const [focus, setFocus] = idSt(null);
  idEf(() => { setFocus(null); }, [id, period.from, period.to, JSON.stringify(cf)]);

  const pack = idMemo(() => {
    try { return IDx().build(id, companyId, period, cmpPeriod, cf, grain); }
    catch (e) { console.error('[Analytics] domain build failed:', id, e); return null; }
  }, [id, companyId, period.from, period.to, cmpPeriod && cmpPeriod.from, cmpPeriod && cmpPeriod.to, JSON.stringify(cf), grain, ver]);

  if (!pack) return (
    <div className="i-card" style={{ marginTop: 16 }}>
      <window.IC.Empty msg="This workspace could not be derived from the current data" h={180} />
    </div>
  );
  const dom = pack.dom;
  const scrollLedger = () => {
    const el = document.getElementById('i-dom-ledger'), box = document.querySelector('.pg-body');
    if (el && box) box.scrollTo({ top: Math.max(0, el.offsetTop - 96), behavior: 'smooth' });
  };
  return (
    <div className="i-dom" id={'i-dom-panel-' + id} role="tabpanel" aria-labelledby={'i-dom-tab-' + id} tabIndex={-1} style={{ '--dAccent': dom.color }}>
      <div className="i-dom-head">
        <div className="i-dom-head-t">
          <div className="i-eyebrow"><i></i>{dom.eyebrow}</div>
          <div className="i-dom-title">{dom.title}</div>
          <div className="i-dom-sub">{dom.sub}</div>
        </div>
        <div className="i-dom-sum">{pack.summary}</div>
      </div>
      <IDKpis pack={pack} onPick={scrollLedger} />
      {(dom.subgroups || []).map(sg => (
        <IDSubgroup key={sg.id} pack={pack} spec={sg} onDrill={() => { setFocus(sg.focus); scrollLedger(); }} />
      ))}
      <IDTrend pack={pack} grain={grain} setGrain={setGrain} onPoint={p => setPeriod && setPeriod(p.period)} onFocus={setFocus} />
      <IDInsights pack={pack} />
      <IDSplits pack={pack} focus={focus} onFocus={setFocus} />
      <IDRhythm pack={pack} onFocus={setFocus} />
      <IDCompare pack={pack} />
      <IDLedger pack={pack} focus={focus} onFocus={setFocus} />
    </div>
  );
}

Object.assign(window, { IDomainWorkspace, IDomainRail, IDSubgroup, IDKpis, IDKpiCard, IDKpiSheet, IDTrend, IDSplits, IDRhythm, IDInsights, IDLedger, IDDelta, idFmt });
