/* ============================================================
   Pipeline Overview — core: shared compute + UI atoms.
   Depends on window.PL (data), Icon, df_pcharts (Sparkline,
   RankedBars, StackedTimeBars, Quadrant, AgingHeatmap), shared
   (Segmented, MultiSelect, useOutside).  useState/useRef/useEffect
   come in as globals from shared.jsx.
   ============================================================ */

const PC = { accent: '#3e6e8c', accent2: '#9db4c0', addon: '#9db4c0', ink: '#1a1c1e', muted: '#6e7479',
  soft: '#9aa0a5', grid: '#e7e8e9', line: '#d9dada', pos: '#3f7d58', warn: '#b07a2e', danger: '#a4453c', ghost: '#cfd2d4' };

/* ---------- period / compare helpers ---------- */
const plYearLabel = (y) => y === PL.CUR_YEAR ? y + ' YTD' : '' + y;
const plPct = (cur, prev) => prev ? Math.round((cur - prev) / prev * 100) : null;
const plPct1 = (cur, prev) => prev ? Math.round((cur - prev) / prev * 1000) / 10 : null;

function plPrevComparable(set, year) {
  if (year === PL.CUR_YEAR) { const mm = PL.monthsOf(PL.CUR_YEAR).map((m) => m.slice(5)); return set.filter((d) => d.year === year - 1 && mm.includes(d.month.slice(5))); }
  return set.filter((d) => d.year === year - 1);
}
const plReach = (set, rank) => set.filter((d) => d.stageRank >= rank);

/* measure-aware reducer: val(set) → number */
function makeVal(measure) {
  if (measure === 'ebitda') return (set) => set.reduce((s, d) => s + (d.ebitda || 0), 0);
  if (measure === 'revenue') return (set) => set.reduce((s, d) => s + (d.revenue || 0), 0);
  return (set) => set.length;
}
function makeFmt(measure) {
  if (measure === 'count') return (v) => Math.round(v).toLocaleString('en-US');
  return (v) => v >= 100 ? '€' + Math.round(v).toLocaleString('en-US') + 'm' : '€' + (Math.round(v * 10) / 10) + 'm';
}
const MEASURE_LABEL = { count: 'deals', ebitda: '€ EBITDA', revenue: '€ revenue' };

/* funnel reach (measure-aware) */
function plFunnel(set, val) {
  return PL.STAGES.map((s, i) => ({ key: s.key, label: s.label, short: s.short, n: val(plReach(set, i)), count: plReach(set, i).length, set: plReach(set, i) }));
}
/* scorecard (measure-aware) */
function plScore(set, val) {
  return {
    leads: val(set), appt: val(plReach(set, 1)), meet: val(set.filter((d) => d.firstMeeting)),
    qual: val(plReach(set, 2)), analysis: val(plReach(set, 3)), nbo: val(plReach(set, 4)),
    passed: val(set.filter((d) => d.terminal === 'Passed')), onhold: val(set.filter((d) => d.terminal === 'On Hold')),
    lost: val(set.filter((d) => d.terminal === 'Lost')), port: val(plReach(set, 8)),
  };
}
const PL_METRICS = [
  { key: 'leads', label: 'Leads added' }, { key: 'meet', label: 'First meetings' },
  { key: 'appt', label: 'Appointments' }, { key: 'qual', label: 'Qualified' },
  { key: 'analysis', label: 'Analysis' }, { key: 'nbo', label: 'NBO+' },
  { key: 'passed', label: 'Deals passed' }, { key: 'onhold', label: 'Deals on hold' },
  { key: 'lost', label: 'Deals lost' }, { key: 'port', label: 'Portfolio' },
];

/* rolling-LTM leads (count) over the full timeline */
function plRollingLTM(set) {
  const map = {}; PL.allMonths.forEach((m) => map[m] = 0); set.forEach((d) => map[d.month]++);
  const lbm = PL.allMonths.map((m) => ({ month: m, leads: map[m] }));
  const out = lbm.map((d, i) => { const w = lbm.slice(Math.max(0, i - 11), i + 1); return { month: d.month, ltm: w.reduce((s, x) => s + x.leads, 0), full: i >= 11 }; });
  out.forEach((d, i) => { d.pct = i > 0 && out[i - 1].ltm ? Math.round((d.ltm - out[i - 1].ltm) / out[i - 1].ltm * 100) : 0; });
  return out.filter((d) => d.full);
}

/* ---------- shared atoms ---------- */
function Delta({ v, unit, size = 11, invert }) {
  if (v == null) return null;
  const up = v >= 0, good = invert ? !up : up;
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 1, fontSize: size, fontWeight: 700, color: good ? PC.pos : PC.danger }}>
      <Icon name={up ? 'arrowUp' : 'arrowDown'} size={size} stroke={2.4} />{Math.abs(v)}{unit || '%'}
    </span>
  );
}
function CardHead({ title, sub, right }) {
  return (
    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 12, gap: 12 }}>
      <div>
        <h3 style={{ fontSize: 13.5, fontWeight: 700 }}>{title}</h3>
        {sub && <div style={{ fontSize: 11.5, color: PC.soft, marginTop: 2, fontWeight: 500 }}>{sub}</div>}
      </div>
      {right}
    </div>
  );
}
function Card({ children, style, pad = '15px 16px' }) {
  return <div className="card" style={{ padding: pad, display: 'flex', flexDirection: 'column', ...style }}>{children}</div>;
}
function SectionBlock({ id, eyebrow, title, sub, right, children }) {
  return (
    <section id={id} data-pl-section={id} style={{ scrollMarginTop: 128, marginTop: 30 }}>
      <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 16, marginBottom: 14, borderLeft: '3px solid var(--accent-line)', paddingLeft: 12 }}>
        <div>
          {eyebrow && <div className="eyebrow" style={{ color: PC.accent, marginBottom: 4 }}>{eyebrow}</div>}
          <h2 style={{ fontSize: 18, fontWeight: 700, letterSpacing: '-0.02em' }}>{title}</h2>
          {sub && <div style={{ fontSize: 12.5, color: PC.muted, marginTop: 3, fontWeight: 500, maxWidth: 680, lineHeight: 1.5 }}>{sub}</div>}
        </div>
        {right}
      </div>
      {children}
    </section>
  );
}
function StageBadge({ rank }) {
  const s = PL.STAGES[rank] || { label: '—' };
  const tone = rank >= 8 ? 'positive' : rank >= 4 ? 'warn' : rank >= 1 ? 'accent' : '';
  return <span className={'badge ' + tone}>{s.label}</span>;
}

/* ---------- rolling-LTM chart ---------- */
function RollChart({ data, height = 190, accent = PC.accent }) {
  const pad = { l: 30, r: 12, t: 16, b: 22 };
  const W = 820, H = height, iw = W - pad.l - pad.r, ih = H - pad.t - pad.b;
  const max = Math.max(...data.map((d) => d.ltm)) * 1.08;
  const xOf = (i) => pad.l + iw * i / (data.length - 1);
  const yOf = (v) => pad.t + ih - v / max * ih;
  const line = data.map((d, i) => `${i ? 'L' : 'M'}${xOf(i).toFixed(1)},${yOf(d.ltm).toFixed(1)}`).join(' ');
  const area = line + ` L${xOf(data.length - 1).toFixed(1)},${pad.t + ih} L${pad.l},${pad.t + ih} Z`;
  const ticks = data.map((d, i) => ({ i, m: d.month })).filter((t) => t.m.slice(5) === '01' || t.i === 0);
  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" style={{ display: 'block', overflow: 'visible' }}>
      {[0, 0.5, 1].map((t, i) => { const y = yOf(max * t); return <line key={i} x1={pad.l} x2={W - pad.r} y1={y} y2={y} stroke={PC.grid} />; })}
      <path d={area} fill={accent} opacity="0.10" />
      <path d={line} fill="none" stroke={accent} strokeWidth="2.2" />
      {data.map((d, i) => (i % 3 === 0) && (
        <text key={i} x={xOf(i)} y={yOf(d.ltm) - 7} textAnchor="middle" fontSize="9" fontWeight="700" fill={d.pct >= 0 ? PC.pos : PC.danger} fontFamily="var(--mono)">{d.pct >= 0 ? '+' : ''}{d.pct}%</text>
      ))}
      {data.map((d, i) => (i % 3 === 0) && <circle key={'c' + i} cx={xOf(i)} cy={yOf(d.ltm)} r="2.6" fill="#fff" stroke={accent} strokeWidth="1.8" />)}
      {ticks.map((t) => <text key={t.i} x={xOf(t.i)} y={H - 5} textAnchor="middle" fontSize="9.5" fill={PC.muted} fontWeight="600">{t.m.slice(0, 4)}</text>)}
      <text x={pad.l} y={11} fontSize="9.5" fill={PC.soft} fontWeight="600" fontFamily="var(--mono)">{Math.round(max)}</text>
    </svg>
  );
}

/* ---------- bucketed distribution bars (deal size / EBITDA) ---------- */
function BucketBars({ buckets, color = PC.accent, onBar, fmtCount }) {
  const max = Math.max(1, ...buckets.map((b) => b.n));
  return (
    <div style={{ display: 'flex', alignItems: 'flex-end', gap: 8, height: 168, paddingTop: 6 }}>
      {buckets.map((b) => (
        <button key={b.label} onClick={() => onBar && onBar(b)} title={`${b.label}: ${b.n}`}
          style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, border: 'none', background: 'transparent', cursor: onBar ? 'pointer' : 'default', padding: 0 }}>
          <span className="tabular" style={{ fontSize: 11.5, fontWeight: 700 }}>{b.n}</span>
          <div style={{ width: '100%', maxWidth: 54, height: Math.max(2, b.n / max * 116), background: b.hi ? color : `color-mix(in srgb, ${color} 72%, #fff)`, borderRadius: '4px 4px 0 0' }}></div>
          <span style={{ fontSize: 10, color: PC.muted, fontWeight: 600, textAlign: 'center', lineHeight: 1.15 }}>{b.label}</span>
        </button>
      ))}
    </div>
  );
}

/* ---------- scorecard matrix (the board drilldown) ---------- */
function ScorecardMatrix({ base }) {
  const years = [...PL.YEARS].reverse();
  const valC = makeVal('count');
  const cards = years.map((y) => ({ y, sc: plScore(base.filter((d) => d.year === y), valC) }));
  const scPrevYTD = plScore(plPrevComparable(base, PL.CUR_YEAR), valC);
  return (
    <div className="card" style={{ padding: 0, overflow: 'hidden' }}>
      <table className="tbl" style={{ tableLayout: 'fixed' }}>
        <thead>
          <tr>
            <th style={{ width: 150 }}>Metric</th>
            {cards.map((c, i) => <th key={c.y} className="num" style={{ background: i === 0 ? 'var(--accent-soft)' : undefined, color: i === 0 ? PC.accent : undefined }}>{plYearLabel(c.y)}</th>)}
          </tr>
        </thead>
        <tbody>
          {PL_METRICS.map((m) => (
            <tr key={m.key}>
              <td style={{ fontWeight: 600 }}>{m.label}</td>
              {cards.map((c, i) => {
                const v = Math.round(c.sc[m.key]);
                const prev = i === 0 ? scPrevYTD[m.key] : (cards[i + 1] ? cards[i + 1].sc[m.key] : null);
                const dl = prev != null ? plPct(v, prev) : null;
                return (
                  <td key={c.y} className="num tabular" style={{ background: i === 0 ? 'var(--accent-soft)' : undefined }}>
                    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 1 }}>
                      <span style={{ fontSize: 14, fontWeight: 700, color: i === 0 ? PC.accent : PC.ink }}>{v}</span>
                      {i === 0 && dl != null && <Delta v={dl} size={9.5} invert={m.key === 'passed' || m.key === 'lost' || m.key === 'onhold'} />}
                    </div>
                  </td>
                );
              })}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/* ---------- drill drawer (companies behind any number) ---------- */
function PlDrawer({ open, onClose, title, subtitle, deals }) {
  const [page, setPage] = useState(0);
  const [exp, setExp] = useState(null);
  const [sortK, setSortK] = useState('ebitda');
  const PAGE = 12;
  useEffect(() => { setPage(0); setExp(null); }, [title, subtitle, deals && deals.length]);
  useEffect(() => { const h = (e) => { if (e.key === 'Escape') onClose(); }; if (open) document.addEventListener('keydown', h); return () => document.removeEventListener('keydown', h); }, [open]);
  if (!open) return null;
  const sorted = [...(deals || [])].sort((a, b) => sortK === 'name' ? a.name.localeCompare(b.name) : (b[sortK] || 0) - (a[sortK] || 0));
  const pages = Math.max(1, Math.ceil(sorted.length / PAGE));
  const pg = Math.min(page, pages - 1);
  const rows = sorted.slice(pg * PAGE, pg * PAGE + PAGE);
  const th = (k, label, num) => <th className={num ? 'num sortable' : 'sortable'} onClick={() => setSortK(k)} style={{ cursor: 'pointer' }}><span style={{ display: 'inline-flex', gap: 3, alignItems: 'center' }}>{label}{sortK === k && <Icon name="chevronDown" size={11} style={{ color: 'var(--accent)' }} />}</span></th>;
  return (
    <div style={{ position: 'fixed', inset: 0, zIndex: 80 }}>
      <div onClick={onClose} style={{ position: 'absolute', inset: 0, background: 'rgba(26,28,30,0.18)', animation: 'fadeIn .15s ease' }}></div>
      <aside style={{ position: 'absolute', top: 0, right: 0, height: '100%', width: 'min(780px, 95vw)', background: 'var(--surface-card)', borderLeft: '1px solid var(--line)', boxShadow: '-12px 0 40px rgba(26,28,30,.14)', display: 'flex', flexDirection: 'column', animation: 'drawerIn .2s cubic-bezier(.2,.7,.3,1)' }}>
        <div style={{ padding: '15px 20px 13px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 12 }}>
          <div>
            <div className="eyebrow" style={{ marginBottom: 4 }}><Icon name="panelRight" size={11} style={{ verticalAlign: -1, marginRight: 5 }} />Companies behind this</div>
            <h3 style={{ fontSize: 17, fontWeight: 700 }}>{title}</h3>
            {subtitle && <div style={{ fontSize: 12.5, color: 'var(--ink-soft)', marginTop: 3, fontWeight: 500 }}>{subtitle}</div>}
          </div>
          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <span className="tabular badge" style={{ height: 24, fontSize: 12 }}>{sorted.length}</span>
            <button className="btn ghost sm" onClick={onClose} style={{ width: 30, padding: 0, justifyContent: 'center' }}><Icon name="x" size={16} /></button>
          </div>
        </div>
        <div style={{ flex: 1, overflow: 'auto' }}>
          <table className="tbl">
            <thead><tr>{th('name', 'Company')}<th style={{ width: 128 }}>Stage</th><th style={{ width: 96 }}>Owner</th><th style={{ width: 70 }}>Added</th>{th('revenue', '€ Rev', true)}{th('ebitda', '€ EBITDA', true)}</tr></thead>
            <tbody>
              {rows.map((d) => (
                <React.Fragment key={d.id}>
                  <tr className="clickable" onClick={() => setExp((e) => e === d.id ? null : d.id)} style={exp === d.id ? { background: 'var(--accent-soft)' } : null}>
                    <td>
                      <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>
                        <Icon name={exp === d.id ? 'chevronDown' : 'chevronRight'} size={13} style={{ color: 'var(--ink-faint)', flexShrink: 0 }} />
                        <div style={{ minWidth: 0 }}><div style={{ fontWeight: 600, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{d.name}</div><div style={{ fontSize: 11, color: 'var(--ink-soft)' }}>{d.sector}</div></div>
                      </div>
                    </td>
                    <td><StageBadge rank={d.stageRank} /></td>
                    <td style={{ color: 'var(--ink-muted)', fontWeight: 500, fontSize: 12 }}>{(PL.oName(d.owner) || '—').split(' ')[0]}</td>
                    <td className="tabular" style={{ color: 'var(--ink-soft)', fontSize: 11.5 }}>{PL.monthLabel(d.month)}</td>
                    <td className="num tabular" style={{ color: 'var(--ink-muted)' }}>€{d.revenue}m</td>
                    <td className="num tabular" style={{ fontWeight: 700 }}>€{d.ebitda}m</td>
                  </tr>
                  {exp === d.id && (
                    <tr><td colSpan={6} style={{ padding: 0, background: 'var(--surface)' }}>
                      <div style={{ padding: '13px 18px 15px', borderTop: '1px solid var(--line)', animation: 'fadeIn .15s ease' }}>
                        <p style={{ margin: '0 0 12px', fontSize: 12.5, lineHeight: 1.5, color: 'var(--ink-muted)' }}>{PL.describe(d)}</p>
                        <div style={{ display: 'flex', gap: 18, flexWrap: 'wrap' }}>
                          <Meta label="Sector">{d.sector}</Meta><Meta label="Subsector">{d.subsector}</Meta>
                          <Meta label="Profile">{d.dealType === 'platform' ? 'Platform' : 'Add-on · ' + d.programme}</Meta>
                          <Meta label="Channel">{d.channel || 'Unattributed'}</Meta>
                          {d.advisorOrg && <Meta label="Advisor">{d.advisorOrg}</Meta>}
                          <Meta label="Owner">{PL.oName(d.owner)}</Meta><Meta label="Country">{d.country}</Meta>
                        </div>
                      </div>
                    </td></tr>
                  )}
                </React.Fragment>
              ))}
              {rows.length === 0 && <tr><td colSpan={6} style={{ textAlign: 'center', padding: 34, color: 'var(--ink-faint)' }}>No companies.</td></tr>}
            </tbody>
          </table>
        </div>
        <div style={{ padding: '10px 20px', borderTop: '1px solid var(--line)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', background: 'var(--surface)' }}>
          <span style={{ fontSize: 12, color: 'var(--ink-soft)', fontWeight: 500 }}>{sorted.length === 0 ? 'No companies' : `${pg * PAGE + 1}–${Math.min(sorted.length, pg * PAGE + PAGE)} of ${sorted.length}`}</span>
          <div style={{ display: 'flex', gap: 6 }}>
            <button className="btn sm" disabled={pg === 0} onClick={() => setPage(pg - 1)} style={{ opacity: pg === 0 ? .4 : 1 }}>Prev</button>
            <span className="tabular" style={{ fontSize: 12, color: 'var(--ink-muted)', alignSelf: 'center', minWidth: 44, textAlign: 'center' }}>{pg + 1} / {pages}</span>
            <button className="btn sm" disabled={pg >= pages - 1} onClick={() => setPage(pg + 1)} style={{ opacity: pg >= pages - 1 ? .4 : 1 }}>Next</button>
          </div>
        </div>
      </aside>
    </div>
  );
}
function Meta({ label, children }) {
  return <div><div className="micro" style={{ marginBottom: 2 }}>{label}</div><div style={{ fontSize: 12.5, fontWeight: 600 }}>{children}</div></div>;
}

Object.assign(window, {
  PC, plYearLabel, plPct, plPct1, plPrevComparable, plReach, makeVal, makeFmt, MEASURE_LABEL,
  plFunnel, plScore, PL_METRICS, plRollingLTM,
  Delta, CardHead, Card, SectionBlock, StageBadge, RollChart, BucketBars, ScorecardMatrix, PlDrawer, Meta,
});
