// Sponsorship Data — market-intel library of sponsorship deals (imported from
// the Weekly Brand Review workbook + manually added). Includes the treemap
// "Sponsorship Distribution by brand category and property segment":
// header = brand category, breakdown = property segments within it.

const DEAL_PALETTE = [
  'var(--accent)', 'var(--sig-blue-fg)', 'var(--sig-purple-fg)', 'var(--positive)',
  'var(--pending)', 'var(--sig-amber-fg)', 'var(--attention)', 'var(--muted)',
];

function SponsorshipDataPage({ onToast }) {
  const [deals, setDeals] = React.useState(null);   // null = loading
  const [error, setError] = React.useState('');
  const [q, setQ] = React.useState('');
  const [seg, setSeg] = React.useState('all');
  const [src, setSrc] = React.useState('all');
  // Category filter. null = all. Otherwise { label, cats:[...] } — cats holds one
  // real category, or every category merged into the treemap's "Other" box.
  const [catSel, setCatSel] = React.useState(null);
  const [expanded, setExpanded] = React.useState(null);
  const [page, setPage] = React.useState(0);
  const PAGE_SIZE = 50;

  // Any filter change returns to the first page.
  React.useEffect(() => { setPage(0); }, [q, seg, src, catSel]);

  // The library loads without `notes` and `assets` — the two long text columns,
  // only ever read for the one row a user expands. On a few thousand deals they
  // were most of the payload, and the server had to build all of it in memory.
  const [truncated, setTruncated] = React.useState(0);   // rows the cap left out
  React.useEffect(() => {
    window.IPSEM_API.getDealLibrary()
      .then((res) => {
        const rows = (res && res.deals) || [];
        setDeals(rows);
        setTruncated(res && res.truncated ? (res.total || 0) - rows.length : 0);
      })
      .catch((e) => { setError(e.message || 'Could not load deals'); setDeals([]); });
  }, []);

  // Expanding a row pulls that deal's full record once, then keeps it.
  const dealsRef = React.useRef(null);
  dealsRef.current = deals;
  const openRow = React.useCallback((id) => {
    setExpanded((cur) => (cur === id ? null : id));
    const row = (dealsRef.current || []).find((d) => d.id === id);
    if (!row || row._full) return;
    window.IPSEM_API.getDeal(id)
      .then((full) => setDeals((c) => (c || []).map((d) => d.id === id ? { ...d, ...full, _full: true } : d)))
      .catch(() => setDeals((c) => (c || []).map((d) => d.id === id ? { ...d, _full: true } : d)));
  }, []);

  const segments = React.useMemo(() => {
    const s = new Set();
    (deals || []).forEach((d) => { if (d.propertySegment) s.add(d.propertySegment); });
    return Array.from(s).sort();
  }, [deals]);

  const filtered = React.useMemo(() => {
    const s = q.trim().toLowerCase();
    return (deals || []).filter((d) =>
      (seg === 'all' || d.propertySegment === seg) &&
      (src === 'all' || (d.dealSource || 'Others') === src) &&
      (!catSel || catSel.cats.includes(d.category || 'Uncategorised')) &&
      (!s ||
        (d.brandName || '').toLowerCase().includes(s) ||
        (d.category || '').toLowerCase().includes(s) ||
        (d.propertyName || '').toLowerCase().includes(s) ||
        (d.propertySegment || '').toLowerCase().includes(s))
    );
  }, [deals, q, seg, src, catSel]);

  // Every category present, by deal count (for the dropdown filter).
  const categories = React.useMemo(() => {
    const m = {};
    (deals || []).forEach((d) => {
      const c = d.category || 'Uncategorised';
      m[c] = (m[c] || 0) + 1;
    });
    return Object.entries(m).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
  }, [deals]);

  // Treemap input: respects search/segment/source filters but NOT the category
  // click (so you can always click a different category).
  const treeDeals = React.useMemo(() => {
    const s = q.trim().toLowerCase();
    return (deals || []).filter((d) =>
      (seg === 'all' || d.propertySegment === seg) &&
      (src === 'all' || (d.dealSource || 'Others') === src) &&
      (!s ||
        (d.brandName || '').toLowerCase().includes(s) ||
        (d.category || '').toLowerCase().includes(s) ||
        (d.propertyName || '').toLowerCase().includes(s) ||
        (d.propertySegment || '').toLowerCase().includes(s))
    );
  }, [deals, q, seg, src]);

  // KPIs reflect the active filters so they reconcile with the table count.
  const kpi = React.useMemo(() => {
    const list = filtered;
    const brands = new Set(list.map((d) => d.brandName)).size;
    const cats = new Set(list.map((d) => d.category || 'Uncategorised')).size;
    const value = list.reduce((a, d) => a + (Number(d.estValueUsd) || 0), 0);
    return { total: list.length, brands, cats, value };
  }, [filtered]);

  // Clamp the page into range on the same render so a shrinking result set
  // never slices past the end (the effect above resets to 0 a frame later).
  const pageCount = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
  const safePage = Math.min(page, pageCount - 1);

  if (deals === null) {
    return <div className="page"><div className="empty">Loading sponsorship data…</div></div>;
  }

  return (
    <div className="page page-cap">
      <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-end', marginBottom: 22, gap: 20, flexWrap: 'wrap' }}>
        <div>
          <h1 className="h1" style={{ margin: 0, fontSize: 28 }}>Sponsorship Data</h1>
          <p className="text-2" style={{ marginTop: 8, fontSize: 14, maxWidth: 620, lineHeight: 1.55 }}>
            Market intelligence: who sponsors what, where, and at what estimated value.
            Imported from the Weekly Brand Review workbook.
          </p>
        </div>
      </div>

      {error ? (
        <div className="row gap-2" style={{ marginBottom: 18, padding: '12px 16px', background: 'var(--negative-soft)', border: '1px solid var(--negative)', borderRadius: 10 }}>
          <window.I.warn size={15} stroke="var(--negative)" />
          <span className="text-sm">{error}</span>
        </div>
      ) : null}

      {truncated > 0 ? (
        <div className="row gap-2" style={{ marginBottom: 18, padding: '12px 16px', background: 'var(--card-alt)', border: '1px solid var(--hairline)', borderRadius: 10 }}>
          <window.I.info size={15} />
          <span className="text-sm">
            Showing the first {(deals || []).length.toLocaleString()} deals — {truncated.toLocaleString()} more are in the
            database but not on this page, so the chart and totals below cover the first {(deals || []).length.toLocaleString()} only.
          </span>
        </div>
      ) : null}

      {/* KPIs */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(160px, 1fr))', gap: 14, marginBottom: 24 }}>
        {[
          { label: 'Deals tracked', value: kpi.total },
          { label: 'Brands', value: kpi.brands },
          { label: 'Brand categories', value: kpi.cats },
          { label: 'Est. value tracked', value: fmtUsd(kpi.value) },
        ].map((k) => (
          <div key={k.label} className="card card-pad">
            <div className="eyebrow" style={{ fontSize: 10.5, marginBottom: 6 }}>{k.label}</div>
            <div className="mono" style={{ fontSize: 26, fontWeight: 500, lineHeight: 1 }}>{k.value}</div>
          </div>
        ))}
      </div>

      {/* Treemap */}
      <Card lg style={{ marginBottom: 18 }}>
        <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-start', flexWrap: 'wrap', gap: 10 }}>
          <div style={{ marginBottom: 12 }}>
            <div className="h3" style={{ fontSize: 16 }}>Sponsorship Distribution by brand category and property segment</div>
            <div className="text-3 text-xs" style={{ marginTop: 3 }}>
              Box size = number of deals. Click a category to filter the table below{catSel ? ` — filtering on “${catSel.label}”` : ''}.
            </div>
          </div>
          {catSel ? (
            <button className="btn btn-sm" onClick={() => setCatSel(null)}>
              <window.I.x size={12} /> Clear “{catSel.label}”
            </button>
          ) : null}
        </div>
        {treeDeals.length === 0 ? (
          <div className="text-3 text-sm" style={{ padding: '24px 4px' }}>No deals match the current filters.</div>
        ) : (
          <DealTreemap
            deals={treeDeals}
            activeCats={catSel ? catSel.cats : null}
            onPickNode={(node) => setCatSel((cur) =>
              cur && cur.label === node.name ? null : { label: node.name, cats: node.cats })}
          />
        )}
      </Card>

      {/* Filters + table */}
      <Card pad={false}>
        <div className="row gap-2" style={{ padding: '14px 18px', borderBottom: '1px solid var(--hairline)', flexWrap: 'wrap', alignItems: 'center' }}>
          <div className="field" style={{ height: 34, width: 240 }}>
            <window.I.search size={14} stroke="var(--ink-3)" />
            <input placeholder="Search brand, category, property…" value={q} onChange={(e) => setQ(e.target.value)} />
          </div>
          <select
            className="field"
            style={{ height: 34, padding: '0 10px', maxWidth: 220 }}
            value={catSel && catSel.cats.length === 1 ? catSel.cats[0] : (catSel ? '__multi__' : 'all')}
            onChange={(e) => {
              const v = e.target.value;
              setCatSel(v === 'all' ? null : { label: v, cats: [v] });
            }}
          >
            <option value="all">All categories</option>
            {catSel && catSel.cats.length > 1 ? <option value="__multi__">{catSel.label}</option> : null}
            {categories.map(([c, n]) => <option key={c} value={c}>{c} ({n})</option>)}
          </select>
          <select className="field" style={{ height: 34, padding: '0 10px' }} value={seg} onChange={(e) => setSeg(e.target.value)}>
            <option value="all">All segments</option>
            {segments.map((s) => <option key={s} value={s}>{s}</option>)}
          </select>
          <select className="field" style={{ height: 34, padding: '0 10px' }} value={src} onChange={(e) => setSrc(e.target.value)}>
            <option value="all">All sources</option>
            <option value="IPSEM (Deal)">IPSEM (Deal)</option>
            <option value="IPSEM (draft)">IPSEM (draft)</option>
            <option value="Others">Others</option>
          </select>
          <span className="text-xs text-3 right">{filtered.length} deal{filtered.length === 1 ? '' : 's'}</span>
        </div>

        <div style={{ overflowX: 'auto' }}>
          <table className="tbl" style={{ minWidth: 900 }}>
            <thead>
              <tr>
                <th>Brand</th>
                <th>Category</th>
                <th>Property</th>
                <th>Segment</th>
                <th style={{ width: 110 }}>Years</th>
                <th>Scope</th>
                <th style={{ textAlign: 'right', width: 120 }}>Est. value / season</th>
                <th style={{ width: 110 }}>Source</th>
                <th style={{ width: 36 }} />
              </tr>
            </thead>
            <tbody>
              {filtered.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE).map((d) => (
                <React.Fragment key={d.id}>
                  <tr style={{ cursor: 'pointer' }} tabIndex={0} role="button"
                      aria-expanded={expanded === d.id}
                      onClick={() => openRow(d.id)}
                      onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); openRow(d.id); } }}>
                    <td data-label="Brand" style={{ fontWeight: 600 }}>{d.brandName}</td>
                    <td data-label="Category" className="text-sm text-2">{d.category || '—'}</td>
                    <td data-label="Property" className="text-sm">{d.propertyName || '—'}</td>
                    <td data-label="Segment" className="text-sm text-2">{d.propertySegment || '—'}</td>
                    <td data-label="Years" className="mono text-sm">{fmtYears(d.startYear, d.endYear)}</td>
                    <td data-label="Scope" className="text-sm text-2">{d.geographicScope || '—'}</td>
                    <td data-label="Est. value / season" className="mono text-sm" style={{ textAlign: 'right' }}>{d.estValueUsd ? fmtUsd(Number(d.estValueUsd)) : '—'}</td>
                    <td data-label="Source">
                      <Pill kind={(d.dealSource || '').startsWith('IPSEM') ? 'positive' : 'muted'} style={{ fontSize: 10 }}>
                        {d.dealSource || 'Others'}
                      </Pill>
                    </td>
                    <td className="mob-hide" style={{ textAlign: 'center' }}>
                      <window.I.chevronRight size={13} style={{ transform: expanded === d.id ? 'rotate(90deg)' : 'none', transition: 'transform 140ms ease' }} />
                    </td>
                  </tr>
                  {expanded === d.id ? (
                    <tr>
                      <td colSpan={9} style={{ background: 'var(--card-alt)', padding: '14px 18px' }}>
                        <div className="col gap-3" style={{ maxWidth: 900 }}>
                          {d.designation ? (
                            <div>
                              <div className="eyebrow text-xs" style={{ marginBottom: 4 }}>Designation</div>
                              <div className="text-sm" style={{ whiteSpace: 'pre-wrap', lineHeight: 1.55 }}>{d.designation}</div>
                            </div>
                          ) : null}
                          {d.notes ? (
                            <div>
                              <div className="eyebrow text-xs" style={{ marginBottom: 4 }}>Notes</div>
                              <div className="text-sm text-2" style={{ whiteSpace: 'pre-wrap', lineHeight: 1.55 }}>{d.notes}</div>
                            </div>
                          ) : null}
                          {d.assets ? (
                            <div>
                              <div className="eyebrow text-xs" style={{ marginBottom: 4 }}>Assets / rights</div>
                              <div className="text-sm text-2" style={{ whiteSpace: 'pre-wrap', lineHeight: 1.55, maxHeight: 320, overflowY: 'auto' }}>{d.assets}</div>
                            </div>
                          ) : null}
                          {!d._full && !d.designation ? (
                            <div className="text-sm text-3">Loading detail…</div>
                          ) : d._full && !d.designation && !d.notes && !d.assets ? (
                            <div className="text-sm text-3">No further detail recorded for this deal.</div>
                          ) : null}
                        </div>
                      </td>
                    </tr>
                  ) : null}
                </React.Fragment>
              ))}
              {filtered.length === 0 ? (
                <tr><td colSpan={9}><div className="empty">No deals match the current filters.</div></td></tr>
              ) : null}
            </tbody>
          </table>
        </div>
        {filtered.length > PAGE_SIZE ? (
          <div className="row gap-2" style={{ padding: '12px 18px', borderTop: '1px solid var(--hairline)', alignItems: 'center', justifyContent: 'flex-end' }}>
            <span className="text-xs text-3">
              {safePage * PAGE_SIZE + 1}–{Math.min((safePage + 1) * PAGE_SIZE, filtered.length)} of {filtered.length}
            </span>
            <button className="btn btn-sm" disabled={safePage === 0} onClick={() => setPage(safePage - 1)}>Previous</button>
            <button className="btn btn-sm" disabled={(safePage + 1) * PAGE_SIZE >= filtered.length} onClick={() => setPage(safePage + 1)}>Next</button>
          </div>
        ) : null}
      </Card>
    </div>
  );
}

// ─── Treemap ────────────────────────────────────────────────────────────────
// Nested treemap: outer boxes are brand categories (sized by deal count),
// inner boxes the property segments within each category.

function DealTreemap({ deals, activeCats, onPickNode }) {
  const W = 1100, H = 560, MAX_CATS = 14;

  const nodes = React.useMemo(() => {
    const byCat = {};
    deals.forEach((d) => {
      const c = d.category || 'Uncategorised';
      const s = d.propertySegment || 'Other';
      byCat[c] = byCat[c] || {};
      byCat[c][s] = (byCat[c][s] || 0) + 1;
    });
    let cats = Object.entries(byCat).map(([name, segs]) => ({
      name,
      cats: [name],   // categories this box represents (for filtering)
      value: Object.values(segs).reduce((a, v) => a + v, 0),
      segs: Object.entries(segs).map(([sn, sv]) => ({ name: sn, value: sv })).sort((a, b) => b.value - a.value),
    })).sort((a, b) => b.value - a.value);

    if (cats.length > MAX_CATS) {
      const rest = cats.slice(MAX_CATS - 1);
      const other = {
        name: `Other (${rest.length} categories)`,
        cats: rest.map((c) => c.name),   // clicking Other filters to all of these
        value: rest.reduce((a, c) => a + c.value, 0),
        segs: [],
      };
      const merged = {};
      rest.forEach((c) => c.segs.forEach((s) => { merged[s.name] = (merged[s.name] || 0) + s.value; }));
      other.segs = Object.entries(merged).map(([sn, sv]) => ({ name: sn, value: sv })).sort((a, b) => b.value - a.value);
      cats = cats.slice(0, MAX_CATS - 1).concat([other]);
    }
    return cats;
  }, [deals]);

  const rects = squarify(nodes, 0, 0, W, H);

  return (
    <div style={{ overflowX: 'auto' }}>
      <svg className="resp-svg" viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', minWidth: 700, height: 'auto', display: 'block' }} role="img"
           aria-label={`Treemap of sponsorship deals. ${nodes.map((n) => `${n.name}: ${n.value}`).join('; ')}.`}>
        {rects.map((r, i) => {
          const color = DEAL_PALETTE[i % DEAL_PALETTE.length];
          const pad = 3, headH = r.h > 46 && r.w > 90 ? 20 : 0;
          const inner = squarify(r.node.segs, r.x + pad, r.y + pad + headH, Math.max(r.w - pad * 2, 1), Math.max(r.h - pad * 2 - headH, 1));
          const active = activeCats && r.node.cats.some((c) => activeCats.includes(c));
          const dim = activeCats && !active;
          return (
            <g key={r.node.name} style={{ cursor: 'pointer', opacity: dim ? 0.35 : 1, transition: 'opacity 150ms ease' }}
               tabIndex={0} role="button" aria-pressed={!!active}
               aria-label={`${r.node.name}: ${r.node.value} deals. Filter table.`}
               onClick={() => onPickNode(r.node)}
               onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onPickNode(r.node); } }}>
              <rect x={r.x} y={r.y} width={r.w} height={r.h} fill={color} opacity="0.14" stroke={color} strokeWidth="1.5" rx="4">
                <title>{`${r.node.name}: ${r.node.value} deal${r.node.value === 1 ? '' : 's'}`}</title>
              </rect>
              {inner.map((s, j) => (
                <g key={s.node.name}>
                  <rect x={s.x + 1} y={s.y + 1} width={Math.max(s.w - 2, 0.5)} height={Math.max(s.h - 2, 0.5)}
                        fill={color} opacity={0.72 - (j % 4) * 0.14} rx="2">
                    <title>{`${r.node.name} → ${s.node.name}: ${s.node.value} deal${s.node.value === 1 ? '' : 's'}`}</title>
                  </rect>
                  {s.w > 58 && s.h > 16 ? (
                    <text x={s.x + 6} y={s.y + 14} fontSize="10.5" fill="white"
                          style={{ pointerEvents: 'none', fontWeight: 500, paintOrder: 'stroke', stroke: 'rgba(0,0,0,0.45)', strokeWidth: 2.4, strokeLinejoin: 'round' }}>
                      {truncText(s.node.name, s.w / 6)} {s.w > 90 ? `· ${s.node.value}` : ''}
                    </text>
                  ) : null}
                </g>
              ))}
              {headH ? (
                <text x={r.x + 8} y={r.y + 16} fontSize="12" fill="var(--ink)" style={{ pointerEvents: 'none', fontWeight: 700 }}>
                  {truncText(r.node.name, r.w / 6.5)} · {r.node.value}
                </text>
              ) : null}
            </g>
          );
        })}
      </svg>
      <div className="row gap-3 text-xs text-3" style={{ marginTop: 10, flexWrap: 'wrap' }}>
        {rects.slice(0, 8).map((r, i) => (
          <span key={r.node.name} className="row gap-1" style={{ alignItems: 'center' }}>
            <span style={{ width: 9, height: 9, borderRadius: 2, background: DEAL_PALETTE[i % DEAL_PALETTE.length], display: 'inline-block' }} />
            {r.node.name}
          </span>
        ))}
      </div>
    </div>
  );
}

// Squarified treemap layout. items: [{name, value, ...}] → rects [{x,y,w,h,node}].
function squarify(items, x, y, w, h) {
  const total = items.reduce((a, it) => a + it.value, 0);
  if (!total || w <= 0 || h <= 0) return [];
  const scaled = items.filter((it) => it.value > 0).map((it) => ({ node: it, area: (it.value / total) * w * h }));
  const out = [];
  let row = [], rowArea = 0;

  const worst = (list, area, side) => {
    const max = Math.max(...list.map((r) => r.area));
    const min = Math.min(...list.map((r) => r.area));
    const s2 = side * side, a2 = area * area;
    return Math.max((s2 * max) / a2, a2 / (s2 * min));
  };

  const layoutRow = (list, area) => {
    const horiz = w >= h;                       // lay the row along the shorter side
    const side = horiz ? h : w;
    const thick = area / side;
    let off = 0;
    list.forEach((r) => {
      const len = r.area / thick;
      if (horiz) out.push({ x, y: y + off, w: thick, h: len, node: r.node });
      else out.push({ x: x + off, y, w: len, h: thick, node: r.node });
      off += len;
    });
    if (horiz) { x += thick; w -= thick; }
    else { y += thick; h -= thick; }
  };

  scaled.forEach((r) => {
    const side = Math.min(w, h);
    if (row.length && worst(row.concat([r]), rowArea + r.area, side) > worst(row, rowArea, side)) {
      layoutRow(row, rowArea);
      row = [r]; rowArea = r.area;
    } else {
      row.push(r); rowArea += r.area;
    }
  });
  if (row.length) layoutRow(row, rowArea);
  return out;
}

function truncText(s, maxChars) {
  const n = Math.max(3, Math.floor(maxChars));
  return s.length > n ? s.slice(0, n - 1) + '…' : s;
}

function fmtYears(a, b) {
  if (!a && !b) return '—';
  if (a && b && a !== b) return `${a}–${b}`;
  return String(a || b);
}

function fmtUsd(v) {
  if (!v) return '$0';
  if (v >= 1e9) return `$${(v / 1e9).toFixed(1)}bn`;
  if (v >= 1e6) return `$${(v / 1e6).toFixed(1)}m`;
  if (v >= 1e3) return `$${Math.round(v / 1e3)}k`;
  return `$${Math.round(v)}`;
}

window.SponsorshipDataPage = SponsorshipDataPage;
