/* ============================================================
   ui-kit.jsx
   Composants haute-fid Gruvbox Seventies.
   Tout est purement décoratif/interactif côté composant.
   Effets : transparence (glass), hover glow, click 3D, tooltips.
   ============================================================ */

const { useState, useRef, useEffect } = React;

/* ============================================================
   Icônes — Font Awesome 6 Free.
   Mapping nom logique → classe FA. Le CSS de FA est chargé en CDN
   dans le <head>. Le composant garde la MÊME API qu'avant (name,
   size, style) pour ne rien casser ailleurs.
   ============================================================ */
const ICON_MAP = {
  cpu:      'microchip',
  memory:   'memory',
  disk:     'hard-drive',
  network:  'network-wired',
  clock:    'clock',
  grid:     'table-cells',
  list:     'list',
  cog:      'gear',
  alert:    'triangle-exclamation',
  bell:     'bell',
  server:   'server',
  chart:    'chart-line',
  bars:     'chart-simple',
  terminal: 'terminal',
  refresh:  'arrows-rotate',
  play:     'play',
  pause:    'pause',
  power:    'power-off',
  sun:      'sun',
  moon:     'moon',
  search:   'magnifying-glass',
  close:    'xmark',
  chevR:    'chevron-right',
  chevL:    'chevron-left',
  chevD:    'chevron-down',
  chevU:    'chevron-up',
  plus:     'plus',
  filter:   'filter',
  download: 'download',
  folder:   'folder',
  node:     'circle-nodes',
  user:     'user',
};

const Icon = ({ name, size = 16, style }) => {
  const fa = ICON_MAP[name] || 'circle-question';
  return (
    <i className={`fa-solid fa-${fa}`} aria-hidden="true" style={{
      fontSize: size,
      width: size,
      height: size,
      lineHeight: `${size}px`,
      textAlign: 'center',
      display: 'inline-flex',
      alignItems: 'center',
      justifyContent: 'center',
      flex: '0 0 auto',
      color: 'currentColor',
      ...style,
    }} />
  );
};

/* ============================================================
   Tooltip — apparaît au hover après 300ms, position auto.
   ============================================================ */
function Tooltip({ children, label, side = 'top' }) {
  const [show, setShow] = useState(false);
  const t = useRef();
  const onEnter = () => { t.current = setTimeout(() => setShow(true), 280); };
  const onLeave = () => { clearTimeout(t.current); setShow(false); };
  const sides = {
    top:    { bottom: 'calc(100% + 8px)', left: '50%', transform: 'translateX(-50%)' },
    bottom: { top:    'calc(100% + 8px)', left: '50%', transform: 'translateX(-50%)' },
    left:   { right:  'calc(100% + 8px)', top: '50%', transform: 'translateY(-50%)' },
    right:  { left:   'calc(100% + 8px)', top: '50%', transform: 'translateY(-50%)' },
  };
  return (
    <span style={{ position: 'relative', display: 'inline-flex' }}
          onMouseEnter={onEnter} onMouseLeave={onLeave}>
      {children}
      {show && (
        <span className="glass-strong" style={{
          position: 'absolute', ...sides[side],
          padding: '6px 10px',
          borderRadius: 6,
          fontSize: 12, lineHeight: 1.3,
          color: 'var(--ink-1)',
          whiteSpace: 'nowrap',
          boxShadow: 'var(--shadow-2)',
          zIndex: 1000,
          pointerEvents: 'none',
          fontFamily: 'JetBrains Mono, monospace',
          letterSpacing: '0.02em',
        }}>{label}</span>
      )}
    </span>
  );
}

/* ============================================================
   IconButton — bouton icône seul + tooltip obligatoire.
   ============================================================ */
function IconButton({ icon, label, onClick, active, danger, size = 34, primary }) {
  const bg = active ? 'var(--accent-tint)'
           : primary ? 'var(--accent)'
           : 'var(--bg-3)';
  const fg = active ? 'var(--accent)'
           : primary ? 'var(--bg-1)'
           : danger  ? 'var(--err)'
           : 'var(--ink-2)';
  const bd = active ? 'var(--accent-soft)' : 'var(--border-2)';
  return (
    <Tooltip label={label}>
      <button onClick={onClick} className="interactive" style={{
        width: size, height: size,
        background: bg,
        color: fg,
        border: `1px solid ${bd}`,
        borderRadius: 8,
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        padding: 0, cursor: 'pointer',
        boxShadow: primary ? '0 2px 6px var(--accent-glow)' : 'var(--shadow-1)',
      }}>
        <Icon name={icon} size={Math.round(size * 0.5)} />
      </button>
    </Tooltip>
  );
}

/* ============================================================
   Toggle on/off — switch tactile avec glow accent quand ON
   ============================================================ */
function Toggle({ on, onChange, label, icon }) {
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
      {icon && <Icon name={icon} size={14} style={{ color: on ? 'var(--accent)' : 'var(--ink-3)' }} />}
      {label && <span className="label" style={{ color: on ? 'var(--ink-1)' : 'var(--ink-3)' }}>{label}</span>}
      <button onClick={() => onChange(!on)} className="interactive" style={{
        width: 42, height: 22, borderRadius: 12,
        background: on ? 'var(--accent)' : 'var(--bg-4)',
        border: `1px solid ${on ? 'var(--accent-soft)' : 'var(--border-2)'}`,
        boxShadow: on ? `0 0 10px var(--accent-glow), var(--shadow-1)` : 'var(--shadow-press)',
        position: 'relative', cursor: 'pointer', padding: 0,
      }}>
        <span style={{
          position: 'absolute', top: 1, left: on ? 21 : 1,
          width: 18, height: 18, borderRadius: '50%',
          background: on ? 'var(--bg-1)' : 'var(--ink-2)',
          transition: 'left .18s cubic-bezier(.5,.2,.3,1.3), background .15s',
          boxShadow: 'var(--shadow-1)',
        }} />
      </button>
    </div>
  );
}

/* ============================================================
   Status LED — pastille pulsante (effet halo si critique)
   ============================================================ */
function StatusLed({ status = 'ok', size = 10, pulse }) {
  const map = {
    ok:   { c: 'var(--ok)',   g: 'var(--ok-glow)' },
    warn: { c: 'var(--warn)', g: 'var(--warn-glow)' },
    err:  { c: 'var(--err)',  g: 'var(--err-glow)' },
    off:  { c: 'var(--ink-4)', g: 'transparent' },
    info: { c: 'var(--info)', g: 'var(--info-glow)' },
  };
  const { c, g } = map[status];
  const id = `pulse-${status}-${size}`;
  return (
    <>
      {pulse && (
        <style>{`@keyframes ${id} { 0%{box-shadow:0 0 0 0 ${g}} 70%{box-shadow:0 0 0 6px transparent} 100%{box-shadow:0 0 0 0 transparent} }`}</style>
      )}
      <span style={{
        display: 'inline-block',
        width: size, height: size,
        borderRadius: '50%',
        background: c,
        boxShadow: status === 'off' ? 'none' : `0 0 6px ${g}, inset 0 0 2px rgba(0,0,0,0.3)`,
        animation: pulse ? `${id} 1.8s ease-out infinite` : 'none',
        flex: '0 0 auto',
      }} />
    </>
  );
}

/* ============================================================
   BatteryGauge — jauge horizontale style batterie
   - Pas de bandes (couleur unie + léger gloss interne)
   - Pas de graduations verticales
   - Hover : glow lumineux dans la couleur de la jauge
   - Mode compact : label [bar] valeur sur une seule ligne
   ============================================================ */
function BatteryGauge({ value = 60, label, max = 100, unit = '%', warnAt = 70, errAt = 90, height = 22, compact = false, color: colorOverride, icon }) {
  const pct = Math.max(0, Math.min(100, (value / max) * 100));
  const color = colorOverride
    || (pct >= errAt ? 'var(--err)' : pct >= warnAt ? 'var(--warn)' : 'var(--ok)');
  const glowVar = pct >= errAt ? 'var(--err-glow)'
                : pct >= warnAt ? 'var(--warn-glow)'
                : 'var(--ok-glow)';

  // Variante compacte : label [bar] valeur sur une seule ligne
  if (compact) {
    return (
      <div className="bg-hover" style={{
        display: 'flex', alignItems: 'center', gap: 10, minWidth: 0,
        '--bg-glow': glowVar,
      }}>
        {(icon || label) && (
          <span style={{
            flex: '0 0 auto', display: 'inline-flex', alignItems: 'center', gap: 6,
            minWidth: 90,
          }}>
            {icon && <Icon name={icon} size={12} style={{ color: 'var(--ink-3)' }} />}
            {label && <span className="label" style={{ fontSize: 11 }}>{label}</span>}
          </span>
        )}
        <div className="bg-bar" style={{
          flex: 1, height: 12, borderRadius: 3,
          background: 'var(--bg-1)',
          border: '1px solid var(--border-2)',
          boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.4)',
          overflow: 'hidden', position: 'relative',
          transition: 'border-color .2s',
        }}>
          <div className="bg-fill" style={{
            position: 'absolute', top: 1, left: 1, bottom: 1,
            width: `calc((100% - 2px) * ${pct / 100})`,
            background: color,
            borderRadius: 2,
            transition: 'width .4s cubic-bezier(.3,.6,.3,1), box-shadow .2s',
          }} />
        </div>
        <span className="mono" style={{
          flex: '0 0 auto', fontSize: 13,
          color: 'var(--ink-1)', minWidth: 52, textAlign: 'right',
        }}>
          {value}<span style={{ color: 'var(--ink-3)', marginLeft: 2 }}>{unit}</span>
        </span>
      </div>
    );
  }

  return (
    <div className="bg-hover" style={{
      display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0,
      '--bg-glow': glowVar,
    }}>
      {label && (
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
          <span className="label">{label}</span>
          <span className="mono" style={{ fontSize: 13, color: 'var(--ink-1)' }}>
            {value}<span style={{ color: 'var(--ink-3)', marginLeft: 2 }}>{unit}</span>
          </span>
        </div>
      )}
      <div className="bg-bar" style={{
        position: 'relative',
        height, borderRadius: 4,
        background: 'var(--bg-1)',
        border: '1px solid var(--border-2)',
        boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.4)',
        overflow: 'hidden',
        transition: 'border-color .2s',
      }}>
        <div className="bg-fill" style={{
          position: 'absolute', top: 1, left: 1, bottom: 1,
          width: `calc((100% - 2px) * ${pct / 100})`,
          background: color,
          borderRadius: 3,
          transition: 'width .4s cubic-bezier(.3,.6,.3,1), box-shadow .2s',
        }} />
        {/* Gloss interne très léger (un seul highlight haut, pas de bande inférieure) */}
        <div style={{
          position: 'absolute', top: 1, left: 1, right: 1, height: '40%',
          background: 'linear-gradient(180deg, rgba(255,255,255,0.18), transparent)',
          borderRadius: '3px 3px 0 0',
          pointerEvents: 'none',
        }} />
      </div>
    </div>
  );
}

/* ============================================================
   RadialGauge — jauge ronde, version épurée
   ============================================================ */
function RadialGauge({ value = 64, label, size = 120, warnAt = 70, errAt = 90 }) {
  const pct = Math.max(0, Math.min(100, value));
  const color = pct >= errAt ? 'var(--err)' : pct >= warnAt ? 'var(--warn)' : 'var(--ok)';
  const glow  = pct >= errAt ? 'var(--err-glow)' : pct >= warnAt ? 'var(--warn-glow)' : 'var(--ok-glow)';
  const r = size / 2 - 10;
  const cx = size / 2;
  const cy = size / 2 + 6;
  const circ = Math.PI * r;
  const offset = circ - (pct / 100) * circ;
  return (
    <div className="gauge-hover" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
      <svg width={size} height={size * 0.72} viewBox={`0 0 ${size} ${size * 0.8}`}>
        <defs>
          <filter id={`glow-${label}`} x="-50%" y="-50%" width="200%" height="200%">
            <feGaussianBlur stdDeviation="2.5" />
          </filter>
        </defs>
        {/* arc background */}
        <path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
              fill="none" stroke="var(--bg-4)" strokeWidth="6" strokeLinecap="round" />
        {/* arc value glow */}
        <path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
              fill="none" stroke={color} strokeWidth="8" strokeLinecap="round"
              strokeDasharray={circ} strokeDashoffset={offset}
              filter={`url(#glow-${label})`} opacity="0.7" />
        {/* arc value crisp */}
        <path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
              fill="none" stroke={color} strokeWidth="5" strokeLinecap="round"
              strokeDasharray={circ} strokeDashoffset={offset}
              style={{ transition: 'stroke-dashoffset .5s cubic-bezier(.3,.6,.3,1)' }} />
      </svg>
      <div style={{ marginTop: -10, textAlign: 'center' }}>
        <div className="mono" style={{ fontSize: size * 0.22, fontWeight: 600, color: 'var(--ink-1)', lineHeight: 1 }}>
          {value}<span style={{ fontSize: '0.55em', color: 'var(--ink-3)', marginLeft: 2 }}>%</span>
        </div>
        {label && <div className="label" style={{ marginTop: 2 }}>{label}</div>}
      </div>
    </div>
  );
}

/* ============================================================
   BigRadialGauge — la grande jauge cockpit "santé système"
   ============================================================ */
function BigRadialGauge({ value = 87, label = 'score santé · stable' }) {
  const size = 320;
  const r = 130;
  const cx = size / 2;
  const cy = size / 2 + 30;
  const circ = Math.PI * r;
  const offset = circ - (value / 100) * circ;
  const color = value >= 80 ? 'var(--ok)' : value >= 50 ? 'var(--warn)' : 'var(--err)';
  return (
    <div className="gauge-hover" style={{ position: 'relative', width: size, height: size * 0.78 }}>
      <svg width={size} height={size * 0.85}>
        <defs>
          <filter id="biggauge-glow" x="-50%" y="-50%" width="200%" height="200%">
            <feGaussianBlur stdDeviation="4" />
          </filter>
          <linearGradient id="biggauge-grad" x1="0" y1="0" x2="1" y2="0">
            <stop offset="0" stopColor={color} stopOpacity="0.7"/>
            <stop offset="1" stopColor={color}/>
          </linearGradient>
        </defs>
        {/* tics */}
        {Array.from({ length: 21 }).map((_, i) => {
          const a = Math.PI - (i / 20) * Math.PI;
          const major = i % 5 === 0;
          const inner = major ? r + 8 : r + 11;
          const outer = major ? r + 20 : r + 15;
          return <line key={i}
            x1={cx + Math.cos(a) * inner} y1={cy - Math.sin(a) * inner}
            x2={cx + Math.cos(a) * outer} y2={cy - Math.sin(a) * outer}
            stroke={major ? 'var(--ink-3)' : 'var(--ink-4)'} strokeWidth={major ? 1.5 : 0.8}
          />;
        })}
        {[0, 50, 100].map(v => {
          const a = Math.PI - (v / 100) * Math.PI;
          const x = cx + Math.cos(a) * (r + 32);
          const y = cy - Math.sin(a) * (r + 32) + 4;
          return <text key={v} x={x} y={y} textAnchor="middle"
                       fontFamily="JetBrains Mono" fontSize="11"
                       fill="var(--ink-3)">{v}</text>;
        })}
        {/* arc bg */}
        <path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
              fill="none" stroke="var(--bg-4)" strokeWidth="10" strokeLinecap="round" />
        {/* arc value glow */}
        <path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
              fill="none" stroke={color} strokeWidth="14" strokeLinecap="round"
              strokeDasharray={circ} strokeDashoffset={offset}
              filter="url(#biggauge-glow)" opacity="0.55" />
        {/* arc value */}
        <path d={`M ${cx - r} ${cy} A ${r} ${r} 0 0 1 ${cx + r} ${cy}`}
              fill="none" stroke="url(#biggauge-grad)" strokeWidth="9" strokeLinecap="round"
              strokeDasharray={circ} strokeDashoffset={offset}
              style={{ transition: 'stroke-dashoffset .8s cubic-bezier(.3,.6,.3,1)' }} />
        {/* needle */}
        <line x1={cx} y1={cy}
              x2={cx + Math.cos(Math.PI - (value / 100) * Math.PI) * (r - 14)}
              y2={cy - Math.sin(Math.PI - (value / 100) * Math.PI) * (r - 14)}
              stroke="var(--accent)" strokeWidth="3" strokeLinecap="round"
              style={{ filter: 'drop-shadow(0 0 4px var(--accent-glow))' }} />
        <circle cx={cx} cy={cy} r="9" fill="var(--bg-3)" stroke="var(--border-3)" strokeWidth="1.5"/>
        <circle cx={cx} cy={cy} r="3" fill="var(--accent)" />
      </svg>
      <div style={{ position: 'absolute', bottom: 12, left: 0, right: 0, textAlign: 'center' }}>
        <div className="mono" style={{
          fontSize: 64, fontWeight: 700, lineHeight: 1,
          color: 'var(--ink-1)',
          textShadow: `0 0 20px ${color}33`,
        }}>{value}</div>
        <div className="label" style={{ marginTop: 6 }}>{label}</div>
      </div>
    </div>
  );
}

/* ============================================================
   Popup — modale glassmorphism centrée + bouton fermer
   ============================================================ */
function Popup({ open, onClose, title, children, footer, width = 460 }) {
  if (!open) return null;
  return (
    <div style={{
      position: 'absolute', inset: 0, zIndex: 100,
      background: 'rgba(0,0,0,0.45)',
      backdropFilter: 'blur(4px)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
      animation: 'fadein .2s ease-out',
    }} onClick={onClose}>
      <style>{`
        @keyframes fadein { from { opacity: 0 } to { opacity: 1 } }
        @keyframes popin { from { opacity: 0; transform: translateY(8px) scale(.98) } to { opacity: 1; transform: translateY(0) scale(1) } }
      `}</style>
      <div className="glass-strong" onClick={e => e.stopPropagation()} style={{
        width, maxWidth: '90%',
        borderRadius: 12,
        boxShadow: 'var(--shadow-3)',
        animation: 'popin .25s cubic-bezier(.3,.7,.3,1.2)',
        overflow: 'hidden',
      }}>
        <div style={{
          padding: '14px 16px',
          borderBottom: '1px solid var(--border-1)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          background: 'var(--bg-3)',
        }}>
          <div style={{ fontWeight: 600, fontSize: 15, color: 'var(--ink-1)' }}>{title}</div>
          <IconButton icon="close" label="Fermer" onClick={onClose} size={28} />
        </div>
        <div style={{ padding: 18 }}>{children}</div>
        {footer && (
          <div style={{
            padding: '12px 16px',
            borderTop: '1px solid var(--border-1)',
            background: 'var(--bg-2)',
            display: 'flex', justifyContent: 'flex-end', gap: 8,
          }}>{footer}</div>
        )}
      </div>
    </div>
  );
}

/* ============================================================
   Button — bouton classique avec variantes
   ============================================================ */
function Button({ children, icon, onClick, variant = 'default', size = 'md' }) {
  const sizes = {
    sm: { padding: '5px 10px', fontSize: 12, h: 28 },
    md: { padding: '7px 14px', fontSize: 13, h: 34 },
    lg: { padding: '10px 18px', fontSize: 14, h: 40 },
  }[size];
  const variants = {
    default: { bg: 'var(--bg-3)', fg: 'var(--ink-1)', bd: 'var(--border-2)' },
    primary: { bg: 'var(--accent)', fg: 'var(--bg-1)', bd: 'var(--accent-soft)' },
    ghost:   { bg: 'transparent', fg: 'var(--ink-2)', bd: 'var(--border-2)' },
    danger:  { bg: 'var(--bg-3)', fg: 'var(--err)', bd: 'var(--err)' },
  }[variant];
  return (
    <button onClick={onClick} className="interactive" style={{
      height: sizes.h,
      padding: sizes.padding,
      background: variants.bg,
      color: variants.fg,
      border: `1px solid ${variants.bd}`,
      borderRadius: 8,
      display: 'inline-flex', alignItems: 'center', gap: 8,
      fontFamily: 'inherit', fontSize: sizes.fontSize, fontWeight: 500,
      cursor: 'pointer',
      boxShadow: variant === 'primary' ? '0 2px 8px var(--accent-glow)' : 'var(--shadow-1)',
    }}>
      {icon && <Icon name={icon} size={14} />}
      {children}
    </button>
  );
}

/* ============================================================
   TreeNav — arbre dépliable avec icône en tête (style B)
   ============================================================ */
function TreeNav({ groups, activeId, onSelect }) {
  const [open, setOpen] = useState(() =>
    Object.fromEntries(groups.map(g => [g.id, g.open !== false]))
  );
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
      {groups.map(g => (
        <div key={g.id}>
          <div className="interactive" onClick={() => setOpen({ ...open, [g.id]: !open[g.id] })}
               style={{
                 display: 'flex', alignItems: 'center', gap: 8,
                 padding: '7px 8px', borderRadius: 6,
                 color: 'var(--ink-2)',
                 background: 'transparent',
                 border: '1px solid transparent',
                 cursor: 'pointer',
               }}>
            <Icon name="chevR" size={12} style={{
              transform: open[g.id] ? 'rotate(90deg)' : 'rotate(0)',
              transition: 'transform .15s',
              color: 'var(--ink-3)',
            }} />
            <Icon name={g.icon || 'folder'} size={15} style={{ color: 'var(--accent)' }} />
            <span style={{ flex: 1, fontSize: 13, fontWeight: 500 }}>{g.label}</span>
            {g.count != null && (
              <span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>
                {g.count}
              </span>
            )}
          </div>
          {open[g.id] && (
            <div style={{ marginLeft: 18, marginTop: 2, display: 'flex', flexDirection: 'column', gap: 1, paddingLeft: 8, borderLeft: '1px dashed var(--border-1)' }}>
              {g.children.map(c => {
                const active = c.id === activeId;
                return (
                  <div key={c.id} className="interactive" onClick={() => onSelect && onSelect(c.id)}
                       style={{
                         display: 'flex', alignItems: 'center', gap: 8,
                         padding: '6px 10px', borderRadius: 6,
                         background: active ? 'var(--accent-tint)' : 'transparent',
                         color: active ? 'var(--ink-1)' : 'var(--ink-2)',
                         borderLeft: active ? '2px solid var(--accent)' : '2px solid transparent',
                         marginLeft: active ? 0 : 2,
                         fontSize: 12.5,
                       }}>
                    <StatusLed status={c.status} size={8} pulse={c.status === 'err'} />
                    <span className="mono" style={{ fontSize: 11.5, flex: 1 }}>{c.label}</span>
                    {c.meta && <span className="mono" style={{ fontSize: 10, color: 'var(--ink-3)' }}>{c.meta}</span>}
                  </div>
                );
              })}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}

/* ============================================================
   Sparkline pour les KPI
   ============================================================ */
function Sparkline({ points = [], color = 'var(--accent)', h = 28 }) {
  const w = 100;
  const max = Math.max(...points);
  const min = Math.min(...points);
  const range = max - min || 1;
  const step = w / (points.length - 1);
  const path = points.map((p, i) =>
    `${i === 0 ? 'M' : 'L'} ${(i * step).toFixed(1)} ${(h - 2 - ((p - min) / range) * (h - 4)).toFixed(1)}`
  ).join(' ');
  const area = path + ` L ${w} ${h} L 0 ${h} Z`;
  return (
    <svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: '100%', height: h }}>
      <path d={area} fill={color} opacity="0.12" />
      <path d={path} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round" />
    </svg>
  );
}

/* ============================================================
   LineChart — grand graph multi-séries
   ============================================================ */
function LineChart({ series, h = 200, labels }) {
  const w = 600;
  const padding = { l: 36, r: 12, t: 12, b: 24 };
  const innerW = w - padding.l - padding.r;
  const innerH = h - padding.t - padding.b;
  const all = series.flatMap(s => s.points);
  const max = Math.max(...all) * 1.1;
  const min = 0;
  const range = max - min;
  const ptsCount = series[0].points.length;
  const step = innerW / (ptsCount - 1);
  return (
    <svg viewBox={`0 0 ${w} ${h}`} preserveAspectRatio="none" style={{ width: '100%', height: h }}>
      {/* grid horizontal */}
      {[0, 0.25, 0.5, 0.75, 1].map(p => {
        const y = padding.t + innerH * p;
        const v = Math.round(max - range * p);
        return (
          <g key={p}>
            <line x1={padding.l} x2={w - padding.r} y1={y} y2={y}
                  stroke="var(--border-1)" strokeWidth="1" strokeDasharray="3 5" />
            <text x={padding.l - 6} y={y + 3} textAnchor="end"
                  fontFamily="JetBrains Mono" fontSize="9" fill="var(--ink-3)">{v}</text>
          </g>
        );
      })}
      {/* labels x */}
      {labels && labels.map((lb, i) => (
        i % Math.ceil(labels.length / 8) === 0 && (
          <text key={i} x={padding.l + i * step} y={h - 6} textAnchor="middle"
                fontFamily="JetBrains Mono" fontSize="9" fill="var(--ink-3)">{lb}</text>
        )
      ))}
      {/* séries */}
      {series.map((s, si) => {
        const path = s.points.map((p, i) =>
          `${i === 0 ? 'M' : 'L'} ${(padding.l + i * step).toFixed(1)} ${(padding.t + innerH - ((p - min) / range) * innerH).toFixed(1)}`
        ).join(' ');
        const area = path + ` L ${padding.l + (ptsCount - 1) * step} ${padding.t + innerH} L ${padding.l} ${padding.t + innerH} Z`;
        return (
          <g key={si}>
            <path d={area} fill={s.color} opacity="0.12" />
            <path d={path} fill="none" stroke={s.color} strokeWidth="1.8"
                  strokeLinejoin="round" strokeLinecap="round" />
          </g>
        );
      })}
    </svg>
  );
}

/* Expose */
Object.assign(window, {
  Icon, Tooltip, IconButton, Toggle, StatusLed,
  BatteryGauge, RadialGauge, BigRadialGauge,
  Popup, Button, TreeNav, Sparkline, LineChart,
});

/* Effets hover sur les jauges (sans effet au clic) */
(function injectGaugeHoverStyles() {
  if (document.getElementById('gauge-hover-styles')) return;
  const s = document.createElement('style');
  s.id = 'gauge-hover-styles';
  s.textContent = `
    .bg-hover:hover .bg-bar {
      border-color: color-mix(in oklch, var(--accent) 60%, var(--border-3));
    }
    .bg-hover:hover .bg-fill {
      box-shadow: 0 0 14px var(--bg-glow, var(--accent-glow));
      filter: brightness(1.15);
    }
    .gauge-hover { transition: filter .2s; }
    .gauge-hover:hover { filter: drop-shadow(0 0 8px var(--accent-glow)) brightness(1.08); }
  `;
  document.head.appendChild(s);
})();
