import { useState, useRef, useEffect, useCallback } from "react";

// ── Design tokens ──────────────────────────────────────────────
const T = {
  bg: "#0A0A0C",
  panel: "#141418",
  surface: "#1C1C22",
  border: "#2A2A32",
  amber: "#E8A030",
  amberDim: "#7A5218",
  blue: "#3A7BA8",
  blueDim: "#1E4060",
  green: "#3AB87A",
  red: "#C84040",
  textPrimary: "#F0EDE6",
  textSec: "#8888A0",
  textDim: "#444458",
};

const API = window.location.origin;
const WS_URL = API.replace(/^http/, 'ws') + '/ws';

// ── Channel state (module-level, synced from localStorage) ─────
let _channel = 'guitar';
try {
  const saved = localStorage.getItem('pedal_channel');
  if (saved === 'guitar' || saved === 'bass') _channel = saved;
} catch {}

export function getChannel() { return _channel; }
export function setChannel(ch) { _channel = ch; }

function _ch(url) {
  const c = _channel;
  if (!c) return url;
  return url + (url.includes('?') ? '&' : '?') + 'channel=' + encodeURIComponent(c);
}

// ── API helpers ────────────────────────────────────────────────
async function apiGet(path) {
  const r = await fetch(`${API}${_ch(path)}`);
  if (!r.ok) throw new Error(`${path} => ${r.status}`);
  return r.json();
}

async function apiPost(path, data) {
  const r = await fetch(`${API}${_ch(path)}`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: data != null ? JSON.stringify(data) : undefined,
  });
  if (!r.ok) throw new Error(`${path} => ${r.status}`);
  return r.json();
}

async function apiPut(path, data) {
  const r = await fetch(`${API}${_ch(path)}`, {
    method: 'PUT', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!r.ok) throw new Error(`${path} => ${r.status}`);
  return r.json();
}

async function apiDelete(path) {
  const r = await fetch(`${API}${_ch(path)}`, { method: 'DELETE' });
  if (!r.ok) throw new Error(`${path} => ${r.status}`);
  return r.json();
}

async function apiUpload(path, file) {
  const fd = new FormData();
  fd.append('file', file);
  const r = await fetch(`${API}${_ch(path)}`, { method: 'POST', body: fd });
  if (!r.ok) throw new Error(`${path} => ${r.status}`);
  return r.json();
}

// ── usePedalState hook ─────────────────────────────────────────
function usePedalState() {
  const [state, setState] = useState(null);
  const [connected, setConnected] = useState(false);
  const wsRef = useRef(null);
  const pollRef = useRef(null);

  useEffect(() => {
    // Fetch initial state
    apiGet('/api/state').then(s => { setState(s); setConnected(true); }).catch(() => { setConnected(false); });

    // WebSocket for real-time updates
    let reconnectTimer;
    function connect() {
      try {
        const ws = new WebSocket(WS_URL);
        ws.onmessage = (e) => {
          try {
            const msg = JSON.parse(e.data);
            if (msg.type === 'connected' || msg.type === 'state') {
              setState(msg.state);
              setConnected(true);
            }
            if (msg.type === 'preset_changed' && msg.state) setState(msg.state);
            if (msg.type === 'bypass_changed') setState(prev => prev ? { ...prev, bypass: msg.bypass } : prev);
            if (msg.type === 'tuner_changed') setState(prev => prev ? { ...prev, tuner_enabled: msg.tuner_enabled } : prev);
            if (msg.type === 'routing_changed') setState(prev => prev ? { ...prev, routing_mode: msg.routing_mode, routing_breakpoint: msg.routing_breakpoint } : prev);
            if (msg.type === 'channel_mode_changed') setState(prev => prev ? { ...prev, channel_mode: msg.channel_mode } : prev);
            if (msg.type === 'model_loaded' || msg.type === 'ir_loaded') {
              apiGet('/api/state').then(s => setState(s));
            }
            if (msg.type === 'snapshot_recalled' && msg.state) {
              setState(msg.state);
            }
            if (msg.type === 'snapshot_saved') {
              // Refresh state to pick up current_snapshot
              apiGet('/api/state').then(s => setState(s));
            }
          } catch (e) { /* ignore parse errors */ }
        };
        ws.onclose = () => {
          setConnected(false);
          reconnectTimer = setTimeout(connect, 3000);
        };
        ws.onerror = () => ws.close();
        wsRef.current = ws;
      } catch (e) { reconnectTimer = setTimeout(connect, 3000); }
    }
    connect();

    // Polling fallback every 5s
    pollRef.current = setInterval(() => {
      apiGet('/api/state').then(s => { setState(s); setConnected(true); }).catch(() => {});
    }, 5000);

    return () => {
      clearInterval(pollRef.current);
      clearTimeout(reconnectTimer);
      if (wsRef.current) wsRef.current.close();
    };
  }, []);

  const refresh = useCallback(() => {
    apiGet('/api/state').then(s => { setState(s); setConnected(true); }).catch(() => {});
  }, []);

  return { state, connected, refresh };
}

const css = `
  @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@300;400;500;600&display=swap');
  * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
  body { background: ${T.bg}; color: ${T.textPrimary}; font-family: 'Inter', sans-serif; user-select: none; }
  :root { --amber: ${T.amber}; --blue: ${T.blue}; --green: ${T.green}; }

  .mono { font-family: 'JetBrains Mono', monospace; }

  ::-webkit-scrollbar { width: 4px; height: 4px; }
  ::-webkit-scrollbar-track { background: ${T.panel}; }
  ::-webkit-scrollbar-thumb { background: ${T.border}; border-radius: 2px; }

  input, textarea, select { font-family: inherit; }

  .knob-wrap { display: flex; flex-direction: column; align-items: center; gap: 6px; cursor: pointer; }
  .knob { border-radius: 50%; position: relative; touch-action: none; }
  .knob-inner { border-radius: 50%; width: 100%; height: 100%; position: relative;
    background: radial-gradient(circle at 35% 30%, #3A3A48, #1A1A22);
    box-shadow: 0 2px 8px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.07); }
  .knob-dot { position: absolute; width: 3px; border-radius: 2px; background: var(--amber);
    left: 50%; transform-origin: bottom center; box-shadow: 0 0 6px var(--amber); }
  .knob-ring { position: absolute; inset: -5px; border-radius: 50%; }
  .knob-label { font-size: 10px; color: ${T.textSec}; letter-spacing: .06em; text-transform: uppercase; text-align: center; }
  .knob-value { font-family: 'JetBrains Mono', monospace; font-size: 11px; color: ${T.amber}; }

  .toggle { display: flex; background: ${T.surface}; border-radius: 6px; border: 1px solid ${T.border}; overflow: hidden; }
  .toggle-btn { flex: 1; padding: 7px 12px; font-size: 12px; font-weight: 500; letter-spacing: .04em;
    background: none; border: none; color: ${T.textSec}; cursor: pointer; transition: all .15s; }
  .toggle-btn.active { background: ${T.amber}; color: #000; }

  .vu-bar { display: flex; gap: 2px; align-items: flex-end; height: 100%; }
  .vu-seg { width: 4px; border-radius: 1px; transition: opacity .05s; }

  .fader-track { width: 4px; border-radius: 2px; background: ${T.border}; position: relative; cursor: pointer; touch-action: none; }
  .fader-fill { width: 100%; border-radius: 2px; position: absolute; bottom: 0; transition: height .05s; }
  .fader-thumb { width: 28px; height: 10px; border-radius: 3px; position: absolute; left: 50%; transform: translateX(-50%);
    background: linear-gradient(180deg, #3A3A4A, #252530);
    border: 1px solid #4A4A5A; box-shadow: 0 2px 4px rgba(0,0,0,.5);
    cursor: grab; touch-action: none; }
  .fader-thumb::after { content: ''; position: absolute; left: 50%; top: 50%; transform: translate(-50%,-50%);
    width: 16px; height: 2px; border-radius: 1px; background: rgba(255,255,255,.15);
    box-shadow: 0 3px 0 rgba(255,255,255,.15), 0 -3px 0 rgba(255,255,255,.15); }

  .tabbar { display: flex; background: ${T.panel}; border-bottom: 1px solid ${T.border}; flex-shrink: 0; }
  .tab { flex: 1; padding: 10px 4px; font-size: 10px; font-weight: 500; letter-spacing: .04em;
    text-align: center; text-transform: uppercase; color: ${T.textDim};
    cursor: pointer; border-bottom: 2px solid transparent; transition: all .15s; }
  .tab.active { color: ${T.amber}; border-bottom-color: ${T.amber}; }

  .card { background: ${T.panel}; border: 1px solid ${T.border}; border-radius: 10px; padding: 16px; }
  .card-sm { background: ${T.surface}; border: 1px solid ${T.border}; border-radius: 8px; padding: 12px; }

  .btn { padding: 10px 18px; border-radius: 7px; font-size: 13px; font-weight: 600;
    letter-spacing: .04em; border: none; cursor: pointer; transition: all .12s; }
  .btn-primary { background: ${T.amber}; color: #000; }
  .btn-primary:active { filter: brightness(.85); transform: scale(.97); }
  .btn-ghost { background: ${T.surface}; color: ${T.textPrimary}; border: 1px solid ${T.border}; }
  .btn-ghost:active { background: ${T.border}; }
  .btn-danger { background: rgba(200,64,64,.2); color: ${T.red}; border: 1px solid rgba(200,64,64,.3); }
  .btn-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center;
    justify-content: center; background: ${T.surface}; border: 1px solid ${T.border}; cursor: pointer;
    font-size: 16px; transition: all .12s; }
  .btn-icon:active { transform: scale(.93); }
  .btn-icon.active { background: rgba(232,160,48,.15); border-color: ${T.amber}; color: ${T.amber}; }
  .btn-sm { padding: 6px 12px; font-size: 12px; }

  .fx-block { background: ${T.panel}; border: 1px solid ${T.border}; border-radius: 10px;
    overflow: hidden; transition: border-color .15s; }
  .fx-block.active { border-color: ${T.amber}; }
  .fx-block.bypassed { opacity: .45; }
  .fx-header { display: flex; align-items: center; gap: 10px; padding: 10px 12px;
    border-bottom: 1px solid ${T.border}; background: ${T.surface}; cursor: pointer; }
  .fx-body { padding: 12px; display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
  .bypass-led { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
  .led-on { background: ${T.green}; box-shadow: 0 0 8px ${T.green}; }
  .led-off { background: ${T.textDim}; }

  .waveform-wrap { position: relative; overflow: hidden; border-radius: 6px; background: #0F0F14; }
  .waveform-canvas { display: block; }
  .playhead { position: absolute; top: 0; bottom: 0; width: 1.5px; background: ${T.amber};
    box-shadow: 0 0 8px ${T.amber}; pointer-events: none; transition: left .08s linear; }

  .nam-chip { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px;
    background: rgba(58,123,168,.15); border: 1px solid rgba(58,123,168,.35); border-radius: 5px;
    font-family: 'JetBrains Mono', monospace; font-size: 11px; color: ${T.blue}; }

  .section-label { font-size: 10px; letter-spacing: .1em; text-transform: uppercase;
    color: ${T.textDim}; font-weight: 600; margin-bottom: 10px; }

  .preset-row { display: flex; align-items: center; gap: 12px; padding: 10px 12px;
    border-radius: 8px; cursor: pointer; transition: background .12s; border: 1px solid transparent; }
  .preset-row:active, .preset-row.active { background: rgba(232,160,48,.08); border-color: rgba(232,160,48,.25); }

  .transport-btn { width: 44px; height: 44px; border-radius: 50%; display: flex;
    align-items: center; justify-content: center; font-size: 16px; cursor: pointer;
    border: 2px solid ${T.border}; background: ${T.surface}; transition: all .12s; }
  .transport-btn:active { transform: scale(.92); }
  .transport-btn.rec { border-color: ${T.red}; background: rgba(200,64,64,.15); }
  .transport-btn.play { border-color: ${T.green}; background: rgba(58,184,122,.15); }

  .badge { display: inline-flex; align-items: center; justify-content: center;
    padding: 2px 7px; border-radius: 4px; font-size: 10px; font-weight: 700;
    letter-spacing: .06em; text-transform: uppercase; }
  .badge-amber { background: rgba(232,160,48,.2); color: ${T.amber}; }
  .badge-green { background: rgba(58,184,122,.2); color: ${T.green}; }
  .badge-blue { background: rgba(58,123,168,.2); color: ${T.blue}; }
  .badge-red { background: rgba(200,64,64,.2); color: ${T.red}; }

  .screen-header { padding: 12px 14px 8px; display: flex; align-items: center;
    justify-content: space-between; border-bottom: 1px solid ${T.border}; flex-shrink: 0; }
  .screen-title { font-size: 13px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: ${T.textSec}; }

  .screen-body { flex: 1; overflow-y: auto; padding: 12px; display: flex;
    flex-direction: column; gap: 12px; }

  /* Settings lists */
  .setting-row { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid ${T.border}; }
  .setting-label { font-size: 13px; color: ${T.textPrimary}; }
  .setting-desc { font-size: 11px; color: ${T.textDim}; margin-top: 2px; }
  .switch { width: 40px; height: 22px; border-radius: 11px; background: ${T.border}; cursor: pointer; position: relative; transition: background .2s; flex-shrink: 0; }
  .switch.on { background: ${T.green}; }
  .switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: white; transition: transform .15s; }
  .switch.on::after { transform: translateX(18px); }

  .search-bar { display: flex; background: ${T.surface}; border-radius: 7px; border: 1px solid ${T.border};
    padding: 8px 12px; gap: 8px; align-items: center; }
  .search-bar input { background: none; border: none; outline: none; color: ${T.textPrimary}; font-size: 13px; flex: 1; }
  .search-bar input::placeholder { color: ${T.textDim}; }

  .file-drop { border: 2px dashed ${T.border}; border-radius: 8px; padding: 20px; text-align: center;
    cursor: pointer; transition: all .15s; font-size: 13px; color: ${T.textDim}; }
  .file-drop:hover, .file-drop.dragover { border-color: ${T.amber}; color: ${T.amber}; }

  .loader { width: 20px; height: 20px; border: 2px solid ${T.border}; border-top-color: ${T.amber};
    border-radius: 50%; animation: spin .6s linear infinite; }
  @keyframes spin { to { transform: rotate(360deg); } }

  .slide-panel { position: fixed; inset: 0; background: rgba(0,0,0,.7); z-index: 100;
    display: flex; align-items: flex-end; justify-content: center; }
  .slide-panel-inner { background: ${T.bg}; max-width: 440px; width: 100%; max-height: 85vh;
    border-radius: 16px 16px 0 0; overflow: hidden; display: flex; flex-direction: column; }
  .slide-panel-handle { width: 36px; height: 4px; border-radius: 2px; background: ${T.border};
    margin: 8px auto; flex-shrink: 0; }

  /* Snapshot slots */
  .snap-grid { display: flex; gap: 6px; overflow-x: auto; padding: 4px 0; }
  .snap-slot { min-width: 60px; flex-shrink: 0; border-radius: 8px; border: 1.5px solid ${T.border};
    background: ${T.surface}; cursor: pointer; text-align: center; padding: 8px 4px;
    transition: all .12s; touch-action: manipulation; position: relative; }
  .snap-slot:active { transform: scale(.95); }
  .snap-slot.filled { border-color: ${T.amberDim}; }
  .snap-slot.active { border-color: ${T.amber}; background: rgba(232,160,48,.08); }
  .snap-slot .snap-num { font-family: 'JetBrains Mono', monospace; font-size: 16px;
    font-weight: 700; color: ${T.textDim}; }
  .snap-slot.filled .snap-num { color: ${T.amber}; }
  .snap-slot.active .snap-num { color: ${T.amber}; text-shadow: 0 0 8px ${T.amber}; }
  .snap-slot .snap-name { font-size: 9px; color: ${T.textDim}; margin-top: 3px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .snap-slot.filled .snap-name { color: ${T.textSec}; }
  .snap-slot.active .snap-name { color: ${T.amber}; }
  .snap-slot .snap-empty { font-size: 9px; color: ${T.textDim}; margin-top: 3px; font-style: italic; }
  .snap-slot .snap-save-indicator { position: absolute; top: -3px; right: -3px;
    width: 10px; height: 10px; border-radius: 50%; background: ${T.green};
    box-shadow: 0 0 6px ${T.green}; animation: snap-pop .3s ease-out; }
  @keyframes snap-pop { 0% { transform: scale(2); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
  .snap-name-input { background: ${T.surface}; border: 1px solid ${T.amber}; border-radius: 4px;
    padding: 2px 6px; color: ${T.amber}; font-size: 10px; text-align: center; outline: none;
    font-family: 'Inter', sans-serif; width: 100%; }
  .snap-header-btn { cursor: pointer; border: none; background: none; color: ${T.textSec};
    font-size: 16px; padding: 4px 6px; border-radius: 6px; transition: all .12s;
    display: flex; align-items: center; gap: 4px; }
  .snap-header-btn:hover { color: ${T.amber}; background: rgba(232,160,48,.1); }
  .snap-header-btn.active { color: ${T.amber}; background: rgba(232,160,48,.15); }
  .snap-header-num { font-family: 'JetBrains Mono', monospace; font-size: 11px;
    font-weight: 700; color: ${T.amber}; min-width: 16px; text-align: center; }
`;

// ── Knob ──────────────────────────────────────────────────────
function Knob({ label, value = 50, onChange, size = 52, color = T.amber, min = 0, max = 100 }) {
  const ref = useRef(null);
  const startRef = useRef(null);
  const norm = (value - min) / (max - min);
  const angle = -140 + norm * 280;

  const handleStart = (e) => {
    e.preventDefault();
    const clientY = e.touches ? e.touches[0].clientY : e.clientY;
    startRef.current = { y: clientY, val: value };
    const move = (ev) => {
      const cy = ev.touches ? ev.touches[0].clientY : ev.clientY;
      const delta = (startRef.current.y - cy) / 120;
      const next = Math.max(min, Math.min(max, startRef.current.val + delta * (max - min)));
      onChange?.(Math.round(next));
    };
    const up = () => { window.removeEventListener("mousemove", move); window.removeEventListener("mouseup", up);
      window.removeEventListener("touchmove", move); window.removeEventListener("touchend", up); };
    window.addEventListener("mousemove", move); window.addEventListener("mouseup", up);
    window.addEventListener("touchmove", move, { passive: false });
    window.addEventListener("touchend", up);
  };

  const svgSize = size + 12;
  const cx = svgSize / 2, cy = svgSize / 2, r = size / 2 + 3;
  const endA = (angle - 90) * (Math.PI / 180);
  const ex = cx + r * Math.cos(endA), ey = cy + r * Math.sin(endA);

  return (
    <div className="knob-wrap" style={{ width: size + 12 }}>
      <div className="knob" style={{ width: size, height: size }} onMouseDown={handleStart} onTouchStart={handleStart} ref={ref}>
        <svg style={{ position: "absolute", inset: -6, width: svgSize, height: svgSize, overflow: "visible" }}>
          <circle cx={cx} cy={cy} r={r} fill="none" stroke={T.border} strokeWidth="2.5" strokeLinecap="round" opacity=".5" />
          {norm > 0 && <path d={`M ${cx} ${cy - r} A ${r} ${r} 0 ${norm > 0.5 ? 1 : 0} 1 ${ex} ${ey}`}
            fill="none" stroke={color} strokeWidth="2.5" strokeLinecap="round"
            style={{ filter: `drop-shadow(0 0 3px ${color})` }} />}
        </svg>
        <div className="knob-inner" style={{ "--amber": color }}>
          <div className="knob-dot" style={{ height: size * 0.38, bottom: size / 2 - 2,
            transform: `translateX(-50%) rotate(${angle}deg)`, transformOrigin: `50% ${size * 0.38}px` }} />
        </div>
      </div>
      <div className="knob-value mono" style={{ color }}>{value}</div>
      <div className="knob-label">{label}</div>
    </div>
  );
}

// ── VU Meter ──────────────────────────────────────────────────
function VUMeter({ level = 0, height = 60, vertical = true }) {
  const segs = 12;
  const active = Math.round((level / 100) * segs);
  const segColor = (i) => {
    if (i >= 10) return T.red;
    if (i >= 8) return "#E8C030";
    return T.green;
  };
  if (vertical) return (
    <div style={{ display: "flex", flexDirection: "column-reverse", gap: 2, height, alignItems: "center" }}>
      {Array.from({ length: segs }).map((_, i) => (
        <div key={i} style={{ width: 6, height: (height - segs * 2) / segs,
          borderRadius: 1, background: i < active ? segColor(i) : T.border,
          boxShadow: i < active ? `0 0 4px ${segColor(i)}` : "none",
          transition: "background .05s, box-shadow .05s" }} />
      ))}
    </div>
  );
  return (
    <div style={{ display: "flex", gap: 2, height: 6, alignItems: "center", width: height }}>
      {Array.from({ length: segs }).map((_, i) => (
        <div key={i} style={{ flex: 1, height: "100%", borderRadius: 1, background: i < active ? segColor(i) : T.border }} />
      ))}
    </div>
  );
}

// ── Waveform ──────────────────────────────────────────────────
function Waveform({ width, height, color = T.amber, playPos = 0, seed = 1 }) {
  const canvasRef = useRef(null);
  useEffect(() => {
    const c = canvasRef.current; if (!c) return;
    const ctx = c.getContext("2d");
    ctx.clearRect(0, 0, width, height);
    const mid = height / 2;
    ctx.strokeStyle = color; ctx.lineWidth = 1;
    ctx.shadowColor = color; ctx.shadowBlur = 2;
    ctx.beginPath();
    for (let x = 0; x < width; x++) {
      const t = x / width;
      const amp = (Math.sin(t * 31 * seed + seed) * 0.4 + Math.sin(t * 97 * seed) * 0.3
        + Math.sin(t * 17 * seed + 2) * 0.2 + Math.random() * 0.1);
      const h = Math.abs(amp) * (mid * 0.85);
      ctx.moveTo(x, mid - h); ctx.lineTo(x, mid + h);
    }
    ctx.stroke();
    ctx.globalAlpha = 0.12;
    ctx.fillStyle = color;
    ctx.beginPath(); ctx.moveTo(0, mid);
    for (let x = 0; x < width; x++) {
      const t = x / width;
      const amp = (Math.sin(t * 31 * seed + seed) * 0.4 + Math.sin(t * 97 * seed) * 0.3
        + Math.sin(t * 17 * seed + 2) * 0.2);
      ctx.lineTo(x, mid - Math.abs(amp) * mid * 0.85);
    }
    ctx.lineTo(width, mid); ctx.closePath(); ctx.fill();
    ctx.globalAlpha = 1;
  }, [width, height, color, seed]);

  return (
    <div className="waveform-wrap" style={{ width, height }}>
      <canvas ref={canvasRef} width={width} height={height} className="waveform-canvas" />
      <div className="playhead" style={{ left: `${playPos}%` }} />
    </div>
  );
}

// ── Fader ──────────────────────────────────────────────────────
function Fader({ value = 75, onChange, height = 120, label, color = T.amber }) {
  const trackRef = useRef(null);
  const handleStart = (e) => {
    e.preventDefault();
    const move = (ev) => {
      const track = trackRef.current.getBoundingClientRect();
      const cy = ev.touches ? ev.touches[0].clientY : ev.clientY;
      const pct = 1 - Math.max(0, Math.min(1, (cy - track.top) / track.height));
      onChange?.(Math.round(pct * 100));
    };
    const up = () => { window.removeEventListener("mousemove", move); window.removeEventListener("mouseup", up);
      window.removeEventListener("touchmove", move); window.removeEventListener("touchend", up); };
    window.addEventListener("mousemove", move); window.addEventListener("mouseup", up);
    window.addEventListener("touchmove", move, { passive: false });
    window.addEventListener("touchend", up);
  };
  const thumbBottom = (value / 100) * (height - 10);

  return (
    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 8 }}>
      <div style={{ position: "relative", height, display: "flex", justifyContent: "center" }}>
        <div className="fader-track" style={{ height }} ref={trackRef} onMouseDown={handleStart} onTouchStart={handleStart}>
          <div className="fader-fill" style={{ height: `${value}%`, background: color, opacity: 0.4 }} />
          <div className="fader-thumb" style={{ bottom: thumbBottom }} onMouseDown={handleStart} onTouchStart={handleStart} />
        </div>
      </div>
      <div className="knob-value mono" style={{ color }}>{value}</div>
      <div className="knob-label">{label}</div>
    </div>
  );
}

// ── FX Block ──────────────────────────────────────────────────
function FXBlock({ title, type, active = true, bypassed = false, params = [], onToggle }) {
  const [open, setOpen] = useState(true);
  const [vals, setVals] = useState(params.map(p => p.default ?? p.default ?? 50));
  return (
    <div className={`fx-block ${active ? "active" : ""} ${bypassed ? "bypassed" : ""}`}>
      <div className="fx-header" onClick={() => setOpen(o => !o)}>
        <div className={`bypass-led ${!bypassed ? "led-on" : "led-off"}`}
          onClick={(e) => { e.stopPropagation(); onToggle?.(); }} />
        <span style={{ flex: 1, fontSize: 13, fontWeight: 600 }}>{title}</span>
        <span className="badge badge-blue" style={{ fontSize: 10 }}>{type}</span>
        <span style={{ color: T.textDim, fontSize: 18, marginLeft: 6 }}>{open ? "▾" : "▸"}</span>
      </div>
      {open && (
        <div className="fx-body">
          {params.map((p, i) => (
            <Knob key={p.key || p.name} label={p.name} value={vals[i]} size={44}
              onChange={v => setVals(prev => prev.map((x, j) => j === i ? v : x))}
              min={p.min ?? 0} max={p.max ?? 100} />
          ))}
        </div>
      )}
    </div>
  );
}

// ── Signal Arrow Connector ──────────────────────────────────
function SignalArrow({ active = true, variant = 'right', animated = true }) {
  const color = active ? T.green : T.textDim;

  if (variant === 'right') {
    return (
      <svg width="22" height="14" viewBox="0 0 22 14" style={{ flexShrink: 0, display: 'block' }}>
        <line x1="2" y1="7" x2="14" y2="7" stroke={color} strokeWidth="2" strokeLinecap="round" />
        <polygon points="12,3 20,7 12,11" fill={color} />
        {active && animated && (
          <circle r="1.5" fill={color} opacity="0.6">
            <animate attributeName="cx" values="4;18" dur="1.2s" repeatCount="indefinite" />
          </circle>
        )}
      </svg>
    );
  }

  if (variant === 'split') {
    return (
      <svg width="26" height="24" viewBox="0 0 26 24" style={{ flexShrink: 0, display: 'block' }}>
        <line x1="2" y1="10" x2="14" y2="10" stroke={color} strokeWidth="2" strokeLinecap="round" />
        <line x1="8" y1="10" x2="18" y2="18" stroke={color} strokeWidth="2" strokeLinecap="round" />
        <polygon points="12,6 18,10 12,14" fill={color} />
        <polygon points="16,14 21,20 19,13" fill={color} />
      </svg>
    );
  }

  if (variant === 'merge') {
    return (
      <svg width="26" height="24" viewBox="0 0 26 24" style={{ flexShrink: 0, display: 'block' }}>
        <line x1="2" y1="4" x2="12" y2="12" stroke={color} strokeWidth="2" strokeLinecap="round" />
        <line x1="2" y1="20" x2="12" y2="12" stroke={color} strokeWidth="2" strokeLinecap="round" />
        <line x1="12" y1="12" x2="14" y2="12" stroke={color} strokeWidth="2" strokeLinecap="round" />
        <polygon points="12,8 18,12 12,16" fill={color} />
      </svg>
    );
  }

  return null;
}

// ── Slide Panel ──────────────────────────────────────────────
function SlidePanel({ children, onClose }) {
  return (
    <div className="slide-panel" onClick={onClose}>
      <div className="slide-panel-inner" onClick={e => e.stopPropagation()}>
        <div className="slide-panel-handle" />
        {children}
      </div>
    </div>
  );
}

// ══════════════════════════════════════════════════════════════
// SCREENS
// ══════════════════════════════════════════════════════════════

// 1. RIG SCREEN ────────────────────────────────────────────────
function RigScreen({ state, connected, refresh }) {
  const [volume, setVolume] = useState(state?.master_volume != null ? Math.round(state.master_volume * 100) : 80);
  // Optimistic local states so toggles respond immediately
  const [localBypass, setLocalBypass] = useState(null);
  const [localTuner, setLocalTuner] = useState(null);

  const bypass = localBypass ?? state?.bypass ?? false;
  const tuner = localTuner ?? state?.tuner_enabled ?? false;

  useEffect(() => {
    if (state?.master_volume != null) setVolume(Math.round(state.master_volume * 100));
  }, [state?.master_volume]);

  // Sync optimistic states from server when they arrive
  useEffect(() => {
    if (state?.bypass !== undefined) setLocalBypass(null);
    if (state?.tuner_enabled !== undefined) setLocalTuner(null);
  }, [state?.bypass, state?.tuner_enabled]);

  const handleVolume = async (val) => {
    setVolume(val);
    try {
      await apiPut('/api/volume', { volume: val / 100 });
    } catch (e) { /* ignore */ }
  };

  const handleBypass = async () => {
    const next = !bypass;
    setLocalBypass(next);
    try {
      await apiPost('/api/bypass', { bypass: next });
    } catch (e) { setLocalBypass(null); }
  };

  const handleTuner = async () => {
    const next = !tuner;
    setLocalTuner(next);
    try {
      await apiPost('/api/tuner', { enabled: next });
    } catch (e) { setLocalTuner(null); }
  };

  // Real audio levels from the pipeline (0-100 scale, updated every poll cycle)
  const inputLevel = state?.input_level ?? 0;
  const outputLevel = state?.output_level ?? 0;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <div className="screen-header">
        <div>
          <div className="screen-title">Active Rig</div>
          <div style={{ display: "flex", gap: 6, marginTop: 4, flexWrap: "wrap", alignItems: "center" }}>
            {state?.current_preset ? (
              <span className="nam-chip" style={{ color: T.amber, borderColor: `${T.amber}55`, background: `${T.amber}15` }}>
                {state.current_preset.name}
              </span>
            ) : (
              <span style={{ fontSize: 11, color: T.textDim }}>No preset loaded</span>
            )}
            {connected ? (
              <span className="badge badge-green">Connected</span>
            ) : (
              <span className="badge badge-red">Offline</span>
            )}
          </div>
        </div>
        <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
          <VUMeter level={inputLevel} height={44} />
          <VUMeter level={outputLevel} height={44} />
        </div>
      </div>
      <div className="screen-body">
        {/* Status row */}
        <div className="card-sm" style={{ display: "flex", gap: 10, justifyContent: "space-around" }}>
          <div style={{ textAlign: "center" }}>
          <div className="section-label" style={{ marginBottom: 4 }}>Bypass</div>
          <div className={`switch ${bypass ? 'on' : ''}`} onClick={handleBypass} style={{ margin: "0 auto" }} />
          </div>
          <div style={{ textAlign: "center" }}>
          <div className="section-label" style={{ marginBottom: 4 }}>Tuner</div>
          <div className={`switch ${tuner ? 'on' : ''}`} onClick={handleTuner} style={{ margin: "0 auto" }} />
          </div>
          <div style={{ textAlign: "center" }}>
          <div className="section-label" style={{ marginBottom: 4 }}>Routing</div>
          <span className="mono" style={{ fontSize: 12, color: T.amber }}>{state?.routing_mode || "mono"}</span>
          </div>
          </div>

          {/* Tuner active indicator */}
          {tuner && (
          <div className="card-sm" style={{ background: `${T.amber}15`, borderColor: T.amber, textAlign: "center", padding: "20px 16px" }}>
          <div style={{ fontSize: 32, marginBottom: 8 }}>🎵</div>
          <div className="section-label" style={{ color: T.amber, marginBottom: 4, fontSize: 14 }}>TUNER ACTIVE</div>
          <div className="mono" style={{ fontSize: 24, color: T.amber, letterSpacing: 4 }}>—————●—————</div>
          <div style={{ fontSize: 11, color: T.textDim, marginTop: 8 }}>Tap switch again to exit tuner mode</div>
          </div>
          )}

        {/* Volume */}
        <div className="card" style={{ display: "flex", gap: 20, alignItems: "center" }}>
          <div style={{ flex: 1 }}>
            <div className="section-label">Master Volume</div>
            <input type="range" min={0} max={100} value={volume}
              onChange={e => handleVolume(+e.target.value)}
              style={{ width: "100%", accentColor: T.amber }} />
            <div className="mono" style={{ fontSize: 11, color: T.amber, marginTop: 4 }}>
              {volume}%
            </div>
          </div>
          <div className="mono" style={{ fontSize: 11, color: T.textSec, textAlign: "right" }}>
            {state?.nam_loaded ? "NAM: Active" : "NAM: —"}
            <br />
            {state?.ir_loaded ? "IR: Active" : "IR: —"}
          </div>
        </div>

        {/* Signal chain */}
        <div className="card-sm">
          <div className="section-label">Signal Chain</div>
          <div style={{ display: "flex", alignItems: "center", gap: 4, overflowX: "auto", padding: "4px 0" }}>
            {(() => {
              const chain = ["IN", state?.bypass ? "BYP" : (state?.nam_loaded ? "NAM" : "AMP"), state?.ir_loaded ? "IR" : "CAB", "OUT"];
              const is4CM = state?.routing_mode === '4cm';
              const bp = state?.routing_breakpoint || 7;
              const active = !state?.bypass;
              return chain.map((s, i, arr) => (
                <div key={i} style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
                  {is4CM && s === "AMP" && (
                    <SignalArrow variant="split" active={active} />
                  )}
                  {is4CM && s === "CAB" && (
                    <SignalArrow variant="merge" active={active} />
                  )}
                  <div style={{ padding: "3px 8px", borderRadius: 4, fontSize: 10, fontWeight: 700,
                    background: i === 0 || i === arr.length - 1 ? T.blueDim : (is4CM && s === "AMP" ? "rgba(58,184,122,.15)" : T.amberDim),
                    color: i === 0 || i === arr.length - 1 ? T.blue : (is4CM && s === "AMP" ? T.green : T.amber),
                    border: `1px solid ${(i === 0 || i === arr.length - 1 ? T.blue : (is4CM && s === "AMP" ? T.green : T.amber))}60` }}>
                    {s}
                    {is4CM && s === "AMP" && <span style={{ fontSize: 8, marginLeft: 4, opacity: 0.7 }}>4CM</span>}
                    {is4CM && s === "BYP" && <span style={{ fontSize: 8, marginLeft: 4, opacity: 0.7 }}>4CM</span>}
                  </div>
                  {is4CM && s === "AMP" && (
                    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", flexShrink: 0, gap: 1 }}>
                      <SignalArrow variant="split" active={active} />
                      <span style={{ fontSize: 7, color: T.textDim, letterSpacing: '.05em', lineHeight: '1' }}>LOOP</span>
                      <SignalArrow variant="merge" active={active} />
                    </div>
                  )}
                  {i < arr.length - 1 && !(is4CM && s === "AMP") && <SignalArrow active={active} />}
                </div>
              ));
            })()}
          </div>
        </div>

        {/* NAM + IR info */}
        <div className="card-sm" style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
          <div>
            <div className="section-label" style={{ marginBottom: 6 }}>NAM Model</div>
            {state?.nam_model ? (
              <div className="nam-chip">{state.nam_model}</div>
            ) : (
              <div style={{ fontSize: 11, color: T.textDim }}>Not loaded</div>
            )}
          </div>
          <div>
            <div className="section-label" style={{ marginBottom: 6 }}>IR</div>
            {state?.ir_name ? (
              <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
                <div className="nam-chip" style={{ color: T.green, borderColor: `${T.green}55`, background: `${T.green}15`, fontSize: 10 }}>{state.ir_name}</div>
              </div>
            ) : (
              <div style={{ fontSize: 11, color: T.textDim }}>Not loaded</div>
            )}
          </div>
        </div>
        {/* Quick links */}
        <div className="card-sm" style={{ display: "flex", justifyContent: "space-around", gap: 6 }}>
          <button className="btn btn-ghost btn-sm" style={{ flex: 1, fontSize: 10 }} onClick={() => {
            const tabbar = document.querySelector('.tabbar');
            const tabs = tabbar?.children;
            if (tabs) tabs[3]?.click(); // IRs tab index
          }}>Browse IRs</button>
          <button className="btn btn-ghost btn-sm" style={{ flex: 1, fontSize: 10 }} onClick={() => {
            const tabbar = document.querySelector('.tabbar');
            const tabs = tabbar?.children;
            if (tabs) tabs[2]?.click(); // Models tab index
          }}>Browse Models</button>
        </div>
      </div>
    </div>
  );
}

// 2. FX CHAIN SCREEN ───────────────────────────────────────────
const FX_TYPE_LABELS = {
  noise_gate: "Noise Gate", compressor: "Compressor", boost: "Boost",
  overdrive: "Overdrive", distortion: "Distortion", fuzz: "Fuzz",
  eq: "EQ", chorus: "Chorus", flanger: "Flanger", phaser: "Phaser",
  tremolo: "Tremolo", vibrato: "Vibrato", delay: "Delay", reverb: "Reverb",
  volume: "Volume", nam_amp: "NAM Amp", ir_cab: "IR Cab",
  octaver: "Octaver", pitch_shifter: "Pitch Shifter", harmonizer: "Harmonizer",
  whammy: "Whammy", detune: "Detune", ring_modulator: "Ring Mod",
  auto_wah: "Auto Wah", envelope_filter: "Env Filter", rotary_speaker: "Rotary",
  uni_vibe: "Uni-Vibe", auto_pan: "Auto Pan", stereo_widener: "Stereo Widener",
  bitcrusher: "Bitcrusher", wavefolder: "Wavefolder", rectifier: "Rectifier",
  expander: "Expander", de_esser: "De-esser", transient_shaper: "Transient Shaper",
  parametric_eq: "Parametric EQ", high_pass_filter: "High Pass",
  low_pass_filter: "Low Pass", band_pass_filter: "Band Pass",
  notch_filter: "Notch Filter", formant_filter: "Formant Filter",
  ping_pong_delay: "Ping Pong Delay", multi_tap_delay: "Multi Tap",
  reverse_delay: "Reverse Delay", tape_echo: "Tape Echo",
  shimmer_reverb: "Shimmer Reverb", looper: "Looper",
  early_reflections: "Early Reflections", sidechain_compressor: "Sidechain Comp",
};

function FXScreen({ state, connected }) {
  const [fxList, setFxList] = useState(null);
  const [loading, setLoading] = useState(true);
  const [showAdd, setShowAdd] = useState(false);
  const [paramSchemas, setParamSchemas] = useState({});

  // Load block param schemas
  const loadParams = useCallback(async (fxType) => {
    if (paramSchemas[fxType]) return paramSchemas[fxType];
    try {
      const data = await apiGet(`/api/block-params/${fxType}`);
      setParamSchemas(prev => ({ ...prev, [fxType]: data.params || [] }));
      return data.params || [];
    } catch (e) { return []; }
  }, [paramSchemas]);

  useEffect(() => {
    if (!connected || !state?.current_preset) {
      // Offline: show a simple empty state
      setFxList([]);
      setLoading(false);
      return;
    }

    // Load active preset's chain from presets API
    apiGet('/api/presets').then(async data => {
      const banks = data.banks || [];
      const cp = state.current_preset;
      const preset = banks[cp.bank]?.presets?.[cp.program];
      if (preset?.chain) {
        const blocks = await Promise.all(preset.chain.map(async (b, i) => {
          const params = await loadParams(b.fx_type);
          return {
            id: i + 1,
            title: FX_TYPE_LABELS[b.fx_type] || b.fx_type.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
            type: b.fx_type,
            bypassed: b.bypass || !b.enabled,
            params: params,
          };
        }));
        setFxList(blocks);
      }
      setLoading(false);
    }).catch(() => {
      setFxList([]);
      setLoading(false);
    });
  }, [connected, state, loadParams]);

  const toggle = (id) => setFxList(prev => prev ? prev.map(f => f.id === id ? { ...f, bypassed: !f.bypassed } : f) : prev);

  const addBlock = async (fxType) => {
    setShowAdd(false);
    const params = await loadParams(fxType);
    setFxList(prev => [...(prev || []), {
      id: Date.now(),
      title: FX_TYPE_LABELS[fxType] || fxType,
      type: fxType,
      bypassed: false,
      params: params,
    }]);
  };

  if (loading) return <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}><div className="loader" /></div>;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <div className="screen-header">
        <div className="screen-title">FX Chain {(fxList?.length || 0) > 0 ? `(${fxList.length})` : ''}</div>
        <button className="btn btn-ghost btn-sm" onClick={() => setShowAdd(true)}>+ Add Block</button>
      </div>
      <div className="screen-body">
        {/* Signal path */}
        {fxList && fxList.length > 0 && (
          <div style={{ display: "flex", alignItems: "center", gap: 4, padding: "4px 2px", overflowX: "auto" }}>
            {(() => {
              const is4CM = state?.routing_mode === '4cm';
              const bp = is4CM ? (state?.routing_breakpoint || 7) : -1;
              const items = [];
              // Build chain items array with connector metadata
              items.push({ label: "IN", type: "io", idx: -1 });
              fxList.forEach((f, idx) => {
                if (is4CM && idx === bp) {
                  items.push({ label: null, type: "merge-bp", idx });
                }
                items.push({ label: f.type.toUpperCase(), type: "fx", idx, bypassed: f.bypassed });
                if (is4CM && idx === bp - 1) {
                  items.push({ label: null, type: "split-bp", idx });
                }
              });
              items.push({ label: "OUT", type: "io", idx: -1 });

              return items.map((item, i) => {
                if (item.type === 'split-bp') {
                  return (
                    <div key={`bp-${i}`} style={{ display: "flex", flexDirection: "column", alignItems: "center", flexShrink: 0, gap: 1 }}>
                      <SignalArrow variant="split" active={true} />
                      <span style={{ fontSize: 7, color: T.textDim, letterSpacing: '.05em', lineHeight: '1' }}>4CM</span>
                      <SignalArrow variant="merge" active={true} />
                    </div>
                  );
                }
                if (item.type === 'merge-bp') {
                  // Already rendered as part of the split-bp composite — skip
                  return null;
                }
                return (
                  <div key={`${item.type}-${i}`} style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
                    {item.type === 'io' && (
                      <div style={{ padding: "3px 8px", borderRadius: 4, fontSize: 10, fontWeight: 700,
                        background: T.blueDim, color: T.blue, border: `1px solid ${T.blue}60` }}>{item.label}</div>
                    )}
                    {item.type === 'fx' && (
                      <div style={{ padding: "3px 8px", borderRadius: 4, fontSize: 10, fontWeight: 700,
                        background: T.amberDim, color: item.bypassed ? T.textDim : T.amber,
                        border: `1px solid ${(item.bypassed ? T.textDim : T.amber)}60`, opacity: item.bypassed ? 0.5 : 1 }}>{item.label}</div>
                    )}
                    {/* Connector after this block */}
                    {i < items.length - 1 && items[i+1]?.type !== 'split-bp' && items[i]?.type !== 'merge-bp' && (
                      <SignalArrow active={item.type === 'io' || !item.bypassed} />
                    )}
                  </div>
                );
              });
            })()}
          </div>
        )}
        {(!fxList || fxList.length === 0) ? (
          <div style={{ textAlign: "center", padding: 30, color: T.textDim, fontSize: 14 }}>
            No FX blocks in the chain.<br />
            <button className="btn btn-ghost btn-sm" style={{ marginTop: 10 }} onClick={() => setShowAdd(true)}>+ Add your first block</button>
          </div>
        ) : (
          fxList.map(f => (
            <FXBlock key={f.id} {...f} active={!f.bypassed} onToggle={() => toggle(f.id)} />
          ))
        )}
      </div>

      {/* Add Block slide panel */}
      {showAdd && (
        <SlidePanel onClose={() => setShowAdd(false)}>
          <div className="screen-header">
            <div className="screen-title">Add FX Block</div>
          </div>
          <div className="screen-body" style={{ gap: 4 }}>
            {Object.entries(FX_TYPE_LABELS).map(([key, label]) => (
              <div key={key} className="preset-row" onClick={() => addBlock(key)} style={{ borderRadius: 6 }}>
                <span className="badge badge-blue" style={{ width: 60, flexShrink: 0 }}>{key.split('_')[0]}</span>
                <span style={{ flex: 1, fontSize: 13 }}>{label}</span>
                <span style={{ color: T.textDim }}>+</span>
              </div>
            ))}
          </div>
        </SlidePanel>
      )}
    </div>
  );
}

// 3. MODELS SCREEN ─────────────────────────────────────────────
function ModelsScreen({ state, connected, refresh }) {
  const [models, setModels] = useState([]);
  const [current, setCurrent] = useState(null);
  const [loading, setLoading] = useState(true);
  const [searching, setSearching] = useState(false);
  const [searchQ, setSearchQ] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const [showSearch, setShowSearch] = useState(false);
  const [uploading, setUploading] = useState(false);
  const fileRef = useRef(null);

  const loadModels = useCallback(async () => {
    if (!connected) { setLoading(false); return; }
    try {
      const data = await apiGet('/api/models');
      setModels(data.models || []);
      setCurrent(data.current);
    } catch (e) { /* ignore */ }
    setLoading(false);
  }, [connected]);

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

  const handleLoad = async (path) => {
    try { await apiPost('/api/models/load', { path }); await loadModels(); refresh(); } catch (e) { alert('Failed to load model'); }
  };

  const handleUnload = async () => {
    try { await apiPost('/api/models/unload'); setCurrent(null); await loadModels(); refresh(); } catch (e) { alert('Failed to unload'); }
  };

  const handleUpload = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    setUploading(true);
    try {
      await apiUpload('/api/models/upload', file);
      await loadModels();
    } catch (err) { alert('Upload failed'); }
    setUploading(false);
  };

  const handleSearch = useCallback(async () => {
    if (!searchQ.trim()) return;
    setSearching(true);
    try {
      const data = await apiGet(`/api/models/tonedownload/search?q=${encodeURIComponent(searchQ)}`);
      setSearchResults(data.results || []);
    } catch (e) { alert('Search failed'); }
    setSearching(false);
  }, [searchQ]);

  const handleInstall = async (result) => {
    try {
      await apiPost('/api/models/tonedownload/install', {
        download_url: result.download_url,
        name: result.name || result.filename,
        id: result.id,
        size: result.size,
        pack_name: result.pack_name,
        display_name: result.name,
      });
      await loadModels();
      refresh();
    } catch (e) { alert('Install failed'); }
  };

  if (loading) return <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}><div className="loader" /></div>;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <div className="screen-header">
        <div className="screen-title">NAM Models ({models.length})</div>
        <div style={{ display: "flex", gap: 6 }}>
          <button className="btn btn-ghost btn-sm" onClick={() => setShowSearch(!showSearch)}>
            {showSearch ? "Local" : "Search"}
          </button>
          <button className="btn btn-primary btn-sm" onClick={() => fileRef.current?.click()}>
            {uploading ? "..." : "+ Upload"}
          </button>
          <input ref={fileRef} type="file" accept=".nam" style={{ display: "none" }} onChange={handleUpload} />
        </div>
      </div>

      {showSearch && (
        <div style={{ padding: "8px 12px", borderBottom: `1px solid ${T.border}` }}>
          <div className="search-bar">
            <span style={{ color: T.textDim }}>🔍</span>
            <input type="text" placeholder="Search Tone3000…" value={searchQ}
              onChange={e => setSearchQ(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleSearch()} />
            <button className="btn btn-primary btn-sm" onClick={handleSearch} disabled={searching}>
              {searching ? "..." : "Go"}
            </button>
          </div>
          {searchResults.length > 0 && (
            <div style={{ marginTop: 8, maxHeight: 240, overflow: "auto" }}>
              {searchResults.map(r => (
                <div key={r.id} className="preset-row" onClick={() => handleInstall(r)} style={{ borderBottom: `1px solid ${T.border}`, borderRadius: 0 }}>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 13 }}>{r.name}</div>
                    <div style={{ fontSize: 10, color: T.textDim }}>{r.author} · {r.size_display} · {r.architecture}</div>
                  </div>
                  <button className="btn btn-primary btn-sm">Get</button>
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      <div className="screen-body">
        {current && (
          <div className="card-sm">
            <div className="section-label" style={{ marginBottom: 6 }}>Loaded Model</div>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <div className="nam-chip">{current}</div>
              <button className="btn btn-danger btn-sm" onClick={handleUnload}>Unload</button>
            </div>
          </div>
        )}

        {models.length === 0 ? (
          <div style={{ textAlign: "center", padding: 30, color: T.textDim, fontSize: 14 }}>
            No NAM models found.<br />Upload a .nam file or search Tone3000.
          </div>
        ) : (
          models.map(m => (
            <div key={m.name} className={`preset-row ${m.loaded ? "active" : ""}`}>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 500 }}>{m.name}</div>
                <div style={{ fontSize: 10, color: T.textDim }}>{m.architecture} · {m.size_mb} MB</div>
              </div>
              {m.loaded ? (
                <span className="badge badge-green">Loaded</span>
              ) : (
                <button className="btn btn-ghost btn-sm" onClick={() => handleLoad(m.path)}>Load</button>
              )}
            </div>
          ))
        )}
      </div>
    </div>
  );
}

// 4. IRS SCREEN ────────────────────────────────────────────────
function IRsScreen({ state, connected, refresh }) {
  const [irs, setIrs] = useState([]);
  const [current, setCurrent] = useState(null);
  const [loading, setLoading] = useState(true);
  const [searching, setSearching] = useState(false);
  const [searchQ, setSearchQ] = useState('');
  const [searchResults, setSearchResults] = useState([]);
  const [showSearch, setShowSearch] = useState(false);
  const [uploading, setUploading] = useState(false);
  const fileRef = useRef(null);

  const loadIrs = useCallback(async () => {
    if (!connected) { setLoading(false); return; }
    try {
      const data = await apiGet('/api/irs');
      setIrs(data.irs || []);
      setCurrent(data.current);
    } catch (e) { /* ignore */ }
    setLoading(false);
  }, [connected]);

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

  const handleLoad = async (path) => {
    try { await apiPost('/api/irs/load', { path }); await loadIrs(); refresh(); } catch (e) { alert('Failed to load IR'); }
  };

  const handleUnload = async () => {
    try { await apiPost('/api/irs/unload'); setCurrent(null); await loadIrs(); refresh(); } catch (e) { alert('Failed to unload'); }
  };

  const handleUpload = async (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    setUploading(true);
    try {
      await apiUpload('/api/irs/upload', file);
      await loadIrs();
    } catch (err) { alert('Upload failed'); }
    setUploading(false);
  };

  const handleSearch = useCallback(async () => {
    if (!searchQ.trim()) return;
    setSearching(true);
    try {
      const data = await apiGet(`/api/irs/tonedownload/search?q=${encodeURIComponent(searchQ)}`);
      setSearchResults(data.results || []);
    } catch (e) { alert('Search failed'); }
    setSearching(false);
  }, [searchQ]);

  const handleInstall = async (result) => {
    try {
      await apiPost('/api/irs/tonedownload/install', {
        download_url: result.download_url,
        name: result.name || result.filename,
        id: result.id,
        size: result.size,
        display_name: result.name,
      });
      await loadIrs();
      refresh();
    } catch (e) { alert('Install failed'); }
  };

  if (loading) return <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}><div className="loader" /></div>;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <div className="screen-header">
        <div className="screen-title">IR Files ({irs.length})</div>
        <div style={{ display: "flex", gap: 6 }}>
          <button className="btn btn-ghost btn-sm" onClick={() => setShowSearch(!showSearch)}>
            {showSearch ? "Local" : "Search"}
          </button>
          <button className="btn btn-primary btn-sm" onClick={() => fileRef.current?.click()}>
            {uploading ? "..." : "+ Upload"}
          </button>
          <input ref={fileRef} type="file" accept=".wav" style={{ display: "none" }} onChange={handleUpload} />
        </div>
      </div>

      {showSearch && (
        <div style={{ padding: "8px 12px", borderBottom: `1px solid ${T.border}` }}>
          <div className="search-bar">
            <span style={{ color: T.textDim }}>🔍</span>
            <input type="text" placeholder="Search Tone3000 IRs…" value={searchQ}
              onChange={e => setSearchQ(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleSearch()} />
            <button className="btn btn-primary btn-sm" onClick={handleSearch} disabled={searching}>
              {searching ? "..." : "Go"}
            </button>
          </div>
          {searchResults.length > 0 && (
            <div style={{ marginTop: 8, maxHeight: 240, overflow: "auto" }}>
              {searchResults.map(r => (
                <div key={r.id} className="preset-row" onClick={() => handleInstall(r)} style={{ borderBottom: `1px solid ${T.border}`, borderRadius: 0 }}>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 13 }}>{r.name}</div>
                    <div style={{ fontSize: 10, color: T.textDim }}>{r.author} · {r.size_display}</div>
                  </div>
                  <button className="btn btn-primary btn-sm">Get</button>
                </div>
              ))}
            </div>
          )}
        </div>
      )}

      <div className="screen-body">
        {current && (
          <div className="card-sm">
            <div className="section-label" style={{ marginBottom: 6 }}>Loaded IR</div>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <div className="nam-chip" style={{ color: T.green, borderColor: `${T.green}55`, background: `${T.green}15` }}>{current}</div>
              <button className="btn btn-danger btn-sm" onClick={handleUnload}>Unload</button>
            </div>
          </div>
        )}

        {irs.length === 0 ? (
          <div style={{ textAlign: "center", padding: 30, color: T.textDim, fontSize: 14 }}>
            No IR files found.<br />Upload a .wav file or search Tone3000.
          </div>
        ) : (
          irs.map(ir => (
            <div key={ir.name} className={`preset-row ${ir.loaded ? "active" : ""}`}>
              <div style={{ flex: 1 }}>
                <div style={{ fontSize: 13, fontWeight: 500 }}>{ir.name}</div>
                <div style={{ fontSize: 10, color: T.textDim }}>{ir.sample_rate} Hz · {ir.length_ms} ms · {ir.num_taps} taps</div>
              </div>
              {ir.loaded ? (
                <span className="badge badge-green">Loaded</span>
              ) : (
                <button className="btn btn-ghost btn-sm" onClick={() => handleLoad(ir.path)}>Load</button>
              )}
            </div>
          ))
        )}
      </div>
    </div>
  );
}

// 5. PRESETS SCREEN ────────────────────────────────────────────
function PresetsScreen({ state, connected, refresh }) {
  const [banks, setBanks] = useState([]);
  const [selectedBank, setSelectedBank] = useState(0);
  const [loading, setLoading] = useState(true);
  const [saveName, setSaveName] = useState('');
  const [showSave, setShowSave] = useState(false);
  const [deleting, setDeleting] = useState(null);

  const loadPresets = useCallback(async () => {
    if (!connected) {
      setBanks([{ name: "Default", number: 0, presets: [
        { name: "Edge of Breakup", bank: 0, program: 0 },
        { name: "Modern High Gain", bank: 0, program: 1 },
        null, null,
      ]}]);
      setLoading(false);
      return;
    }
    try {
      const data = await apiGet('/api/presets');
      setBanks(data.banks || []);
    } catch (e) { /* ignore */ }
    setLoading(false);
  }, [connected]);

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

  const handleActivate = async (bank, program) => {
    try {
      await apiPost(`/api/presets/${bank}/${program}/activate`);
      await loadPresets();
      refresh();
    } catch (e) { alert('Failed to load preset'); }
  };

  const handleSave = async () => {
    if (!saveName.trim()) return;
    const currentBank = state?.current_preset?.bank ?? 0;
    const currentProgram = state?.current_preset?.program ?? 0;
    try {
      await apiPut(`/api/presets/${currentBank}/${currentProgram}`, { name: saveName });
      setShowSave(false);
      await loadPresets();
    } catch (e) { alert('Failed to save'); }
  };

  const handleNewPreset = async () => {
    // Find the first empty slot, or create at next program number
    const bank = selectedBank;
    const bankData = banks[bank];
    const presets = bankData?.presets || [];
    let nextProg = presets.findIndex(p => p == null);
    if (nextProg === -1) nextProg = presets.length;
    const name = `New Preset ${nextProg + 1}`;
    try {
      await apiPut(`/api/presets/${bank}/${nextProg}`, { name });
      await loadPresets();
      await apiPost(`/api/presets/${bank}/${nextProg}/activate`);
      refresh();
    } catch (e) { alert('Failed to create preset'); }
  };

  const handleDelete = async (bank, program) => {
    try {
      await apiDelete(`/api/presets/${bank}/${program}`);
      setDeleting(null);
      await loadPresets();
    } catch (e) { alert('Failed to delete'); }
  };

  if (loading) return <div style={{ flex: 1, display: "flex", alignItems: "center", justifyContent: "center" }}><div className="loader" /></div>;

  const currentPreset = state?.current_preset;
  const bankData = banks[selectedBank];
  const presets = bankData?.presets || [];

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <div className="screen-header">
        <div className="screen-title">Presets</div>
        <div style={{ display: "flex", gap: 6 }}>
          <button className="btn btn-ghost btn-sm" onClick={() => setShowSave(true)}>Save</button>
          <button className="btn btn-primary btn-sm" onClick={handleNewPreset}>+ New</button>
        </div>
      </div>

      <div className="screen-body">
        {/* Bank selector */}
        {banks.length > 1 && (
          <div className="toggle">
            {banks.map((b, i) => (
              <button key={b.number} className={`toggle-btn ${selectedBank === i ? "active" : ""}`}
                onClick={() => setSelectedBank(i)}>{b.name}</button>
            ))}
          </div>
        )}

        {/* Current preset indicator */}
        {currentPreset && (
          <div className="card-sm" style={{ display: "flex", alignItems: "center", gap: 8 }}>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 11, color: T.textDim }}>Current</div>
              <div style={{ fontSize: 14, fontWeight: 600, color: T.amber }}>
                {currentPreset.name}
                <span className="mono" style={{ fontSize: 10, color: T.textDim, marginLeft: 8 }}>
                  Bank {currentPreset.bank} · Prog {currentPreset.program}
                </span>
              </div>
            </div>
          </div>
        )}

        {/* Preset list */}
        {presets.length === 0 ? (
          <div style={{ textAlign: "center", padding: 30, color: T.textDim, fontSize: 14 }}>No presets in this bank.</div>
        ) : (
          presets.map((p, i) => {
            if (!p) return null;
            const isActive = currentPreset?.bank === p.bank && currentPreset?.program === p.program;
            const isDeleting = deleting === `${p.bank}-${p.program}`;
            return (
              <div key={i} className={`preset-row ${isActive ? "active" : ""}`}
                onClick={() => !isDeleting && handleActivate(p.bank, p.program)}>
                <div className="mono" style={{ width: 28, height: 28, borderRadius: 6, display: "flex",
                  alignItems: "center", justifyContent: "center", fontSize: 12, fontWeight: 700,
                  background: isActive ? `${T.amber}22` : T.surface,
                  color: isActive ? T.amber : T.textDim,
                  border: `1px solid ${isActive ? T.amber+"60" : T.border}` }}>{p.program + 1}</div>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 13, fontWeight: 600 }}>{p.name}</div>
                  <div style={{ fontSize: 10, color: T.textDim }}>Bank {p.bank} · Program {p.program}</div>
                </div>
                {isActive && <span style={{ color: T.amber, fontSize: 16 }}>●</span>}
                {isDeleting ? (
                  <div style={{ display: "flex", gap: 4 }}>
                    <button className="btn btn-danger btn-sm" onClick={e => { e.stopPropagation(); handleDelete(p.bank, p.program); }}>Confirm</button>
                    <button className="btn btn-ghost btn-sm" onClick={e => { e.stopPropagation(); setDeleting(null); }}>Cancel</button>
                  </div>
                ) : (
                  <button className="btn btn-ghost btn-sm" style={{ color: T.red }}
                    onClick={e => { e.stopPropagation(); setDeleting(`${p.bank}-${p.program}`); }}>Delete</button>
                )}
              </div>
            );
          })
        )}
      </div>

      {/* Save dialog */}
      {showSave && (
        <SlidePanel onClose={() => setShowSave(false)}>
          <div className="screen-header">
            <div className="screen-title">Save Preset</div>
          </div>
          <div className="screen-body">
            <div className="search-bar">
              <input type="text" placeholder="Preset name…" value={saveName}
                onChange={e => setSaveName(e.target.value)} autoFocus
                onKeyDown={e => e.key === 'Enter' && handleSave()} />
            </div>
            <div style={{ display: "flex", gap: 8 }}>
              <button className="btn btn-primary" style={{ flex: 1 }} onClick={handleSave}>Save</button>
              <button className="btn btn-ghost" onClick={() => setShowSave(false)}>Cancel</button>
            </div>
          </div>
        </SlidePanel>
      )}
    </div>
  );
}

// 6. SETTINGS SCREEN ───────────────────────────────────────────
function SettingsScreen({ state, connected, refresh }) {
  const [tab, setTab] = useState('wifi');
  const [wifiNetworks, setWifiNetworks] = useState([]);
  const [btDevices, setBtDevices] = useState([]);
  const [btPaired, setBtPaired] = useState([]);
  const [wifiScanning, setWifiScanning] = useState(false);
  const [btScanning, setBtScanning] = useState(false);
  const [connectSsid, setConnectSsid] = useState('');
  const [connectPw, setConnectPw] = useState('');
  const [showConnect, setShowConnect] = useState(null);
  const [routingMode, setRoutingMode] = useState(state?.routing_mode || 'mono');
  const [routingBp, setRoutingBp] = useState(state?.routing_breakpoint || 7);
  const [channelMode, setChannelMode] = useState(state?.channel_mode || 'dual-mono');

  useEffect(() => {
    if (state?.routing_mode) setRoutingMode(state.routing_mode);
    if (state?.routing_breakpoint != null) setRoutingBp(state.routing_breakpoint);
    if (state?.channel_mode) setChannelMode(state.channel_mode);
  }, [state?.routing_mode, state?.routing_breakpoint, state?.channel_mode]);

  const scanWifi = async () => {
    setWifiScanning(true);
    try {
      const data = await apiGet('/api/wifi/scan');
      setWifiNetworks(data.networks || []);
    } catch (e) { /* ignore */ }
    setWifiScanning(false);
  };

  const connectWifi = async (ssid, password) => {
    try {
      await apiPost('/api/wifi/connect', { ssid, password });
      setShowConnect(null);
      setConnectSsid('');
      setConnectPw('');
      setTimeout(refresh, 2000);
    } catch (e) { alert('Failed to connect'); }
  };

  const disconnectWifi = async () => {
    try { await apiPost('/api/wifi/disconnect'); setTimeout(refresh, 2000); } catch (e) { /* ignore */ }
  };

  const toggleHotspot = async () => {
    const active = state?.wifi?.hotspot_active;
    try {
      if (active) await apiPost('/api/wifi/hotspot/disable');
      else await apiPost('/api/wifi/hotspot/enable', { ssid: 'Pi-Pedal', password: 'pedal1234' });
      setTimeout(refresh, 2000);
    } catch (e) { /* ignore */ }
  };

  const handleBtPower = async (on) => {
    try { await apiPost('/api/bluetooth/power', { on }); setTimeout(refresh, 1000); } catch (e) { /* ignore */ }
  };

  const scanBt = async () => {
    setBtScanning(true);
    try {
      const data = await apiPost('/api/bluetooth/scan');
      setBtDevices(data.devices || []);
    } catch (e) { /* ignore */ }
    setBtScanning(false);
  };

  const loadBtPaired = async () => {
    try {
      const data = await apiGet('/api/bluetooth/paired');
      setBtPaired(data.devices || []);
    } catch (e) { /* ignore */ }
  };

  const pairBt = async (addr) => {
    try { await apiPost('/api/bluetooth/pair', { address: addr }); await loadBtPaired(); } catch (e) { alert('Pairing failed'); }
  };

  const handleRouting = async (mode, bp) => {
    try { await apiPost('/api/routing', { routing_mode: mode, routing_breakpoint: bp }); } catch (e) { /* ignore */ }
  };

  const handleChannelMode = async (mode) => {
    try {
      await apiPost('/api/channel-mode', { channel_mode: mode });
      setChannelMode(mode);
    } catch (e) { /* ignore */ }
  };

  useEffect(() => { if (connected) { scanWifi(); loadBtPaired(); } }, [connected]);

  const wifiState = state?.wifi;
  const btState = state?.bluetooth;

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <div className="screen-header">
        <div className="screen-title">Settings</div>
      </div>

      {/* Settings tabs */}
      <div className="toggle" style={{ margin: "6px 10px" }}>
        {[{ id: 'wifi', label: 'WiFi' }, { id: 'bt', label: 'Bluetooth' }, { id: 'routing', label: 'Routing' }].map(t => (
          <button key={t.id} className={`toggle-btn ${tab === t.id ? "active" : ""}`} onClick={() => setTab(t.id)}>{t.label}</button>
        ))}
      </div>

      <div className="screen-body" style={{ gap: 4 }}>
        {/* ── WiFi Tab ─────────────────────────────────────────────*/}
        {tab === 'wifi' && (
          <>
            {/* WiFi Status */}
            <div className="card-sm">
              <div className="setting-row" style={{ border: "none", paddingTop: 0 }}>
                <div>
                  <div className="setting-label">WiFi</div>
                  <div className="setting-desc">{wifiState?.connected ? `Connected to ${wifiState.ssid}` : 'Disconnected'}</div>
                </div>
                <div className={`switch ${wifiState?.connected ? 'on' : ''}`} onClick={disconnectWifi} />
              </div>
              {wifiState?.connected && (
                <div style={{ fontSize: 11, color: T.textDim }}>
                  IP: {wifiState.ip} · Signal: {wifiState.signal}%
                </div>
              )}

              {/* Hotspot */}
              <div className="setting-row">
                <div>
                  <div className="setting-label">Hotspot</div>
                  <div className="setting-desc">{wifiState?.hotspot_active ? `Active: ${wifiState.hotspot_ssid}` : 'Off'}</div>
                </div>
                <div className={`switch ${wifiState?.hotspot_active ? 'on' : ''}`} onClick={toggleHotspot} />
              </div>
            </div>

            {/* Scan networks */}
            <div className="card-sm">
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
                <div className="section-label" style={{ marginBottom: 0 }}>Networks</div>
                <button className="btn btn-ghost btn-sm" onClick={scanWifi} disabled={wifiScanning}>
                  {wifiScanning ? "..." : "Scan"}
                </button>
              </div>
              {wifiNetworks.length === 0 ? (
                <div style={{ fontSize: 12, color: T.textDim, padding: "8px 0" }}>Scan to see available networks.</div>
              ) : (
                wifiNetworks.map(n => (
                  <div key={n.ssid} className="preset-row" style={{ padding: "8px 0", borderRadius: 0, borderBottom: `1px solid ${T.border}` }}
                    onClick={() => wpaState?.ssid !== n.ssid && setShowConnect(n.ssid)}>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 13 }}>{n.ssid}</div>
                      <div className="mono" style={{ fontSize: 10, color: T.textDim }}>Signal: {n.signal}% · {n.encryption || 'Open'}</div>
                    </div>
                    {wifiState?.ssid === n.ssid && <span className="badge badge-green">Connected</span>}
                  </div>
                ))
              )}
            </div>

            {/* Connect dialog */}
            {showConnect && (
              <div className="card-sm">
                <div className="section-label">Connect to {showConnect}</div>
                <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                  <input type="password" placeholder="Password (leave blank for open network)"
                    value={connectPw} onChange={e => setConnectPw(e.target.value)}
                    style={{ background: T.surface, border: `1px solid ${T.border}`, borderRadius: 6, padding: "8px 12px", color: T.textPrimary, fontSize: 13, outline: "none" }}
                    onKeyDown={e => e.key === 'Enter' && connectWifi(showConnect, connectPw)} />
                  <div style={{ display: "flex", gap: 6 }}>
                    <button className="btn btn-primary btn-sm" style={{ flex: 1 }}
                      onClick={() => connectWifi(showConnect, connectPw)}>Connect</button>
                    <button className="btn btn-ghost btn-sm" onClick={() => setShowConnect(null)}>Cancel</button>
                  </div>
                </div>
              </div>
            )}
          </>
        )}

        {/* ── Bluetooth Tab ─────────────────────────────────────────*/}
        {tab === 'bt' && (
          <>
            <div className="card-sm">
              <div className="setting-row" style={{ border: "none", paddingTop: 0 }}>
                <div>
                  <div className="setting-label">Bluetooth</div>
                  <div className="setting-desc">{btState?.powered ? 'Powered on' : 'Off'}</div>
                </div>
                <div className={`switch ${btState?.powered ? 'on' : ''}`}
                  onClick={() => handleBtPower(!btState?.powered)} />
              </div>
              {btState?.powered && (
                <>
                  <div className="setting-row">
                    <div>
                      <div className="setting-label">Discoverable</div>
                      <div className="setting-desc">{btState?.name} ({btState?.address})</div>
                    </div>
                    <div className={`switch ${btState?.discoverable ? 'on' : ''}`}
                      onClick={async () => { try { await apiPost('/api/bluetooth/discoverable', { on: !btState?.discoverable }); setTimeout(refresh, 500); } catch(e) {} }} />
                  </div>
                  <div className="setting-row">
                    <div>
                      <div className="setting-label">Bluetooth MIDI</div>
                      <div className="setting-desc">{btState?.midi_enabled ? 'Enabled' : 'Disabled'}</div>
                    </div>
                    <div className={`switch ${btState?.midi_enabled ? 'on' : ''}`}
                      onClick={async () => { try { await (btState?.midi_enabled ? apiPost('/api/bluetooth/midi-disable') : apiPost('/api/bluetooth/midi-enable')); setTimeout(refresh, 500); } catch(e) {} }} />
                  </div>
                </>
              )}
            </div>

            {btState?.powered && (
              <div className="card-sm">
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
                  <div className="section-label" style={{ marginBottom: 0 }}>Paired Devices ({btPaired.length})</div>
                  <button className="btn btn-ghost btn-sm" onClick={scanBt} disabled={btScanning}>
                    {btScanning ? "..." : "Scan"}
                  </button>
                </div>
                {btPaired.length === 0 ? (
                  <div style={{ fontSize: 12, color: T.textDim, padding: "8px 0" }}>No paired devices.</div>
                ) : (
                  btPaired.map(d => (
                    <div key={d.address} className="preset-row" style={{ padding: "8px 0", borderRadius: 0, borderBottom: `1px solid ${T.border}` }}>
                      <div style={{ flex: 1 }}>
                        <div style={{ fontSize: 13 }}>{d.name || d.address}</div>
                        <div className="mono" style={{ fontSize: 10, color: T.textDim }}>{d.address} · {d.connected ? 'Connected' : 'Paired'}</div>
                      </div>
                      {!d.connected && (
                        <button className="btn btn-ghost btn-sm" onClick={async () => { try { await apiPost('/api/bluetooth/connect', { address: d.address }); } catch(e) { alert('Failed'); } }}>Connect</button>
                      )}
                    </div>
                  ))
                )}
              </div>
            )}

            {btScanning && btDevices.length > 0 && (
              <div className="card-sm">
                <div className="section-label" style={{ marginBottom: 6 }}>Discovered</div>
                {btDevices.map(d => (
                  <div key={d.address} className="preset-row" style={{ padding: "8px 0", borderRadius: 0, borderBottom: `1px solid ${T.border}` }}>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 13 }}>{d.name || d.address}</div>
                      <div className="mono" style={{ fontSize: 10, color: T.textDim }}>{d.address}</div>
                    </div>
                    <button className="btn btn-ghost btn-sm" onClick={() => pairBt(d.address)}>Pair</button>
                  </div>
                ))}
              </div>
            )}
          </>
        )}

        {/* ── Routing Tab ───────────────────────────────────────────*/}
        {tab === 'routing' && (
          <>
          <div className="card-sm">
            <div className="section-label">4CM Routing</div>
            <div className="setting-row">
              <div>
                <div className="setting-label">Mode</div>
                <div className="setting-desc">Mono (normal) or 4CM (4-cable method)</div>
              </div>
              <select value={routingMode} onChange={e => { setRoutingMode(e.target.value); handleRouting(e.target.value, routingBp); }}
                style={{ background: T.surface, border: `1px solid ${T.border}`, borderRadius: 6, padding: "6px 10px", color: T.textPrimary, fontSize: 13, outline: "none" }}>
                <option value="mono">Mono</option>
                <option value="4cm">4CM</option>
              </select>
            </div>
            {routingMode === '4cm' && (
              <div className="setting-row">
                <div>
                  <div className="setting-label">Breakpoint</div>
                  <div className="setting-desc">Chain position for amp send/return</div>
                </div>
                <input type="number" min={1} max={15} value={routingBp}
                  onChange={e => { const v = +e.target.value; setRoutingBp(v); handleRouting(routingMode, v); }}
                  style={{ background: T.surface, border: `1px solid ${T.border}`, borderRadius: 6, padding: "6px 10px", color: T.textPrimary, fontSize: 13, width: 60, outline: "none", textAlign: "center" }} />
              </div>
            )}
          </div>
          {/* Channel Mode */}
          <div className="card-sm">
            <div className="section-label">Channel Mode</div>
            <div className="setting-row">
              <div>
                <div className="setting-label">Routing</div>
                <div className="setting-desc">Dual-mono: two independent channels · Stereo: linked as L/R pair</div>
              </div>
              <select value={channelMode} onChange={e => handleChannelMode(e.target.value)}
                style={{ background: T.surface, border: `1px solid ${T.border}`, borderRadius: 6, padding: "6px 10px", color: T.textPrimary, fontSize: 13, outline: "none" }}>
                <option value="dual-mono">Dual-Mono</option>
                <option value="stereo">Stereo</option>
              </select>
            </div>
            <div style={{ fontSize: 10, color: T.textDim, marginTop: 4, padding: '4px 0' }}>
              {channelMode === 'dual-mono'
                ? 'Each channel controlled independently from its own phone.'
                : 'Channels A (Left) and B (Right) are controlled together.'}
            </div>
          </div>
          </>
        )}
      </div>
    </div>
  );
}

// 7. LOOPER / RECORDER SCREEN ──────────────────────────────────
function RecordScreen({ state }) {
  const [recording, setRecording] = useState(false);
  const [captures, setCaptures] = useState([]);
  const [loading, setLoading] = useState(true);
  const inputLevel = state?.input_level ?? 0;
  const outputLevel = state?.output_level ?? 0;

  const loadCaptures = useCallback(async () => {
    try {
      const data = await apiGet('/api/captures');
      setCaptures(data.captures || []);
    } catch (e) { /* ignore */ }
    setLoading(false);
  }, []);

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

  const handleRecord = async () => {
    try {
      await apiPost('/api/capture/start');
      setRecording(true);
    } catch (e) { alert('Failed to start recording'); }
  };

  const handleStop = async () => {
    try {
      await apiPost('/api/capture/stop');
      setRecording(false);
      loadCaptures();
    } catch (e) { alert('Failed to stop recording'); }
  };

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%" }}>
      <div className="screen-header">
        <div>
          <div className="screen-title">Looper / Recorder</div>
          <div className="mono" style={{ fontSize: 10, color: T.textDim }}>
            {state?.sample_rate ? `${state.sample_rate/1000}kHz` : '—'}
            {recording && <span style={{ color: T.red, marginLeft: 8 }}>● RECORDING</span>}
          </div>
        </div>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          {recording ? (
            <button className="btn btn-danger btn-sm" onClick={handleStop} style={{ padding: "8px 16px" }}>
              ⏹ Stop
            </button>
          ) : (
            <button className="btn btn-primary btn-sm" onClick={handleRecord} style={{ padding: "8px 16px", background: T.red, color: '#fff' }}>
              ● Record
            </button>
          )}
        </div>
      </div>

      <div className="screen-body">
        {/* Live levels during recording */}
        <div className="card" style={{ padding: "16px", display: "flex", gap: 16, alignItems: "center" }}>
          <div style={{ flex: 1 }}>
            <div className="section-label">Input Level</div>
            <div style={{ display: "flex", gap: 2, height: 20, marginTop: 4 }}>
              {Array.from({ length: 12 }).map((_, i) => {
                const active = Math.round((inputLevel / 100) * 12) > i;
                const c = i >= 10 ? T.red : i >= 8 ? '#E8C030' : T.green;
                return <div key={i} style={{ flex: 1, borderRadius: 2, background: active ? c : T.border,
                  transition: 'all .05s' }} />;
              })}
            </div>
          </div>
          <div style={{ flex: 1 }}>
            <div className="section-label">Output Level</div>
            <div style={{ display: "flex", gap: 2, height: 20, marginTop: 4 }}>
              {Array.from({ length: 12 }).map((_, i) => {
                const active = Math.round((outputLevel / 100) * 12) > i;
                const c = i >= 10 ? T.red : i >= 8 ? '#E8C030' : T.green;
                return <div key={i} style={{ flex: 1, borderRadius: 2, background: active ? c : T.border,
                  transition: 'all .05s' }} />;
              })}
            </div>
          </div>
        </div>

        {/* Recording status */}
        {recording && (
          <div className="card-sm" style={{ background: `${T.red}15`, borderColor: T.red, textAlign: 'center', padding: 20 }}>
            <div style={{ fontSize: 40, marginBottom: 8, animation: 'pulse 1s ease-in-out infinite' }}>🔴</div>
            <div style={{ fontSize: 14, fontWeight: 600, color: T.red }}>Recording...</div>
            <div className="mono" style={{ fontSize: 11, color: T.textDim, marginTop: 4 }}>
              Saving to captures folder — tap Stop to finish
            </div>
          </div>
        )}

        {/* Captured files */}
        {!recording && (
          <>
            <div className="card-sm">
              <div className="section-label" style={{ marginBottom: 6 }}>Recordings ({captures.length})</div>
              {loading ? (
                <div className="loader" style={{ margin: '10px auto' }} />
              ) : captures.length === 0 ? (
                <div style={{ fontSize: 12, color: T.textDim, padding: '10px 0' }}>
                  No recordings yet. Tap ● Record to capture audio.
                </div>
              ) : (
                captures.map(c => (
                  <div key={c.name} className="preset-row" style={{ padding: '8px 0', borderRadius: 0, borderBottom: `1px solid ${T.border}` }}>
                    <div style={{ flex: 1 }}>
                      <div style={{ fontSize: 13 }}>{c.name}</div>
                      <div className="mono" style={{ fontSize: 10, color: T.textDim }}>{c.size_display}</div>
                    </div>
                    <a href={`/api/captures/${c.name}`} target="_blank" rel="noopener"
                      className="btn btn-ghost btn-sm" style={{ textDecoration: 'none' }}>▶ Play</a>
                  </div>
                ))
              )}
            </div>
          </>
        )}
      </div>

      <style>{`@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }`}</style>
    </div>
  );
}

// ══════════════════════════════════════════════════════════════
// SNAPSHOT PANEL
// ══════════════════════════════════════════════════════════════

function SnapshotPanel({ onClose, state, connected }) {
  const [snapshots, setSnapshots] = useState({});
  const [loading, setLoading] = useState(true);
  const [editingSlot, setEditingSlot] = useState(null);
  const [editName, setEditName] = useState('');
  const [saveFlash, setSaveFlash] = useState(null);
  const [discardEdits, setDiscardEdits] = useState(false);
  const holdTimer = useRef(null);
  const currentSlot = state?.current_snapshot ?? 0;

  const loadSnapshots = useCallback(async () => {
    if (!connected) { setLoading(false); return; }
    try {
      const data = await apiGet('/api/snapshots');
      setSnapshots(data.snapshots || {});
    } catch (e) { /* ignore */ }
    setLoading(false);
  }, [connected]);

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

  // Handle press & hold vs tap
  const handlePointerDown = (slot) => {
    holdTimer.current = setTimeout(() => {
      // Long press → save
      holdTimer.current = null;
      handleSave(slot);
    }, 600);
  };

  const handlePointerUp = (slot) => {
    if (holdTimer.current) {
      clearTimeout(holdTimer.current);
      holdTimer.current = null;
      // Short tap → recall (or save if empty)
      if (snapshots[String(slot)]) {
        handleRecall(slot);
      } else {
        handleSave(slot);
      }
    }
  };

  const handlePointerLeave = () => {
    if (holdTimer.current) {
      clearTimeout(holdTimer.current);
      holdTimer.current = null;
    }
  };

  const handleSave = async (slot) => {
    try {
      await apiPost(`/api/snapshots/${slot}/save`);
      setSaveFlash(slot);
      setTimeout(() => setSaveFlash(null), 800);
      await loadSnapshots();
    } catch (e) { /* ignore */ }
  };

  const handleRecall = async (slot) => {
    try {
      await apiPost(`/api/snapshots/${slot}/recall`);
      await loadSnapshots();
    } catch (e) { /* ignore */ }
  };

  const handleRename = async (slot) => {
    if (!editName.trim()) return;
    try {
      await apiPut(`/api/snapshots/${slot}`, { name: editName.trim() });
      setEditingSlot(null);
      await loadSnapshots();
    } catch (e) { /* ignore */ }
  };

  const handleDelete = async (slot) => {
    try {
      await apiDelete(`/api/snapshots/${slot}`);
      await loadSnapshots();
    } catch (e) { /* ignore */ }
  };

  return (
    <SlidePanel onClose={onClose}>
      <div className="screen-header" style={{ justifyContent: "space-between" }}>
        <div className="screen-title">Snapshots</div>
        <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
          <label style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 10, color: T.textDim, cursor: "pointer" }}>
            <div className={`switch ${discardEdits ? 'on' : ''}`} style={{ width: 28, height: 16 }}
              onClick={() => setDiscardEdits(d => !d)}>
              <div style={{ position: "absolute", top: 1, left: discardEdits ? 11 : 1, width: 13, height: 13,
                borderRadius: "50%", background: "#fff", transition: "left .15s" }} />
            </div>
            Discard Edits
          </label>
        </div>
      </div>
      <div className="screen-body" style={{ gap: 8 }}>
        <div style={{ fontSize: 11, color: T.textDim, marginBottom: 4 }}>
          Tap to recall &middot; Hold to save
          {discardEdits && <span style={{ color: T.amber, marginLeft: 8 }}>Edits will be discarded on recall</span>}
        </div>
        <div className="snap-grid">
          {[1,2,3,4,5,6,7,8].map(slot => {
            const snap = snapshots[String(slot)];
            const isActive = currentSlot === slot;
            const isFilled = !!snap;
            const isEditing = editingSlot === slot;
            const isFlashing = saveFlash === slot;
            return (
              <div key={slot} className={`snap-slot ${isFilled ? "filled" : ""} ${isActive ? "active" : ""}`}
                onMouseDown={() => handlePointerDown(slot)}
                onTouchStart={() => handlePointerDown(slot)}
                onMouseUp={() => handlePointerUp(slot)}
                onTouchEnd={() => handlePointerUp(slot)}
                onMouseLeave={handlePointerLeave}
                onTouchCancel={handlePointerLeave}
                style={isFlashing ? { background: 'rgba(58,184,122,.15)' } : {}}>
                <div className="snap-num">{slot}</div>
                {isEditing ? (
                  <input className="snap-name-input" value={editName}
                    onChange={e => setEditName(e.target.value)}
                    onBlur={() => handleRename(slot)}
                    onKeyDown={e => { if (e.key === 'Enter') handleRename(slot); if (e.key === 'Escape') setEditingSlot(null); }}
                    autoFocus onClick={e => e.stopPropagation()} />
                ) : snap ? (
                  <div className="snap-name"
                    onClick={e => {
                      e.stopPropagation();
                      setEditingSlot(slot);
                      setEditName(snap.name);
                    }}>{snap.name}</div>
                ) : (
                  <div className="snap-empty">Empty</div>
                )}
                {isFlashing && <div className="snap-save-indicator" />}
                {isActive && <div style={{ position: "absolute", bottom: -2, left: "50%", transform: "translateX(-50%)",
                  width: 20, height: 2, borderRadius: 1, background: T.amber }} />}
              </div>
            );
          })}
        </div>

        {/* Delete button for active snapshot */}
        {currentSlot > 0 && snapshots[String(currentSlot)] && (
          <div style={{ marginTop: 8, display: "flex", gap: 8 }}>
            <button className="btn btn-danger btn-sm" style={{ flex: 1 }}
              onClick={() => handleDelete(currentSlot)}>
              Delete Snapshot {currentSlot}
            </button>
          </div>
        )}
      </div>
    </SlidePanel>
  );
}

// ══════════════════════════════════════════════════════════════
// APP SHELL
// ══════════════════════════════════════════════════════════════

const TABS = [
  { id: "rig", label: "Rig", icon: "🎛" },
  { id: "fx", label: "FX Chain", icon: "⛓" },
  { id: "models", label: "Models", icon: "📁" },
  { id: "irs", label: "IRs", icon: "🎚" },
  { id: "presets", label: "Presets", icon: "⭐" },
  { id: "settings", label: "Settings", icon: "⚙" },
  { id: "record", label: "Looper", icon: "🎙" },
];

export default function App() {
  const [tab, setTab] = useState("rig");
  const [showSnapshots, setShowSnapshots] = useState(false);
  const [channel, setChannelState] = useState(() => {
    try { return localStorage.getItem('pedal_channel') || 'guitar'; } catch { return 'guitar'; }
  });
  const { state, connected, refresh } = usePedalState();

  useEffect(() => { document.title = "Pi Multi-FX Pedal"; }, []);

  // Sync channel to localStorage + module-level API var
  useEffect(() => {
    try { localStorage.setItem('pedal_channel', channel); } catch (e) {}
    setChannel(channel);
  }, [channel]);

  const handleChannel = (ch) => {
    setChannelState(ch);
  };

  return (
    <>
      <style>{css}</style>
      <div style={{ width: "100%", maxWidth: 440, margin: "0 auto", height: "100dvh",
        display: "flex", flexDirection: "column", background: T.bg, overflow: "hidden",
        fontFamily: "'Inter', sans-serif" }}>

        {/* Status bar */}
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center",
          padding: "4px 10px", background: T.panel, borderBottom: `1px solid ${T.border}`, flexShrink: 0 }}>
          <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
            <div style={{ width: 6, height: 6, borderRadius: "50%",
              background: connected ? T.green : T.red,
              boxShadow: connected ? `0 0 6px ${T.green}` : "none" }} />
            {/* Channel toggle — hidden in stereo mode */}
            {state?.channel_mode === 'stereo' ? (
              <span className="badge badge-amber" style={{ fontSize: 9, padding: "2px 7px", letterSpacing: ".08em" }}>
                STEREO
              </span>
            ) : (
              <div className="toggle" style={{ margin: 0, height: 22 }}>
                <button className={`toggle-btn ${channel === 'guitar' ? 'active' : ''}`}
                  onClick={() => handleChannel('guitar')}
                  style={{ padding: '2px 8px', fontSize: 10, fontWeight: 700, letterSpacing: '.1em' }}>
                  GUITAR
                </button>
                <button className={`toggle-btn ${channel === 'bass' ? 'active' : ''}`}
                  onClick={() => handleChannel('bass')}
                  style={{ padding: '2px 8px', fontSize: 10, fontWeight: 700, letterSpacing: '.1em' }}>
                  BASS
                </button>
              </div>
            )}
            {state?.tuner_enabled && <span className="badge badge-amber" style={{ fontSize: 8, padding: "1px 5px" }}>TUNER</span>}
          </div>
          <div style={{ display: "flex", gap: 8, alignItems: "center" }}>
            {/* Snapshot camera icon */}
            <button className={`snap-header-btn ${showSnapshots ? "active" : ""}`}
              onClick={() => setShowSnapshots(true)}
              title="Snapshots">
              📷
              {state?.current_snapshot > 0 && (
                <span className="snap-header-num">{state.current_snapshot}</span>
              )}
            </button>
            {state?.cpu_percent != null && (
              <span className="mono" style={{ fontSize: 9, color: T.textDim }}>CPU {state.cpu_percent}%</span>
            )}
            {state?.sample_rate && (
              <span className="mono" style={{ fontSize: 9, color: T.textDim }}>{state.sample_rate/1000}kHz</span>
            )}
            {state?.bypass && <span className="badge badge-red" style={{ fontSize: 8, padding: "1px 5px" }}>BYP</span>}
            {state?.nam_loaded && <span className="badge badge-blue" style={{ fontSize: 8, padding: "1px 5px" }}>NAM</span>}
            {state?.ir_loaded && <span className="badge badge-green" style={{ fontSize: 8, padding: "1px 5px" }}>IR</span>}
          </div>
        </div>

        {/* Screen */}
        <div style={{ flex: 1, overflow: "hidden", display: "flex", flexDirection: "column" }}>
          {tab === "rig" && <RigScreen state={state} connected={connected} refresh={refresh} />}
          {tab === "fx" && <FXScreen state={state} connected={connected} />}
          {tab === "models" && <ModelsScreen state={state} connected={connected} refresh={refresh} />}
          {tab === "irs" && <IRsScreen state={state} connected={connected} refresh={refresh} />}
          {tab === "presets" && <PresetsScreen state={state} connected={connected} refresh={refresh} />}
          {tab === "settings" && <SettingsScreen state={state} connected={connected} refresh={refresh} />}
          {tab === "record" && <RecordScreen state={state} />}
        </div>

        {/* Tab bar */}
        <div className="tabbar">
          {TABS.map(t => (
            <div key={t.id} className={`tab ${tab === t.id ? "active" : ""}`} onClick={() => setTab(t.id)}>
              <span style={{ fontSize: 14, display: "block" }}>{t.icon}</span>
              {t.label}
            </div>
          ))}
        </div>

        {/* Snapshot panel overlay */}
        {showSnapshots && (
          <SnapshotPanel onClose={() => setShowSnapshots(false)} state={state} connected={connected} />
        )}
      </div>
    </>
  );
}
