// IEDF 2026 — chart components (SVG, hand-rolled, editorial style)
const { useState, useEffect, useRef, useMemo } = React;

// ---------- palette helpers ----------
const SPECTRAL9 = ['#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd'];
const SPECTRAL5 = ['#d53e4f','#fdae61','#ffffbf','#abdda4','#3288bd'];
const INK = '#1d1d1f';
const BRAND_BLUE = '#275b9b';

// t in [0,1] — 0 = worst, 1 = best. Returns a bin color (best → blue/green end).
function semaforo(t, palette) {
  const p = palette || SPECTRAL9;
  const i = Math.max(0, Math.min(p.length - 1, Math.floor(t * p.length)));
  return p[i];
}

// rank-based color: rank 1 = best
function rankColor(rank, total, palette) {
  return semaforo(1 - (rank - 0.5) / total, palette);
}

function useMeasure() {
  const ref = useRef(null);
  const [w, setW] = useState(0);
  useEffect(() => {
    const measure = () => {
      if (ref.current) setW(ref.current.getBoundingClientRect().width);
    };
    measure();
    window.addEventListener('resize', measure);
    let ro = null;
    if (window.ResizeObserver && ref.current) {
      ro = new ResizeObserver(measure);
      ro.observe(ref.current);
    }
    return () => {
      window.removeEventListener('resize', measure);
      if (ro) ro.disconnect();
    };
  }, []);
  return [ref, w];
}

const fmt = (v, dec = 1) => v == null ? '—' : v.toLocaleString('es-MX', { maximumFractionDigits: dec, minimumFractionDigits: 0 });

// ---------- shared tooltip ----------
function Tooltip({ tip }) {
  if (!tip) return null;
  return (
    <div style={{
      position: 'fixed', left: Math.min(tip.x + 14, window.innerWidth - 240), top: tip.y + 12,
      zIndex: 50, pointerEvents: 'none', background: INK, color: '#faf9f6',
      padding: '10px 12px', fontSize: 13, lineHeight: 1.45, maxWidth: 230,
      fontFamily: "'Alegreya Sans', 'Helvetica Neue', sans-serif", boxShadow: '0 6px 24px rgba(0,0,0,.25)'
    }}>{tip.body}</div>
  );
}

// ---------- ranking bars ----------
function RankingChart({ data, metric, palette, selected, onSelect, setTip }) {
  const [wrapRef, width] = useMeasure();
  const sorted = useMemo(() =>
    [...data].sort((a, b) => b[metric] - a[metric]), [data, metric]);
  const posOf = useMemo(() => {
    const m = {};
    sorted.forEach((d, i) => m[d.entidad_std] = i);
    return m;
  }, [sorted]);
  const max = Math.max(...data.map(d => d[metric]));
  const ROW = 30, LABELW = 200, VALW = 52;
  const barMax = Math.max(60, width - LABELW - VALW);
  const n = data.length;

  return (
    <div ref={wrapRef} style={{ position: 'relative', height: n * ROW }}>
      {width > 0 && data.map(d => {
        const i = posOf[d.entidad_std];
        const isSel = selected === d.entidad_std;
        const w = Math.max(2, (d[metric] / max) * barMax);
        const color = rankColor(i + 1, n, palette);
        return (
          <div key={d.entidad_std}
            onClick={() => onSelect(d.entidad_std)}
            onMouseMove={e => setTip({
              x: e.clientX, y: e.clientY,
              body: (<div>
                <div style={{ fontWeight: 700, fontSize: 14 }}>{d.entidad}</div>
                <div style={{ opacity: .75, marginBottom: 4 }}>Posición {d.ranking} de 32 en el IEDF</div>
                <div>IEDF <b style={{ float: 'right' }}>{fmt(d.iedf_total)}</b></div>
                <div>Estructura <b style={{ float: 'right' }}>{fmt(d.estructura)}</b></div>
                <div>Operación <b style={{ float: 'right' }}>{fmt(d.operacion)}</b></div>
                <div>Resultado <b style={{ float: 'right' }}>{fmt(d.resultado)}</b></div>
                <div style={{ opacity: .6, marginTop: 4, fontSize: 12 }}>Clic para ver el perfil</div>
              </div>)
            })}
            onMouseLeave={() => setTip(null)}
            style={{
              position: 'absolute', top: 0, left: 0, right: 0, height: ROW,
              transform: `translateY(${i * ROW}px)`,
              transition: 'transform .6s cubic-bezier(.22,1,.36,1)',
              display: 'flex', alignItems: 'center', cursor: 'pointer',
              background: isSel ? 'rgba(39,91,155,.07)' : 'transparent'
            }}>
            <div style={{
              width: LABELW, flex: 'none', display: 'flex', alignItems: 'baseline', gap: 7,
              paddingRight: 10, justifyContent: 'flex-end'
            }}>
              <span style={{
                fontFamily: "Archivo, 'Helvetica Neue', sans-serif", fontWeight: 700, fontSize: 11,
                color: '#9b9b94', minWidth: 16, textAlign: 'right'
              }}>{i + 1}</span>
              <span style={{
                fontSize: 13, fontWeight: isSel ? 700 : 400, color: INK,
                whiteSpace: 'nowrap'
              }}>{d.entidad_std}</span>
            </div>
            <div style={{
              width: w, height: 17, background: color,
              outline: isSel ? `2px solid ${INK}` : '1px solid rgba(0,0,0,.14)',
              outlineOffset: isSel ? 1 : -1,
              transition: 'width .6s cubic-bezier(.22,1,.36,1)'
            }}></div>
            <span style={{
              marginLeft: 7, fontFamily: "Archivo, 'Helvetica Neue', sans-serif", fontWeight: 600,
              fontSize: 11.5, color: '#55554f'
            }}>{fmt(d[metric])}</span>
          </div>
        );
      })}
    </div>
  );
}

// ---------- scatter ----------
function ScatterChart({ data, xKey, yKey, palette, selected, onSelect, setTip, labels }) {
  const [wrapRef, width] = useMeasure();
  const H = 480, PAD = { t: 18, r: 24, b: 44, l: 52 };
  const w = Math.max(320, width);
  const iw = w - PAD.l - PAD.r, ih = H - PAD.t - PAD.b;
  const sx = v => PAD.l + (v / 100) * iw;
  const sy = v => PAD.t + ih - (v / 100) * ih;
  const med = arr => {
    const s = [...arr].sort((a, b) => a - b);
    return (s[Math.floor((s.length - 1) / 2)] + s[Math.ceil((s.length - 1) / 2)]) / 2;
  };
  const mx = med(data.map(d => d[xKey])), my = med(data.map(d => d[yKey]));
  const ticks = [0, 25, 50, 75, 100];
  const sortedByTotal = [...data].sort((a, b) => b.iedf_total - a.iedf_total);

  return (
    <div ref={wrapRef}>
      {width > 0 && (
        <svg width={w} height={H} style={{ display: 'block', overflow: 'visible' }}>
          {ticks.map(t => (
            <g key={t}>
              <line x1={sx(t)} y1={PAD.t} x2={sx(t)} y2={PAD.t + ih} stroke="#e8e6df" strokeWidth="1"></line>
              <line x1={PAD.l} y1={sy(t)} x2={PAD.l + iw} y2={sy(t)} stroke="#e8e6df" strokeWidth="1"></line>
              <text x={sx(t)} y={H - 22} textAnchor="middle" fontSize="11" fill="#9b9b94" fontFamily="Archivo, sans-serif">{t}</text>
              <text x={PAD.l - 10} y={sy(t) + 4} textAnchor="end" fontSize="11" fill="#9b9b94" fontFamily="Archivo, sans-serif">{t}</text>
            </g>
          ))}
          <line x1={sx(mx)} y1={PAD.t} x2={sx(mx)} y2={PAD.t + ih} stroke={INK} strokeWidth="1" strokeDasharray="4 4" opacity=".45"></line>
          <line x1={PAD.l} y1={sy(my)} x2={PAD.l + iw} y2={sy(my)} stroke={INK} strokeWidth="1" strokeDasharray="4 4" opacity=".45"></line>
          <text x={sx(mx) + 6} y={PAD.t + 12} fontSize="10.5" fill="#76766f" fontFamily="Archivo, sans-serif" fontWeight="600">MEDIANA</text>
          <text x={w - PAD.r} y={H - 6} textAnchor="end" fontSize="12" fill={INK} fontFamily="Archivo, sans-serif" fontWeight="700">{labels[xKey]} →</text>
          <text x={14} y={PAD.t + 6} fontSize="12" fill={INK} fontFamily="Archivo, sans-serif" fontWeight="700" transform={`rotate(-90 14 ${PAD.t + 6})`} textAnchor="end">{labels[yKey]} →</text>
          {sortedByTotal.map(d => {
            const isSel = selected === d.entidad_std;
            const r = 5 + (d.iedf_total / 62) * 9;
            return (
              <g key={d.entidad_std} style={{ cursor: 'pointer' }}
                onClick={() => onSelect(d.entidad_std)}
                onMouseMove={e => setTip({
                  x: e.clientX, y: e.clientY,
                  body: (<div>
                    <div style={{ fontWeight: 700, fontSize: 14 }}>{d.entidad}</div>
                    <div>{labels[xKey]} <b style={{ float: 'right' }}>{fmt(d[xKey])}</b></div>
                    <div>{labels[yKey]} <b style={{ float: 'right' }}>{fmt(d[yKey])}</b></div>
                    <div style={{ opacity: .75 }}>IEDF {fmt(d.iedf_total)} · {d.ranking}º</div>
                  </div>)
                })}
                onMouseLeave={() => setTip(null)}>
                <circle cx={sx(d[xKey])} cy={sy(d[yKey])} r={r}
                  fill={rankColor(d.ranking, data.length, palette)}
                  stroke={isSel ? INK : 'rgba(0,0,0,.3)'} strokeWidth={isSel ? 2.5 : 1}
                  style={{ transition: 'cx .5s, cy .5s' }}></circle>
                {(isSel || d.ranking <= 3 || d.ranking >= 30) && (
                  <text x={sx(d[xKey])} y={sy(d[yKey]) - r - 5} textAnchor="middle"
                    fontSize="11" fontWeight="700" fill={INK} fontFamily="Archivo, sans-serif"
                    style={{ paintOrder: 'stroke', stroke: '#faf9f6', strokeWidth: 3 }}>{d.entidad_std}</text>
                )}
              </g>
            );
          })}
        </svg>
      )}
    </div>
  );
}

// ---------- time series ----------
function SeriesChart({ seriesData, indicator, focus, palette, setTip, focusColors }) {
  const [wrapRef, width] = useMeasure();
  const H = 420, PAD = { t: 16, r: 16, b: 36, l: 56 };
  const w = Math.max(320, width);
  const iw = w - PAD.l - PAD.r, ih = H - PAD.t - PAD.b;

  const { years, vmin, vmax, medianLine } = useMemo(() => {
    const ys = new Set(); let lo = Infinity, hi = -Infinity;
    const byYear = {};
    for (const ent of Object.keys(seriesData)) {
      for (const [y, v] of seriesData[ent]) {
        ys.add(y); lo = Math.min(lo, v); hi = Math.max(hi, v);
        (byYear[y] = byYear[y] || []).push(v);
      }
    }
    const years = [...ys].sort((a, b) => a - b);
    const medianLine = years.map(y => {
      const s = byYear[y].sort((a, b) => a - b);
      return [y, (s[Math.floor((s.length - 1) / 2)] + s[Math.ceil((s.length - 1) / 2)]) / 2];
    });
    const pad = (hi - lo) * 0.06;
    return { years, vmin: Math.max(0, lo - pad), vmax: hi + pad, medianLine };
  }, [seriesData]);

  const sx = y => PAD.l + ((y - years[0]) / Math.max(1, years[years.length - 1] - years[0])) * iw;
  const sy = v => PAD.t + ih - ((v - vmin) / (vmax - vmin)) * ih;
  const path = pts => pts.map((p, i) => `${i ? 'L' : 'M'}${sx(p[0]).toFixed(1)},${sy(p[1]).toFixed(1)}`).join(' ');
  const yticks = useMemo(() => {
    const n = 5, out = [];
    for (let i = 0; i <= n; i++) out.push(vmin + (i / n) * (vmax - vmin));
    return out;
  }, [vmin, vmax]);

  return (
    <div ref={wrapRef}>
      {width > 0 && years.length > 0 && (
        <svg width={w} height={H} style={{ display: 'block', overflow: 'visible' }}>
          {yticks.map((t, i) => (
            <g key={i}>
              <line x1={PAD.l} y1={sy(t)} x2={w - PAD.r} y2={sy(t)} stroke="#e8e6df"></line>
              <text x={PAD.l - 8} y={sy(t) + 4} textAnchor="end" fontSize="11" fill="#9b9b94" fontFamily="Archivo, sans-serif">{fmt(t, t < 10 ? 1 : 0)}</text>
            </g>
          ))}
          {years.map(y => (
            <text key={y} x={sx(y)} y={H - 14} textAnchor="middle" fontSize="11" fill="#9b9b94" fontFamily="Archivo, sans-serif">{y}</text>
          ))}
          {Object.keys(seriesData).map(ent => !focus.includes(ent) && (
            <path key={ent} d={path(seriesData[ent])} fill="none" stroke="#d4d2c9" strokeWidth="1.2"></path>
          ))}
          <path d={path(medianLine)} fill="none" stroke={INK} strokeWidth="2.5" strokeDasharray="6 4"></path>
          {focus.map((ent, fi) => seriesData[ent] && (
            <g key={ent}>
              <path d={path(seriesData[ent])} fill="none" stroke={focusColors[fi % focusColors.length]} strokeWidth="3"></path>
              {seriesData[ent].map(([y, v]) => (
                <circle key={y} cx={sx(y)} cy={sy(v)} r="4.5" fill={focusColors[fi % focusColors.length]}
                  stroke="#faf9f6" strokeWidth="1.5" style={{ cursor: 'pointer' }}
                  onMouseMove={e => setTip({
                    x: e.clientX, y: e.clientY,
                    body: (<div><b>{ent}</b> · {y}<br></br>{fmt(v)} {indicator.unidad}</div>)
                  })}
                  onMouseLeave={() => setTip(null)}></circle>
              ))}
            </g>
          ))}
          <g>
            <line x1={w - PAD.r - 150} y1={PAD.t + 8} x2={w - PAD.r - 122} y2={PAD.t + 8} stroke={INK} strokeWidth="2.5" strokeDasharray="6 4"></line>
            <text x={w - PAD.r - 116} y={PAD.t + 12} fontSize="11.5" fill={INK} fontFamily="Archivo, sans-serif" fontWeight="600">Mediana nacional</text>
          </g>
        </svg>
      )}
    </div>
  );
}

// ---------- choropleth map of Mexico ----------
function MexicoMap({ data, metric, palette, selected, onSelect, onHover, setTip, metricLabel }) {
  const geo = window.MX_MAP;
  const byClave = useMemo(() => {
    const m = {};
    data.forEach(d => m[d.clave] = d);
    return m;
  }, [data]);
  const rankOf = useMemo(() => {
    const sorted = [...data].sort((a, b) => b[metric] - a[metric]);
    const m = {};
    sorted.forEach((d, i) => m[d.clave] = i + 1);
    return m;
  }, [data, metric]);
  if (!geo) return null;
  const n = data.length;
  const selSt = geo.states.find(st => byClave[st.c] && byClave[st.c].entidad_std === selected);
  return (
    <svg viewBox={`0 0 ${geo.w} ${geo.h}`} style={{ display: 'block', width: '100%', height: 'auto' }}>
      {geo.states.map(st => {
        const d = byClave[st.c];
        if (!d) return null;
        const rk = rankOf[st.c];
        return (
          <path key={st.c} d={st.d}
            fill={rankColor(rk, n, palette)}
            stroke="#faf9f6" strokeWidth="0.9" strokeLinejoin="round"
            style={{ cursor: 'pointer', transition: 'fill .4s' }}
            onClick={() => onSelect(d.entidad_std)}
            onMouseMove={e => { if (onHover) onHover(d.entidad_std); setTip({
              x: e.clientX, y: e.clientY,
              body: (<div>
                <div style={{ fontWeight: 700, fontSize: 14 }}>{d.entidad}</div>
                <div>{metricLabel} <b style={{ float: 'right' }}>{fmt(d[metric])}</b></div>
                <div style={{ opacity: .75 }}>{rk}º de 32 en {metricLabel.toLowerCase()}</div>
                <div style={{ opacity: .6, marginTop: 4, fontSize: 12 }}>Clic para ver el perfil</div>
              </div>)
            }); }}
            onMouseLeave={() => { if (onHover) onHover(null); setTip(null); }}></path>
        );
      })}

    </svg>
  );
}

// ---------- vertical column ranking ----------
const MX_ABBR = { 1:'Ags.', 2:'BC', 3:'BCS', 4:'Camp.', 5:'Coah.', 6:'Col.', 7:'Chis.', 8:'Chih.', 9:'CDMX', 10:'Dgo.', 11:'Gto.', 12:'Gro.', 13:'Hgo.', 14:'Jal.', 15:'Méx.', 16:'Mich.', 17:'Mor.', 18:'Nay.', 19:'NL', 20:'Oax.', 21:'Pue.', 22:'Qro.', 23:'Q.Roo', 24:'SLP', 25:'Sin.', 26:'Son.', 27:'Tab.', 28:'Tamps.', 29:'Tlax.', 30:'Ver.', 31:'Yuc.', 32:'Zac.' };

function ColumnChart({ data, metric, palette, selected, onSelect, setTip }) {
  const [wrapRef, width] = useMeasure();
  const n = data.length;
  const H = 350, TOP = 26, BOT = 46;
  const sorted = useMemo(() => [...data].sort((a, b) => b[metric] - a[metric]), [data, metric]);
  const posOf = useMemo(() => {
    const m = {};
    sorted.forEach((d, i) => m[d.entidad_std] = i);
    return m;
  }, [sorted]);
  const max = Math.max(...data.map(d => d[metric]));
  const ih = H - TOP - BOT;
  const pitch = width / n;
  const colW = Math.max(8, pitch - 7);
  return (
    <div ref={wrapRef}>
      {width > 0 && (
        <svg width={width} height={H} style={{ display: 'block', overflow: 'visible' }}>
          <line x1="0" y1={TOP + ih} x2={width} y2={TOP + ih} stroke={INK} strokeWidth="1.5"></line>
          {data.map(d => {
            const i = posOf[d.entidad_std];
            const isSel = selected === d.entidad_std;
            const h = Math.max(2, (d[metric] / max) * ih);
            const x = i * pitch + (pitch - colW) / 2;
            const y = TOP + ih - h;
            const color = rankColor(i + 1, n, palette);
            const cx = x + colW / 2;
            return (
              <g key={d.entidad_std} style={{ cursor: 'pointer' }}
                onClick={() => onSelect(d.entidad_std)}
                onMouseMove={e => setTip({
                  x: e.clientX, y: e.clientY,
                  body: (<div>
                    <div style={{ fontWeight: 700, fontSize: 14 }}>{d.entidad}</div>
                    <div style={{ opacity: .75, marginBottom: 4 }}>Posición {d.ranking} de 32 en el IEDF</div>
                    <div>IEDF <b style={{ float: 'right' }}>{fmt(d.iedf_total)}</b></div>
                    <div>Estructura <b style={{ float: 'right' }}>{fmt(d.estructura)}</b></div>
                    <div>Operación <b style={{ float: 'right' }}>{fmt(d.operacion)}</b></div>
                    <div>Resultado <b style={{ float: 'right' }}>{fmt(d.resultado)}</b></div>
                    <div style={{ opacity: .6, marginTop: 4, fontSize: 12 }}>Clic para ver el perfil</div>
                  </div>)
                })}
                onMouseLeave={() => setTip(null)}>
                <rect x={x - (pitch - colW) / 2} y={TOP - 14} width={pitch} height={H - TOP + 14} fill={isSel ? 'rgba(39,91,155,.07)' : 'transparent'}></rect>
                <rect x={x} y={y} width={colW} height={h} fill={color}
                  stroke={isSel ? INK : 'rgba(0,0,0,.18)'} strokeWidth={isSel ? 1.5 : 1}
                  style={{ transition: 'x .6s cubic-bezier(.22,1,.36,1), y .6s cubic-bezier(.22,1,.36,1), height .6s cubic-bezier(.22,1,.36,1)' }}></rect>
                <text x={cx} y={y - 6} textAnchor="middle" fontSize="10" fontWeight="700"
                  fontFamily="Archivo, sans-serif" fill="#55554f"
                  style={{ transition: 'x .6s cubic-bezier(.22,1,.36,1), y .6s cubic-bezier(.22,1,.36,1)' }}>{fmt(d[metric], 1)}</text>
                <text x={cx} y={TOP + ih + 10} textAnchor="end" fontSize="11"
                  fontFamily="Archivo, sans-serif" fontWeight={isSel ? 800 : 500} fill={isSel ? INK : '#55554f'}
                  transform={`rotate(-52 ${cx} ${TOP + ih + 10})`}
                  style={{ transition: 'x .6s cubic-bezier(.22,1,.36,1)' }}>{MX_ABBR[d.clave]}</text>
              </g>
            );
          })}
        </svg>
      )}
    </div>
  );
}

Object.assign(window, { SPECTRAL9, SPECTRAL5, INK, BRAND_BLUE, semaforo, rankColor, useMeasure, fmt, Tooltip, RankingChart, ScatterChart, SeriesChart, MexicoMap, ColumnChart, MX_ABBR });
