// views/IndiaPlayersView.jsx - Visual 2: India initiative & player map (value chain view)
// Same column structure as the global map. Horizontal bands:
//   Top    - initiatives & programmes (interventions sitting on top)
//   Below  - value-chain players (industry players - incl. tech providers &
//            manufacturers - and policymakers)
// Filters: pillar · band · barrier type · organisation type
// Anchor players bold; +N more overflow pill per column.

const { useMemo: useMemoIP, useState: useStateIP, useRef: useRefIP, useEffect: useEffectIP } = React;

function useDragScrollIP() {
  const ref = useRefIP(null);
  const drag = useRefIP({ active: false, startX: 0, scrollLeft: 0, moved: false });

  function onMouseDown(e) {
    if (e.button !== 0) return;
    const el = ref.current;
    if (!el) return;
    drag.current = { active: true, startX: e.clientX, scrollLeft: el.scrollLeft, moved: false };
    el.style.cursor = 'grabbing';
    el.style.userSelect = 'none';
  }

  function onMouseMove(e) {
    const d = drag.current;
    if (!d.active) return;
    const dx = e.clientX - d.startX;
    if (Math.abs(dx) > 3) d.moved = true;
    if (d.moved) ref.current.scrollLeft = d.scrollLeft - dx;
  }

  function onMouseUp() {
    const el = ref.current;
    drag.current.active = false;
    if (el) { el.style.cursor = ''; el.style.userSelect = ''; }
  }

  useEffectIP(() => {
    window.addEventListener('mousemove', onMouseMove);
    window.addEventListener('mouseup', onMouseUp);
    return () => {
      window.removeEventListener('mousemove', onMouseMove);
      window.removeEventListener('mouseup', onMouseUp);
    };
  }, []);

  return { ref, onMouseDown };
}

// Column groups mirror the global Heat Map (MatrixView.M_GROUPS) so the two
// initiatives maps read identically (Andrea, 2026-05-28).
// Column order mirrors the Global Initiatives Heat Map (MatrixView.M_GROUPS) so
// the two initiatives maps read identically: Generation → Grids (T/D +
// Decentralised RES) → Overarching (storage, digitalisation) between grids and
// demand → Power demand. India has no tech-provider/policy columns (those orgs
// are shown as bands), so there's no trailing "Enablers" group here.
const IP_GROUPS = [
  {
    id: 'production', label: 'Power production', tint: 'rgba(253,206,7,0.20)',
    cols: [
      { id: 'prod-utility',       label: 'Generation' },
    ],
  },
  {
    id: 'sysgrids', label: 'Grids & System operation', tint: 'rgba(132,201,143,0.22)', gapBefore: true,
    cols: [
      { id: 'transmission',       label: 'Transmission' },
      { id: 'distribution',       label: 'Distribution' },
      { id: 'prod-decentralised', label: 'Decentralised RES' },
    ],
  },
  {
    id: 'overarching', label: 'Overarching', tint: 'rgba(140,89,131,0.22)', gapBefore: true,
    cols: [
      { id: 'storage',        label: 'Grid flexibility &', sub: 'energy storage' },
      { id: 'grid-digital',   label: 'Grid digitalisation &', sub: 'data providers' },
    ],
  },
  {
    id: 'power', label: 'Power demand', tint: 'rgba(74,144,180,0.22)', gapBefore: true,
    cols: [
      { id: 'demand-agriculture', label: 'Agriculture' },
      { id: 'demand-industry',    label: 'Industry' },
      { id: 'demand-transport',   label: 'Transport' },
      { id: 'demand-households',  label: 'Households &', sub: 'Cooking' },
      { id: 'demand-services',    label: 'Services &', sub: 'other' },
    ],
  },
];
const IP_COLS = IP_GROUPS.flatMap(g => g.cols.map((c, i) => ({ ...c, group: g.id, groupLabel: g.label, gapBefore: !!(g.gapBefore && i === 0) })));

// Pillar → columns. "both" columns (decentralised RES, storage, digitalisation,
// tech providers) light up for either pillar - mirrors the Energy Ecosystem map.
const IP_COL_PILLARS = {
  'prod-utility':       ['powering'],
  'prod-decentralised': ['powering', 'grids'],
  'transmission':       ['grids'],
  'distribution':       ['grids'],
  'storage':            ['powering', 'grids'],
  'grid-digital':       ['powering', 'grids'],
  'demand-agriculture': ['powering'],
  'demand-industry':    ['powering'],
  'demand-transport':   ['powering'],
  'demand-households':  ['powering'],
  'demand-services':    ['powering'],
};
function ipColMatchesPillar(colId, activePillars) {
  if (!activePillars || !activePillars.length) return true;
  const cps = IP_COL_PILLARS[colId] || [];
  if (cps.length === 0) return true; // overarching always visible
  for (const p of activePillars) if (cps.includes(p)) return true;
  return false;
}

// Anchor-org markers per column (illustrative - these are organisations whose
// absence would make the India map incredible; can be expanded as the dataset grows).
const ANCHOR_NAMES = new Set([
  'NTPC Limited','Adani Green Energy Limited (AGEL)','Tata Power Renewable Energy Limited (TPREL)','ReNew Energy Global plc (ReNew)','Solar Energy Corporation of India Limited (SECI)',
  'Husk Power Systems','OMC Power (Omnigrid Micropower Company)',
  'Exide Energy Solutions Limited (EESL)',
  'Waaree Energies Limited','Vikram Solar Limited','Hitachi Energy India Limited (POWERINDIA)','GE Vernova T&D India Limited (Grid Solutions)','Siemens Energy India Limited (SEIL)',
  'Power Grid Corporation of India Limited (POWERGRID)','Tata Power Delhi Distribution Limited (TPDDL)','Adani Electricity Mumbai Limited (AEML)','BSES Rajdhani Power Limited (BRPL)',
  'Genus Power Infrastructures Limited',
  'NHPC Limited','IndiGrid Infrastructure Trust','Indian Renewable Energy Development Agency (IREDA)',
]);

// Two colour rules to match the Global Initiatives Heat Map's TYPE_TINT_HEX
// (Andrea, 2026-05-28: "a key for the colour of the dots - that match the
// global initiatives map"). Includes the India-only types found in
// data-india.js so every dot lands on a known colour.
//
// India data uses "MDB/IFI" without a space; the global file uses "MDB/ IFI / DFI".
// Both are mapped to the same blue so the colour is consistent across views.
const IP_TYPE_COLOR = {
  // Mirrors of the Global Heat Map palette
  'MDB/ IFI':                                '#3b6ea8',
  'MDB/IFI':                                 '#3b6ea8',
  'MDB/IFI/DFI':                             '#3b6ea8', // India data spelling
  'Coalition / platform':                    '#5a8c66', // short label used in the global facet
  'Bilateral development agency':            '#c87a4e',
  'Intergovernmental organization (IGO)':    '#8c5983',
  'Coalition or multi-stakeholder platform': '#5a8c66',
  'NGO':                                     '#d9b441',
  'Philanthropic foundation':                '#e07b4a',
  'Philanthropy':                            '#e07b4a',
  'Private company (IPP)':                   '#2a7a5a',
  'Private investor':                        '#7a5da8',
  'Commercial system actor':                 '#b05c7a',
  'Research & Academic institution':         '#6a9bb8',
  // India-only types
  'Industry player':                         '#0a0a0a',
  'Financial intermediary (domestic)':       '#0e2c66',
  'System owner (policymakers, regulators)': '#1a4d6b',
  'Regulated infrastructure operator':       '#3b6ea8',
  'Tech provision & manufacturer':           '#c87a4e',
  'Utilities and system operation (e.g., TSO, DSO)': '#5a8c66',
};
function ipTypeColor(t) { return IP_TYPE_COLOR[t] || '#7a9082'; }

// Heatmap colour ramp - a clear multi-hue scale (cool green → yellow → orange →
// red) so density differences read at a glance rather than as faint lightness
// steps. Mirrors the Global Heat Map's ramp so the two views read identically.
const IP_HEAT_STOPS = [
  [0.00, [ 38, 128,  92]],  // few - green
  [0.45, [253, 206,   7]],  // mid - yellow
  [0.75, [240, 136,  62]],  // orange
  [1.00, [224,  58,  49]],  // many - red
];
function ipHeatColor(t) {
  t = Math.max(0, Math.min(1, t));
  let a = IP_HEAT_STOPS[0], b = IP_HEAT_STOPS[IP_HEAT_STOPS.length - 1];
  for (let i = 0; i < IP_HEAT_STOPS.length - 1; i++) {
    if (t >= IP_HEAT_STOPS[i][0] && t <= IP_HEAT_STOPS[i + 1][0]) { a = IP_HEAT_STOPS[i]; b = IP_HEAT_STOPS[i + 1]; break; }
  }
  const f = (t - a[0]) / ((b[0] - a[0]) || 1);
  const c = [0, 1, 2].map(k => Math.round(a[1][k] + (b[1][k] - a[1][k]) * f));
  const lum = (0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2]) / 255;
  return { fill: `rgba(${c[0]}, ${c[1]}, ${c[2]}, 0.9)`, dark: lum > 0.6 };
}

// Canonical label + colour for the dot-colour legend. The India dataset and the
// augmented global table spell some types differently ("MDB/IFI/DFI" vs
// "MDB/ IFI", "Philanthropy" vs "Philanthropic foundation"), and the raw type
// strings are long. This collapses each semantic type to one consistently-named
// legend entry so e.g. coalitions read as "Coalition / platform" - matching the
// Global Initiatives Heat Map's facet labels (feedback: naming was inconsistent
// and some entries didn't surface).
const IP_TYPE_CANON = {
  'MDB/ IFI':                                { label: 'MDB / IFI / DFI',      color: '#3b6ea8' },
  'MDB/IFI':                                 { label: 'MDB / IFI / DFI',      color: '#3b6ea8' },
  'MDB/IFI/DFI':                             { label: 'MDB / IFI / DFI',      color: '#3b6ea8' },
  'Bilateral development agency':            { label: 'Bilateral agency',     color: '#c87a4e' },
  'Intergovernmental organisation (IGO)':    { label: 'IGO',                  color: '#8c5983' },
  'Coalition or multi-stakeholder platform': { label: 'Coalition / platform', color: '#5a8c66' },
  'Coalition / platform':                    { label: 'Coalition / platform', color: '#5a8c66' },
  'NGO':                                     { label: 'NGO',                  color: '#d9b441' },
  'Philanthropic foundation':                { label: 'Philanthropy',         color: '#e07b4a' },
  'Philanthropy':                            { label: 'Philanthropy',         color: '#e07b4a' },
  'Private company (IPP)':                   { label: 'Private company (IPP)', color: '#2a7a5a' },
  'Private investor':                        { label: 'Private investor',     color: '#7a5da8' },
  'Commercial system actor':                 { label: 'Commercial actor',     color: '#b05c7a' },
  'Research & Academic institution':         { label: 'Research / academia',  color: '#6a9bb8' },
  'Financial intermediary (domestic)':       { label: 'Financial intermediary', color: '#0e2c66' },
};
function ipTypeCanon(t) { return IP_TYPE_CANON[t] || { label: t, color: ipTypeColor(t) }; }

function colsForOrgIP(org) {
  if (org.valueChainCells && org.valueChainCells.length) return org.valueChainCells;
  // Fall back to deriving from valueChain / vcRaw / topics (same logic as global map)
  const cells = new Set();
  const vc  = new Set(org.valueChain || []);
  const raw = new Set(org.vcRaw || []);
  const tp  = new Set(org.topics || []);

  if (vc.has('Production (utility scale)'))   cells.add('prod-utility');
  if (vc.has('Production (decentralised)'))   cells.add('prod-decentralised');
  if (vc.has('Transmission (TSOs)'))          cells.add('transmission');
  if (vc.has('Distribution (DSOs)'))          cells.add('distribution');
  if (vc.has('Energy storage') || tp.has('Grids - Energy Storage'))   cells.add('storage');
  if (vc.has('Grid digitalisation & data providers') || tp.has('Grids - Digitalisation')) cells.add('grid-digital');
  if (raw.has('Agriculture'))                                          cells.add('demand-agriculture');
  if (raw.has('Industry'))                                             cells.add('demand-industry');
  if (raw.has('Households & cooking') || tp.has('Power - Clean cooking')) cells.add('demand-households');
  if (raw.has('Transport'))                                            cells.add('demand-transport');
  if (raw.has('Services & other') || raw.has('Buildings'))             cells.add('demand-services');
  if (tp.has('Power - Energy efficiency')) { cells.add('demand-industry'); cells.add('demand-services'); }
  // 'tech-provision', 'policy' and 'financial' columns removed per feedback -
  // see IP_GROUPS note. These roles are kept as tags in the passport instead.

  return Array.from(cells);
}

// Decide which band an org belongs to. Driven by the Excel "Type of
// organisation" column - each value maps explicitly to one band.
//
// Three horizontal bands (feedback: Andrea & Rintati; tech-providers row
// removed per 2026-06 feedback - those orgs are industry players):
//   Top    - industry players (build, finance, operate the system; incl. tech
//            providers & manufacturers)
//   Middle - policymakers & regulators (set the rules of the system)
//   Bottom - initiatives & programmes (interventions sitting on top)
const POLICYMAKER_TYPES = new Set([
  'System owner (policymakers, regulators)',
  'Policymakers and regulators', // v03_06 Regional Overview vocabulary
]);
const PLAYER_TYPES = new Set([
  // India dataset
  'Industry player',
  'Regulated infrastructure operator',
  'Financial intermediary (domestic)',
  'Utilities and system operation (e.g., TSO, DSO)',
  // Tech providers & manufacturers are industry players (feedback 2026-06).
  'Tech provision & manufacturer',
  // Global types pulled in via data-india-augment.js
  'Private company (IPP)',
]);

function bandFor(org) {
  if (POLICYMAKER_TYPES.has(org.type)) return 'policymakers';
  if ((org.influencer || []).includes('industry-players')) return 'players';
  if (PLAYER_TYPES.has(org.type)) return 'players';
  // If org has barriers tagged → initiative; otherwise default to players for India dataset
  if ((org.barriers || []).length > 0) return 'initiatives';
  return 'players';
}

function IndiaPlayersView({ orgs, allOrgs, edges = [], onPickOrg, filters, setFilters, highlightOrgId }) {
  const dragScroll = useDragScrollIP();
  const tableRef = useRefIP(null);
  // Zoom - applied as CSS `zoom` on the table so the scroll wrapper adapts.
  const [zoom, setZoom] = useStateIP(1);
  const ZMIN = 0.4, ZMAX = 2;
  const clampZoom = (z) => Math.max(ZMIN, Math.min(ZMAX, z));
  const zoomIn = () => setZoom(z => clampZoom(Math.round(z * 110) / 100));
  const zoomOut = () => setZoom(z => clampZoom(Math.round(z * 100 / 110) / 100));
  const fitHeight = () => {
    const wrap = dragScroll.ref.current, table = tableRef.current;
    if (!wrap || !table) return;
    const natural = table.getBoundingClientRect().height / zoom; // unzoomed height
    if (!natural) return;
    const top = wrap.getBoundingClientRect().top;
    const avail = window.innerHeight - top - 24; // viewport below the table's top
    setZoom(clampZoom(avail / natural));
  };
  const [band, setBand] = useStateIP('both');         // 'both'(all) | 'players' | 'enablers' | 'initiatives'
  const [barrierFilter, setBarrierFilter] = useStateIP(null); // string | null
  const [expanded, setExpanded] = useStateIP({});      // { [bandColKey]: true } for "+N more"
  const [popup, setPopup] = useStateIP(null);          // { title, subtitle, orgs } for the cell network modal
  const CellNetworkModal = window.MatrixNetworkModal;  // exported from MatrixView.jsx

  const [heatmap, setHeatmap] = useStateIP(false); // initiatives band as a count heatmap

  // Pillar filter - mirrors the global Heat Map (Andrea, 2026-05-28).
  const [pillarFilter, setPillarFilter] = useStateIP([]);
  const togglePillarFilter = (p) => setPillarFilter(prev =>
    prev.includes(p) ? prev.filter(x => x !== p) : [...prev, p]
  );

  // Bucket orgs into (band, col) cells.
  // Filtering (the side-panel filters that produce `orgs`, plus the local
  // barrier select) applies ONLY to the initiatives band - the value-chain
  // bands (players, policymakers) always show the full set so
  // the underlying system stays constant while you narrow the interventions
  // sitting on top of it (feedback: filters should narrow initiatives, not the
  // players). Hence the non-initiatives bands are built from the unfiltered
  // `allOrgs` set, and only initiatives are gated by the filtered `orgs`.
  const buckets = useMemoIP(() => {
    const out = { players: {}, policymakers: {}, initiatives: {} };
    for (const c of IP_COLS) { out.players[c.id] = []; out.policymakers[c.id] = []; out.initiatives[c.id] = []; }
    const source = (allOrgs && allOrgs.length) ? allOrgs : orgs;
    const passedIds = new Set(orgs.map(o => o.id));
    for (const o of source) {
      const b = bandFor(o);
      if (b === 'initiatives') {
        if (!passedIds.has(o.id)) continue;
        if (barrierFilter && !(o.barriers || []).includes(barrierFilter)) continue;
      }
      const cols = colsForOrgIP(o);
      for (const c of cols) { if (out[b][c]) out[b][c].push(o); }
    }
    // Sort:
    //  · Initiatives band - by organisation type (so same-coloured dots cluster,
    //    matching the dot-colour legend), alphabetical within each type.
    //  · Value-chain bands - anchors first (alphabetical within), then
    //    secondaries (alphabetical).
    for (const bk of ['players','policymakers','initiatives']) {
      for (const c of IP_COLS) {
        if (bk === 'initiatives') {
          out[bk][c.id].sort((a, b) => {
            const ta = ipTypeCanon(a.type).label, tb = ipTypeCanon(b.type).label;
            if (ta !== tb) return ta.localeCompare(tb);
            return a.name.localeCompare(b.name);
          });
        } else {
          out[bk][c.id].sort((a, b) => {
            const aa = ANCHOR_NAMES.has(a.name) ? 0 : 1;
            const bb = ANCHOR_NAMES.has(b.name) ? 0 : 1;
            if (aa !== bb) return aa - bb;
            return a.name.localeCompare(b.name);
          });
        }
      }
    }
    return out;
  }, [orgs, allOrgs, barrierFilter]);

  // Busiest initiatives cell - normalises the heatmap colour ramp. Only the
  // initiatives band is counted, and buckets.initiatives is already gated by the
  // filtered `orgs` set and the barrier select, so the heatmap tracks filters.
  const heatMax = useMemoIP(
    () => IP_COLS.reduce((m, c) => Math.max(m, buckets.initiatives[c.id].length), 0),
    [buckets]
  );

  const barriersPresent = useMemoIP(() => {
    const s = new Set();
    for (const o of orgs) for (const b of (o.barriers||[])) s.add(b);
    return [...s];
  }, [orgs]);

  const MAX_VISIBLE = 8; // per cell before "+N more" in the players band

  // Search highlight (from the side panel via highlightOrgId). Expand any
  // collapsed chip cells holding the picked org so it's visible, then scroll
  // it into view (feedback: highlighting an industry player did nothing).
  useEffectIP(() => {
    if (!highlightOrgId) return;
    const org = orgs.find(o => o.id === highlightOrgId);
    if (!org) return;
    const b = bandFor(org);
    if (b !== 'initiatives') {
      const cols = colsForOrgIP(org);
      setExpanded(prev => {
        const next = { ...prev };
        for (const c of cols) next[b + ':' + c] = true;
        return next;
      });
    }
    const raf = requestAnimationFrame(() => {
      const el = tableRef.current && tableRef.current.querySelector('.is-highlight');
      if (el && el.scrollIntoView) el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'smooth' });
    });
    return () => cancelAnimationFrame(raf);
  }, [highlightOrgId, orgs]);

  // Open the collaboration network for an initiatives cell (feedback: clickable
  // cells, not just headers). India currently records no edges, so the modal
  // shows the organisations as a list (matching global heat map behavior).
  function openInitNetwork(colId) {
    const list = buckets.initiatives[colId];
    if (!list || list.length === 0) return;
    const col = IP_COLS.find(c => c.id === colId);
    setPopup({
      title: `Initiatives · ${col.label}${col.sub ? ' ' + col.sub : ''}`,
      subtitle: 'Initiatives within cell',
      orgs: list,
    });
  }

  function renderCell(bandKey, colId, gapBefore) {
    const list = buckets[bandKey][colId];
    const key = bandKey + ':' + colId;
    const pillarDim = pillarFilter.length > 0 && !ipColMatchesPillar(colId, pillarFilter);
    const cellCls =
      'ip-cell ip-cell--' + bandKey +
      (gapBefore ? ' mx-gap-before' : '') +
      (pillarDim ? ' is-pillar-dim' : '');

    // Heatmap mode (initiatives band only) - replace the dot cloud with a single
    // count, the cell tinted by how many initiatives it holds relative to the
    // busiest cell. `list` is already filtered, so the colour tracks the filters.
    if (bandKey === 'initiatives' && heatmap) {
      const count = list.length;
      const t = heatMax > 0 ? count / heatMax : 0;
      const hc = count === 0 ? null : ipHeatColor(t);
      return (
        <td
          key={colId}
          className={
            'ip-cell ip-cell--initiatives ip-cell--heat' +
            (count > 0 ? ' is-clickable' : '') +
            (gapBefore ? ' mx-gap-before' : '') +
            (pillarDim ? ' is-pillar-dim' : '')
          }
          style={{ background: hc ? hc.fill : 'transparent' }}
          title={count > 0 ? `${count} initiative${count === 1 ? '' : 's'} · click for the collaboration network in this cell` : undefined}
          onClick={() => openInitNetwork(colId)}
        >
          <div className="ip-cell-inner ip-cell-inner--heat">
            {count > 0 && <span className={'ip-heat-num' + (hc.dark ? ' is-dark' : '')}>{count}</span>}
          </div>
        </td>
      );
    }

    // Initiatives band: dot-cell style, consistent with the Global Initiatives Map.
    // Dots are coloured by organisation type, matching the Global Heat Map (so
    // the same legend and key apply across both views).
    if (bandKey === 'initiatives') {
      // Dots stop propagation (→ open passport); a click on the cell background
      // opens the cell's collaboration network.
      return (
        <td
          key={colId}
          className={cellCls + (list.length > 0 ? ' is-clickable' : '')}
          title={list.length > 0 ? 'Click for the collaboration network in this cell · click a dot to open its passport' : undefined}
          onClick={() => openInitNetwork(colId)}
        >
          <div className="ip-cell-inner">
            {list.length === 0 ? (
              <span className="ip-empty">-</span>
            ) : (
              <div className="ip-dots">
                {list.map(o => (
                  <span
                    key={o.id}
                    className={'mx-dot ip-dot' + (o.id === highlightOrgId ? ' is-highlight' : '')}
                    style={{ background: ipTypeColor(o.type) }}
                    title={o.name + (o.type ? ' · ' + o.type : '')}
                    onClick={(e) => { e.stopPropagation(); onPickOrg(o); }}
                  />
                ))}
              </div>
            )}
          </div>
        </td>
      );
    }

    // Players band: chip-style with overflow toggle (preserves named identity).
    const isOpen = expanded[key];
    const visible = isOpen ? list : list.slice(0, MAX_VISIBLE);
    const overflow = list.length - visible.length;
    return (
      <td key={colId} className={cellCls}>
        <div className="ip-cell-inner">
          {visible.map(o => {
            const isAnchor = ANCHOR_NAMES.has(o.name);
            const isHighlight = o.id === highlightOrgId;
            return (
              <button key={o.id} type="button"
                className={'ip-chip ip-chip--' + bandKey + (isAnchor ? ' is-anchor' : '') + (isHighlight ? ' is-highlight' : '')}
                onClick={() => onPickOrg(o)}
                title={o.description || o.name}>
                <span className="ip-chip-name">{o.name}</span>
              </button>
            );
          })}
          {overflow > 0 && (
            <button type="button" className="ip-more"
              onClick={() => setExpanded({ ...expanded, [key]: true })}>
              +{overflow} more
            </button>
          )}
          {visible.length === 0 && <span className="ip-empty">-</span>}
        </div>
      </td>
    );
  }

  return (
    <div className="ip-view">
      {/* Toolbar */}
      <div className="ip-toolbar">
        <div className="ip-toolbar-group">
          <span className="ip-toolbar-label">Band</span>
          {['both','players','policymakers','initiatives'].map(v => (
            <button key={v} type="button"
              className={'ip-pill' + (band===v ? ' is-on' : '')}
              onClick={() => setBand(v)}>
              {v === 'both' ? 'All'
                : v === 'players' ? 'Industry players'
                : v === 'policymakers' ? 'Policymakers & regulators'
                : 'Initiatives'}
            </button>
          ))}
        </div>
        {barriersPresent.length > 0 && (
          <div className="ip-toolbar-group">
            <span className="ip-toolbar-label">Initiatives barriers</span>
            <select className="ip-select" value={barrierFilter || ''} onChange={(e) => setBarrierFilter(e.target.value || null)}>
              <option value="">All</option>
              {barriersPresent.map(b => <option key={b} value={b}>{b}</option>)}
            </select>
          </div>
        )}
        <button
          type="button"
          className={'mx-btn mx-btn--heat' + (heatmap ? ' is-on' : '')}
          onClick={() => setHeatmap(v => !v)}
          title="Show a heatmap of how many initiatives fall in each cell instead of the dots (responds to active filters)"
        >
          <span className="mx-btn-dot mx-btn-dot--heat"></span>
          {heatmap ? 'Show as dots' : 'Show as heatmap'}
        </button>
        {heatmap && (
          <div className="mx-heat-legend" title="Cell colour = number of initiatives in the cell">
            <span className="mx-heat-legend-cap">Fewer</span>
            <span className="mx-heat-legend-bar"></span>
            <span className="mx-heat-legend-cap">More{heatMax ? ` (max ${heatMax})` : ''}</span>
          </div>
        )}
        <div className="tbl-zoom" role="group" aria-label="Zoom">
          <button type="button" className="tbl-zoom-btn" onClick={zoomOut} disabled={zoom <= ZMIN} title="Zoom out" aria-label="Zoom out">−</button>
          <button type="button" className="tbl-zoom-val" onClick={() => setZoom(1)} title="Reset zoom to 100%">{Math.round(zoom * 100)}%</button>
          <button type="button" className="tbl-zoom-btn" onClick={zoomIn} disabled={zoom >= ZMAX} title="Zoom in" aria-label="Zoom in">+</button>
          <button type="button" className="tbl-zoom-fit" onClick={fitHeight} title="Fit the whole table height to the frame">Fit height</button>
        </div>
        <div className="ip-toolbar-hint">Same column structure as the global map · click any organization to open detail</div>
      </div>

      {/* Pillar filter - mirrors the Energy Ecosystem schematic's Powering /
          Grids cards. Highlights matching value-chain columns. */}
      <div className="mx-legend mx-legend--pillars">
        <span className="mx-legend-label">Filter by value-chain pillar</span>
        {[
          { id: 'powering', label: 'Energy access', dot: '#fdce07' },
          { id: 'grids',    label: 'Grid modernization',  dot: '#84c98f' },
        ].map(p => {
          const on  = pillarFilter.includes(p.id);
          const off = pillarFilter.length > 0 && !on;
          return (
            <button
              key={p.id}
              type="button"
              className={'mx-legend-item is-clickable' + (on ? ' is-on' : '') + (off ? ' is-faded' : '')}
              onClick={() => togglePillarFilter(p.id)}
              title={on ? `Stop highlighting ${p.label}` : `Highlight ${p.label} columns`}
            >
              <span className="mx-legend-dot" style={{ background: p.dot }}></span>
              {p.label}
            </button>
          );
        })}
        {pillarFilter.length > 0 && (
          <button type="button" className="mx-legend-clear" onClick={() => setPillarFilter([])}>Clear</button>
        )}
      </div>

      {/* Dot-colour legend - the coloured dots only appear in the Initiatives
          band (the player/policymaker/tech-provider bands name their orgs
          directly), so the key is scoped to initiative organisation types
          (feedback: the bands already make the other orgs clear). Labels are
          canonicalised to match the Global Initiatives Heat Map's facet names.
          Clickable: each chip toggles a filter on filters.types (passes() in
          app.jsx). */}
      <div className="mx-legend mx-legend--types">
        <span className="mx-legend-label">Initiative types</span>
        {(() => {
          // Group the initiatives-band types by canonical label so differing
          // spellings (e.g. "MDB/IFI/DFI" vs "MDB/ IFI") collapse into one
          // entry - but remember every raw type behind a label so the click
          // toggles all of them together in filters.types. Computed from the
          // unfiltered set so the key stays stable while a type is filtered.
          const source = (allOrgs && allOrgs.length) ? allOrgs : orgs;
          const byLabel = new Map(); // label → { color, types: [] }
          for (const o of source) {
            if (!o.type || bandFor(o) !== 'initiatives') continue;
            const { label, color } = ipTypeCanon(o.type);
            if (byLabel.has(label)) {
              const e = byLabel.get(label);
              if (!e.types.includes(o.type)) e.types.push(o.type);
            } else {
              byLabel.set(label, { color, types: [o.type] });
            }
          }
          const activeTypes = filters.types || [];
          const anyActive = activeTypes.length > 0;
          const items = [...byLabel.entries()];
          return (
            <>
              {items.map(([label, { color, types }]) => {
                const isActive = types.some(t => activeTypes.includes(t));
                return (
                  <button
                    key={label}
                    type="button"
                    className={'mx-legend-item is-clickable' + (isActive ? ' is-on' : '') + (anyActive && !isActive ? ' is-faded' : '')}
                    title={isActive ? `Remove filter: ${label}` : `Filter to ${label}`}
                    onClick={(e) => {
                      e.stopPropagation();
                      let next;
                      if (isActive) {
                        next = activeTypes.filter(x => !types.includes(x));
                      } else {
                        next = [...activeTypes, ...types.filter(x => !activeTypes.includes(x))];
                      }
                      setFilters({ ...filters, types: next });
                    }}
                  >
                    <span className="mx-legend-dot" style={{ background: color }}></span>
                    {label}
                  </button>
                );
              })}
              {anyActive && (
                <button
                  type="button"
                  className="mx-legend-clear"
                  onClick={(e) => { e.stopPropagation(); setFilters({ ...filters, types: [] }); }}
                >
                  Clear
                </button>
              )}
            </>
          );
        })()}
      </div>

      {/* Two-band table */}
      <div className="ip-tablewrap" ref={dragScroll.ref} onMouseDown={dragScroll.onMouseDown}>
        <table className="ip-table" ref={tableRef} style={{ zoom }}>
          <thead>
            <tr className="ip-grouphead">
              <th className="ip-rowhead-corner"></th>
              {IP_GROUPS.map(g => {
                const groupHasMatch = g.cols.some(c => ipColMatchesPillar(c.id, pillarFilter));
                const groupDim = pillarFilter.length > 0 && !groupHasMatch;
                return (
                  <th
                    key={g.id}
                    colSpan={g.cols.length}
                    className={'ip-grouphead-cell' + (g.gapBefore ? ' mx-gap-before' : '') + (groupDim ? ' is-pillar-dim' : '')}
                    style={{ background: `linear-gradient(${g.tint}, ${g.tint}), var(--panel)` }}
                  >
                    <span>{g.label}</span>
                  </th>
                );
              })}
            </tr>
            <tr className="ip-colhead">
              <th className="ip-rowhead-corner"></th>
              {IP_COLS.map(c => {
                const dim = pillarFilter.length > 0 && !ipColMatchesPillar(c.id, pillarFilter);
                return (
                  <th
                    key={c.id}
                    className={'ip-colhead-cell' + (c.gapBefore ? ' mx-gap-before' : '') + (dim ? ' is-pillar-dim' : '')}
                  >
                    <div className="ip-col-label">{c.label}</div>
                    <div className="ip-col-sub">{c.sub}</div>
                  </th>
                );
              })}
            </tr>
          </thead>
          <tbody>
            {(band === 'both' || band === 'initiatives') && (
              <tr className="ip-band ip-band--initiatives">
                <th scope="row" className="ip-rowhead">
                  <div className="ip-rowhead-title">Initiatives &amp; programmes</div>
                  <div className="ip-rowhead-sub">Interventions sitting on top of the system. Dot colour = organization type (see legend above).</div>
                </th>
                {IP_COLS.map(c => renderCell('initiatives', c.id, c.gapBefore))}
              </tr>
            )}
            {(band === 'both' || band === 'players') && (
              <tr className="ip-band ip-band--players">
                <th scope="row" className="ip-rowhead">
                  <div className="ip-rowhead-title">Industry players</div>
                  <div className="ip-rowhead-sub">Build, finance and operate the system. <b>Bold</b> = anchor organisations.</div>
                </th>
                {IP_COLS.map(c => renderCell('players', c.id, c.gapBefore))}
              </tr>
            )}
            {(band === 'both' || band === 'policymakers') && (
              <tr className="ip-band ip-band--policymakers">
                <th scope="row" className="ip-rowhead">
                  <div className="ip-rowhead-title">Policymakers &amp; regulators</div>
                  <div className="ip-rowhead-sub">Ministries &amp; regulators that set the rules of the system. <b>Bold</b> = anchor organisations.</div>
                </th>
                {IP_COLS.map(c => renderCell('policymakers', c.id, c.gapBefore))}
              </tr>
            )}
          </tbody>
        </table>
      </div>

      <p className="ip-foot">
        Each organization sits in the column representing its primary energy ecosystem role. Anchor players are shown bold; the most important organizations any expert would expect to see. <em>"+N more"</em> indicates additional entries - click to expand. For illustrative purposes only - Systemiq 2026.
      </p>

      {/* Cell collaboration network - reuses the Global Heat Map's modal with
          India-specific dot colours. */}
      {popup && CellNetworkModal && (
        <CellNetworkModal
          title={popup.title}
          subtitle={popup.subtitle}
          orgs={popup.orgs}
          edges={edges}
          onPickOrg={onPickOrg}
          colorFor={(o) => ipTypeColor(o.type)}
          typePalette={IP_TYPE_COLOR}
          hideNetwork={true}
          onClose={() => setPopup(null)}
        />
      )}
    </div>
  );
}

window.IndiaPlayersView = IndiaPlayersView;
