// IndiaNetworkView.jsx - Regional Collaboration Network · India (dummy)
// Mirrors the global NetworkView layout, but operates on the India dataset
// and generates synthetic edges so the structure can be reviewed before the
// real collaboration data is collected.

const { useMemo: useMemoIN, useState: useStateIN, useEffect: useEffectIN } = React;

// India archetype palette - covers the organisation types present in INDIA_DATA
// (Regional overview, v22_06). Distinct, readable hues since colour = identity
// in the node-link view.
const TYPE_COLORS_IN = {
  'Industry player':                          '#0a0a0a',
  'MDB/IFI/DFI':                              '#3b6ea8',
  'Private investor':                         '#7a5da8',
  'Financial intermediary (domestic)':        '#0e2c66',
  'Philanthropy':                             '#e07b4a',
  'NGO':                                      '#d9b441',
  'Coalition or multi-stakeholder platform':  '#5a8c66',
  'Intergovernmental organization (IGO)':     '#8c5983',
  'Bilateral development agency':             '#c87a4e',
  'Policymakers and regulators':              '#1a4d6b',
  'Regulated infrastructure operator':        '#2a9d8f',
};

function IndiaNetworkView({ orgs, allOrgs, edges, onPickOrg, highlightOrgId, dataset }) {
  const facets = (dataset || window.INITIATIVES_DATA).facets;
  const [hover, setHover] = useStateIN(null);
  const [positions, setPositions] = useStateIN(null);
  // Filter sets - drive node dimming (mirrors the global Collaboration Network).
  const [filterTypes,    setFilterTypes]    = useStateIN(() => new Set());
  const [filterBarriers, setFilterBarriers] = useStateIN(() => new Set());

  const W = 1600, H = 1000;

  const COMMUNITIES_R    = 110;
  const COMMUNITIES_EXCL = 195;

  // Real collaboration edges from the global dataset, filtered to those
  // between visible India-relevant orgs.
  const visibleIds = useMemoIN(() => new Set(orgs.map(o => o.id)), [orgs]);
  const liveEdges = useMemoIN(
    () => (edges || []).filter(e => visibleIds.has(e.a) && visibleIds.has(e.b)),
    [edges, visibleIds]
  );

  const degree = useMemoIN(() => {
    const m = new Map();
    for (const o of orgs) m.set(o.id, 0);
    for (const e of liveEdges) {
      m.set(e.a, (m.get(e.a) || 0) + 1);
      m.set(e.b, (m.get(e.b) || 0) + 1);
    }
    return m;
  }, [orgs, liveEdges]);

  useEffectIN(() => {
    const cx = W / 2, cy = H / 2;
    // Hard clamp - projects a position outside the Communities exclusion zone
    // if it has drifted inside. Always called after integration so the rule
    // is satisfied at every step (not just on average).
    const clampOutside = (p) => {
      const ex = p.x - cx, ey = p.y - cy;
      const dist = Math.sqrt(ex * ex + ey * ey);
      if (dist < COMMUNITIES_EXCL) {
        const target = COMMUNITIES_EXCL + 8;
        if (dist < 0.5) {
          // Effectively at the centre - shove out in a stable direction.
          p.x = cx + target; p.y = cy;
        } else {
          const scale = target / dist;
          p.x = cx + ex * scale;
          p.y = cy + ey * scale;
        }
        // Kill inward velocity so the spring doesn't immediately pull back in.
        p.vx = 0; p.vy = 0;
      }
    };

    const sorted = [...orgs].sort((a, b) => degree.get(b.id) - degree.get(a.id));
    const n = sorted.length;
    const pos = new Map();
    sorted.forEach((o, i) => {
      const deg = degree.get(o.id);
      // Start everything well outside the exclusion zone, on three loose rings.
      // Horizontal stretch matches the 1.6:1 canvas aspect ratio so the seed
      // positions fill an ellipse, not a tiny central circle.
      const r = deg === 0 ? 440 : (deg > 8 ? 260 : 360);
      const a = (i / n) * Math.PI * 2 + (deg === 0 ? Math.PI / 5 : 0);
      pos.set(o.id, {
        x: cx + Math.cos(a) * r * 1.5 + (Math.random() - 0.5) * 60,
        y: cy + Math.sin(a) * r + (Math.random() - 0.5) * 60,
        vx: 0, vy: 0,
      });
    });

    const ITER = 460;
    const REPULSE = 22000;
    const SPRING = 0.04;
    const SPRING_LEN = 230;
    const CENTER_PULL = 0.004;
    const DAMP = 0.80;
    // Elliptical "soft canvas" - nodes are clamped to lie inside an ellipse
    // that fits the visible area. Removes the rectangular corners that the
    // previous bounds clamp turned into accumulation zones.
    const ELLIPSE_A = W / 2 - 70;   // ~730
    const ELLIPSE_B = H / 2 - 50;   // ~450

    const ids = orgs.map(o => o.id);
    for (let it = 0; it < ITER; it++) {
      for (let i = 0; i < ids.length; i++) {
        const a = pos.get(ids[i]);
        for (let j = i + 1; j < ids.length; j++) {
          const b = pos.get(ids[j]);
          let dx = a.x - b.x, dy = a.y - b.y;
          let d2 = dx * dx + dy * dy;
          if (d2 < 1) d2 = 1;
          const f = REPULSE / d2;
          const d = Math.sqrt(d2);
          const fx = (dx / d) * f, fy = (dy / d) * f;
          a.vx += fx; a.vy += fy;
          b.vx -= fx; b.vy -= fy;
        }
      }
      for (const e of liveEdges) {
        const a = pos.get(e.a), b = pos.get(e.b);
        if (!a || !b) continue;
        const dx = b.x - a.x, dy = b.y - a.y;
        const d = Math.sqrt(dx * dx + dy * dy) || 1;
        const f = (d - SPRING_LEN) * SPRING;
        const fx = (dx / d) * f, fy = (dy / d) * f;
        a.vx += fx; a.vy += fy;
        b.vx -= fx; b.vy -= fy;
      }
      for (const id of ids) {
        const p = pos.get(id);
        p.vx += (cx - p.x) * CENTER_PULL;
        p.vy += (cy - p.y) * CENTER_PULL;
        const ex = p.x - cx, ey = p.y - cy;
        const dist = Math.sqrt(ex * ex + ey * ey) || 1;
        if (dist < COMMUNITIES_EXCL) {
          const push = (COMMUNITIES_EXCL - dist) * 1.2;
          p.vx += (ex / dist) * push;
          p.vy += (ey / dist) * push;
        }
        p.vx *= DAMP; p.vy *= DAMP;
        p.x += p.vx; p.y += p.vy;
        // Elliptical clamp - project nodes back onto the ellipse boundary
        // when they drift outside it. Removes the rectangular corners.
        const nx = (p.x - cx) / ELLIPSE_A;
        const ny = (p.y - cy) / ELLIPSE_B;
        const norm = Math.sqrt(nx * nx + ny * ny);
        if (norm > 1) {
          p.x = cx + (p.x - cx) / norm;
          p.y = cy + (p.y - cy) / norm;
          p.vx *= 0.3; p.vy *= 0.3;  // shed inertia so they don't bounce
        }
        clampOutside(p);
      }
    }
    const arr = [...pos.entries()].map(([id, p]) => ({ id, p }));
    const LABEL_W = 180, LABEL_H = 26;
    const ellipseClamp = (p) => {
      const nx = (p.x - cx) / ELLIPSE_A;
      const ny = (p.y - cy) / ELLIPSE_B;
      const norm = Math.sqrt(nx * nx + ny * ny);
      if (norm > 1) {
        p.x = cx + (p.x - cx) / norm;
        p.y = cy + (p.y - cy) / norm;
      }
    };
    for (let pass = 0; pass < 40; pass++) {
      let moved = false;
      for (let i = 0; i < arr.length; i++) {
        for (let j = i + 1; j < arr.length; j++) {
          const a = arr[i].p, b = arr[j].p;
          const dx = Math.abs(a.x - b.x), dy = Math.abs(a.y - b.y);
          if (dx < LABEL_W && dy < LABEL_H) {
            const push = (LABEL_H - dy) / 2 + 1;
            if (a.y < b.y) { a.y -= push; b.y += push; }
            else { a.y += push; b.y -= push; }
            ellipseClamp(a); ellipseClamp(b);
            moved = true;
          }
        }
      }
      if (!moved) break;
    }
    // Final guarantee: no node sits inside the Communities exclusion zone,
    // even if the label de-collision pass nudged something inward.
    for (const id of ids) clampOutside(pos.get(id));
    setPositions(pos);
  }, [orgs, liveEdges]);

  if (!positions) return <div style={{ padding: 40, color: 'var(--text-mid)' }}>Computing layout...</div>;

  const activeId = hover || highlightOrgId;
  const focusOrg = activeId ? orgs.find(o => o.id === activeId) : null;
  const focusNeighbors = new Set();
  if (focusOrg) {
    focusNeighbors.add(focusOrg.id);
    for (const e of liveEdges) {
      if (e.a === focusOrg.id) focusNeighbors.add(e.b);
      if (e.b === focusOrg.id) focusNeighbors.add(e.a);
    }
  }


  // Show only the archetype types actually present in this dataset, plus a
  // few canonical legend entries. Exclude industry players, financial intermediaries,
  // policymakers, and regulated infrastructure operators from the India network filter.
  const typesInUse = facets.types.order.filter(t => TYPE_COLORS_IN[t] && ![
    'Industry player',
    'Financial intermediary (domestic)',
    'Policymakers and regulators',
    'Regulated infrastructure operator'
  ].includes(t));

  // Filtering - mirrors the global Collaboration Network: clickable Type and
  // Barrier chips dim the non-matching nodes. (No value-chain filter - removed
  // from the global view too.)
  const anyFilterActive = filterTypes.size + filterBarriers.size > 0;
  const matchesFilters = (o) => {
    if (filterTypes.size && !filterTypes.has(o.type)) return false;
    if (filterBarriers.size) {
      const bs = o.barriers || [];
      let any = false;
      for (const b of bs) if (filterBarriers.has(b)) { any = true; break; }
      if (!any) return false;
    }
    return true;
  };
  const toggleIn = (set, setter) => (v) => {
    const next = new Set(set);
    if (next.has(v)) next.delete(v); else next.add(v);
    setter(next);
  };
  const toggleType    = toggleIn(filterTypes,    setFilterTypes);
  const toggleBarrier = toggleIn(filterBarriers, setFilterBarriers);

  // Barriers present in the visible orgs - only show chips that can match.
  const barriersPresent = (() => {
    const s = new Set();
    for (const o of orgs) for (const b of (o.barriers || [])) s.add(b);
    return [...s].sort();
  })();

  return (
    <div>
      {/* Filter legend - clickable chips for type / barrier */}
      <div className="net-filters">
        <div className="net-filter-row">
          <span className="net-filter-label">Type</span>
          {typesInUse.map(t => {
            const on = filterTypes.has(t);
            const off = filterTypes.size > 0 && !on;
            return (
              <button
                key={t}
                type="button"
                className={'net-filter-chip is-clickable' + (on ? ' is-on' : '') + (off ? ' is-faded' : '')}
                onClick={() => toggleType(t)}
                title={on ? `Remove ${t} filter` : `Filter to ${t}`}
              >
                <span className="net-legend-dot" style={{ background: TYPE_COLORS_IN[t] }}></span>
                {facets.types.labels[t]}
              </button>
            );
          })}
        </div>
        {barriersPresent.length > 0 && (
          <div className="net-filter-row">
            <span className="net-filter-label">Barrier</span>
            {barriersPresent.map(b => {
              const on = filterBarriers.has(b);
              const off = filterBarriers.size > 0 && !on;
              return (
                <button
                  key={b}
                  type="button"
                  className={'net-filter-chip is-clickable' + (on ? ' is-on' : '') + (off ? ' is-faded' : '')}
                  onClick={() => toggleBarrier(b)}
                  title={on ? `Remove ${b} filter` : `Filter to ${b}`}
                >
                  {b}
                </button>
              );
            })}
          </div>
        )}
        {anyFilterActive && (
          <button
            type="button"
            className="net-filter-clear"
            onClick={() => { setFilterTypes(new Set()); setFilterBarriers(new Set()); }}
          >
            Clear filters
          </button>
        )}
      </div>

      <div className="net-canvas">
        <svg viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMid meet">
          <g className="net-edges">
            {liveEdges.map((e, i) => {
              const pa = positions.get(e.a), pb = positions.get(e.b);
              if (!pa || !pb) return null;
              const isActive = focusOrg && (e.a === focusOrg.id || e.b === focusOrg.id);
              return (
                <line
                  key={i}
                  x1={pa.x} y1={pa.y} x2={pb.x} y2={pb.y}
                  className={'net-edge' + (isActive ? ' is-active' : '')}
                  strokeWidth={isActive ? 1.6 : 0.8}
                  opacity={focusOrg && !isActive ? 0.05 : undefined}
                />
              );
            })}
          </g>

          <g className="net-communities" transform={`translate(${W / 2},${H / 2})`} aria-label="Communities and local leadership">
            <circle r={COMMUNITIES_R} fill="#2e8055" fillOpacity="0.55" stroke="#84c98f" strokeOpacity="0.85" strokeWidth="3" />
            <text
              textAnchor="middle"
              fontSize="24"
              fontWeight="700"
              fill="#e8f5ee"
              style={{ pointerEvents: 'none', userSelect: 'none', letterSpacing: '0.02em' }}
            >
              <tspan x="0" dy="-10">Communities and</tspan>
              <tspan x="0" dy="28">local leadership</tspan>
            </text>
          </g>

          <g className="net-nodes">
            {orgs.map(o => {
              const p = positions.get(o.id);
              if (!p) return null;
              const deg = degree.get(o.id) || 0;
              const r = 5 + Math.sqrt(deg) * 2.8;
              const dim = (focusOrg && !focusNeighbors.has(o.id)) || (anyFilterActive && !matchesFilters(o));
              const color = TYPE_COLORS_IN[o.type] || '#84c98f';
              const isFocus = focusNeighbors.has(o.id) || activeId === o.id;
              const labelText = o.name.length > 30 ? o.name.slice(0, 28) + '...' : o.name;
              return (
                <g
                  key={o.id}
                  transform={`translate(${p.x},${p.y})`}
                  className={'net-node' + (activeId === o.id ? ' is-focus' : '')}
                  style={{ opacity: dim ? 0.18 : 1, cursor: 'pointer' }}
                  onMouseEnter={() => setHover(o.id)}
                  onMouseLeave={() => setHover(null)}
                  onClick={() => onPickOrg(o)}
                >
                  <circle className="halo" r={r + 7} />
                  <circle r={r} fill={color} stroke={color} strokeWidth="1.2" fillOpacity="0.92" />
                  <text
                    className={'net-label' + (isFocus ? ' is-focus' : '')}
                    y={-(r + 7)}
                    textAnchor="middle"
                  >
                    {labelText}
                  </text>
                </g>
              );
            })}
          </g>
        </svg>
      </div>

      <p className="net-foot">
        Organisations active in India’s energy ecosystem and their publicly recorded partnerships. Hover a node to highlight its partners. Click for full details. Edges are symmetric - each pair appears once.
      </p>
    </div>
  );
}

window.IndiaNetworkView = IndiaNetworkView;
