/* ==========================================================================
   FondAnalyse Pro - React frontend
   ==========================================================================
   Tous les composants et charts (SVG fait main) dans ce fichier.
   Charge via Babel-standalone (CDN) - transpilation au runtime.
   ========================================================================== */

const { useState, useEffect, useMemo, useCallback, useRef } = React;

/* ========== CONSTANTS ==================================================== */

const COLOR  = { bull: '#34d6a5', bear: '#ff4757', neut: '#f5c542' };
const TXT    = { bull: 'text-bull', bear: 'text-bear', neut: 'text-neut' };
const BORDER = { bull: 'border-bull', bear: 'border-bear', neut: 'border-neut' };
const ARROW  = { bull: '↑', bear: '↓', neut: '—' };
const TREND_CLS = { up: 'text-bull', down: 'text-bear', bull: 'text-bull', bear: 'text-bear', neut: 'text-ink', flat: 'text-ink' };

const fmt = (v, suffix = '') => (v === null || v === undefined) ? 'n/a' : `${v}${suffix}`;

/* ========== CHARTS ======================================================= */

function Sparkline({ data, color, width = 80, height = 28 }) {
  const { points, lastX, lastY } = useMemo(() => {
    if (!data || data.length === 0) return { points: '', lastX: 0, lastY: 0 };
    const min = Math.min(...data), max = Math.max(...data), range = max - min || 1;
    const pts = data.map((v, i) => {
      const x = (i / (data.length - 1)) * width;
      const y = height - ((v - min) / range) * (height - 4) - 2;
      return [x, y];
    });
    return {
      points: pts.map(p => p.join(',')).join(' '),
      lastX: pts[pts.length - 1][0],
      lastY: pts[pts.length - 1][1],
    };
  }, [data, width, height]);

  if (!data || data.length === 0) return <div style={{width, height}} className="text-[0.6rem] text-ink-mute">—</div>;
  return (
    <svg width={width} height={height} className="overflow-visible">
      <polyline points={points} fill="none" stroke={color} strokeWidth="1" strokeOpacity="0.75" strokeLinejoin="round" strokeLinecap="round" />
      <circle cx={lastX} cy={lastY} r="2" fill={color}>
        <animate attributeName="r" values="2;3;2" dur="2.4s" repeatCount="indefinite" />
      </circle>
    </svg>
  );
}

function AreaChart({ values, height = 180 }) {
  if (!values || values.length === 0) return <div className="text-ink-mute text-xs">Données indisponibles</div>;
  const width = 800, padX = 8, padTop = 16, padBottom = 24;
  const min = Math.min(...values), max = Math.max(...values), range = max - min || 1;

  const pts = values.map((v, i) => {
    const x = padX + (i / (values.length - 1)) * (width - padX * 2);
    const y = padTop + (1 - (v - min) / range) * (height - padTop - padBottom);
    return [x, y];
  });
  const linePath = pts.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' ');
  const areaPath = `${linePath} L ${pts[pts.length-1][0].toFixed(1)} ${height - padBottom} L ${pts[0][0].toFixed(1)} ${height - padBottom} Z`;
  const lastX = pts[pts.length-1][0], lastY = pts[pts.length-1][1];
  const gridY = [0.25, 0.5, 0.75].map(r => padTop + r * (height - padTop - padBottom));

  return (
    <svg viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="none" className="w-full h-full">
      <defs>
        <linearGradient id="goldArea" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%"  stopColor="#d4af37" stopOpacity="0.35" />
          <stop offset="60%" stopColor="#d4af37" stopOpacity="0.08" />
          <stop offset="100%" stopColor="#d4af37" stopOpacity="0" />
        </linearGradient>
        <linearGradient id="goldStroke" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%"   stopColor="#8b6f1f" />
          <stop offset="50%"  stopColor="#f4d06f" />
          <stop offset="100%" stopColor="#d4af37" />
        </linearGradient>
        <filter id="glow" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="2" result="b" />
          <feMerge><feMergeNode in="b" /><feMergeNode in="SourceGraphic" /></feMerge>
        </filter>
      </defs>
      {gridY.map((y, i) => (
        <line key={i} x1={padX} x2={width - padX} y1={y} y2={y}
              stroke="rgba(212,175,55,0.06)" strokeWidth="1" strokeDasharray="2 4" />
      ))}
      <path d={areaPath} fill="url(#goldArea)" />
      <path d={linePath} fill="none" stroke="url(#goldStroke)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round" filter="url(#glow)" />
      <circle cx={lastX} cy={lastY} r="3.5" fill="#f4d06f">
        <animate attributeName="opacity" values="1;0.4;1" dur="2.5s" repeatCount="indefinite" />
      </circle>
      <circle cx={lastX} cy={lastY} r="6" fill="none" stroke="#f4d06f" strokeOpacity="0.4">
        <animate attributeName="r" values="6;14;6" dur="2.5s" repeatCount="indefinite" />
        <animate attributeName="opacity" values="0.4;0;0.4" dur="2.5s" repeatCount="indefinite" />
      </circle>
    </svg>
  );
}

function BarChart({ values, labels, height = 240 }) {
  if (!values || values.length === 0) return <div className="text-ink-mute text-xs">Pas de données</div>;
  const width = 800, padX = 36, padTop = 20, padBottom = 40;
  const max = Math.max(...values, 0), min = Math.min(...values, 0);
  const range = max - min || 1;
  const zero = padTop + (max / range) * (height - padTop - padBottom);
  const barW = (width - padX * 2) / values.length * 0.6;
  const gap  = (width - padX * 2) / values.length;

  return (
    <svg viewBox={`0 0 ${width} ${height}`} className="w-full h-full">
      <defs>
        <linearGradient id="barGold" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%"  stopColor="#f4d06f" stopOpacity="0.9" />
          <stop offset="100%" stopColor="#8b6f1f" stopOpacity="0.6" />
        </linearGradient>
      </defs>
      <line x1={padX} x2={width-padX} y1={zero} y2={zero} stroke="rgba(212,175,55,0.15)" strokeWidth="1" />
      {values.map((v, i) => {
        const x = padX + gap * i + (gap - barW) / 2;
        const barH = Math.abs((v / range) * (height - padTop - padBottom));
        const y = v >= 0 ? zero - barH : zero;
        return (
          <g key={i}>
            <rect x={x} y={y} width={barW} height={barH} fill="url(#barGold)" />
            <text x={x + barW/2} y={zero + 20} textAnchor="middle" fontSize="10" fill="#b0aa9a" fontFamily="Inter">{labels?.[i] || ''}</text>
            <text x={x + barW/2} y={y - 6} textAnchor="middle" fontSize="11" fill="#f0ecdc" fontFamily="Inter" fontWeight="500">{v.toFixed(1)}</text>
          </g>
        );
      })}
    </svg>
  );
}

function DualLineChart({ labels, seriesA, seriesB, nameA, nameB, colorA = '#f4d06f', colorB = '#34d6a5', height = 260 }) {
  if (!seriesA?.length || !seriesB?.length) return <div className="text-ink-mute text-xs">Pas de données</div>;
  const width = 800, padX = 40, padTop = 28, padBottom = 40;
  const all = [...seriesA, ...seriesB];
  const min = Math.min(...all), max = Math.max(...all), range = max - min || 1;

  const mk = (s) => s.map((v, i) => [
    padX + (i / (s.length - 1)) * (width - padX * 2),
    padTop + (1 - (v - min) / range) * (height - padTop - padBottom),
  ]);
  const pathA = mk(seriesA).map((p, i) => `${i===0?'M':'L'} ${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' ');
  const pathB = mk(seriesB).map((p, i) => `${i===0?'M':'L'} ${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' ');

  const gridY = [0, 0.25, 0.5, 0.75, 1].map(r => {
    const y = padTop + r * (height - padTop - padBottom);
    const val = max - r * range;
    return { y, val };
  });

  return (
    <svg viewBox={`0 0 ${width} ${height}`} className="w-full h-full">
      {gridY.map((g, i) => (
        <g key={i}>
          <line x1={padX} x2={width-padX} y1={g.y} y2={g.y} stroke="rgba(212,175,55,0.06)" strokeDasharray="2 4" />
          <text x={padX - 6} y={g.y + 3} textAnchor="end" fontSize="9" fill="#5f5a4d">{g.val.toFixed(1)}</text>
        </g>
      ))}
      {(labels || []).map((l, i) => {
        const x = padX + (i / (labels.length - 1)) * (width - padX * 2);
        return <text key={i} x={x} y={height - 14} textAnchor="middle" fontSize="10" fill="#b0aa9a">{l}</text>;
      })}
      <path d={pathA} fill="none" stroke={colorA} strokeWidth="1.5" strokeLinejoin="round" />
      <path d={pathB} fill="none" stroke={colorB} strokeWidth="1.5" strokeLinejoin="round" strokeDasharray="4 3" />
      <g transform={`translate(${padX}, 14)`}>
        <rect width="9" height="2" y="3" fill={colorA} />
        <text x="14" y="8" fontSize="10" fill="#b0aa9a">{nameA}</text>
        <rect x="90" width="9" height="2" y="3" fill={colorB} />
        <text x="104" y="8" fontSize="10" fill="#b0aa9a">{nameB}</text>
      </g>
    </svg>
  );
}

function HoverLineChart({ points, unit = '', height = 260 }) {
  const [hover, setHover] = useState(null);
  if (!points || points.length === 0) {
    return <div className="text-ink-mute text-xs text-center py-12">Données indisponibles</div>;
  }
  const width = 720, padX = 56, padTop = 30, padBottom = 50;
  const values = points.map(p => p.value);
  const minV = Math.min(...values), maxV = Math.max(...values);
  const span = maxV - minV || Math.abs(maxV) || 1;
  const yPad = span * 0.15;
  const ymin = minV - yPad, ymax = maxV + yPad;
  const yrange = (ymax - ymin) || 1;

  const xs = points.map((_, i) =>
    points.length === 1 ? width / 2 : padX + (i / (points.length - 1)) * (width - padX * 2)
  );
  const ys = values.map(v => padTop + (1 - (v - ymin) / yrange) * (height - padTop - padBottom));

  const linePath = xs.map((x, i) => `${i === 0 ? 'M' : 'L'} ${x.toFixed(1)} ${ys[i].toFixed(1)}`).join(' ');
  const areaPath = `${linePath} L ${xs[xs.length-1].toFixed(1)} ${height - padBottom} L ${xs[0].toFixed(1)} ${height - padBottom} Z`;

  const ticks = [0, 0.25, 0.5, 0.75, 1].map(r => {
    const y = padTop + r * (height - padTop - padBottom);
    return { y, v: ymax - r * yrange };
  });

  const MONTHS = ['Jan','Fev','Mar','Avr','Mai','Juin','Juil','Aou','Sep','Oct','Nov','Dec'];
  const fmtDate = d => {
    if (!d) return '';
    const parts = d.split('-');
    if (parts.length < 2) return d;
    const m = parseInt(parts[1], 10) - 1;
    const yy = parts[0].slice(2);
    return `${MONTHS[m] || ''} ${yy}`;
  };
  const fmtVal = v => {
    const sign = v > 0 && (unit === '%' || unit === 'K') ? '+' : '';
    const num = Math.abs(v) >= 100 ? v.toFixed(0) : Math.abs(v) >= 10 ? v.toFixed(1) : v.toFixed(2);
    return `${sign}${num}${unit || ''}`;
  };

  return (
    <svg viewBox={`0 0 ${width} ${height}`} className="w-full h-full"
         onMouseLeave={() => setHover(null)}>
      <defs>
        <linearGradient id="indArea" x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%"   stopColor="#d4af37" stopOpacity="0.30"/>
          <stop offset="60%"  stopColor="#d4af37" stopOpacity="0.06"/>
          <stop offset="100%" stopColor="#d4af37" stopOpacity="0"/>
        </linearGradient>
        <linearGradient id="indStroke" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%"   stopColor="#8b6f1f"/>
          <stop offset="50%"  stopColor="#f4d06f"/>
          <stop offset="100%" stopColor="#d4af37"/>
        </linearGradient>
      </defs>

      {ticks.map((t, i) => (
        <g key={i}>
          <line x1={padX} x2={width-padX} y1={t.y} y2={t.y}
                stroke="rgba(212,175,55,0.06)" strokeDasharray="2 4"/>
          <text x={padX-10} y={t.y+3} textAnchor="end" fontSize="10" fill="#5f5a4d">
            {Math.abs(t.v) >= 100 ? t.v.toFixed(0) : t.v.toFixed(1)}
          </text>
        </g>
      ))}

      <path d={areaPath} fill="url(#indArea)" />
      <path d={linePath} fill="none" stroke="url(#indStroke)" strokeWidth="2"
            strokeLinejoin="round" strokeLinecap="round"/>

      {points.map((p, i) => (
        <text key={i} x={xs[i]} y={height - 18} textAnchor="middle" fontSize="10" fill="#b0aa9a">
          {fmtDate(p.date)}
        </text>
      ))}

      {points.map((p, i) => {
        const isH = hover === i;
        return (
          <g key={i}>
            <circle cx={xs[i]} cy={ys[i]} r="16" fill="transparent"
                    onMouseEnter={() => setHover(i)} style={{cursor:'crosshair'}}/>
            <circle cx={xs[i]} cy={ys[i]} r={isH ? 6 : 3.5}
                    fill={isH ? '#fff' : '#f4d06f'} stroke="#d4af37" strokeWidth={isH ? 2 : 1}
                    style={isH ? {filter:'drop-shadow(0 0 8px #f4d06f)'} : null}/>
          </g>
        );
      })}

      {hover !== null && (() => {
        const p = points[hover];
        const tx = xs[hover], ty = ys[hover];
        const left = tx > width * 0.6;
        const tw = 110, th = 38;
        const bx = left ? tx - tw - 12 : tx + 12;
        const by = Math.max(padTop, ty - th / 2);
        return (
          <g pointerEvents="none">
            <line x1={tx} x2={tx} y1={padTop} y2={height - padBottom}
                  stroke="#d4af37" strokeOpacity="0.25" strokeDasharray="2 3"/>
            <rect x={bx} y={by} width={tw} height={th} rx="2"
                  fill="#000" stroke="#d4af37" strokeOpacity="0.6"/>
            <text x={bx + tw/2} y={by + 14} textAnchor="middle"
                  fontSize="9" fill="#b0aa9a" letterSpacing="2">{fmtDate(p.date).toUpperCase()}</text>
            <text x={bx + tw/2} y={by + 30} textAnchor="middle"
                  fontSize="14" fontWeight="700" fill="#f4d06f"
                  style={{filter:'drop-shadow(0 0 4px rgba(244,208,111,0.5))'}}>
              {fmtVal(p.value)}
            </text>
          </g>
        );
      })()}
    </svg>
  );
}

function IndicatorModal({ ind, onClose }) {
  useEffect(() => {
    if (!ind) return;
    const h = e => e.key === 'Escape' && onClose();
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [ind, onClose]);

  if (!ind) return null;
  const fmtMain = v => {
    if (v == null) return 'n/a';
    const sign = v > 0 && (ind.unit === '%' || ind.unit === 'K') ? '+' : '';
    const num = Math.abs(v) >= 100 ? v.toFixed(0) : Math.abs(v) >= 10 ? v.toFixed(1) : v.toFixed(2);
    return `${sign}${num}${ind.unit || ''}`;
  };
  const delta = (ind.value != null && ind.prev != null) ? (ind.value - ind.prev) : null;

  return (
    <div className="fixed inset-0 z-50 bg-black/85 backdrop-blur-md flex items-center justify-center p-8"
         onClick={onClose}
         style={{animation:'fadeIn 0.2s ease-out'}}>
      <div className="glass top-shine p-9 w-full max-w-3xl relative"
           onClick={e => e.stopPropagation()}>
        <button onClick={onClose}
                className="absolute top-4 right-5 text-ink-mute hover:text-gold-hi text-3xl leading-none transition-colors">×</button>
        <div className="mb-6 pr-10">
          <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-2">
            Historique · {ind.series?.length || 0} derniers points
          </div>
          <h3 className="font-medium text-[1.7rem] gold-text tracking-tight leading-none mb-4">
            {ind.name}
          </h3>
          <div className="flex items-baseline gap-5">
            <span className={`text-[2.4rem] font-semibold leading-none ${TXT[ind.trend]}`}
                  style={{textShadow:'0 0 18px currentColor'}}>
              {fmtMain(ind.value)}
            </span>
            {ind.prev != null && (
              <span className="text-[0.85rem] text-ink-mute">
                Préc. <span className="text-ink-soft">{fmtMain(ind.prev)}</span>
                {delta != null && (
                  <span className={`ml-2 ${delta > 0 ? 'text-bull' : delta < 0 ? 'text-bear' : 'text-neut'}`}>
                    ({delta > 0 ? '+' : ''}{delta.toFixed(2)})
                  </span>
                )}
              </span>
            )}
            <span className={`ml-auto inline-block text-[0.62rem] tracking-[0.32em] uppercase px-3 py-1.5 border ${TXT[ind.impact]}`}
                  style={{borderColor:'currentColor'}}>{ind.impact_label}</span>
          </div>
        </div>
        <div className="h-[300px]">
          <HoverLineChart points={ind.series || []} unit={ind.unit || ''} height={300}/>
        </div>
        <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold-dim text-center mt-4">
          Survolez les points pour voir date et valeur · Échap pour fermer
        </div>
      </div>
    </div>
  );
}

function HProbaBar({ segments }) {
  return (
    <div className="w-full">
      <div className="flex h-4 overflow-hidden">
        {segments.map((s, i) => (
          <div key={i} className="h-full transition-all" style={{ width: `${s.value}%`, background: s.color, boxShadow: `inset 0 0 8px rgba(0,0,0,0.3)` }} />
        ))}
      </div>
      <div className="flex justify-between mt-3 text-[0.7rem]">
        {segments.map((s, i) => (
          <div key={i} className="flex flex-col">
            <span className="text-ink-mute tracking-wider uppercase text-[0.6rem]">{s.label}</span>
            <span className="font-semibold text-[1.1rem]" style={{ color: s.color }}>{s.value}%</span>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ========== ATOMS ======================================================== */

const SUPPORTED_COUNTRIES = ['USD', 'EUR', 'GBP', 'JPY', 'CHF', 'AUD', 'CAD', 'NZD'];
const VIEW_PRINCIPAL = 'PRINCIPALE';

function Header({ activeView, onView, onRefresh, refreshing }) {
  const Item = ({ id, label }) => (
    <button onClick={() => onView(id)}
            className={`relative py-2 text-[0.7rem] tracking-[0.32em] uppercase transition-colors ${
              activeView === id ? 'gold-text font-medium' : 'text-ink-mute hover:text-ink-soft'
            }`}>
      {label}
      {activeView === id && (
        <span className="absolute -bottom-[21px] -left-2 -right-2 h-px"
              style={{background:'linear-gradient(90deg, transparent, #f4d06f, transparent)', boxShadow:'0 0 12px rgba(212,175,55,0.35)'}}/>
      )}
    </button>
  );
  return (
    <header className="sticky top-0 z-40 bg-black/70 backdrop-blur-xl border-b border-gold/15">
      <div className="max-w-[1320px] mx-auto px-11 py-5 flex items-center justify-between">
        <div className="font-semibold text-[1.4rem] tracking-[0.18em] uppercase gold-text">FondAnalyse</div>
        <nav className="flex items-center gap-6">
          <Item id={VIEW_PRINCIPAL} label="Principale" />
          <span className="h-4 w-px bg-gold/20 mx-1"/>
          {SUPPORTED_COUNTRIES.map(f => <Item key={f} id={f} label={f} />)}
        </nav>
        <button onClick={onRefresh} disabled={refreshing}
                className="relative overflow-hidden border border-gold/40 bg-gold/[0.04] text-gold-hi px-5 py-2.5 text-[0.7rem] tracking-[0.22em] uppercase hover:bg-gold/10 hover:border-gold-hi transition-all hover:shadow-[0_0_24px_rgba(212,175,55,0.35)] disabled:opacity-50">
          <span className="absolute inset-0 animate-shimmer pointer-events-none"
                style={{background:'linear-gradient(90deg, transparent, rgba(244,208,111,0.25), transparent)'}}/>
          <span className="relative">{refreshing ? 'Actualisation...' : 'Actualiser'}</span>
        </button>
      </div>
    </header>
  );
}

function PageTitle({ title, sub }) {
  return (
    <>
      <h1 className="font-medium text-[3.2rem] tracking-tightest gold-text leading-none mb-2"
          style={{textShadow:'0 0 60px rgba(212,175,55,0.15)'}}>{title}</h1>
      {sub ? (
        <div className="text-[0.72rem] tracking-[0.4em] uppercase text-gold-dim relative pl-5 mb-14">
          <span className="absolute left-0 top-1/2 w-3 h-px bg-gold"/>{sub}
        </div>
      ) : (
        <div className="mb-14" />
      )}
    </>
  );
}

const COUNTRY_TABS = ['Général', 'Politique Monétaire', 'Données', 'Speech'];
const PRINCIPAL_TABS = ['Direction du marché', 'Calendrier', 'Entreprise', 'Calculateur', 'Corrélations', 'Sentiment Retail'];

function Tabs({ tabs, active, onChange }) {
  return (
    <div className="flex gap-11 mb-12 relative pb-px">
      <span className="absolute bottom-0 left-0 right-0 h-px"
            style={{background:'linear-gradient(90deg, rgba(212,175,55,0.3) 0%, rgba(255,255,255,0.05) 50%, transparent 100%)'}}/>
      {tabs.map(t => (
        <button key={t} onClick={() => onChange(t)}
                className={`relative py-4 text-[0.72rem] tracking-[0.3em] uppercase transition-colors ${
                  active === t ? 'gold-text font-medium' : 'text-ink-mute hover:text-ink-soft'
                }`}>
          {t}
          {active === t && (
            <span className="absolute bottom-0 -left-3 -right-3 h-px"
                  style={{background:'linear-gradient(90deg, transparent, #f4d06f, transparent)', boxShadow:'0 0 14px rgba(212,175,55,0.35)'}}/>
          )}
        </button>
      ))}
    </div>
  );
}

function SectionHead({ title, meta }) {
  return (
    <div className="flex items-baseline justify-between mb-7 relative pb-4">
      <h2 className="font-normal text-[1.5rem] text-ink tracking-tight">{title}</h2>
      {meta && <div className="text-[0.65rem] tracking-[0.32em] uppercase text-gold font-medium">{meta}</div>}
      <span className="absolute bottom-0 left-0 right-0 h-px"
            style={{background:'linear-gradient(90deg, #d4af37 0%, transparent 25%)'}}/>
    </div>
  );
}

function Wip({ label = "En cours de code", note, compact = false }) {
  return (
    <div className={`glass top-shine flex flex-col items-center justify-center text-center ${compact ? 'p-6' : 'p-10'}`}
         style={{borderColor:'rgba(212,175,55,0.18)', borderStyle:'dashed'}}>
      <div className="text-[1.4rem] mb-2">🚧</div>
      <div className="text-[0.68rem] tracking-[0.32em] uppercase gold-text font-medium">{label}</div>
      {note && <div className="text-ink-mute text-[0.72rem] mt-2 max-w-md">{note}</div>}
    </div>
  );
}

function Pillar(props) {
  if (props.wip) {
    return (
      <div className="glass top-shine p-7 flex flex-col items-center justify-center text-center"
           style={{borderColor:'rgba(212,175,55,0.18)', borderStyle:'dashed', minHeight:'220px'}}>
        <div className="text-[0.7rem] tracking-[0.28em] uppercase text-ink-soft font-medium mb-3">{props.name}</div>
        <div className="text-[1.4rem] mb-1">🚧</div>
        <div className="text-[0.62rem] tracking-[0.32em] uppercase gold-text font-medium">En cours de code</div>
      </div>
    );
  }
  const { name, value, unit, verdict, status, spark, lines } = props;
  return (
    <div className={`glass top-shine ${status} p-7 transition-transform hover:-translate-y-0.5`}>
      <div className="flex items-center justify-between mb-4">
        <div className="text-[0.7rem] tracking-[0.28em] uppercase text-ink-soft font-medium">{name}</div>
        <div className={`text-xl font-medium ${TXT[status]}`} style={{textShadow:`0 0 12px currentColor`}}>{ARROW[status]}</div>
      </div>
      <div className="flex items-end justify-between mb-4">
        <div className="gold-text text-[2.4rem] font-semibold leading-none tracking-tightest">
          {value}<span className="text-[1rem] font-light ml-0.5" style={{WebkitTextFillColor:'#5f5a4d'}}>{unit}</span>
        </div>
        <Sparkline data={spark} color={COLOR[status]} width={80} height={28} />
      </div>
      <div className="text-[0.76rem] text-ink-soft leading-relaxed mb-5">{verdict}</div>
      <div>
        {(lines || []).map((ln, i) => (
          <div key={i} className={`flex justify-between text-[0.74rem] py-2 ${i > 0 ? 'border-t border-gold/[0.08]' : ''}`}>
            <span className="text-ink-mute">{ln.l}</span>
            <span className={`font-medium ${TREND_CLS[ln.t] || 'text-ink'}`}>{ln.v}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function ThemeBanner({ theme }) {
  const values = theme.chart?.values || [];
  const last = values[values.length - 1];
  const first = values[0];
  const delta = (first && last) ? ((last - first) / first * 100) : 0;
  const isUp = delta >= 0;

  return (
    <div className={`glass top-shine ${theme.status} p-10 grid gap-10 overflow-hidden`}
         style={{gridTemplateColumns:'70px 1fr 320px'}}>
      <div className={`text-[3.2rem] font-medium text-center ${TXT[theme.status]} animate-pulseSoft`}
           style={{textShadow:`0 0 30px currentColor, 0 0 60px currentColor`}}>
        {ARROW[theme.status]}
      </div>

      <div className="min-w-0">
        <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-3">{theme.label}</div>
        <div className="font-semibold text-[1.8rem] gold-text tracking-tight leading-tight mb-2.5">{theme.headline}</div>
        <div className="text-[0.85rem] text-ink-soft leading-relaxed mb-5">{theme.detail}</div>
        <div className={`inline-block text-[0.65rem] tracking-[0.32em] uppercase px-5 py-2.5 border font-medium ${TXT[theme.status]}`}
             style={{borderColor:'currentColor'}}>{theme.badge}</div>
      </div>

      <div className="flex flex-col">
        <div className="flex items-baseline justify-between mb-2">
          <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium">{theme.chart?.title}</div>
          {last && first && (
            <div className={`text-[0.78rem] font-semibold ${isUp ? 'text-bull' : 'text-bear'}`}>
              {isUp ? '+' : ''}{delta.toFixed(2)}%
            </div>
          )}
        </div>
        {last && (
          <div className="flex items-baseline gap-2 mb-3">
            <div className="gold-text text-[1.7rem] font-semibold tracking-tightest leading-none">{last.toFixed(2)}</div>
            <div className="text-[0.7rem] text-ink-mute">DXY</div>
          </div>
        )}
        <div className="flex-1 min-h-[140px]">
          <AreaChart values={values} height={180} />
        </div>
      </div>
    </div>
  );
}

const TONE_BADGE = {
  bull: { cls: 'text-bull', label: 'Détente'   },
  bear: { cls: 'text-bear', label: 'Tension'   },
  warn: { cls: 'text-bear', label: 'Stress'    },
  neut: { cls: 'text-ink-soft', label: 'Stable' },
};

function GaugePanel({ num, suffix, label, statBlocks, position, footnote }) {
  return (
    <div className="glass top-shine p-9 transition-transform hover:-translate-y-0.5">
      <div className="flex flex-col items-center mb-7">
        <div className="gold-text font-semibold text-[4.5rem] leading-none tracking-tightest">
          {num}<span className="text-[1.3rem] font-light" style={{WebkitTextFillColor:'#5f5a4d'}}>{suffix}</span>
        </div>
        <div className="text-[0.7rem] tracking-[0.32em] uppercase text-ink-soft mt-3 font-medium">{label}</div>
        <div className="w-full h-px mt-6 relative"
             style={{background:'linear-gradient(90deg, #ff4757 0%, #f5c542 50%, #34d6a5 100%)', boxShadow:'0 0 10px rgba(212,175,55,0.2)'}}>
          <span className="absolute -top-1 w-px h-3 bg-gold-hi"
                style={{left:`${position}%`, boxShadow:'0 0 12px #f4d06f, 0 0 4px #fff'}}/>
        </div>
      </div>
      <div className="grid grid-cols-2 gap-px bg-gold/10">
        {statBlocks.map((s, i) => (
          <div key={i} className="bg-black/60 px-5 py-4 flex justify-between items-baseline">
            <span className="text-[0.68rem] tracking-[0.2em] uppercase text-ink-mute">{s.l}</span>
            <span className={`font-semibold text-[1.15rem] tracking-tight ${TREND_CLS[s.t] || 'text-ink'}`}>{s.v}</span>
          </div>
        ))}
      </div>
      {footnote && (
        <div className="mt-5 pt-4 border-t border-gold/10">
          <div className="flex items-baseline gap-3">
            <span className={`text-[0.6rem] tracking-[0.32em] uppercase font-semibold ${TONE_BADGE[footnote.tone]?.cls || 'text-ink-soft'}`}>
              {TONE_BADGE[footnote.tone]?.label || 'Lecture'}
            </span>
            <span className="text-[0.62rem] tracking-[0.32em] uppercase text-gold-dim">Lecture obligataire</span>
          </div>
          <div className="text-[0.82rem] text-ink-soft leading-relaxed mt-2">{footnote.text}</div>
        </div>
      )}
    </div>
  );
}

function PositionsCard({ retail, cot }) {
  const Block = ({ title, data, hint }) => (
    <div className="flex flex-col items-center text-center px-4">
      <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mb-3">{title}</div>
      {data && data.long != null ? (
        <>
          <div className="text-bull font-semibold text-[2.6rem] leading-none" style={{textShadow:'0 0 14px currentColor'}}>
            {data.long}<span className="text-[1rem] font-light text-ink-mute ml-1">% L</span>
          </div>
          <div className="text-bear font-medium text-[1.05rem] mt-1">
            {data.short}<span className="text-[0.7rem] font-light text-ink-mute ml-1">% S</span>
          </div>
          <div className="mt-3 text-[0.6rem] tracking-[0.18em] uppercase text-ink-mute">
            {hint}
          </div>
        </>
      ) : (
        <div className="text-ink-mute text-[0.78rem] py-6">Indisponible</div>
      )}
    </div>
  );

  return (
    <div className="glass top-shine p-9 transition-transform hover:-translate-y-0.5">
      <div className="text-[0.7rem] tracking-[0.32em] uppercase text-ink-soft font-medium text-center mb-7">
        Positionnement marché
      </div>
      <div className="grid grid-cols-2 gap-px bg-gold/10">
        <div className="bg-black/60 py-6">
          <Block title="Retail"          data={retail} hint={retail?.pair || ''} />
        </div>
        <div className="bg-black/60 py-6">
          <Block title="Institutionnel"  data={cot}    hint={cot?.pair    || ''} />
        </div>
      </div>
    </div>
  );
}

function StatusBanner({ status, label, headline, detail, badge }) {
  return (
    <div className={`glass top-shine ${status} p-8 grid gap-8 items-center`}
         style={{gridTemplateColumns:'60px 1fr auto'}}>
      <div className={`text-[2.6rem] font-medium text-center ${TXT[status]} animate-pulseSoft`}
           style={{textShadow:`0 0 30px currentColor`}}>{ARROW[status]}</div>
      <div>
        <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-2">{label}</div>
        <div className="font-semibold text-[1.35rem] gold-text tracking-tight leading-tight mb-1.5">{headline}</div>
        <div className="text-[0.82rem] text-ink-soft leading-relaxed">{detail}</div>
      </div>
      <div className={`text-[0.65rem] tracking-[0.32em] uppercase px-4 py-2 border font-medium ${TXT[status]}`}
           style={{borderColor:'currentColor'}}>{badge}</div>
    </div>
  );
}

/* ========== CALENDAR ===================================================== */

const IMPACT_COLOR = {
  High:   '#ff4757',
  Medium: '#f5c542',
  Low:    '#5f5a4d',
  Holiday:'#5f5a4d',
};

function ImpactDot({ impact }) {
  const c = IMPACT_COLOR[impact] || '#5f5a4d';
  return (
    <span className="inline-block w-2 h-2 rounded-full" style={{background: c, boxShadow: `0 0 6px ${c}`}}/>
  );
}

function WeeklyHighlights({ cal }) {
  if (!cal?.days?.length) {
    return (
      <>
        <SectionHead title="Moments clés de cette semaine" meta="Indisponible" />
        <div className="glass top-shine p-6 text-center text-ink-mute text-sm">
          Aucun événement chargé.
        </div>
      </>
    );
  }
  const highDays = cal.days
    .map(d => ({ ...d, events: d.events.filter(e => e.impact === 'High') }))
    .filter(d => d.events.length);
  const totalHigh = highDays.reduce((acc, d) => acc + d.events.length, 0);

  return (
    <>
      <SectionHead
        title="Moments clés de cette semaine"
        meta={`${totalHigh} événement${totalHigh > 1 ? 's' : ''} High · Pour le détail → onglet Calendrier (page Principale)`} />
      <div className="glass top-shine p-6">
        {totalHigh === 0 ? (
          <div className="text-center text-ink-mute text-sm py-4">
            Aucun événement High cette semaine pour {cal.country}.
          </div>
        ) : (
          <div className="space-y-4">
            {highDays.map(day => (
              <div key={day.date}>
                <div className="flex items-baseline gap-3 mb-2 pb-2 border-b border-gold/[0.1]">
                  <span className="text-[0.78rem] gold-text font-semibold capitalize">{day.label}</span>
                  <span className="text-[0.62rem] tracking-[0.2em] uppercase text-ink-mute">
                    {day.events.length} high
                  </span>
                </div>
                <div className="space-y-1">
                  {day.events.map((e, i) => (
                    <div key={i}
                         className="grid items-center gap-3 py-2 px-2 text-[0.84rem] border-b border-gold/[0.04]"
                         style={{gridTemplateColumns:'56px 14px 1fr'}}>
                      <span className="text-ink-soft tabular-nums">{e.time}</span>
                      <ImpactDot impact={e.impact} />
                      <span className="text-ink font-medium truncate" title={e.title}>{e.title}</span>
                    </div>
                  ))}
                </div>
              </div>
            ))}
          </div>
        )}
      </div>
    </>
  );
}

/* ========== SECTIONS ===================================================== */

function TabGeneral({ state }) {
  const s = state.sentiment;
  const discourse = state.discourse;
  const country = state.meta?.country || '';
  return (
    <>
      <section className="mb-14">
        <SectionHead title={`Analyse Économique — ${country}`} />
        {discourse?.wip ? (
          <Wip note={`Analyse économique ${state.meta?.countryCode || ''} momentanément indisponible, réessayer dans quelques minutes.`} />
        ) : (
          <div className="glass top-shine p-8">
            <div className="text-[0.65rem] tracking-[0.3em] uppercase text-gold font-medium mb-5">
              Mise à jour : {discourse.date}
            </div>
            <div className="text-[0.92rem] text-ink-soft leading-relaxed whitespace-pre-line">{discourse.text}</div>
          </div>
        )}
      </section>

      <section className="mb-14">
        <SectionHead title="Vue d'ensemble" meta="4 piliers" />
        <div className="grid grid-cols-4 gap-5">
          {state.pillars.map((p, i) => <Pillar key={i} {...p} />)}
        </div>
      </section>

      <section className="mb-14">
        {state.theme?.wip ? <Wip note="Theme macro narratif en cours de code pour cette devise." /> : <ThemeBanner theme={state.theme} />}
      </section>

      {state.calendar && (
        <section className="mb-14">
          <WeeklyHighlights cal={state.calendar} />
        </section>
      )}

      <section className="mb-14">
        <SectionHead title="Sentiment Marché" meta="Live" />
        {s?.wip ? (
          <Wip note="Indicateurs de sentiment (Fear & Greed, VIX, retail FX) spécifiques USD pour l'instant." />
        ) : (
        <div className="grid grid-cols-2 gap-5">
          <GaugePanel
            num={s.fearGreed} suffix="/100"
            label={`Fear & Greed · ${s.fearGreedLabel}`}
            position={s.fearGreed}
            statBlocks={[
              { l: s.vixLabel || 'VIX', v: fmt(s.vix) },
              { l: 'F&G veille',  v: fmt(s.fearGreedPrev) },
              { l: 'Yield 10Y',   v: s.yield10 != null ? `${s.yield10}%` : 'n/a', t: s.yield10Change >= 0 ? 'up' : 'down' },
              { l: 'Yield 2Y',    v: s.yield2 != null ? `${s.yield2}%` : 'n/a',  t: s.yield2Change  >= 0 ? 'up' : 'down' },
            ]}
            footnote={s.yieldsNarrative}
          />
          <PositionsCard
            retail={s.myfxbook}
            cot={s.retailLong != null
              ? { long: s.retailLong, short: s.retailShort, pair: s.retailPairs?.[0]?.pair }
              : null}
          />
        </div>
        )}
      </section>

      <section className="mb-14">
        <SectionHead title="Synthèse" meta="Decision support" />
        {state.synth?.wip ? (
          <Wip note="Synthèse Forces / Risques / Reco générée à partir des piliers - couche 2." />
        ) : (
        <>
        <div className="grid grid-cols-2 gap-10 mb-7">
          <div>
            <h3 className="font-medium text-[1.15rem] text-bull tracking-wide mb-5" style={{textShadow:'0 0 20px currentColor'}}>Forces</h3>
            {state.synth.forces.map((f, i) => (
              <div key={i} className="border-l border-bull pl-5 pr-5 py-3 mb-2.5 text-[0.82rem] text-ink-soft item-hover"
                   style={{background:'linear-gradient(90deg, rgba(212,175,55,0.04), transparent 50%), rgba(0,0,0,0.3)'}}>{f}</div>
            ))}
          </div>
          <div>
            <h3 className="font-medium text-[1.15rem] text-bear tracking-wide mb-5" style={{textShadow:'0 0 20px currentColor'}}>Risques</h3>
            {state.synth.risques.map((r, i) => (
              <div key={i} className="border-l border-bear pl-5 pr-5 py-3 mb-2.5 text-[0.82rem] text-ink-soft item-hover"
                   style={{background:'linear-gradient(90deg, rgba(212,175,55,0.04), transparent 50%), rgba(0,0,0,0.3)'}}>{r}</div>
            ))}
          </div>
        </div>
        <div className="glass top-shine p-6 text-[0.88rem] text-ink leading-relaxed reco-bg">
          <strong className="gold-text font-semibold">Recommandation : {state.synth.reco.dir}</strong> — {state.synth.reco.text}
        </div>
        </>
        )}
      </section>
    </>
  );
}

function TabPolitique({ state }) {
  const f = state.fed;

  if (f?.wip) {
    return (
      <section className="mb-14">
        <Wip note={`Politique monétaire détaillée pour ${state.meta?.country} — couche 2 (signal banque centrale, probas, paramètres, force devise).`} />
      </section>
    );
  }

  const cbShort = state.meta?.central_bank
    ? (state.meta.central_bank.match(/\(([^)]+)\)/)?.[1] || 'BC')
    : (state.meta?.countryCode === 'USD' ? 'Fed' : 'BC');
  const ccyCode = state.meta?.countryCode || 'USD';
  const strengthTitle = f.usd_strength?.title || (ccyCode === 'USD' ? 'Force du Dollar (panier DXY)' : `Force ${ccyCode} (panier)`);
  const componentsTitle = ccyCode === 'USD' ? 'Composantes (poids DXY)' : 'Composantes du panier';
  const probaTitle = `Probabilité prochaine décision ${cbShort}`;
  const probaUnavailable = f.proba_unavailable === true;

  return (
    <>
      <section className="mb-8">
        <StatusBanner status={f.signal.status} label={`Signal ${cbShort}`}
                      headline={f.signal.headline} detail={f.signal.detail} badge={f.signal.badge} />
      </section>

      <section className="mb-14 grid grid-cols-2 gap-5">
        <div className="glass top-shine p-7">
          <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-5">Paramètres {cbShort}</div>
          {f.params.map((p, i) => (
            <div key={i} className={`flex justify-between py-3 text-[0.82rem] ${i > 0 ? 'border-t border-gold/[0.08]' : ''}`}>
              <span className="text-ink-soft">{p.label}</span>
              <span className={`font-medium ${p.highlight ? 'gold-text font-semibold' : 'text-ink'}`}>{p.value}</span>
            </div>
          ))}
        </div>
        <div className="glass top-shine p-7">
          <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-5">{probaTitle}</div>
          {probaUnavailable ? (
            <div className="py-6">
              <div className="text-[0.78rem] text-ink-soft leading-relaxed">
                Pas de fed-watch équivalent public pour la <strong className="gold-text">{cbShort}</strong>.
                Le sentiment monétaire s'apprécie via la stance, l'évolution récente du taux directeur et les rendements obligataires.
              </div>
            </div>
          ) : (
            <>
              <HProbaBar segments={[
                { label: 'Hausse 25bp',  value: f.proba.hausse,  color: '#ff4757' },
                { label: 'Maintien',     value: f.proba.maintien, color: '#f5c542' },
                { label: 'Baisse 25bp',  value: f.proba.baisse,  color: '#34d6a5' },
              ]}/>
              <div className="mt-8 text-[0.78rem] text-ink-soft leading-relaxed">
                {f.proba.baisse >= 50 ? (
                  <>Le marché price <strong className="text-bull">{f.proba.baisse}%</strong> de baisse de taux à la prochaine réunion {cbShort}.</>
                ) : f.proba.hausse >= 50 ? (
                  <>Le marché price <strong className="text-bear">{f.proba.hausse}%</strong> de hausse de taux à la prochaine réunion.</>
                ) : (
                  <>La majorité anticipe un <strong className="text-neut">maintien</strong> ({f.proba.maintien}%) à court terme.</>
                )}
              </div>
            </>
          )}
        </div>
      </section>

      <section className="mb-14 grid grid-cols-2 gap-5">
        <div className="glass top-shine p-7 flex flex-col">
          <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-5">{strengthTitle}</div>
          <div className="flex flex-col items-center gap-2">
            <div className="gold-text text-[4.5rem] font-semibold leading-none tracking-tightest">
              {f.usd_strength.score}<span className="text-[1.3rem] font-light" style={{WebkitTextFillColor:'#5f5a4d'}}>/100</span>
            </div>
            <div className="text-[0.7rem] tracking-[0.32em] uppercase text-ink-soft font-medium">{f.usd_strength.label}</div>
            <div className="w-full h-px mt-4 relative"
                 style={{background:'linear-gradient(90deg, #ff4757 0%, #f5c542 50%, #34d6a5 100%)'}}>
              <span className="absolute -top-1 w-px h-3 bg-gold-hi" style={{left:`${f.usd_strength.score}%`, boxShadow:'0 0 12px #f4d06f'}}/>
            </div>
            <div className="w-full mt-5 grid grid-cols-2 gap-px bg-gold/10">
              {f.usd_strength.stats.map((st, i) => (
                <div key={i} className="bg-black/60 px-4 py-3 flex justify-between items-baseline">
                  <span className="text-[0.65rem] tracking-[0.2em] uppercase text-ink-mute">{st.l}</span>
                  <span className={`font-semibold text-[1rem] ${TREND_CLS[st.t] || 'text-ink'}`}>{st.v}</span>
                </div>
              ))}
            </div>
          </div>
          {f.usd_strength.components?.length > 0 && (
            <div className="mt-6">
              <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mb-3">{componentsTitle}</div>
              <div className="space-y-1.5">
                {f.usd_strength.components.map((c, i) => (
                  <div key={i} className="flex justify-between text-[0.74rem]">
                    <span className="text-ink-soft">{c.pair} <span className="text-ink-mute">({c.weight}%)</span></span>
                    <span className={c.usd_impact >= 0 ? 'text-bull' : 'text-bear'}>
                      {ccyCode} {c.usd_impact >= 0 ? '+' : ''}{c.usd_impact}%
                    </span>
                  </div>
                ))}
              </div>
            </div>
          )}
        </div>

        <div className="glass top-shine p-6">
          <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-3">{f.inflation_chart.title}</div>
          <div className="h-72">
            <DualLineChart
              labels={f.inflation_chart.labels}
              seriesA={f.inflation_chart.cpi} nameA="CPI YoY"
              seriesB={f.inflation_chart.fed} nameB={f.inflation_chart.label_b || `Taux ${cbShort}`}
              colorA="#f4d06f" colorB="#34d6a5" />
          </div>
        </div>
      </section>

      {f.proba_details?.length > 0 && (
        <section className="mb-14">
          <SectionHead title="Probabilités détaillées par target rate" meta="CME / Investing" />
          <div className="glass top-shine p-6">
            <div className="grid gap-2">
              {f.proba_details.map((d, i) => (
                <div key={i} className="grid items-center gap-4" style={{gridTemplateColumns:'120px 1fr 60px'}}>
                  <span className="text-ink-soft text-[0.82rem]">{d.range}%</span>
                  <div className="h-2 bg-black/40 rounded-sm overflow-hidden">
                    <div className="h-full bg-gold transition-all" style={{width: `${Math.min(d.proba, 100)}%`}}/>
                  </div>
                  <span className="text-gold-hi font-semibold text-[0.82rem] text-right">{d.proba}%</span>
                </div>
              ))}
            </div>
          </div>
        </section>
      )}

      {f.communications?.length > 0 && (
        <section className="mb-14">
          <SectionHead title="Communications & contexte" meta={`${f.communications.length} entrées`} />
          <div className="space-y-3">
            {f.communications.map((c, i) => (
              <details key={i} className="glass top-shine">
                <summary className="flex items-center gap-4 px-6 py-4 select-none cursor-pointer">
                  <span className={`text-lg font-medium ${TXT[c.market_impact]}`} style={{textShadow:`0 0 10px currentColor`}}>{ARROW[c.market_impact]}</span>
                  <span className="flex-1 text-[0.9rem] text-ink font-medium">{c.title} — <span className="text-ink-soft">{c.speaker}</span></span>
                  <span className="text-[0.7rem] tracking-[0.2em] uppercase text-ink-mute">{c.date}</span>
                  <span className="chev text-gold-dim">⌄</span>
                </summary>
                <div className="px-6 pb-5 pt-0 border-t border-gold/[0.1] text-[0.82rem] text-ink-soft">
                  <p className="mt-4 mb-3"><strong className="text-ink">Impact marché : </strong>{c.market_impact_label}</p>
                  <ul className="list-disc list-inside space-y-1.5 marker:text-gold-dim">
                    {c.key_points.map((pt, j) => <li key={j}>{pt}</li>)}
                  </ul>
                </div>
              </details>
            ))}
          </div>
        </section>
      )}

    </>
  );
}

function TabDonnees({ state }) {
  const d = state.data;
  const [modalInd, setModalInd] = useState(null);

  const IndicatorRow = ({ ind }) => {
    const clickable = !!(ind.series && ind.series.length);
    return (
      <button
        type="button"
        disabled={!clickable}
        onClick={() => clickable && setModalInd(ind)}
        className={`grid items-center w-full text-left px-4 py-3 border-b border-gold/[0.06] transition-colors ${
          clickable ? 'hover:bg-gold/[0.04] cursor-pointer group' : 'cursor-default'
        }`}
        style={{gridTemplateColumns:'2.4fr 0.9fr 0.9fr 0.9fr 1.2fr', gap:'0.75rem'}}>
        <span className="text-[0.84rem] text-ink font-medium flex items-center gap-2">
          <span className={`text-[0.7rem] ${TXT[ind.trend]}`}>{ARROW[ind.trend]}</span>
          {ind.name}
          {clickable && (
            <span className="text-[0.6rem] text-gold-dim opacity-0 group-hover:opacity-100 transition-opacity ml-1">
              ⌕ chart
            </span>
          )}
        </span>
        <span className="text-[0.84rem] font-semibold gold-text">{ind.value}{ind.unit}</span>
        <span className="text-[0.82rem] text-ink-soft">{ind.prev != null ? `${ind.prev}${ind.unit}` : '—'}</span>
        <span className="text-[0.82rem] text-ink-mute">{ind.expected != null ? `${ind.expected}${ind.unit}` : '—'}</span>
        <span>
          <span className={`inline-block px-2.5 py-1 text-[0.68rem] tracking-[0.2em] uppercase font-medium border ${TXT[ind.impact]}`}
                style={{borderColor:'currentColor'}}>{ind.impact_label}</span>
        </span>
      </button>
    );
  };

  const TableHeader = () => (
    <div className="grid px-4 py-3 text-[0.62rem] tracking-[0.22em] uppercase text-gold font-medium border-b border-gold/[0.15]"
         style={{gridTemplateColumns:'2.4fr 0.9fr 0.9fr 0.9fr 1.2fr', gap:'0.75rem'}}>
      <span>Indicateur</span><span>Actuel</span><span>Précédent</span><span>Attendu</span><span>Impact</span>
    </div>
  );

  const hasIndEco = Array.isArray(d.indicators_economic) && d.indicators_economic.length > 0;
  const hasIndInf = Array.isArray(d.indicators_inflation) && d.indicators_inflation.length > 0;

  return (
    <>
      <section className="mb-14">
        <SectionHead title="Santé Sectorielle" meta={Array.isArray(d.sectors) ? `${d.sectors.length} secteurs` : 'Sectoriel'} />
        {d.sectors?.wip ? (
          <Wip note="Score sectoriel par devise — couche 2 (nécessite données financières par marché)." />
        ) : (
        <div className="grid grid-cols-2 gap-3">
          {d.sectors.map((sec, i) => (
            <div key={i} className={`flex items-center justify-between px-5 py-4 glass top-shine ${sec.status}`}>
              <span className="text-[0.88rem] text-ink font-medium">{sec.name}</span>
              <div className="flex items-center gap-3">
                <span className={`text-[0.9rem] font-semibold ${TXT[sec.status]}`}>{sec.value}</span>
                <span className={`text-xs ${TXT[sec.status]}`} style={{textShadow:`0 0 8px currentColor`}}>{ARROW[sec.status]}</span>
              </div>
            </div>
          ))}
        </div>
        )}
      </section>

      <section className="mb-14">
        <SectionHead title="Économie & Emploi" meta={hasIndEco ? `${d.indicators_economic.length} indicateurs` : ''} />
        {hasIndEco ? (
          <div className="glass top-shine">
            <TableHeader />
            {d.indicators_economic.map((ind, i) => <IndicatorRow key={i} ind={ind} />)}
          </div>
        ) : (
          <Wip compact note="Indicateurs économiques détaillés — couche 2 (NFP/ISM-équivalents par pays, retail, confiance, balance commerciale)." />
        )}
      </section>

      <section className="mb-14">
        <SectionHead title="Inflation & Prix" meta={hasIndInf ? `${d.indicators_inflation.length} indicateurs` : ''} />
        {hasIndInf ? (
          <div className="glass top-shine">
            <TableHeader />
            {d.indicators_inflation.map((ind, i) => <IndicatorRow key={i} ind={ind} />)}
          </div>
        ) : (
          <Wip compact note="Indicateurs d'inflation détaillés (Core CPI, PPI, anticipations) — couche 2." />
        )}
      </section>

      <section className="mb-14">
        <SectionHead title="Macro Structurel" meta={Array.isArray(d.macro_cards) ? `${d.macro_cards.length} cartes` : 'Cartes'} />
        {d.macro_cards?.wip ? (
          <Wip note="Cartes macro structurelles (PIB, balance commerciale, indice devise, pétrole, VIX) — couche 2." />
        ) : (
          <div className={`grid gap-5 mb-5`} style={{gridTemplateColumns: `repeat(${Math.min(d.macro_cards.length, 5)}, minmax(0, 1fr))`}}>
            {d.macro_cards.map((c, i) => (
              <div key={i} className="glass top-shine p-6 text-center">
                <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mb-2">{c.label}</div>
                <div className="gold-text text-[2rem] font-semibold tracking-tightest leading-none mb-2">{c.value}</div>
                <div className={`text-[0.78rem] font-medium ${TREND_CLS[c.trend]}`}>
                  {ARROW[c.trend === 'up' || c.trend === 'bull' ? 'bull' : c.trend === 'down' || c.trend === 'bear' ? 'bear' : 'neut']} {c.delta}
                </div>
              </div>
            ))}
          </div>
        )}
        <div className="grid grid-cols-2 gap-5">
          <div className="glass top-shine p-6">
            <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-3">{d.gdp_chart?.title || 'PIB'}</div>
            <div className="h-60">
              {(d.gdp_chart?.values?.length) ? <BarChart values={d.gdp_chart.values} labels={d.gdp_chart.labels || []} />
                                              : <Wip compact label="PIB en cours" />}
            </div>
          </div>
          <div className="glass top-shine p-6">
            {d.dxy_chart?.wip ? (
              <Wip compact label="Indice devise en cours" note="Indice par devise — couche 2." />
            ) : (
              <>
                <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-3">{d.dxy_chart.title}</div>
                <div className="h-60"><AreaChart values={d.dxy_chart.values} height={200} /></div>
              </>
            )}
          </div>
        </div>
      </section>

      <IndicatorModal ind={modalInd} onClose={() => setModalInd(null)} />
    </>
  );
}

/* ========== CORRELATION ================================================== */

function corrColor(r) {
  if (r === null || r === undefined) return '#5f5a4d';
  if (r >= 0.7) return '#34d6a5';
  if (r >= 0.3) return '#7ad9b3';
  if (r > -0.3) return '#f5c542';
  if (r > -0.7) return '#ff8b7a';
  return '#ff4757';
}

function corrLabel(r) {
  const a = Math.abs(r);
  if (a >= 0.8) return 'Très forte';
  if (a >= 0.6) return 'Forte';
  if (a >= 0.4) return 'Modérée';
  if (a >= 0.2) return 'Faible';
  return 'Négligeable';
}

function CorrelationGauge({ value }) {
  if (value === null || value === undefined) return null;
  const color = corrColor(value);
  const angle = Math.max(-1, Math.min(1, value)) * 90; // -90 to +90 deg
  const cx = 200, cy = 180, r = 140;
  return (
    <svg viewBox="0 0 400 220" className="w-full max-w-[420px]">
      <defs>
        <linearGradient id="gaugeArc" x1="0" y1="0" x2="1" y2="0">
          <stop offset="0%"   stopColor="#ff4757"/>
          <stop offset="50%"  stopColor="#f5c542"/>
          <stop offset="100%" stopColor="#34d6a5"/>
        </linearGradient>
      </defs>
      {/* Arc fond */}
      <path d={`M ${cx-r} ${cy} A ${r} ${r} 0 0 1 ${cx+r} ${cy}`}
            fill="none" stroke="url(#gaugeArc)" strokeWidth="14" strokeLinecap="round"/>
      {/* Aiguille */}
      <g transform={`rotate(${angle} ${cx} ${cy})`}>
        <line x1={cx} y1={cy} x2={cx} y2={cy - r + 12}
              stroke="#f4d06f" strokeWidth="3" strokeLinecap="round"
              style={{filter: 'drop-shadow(0 0 6px #f4d06f)'}} />
        <circle cx={cx} cy={cy} r="8" fill="#f4d06f"/>
      </g>
      {/* Labels */}
      <text x={cx-r} y={cy+30} textAnchor="middle" fontSize="11" fill="#5f5a4d">-1.0</text>
      <text x={cx} y={cy+30} textAnchor="middle" fontSize="11" fill="#5f5a4d">0.0</text>
      <text x={cx+r} y={cy+30} textAnchor="middle" fontSize="11" fill="#5f5a4d">+1.0</text>
      {/* Valeur */}
      <text x={cx} y={cy-30} textAnchor="middle" fontSize="48" fill={color}
            fontFamily="Inter" fontWeight="700"
            style={{filter:`drop-shadow(0 0 12px ${color})`}}>
        {value >= 0 ? '+' : ''}{value.toFixed(2)}
      </text>
    </svg>
  );
}

function ConstellationMap({ instruments, matrix, labels, selectedA, selectedB, onSelect }) {
  // Place tous les instruments en cercle, regroupes par categorie
  // Affiche les liens depuis selectedA vers tous les autres avec couleur=correlation, epaisseur=|r|
  if (!instruments?.length || !matrix) return null;

  const n = instruments.length;
  const cx = 350, cy = 350, R = 280;

  const idx = labels.indexOf(selectedA);
  const idxB = labels.indexOf(selectedB);

  // Position de chaque noeud
  const nodes = instruments.map((it, i) => {
    const angle = (i / n) * 2 * Math.PI - Math.PI / 2;
    return {
      ...it,
      x: cx + Math.cos(angle) * R,
      y: cy + Math.sin(angle) * R,
      angle,
      i,
    };
  });

  return (
    <svg viewBox="0 0 700 700" className="w-full">
      <defs>
        <radialGradient id="centerGlow">
          <stop offset="0%" stopColor="#f4d06f" stopOpacity="0.25"/>
          <stop offset="100%" stopColor="#f4d06f" stopOpacity="0"/>
        </radialGradient>
      </defs>

      {/* Glow de fond */}
      <circle cx={cx} cy={cy} r={R * 0.95} fill="url(#centerGlow)" />

      {/* Liens depuis selectedA vers tous les autres */}
      {idx >= 0 && nodes.map((n2, j) => {
        if (j === idx) return null;
        const r = matrix[idx]?.[j];
        if (r === null || r === undefined) return null;
        const isSelected = j === idxB;
        const opacity = isSelected ? 0.95 : Math.max(0.08, Math.abs(r) * 0.55);
        const stroke = isSelected ? '#f4d06f' : corrColor(r);
        const sw = isSelected ? 3 : Math.max(0.5, Math.abs(r) * 2.2);
        return (
          <line key={j} x1={nodes[idx].x} y1={nodes[idx].y} x2={n2.x} y2={n2.y}
                stroke={stroke} strokeWidth={sw} strokeOpacity={opacity}
                style={isSelected ? {filter:'drop-shadow(0 0 6px #f4d06f)'} : null}/>
        );
      })}

      {/* Noeuds */}
      {nodes.map((n2, j) => {
        const isA = j === idx;
        const isB = j === idxB;
        const r = idx >= 0 ? matrix[idx]?.[j] : null;
        const fill = isA ? '#f4d06f' : isB ? '#fff' : corrColor(r);
        const size = isA ? 9 : isB ? 8 : 5;
        // Position du label (offset radial)
        const lx = cx + Math.cos(n2.angle) * (R + 25);
        const ly = cy + Math.sin(n2.angle) * (R + 25);
        const anchor = Math.cos(n2.angle) > 0.2 ? 'start' : Math.cos(n2.angle) < -0.2 ? 'end' : 'middle';
        return (
          <g key={j}
             className="cursor-pointer"
             onClick={() => onSelect(n2.label)}>
            <circle cx={n2.x} cy={n2.y} r={size + 6} fill="transparent"/>
            <circle cx={n2.x} cy={n2.y} r={size} fill={fill}
                    stroke={isA || isB ? '#f4d06f' : 'none'} strokeWidth={isA || isB ? 1.5 : 0}
                    style={(isA || isB) ? {filter:`drop-shadow(0 0 ${isA ? 12 : 8}px ${fill})`} : null}/>
            <text x={lx} y={ly} textAnchor={anchor} fontSize="11"
                  fill={isA || isB ? '#f4d06f' : '#b0aa9a'}
                  fontWeight={isA || isB ? '600' : '400'}
                  fontFamily="Inter">
              {n2.label}
            </text>
          </g>
        );
      })}

      {/* Centre : libelle de A */}
      {idx >= 0 && (
        <text x={cx} y={cy} textAnchor="middle" fontSize="14"
              fill="#f4d06f" fontWeight="600" fontFamily="Inter"
              style={{filter:'drop-shadow(0 0 4px #f4d06f)'}}>
          {selectedA} → all
        </text>
      )}
    </svg>
  );
}

function PairOverlay({ data }) {
  if (!data?.series_a?.length) return null;
  const sa = data.series_a, sb = data.series_b;
  const width = 800, height = 200, padX = 40, padTop = 20, padBottom = 30;
  const allMin = 0, allMax = 100, range = 100;

  const mk = s => s.map((v, i) => [
    padX + (i / (s.length - 1)) * (width - padX * 2),
    padTop + (1 - (v - allMin) / range) * (height - padTop - padBottom),
  ]);
  const pa = mk(sa).map((p,i) => `${i===0?'M':'L'} ${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' ');
  const pb = mk(sb).map((p,i) => `${i===0?'M':'L'} ${p[0].toFixed(1)} ${p[1].toFixed(1)}`).join(' ');

  return (
    <svg viewBox={`0 0 ${width} ${height}`} className="w-full h-full">
      {[0.25, 0.5, 0.75].map((r, i) => (
        <line key={i} x1={padX} x2={width-padX}
              y1={padTop + r*(height-padTop-padBottom)}
              y2={padTop + r*(height-padTop-padBottom)}
              stroke="rgba(212,175,55,0.05)" strokeDasharray="2 4"/>
      ))}
      <path d={pa} fill="none" stroke="#f4d06f" strokeWidth="1.6"/>
      <path d={pb} fill="none" stroke="#34d6a5" strokeWidth="1.6" strokeDasharray="3 3"/>
      <g transform={`translate(${padX}, 8)`}>
        <rect width="9" height="2" y="3" fill="#f4d06f"/>
        <text x="14" y="8" fontSize="10" fill="#b0aa9a">{data.a}</text>
        <rect x="120" width="9" height="2" y="3" fill="#34d6a5"/>
        <text x="134" y="8" fontSize="10" fill="#b0aa9a">{data.b}</text>
        <text x={width-padX*2} y="8" fontSize="10" fill="#5f5a4d" textAnchor="end">{data.days}j normalisé 0-100</text>
      </g>
    </svg>
  );
}

function CategoryDropdown({ value, onChange, instruments, label }) {
  const grouped = useMemo(() => {
    const g = {};
    (instruments || []).forEach(it => {
      if (!g[it.category]) g[it.category] = [];
      g[it.category].push(it);
    });
    return g;
  }, [instruments]);

  return (
    <div className="flex flex-col gap-2">
      <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium">{label}</div>
      <select
        value={value || ''}
        onChange={e => onChange(e.target.value)}
        className="bg-black/60 border border-gold/30 text-ink px-4 py-3 text-[0.92rem] gold-text font-medium hover:border-gold/60 focus:border-gold-hi focus:outline-none">
        {Object.entries(grouped).map(([cat, items]) => (
          <optgroup key={cat} label={cat}>
            {items.map(it => <option key={it.label} value={it.label}>{it.label}</option>)}
          </optgroup>
        ))}
      </select>
    </div>
  );
}

function MatchRow({ a, b, data, onRemove, onPromote, onSwap, isPrimary }) {
  if (!data) {
    return (
      <div className="grid items-center gap-4 px-4 py-3 border-b border-gold/[0.06]"
           style={{gridTemplateColumns:'1.4fr 80px 1fr 1fr 110px'}}>
        <span className="text-[0.86rem] text-ink-soft">{b}</span>
        <span className="text-ink-mute text-xs">…</span>
        <span/><span/><span/>
      </div>
    );
  }
  const r = data.correlation;
  const color = corrColor(r);
  const pct = Math.min(100, Math.abs(r) * 100);
  return (
    <div className={`grid items-center gap-4 px-4 py-3 border-b border-gold/[0.06] transition-colors ${
                      isPrimary ? 'bg-gold/[0.04]' : 'hover:bg-white/[0.02]'
                    }`}
         style={{gridTemplateColumns:'1.4fr 80px 1fr 1fr 110px'}}>
      <span className="text-[0.88rem] text-ink font-medium flex items-center gap-2">
        {isPrimary && <span className="text-[0.55rem] tracking-[0.2em] uppercase gold-text">★</span>}
        {b}
      </span>
      <span className="text-[0.95rem] font-semibold tracking-tight text-right" style={{color}}>
        {r >= 0 ? '+' : ''}{r.toFixed(2)}
      </span>
      <div className="h-1.5 bg-black/40 overflow-hidden rounded-sm">
        <div className="h-full transition-all" style={{width:`${pct}%`, background:color, boxShadow:`0 0 8px ${color}`}}/>
      </div>
      <span className="text-[0.75rem] text-ink-soft">{data.strength} {r >= 0 ? 'positive' : 'négative'}</span>
      <div className="flex justify-end gap-1.5">
        <button onClick={() => onSwap(b)}
                title={`Swap : ${b} devient la référence A, ${a} prend sa place`}
                className="text-gold-dim hover:text-gold-hi text-[0.78rem] px-2 py-1 border border-gold/15 hover:border-gold/40 leading-none"
                style={{fontFamily:'system-ui'}}>
          ⇄
        </button>
        {!isPrimary && (
          <button onClick={() => onPromote(b)}
                  title="Mettre en tête (vue détaillée)"
                  className="text-gold-dim hover:text-gold-hi text-[0.7rem] px-2 py-1 border border-gold/15 hover:border-gold/40">
            ★
          </button>
        )}
        <button onClick={() => onRemove(b)}
                title="Retirer"
                className="text-ink-mute hover:text-bear text-[0.7rem] px-2 py-1 border border-gold/15 hover:border-bear">
          ×
        </button>
      </div>
    </div>
  );
}

function TabCorrelation() {
  const [instruments, setInstruments] = useState([]);
  const [matrixData, setMatrixData] = useState(null);
  const [pairA, setPairA] = useState('EUR/USD');
  const [pairsB, setPairsB] = useState(['GBP/USD']);
  const [pendingB, setPendingB] = useState('USD/JPY');
  const [pairsData, setPairsData] = useState({});  // { "GBP/USD": {...}, ... }
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [mode, setMode] = useState('1vAll'); // '1v1' | '1vAll' | 'setup'

  // ===== Mode "setup" : analyse d'un panier d'idees de trade =================
  const [setupPairs, setSetupPairs] = useState(['EUR/USD', 'GBP/USD', 'USD/JPY']);
  const [setupPending, setSetupPending] = useState('AUD/USD');
  const [setupResult, setSetupResult] = useState(null);
  const [setupLoading, setSetupLoading] = useState(false);
  const [setupError, setSetupError] = useState(null);

  const addSetupPair = () => {
    if (!setupPending || setupPairs.includes(setupPending)) return;
    setSetupPairs([...setupPairs, setupPending]);
    setSetupResult(null);
  };
  const removeSetupPair = (p) => {
    setSetupPairs(setupPairs.filter(x => x !== p));
    setSetupResult(null);
  };
  const runSetupAnalysis = () => {
    if (setupPairs.length < 2) {
      setSetupError('Ajoute au moins 2 paires pour analyser le setup.');
      return;
    }
    setSetupLoading(true);
    setSetupError(null);
    const url = `/api/correlation/basket?pairs=${encodeURIComponent(setupPairs.join(','))}`;
    fetch(url)
      .then(r => r.ok ? r.json() : r.json().then(j => Promise.reject(j.detail || 'Erreur API')))
      .then(d => { setSetupResult(d); setSetupLoading(false); })
      .catch(e => { setSetupError(typeof e === 'string' ? e : 'Erreur lors de l\'analyse'); setSetupLoading(false); });
  };

  // Init: charge instruments + matrice
  useEffect(() => {
    Promise.all([
      fetch('/api/correlation/instruments').then(r => r.json()),
      fetch('/api/correlation/matrix').then(r => r.json()),
    ]).then(([insts, mat]) => {
      setInstruments(insts.instruments);
      setMatrixData(mat);
      setLoading(false);
    }).catch(e => {
      setError(e.message);
      setLoading(false);
    });
  }, []);

  // (Re)charge les correlations A vs chaque B quand A ou la liste change
  useEffect(() => {
    if (!pairA) return;
    const targets = pairsB.filter(b => b && b !== pairA);
    targets.forEach(b => {
      const key = `${pairA}::${b}`;
      if (pairsData[key]) return;  // deja en cache
      fetch(`/api/correlation/pair?a=${encodeURIComponent(pairA)}&b=${encodeURIComponent(b)}`)
        .then(r => r.ok ? r.json() : null)
        .then(d => {
          if (!d) return;
          setPairsData(prev => ({ ...prev, [key]: d }));
        })
        .catch(() => {});
    });
  }, [pairA, pairsB]);

  // Quand A change, on vide le cache (toutes les correlations sont obsoletes)
  useEffect(() => {
    setPairsData({});
  }, [pairA]);

  // Mode 1v1 : un seul B autorisé ; on tronque si on bascule depuis 1vAll.
  useEffect(() => {
    if (mode === '1v1' && pairsB.length > 1) {
      setPairsB(pairsB.slice(0, 1));
    }
  }, [mode]);

  if (loading) return <div className="text-ink-soft text-center py-20">Chargement des corrélations...</div>;
  if (error) return <div className="text-bear text-center py-20">Erreur : {error}</div>;

  const primaryB = pairsB[0];
  const primaryData = primaryB ? pairsData[`${pairA}::${primaryB}`] : null;

  const addPair = () => {
    if (!pendingB || pendingB === pairA || pairsB.includes(pendingB)) return;
    setPairsB([...pairsB, pendingB]);
  };
  const removePair = (b) => {
    const next = pairsB.filter(x => x !== b);
    setPairsB(next.length ? next : [pendingB && pendingB !== pairA ? pendingB : 'GBP/USD']);
  };
  const promotePair = (b) => {
    setPairsB([b, ...pairsB.filter(x => x !== b)]);
  };
  // Swap : la paire B cliquée devient A ; A prend la place de B dans la liste.
  const swapWith = (b) => {
    if (!b || b === pairA) return;
    setPairsB(prev => prev.map(x => x === b ? pairA : x));
    setPairA(b);
  };
  const onSelectFromMap = (label) => {
    if (label === pairA) return;
    if (pairsB.includes(label)) {
      promotePair(label);
    } else {
      setPairsB([label, ...pairsB]);
    }
  };

  // Pre-filtre pour le dropdown d'ajout : exclure A et celles deja matchees
  const availableForAdd = instruments.filter(it => it.label !== pairA && !pairsB.includes(it.label));

  return (
    <>
      <section className="mb-10">
        <SectionHead title="Sélection des paires"
                     meta={mode === '1v1'
                       ? '1 vs 1 — comparaison directe'
                       : mode === 'setup'
                         ? `Setup — analyse d'un panier de ${setupPairs.length} idée${setupPairs.length>1?'s':''}`
                         : `1 vs all — ${instruments.length} instruments · ${pairsB.length} matchés`} />

        {/* Mode toggle */}
        <div className="flex items-center gap-3 mb-5">
          <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mr-1">Mode</div>
          {[
            { k: '1v1',   label: '1 vs 1',   sub: 'duel' },
            { k: '1vAll', label: '1 vs all', sub: 'multi-match' },
            { k: 'setup', label: 'Setup',    sub: 'panier d\'idées' },
          ].map(({ k, label, sub }) => (
            <button key={k} onClick={() => setMode(k)}
                    className={`flex items-baseline gap-2 px-4 py-2 border transition-colors text-[0.7rem] tracking-[0.22em] uppercase ${
                      mode === k
                        ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.16]'
                        : 'border-gold/40 text-ink font-medium hover:border-gold/70 hover:text-gold-hi'
                    }`}>
              <span className="font-semibold">{label}</span>
              <span className="text-[0.55rem] text-ink-mute">{sub}</span>
            </button>
          ))}
        </div>

        <div className="glass top-shine p-7">
          {mode === 'setup' ? (
            // ===== MODE SETUP (panier d'idees de trade) ===================
            <SetupPanel
              instruments={instruments}
              setupPairs={setupPairs}
              setupPending={setupPending}
              setSetupPending={setSetupPending}
              addSetupPair={addSetupPair}
              removeSetupPair={removeSetupPair}
              runSetupAnalysis={runSetupAnalysis}
              setupLoading={setupLoading} />
          ) : mode === '1v1' ? (
            // ===== MODE 1 vs 1 ===========================================
            <div className="grid items-end gap-5" style={{gridTemplateColumns:'1fr auto 1fr'}}>
              <CategoryDropdown value={pairA} onChange={setPairA}
                                instruments={instruments}
                                label="Paire A" />
              <button onClick={() => {
                        const b = pairsB[0];
                        if (b) swapWith(b);
                      }}
                      disabled={!pairsB[0] || pairsB[0] === pairA}
                      title="Swap A ↔ B"
                      className="border border-gold/40 bg-gold/[0.04] text-gold-hi px-4 py-3 text-[1.1rem] hover:bg-gold/10 hover:border-gold-hi transition-all disabled:opacity-30 disabled:cursor-not-allowed leading-none"
                      style={{fontFamily:'system-ui'}}>
                ⇄
              </button>
              <CategoryDropdown
                value={pairsB[0] || ''}
                onChange={(v) => v && v !== pairA && setPairsB([v])}
                instruments={instruments.filter(it => it.label !== pairA)}
                label="Paire B" />
            </div>
          ) : (
            // ===== MODE 1 vs ALL =========================================
            <>
              <div className="grid gap-8 mb-6" style={{gridTemplateColumns:'1fr 1fr auto'}}>
                <CategoryDropdown value={pairA} onChange={setPairA} instruments={instruments} label="Instrument A (référence)" />
                <CategoryDropdown
                  value={pendingB}
                  onChange={setPendingB}
                  instruments={availableForAdd.length ? availableForAdd : instruments}
                  label="Ajouter un instrument à matcher" />
                <div className="flex flex-col justify-end">
                  <button
                    onClick={addPair}
                    disabled={!pendingB || pendingB === pairA || pairsB.includes(pendingB)}
                    className="border border-gold/40 bg-gold/[0.04] text-gold-hi px-5 py-3 text-[0.7rem] tracking-[0.22em] uppercase hover:bg-gold/10 hover:border-gold-hi transition-all disabled:opacity-30 disabled:cursor-not-allowed">
                    + Ajouter
                  </button>
                </div>
              </div>

              <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mb-3">
                Matches actuels ({pairA} ↔ …)
              </div>
              <div className="border border-gold/[0.1]">
                <div className="grid items-center gap-4 px-4 py-2.5 text-[0.6rem] tracking-[0.22em] uppercase text-gold font-medium border-b border-gold/[0.15] bg-black/40"
                     style={{gridTemplateColumns:'1.4fr 80px 1fr 1fr 110px'}}>
                  <span>Instrument B</span>
                  <span className="text-right">r</span>
                  <span>Intensité</span>
                  <span>Force</span>
                  <span className="text-right">Actions</span>
                </div>
                {pairsB.map((b, i) => (
                  <MatchRow
                    key={b}
                    a={pairA}
                    b={b}
                    data={pairsData[`${pairA}::${b}`]}
                    onRemove={removePair}
                    onPromote={promotePair}
                    onSwap={swapWith}
                    isPrimary={i === 0}
                  />
                ))}
                {pairsB.length === 0 && (
                  <div className="px-4 py-6 text-ink-mute text-sm text-center">
                    Aucune paire à matcher — ajoutez-en une via le sélecteur ci-dessus.
                  </div>
                )}
              </div>
            </>
          )}
        </div>
      </section>

      {/* ===== Resultats Setup ====================================== */}
      {mode === 'setup' && (
        <SetupResult
          result={setupResult}
          loading={setupLoading}
          error={setupError}
          pairs={setupPairs} />
      )}

      {mode !== 'setup' && primaryData && primaryB !== pairA && (
        <section className="mb-10">
          <SectionHead title={`${primaryData.a} ↔ ${primaryData.b}`} meta={`Corrélation Pearson · ${primaryData.days} jours · paire principale`} />
          <div className="grid grid-cols-2 gap-5">
            <div className="glass top-shine p-7 flex flex-col items-center justify-center">
              <CorrelationGauge value={primaryData.correlation} />
              <div className="mt-4 text-[0.7rem] tracking-[0.32em] uppercase font-medium"
                   style={{color: corrColor(primaryData.correlation)}}>
                {primaryData.strength} {primaryData.correlation >= 0 ? '· positive' : '· négative'}
              </div>
            </div>
            <div className="glass top-shine p-7 flex flex-col">
              <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-4">Interprétation</div>
              <p className="text-ink-soft text-[0.92rem] leading-relaxed mb-6">{primaryData.interpretation}</p>
              <div className="flex justify-around mt-auto pt-4 border-t border-gold/[0.08]">
                <div className="text-center">
                  <div className="text-[0.6rem] tracking-[0.2em] uppercase text-ink-mute mb-1">{primaryData.a}</div>
                  <div className="gold-text text-[1.3rem] font-semibold">{primaryData.raw_a_last}</div>
                </div>
                <div className="text-center">
                  <div className="text-[0.6rem] tracking-[0.2em] uppercase text-ink-mute mb-1">{primaryData.b}</div>
                  <div className="gold-text text-[1.3rem] font-semibold">{primaryData.raw_b_last}</div>
                </div>
              </div>
            </div>
          </div>

          <div className="glass top-shine p-7 mt-5">
            <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-4">
              Évolution conjointe (normalisée 0-100)
            </div>
            <div className="h-52">
              <PairOverlay data={primaryData} />
            </div>
          </div>
        </section>
      )}

      {mode !== 'setup' && (
      <section className="mb-14">
        <SectionHead title="Constellation des corrélations"
                     meta={`${pairA} vs tout le reste · cliquez pour ajouter au matching`} />
        <div className="glass top-shine p-6">
          <ConstellationMap
            instruments={instruments}
            matrix={matrixData?.matrix}
            labels={matrixData?.labels || []}
            selectedA={pairA}
            selectedB={primaryB}
            onSelect={onSelectFromMap} />
          <div className="grid grid-cols-5 gap-2 mt-6 text-[0.65rem] tracking-[0.18em] uppercase">
            {[
              {l:'Forte +',  c:'#34d6a5'},
              {l:'Modérée +',c:'#7ad9b3'},
              {l:'Faible',   c:'#f5c542'},
              {l:'Modérée -',c:'#ff8b7a'},
              {l:'Forte -',  c:'#ff4757'},
            ].map((s,i) => (
              <div key={i} className="flex items-center gap-2">
                <span className="w-3 h-3 rounded-full" style={{background:s.c}}/>
                <span className="text-ink-mute">{s.l}</span>
              </div>
            ))}
          </div>
        </div>
      </section>
      )}
    </>
  );
}

/* ========== SETUP (panier d'idees de trade) ============================== */

function SetupPanel({ instruments, setupPairs, setupPending, setSetupPending,
                     addSetupPair, removeSetupPair, runSetupAnalysis, setupLoading }) {
  const available = instruments.filter(it => !setupPairs.includes(it.label));
  return (
    <>
      <div className="text-[0.78rem] text-ink-soft leading-relaxed mb-5">
        Sélectionne tes idées de trade (2 à 10 paires). L'analyse te dit lesquelles sont des
        <strong className="gold-text"> setups distincts</strong> et lesquelles
        sont <strong style={{color:'#ff4757'}}>redondantes</strong> (à arbitrer).
      </div>

      <div className="grid gap-5 mb-5" style={{gridTemplateColumns:'1fr auto'}}>
        <CategoryDropdown
          value={setupPending}
          onChange={setSetupPending}
          instruments={available.length ? available : instruments}
          label={`Ajouter une idée (${setupPairs.length}/10)`} />
        <div className="flex flex-col justify-end">
          <button onClick={addSetupPair}
                  disabled={!setupPending || setupPairs.includes(setupPending) || setupPairs.length >= 10}
                  className="border border-gold/40 bg-gold/[0.04] text-gold-hi px-5 py-3 text-[0.7rem] tracking-[0.22em] uppercase hover:bg-gold/10 hover:border-gold-hi transition-all disabled:opacity-30 disabled:cursor-not-allowed">
            + Ajouter
          </button>
        </div>
      </div>

      <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mb-2">
        Idées sélectionnées
      </div>
      <div className="flex flex-wrap gap-2 mb-6 min-h-[44px] p-3 border border-gold/[0.1] bg-black/30">
        {setupPairs.length === 0 && (
          <span className="text-ink-mute text-[0.78rem]">Aucune idée sélectionnée — ajoutes-en au moins 2.</span>
        )}
        {setupPairs.map((p, i) => (
          <span key={p} className="inline-flex items-center gap-2 px-3 py-1.5 border border-gold/30 bg-gold/[0.05] text-[0.82rem] gold-text">
            <span className="text-[0.62rem] tracking-[0.2em] uppercase text-gold-dim">{i+1}</span>
            {p}
            <button onClick={() => removeSetupPair(p)}
                    className="text-ink-mute hover:text-bear text-[0.9rem] leading-none ml-1"
                    title="Retirer">×</button>
          </span>
        ))}
      </div>

      <div className="flex items-center gap-4">
        <button onClick={runSetupAnalysis}
                disabled={setupPairs.length < 2 || setupLoading}
                className="border-2 border-gold-hi bg-gold/[0.08] gold-text px-7 py-3 text-[0.78rem] tracking-[0.28em] uppercase font-semibold hover:bg-gold/15 transition-all disabled:opacity-30 disabled:cursor-not-allowed">
          {setupLoading ? 'Analyse en cours…' : '⚡ Analyser le setup'}
        </button>
        <span className="text-[0.7rem] text-ink-mute">
          Pearson · 90 jours · indépendant si |r| ≤ 30%, redondant si |r| {'>'} 60%
        </span>
      </div>
    </>
  );
}


function SetupResult({ result, loading, error, pairs }) {
  if (loading) {
    return (
      <section className="mb-14">
        <div className="glass top-shine p-10 text-center text-gold-dim text-[0.78rem] tracking-[0.32em] uppercase">
          Calcul des corrélations…
        </div>
      </section>
    );
  }
  if (error) {
    return (
      <section className="mb-14">
        <div className="glass top-shine p-7 border border-bear/30">
          <div className="text-bear text-[0.82rem]">⚠ {error}</div>
        </div>
      </section>
    );
  }
  if (!result) {
    return (
      <section className="mb-14">
        <div className="glass top-shine p-10 text-center text-ink-mute text-[0.82rem]">
          Configure tes idées et clique sur <strong className="gold-text">Analyser le setup</strong>.
        </div>
      </section>
    );
  }
  if (result.error) {
    return (
      <section className="mb-14">
        <div className="glass top-shine p-7 border border-bear/30">
          <div className="text-bear text-[0.82rem]">⚠ {result.error}</div>
        </div>
      </section>
    );
  }

  const RISK_COLOR = { Vert: '#34d6a5', Orange: '#f5c542', Rouge: '#ff4757' };
  const riskColor = RISK_COLOR[result.risk] || '#f5c542';
  const VERDICT_LABEL = {
    OK: 'Setup OK · diversification réelle',
    Mixte: 'Setup mixte · arbitrage requis',
    Concentre: 'Setup concentré · redondance forte',
  };

  const bucketStyle = (bk) => {
    if (bk === 'high')       return { color: '#ff4757', label: 'Redondant' };
    if (bk === 'moderate')   return { color: '#f5c542', label: 'À surveiller' };
    return { color: '#34d6a5', label: 'Indépendant' };
  };

  return (
    <>
      {/* === Verdict + reco === */}
      <section className="mb-10">
        <SectionHead title="Verdict du setup" meta={`${result.labels.length} paires · ${result.n_distinct_setups} setup${result.n_distinct_setups>1?'s':''} distinct${result.n_distinct_setups>1?'s':''}`} />
        <div className="glass top-shine p-8 grid gap-6" style={{gridTemplateColumns:'auto 1fr'}}>
          <div className="flex flex-col items-center justify-center px-6 border-r border-gold/[0.12]">
            <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold-dim mb-2">Risque</div>
            <div className="text-[2.4rem] font-semibold leading-none"
                 style={{color: riskColor, textShadow: `0 0 20px ${riskColor}`}}>
              {result.risk}
            </div>
            <div className="text-[0.7rem] tracking-[0.22em] uppercase text-ink-soft mt-3">{VERDICT_LABEL[result.verdict] || result.verdict}</div>
          </div>
          <div className="flex flex-col justify-center">
            <p className="text-ink-soft text-[0.95rem] leading-relaxed">{result.reco}</p>
            {result.missing && result.missing.length > 0 && (
              <div className="mt-4 text-[0.78rem] text-bear">
                Données indisponibles pour : {result.missing.join(', ')}
              </div>
            )}
          </div>
        </div>
      </section>

      {/* === Clusters === */}
      <section className="mb-10">
        <SectionHead title="Groupes" meta={`${result.independent_setups.length} solos · ${result.cluster_groups.length} groupes`} />
        <div className="grid gap-4">
          {result.independent_setups.length > 0 && (
            <div className="glass top-shine p-6">
              <div className="text-[0.62rem] tracking-[0.4em] uppercase font-medium mb-3" style={{color:'#34d6a5'}}>
                ✓ Idées indépendantes — tradables ensemble
              </div>
              <div className="flex flex-wrap gap-2">
                {result.independent_setups.map(p => (
                  <span key={p} className="px-3 py-1.5 border border-bull/40 bg-bull/[0.07] text-bull text-[0.82rem]">{p}</span>
                ))}
              </div>
            </div>
          )}
          {result.cluster_groups.map((group, i) => (
            <div key={i} className="glass top-shine p-6 border border-bear/30">
              <div className="text-[0.62rem] tracking-[0.4em] uppercase font-medium mb-3" style={{color:'#ff4757'}}>
                ⚠ Groupe {String.fromCharCode(65+i)} — choisis-en UNE seule (les {group.length} sont fortement liées)
              </div>
              <div className="flex flex-wrap gap-2">
                {group.map(p => (
                  <span key={p} className="px-3 py-1.5 border border-bear/40 bg-bear/[0.07] text-bear text-[0.82rem]">{p}</span>
                ))}
              </div>
            </div>
          ))}
        </div>
      </section>

      {/* === Détail des couples === */}
      <section className="mb-10">
        <SectionHead title="Détail des corrélations" meta={`${result.pairs.length} couples analysés`} />
        <div className="glass top-shine">
          <div className="grid items-center gap-4 px-4 py-3 text-[0.6rem] tracking-[0.22em] uppercase text-gold font-medium border-b border-gold/[0.15] bg-black/40"
               style={{gridTemplateColumns:'1.6fr 1.6fr 90px 1fr 130px'}}>
            <span>Paire A</span>
            <span>Paire B</span>
            <span className="text-right">r</span>
            <span>Intensité</span>
            <span className="text-right">Statut</span>
          </div>
          {result.pairs
            .slice()
            .sort((a, b) => b.abs_r - a.abs_r)
            .map((p, i) => {
              const st = bucketStyle(p.bucket);
              const pct = Math.min(100, p.abs_r * 100);
              return (
                <div key={i} className="grid items-center gap-4 px-4 py-3 border-b border-gold/[0.06] hover:bg-white/[0.02]"
                     style={{gridTemplateColumns:'1.6fr 1.6fr 90px 1fr 130px'}}>
                  <span className="text-[0.86rem] text-ink">{p.a}</span>
                  <span className="text-[0.86rem] text-ink">{p.b}</span>
                  <span className="text-[0.92rem] font-semibold tabular-nums text-right" style={{color: st.color}}>
                    {p.r >= 0 ? '+' : ''}{p.r.toFixed(2)}
                  </span>
                  <div className="h-1.5 bg-black/40 overflow-hidden rounded-sm">
                    <div className="h-full transition-all" style={{width:`${pct}%`, background:st.color, boxShadow:`0 0 6px ${st.color}`}}/>
                  </div>
                  <span className="text-[0.7rem] tracking-[0.22em] uppercase font-medium text-right" style={{color: st.color}}>
                    {st.label}
                  </span>
                </div>
              );
            })}
        </div>
      </section>
    </>
  );
}

/* ========== SENTIMENT RETAIL (MYFXBOOK) ================================== */

function SentimentBar({ longPct, shortPct, height = 16 }) {
  // Barre horizontale long/short avec gradient et texte centre
  return (
    <div className="w-full">
      <div className="flex overflow-hidden border border-gold/15" style={{height}}>
        <div className="h-full transition-all flex items-center justify-end pr-2"
             style={{width:`${longPct}%`, background:'linear-gradient(90deg, rgba(52,214,165,0.25), rgba(52,214,165,0.65))', boxShadow:'inset 0 0 12px rgba(52,214,165,0.4)'}}>
          {longPct >= 12 && <span className="text-[0.62rem] font-semibold text-bull tracking-wider" style={{textShadow:'0 0 6px rgba(0,0,0,0.6)'}}>{longPct}%</span>}
        </div>
        <div className="h-full transition-all flex items-center pl-2"
             style={{width:`${shortPct}%`, background:'linear-gradient(90deg, rgba(255,71,87,0.65), rgba(255,71,87,0.25))', boxShadow:'inset 0 0 12px rgba(255,71,87,0.4)'}}>
          {shortPct >= 12 && <span className="text-[0.62rem] font-semibold text-bear tracking-wider" style={{textShadow:'0 0 6px rgba(0,0,0,0.6)'}}>{shortPct}%</span>}
        </div>
      </div>
    </div>
  );
}

function SentimentWheel({ longPct, shortPct, size = 260 }) {
  // Donut SVG : arc long (vert) + arc short (rouge), texte central
  if (longPct == null || shortPct == null) return null;
  const cx = size/2, cy = size/2;
  const radius = size/2 - 18;
  const stroke = 22;
  const C = 2 * Math.PI * radius;
  const longLen = (longPct/100) * C;
  const dominant = longPct >= shortPct ? 'long' : 'short';
  const dominantPct = Math.max(longPct, shortPct);

  return (
    <svg viewBox={`0 0 ${size} ${size}`} className="w-full max-w-[280px]">
      <defs>
        <linearGradient id="bullArc" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0%"  stopColor="#34d6a5" stopOpacity="0.95"/>
          <stop offset="100%" stopColor="#1f8a6a" stopOpacity="0.85"/>
        </linearGradient>
        <linearGradient id="bearArc" x1="1" y1="0" x2="0" y2="1">
          <stop offset="0%"  stopColor="#ff4757" stopOpacity="0.95"/>
          <stop offset="100%" stopColor="#a02a35" stopOpacity="0.85"/>
        </linearGradient>
        <filter id="wheelGlow" x="-20%" y="-20%" width="140%" height="140%">
          <feGaussianBlur stdDeviation="3" result="b"/>
          <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
        </filter>
      </defs>
      {/* Track de fond */}
      <circle cx={cx} cy={cy} r={radius} fill="none" stroke="rgba(212,175,55,0.08)" strokeWidth={stroke}/>
      {/* Arc short (rouge) - dessous */}
      <circle cx={cx} cy={cy} r={radius} fill="none"
              stroke="url(#bearArc)" strokeWidth={stroke}
              strokeDasharray={`${C} 0`}
              transform={`rotate(-90 ${cx} ${cy})`}/>
      {/* Arc long (vert) - dessus */}
      <circle cx={cx} cy={cy} r={radius} fill="none"
              stroke="url(#bullArc)" strokeWidth={stroke}
              strokeDasharray={`${longLen} ${C-longLen}`}
              strokeLinecap="butt"
              transform={`rotate(-90 ${cx} ${cy})`}
              filter="url(#wheelGlow)"/>
      {/* Texte central */}
      <text x={cx} y={cy-6} textAnchor="middle" fontSize="46" fontWeight="700" fontFamily="Inter"
            fill={dominant === 'long' ? '#34d6a5' : '#ff4757'}
            style={{filter:`drop-shadow(0 0 14px ${dominant === 'long' ? '#34d6a5' : '#ff4757'})`}}>
        {dominantPct}%
      </text>
      <text x={cx} y={cy+18} textAnchor="middle" fontSize="11"
            fill="#b0aa9a" fontFamily="Inter" letterSpacing="3">
        {dominant === 'long' ? 'LONG' : 'SHORT'}
      </text>
    </svg>
  );
}

function SentimentSelector({ value, onChange, pairs }) {
  return (
    <div className="flex flex-col gap-2">
      <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium">Paire</div>
      <select value={value || ''} onChange={e => onChange(e.target.value)}
              className="bg-black/60 border border-gold/30 text-ink px-4 py-3 text-[0.92rem] gold-text font-medium hover:border-gold/60 focus:border-gold-hi focus:outline-none">
        {pairs.map(p => (
          <option key={p.symbol} value={p.symbol}>{p.symbol}</option>
        ))}
      </select>
    </div>
  );
}

function ContrarianHint({ longPct }) {
  // Le sentiment retail est traditionnellement contrarien (>70% = signal de retournement)
  if (longPct == null) return null;
  let txt, color, label;
  if (longPct >= 75) {
    txt = "Foule massivement longue — risque de retournement baissier élevé. Position contrarian : SHORT.";
    color = '#ff4757'; label = 'CONTRARIAN SHORT';
  } else if (longPct >= 60) {
    txt = "Retail majoritairement long. Lecture contrarian modérée : prudence sur les achats.";
    color = '#ff8b7a'; label = 'BIAIS BAISSIER';
  } else if (longPct <= 25) {
    txt = "Foule massivement short — risque de squeeze haussier. Position contrarian : LONG.";
    color = '#34d6a5'; label = 'CONTRARIAN LONG';
  } else if (longPct <= 40) {
    txt = "Retail majoritairement short. Lecture contrarian modérée : surveiller les achats.";
    color = '#7ad9b3'; label = 'BIAIS HAUSSIER';
  } else {
    txt = "Sentiment équilibré. Pas de signal contrarian fort.";
    color = '#f5c542'; label = 'NEUTRE';
  }
  return (
    <div className="glass top-shine p-6">
      <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-3">Lecture contrarian</div>
      <div className="inline-block text-[0.65rem] tracking-[0.32em] uppercase px-4 py-2 border font-medium mb-4"
           style={{borderColor: color, color}}>{label}</div>
      <div className="text-[0.88rem] text-ink-soft leading-relaxed">{txt}</div>
    </div>
  );
}

function SentimentRanking({ pairs, selected, onSelect }) {
  // Tri par % long decroissant
  const sorted = [...pairs].sort((a,b) => b.long_pct - a.long_pct);
  return (
    <div className="space-y-1.5">
      {sorted.map(p => {
        const isSel = p.symbol === selected;
        return (
          <button key={p.symbol}
                  onClick={() => onSelect(p.symbol)}
                  className={`w-full grid items-center gap-3 px-4 py-2.5 transition-colors text-left ${
                    isSel ? 'bg-gold/[0.06] border-l-2 border-gold-hi' : 'hover:bg-white/[0.02] border-l-2 border-transparent'
                  }`}
                  style={{gridTemplateColumns:'80px 1fr 80px'}}>
            <span className={`text-[0.8rem] font-semibold tracking-wide ${isSel ? 'gold-text' : 'text-ink'}`}>{p.symbol}</span>
            <SentimentBar longPct={p.long_pct} shortPct={p.short_pct} height={12}/>
            <span className="text-[0.74rem] text-right">
              <span className="text-bull">{p.long_pct}</span>
              <span className="text-ink-mute mx-1">/</span>
              <span className="text-bear">{p.short_pct}</span>
            </span>
          </button>
        );
      })}
    </div>
  );
}

function PositionsBlock({ pair }) {
  if (!pair) return null;
  const fmtPrice = v => v == null ? '—' : (v < 100 ? v.toFixed(4) : v.toFixed(2));
  const fmtVol = v => {
    if (v == null) return '—';
    if (v >= 1_000_000) return `${(v/1_000_000).toFixed(1)}M`;
    if (v >= 1_000)     return `${(v/1_000).toFixed(1)}K`;
    return v.toFixed(0);
  };
  const fmtPos = v => v == null ? '—' : v.toLocaleString('fr-FR');

  const stats = [
    { l:'Volume long',  v: fmtVol(pair.long_volume),  c:'#34d6a5'},
    { l:'Volume short', v: fmtVol(pair.short_volume), c:'#ff4757'},
    { l:'Positions long',  v: fmtPos(pair.long_positions),  c:'#34d6a5'},
    { l:'Positions short', v: fmtPos(pair.short_positions), c:'#ff4757'},
    { l:'Prix moy. long',  v: fmtPrice(pair.avg_long_price),  c:'#34d6a5'},
    { l:'Prix moy. short', v: fmtPrice(pair.avg_short_price), c:'#ff4757'},
  ];
  return (
    <div className="grid grid-cols-2 gap-px bg-gold/10">
      {stats.map((s,i) => (
        <div key={i} className="bg-black/60 px-4 py-3.5 flex justify-between items-baseline">
          <span className="text-[0.66rem] tracking-[0.22em] uppercase text-ink-mute">{s.l}</span>
          <span className="font-semibold text-[1.02rem] tracking-tight" style={{color:s.c}}>{s.v}</span>
        </div>
      ))}
    </div>
  );
}

/* ========== SPEECH ======================================================= */

function TrumpCard({ post }) {
  return (
    <div className="glass top-shine p-5 mb-3">
      <div className="flex items-center justify-between mb-3">
        <div className="flex items-center gap-3">
          <div className="w-9 h-9 rounded-full bg-gold/20 border border-gold/40 flex items-center justify-center text-gold-hi font-semibold text-sm">DT</div>
          <div>
            <div className="text-[0.85rem] text-ink font-medium">Donald Trump</div>
            <div className="text-[0.65rem] tracking-[0.2em] uppercase text-ink-mute">@realDonaldTrump · Truth Social</div>
          </div>
        </div>
        <div className="text-[0.7rem] text-gold-dim tracking-wide">{post.date}</div>
      </div>
      {post.is_repost && (
        <div className="text-[0.62rem] tracking-[0.3em] uppercase text-gold-dim mb-2">↻ Repost</div>
      )}
      {post.text && (
        <div className="text-[0.92rem] text-ink-soft leading-relaxed whitespace-pre-line mb-3">{post.text}</div>
      )}
      {post.media?.length > 0 && (
        <div className="grid gap-2 mb-3" style={{gridTemplateColumns:`repeat(${post.media.length}, minmax(0,1fr))`}}>
          {post.media.map((m, i) => (
            <a key={i} href={post.url} target="_blank" rel="noopener noreferrer">
              <img src={m} alt="" className="w-full h-32 object-cover border border-gold/15"/>
            </a>
          ))}
        </div>
      )}
      <div className="flex items-center gap-6 text-[0.72rem] text-ink-mute pt-3 border-t border-gold/[0.08]">
        <span>♻ {post.stats.reposts}</span>
        <span>♥ {post.stats.likes}</span>
        <span>💬 {post.stats.replies}</span>
        {post.url && (
          <a href={post.url} target="_blank" rel="noopener noreferrer"
             className="ml-auto text-gold-dim hover:text-gold-hi text-[0.7rem] tracking-[0.2em] uppercase">
            Voir →
          </a>
        )}
      </div>
    </div>
  );
}

function PressCard({ item }) {
  return (
    <a href={item.url} target="_blank" rel="noopener noreferrer"
       className="block glass top-shine p-5 mb-3 hover:bg-gold/[0.04] transition-colors group">
      <div className="flex items-start justify-between gap-4 mb-2">
        <div className="text-[0.92rem] text-ink font-medium leading-snug group-hover:text-gold-hi transition-colors">
          {item.title}
        </div>
        <div className="text-[0.68rem] text-gold-dim tracking-wide whitespace-nowrap mt-1">{item.date}</div>
      </div>
      {item.summary && (
        <div className="text-[0.82rem] text-ink-soft leading-relaxed">{item.summary}</div>
      )}
    </a>
  );
}

function TabSpeech() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('/api/speech')
      .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="text-ink-soft text-center py-20">Chargement des discours et tweets...</div>;
  if (error)   return <div className="text-bear text-center py-20">Erreur : {error}</div>;

  const trumpCount = data?.trump?.length || 0;
  const fedCount   = data?.fed?.length   || 0;
  const ecbCount   = data?.ecb?.length   || 0;

  return (
    <>
      <section className="mb-12">
        <SectionHead title="Trump · Truth Social"
                     meta={trumpCount ? `${trumpCount} derniers posts` : 'Indisponible'} />
        {trumpCount === 0 ? (
          <div className="glass top-shine p-8 text-center text-ink-mute text-sm">
            Flux Truth Social temporairement injoignable.
          </div>
        ) : (
          <div>{data.trump.map((p, i) => <TrumpCard key={i} post={p} />)}</div>
        )}
      </section>

      <section className="mb-12 grid grid-cols-2 gap-8">
        <div>
          <SectionHead title="Federal Reserve" meta={fedCount ? `${fedCount} communiqués` : 'Indisponible'} />
          {fedCount === 0 ? (
            <div className="glass top-shine p-6 text-center text-ink-mute text-sm">Flux Fed indisponible.</div>
          ) : (
            data.fed.map((it, i) => <PressCard key={i} item={it} />)
          )}
        </div>
        <div>
          <SectionHead title="Banque Centrale Européenne" meta={ecbCount ? `${ecbCount} communiqués` : 'Indisponible'} />
          {ecbCount === 0 ? (
            <div className="glass top-shine p-6 text-center text-ink-mute text-sm">Flux ECB indisponible.</div>
          ) : (
            data.ecb.map((it, i) => <PressCard key={i} item={it} />)
          )}
        </div>
      </section>

      <section className="mb-10">
        <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium text-center">
          Mise à jour {data.updated_at}
        </div>
      </section>
    </>
  );
}

/* ========== SENTIMENT ==================================================== */

function TabSentiment() {
  const [data, setData] = useState(null);
  const [selected, setSelected] = useState('EURUSD');
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('/api/sentiment/myfxbook')
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
      .then(d => {
        setData(d);
        setLoading(false);
        if (d.pairs?.length && !d.pairs.find(p => p.symbol === 'EURUSD')) {
          setSelected(d.pairs[0].symbol);
        }
      })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="text-ink-soft text-center py-20">Chargement du sentiment retail...</div>;
  if (error)   return <div className="text-bear text-center py-20">Erreur : {error}<div className="text-ink-mute text-xs mt-2">Première requête : ~10s.</div></div>;
  if (!data?.pairs?.length) return <div className="text-ink-mute text-center py-20">Aucune donnée disponible.</div>;

  const pair = data.pairs.find(p => p.symbol === selected) || data.pairs[0];

  return (
    <>
      <section className="mb-10">
        <SectionHead title="Sentiment retail global"
                     meta={`${data.n_pairs} paires`} />
        <div className="grid gap-5" style={{gridTemplateColumns:'1.1fr 1fr'}}>
          <div className="glass top-shine p-7 flex flex-col items-center justify-center">
            <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-4">Biais USD retail</div>
            {data.usd_retail_long != null ? (
              <>
                <SentimentWheel longPct={data.usd_retail_long} shortPct={data.usd_retail_short} />
                <div className="mt-3 flex gap-8 text-[0.78rem]">
                  <div className="flex items-center gap-2">
                    <span className="w-2 h-2 rounded-full bg-bull"/>
                    <span className="text-ink-soft">Long USD <span className="text-bull font-semibold">{data.usd_retail_long}%</span></span>
                  </div>
                  <div className="flex items-center gap-2">
                    <span className="w-2 h-2 rounded-full bg-bear"/>
                    <span className="text-ink-soft">Short USD <span className="text-bear font-semibold">{data.usd_retail_short}%</span></span>
                  </div>
                </div>
                <div className="mt-5 text-[0.78rem] text-ink-mute text-center max-w-xs">
                  Moyenne pondérée du positionnement retail sur les majors USD.
                </div>
              </>
            ) : (
              <div className="text-ink-mute text-sm">Biais USD non calculable</div>
            )}
          </div>

          <div className="glass top-shine p-7">
            <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-4">
              Classement par % long
            </div>
            <div className="max-h-[360px] overflow-y-auto pr-2">
              <SentimentRanking pairs={data.pairs} selected={selected} onSelect={setSelected} />
            </div>
          </div>
        </div>
      </section>

      <section className="mb-10">
        <SectionHead title={`Focus ${pair.symbol}`} meta="Détail des positions" />
        <div className="glass top-shine p-7 mb-5">
          <div className="grid gap-8 items-center" style={{gridTemplateColumns:'auto 1fr'}}>
            <SentimentSelector value={selected} onChange={setSelected} pairs={data.pairs} />
            <SentimentBar longPct={pair.long_pct} shortPct={pair.short_pct} height={36} />
          </div>
        </div>

        <div className="grid gap-5" style={{gridTemplateColumns:'1fr 1.1fr'}}>
          <div className="glass top-shine p-7 flex flex-col items-center justify-center">
            <SentimentWheel longPct={pair.long_pct} shortPct={pair.short_pct} size={240}/>
            <div className="mt-4 grid grid-cols-2 gap-6 w-full max-w-xs">
              <div className="text-center">
                <div className="text-[0.6rem] tracking-[0.28em] uppercase text-ink-mute mb-1">Long</div>
                <div className="text-bull font-semibold text-[1.6rem]" style={{textShadow:'0 0 14px currentColor'}}>{pair.long_pct}%</div>
              </div>
              <div className="text-center">
                <div className="text-[0.6rem] tracking-[0.28em] uppercase text-ink-mute mb-1">Short</div>
                <div className="text-bear font-semibold text-[1.6rem]" style={{textShadow:'0 0 14px currentColor'}}>{pair.short_pct}%</div>
              </div>
            </div>
          </div>

          <div className="glass top-shine p-7 flex flex-col">
            <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-4">Volumes & positions</div>
            <PositionsBlock pair={pair} />
          </div>
        </div>

        <div className="mt-5">
          <ContrarianHint longPct={pair.long_pct} />
        </div>
      </section>

      <section className="mb-10">
        <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium text-center">
          Données rafraîchies toutes les 5 min
        </div>
      </section>
    </>
  );
}

/* ========== TAB CALENDAR ================================================= */

const CAL_COUNTRIES = ['USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD', 'CHF', 'NZD', 'CNY'];
const CAL_LABELS = {
  USD: 'États-Unis', EUR: 'Zone Euro', GBP: 'Royaume-Uni', JPY: 'Japon',
  AUD: 'Australie', CAD: 'Canada', CHF: 'Suisse', NZD: 'Nouvelle-Zélande', CNY: 'Chine',
};

function CountryBadge({ code, dim = false }) {
  return (
    <span className={`inline-flex items-center justify-center text-[0.58rem] tracking-[0.18em] font-semibold px-1.5 py-[2px] min-w-[36px] border ${
      dim
        ? 'border-gold/15 text-ink-mute bg-transparent'
        : 'border-gold/30 gold-text bg-gold/[0.05]'
    }`}>
      {code}
    </span>
  );
}

function ImpactBar({ impact }) {
  const c = IMPACT_COLOR[impact] || '#5f5a4d';
  const bars = impact === 'High' ? 3 : impact === 'Medium' ? 2 : 1;
  return (
    <span className="inline-flex gap-[2px] items-end h-3" title={impact}>
      {[1, 2, 3].map(i => (
        <span key={i}
              className="w-[3px] rounded-sm"
              style={{
                height: `${i * 30 + 10}%`,
                background: i <= bars ? c : 'rgba(212,175,55,0.1)',
                boxShadow: i <= bars ? `0 0 4px ${c}` : 'none',
              }}/>
      ))}
    </span>
  );
}

function CalCountryChip({ code, active, count, onToggle }) {
  return (
    <button onClick={() => onToggle(code)}
            className={`flex items-center gap-2 px-3 py-1.5 border transition-colors text-[0.65rem] tracking-[0.2em] uppercase ${
              active
                ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.16]'
                : 'border-gold/40 text-ink font-medium hover:border-gold/70 hover:text-gold-hi'
            }`}>
      <span className="font-semibold">{code}</span>
      <span className={`text-[0.62rem] font-semibold ${active ? 'text-gold-hi' : 'text-gold'}`}>{count}</span>
    </button>
  );
}

function UpcomingStrip({ events }) {
  if (!events.length) return null;
  return (
    <div className="glass top-shine p-5 mb-7">
      <div className="flex items-center justify-between mb-4">
        <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium">Prochains événements à fort impact</div>
        <div className="text-[0.6rem] tracking-[0.22em] uppercase text-ink-mute">{events.length} à venir</div>
      </div>
      <div className="grid gap-3" style={{gridTemplateColumns: `repeat(${Math.min(events.length, 5)}, minmax(0, 1fr))`}}>
        {events.slice(0, 5).map((e, i) => (
          <div key={i} className="border border-gold/[0.12] bg-gold/[0.02] p-3 hover:border-gold/30 transition-colors">
            <div className="flex items-center gap-2 mb-2">
              <CountryBadge code={e.country}/>
              <ImpactBar impact={e.impact}/>
            </div>
            <div className="text-[0.78rem] text-ink font-medium leading-snug mb-2 line-clamp-2" title={e.title}>{e.title}</div>
            <div className="text-[0.62rem] tracking-[0.22em] uppercase gold-text">
              {e.weekday} · {e.time}
            </div>
            <div className="text-[0.62rem] tracking-[0.18em] uppercase text-ink-mute mt-1">
              dans {e.in_human}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

/* ========== DIRECTION DU MARCHE ========================================== */

const VERDICT_BG = {
  bull: 'rgba(52,214,165,0.10)',
  bear: 'rgba(255,71,87,0.10)',
  warn: 'rgba(245,197,66,0.12)',
  neut: 'rgba(212,175,55,0.06)',
};
const VERDICT_BORDER = {
  bull: '#34d6a5', bear: '#ff4757', warn: '#f5c542', neut: '#d4af37',
};
const VERDICT_TXT = {
  bull: 'text-bull', bear: 'text-bear', warn: 'text-gold-hi', neut: 'gold-text',
};

function VerdictBanner({ horizon, verdict }) {
  if (!verdict) return null;
  const tone = verdict.tone || 'neut';
  return (
    <div className="glass top-shine p-7 mb-5"
         style={{
           background: `linear-gradient(135deg, ${VERDICT_BG[tone]}, rgba(0,0,0,0.4))`,
           borderLeft: `3px solid ${VERDICT_BORDER[tone]}`,
         }}>
      <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium mb-3">{horizon}</div>
      <div className={`text-[1.8rem] font-semibold tracking-tight mb-3 ${VERDICT_TXT[tone]}`}
           style={{textShadow: `0 0 18px currentColor`}}>{verdict.label}</div>
      <div className="text-[0.86rem] text-ink-soft leading-relaxed mb-4">{verdict.detail}</div>
      {verdict.drivers && verdict.drivers.length > 0 && (
        <div className="flex flex-wrap gap-2">
          {verdict.drivers.map((d, i) => (
            <span key={i}
                  className="text-[0.62rem] tracking-[0.2em] uppercase px-3 py-1.5 border border-gold/20 text-ink-soft bg-black/40">
              {d}
            </span>
          ))}
        </div>
      )}
    </div>
  );
}

function ScoreBar({ score, max = 3 }) {
  const pct = ((score + max) / (2 * max)) * 100;
  const color = score > 0 ? '#34d6a5' : score < 0 ? '#ff4757' : '#5f5a4d';
  return (
    <div className="flex items-center gap-2">
      <div className="relative w-24 h-1.5 bg-black/40 rounded-sm overflow-hidden">
        <div className="absolute top-0 bottom-0 left-1/2 w-px bg-gold/30"/>
        <div className="absolute top-0 bottom-0"
             style={{
               background: color,
               left: score >= 0 ? '50%' : `${pct}%`,
               width: score >= 0 ? `${pct - 50}%` : `${50 - pct}%`,
               boxShadow: `0 0 8px ${color}`,
             }}/>
      </div>
      <span className="text-[0.82rem] font-semibold tabular-nums text-right w-7"
            style={{color}}>{score > 0 ? `+${score}` : score}</span>
    </div>
  );
}

function RankingTable({ ranking }) {
  return (
    <div className="glass top-shine p-6">
      <div className="grid items-center gap-3 py-2 px-3 text-[0.62rem] tracking-[0.28em] uppercase text-gold-dim border-b border-gold/15 mb-2"
           style={{gridTemplateColumns: '36px 2fr 1.2fr 1.2fr 50px'}}>
        <span>#</span>
        <span>Devise</span>
        <span>Court terme</span>
        <span>Long terme</span>
        <span className="text-right">Total</span>
      </div>
      {ranking.map((r, i) => (
        <details key={r.code} className="border-b border-gold/[0.06] group">
          <summary className="grid items-center gap-3 py-3 px-3 cursor-pointer hover:bg-gold/[0.03] list-none"
                   style={{gridTemplateColumns: '36px 2fr 1.2fr 1.2fr 50px'}}>
            <span className="text-[0.78rem] gold-text font-semibold tabular-nums">#{i + 1}</span>
            <div>
              <div className="text-[0.92rem] text-ink font-medium">{r.code}</div>
              <div className="text-[0.68rem] text-ink-mute">{r.name}</div>
            </div>
            <ScoreBar score={r.scoreShort} />
            <ScoreBar score={r.scoreLong} />
            <span className={`text-[0.95rem] font-semibold tabular-nums text-right ${
              r.scoreTotal > 0 ? 'text-bull' : r.scoreTotal < 0 ? 'text-bear' : 'text-ink-mute'
            }`}>{r.scoreTotal > 0 ? `+${r.scoreTotal}` : r.scoreTotal}</span>
          </summary>
          <div className="px-3 pb-4 grid grid-cols-2 gap-5">
            <div>
              <div className="text-[0.6rem] tracking-[0.28em] uppercase text-gold-dim mb-2">Drivers court terme</div>
              {r.driversShort?.length ? (
                <ul className="space-y-1">
                  {r.driversShort.map((d, j) => (
                    <li key={j} className="text-[0.78rem] text-ink-soft border-l border-gold/15 pl-3">{d}</li>
                  ))}
                </ul>
              ) : <div className="text-ink-mute text-[0.78rem]">Aucun signal CT</div>}
            </div>
            <div>
              <div className="text-[0.6rem] tracking-[0.28em] uppercase text-gold-dim mb-2">Drivers long terme</div>
              {r.driversLong?.length ? (
                <ul className="space-y-1">
                  {r.driversLong.map((d, j) => (
                    <li key={j} className="text-[0.78rem] text-ink-soft border-l border-gold/15 pl-3">{d}</li>
                  ))}
                </ul>
              ) : <div className="text-ink-mute text-[0.78rem]">Aucun signal LT</div>}
            </div>
          </div>
        </details>
      ))}
    </div>
  );
}

function TradeIdeaCard({ idea }) {
  const isLong = idea.direction === 'long';
  const color = isLong ? '#34d6a5' : '#ff4757';
  return (
    <div className="glass top-shine p-5 flex items-center gap-5"
         style={{borderLeft: `3px solid ${color}`}}>
      <div className="text-[1.4rem] font-semibold"
           style={{color, textShadow: `0 0 14px currentColor`}}>
        {isLong ? '↑' : '↓'}
      </div>
      <div className="flex-1">
        <div className="flex items-baseline gap-3 mb-1">
          <span className="text-[1.1rem] font-semibold gold-text tracking-tight">{idea.pair}</span>
          <span className="text-[0.62rem] tracking-[0.28em] uppercase font-medium"
                style={{color}}>{idea.direction}</span>
          <span className="text-[0.6rem] tracking-[0.22em] uppercase text-gold-dim ml-auto">{idea.horizon}</span>
        </div>
        <div className="text-[0.78rem] text-ink-soft">{idea.rationale}</div>
      </div>
    </div>
  );
}

function TabDirection() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('/api/direction')
      .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return (
    <div className="text-ink-soft text-center py-20">
      Agrégation de toutes les devises…
      <div className="text-ink-mute text-xs mt-2">Premier calcul ~30s (8 devises × providers). Ensuite cache 15 min.</div>
    </div>
  );
  if (error)   return <div className="text-bear text-center py-20">Erreur : {error}</div>;
  if (!data)   return <div className="text-ink-mute text-center py-20">Aucune donnée.</div>;

  return (
    <>
      <section className="mb-12">
        <SectionHead title="Verdict marché"
                     meta={`Calculé en ${data.buildSeconds}s · ${data.generatedAt}`} />
        <div className="grid grid-cols-2 gap-5">
          <VerdictBanner horizon="Court terme (sentiment + flux)" verdict={data.shortTerm} />
          <VerdictBanner horizon="Long terme (macro structurelle)" verdict={data.longTerm} />
        </div>
      </section>

      <section className="mb-12">
        <SectionHead title="Ranking des devises"
                     meta="Cliquer une ligne pour voir les drivers" />
        <RankingTable ranking={data.ranking || []} />
      </section>

      <section className="mb-12">
        <SectionHead title="Trade ideas" meta="Top forte vs faible" />
        <div className="grid grid-cols-2 gap-5">
          <div>
            <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mb-3">Court terme</div>
            {(data.tradeIdeasShort || []).length === 0 ? (
              <div className="glass top-shine p-5 text-ink-mute text-sm text-center">Aucune idée CT (écarts trop faibles).</div>
            ) : (
              <div className="space-y-3">
                {data.tradeIdeasShort.map((idea, i) => <TradeIdeaCard key={i} idea={idea} />)}
              </div>
            )}
          </div>
          <div>
            <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mb-3">Long terme</div>
            {(data.tradeIdeasLong || []).length === 0 ? (
              <div className="glass top-shine p-5 text-ink-mute text-sm text-center">Aucune idée LT (écarts trop faibles).</div>
            ) : (
              <div className="space-y-3">
                {data.tradeIdeasLong.map((idea, i) => <TradeIdeaCard key={i} idea={idea} />)}
              </div>
            )}
          </div>
        </div>
      </section>

      <section className="mb-12">
        <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold-dim text-center">
          {data.ranking?.length || 0} devises agrégées
        </div>
      </section>
    </>
  );
}

/* ========== ECO ALERTS (push tel) ======================================== */

const eventClientKey = (e) => `${e.datetime_iso}|${e.country}|${e.title}`;

function useAlertsApi() {
  const [alerts, setAlerts] = useState({});   // map keyed by eventClientKey
  const [config, setConfig] = useState(null);

  const reload = useCallback(async () => {
    try {
      const [aRes, cRes] = await Promise.all([
        fetch('/api/alerts'),
        fetch('/api/alerts/config'),
      ]);
      const aData = aRes.ok ? await aRes.json() : { alerts: [] };
      const cData = cRes.ok ? await cRes.json() : null;
      const map = {};
      for (const a of aData.alerts || []) {
        map[`${a.datetime_iso}|${a.country}|${a.title}`] = a;
      }
      setAlerts(map);
      if (cData) setConfig(cData);
    } catch (e) {
      console.warn('alerts reload failed', e);
    }
  }, []);

  useEffect(() => { reload(); }, [reload]);

  const upsert = async (payload) => {
    const r = await fetch('/api/alerts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    await reload();
  };

  const remove = async (id) => {
    const r = await fetch(`/api/alerts/${id}`, { method: 'DELETE' });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    await reload();
  };

  const saveConfig = async (payload) => {
    const r = await fetch('/api/alerts/config', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    const next = await r.json();
    setConfig(next);
  };

  const testNotif = async () => {
    const r = await fetch('/api/alerts/test', { method: 'POST' });
    if (!r.ok) throw new Error(`HTTP ${r.status}`);
    return r.json();
  };

  return { alerts, config, reload, upsert, remove, saveConfig, testNotif };
}

function BellIcon({ active }) {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill="none"
         stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
      <path d="M13.73 21a2 2 0 0 1-3.46 0"/>
      {active && <circle cx="18" cy="6" r="3" fill="#f5c542" stroke="none"/>}
    </svg>
  );
}

function AlertPopover({ event, existing, config, onSave, onRemove, onClose }) {
  const dflt = config || { default_lead_min: 5, default_channels: 'ntfy,telegram' };
  const [lead, setLead] = useState(existing?.lead_min ?? dflt.default_lead_min);
  const [channels, setChannels] = useState(() => new Set(
    (existing?.channels || dflt.default_channels).split(',').map(s => s.trim()).filter(Boolean)
  ));
  const [customMsg, setCustomMsg] = useState(existing?.custom_msg || '');
  const [saving, setSaving] = useState(false);
  const [err, setErr] = useState(null);

  const toggleChan = (c) => {
    const n = new Set(channels);
    if (n.has(c)) n.delete(c); else n.add(c);
    setChannels(n);
  };

  const handleSave = async () => {
    if (channels.size === 0) { setErr('Sélectionne au moins un canal'); return; }
    setSaving(true); setErr(null);
    try {
      await onSave({
        datetime_iso: event.datetime_iso,
        title: event.title,
        country: event.country,
        impact: event.impact,
        forecast: event.forecast || '',
        previous: event.previous || '',
        lead_min: Number(lead) || 5,
        channels: [...channels].join(','),
        custom_msg: customMsg.trim(),
        enabled: true,
      });
      onClose();
    } catch (e) {
      setErr(e.message);
    } finally {
      setSaving(false);
    }
  };

  const handleRemove = async () => {
    if (!existing) return;
    setSaving(true);
    try { await onRemove(existing.id); onClose(); }
    catch (e) { setErr(e.message); }
    finally { setSaving(false); }
  };

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"
         onClick={onClose}>
      <div className="glass top-shine p-6 w-[480px] max-w-[92vw]" onClick={e => e.stopPropagation()}>
        <div className="flex items-baseline justify-between mb-1">
          <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold-dim">Alerte push</div>
          <button onClick={onClose} className="text-ink-mute hover:text-ink text-lg leading-none">×</button>
        </div>
        <div className="text-[0.95rem] text-ink font-semibold mb-1 truncate">{event.title}</div>
        <div className="text-[0.7rem] text-ink-mute mb-5">
          {event.country} · {event.impact} · {new Date(event.datetime_iso).toLocaleString('fr-FR', { dateStyle: 'short', timeStyle: 'short' })}
        </div>

        <div className="mb-4">
          <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium mb-1.5">Délai avant l'event</div>
          <div className="flex gap-1">
            {[5, 15, 30, 60].map(v => (
              <button key={v} onClick={() => setLead(v)}
                      className={`flex-1 px-3 py-2 text-[0.72rem] tracking-[0.18em] uppercase border transition-colors ${
                        Number(lead) === v
                          ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.16]'
                          : 'border-gold/40 text-ink font-medium hover:border-gold/70 hover:text-gold-hi'
                      }`}>
                {v} min
              </button>
            ))}
            <input type="number" min="1" max="240" value={lead}
                   onChange={e => setLead(e.target.value)}
                   className="w-16 bg-transparent border border-gold/15 text-ink-soft text-[0.82rem] px-2 py-2 text-center focus:outline-none focus:border-gold/40"/>
          </div>
        </div>

        <div className="mb-4">
          <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium mb-1.5">Canaux</div>
          <div className="flex gap-2">
            {[
              { k: 'ntfy', label: 'ntfy.sh' },
              { k: 'telegram', label: 'Telegram' },
            ].map(({ k, label }) => {
              const on = channels.has(k);
              return (
                <button key={k} onClick={() => toggleChan(k)}
                        className={`flex-1 px-3 py-2 text-[0.72rem] tracking-[0.18em] uppercase border transition-colors ${
                          on
                            ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.16]'
                            : 'border-gold/40 text-ink font-medium hover:border-gold/70 hover:text-gold-hi'
                        }`}>
                  {label}
                </button>
              );
            })}
          </div>
        </div>

        <div className="mb-4">
          <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium mb-1.5">
            Message custom <span className="text-ink-mute normal-case tracking-normal">(vide = template par défaut)</span>
          </div>
          <textarea value={customMsg} onChange={e => setCustomMsg(e.target.value)} rows={3}
                    placeholder="Ex: NFP imminent — surveiller DXY et US10Y. {country} {impact} dans {lead} min"
                    className="w-full bg-transparent border border-gold/15 text-ink-soft text-[0.82rem] px-3 py-2 focus:outline-none focus:border-gold/40 placeholder:text-ink-mute/50 resize-none"/>
          <div className="text-[0.6rem] text-ink-mute mt-1">
            Variables : <code>{'{title}'}</code> <code>{'{country}'}</code> <code>{'{flag}'}</code> <code>{'{impact}'}</code> <code>{'{lead}'}</code> <code>{'{forecast}'}</code> <code>{'{previous}'}</code>
          </div>
        </div>

        {err && <div className="text-bear text-[0.78rem] mb-3">{err}</div>}

        <div className="flex items-center gap-2 pt-3 border-t border-gold/[0.08]">
          {existing && (
            <button onClick={handleRemove} disabled={saving}
                    className="text-[0.7rem] tracking-[0.22em] uppercase px-3 py-2 border border-bear/40 text-bear hover:bg-bear/[0.08]">
              Supprimer
            </button>
          )}
          <div className="ml-auto flex gap-2">
            <button onClick={onClose}
                    className="text-[0.7rem] tracking-[0.22em] uppercase px-3 py-2 border border-gold/15 text-ink-mute hover:text-ink-soft">
              Annuler
            </button>
            <button onClick={handleSave} disabled={saving}
                    className="text-[0.7rem] tracking-[0.22em] uppercase px-4 py-2 border border-gold-hi text-gold-hi font-semibold bg-gold/[0.16] hover:bg-gold/[0.14]">
              {saving ? '...' : (existing ? 'Mettre à jour' : 'Activer')}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

function AlertsConfigPanel({ config, onSave, onTest, onClose }) {
  const [form, setForm] = useState(() => ({
    ntfy_topic: config?.ntfy_topic || '',
    ntfy_server: config?.ntfy_server || 'https://ntfy.sh',
    telegram_token: config?.telegram_token || '',
    telegram_chat_id: config?.telegram_chat_id || '',
    default_lead_min: config?.default_lead_min || 5,
    default_channels: config?.default_channels || 'ntfy,telegram',
    default_msg_template: config?.default_msg_template || '',
  }));
  const [saving, setSaving] = useState(false);
  const [testing, setTesting] = useState(false);
  const [msg, setMsg] = useState(null);

  const upd = (k, v) => setForm(f => ({ ...f, [k]: v }));

  const handleSave = async () => {
    setSaving(true); setMsg(null);
    try { await onSave(form); setMsg({ ok: true, txt: 'Config enregistrée.' }); }
    catch (e) { setMsg({ ok: false, txt: e.message }); }
    finally { setSaving(false); }
  };

  const handleTest = async () => {
    setTesting(true); setMsg(null);
    try {
      const r = await onTest();
      const lines = Object.entries(r.results || {})
        .map(([k, v]) => `${k}: ${v.ok ? 'OK' : 'ERR'} (${v.info})`)
        .join(' · ');
      setMsg({ ok: true, txt: `Test envoyé · ${lines}` });
    } catch (e) { setMsg({ ok: false, txt: e.message }); }
    finally { setTesting(false); }
  };

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"
         onClick={onClose}>
      <div className="glass top-shine p-6 w-[560px] max-w-[94vw] max-h-[90vh] overflow-y-auto"
           onClick={e => e.stopPropagation()}>
        <div className="flex items-baseline justify-between mb-5">
          <div>
            <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold-dim">Paramètres alertes push</div>
            <div className="text-[0.74rem] text-ink-mute mt-1">Stocké en local (SQLite). Aucune donnée n'est envoyée à un tiers sans ton accord.</div>
          </div>
          <button onClick={onClose} className="text-ink-mute hover:text-ink text-lg leading-none">×</button>
        </div>

        <div className="mb-5">
          <div className="text-[0.7rem] tracking-[0.28em] uppercase gold-text font-semibold mb-3">ntfy.sh</div>
          <label className="block mb-2">
            <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold-dim mb-1">Topic (secret)</div>
            <input value={form.ntfy_topic} onChange={e => upd('ntfy_topic', e.target.value)}
                   placeholder="ex: panal-derbi-x7k2"
                   className="w-full bg-transparent border border-gold/15 text-ink-soft text-[0.85rem] px-3 py-2 focus:outline-none focus:border-gold/40 placeholder:text-ink-mute/50"/>
          </label>
          <label className="block">
            <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold-dim mb-1">Serveur</div>
            <input value={form.ntfy_server} onChange={e => upd('ntfy_server', e.target.value)}
                   className="w-full bg-transparent border border-gold/15 text-ink-soft text-[0.85rem] px-3 py-2 focus:outline-none focus:border-gold/40"/>
          </label>
          <div className="text-[0.66rem] text-ink-mute mt-2">
            Installe <code>ntfy</code> (Play Store / App Store), abonne-toi à ton topic, c'est tout.
          </div>
        </div>

        <div className="mb-5 pt-4 border-t border-gold/[0.08]">
          <div className="text-[0.7rem] tracking-[0.28em] uppercase gold-text font-semibold mb-3">Telegram bot</div>
          <label className="block mb-2">
            <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold-dim mb-1">Bot token</div>
            <input value={form.telegram_token} onChange={e => upd('telegram_token', e.target.value)}
                   placeholder="123456:ABC-DEF..."
                   className="w-full bg-transparent border border-gold/15 text-ink-soft text-[0.85rem] px-3 py-2 focus:outline-none focus:border-gold/40 placeholder:text-ink-mute/50"/>
          </label>
          <label className="block">
            <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold-dim mb-1">Chat ID</div>
            <input value={form.telegram_chat_id} onChange={e => upd('telegram_chat_id', e.target.value)}
                   placeholder="ex: 12345678"
                   className="w-full bg-transparent border border-gold/15 text-ink-soft text-[0.85rem] px-3 py-2 focus:outline-none focus:border-gold/40 placeholder:text-ink-mute/50"/>
          </label>
          <div className="text-[0.66rem] text-ink-mute mt-2">
            Crée un bot via <code>@BotFather</code>, démarre-le, puis récupère ton chat_id via <code>@userinfobot</code>.
          </div>
        </div>

        <div className="mb-5 pt-4 border-t border-gold/[0.08]">
          <div className="text-[0.7rem] tracking-[0.28em] uppercase gold-text font-semibold mb-3">Défauts</div>
          <div className="grid grid-cols-2 gap-3 mb-3">
            <label className="block">
              <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold-dim mb-1">Délai par défaut (min)</div>
              <input type="number" min="1" max="240" value={form.default_lead_min}
                     onChange={e => upd('default_lead_min', Number(e.target.value) || 5)}
                     className="w-full bg-transparent border border-gold/15 text-ink-soft text-[0.85rem] px-3 py-2 focus:outline-none focus:border-gold/40"/>
            </label>
            <label className="block">
              <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold-dim mb-1">Canaux par défaut (csv)</div>
              <input value={form.default_channels} onChange={e => upd('default_channels', e.target.value)}
                     placeholder="ntfy,telegram"
                     className="w-full bg-transparent border border-gold/15 text-ink-soft text-[0.85rem] px-3 py-2 focus:outline-none focus:border-gold/40"/>
            </label>
          </div>
          <label className="block">
            <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold-dim mb-1">Template du message</div>
            <textarea rows={2} value={form.default_msg_template}
                      onChange={e => upd('default_msg_template', e.target.value)}
                      placeholder="[ECO] {country} {impact} dans {lead} min : {title} (prev {previous} / fcst {forecast})"
                      className="w-full bg-transparent border border-gold/15 text-ink-soft text-[0.82rem] px-3 py-2 focus:outline-none focus:border-gold/40 placeholder:text-ink-mute/50 resize-none"/>
          </label>
        </div>

        {msg && (
          <div className={`text-[0.78rem] mb-3 ${msg.ok ? 'text-bull' : 'text-bear'}`}>{msg.txt}</div>
        )}

        <div className="flex items-center gap-2 pt-3 border-t border-gold/[0.08]">
          <button onClick={handleTest} disabled={testing}
                  className="text-[0.7rem] tracking-[0.22em] uppercase px-3 py-2 border border-gold/30 text-ink-soft hover:bg-gold/[0.05]">
            {testing ? '...' : 'Envoyer un test'}
          </button>
          <div className="ml-auto flex gap-2">
            <button onClick={onClose}
                    className="text-[0.7rem] tracking-[0.22em] uppercase px-3 py-2 border border-gold/15 text-ink-mute hover:text-ink-soft">
              Fermer
            </button>
            <button onClick={handleSave} disabled={saving}
                    className="text-[0.7rem] tracking-[0.22em] uppercase px-4 py-2 border border-gold-hi text-gold-hi font-semibold bg-gold/[0.16] hover:bg-gold/[0.14]">
              {saving ? '...' : 'Enregistrer'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

const WEEKDAYS_FR = ['Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi'];

function buildTradingViewSnippet(days) {
  const byWeekday = {};
  for (const w of WEEKDAYS_FR) byWeekday[w] = [];
  for (const day of days) {
    const d = new Date(day.date + 'T12:00:00');
    const idx = (d.getDay() + 6) % 7; // 0 = Lundi
    if (idx > 4) continue;            // ignore week-end
    const wd = WEEKDAYS_FR[idx];
    for (const ev of day.events) {
      const hh = (ev.time || '').replace(':', 'h');
      if (hh) byWeekday[wd].push(hh);
    }
  }
  const lines = ['Annonce économique'];
  for (const wd of WEEKDAYS_FR) {
    const uniq = [...new Set(byWeekday[wd])].sort();
    lines.push(uniq.length ? `${wd} : ${uniq.join(' · ')}` : `${wd} :`);
  }
  return lines.join('\n');
}

function TabCalendar() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [selected, setSelected] = useState(() => new Set(CAL_COUNTRIES));
  const [impacts, setImpacts] = useState(() => new Set(['Medium', 'High'])); // multi : Low / Medium / High
  const [nextWeek, setNextWeek] = useState(false);
  const [search, setSearch] = useState('');
  const [popoverEvent, setPopoverEvent] = useState(null);
  const [configOpen, setConfigOpen] = useState(false);
  const [copied, setCopied] = useState(false);
  const alertsApi = useAlertsApi();

  const load = useCallback(async () => {
    setLoading(true);
    try {
      const params = new URLSearchParams({
        countries: CAL_COUNTRIES.join(','),
        min_impact: 'Low',
      });
      if (nextWeek) params.set('next_week', 'true');
      const r = await fetch(`/api/calendar?${params}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const d = await r.json();
      setData(d);
      setError(null);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  }, [nextWeek]);

  useEffect(() => { load(); }, [load]);

  const toggleCountry = (c) => {
    const next = new Set(selected);
    if (next.has(c)) next.delete(c); else next.add(c);
    if (next.size === 0) return; // garde au moins 1
    setSelected(next);
  };

  const allOn = selected.size === CAL_COUNTRIES.length;
  const setAll = () => setSelected(new Set(CAL_COUNTRIES));
  const setOnly = (c) => setSelected(new Set([c]));

  const filtered = useMemo(() => {
    if (!data?.days) return { days: [], total: 0, high: 0 };
    const q = search.trim().toLowerCase();
    const days = [];
    let total = 0, high = 0;
    for (const day of data.days) {
      const evs = day.events.filter(e =>
        selected.has(e.country) &&
        impacts.has(e.impact) &&
        (!q || e.title.toLowerCase().includes(q))
      );
      if (!evs.length) continue;
      const dayHigh = evs.filter(e => e.impact === 'High').length;
      total += evs.length;
      high += dayHigh;
      days.push({ ...day, events: evs, high_count: dayHigh, total: evs.length });
    }
    return { days, total, high };
  }, [data, selected, impacts, search]);

  const upcoming = useMemo(() => {
    if (!data?.days) return [];
    const now = Date.now();
    const all = [];
    for (const d of data.days) for (const e of d.events) {
      if (e.impact !== 'High') continue;
      if (!selected.has(e.country)) continue;
      const t = new Date(e.datetime_iso).getTime();
      if (t < now) continue;
      const diffMs = t - now;
      const h = diffMs / 3.6e6;
      const human = h < 1
        ? `${Math.max(1, Math.round(diffMs / 6e4))} min`
        : h < 24 ? `${Math.round(h)} h`
        : `${Math.round(h / 24)} j`;
      all.push({ ...e, in_human: human, _t: t });
    }
    all.sort((a, b) => a._t - b._t);
    return all.slice(0, 5);
  }, [data, selected]);

  if (loading) return <div className="text-ink-soft text-center py-20 text-sm tracking-[0.2em] uppercase">Chargement du calendrier...</div>;
  if (error)   return <div className="text-bear text-center py-20">Erreur : {error}</div>;
  if (!data)   return <div className="text-ink-mute text-center py-20">Aucune donnée.</div>;

  const weekRange = data.week_start && data.week_end
    ? `${new Date(data.week_start).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' })} → ${new Date(data.week_end).toLocaleDateString('fr-FR', { day: '2-digit', month: 'short' })}`
    : '—';

  return (
    <>
      <section className="mb-8">
        <SectionHead
          title="Calendrier économique mondial"
          meta={`${weekRange} · ${data.high_impact_count} High · ${data.total_events} événements`} />

        {/* HEADER FILTERS */}
        <div className="glass top-shine p-5 mb-6">
          <div className="flex flex-wrap items-center gap-3 mb-4">
            <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mr-2">Devises</div>
            <button onClick={setAll}
                    className={`text-[0.6rem] tracking-[0.22em] uppercase px-2.5 py-1.5 border transition-colors ${
                      allOn ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.16]' : 'border-gold/40 text-ink font-medium hover:border-gold/70 hover:text-gold-hi'
                    }`}>Toutes</button>
            {CAL_COUNTRIES.map(c => {
              const cnt = data.per_country?.[c]?.total ?? 0;
              return (
                <CalCountryChip key={c} code={c} count={cnt}
                                active={selected.has(c)} onToggle={toggleCountry} />
              );
            })}
          </div>

          <div className="flex flex-wrap items-center gap-3 pt-3 border-t border-gold/[0.08]">
            <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium mr-2">Impact</div>
            {[
              { key: 'High',   label: 'High',   color: IMPACT_COLOR.High },
              { key: 'Medium', label: 'Medium', color: IMPACT_COLOR.Medium },
              { key: 'Low',    label: 'Low',    color: IMPACT_COLOR.Low },
            ].map(({ key, label, color }) => {
              const active = impacts.has(key);
              return (
                <button key={key}
                        onClick={() => {
                          const next = new Set(impacts);
                          if (next.has(key)) next.delete(key); else next.add(key);
                          if (next.size === 0) return; // garde au moins 1
                          setImpacts(next);
                        }}
                        className={`flex items-center gap-2 text-[0.6rem] tracking-[0.22em] uppercase px-3 py-1.5 border transition-colors ${
                          active
                            ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.16]'
                            : 'border-gold/40 text-ink font-medium hover:border-gold/70 hover:text-gold-hi'
                        }`}>
                  <span className="w-2 h-2 rounded-full"
                        style={{background: color, boxShadow: active ? `0 0 6px ${color}` : 'none', opacity: active ? 1 : 0.4}}/>
                  {label}
                </button>
              );
            })}

            <div className="w-px h-6 bg-gold/15 mx-2"/>

            <button onClick={() => setNextWeek(v => !v)}
                    className={`text-[0.6rem] tracking-[0.22em] uppercase px-3 py-1.5 border transition-colors ${
                      nextWeek
                        ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.16]'
                        : 'border-gold/40 text-ink font-medium hover:border-gold/70 hover:text-gold-hi'
                    }`}>
              + Semaine prochaine
            </button>

            <div className="ml-auto flex items-center gap-2">
              <button onClick={async () => {
                        if (selected.size !== 2) return;
                        const txt = buildTradingViewSnippet(filtered.days);
                        try { await navigator.clipboard.writeText(txt); }
                        catch { /* clipboard indisponible */ }
                        setCopied(true);
                        setTimeout(() => setCopied(false), 1800);
                      }}
                      disabled={selected.size !== 2}
                      title={selected.size === 2
                        ? `Copier l'encadré ${[...selected].sort().join('/')} pour TradingView`
                        : 'Sélectionner exactement 2 devises pour générer l\'encadré'}
                      className={`flex items-center gap-1.5 text-[0.6rem] tracking-[0.22em] uppercase px-3 py-1.5 border transition-colors ${
                        selected.size !== 2
                          ? 'border-gold/30 text-ink-soft cursor-not-allowed'
                          : copied
                            ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.18]'
                            : 'border-gold/45 text-ink font-medium hover:bg-gold/[0.08]'
                      }`}>
                {copied ? 'Copié ✓' : 'Copier TradingView'}
              </button>
              <button onClick={() => setConfigOpen(true)}
                      title="Configurer les notifications push (ntfy + Telegram)"
                      className="flex items-center gap-1.5 text-[0.6rem] tracking-[0.22em] uppercase px-3 py-1.5 border border-gold/45 text-ink font-medium hover:bg-gold/[0.08]">
                <BellIcon active={!!(alertsApi.config?.ntfy_topic || alertsApi.config?.telegram_token)}/>
                Alertes
                {Object.keys(alertsApi.alerts).length > 0 && (
                  <span className="ml-1 px-1.5 py-px gold-text border border-gold-hi/40 text-[0.55rem]">
                    {Object.values(alertsApi.alerts).filter(a => a.enabled && !a.sent_at).length}
                  </span>
                )}
              </button>
              <input
                type="text"
                value={search}
                onChange={e => setSearch(e.target.value)}
                placeholder="Rechercher un événement..."
                className="bg-transparent border border-gold/25 text-ink text-[0.78rem] px-3 py-1.5 w-64 focus:outline-none focus:border-gold/40 placeholder:text-ink-soft/70"/>
              <span className="text-[0.6rem] tracking-[0.22em] uppercase text-ink font-medium">
                {filtered.total} affichés · MaJ {data.fetched_at}
              </span>
            </div>
          </div>
        </div>

        {/* UPCOMING */}
        <UpcomingStrip events={upcoming}/>

        {/* DAYS */}
        <div className="glass top-shine p-6">
          {filtered.days.length === 0 ? (
            <div className="text-center py-12 text-ink-mute text-sm">
              Aucun événement ne correspond aux filtres.
            </div>
          ) : (
            <div className="space-y-6">
              {filtered.days.map(day => (
                <div key={day.date}>
                  <div className="flex items-baseline gap-3 mb-2 pb-2 border-b border-gold/[0.1]">
                    <span className="text-[0.82rem] gold-text font-semibold capitalize">{day.label}</span>
                    <span className="text-[0.62rem] tracking-[0.2em] uppercase text-ink-mute">
                      {day.events.length} évén. · {day.high_count} high
                    </span>
                  </div>
                  <div>
                    {day.events.map((e, i) => {
                      const eKey = eventClientKey(e);
                      const existing = alertsApi.alerts[eKey];
                      const future = new Date(e.datetime_iso).getTime() > Date.now();
                      const armed = existing && existing.enabled && !existing.sent_at;
                      return (
                      <div key={i}
                           className="grid items-center gap-3 py-2 px-2 hover:bg-gold/[0.03] text-[0.84rem] border-b border-gold/[0.04]"
                           style={{gridTemplateColumns:'56px 44px 16px 1fr 70px 70px 70px 32px'}}>
                        <span className="text-ink-soft tabular-nums text-[0.82rem]">{e.time}</span>
                        <button onClick={() => setOnly(e.country)} title={`Filtrer ${CAL_LABELS[e.country] || e.country}`}>
                          <CountryBadge code={e.country}/>
                        </button>
                        <ImpactBar impact={e.impact}/>
                        <span className="text-ink font-medium truncate" title={e.title}>{e.title}</span>
                        <span className="text-right text-[0.78rem] text-ink-mute">
                          <span className="text-[0.58rem] tracking-[0.18em] uppercase block">Préc.</span>
                          {e.previous || '—'}
                        </span>
                        <span className="text-right text-[0.78rem] text-ink-soft">
                          <span className="text-[0.58rem] tracking-[0.18em] uppercase block">Prév.</span>
                          {e.forecast || '—'}
                        </span>
                        <span className="text-right text-[0.78rem] gold-text font-semibold">
                          <span className="text-[0.58rem] tracking-[0.18em] uppercase block text-gold-dim">Réel</span>
                          {e.actual || '—'}
                        </span>
                        <button
                          onClick={() => future && setPopoverEvent(e)}
                          disabled={!future}
                          title={future
                            ? (armed ? `Alerte active · ${existing.lead_min} min avant` : 'Activer une notif push')
                            : 'Évènement passé'}
                          className={`flex items-center justify-center w-7 h-7 transition-colors ${
                            !future ? 'text-ink-mute/30 cursor-not-allowed' :
                            armed ? 'gold-text hover:text-gold-hi' :
                            'text-ink-mute hover:text-ink-soft'
                          }`}>
                          <BellIcon active={armed}/>
                        </button>
                      </div>
                      );
                    })}
                  </div>
                </div>
              ))}
            </div>
          )}

          <div className="mt-6 pt-4 border-t border-gold/[0.08] text-[0.6rem] tracking-[0.28em] uppercase text-ink-mute text-center">
            Heures locales
          </div>
        </div>
      </section>

      {popoverEvent && (
        <AlertPopover
          event={popoverEvent}
          existing={alertsApi.alerts[eventClientKey(popoverEvent)]}
          config={alertsApi.config}
          onSave={alertsApi.upsert}
          onRemove={alertsApi.remove}
          onClose={() => setPopoverEvent(null)}
        />
      )}
      {configOpen && (
        <AlertsConfigPanel
          config={alertsApi.config}
          onSave={alertsApi.saveConfig}
          onTest={alertsApi.testNotif}
          onClose={() => setConfigOpen(false)}
        />
      )}
    </>
  );
}

/* ========== TAB ENTREPRISE =============================================== */

const CUR_SYM = { USD: '$', EUR: '€', GBP: '£', JPY: '¥', CHF: 'CHF', HKD: 'HK$', CNY: '¥' };
const curSym = c => CUR_SYM[c] || (c ? c + ' ' : '$');
const fmtMoney = (v, cur) => v == null ? 'n/d' : `${curSym(cur)}${v}`;

const fmtMcap = (v, cur) => {
  if (v == null) return 'n/d';
  const s = curSym(cur);
  if (v >= 1e12) return `${(v / 1e12).toFixed(2)} T${s}`;
  if (v >= 1e9)  return `${(v / 1e9).toFixed(0)} Md${s}`;
  if (v >= 1e6)  return `${(v / 1e6).toFixed(0)} M${s}`;
  return `${s}${v}`;
};

const fmtEarnDate = iso => {
  if (!iso) return 'n/d';
  const d = new Date(iso + 'T00:00:00');
  return d.toLocaleDateString('fr-FR', { day: '2-digit', month: 'short', year: 'numeric' });
};

function SurpriseTag({ pct }) {
  if (pct == null) return <span className="text-ink-mute">—</span>;
  const up = pct >= 0;
  return (
    <span className={`font-semibold ${up ? 'text-bull' : 'text-bear'}`}>
      {up ? '▲' : '▼'} {up ? '+' : ''}{pct}%
    </span>
  );
}

function CountdownChip({ days }) {
  if (days == null) return null;
  const soon = days <= 7;
  const cls = soon
    ? 'border-neut/50 text-neut bg-neut/[0.06]'
    : 'border-gold/30 text-gold-hi bg-gold/[0.04]';
  const label = days === 0 ? "Aujourd'hui" : days < 0 ? 'Passé' : `J-${days}`;
  return (
    <span className={`text-[0.62rem] tracking-[0.18em] uppercase px-2.5 py-1 border ${cls}`}>{label}</span>
  );
}

function CompanyLogo({ domain, name, size = 38 }) {
  const [ok, setOk] = useState(true);
  const initial = (name || '?').trim().charAt(0).toUpperCase();
  if (domain && ok) {
    return (
      <img
        src={`https://www.google.com/s2/favicons?sz=128&domain=${domain}`}
        alt={name}
        onError={() => setOk(false)}
        className="rounded-full bg-white/[0.04] border border-gold/20 object-contain p-1 shrink-0"
        style={{ width: size, height: size }}
      />
    );
  }
  return (
    <div className="rounded-full bg-gold/[0.08] border border-gold/25 flex items-center justify-center shrink-0"
         style={{ width: size, height: size }}>
      <span className="gold-text font-semibold text-sm">{initial}</span>
    </div>
  );
}

function CompanyCard({ c }) {
  const ne = c.next_event;
  const lr = c.last_result;
  const cur = c.currency;
  return (
    <div className="glass top-shine p-6 flex flex-col">
      {/* En-tête société */}
      <div className="flex items-start justify-between mb-5">
        <div className="flex items-center gap-3 min-w-0">
          <CompanyLogo domain={c.domain} name={c.name} />
          <div className="min-w-0">
            <div className="text-ink font-medium text-[1.15rem] leading-tight truncate">{c.name}</div>
            <div className="text-[0.6rem] tracking-[0.28em] uppercase text-gold-dim mt-1">{c.ticker} · {c.sector}</div>
          </div>
        </div>
        <div className="text-right">
          <div className="text-ink-soft font-semibold text-[1.05rem]">{fmtMoney(c.price, cur)}</div>
          <div className="text-[0.62rem] text-ink-mute mt-1">{fmtMcap(c.market_cap, cur)}</div>
        </div>
      </div>

      {/* Prochaine parution + anticipation */}
      <div className="rounded border border-gold/15 bg-gold/[0.03] p-4 mb-4">
        <div className="flex items-center justify-between mb-2">
          <span className="text-[0.58rem] tracking-[0.3em] uppercase text-gold font-medium">Prochaine parution</span>
          {ne && <CountdownChip days={ne.days_until} />}
        </div>
        {ne ? (
          <div className="flex items-baseline justify-between">
            <span className="text-ink-soft text-[0.95rem]">{fmtEarnDate(ne.date)}</span>
            <span className="text-[0.72rem] text-ink-mute">
              EPS anticipé <span className="text-gold-hi font-semibold">{fmtMoney(ne.eps_estimate, cur)}</span>
            </span>
          </div>
        ) : (
          <div className="text-ink-mute text-[0.8rem]">Date non publiée</div>
        )}
      </div>

      {/* Dernier résultat */}
      <div className="mb-3">
        <span className="text-[0.58rem] tracking-[0.3em] uppercase text-gold font-medium">Dernier résultat publié</span>
        {lr ? (
          <div className="mt-2 grid grid-cols-3 gap-2 text-center">
            <div>
              <div className="text-[0.55rem] tracking-[0.2em] uppercase text-ink-mute mb-1">Date</div>
              <div className="text-ink-soft text-[0.8rem]">{fmtEarnDate(lr.date)}</div>
            </div>
            <div>
              <div className="text-[0.55rem] tracking-[0.2em] uppercase text-ink-mute mb-1">EPS réalisé</div>
              <div className="text-ink text-[0.85rem] font-semibold">{fmtMoney(lr.reported_eps, cur)}</div>
              <div className="text-[0.58rem] text-ink-mute">cons. {fmtMoney(lr.eps_estimate, cur)}</div>
            </div>
            <div>
              <div className="text-[0.55rem] tracking-[0.2em] uppercase text-ink-mute mb-1">Surprise</div>
              <div className="text-[0.85rem] mt-0.5"><SurpriseTag pct={lr.surprise_pct} /></div>
            </div>
          </div>
        ) : (
          <div className="text-ink-mute text-[0.8rem] mt-2">Aucun historique disponible</div>
        )}
      </div>

      {/* Historique compact */}
      {c.history?.length > 1 && (
        <div className="mt-auto pt-3 border-t border-gold/10">
          <div className="flex gap-1.5">
            {c.history.slice(0, 4).map((h, i) => {
              const up = h.surprise_pct != null && h.surprise_pct >= 0;
              return (
                <div key={i} className="flex-1 text-center" title={`${h.date} · surprise ${h.surprise_pct ?? 'n/d'}%`}>
                  <div className={`h-1 rounded ${h.surprise_pct == null ? 'bg-ink-mute/30' : up ? 'bg-bull' : 'bg-bear'}`}/>
                  <div className="text-[0.5rem] text-ink-mute mt-1">{h.date.slice(2, 7)}</div>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

function TabEntreprise() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('/api/earnings')
      .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(d => { setData(d); setLoading(false); })
      .catch(e => { setError(e.message); setLoading(false); });
  }, []);

  if (loading) return <div className="text-ink-soft text-center py-20">Chargement des résultats d'entreprises...<div className="text-ink-mute text-xs mt-2">Première requête : ~5s.</div></div>;
  if (error)   return <div className="text-bear text-center py-20">Erreur : {error}</div>;
  if (!data?.companies?.length) return <div className="text-ink-mute text-center py-20">Aucune donnée disponible.</div>;

  const upcoming = data.companies.filter(c => c.next_event && c.next_event.days_until <= 14);

  return (
    <>
      {upcoming.length > 0 && (
        <section className="mb-10">
          <SectionHead title="Parutions imminentes" meta={`${upcoming.length} sous 14 jours`} />
          <div className="flex flex-wrap gap-3">
            {upcoming.map(c => (
              <div key={c.ticker} className="glass top-shine px-4 py-3 flex items-center gap-3">
                <CountdownChip days={c.next_event.days_until} />
                <CompanyLogo domain={c.domain} name={c.name} size={22} />
                <span className="text-ink-soft font-medium text-[0.9rem]">{c.name}</span>
                <span className="text-ink-mute text-[0.7rem]">{fmtEarnDate(c.next_event.date)}</span>
                <span className="text-[0.7rem] text-gold-dim">EPS att. {fmtMoney(c.next_event.eps_estimate, c.currency)}</span>
              </div>
            ))}
          </div>
        </section>
      )}

      <section className="mb-10">
        <SectionHead title="Résultats & anticipations" meta={`${data.count} sociétés · triées par prochaine parution`} />
        <div className="grid gap-5" style={{gridTemplateColumns:'repeat(auto-fill, minmax(360px, 1fr))'}}>
          {data.companies.map(c => <CompanyCard key={c.ticker} c={c} />)}
        </div>
      </section>

      <section className="mb-4">
        <div className="text-[0.62rem] tracking-[0.32em] uppercase text-gold font-medium text-center">
          EPS anticipé = consensus analystes
        </div>
      </section>
    </>
  );
}

/* ========== TAB LOT SIZER ================================================ */

const ACCOUNT_SYMBOL = { EUR: '€', USD: '$', GBP: '£', JPY: '¥', CHF: 'CHF', AUD: 'A$', CAD: 'C$', NZD: 'NZ$' };
const ACCOUNT_OPTIONS = [
  { value: 'EUR', label: 'EUR €' },
  { value: 'USD', label: 'USD $' },
  { value: 'GBP', label: 'GBP £' },
  { value: 'JPY', label: 'JPY ¥' },
  { value: 'CHF', label: 'CHF' },
  { value: 'AUD', label: 'AUD A$' },
  { value: 'CAD', label: 'CAD C$' },
  { value: 'NZD', label: 'NZD NZ$' },
];

function nfmt(n, d = 2) {
  if (n === null || n === undefined || !isFinite(n)) return '—';
  return new Intl.NumberFormat('fr-FR', { minimumFractionDigits: d, maximumFractionDigits: d }).format(n);
}

function floorTo(n, step) {
  return Math.floor(n / step) * step;
}

function LotInput({ label, value, onChange, suffix, type = 'number', step, placeholder, hint, invalid }) {
  return (
    <label className="block">
      <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium mb-1.5">{label}</div>
      <div className={`flex items-center bg-gold/[0.02] border ${invalid ? 'border-bear/60' : 'border-gold/20 focus-within:border-gold/50'} transition-colors`}>
        <input type={type} value={value ?? ''} onChange={e => onChange(e.target.value)}
               step={step} placeholder={placeholder}
               className="w-full bg-transparent px-3 py-2.5 text-[0.95rem] text-ink-soft tabular-nums focus:outline-none placeholder:text-ink-mute/50"/>
        {suffix && <span className="px-3 text-[0.7rem] tracking-[0.2em] uppercase text-ink-mute">{suffix}</span>}
      </div>
      {hint && <div className="text-[0.62rem] text-ink-mute mt-1">{hint}</div>}
    </label>
  );
}

function LotSegmented({ label, options, value, onChange }) {
  return (
    <div>
      <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium mb-1.5">{label}</div>
      <div className="flex">
        {options.map((o, i) => (
          <button key={o.value} onClick={() => onChange(o.value)}
                  className={`flex-1 px-3 py-2.5 text-[0.7rem] tracking-[0.2em] uppercase border transition-colors ${
                    value === o.value
                      ? 'border-gold-hi text-gold-hi font-semibold bg-gold/[0.16]'
                      : 'border-gold/40 text-ink font-medium hover:border-gold/70 hover:text-gold-hi'
                  } ${i > 0 ? '-ml-px' : ''}`}>
            {o.label}
          </button>
        ))}
      </div>
    </div>
  );
}

function StepRow({ label, value, sub, accent }) {
  return (
    <div className="flex items-baseline justify-between py-2 border-b border-gold/[0.06]">
      <div>
        <div className="text-[0.78rem] text-ink-soft">{label}</div>
        {sub && <div className="text-[0.62rem] text-ink-mute mt-0.5">{sub}</div>}
      </div>
      <div className={`text-[0.92rem] tabular-nums font-medium ${accent ? 'gold-text' : 'text-ink'}`}>
        {value}
      </div>
    </div>
  );
}

function LotResultCard({ title, lots, risk, currency, hint, primary }) {
  const lotsValid = lots !== null && isFinite(lots) && lots > 0;
  return (
    <div className={`p-5 border transition-colors ${
      primary ? 'border-gold-hi bg-gold/[0.05]' : 'border-gold/15 bg-gold/[0.01]'
    }`}>
      <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium mb-2">{title}</div>
      <div className={`font-semibold leading-none tracking-tightest ${primary ? 'gold-text text-[2.6rem]' : 'text-ink text-[1.8rem]'}`}>
        {lotsValid ? nfmt(lots, primary ? 3 : 2) : '—'}
        <span className={`ml-1.5 ${primary ? 'text-[0.85rem]' : 'text-[0.65rem]'} font-light`} style={{WebkitTextFillColor:'#5f5a4d'}}>lots</span>
      </div>
      <div className="mt-3 text-[0.72rem] text-ink-soft">
        Risque effectif : <span className="gold-text font-semibold tabular-nums">{nfmt(risk, 2)} {currency}</span>
      </div>
      {hint && <div className="mt-1 text-[0.6rem] tracking-[0.18em] uppercase text-ink-mute">{hint}</div>}
    </div>
  );
}

function TabLotSizer() {
  const [setup, setSetup] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // Inputs
  const [account, setAccount] = useState('EUR');
  const [balance, setBalance] = useState('80000');
  const [riskMode, setRiskMode] = useState('pct'); // pct | cash
  const [riskValue, setRiskValue] = useState('1');
  const [symbol, setSymbol] = useState('EURUSD');
  const [entry, setEntry] = useState('');
  const [slPips, setSlPips] = useState('30');
  const [side, setSide] = useState('long'); // long | short

  const loadSetup = useCallback(async (acc) => {
    setLoading(true);
    try {
      const r = await fetch(`/api/lot/setup?account=${acc}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const d = await r.json();
      setSetup(d);
      setError(null);
    } catch (e) { setError(e.message); }
    finally { setLoading(false); }
  }, []);

  useEffect(() => { loadSetup(account); }, [account, loadSetup]);

  // Auto-fill entry quand on change de symbol (avec le live price)
  useEffect(() => {
    if (setup?.prices && setup.prices[symbol] != null) {
      setEntry(String(setup.prices[symbol]));
    }
  }, [symbol, setup]);

  if (loading) return <div className="text-ink-soft text-center py-20 text-sm tracking-[0.2em] uppercase">Chargement des taux live...</div>;
  if (error)   return <div className="text-bear text-center py-20">Erreur : {error}</div>;
  if (!setup)  return null;

  const ins = setup.instruments.find(i => i.symbol === symbol);
  const ccySymbol = ACCOUNT_SYMBOL[account] || account;

  // ---- MATH (reactive) -------------------------------------------------
  const balanceN = parseFloat(balance);
  const riskValueN = parseFloat(riskValue);
  const slPipsN = parseFloat(slPips);
  const entryN = parseFloat(entry);

  const riskCash = riskMode === 'pct'
    ? (isFinite(balanceN) && isFinite(riskValueN) ? balanceN * riskValueN / 100 : NaN)
    : (isFinite(riskValueN) ? riskValueN : NaN);

  const quoteCcy = ins.quote;
  const rate = setup.rates[quoteCcy];      // quote -> account
  const pipValueQuote = ins.pip_value_quote; // par pip par lot, en quote
  const pipValueAccount = (rate != null) ? pipValueQuote * rate : null;

  const denom = (slPipsN > 0 && pipValueAccount > 0) ? slPipsN * pipValueAccount : 0;
  const lotsRaw = (riskCash > 0 && denom > 0) ? riskCash / denom : null;

  const lot001 = lotsRaw ? floorTo(lotsRaw, 0.01) : null;
  const lot01  = lotsRaw ? floorTo(lotsRaw, 0.10) : null;

  const riskAt = (l) => (l != null && pipValueAccount && slPipsN > 0) ? l * slPipsN * pipValueAccount : null;
  const risk001 = riskAt(lot001);
  const risk01  = riskAt(lot01);

  // SL price (long: entry - sl*pip ; short: entry + sl*pip)
  const slPrice = (isFinite(entryN) && slPipsN > 0)
    ? (side === 'long' ? entryN - slPipsN * ins.pip_size : entryN + slPipsN * ins.pip_size)
    : null;

  // Notional (taille de position en devise de compte) sur entry
  const notional = (lot001 && isFinite(entryN))
    ? lot001 * ins.lot_units * entryN * (setup.rates[quoteCcy] ?? 1)
    : null;

  // ---- WARNINGS --------------------------------------------------------
  const warnings = [];
  const inputsInvalid = !(balanceN > 0) || !(riskValueN > 0) || !(slPipsN > 0) || !isFinite(entryN);
  if (riskMode === 'pct' && riskValueN > 5) warnings.push('Risque > 5 % du compte — agressif.');
  if (riskMode === 'cash' && balanceN > 0 && riskValueN / balanceN > 0.05)
    warnings.push(`Risque > 5 % du compte (${nfmt(riskValueN/balanceN*100, 1)} %).`);
  if (lotsRaw != null && lot001 < 0.01) warnings.push('Lot calculé < 0.01 — sous le minimum brokers FX.');
  if (rate == null) warnings.push(`Taux ${quoteCcy}→${account} indisponible — résultat impossible.`);

  return (
    <>
      <section className="mb-8">
        <SectionHead
          title="Calculateur de lot"
          meta={`Compte ${account} · ${setup.instruments.length} instruments · MaJ ${setup.fetched_at}`} />

        <div className="grid gap-6" style={{gridTemplateColumns:'1.05fr 1fr'}}>

          {/* ===== INPUTS ===== */}
          <div className="glass top-shine p-7 space-y-5">
            <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium pb-3 border-b border-gold/[0.1]">
              Paramètres du trade
            </div>

            <div className="grid grid-cols-2 gap-4">
              <div>
                <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium mb-1.5">Devise du compte</div>
                <select value={account} onChange={e => setAccount(e.target.value)}
                        className="w-full bg-gold/[0.02] border border-gold/20 px-3 py-2.5 text-[0.92rem] text-ink-soft focus:outline-none focus:border-gold/50 appearance-none cursor-pointer">
                  {ACCOUNT_OPTIONS.map(o => (
                    <option key={o.value} value={o.value}>{o.label}</option>
                  ))}
                </select>
              </div>
              <LotInput
                label={`Solde (${ccySymbol})`}
                value={balance} onChange={setBalance}
                step="100" placeholder="80000"
                invalid={!(balanceN > 0)}/>
            </div>

            <div className="grid grid-cols-2 gap-4">
              <LotSegmented
                label="Mode de risque"
                value={riskMode}
                onChange={setRiskMode}
                options={[{value:'pct',label:'% du solde'},{value:'cash',label:`Montant ${ccySymbol}`}]}/>
              <LotInput
                label={riskMode === 'pct' ? 'Risque (%)' : `Risque (${ccySymbol})`}
                value={riskValue} onChange={setRiskValue}
                suffix={riskMode === 'pct' ? '%' : ccySymbol}
                step={riskMode === 'pct' ? '0.1' : '10'}
                placeholder={riskMode === 'pct' ? '1' : '100'}
                invalid={!(riskValueN > 0)}
                hint={riskMode === 'pct' && balanceN > 0 && riskValueN > 0
                  ? `≈ ${nfmt(balanceN * riskValueN / 100, 2)} ${ccySymbol}` : null}/>
            </div>

            <div>
              <div className="text-[0.6rem] tracking-[0.32em] uppercase text-gold font-medium mb-1.5">Instrument</div>
              <select value={symbol} onChange={e => setSymbol(e.target.value)}
                      className="w-full bg-gold/[0.02] border border-gold/20 px-3 py-2.5 text-[0.92rem] text-ink-soft focus:outline-none focus:border-gold/50 appearance-none cursor-pointer">
                <optgroup label="— FOREX —">
                  {setup.instruments.filter(i => i.kind === 'fx').map(i => (
                    <option key={i.symbol} value={i.symbol}>{i.label}</option>
                  ))}
                </optgroup>
                <optgroup label="— INDICES —">
                  {setup.instruments.filter(i => i.kind === 'index').map(i => (
                    <option key={i.symbol} value={i.symbol}>{i.symbol} · {i.label}</option>
                  ))}
                </optgroup>
                <optgroup label="— CRYPTO —">
                  {setup.instruments.filter(i => i.kind === 'crypto').map(i => (
                    <option key={i.symbol} value={i.symbol}>{i.symbol} · {i.label}</option>
                  ))}
                </optgroup>
              </select>
              <div className="text-[0.62rem] text-ink-mute mt-1">
                Pip = {ins.pip_size} · 1 lot = {ins.pip_value_quote} {ins.quote}/pip
                {(ins.kind === 'index' || ins.kind === 'crypto') && ' · spec CFD IC Markets / Pepperstone'}
              </div>
            </div>

            <div className="grid grid-cols-3 gap-4">
              <LotSegmented
                label="Direction"
                value={side}
                onChange={setSide}
                options={[{value:'long',label:'Long'},{value:'short',label:'Short'}]}/>
              <LotInput
                label="Prix d'entrée"
                value={entry} onChange={setEntry}
                step={ins.pip_size}
                placeholder={setup.prices[symbol]?.toString() || '0'}
                invalid={!isFinite(entryN)}
                hint={setup.prices[symbol] != null ? `Live ${nfmt(setup.prices[symbol], ins.kind === 'fx' && ins.quote !== 'JPY' ? 5 : ins.quote === 'JPY' ? 3 : 2)}` : null}/>
              <LotInput
                label="Stop Loss (pips)"
                value={slPips} onChange={setSlPips}
                suffix="pips"
                step="1"
                placeholder="30"
                invalid={!(slPipsN > 0)}
                hint={slPrice ? `SL prix ≈ ${nfmt(slPrice, ins.kind === 'fx' && ins.quote !== 'JPY' ? 5 : ins.quote === 'JPY' ? 3 : 2)}` : null}/>
            </div>

            {warnings.length > 0 && (
              <div className="border border-bear/30 bg-bear/[0.05] p-3 space-y-1">
                {warnings.map((w, i) => (
                  <div key={i} className="text-[0.72rem] text-bear">› {w}</div>
                ))}
              </div>
            )}
          </div>

          {/* ===== RESULT ===== */}
          <div className="space-y-6">

            <div className="grid grid-cols-2 gap-4">
              <LotResultCard
                primary
                title="Lot recommandé (arrondi 0.01)"
                lots={lot001}
                risk={risk001}
                currency={ccySymbol}
                hint={lot001 != null && lotsRaw ? `vs brut ${nfmt(lotsRaw, 4)}` : null}/>
              <LotResultCard
                title="Lot arrondi 0.10"
                lots={lot01}
                risk={risk01}
                currency={ccySymbol}
                hint="pour comptes à pas plus large"/>
            </div>

            <div className="glass top-shine p-6">
              <div className="text-[0.62rem] tracking-[0.4em] uppercase text-gold font-medium pb-3 mb-2 border-b border-gold/[0.1]">
                Détail du calcul
              </div>

              <StepRow
                label="Risque cash"
                value={inputsInvalid ? '—' : `${nfmt(riskCash, 2)} ${ccySymbol}`}
                sub={riskMode === 'pct' ? `${nfmt(balanceN, 2)} ${ccySymbol} × ${nfmt(riskValueN, 2)} %` : 'Montant fixe'}
                accent/>

              <StepRow
                label={`Pip value (en ${quoteCcy})`}
                value={`${nfmt(pipValueQuote, 0)} ${quoteCcy}`}
                sub={ins.kind === 'fx'
                  ? `${nfmt(ins.lot_units, 0)} unités × ${ins.pip_size} pip`
                  : `1 contrat × 1 ${quoteCcy}/point`}/>

              <StepRow
                label={`Conversion ${quoteCcy} → ${account}`}
                value={rate != null ? `× ${nfmt(rate, 6)}` : '—'}
                sub={quoteCcy === account ? 'Même devise (rate = 1)' : 'Taux live via USD-bridge'}/>

              <StepRow
                label={`Pip value (en ${account})`}
                value={pipValueAccount != null ? `${nfmt(pipValueAccount, 4)} ${ccySymbol}` : '—'}
                sub={`par pip · par lot`}
                accent/>

              <StepRow
                label="Diviseur"
                value={denom > 0 ? `${nfmt(denom, 2)} ${ccySymbol}` : '—'}
                sub={`SL ${slPipsN || '?'} pips × pip value`}/>

              <StepRow
                label="Lot brut"
                value={lotsRaw != null ? nfmt(lotsRaw, 4) : '—'}
                sub={`${nfmt(riskCash, 2)} ÷ ${nfmt(denom, 2)}`}/>

              <StepRow
                label="SL en prix"
                value={slPrice != null ? nfmt(slPrice, ins.kind === 'fx' && ins.quote !== 'JPY' ? 5 : ins.quote === 'JPY' ? 3 : 2) : '—'}
                sub={`${side === 'long' ? 'Long' : 'Short'} · entrée ${isFinite(entryN) ? nfmt(entryN, ins.kind === 'fx' && ins.quote !== 'JPY' ? 5 : ins.quote === 'JPY' ? 3 : 2) : '—'} ${side === 'long' ? '−' : '+'} ${slPipsN || '?'} × ${ins.pip_size}`}/>

              <StepRow
                label={`Exposition notionnelle (${account})`}
                value={notional ? `${nfmt(notional, 0)} ${ccySymbol}` : '—'}
                sub={`${lot001 ?? '—'} lot × ${nfmt(ins.lot_units, 0)} × prix`}/>
            </div>

            <div className="text-[0.6rem] tracking-[0.28em] uppercase text-ink-mute text-center">
              Arrondi systématique vers le bas · Vérifie les contract specs avec ton broker
            </div>
          </div>
        </div>
      </section>
    </>
  );
}

/* ========== APP ========================================================== */

function App() {
  const [state, setState] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [refreshing, setRefreshing] = useState(false);
  const [view, setView] = useState(VIEW_PRINCIPAL);  // 'PRINCIPALE' | 'USD' | 'EUR' | ...
  const [tabPrincipal, setTabPrincipal] = useState('Direction du marché');
  const [tabCountry, setTabCountry] = useState('Général');

  const isCountryView = SUPPORTED_COUNTRIES.includes(view);
  const country = isCountryView ? view : null;

  const load = useCallback(async () => {
    if (!isCountryView) { setState(null); return; }
    setLoading(true);
    try {
      const r = await fetch(`/api/state?country=${country}`);
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const d = await r.json();
      setState(d);
      setError(null);
    } catch (e) {
      setError(e.message);
    } finally {
      setLoading(false);
    }
  }, [isCountryView, country]);

  useEffect(() => { load(); }, [load]);

  const onRefresh = async () => {
    if (!isCountryView) return;
    setRefreshing(true);
    try {
      const r = await fetch(`/api/refresh?country=${country}`, { method: 'POST' });
      if (!r.ok) throw new Error(`HTTP ${r.status}`);
      const d = await r.json();
      setState(d);
    } catch (e) {
      setError(e.message);
    } finally {
      setRefreshing(false);
    }
  };

  if (loading) return (
    <div className="fixed inset-0 flex flex-col items-center justify-center">
      <div className="gold-text font-semibold text-3xl tracking-[0.18em] uppercase mb-4">FondAnalyse</div>
      <div className="text-ink-mute text-[0.65rem] tracking-[0.3em] uppercase animate-pulse">Chargement des données...</div>
    </div>
  );

  if (error) return (
    <div className="fixed inset-0 flex flex-col items-center justify-center p-8">
      <div className="text-bear text-[0.72rem] tracking-[0.3em] uppercase mb-3">Erreur de chargement</div>
      <div className="text-ink-soft text-sm mb-5">{error}</div>
      <button onClick={load} className="border border-gold/40 text-gold-hi px-5 py-2.5 text-[0.7rem] tracking-[0.22em] uppercase hover:bg-gold/10">Réessayer</button>
    </div>
  );

  // ========== VUE PRINCIPALE ==========
  if (!isCountryView) {
    return (
      <>
        <Header activeView={view} onView={setView} onRefresh={() => {}} refreshing={false} />
        <main className="max-w-[1320px] mx-auto px-11 py-14">
          <PageTitle title="Principale" />
          <Tabs tabs={PRINCIPAL_TABS} active={tabPrincipal} onChange={setTabPrincipal} />
          {tabPrincipal === 'Direction du marché' && <TabDirection />}
          {tabPrincipal === 'Calendrier'          && <TabCalendar />}
          {tabPrincipal === 'Entreprise'          && <TabEntreprise />}
          {tabPrincipal === 'Calculateur'         && <TabLotSizer />}
          {tabPrincipal === 'Corrélations'        && <TabCorrelation />}
          {tabPrincipal === 'Sentiment Retail'    && <TabSentiment />}
        </main>
        <footer className="border-t border-gold/[0.12] mt-20 max-w-[1320px] mx-auto px-11 py-8 flex justify-between text-[0.65rem] tracking-[0.28em] uppercase text-ink-mute">
          <span>Outils transverses</span>
          <span className="gold-text font-medium">FondAnalyse Pro</span>
          <span>{new Date().toLocaleString('fr-FR')}</span>
        </footer>
      </>
    );
  }

  // ========== VUE PAYS ==========
  if (!state) return null;
  return (
    <>
      <Header activeView={view} onView={setView} onRefresh={onRefresh} refreshing={refreshing} />
      <main className="max-w-[1320px] mx-auto px-11 py-14">
        <PageTitle title={state.meta.country} sub={`Macro · ${state.meta.now}`} />
        <Tabs tabs={COUNTRY_TABS} active={tabCountry} onChange={setTabCountry} />
        {tabCountry === 'Général'             && <TabGeneral     state={state} />}
        {tabCountry === 'Politique Monétaire' && <TabPolitique   state={state} />}
        {tabCountry === 'Données'             && <TabDonnees     state={state} />}
        {tabCountry === 'Speech'              && <TabSpeech />}
      </main>
      <footer className="border-t border-gold/[0.12] mt-20 max-w-[1320px] mx-auto px-11 py-8 flex justify-between text-[0.65rem] tracking-[0.28em] uppercase text-ink-mute">
        <span>
          {country === 'USD'
            ? <>DXY {state.meta.dxy ?? 'n/a'}{state.meta.usd_score != null ? ` · USD ${state.meta.usd_score}/100` : ''}</>
            : <>Indice {state.meta.countryCode} {state.meta.dxy ?? 'n/a'}{state.meta.usd_score != null ? ` · ${state.meta.countryCode} ${state.meta.usd_score}/100` : ''} · {state.meta.central_bank || ''}</>}
        </span>
        <span className="gold-text font-medium">FondAnalyse Pro</span>
        <span>{state.meta.now}</span>
      </footer>
    </>
  );
}

/* ========== AUTH / LICENCE / PAIEMENT =================================== */

async function apiJson(url, opts = {}) {
  const r = await fetch(url, {
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
    ...opts,
  });
  let body = null;
  try { body = await r.json(); } catch (e) { /* ignore */ }
  if (!r.ok) {
    const msg = (body && (body.detail || body.message)) || `Erreur ${r.status}`;
    throw new Error(typeof msg === 'string' ? msg : 'Erreur');
  }
  return body;
}

function AuthScreen({ onAuthed }) {
  const [mode, setMode] = useState('login');     // 'login' | 'register'
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [name, setName] = useState('');
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);

  const submit = async (e) => {
    e.preventDefault();
    setBusy(true); setError(null);
    try {
      const url = mode === 'login' ? '/api/auth/login' : '/api/auth/register';
      const payload = mode === 'login'
        ? { email, password }
        : { email, password, full_name: name };
      const user = await apiJson(url, { method: 'POST', body: JSON.stringify(payload) });
      onAuthed(user);
    } catch (err) {
      setError(err.message);
    } finally {
      setBusy(false);
    }
  };

  const inputCls = "w-full bg-white/[0.03] border border-gold/20 focus:border-gold/60 outline-none px-4 py-3 text-sm text-ink placeholder:text-ink-mute/60 transition-colors";

  return (
    <div className="min-h-screen flex flex-col items-center justify-center px-6">
      <div className="gold-text font-semibold text-3xl tracking-[0.18em] uppercase mb-1">FondAnalyse</div>
      <div className="text-ink-mute text-[0.62rem] tracking-[0.34em] uppercase mb-10">Pro · Analyse macro</div>

      <form onSubmit={submit} className="w-full max-w-sm flex flex-col gap-3">
        <div className="flex border border-gold/20 mb-3">
          {['login', 'register'].map(m => (
            <button key={m} type="button" onClick={() => { setMode(m); setError(null); }}
              className={`flex-1 py-2.5 text-[0.65rem] tracking-[0.22em] uppercase transition-colors ${mode === m ? 'bg-gold/15 text-gold-hi' : 'text-ink-mute hover:text-ink'}`}>
              {m === 'login' ? 'Connexion' : 'Inscription'}
            </button>
          ))}
        </div>

        {mode === 'register' && (
          <input className={inputCls} placeholder="Nom (optionnel)" value={name}
            onChange={e => setName(e.target.value)} autoComplete="name" />
        )}
        <input className={inputCls} type="email" placeholder="Email" value={email} required
          onChange={e => setEmail(e.target.value)} autoComplete="email" />
        <input className={inputCls} type="password" placeholder="Mot de passe (8 caractères min.)" value={password}
          required minLength={8} onChange={e => setPassword(e.target.value)}
          autoComplete={mode === 'login' ? 'current-password' : 'new-password'} />

        {error && <div className="text-bear text-xs text-center py-1">{error}</div>}

        <button type="submit" disabled={busy}
          className="mt-2 border border-gold/50 bg-gold/10 text-gold-hi py-3 text-[0.7rem] tracking-[0.24em] uppercase hover:bg-gold/20 disabled:opacity-40 transition-colors">
          {busy ? '…' : (mode === 'login' ? 'Se connecter' : 'Créer mon compte')}
        </button>

        {mode === 'register' && (
          <p className="text-ink-mute text-[0.62rem] text-center leading-relaxed mt-1">
            Essai gratuit inclus. Aucune carte requise pour démarrer.
          </p>
        )}
      </form>
    </div>
  );
}

function Paywall({ user, onLogout, onRefresh }) {
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState(null);
  const lic = user.license;
  const expired = lic && !lic.valid;

  const subscribe = async () => {
    setBusy(true); setError(null);
    try {
      const { url } = await apiJson('/api/billing/checkout', { method: 'POST' });
      window.location.href = url;
    } catch (err) {
      setError(err.message);
      setBusy(false);
    }
  };

  return (
    <div className="min-h-screen flex flex-col items-center justify-center px-6 text-center">
      <div className="gold-text font-semibold text-2xl tracking-[0.18em] uppercase mb-2">FondAnalyse Pro</div>
      <div className="text-ink-mute text-[0.62rem] tracking-[0.3em] uppercase mb-8">
        {expired ? 'Votre accès a expiré' : 'Abonnement requis'}
      </div>

      <div className="max-w-md border border-gold/20 p-8 w-full">
        <p className="text-ink-soft text-sm leading-relaxed mb-6">
          {expired
            ? "Votre période d'essai ou votre abonnement est arrivé à échéance. Réactivez votre accès pour retrouver toutes les analyses."
            : "Abonnez-vous pour accéder à l'ensemble des analyses macroéconomiques, corrélations, sentiment et calendrier."}
        </p>

        {error && <div className="text-bear text-xs mb-4">{error}</div>}

        <button onClick={subscribe} disabled={busy}
          className="w-full border border-gold/50 bg-gold/10 text-gold-hi py-3 text-[0.7rem] tracking-[0.24em] uppercase hover:bg-gold/20 disabled:opacity-40 transition-colors mb-3">
          {busy ? 'Redirection…' : "S'abonner"}
        </button>

        <div className="flex justify-center gap-5 text-[0.62rem] tracking-[0.2em] uppercase text-ink-mute mt-4">
          <button onClick={onRefresh} className="hover:text-ink">Actualiser</button>
          <button onClick={onLogout} className="hover:text-ink">Déconnexion</button>
        </div>
      </div>

      <p className="text-ink-mute text-[0.6rem] mt-6">Connecté en tant que {user.email}</p>
    </div>
  );
}

function SuggestionModal({ onClose }) {
  const [category, setCategory] = useState('fonctionnalite');
  const [message, setMessage] = useState('');
  const [busy, setBusy] = useState(false);
  const [sent, setSent] = useState(false);
  const [error, setError] = useState(null);

  const send = async () => {
    if (message.trim().length < 4) { setError('Décrivez votre idée (4 caractères min).'); return; }
    setBusy(true); setError(null);
    try {
      await apiJson('/api/feedback', { method: 'POST', body: JSON.stringify({ category, message }) });
      setSent(true);
    } catch (err) {
      setError(err.message);
    } finally {
      setBusy(false);
    }
  };

  const CATS = [
    ['fonctionnalite', 'Nouvelle fonctionnalité'],
    ['donnee', 'Nouvelle donnée / source'],
    ['bug', 'Anomalie / bug'],
    ['autre', 'Autre'],
  ];

  return (
    <div className="fixed inset-0 z-[60] flex items-center justify-center bg-black/80 backdrop-blur-sm px-6"
      onClick={onClose}>
      <div className="w-full max-w-md border border-gold/25 bg-black/95 p-6 text-left"
        onClick={e => e.stopPropagation()}>
        <div className="gold-text font-semibold text-sm tracking-[0.18em] uppercase mb-1">Proposer une amélioration</div>
        <div className="text-ink-mute text-[0.6rem] tracking-[0.2em] uppercase mb-5">Votre idée pour faire évoluer le programme</div>

        {sent ? (
          <>
            <p className="text-ink-soft text-sm leading-relaxed mb-6">Merci ! Votre proposition a bien été enregistrée.</p>
            <button onClick={onClose}
              className="w-full border border-gold/40 text-gold-hi py-2.5 text-[0.62rem] tracking-[0.2em] uppercase hover:bg-gold/10">
              Fermer
            </button>
          </>
        ) : (
          <>
            <div className="text-ink-mute text-[0.58rem] tracking-[0.2em] uppercase mb-1.5">Catégorie</div>
            <select value={category} onChange={e => setCategory(e.target.value)}
              className="w-full bg-black border border-gold/20 text-ink text-xs px-3 py-2 mb-4 focus:border-gold/50 outline-none">
              {CATS.map(([v, l]) => <option key={v} value={v}>{l}</option>)}
            </select>

            <div className="text-ink-mute text-[0.58rem] tracking-[0.2em] uppercase mb-1.5">Votre proposition</div>
            <textarea value={message} onChange={e => setMessage(e.target.value)} rows={5} maxLength={4000}
              placeholder="Décrivez la fonctionnalité, la donnée ou l'amélioration souhaitée…"
              className="w-full bg-black border border-gold/20 text-ink text-xs px-3 py-2 mb-4 focus:border-gold/50 outline-none resize-none" />

            {error && <div className="text-bear text-xs mb-3">{error}</div>}

            <div className="flex gap-3">
              <button onClick={onClose}
                className="flex-1 border border-white/10 text-ink-mute py-2.5 text-[0.62rem] tracking-[0.2em] uppercase hover:text-ink">
                Annuler
              </button>
              <button onClick={send} disabled={busy}
                className="flex-1 border border-gold/50 bg-gold/10 text-gold-hi py-2.5 text-[0.62rem] tracking-[0.2em] uppercase hover:bg-gold/20 disabled:opacity-40">
                {busy ? 'Envoi…' : 'Envoyer'}
              </button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

function AccountMenu({ user, onLogout }) {
  const [open, setOpen] = useState(false);
  const [suggesting, setSuggesting] = useState(false);
  const lic = user.license;

  const manage = async () => {
    try {
      const { url } = await apiJson('/api/billing/portal', { method: 'POST' });
      window.location.href = url;
    } catch (err) {
      alert(err.message);
    }
  };
  const logout = async () => {
    try { await apiJson('/api/auth/logout', { method: 'POST' }); } catch (e) { /* */ }
    onLogout();
  };

  const statusLabel = lic ? ({
    trialing: 'Essai', active: 'Actif', past_due: 'Paiement dû',
    canceled: 'Annulé', expired: 'Expiré',
  }[lic.status] || lic.status) : '—';

  return (
    <div className="fixed top-4 right-5 z-50">
      <button onClick={() => setOpen(o => !o)}
        className="flex items-center gap-2 border border-gold/25 bg-black/70 backdrop-blur px-3 py-2 text-[0.62rem] tracking-[0.18em] uppercase text-ink-soft hover:border-gold/50">
        <span className="w-1.5 h-1.5 rounded-full" style={{ background: lic && lic.valid ? '#34d6a5' : '#ff4757' }} />
        {user.email.split('@')[0]}
      </button>

      {open && (
        <div className="absolute right-0 mt-2 w-64 border border-gold/25 bg-black/95 backdrop-blur p-4 text-left">
          <div className="text-ink-mute text-[0.58rem] tracking-[0.2em] uppercase">Compte</div>
          <div className="text-ink text-xs mb-3 truncate">{user.email}</div>

          <div className="text-ink-mute text-[0.58rem] tracking-[0.2em] uppercase">Statut</div>
          <div className="text-ink-soft text-xs mb-3">{statusLabel}{lic && lic.period_end ? ` · jusqu'au ${new Date(lic.period_end).toLocaleDateString('fr-FR')}` : ''}</div>

          {lic && (
            <>
              <div className="text-ink-mute text-[0.58rem] tracking-[0.2em] uppercase">Clé de licence</div>
              <div className="text-gold-hi text-[0.7rem] font-mono mb-4 break-all select-all">{lic.key}</div>
            </>
          )}

          <button onClick={manage} className="w-full border border-gold/30 text-gold-hi py-2 text-[0.62rem] tracking-[0.18em] uppercase hover:bg-gold/10 mb-2">
            Gérer l'abonnement
          </button>
          <button onClick={() => { setOpen(false); setSuggesting(true); }}
            className="w-full border border-gold/30 text-gold-hi py-2 text-[0.62rem] tracking-[0.18em] uppercase hover:bg-gold/10 mb-2">
            Proposer une amélioration
          </button>
          <button onClick={logout} className="w-full border border-white/10 text-ink-mute py-2 text-[0.62rem] tracking-[0.18em] uppercase hover:text-ink">
            Déconnexion
          </button>
        </div>
      )}

      {suggesting && <SuggestionModal onClose={() => setSuggesting(false)} />}
    </div>
  );
}

function Root() {
  const [auth, setAuth] = useState('loading');   // 'loading' | null | userObject

  const loadMe = useCallback(async () => {
    try {
      const user = await apiJson('/api/auth/me');
      setAuth(user);
    } catch (e) {
      setAuth(null);
    }
  }, []);

  useEffect(() => { loadMe(); }, [loadMe]);

  // Retour de Stripe Checkout : on rafraîchit le statut (webhook peut prendre 1-2s)
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    if (params.get('paid') === '1') {
      let tries = 0;
      const id = setInterval(async () => {
        tries += 1;
        await loadMe();
        if (tries >= 4) clearInterval(id);
      }, 1500);
      window.history.replaceState({}, '', window.location.pathname);
      return () => clearInterval(id);
    }
  }, [loadMe]);

  if (auth === 'loading') return (
    <div className="fixed inset-0 flex flex-col items-center justify-center">
      <div className="gold-text font-semibold text-3xl tracking-[0.18em] uppercase mb-4">FondAnalyse</div>
      <div className="text-ink-mute text-[0.65rem] tracking-[0.3em] uppercase animate-pulse">Chargement...</div>
    </div>
  );

  if (auth === null) return <AuthScreen onAuthed={setAuth} />;

  const valid = auth.is_admin || (auth.license && auth.license.valid);
  if (!valid) return <Paywall user={auth} onLogout={() => setAuth(null)} onRefresh={loadMe} />;

  return (
    <>
      <AccountMenu user={auth} onLogout={() => setAuth(null)} />
      <App />
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<Root />);
