/* watchlist.jsx — watchlist personale per-utente (stile Investing).
   3 schermate: gruppi → lista ticker (prezzo + Δ% live) → dettaglio grafico.
   Prezzi live via /api/quotes (WebSocket Finnhub lato server). Avatar =
   monogramma del ticker (scelta utente 12/6: niente loghi → niente chiamate
   profile2 dietro il throttle, zero rallentamenti). Drag&drop dei gruppi.
   FUORI dal forward-test freeze: solo visualizzazione, nessun segnale. */

(function () {
  const { useState, useEffect, useRef } = React;

  // ── helper API ──────────────────────────────────────────────────────────
  const _api = (method, path, body) =>
    fetch(path, {
      method,
      headers: body ? { "Content-Type": "application/json" } : undefined,
      body: body ? JSON.stringify(body) : undefined,
    }).then(r => r.json());

  // conversione leva3 (l'utente opera a leva3: la % reale triplica)
  const _lev3 = (dp) => (dp == null ? "" : (dp * 3 >= 0 ? "+" : "") + (dp * 3).toFixed(1) + "%");

  // ── hook: prezzi live (poll /api/quotes mentre la schermata è attiva) ─────
  function useLiveQuotes(symbols, active) {
    const [quotes, setQuotes] = useState({});
    const [ws, setWs] = useState("off");
    const [mktOpen, setMktOpen] = useState(false);
    const key = symbols.join(",");
    const ref = useRef(symbols);
    ref.current = symbols;

    useEffect(() => {
      if (!active || !symbols.length) return;
      let stop = false;
      const poll = () => {
        if (document.visibilityState !== "visible") return;
        const s = ref.current;
        if (!s.length) return;
        fetch(`/api/quotes?symbols=${encodeURIComponent(s.join(","))}`)
          .then(r => r.json())
          .then(d => {
            if (stop) return;
            setQuotes(d.quotes || {});
            setWs(d.ws || "off");
            setMktOpen(!!d.market_open);
          })
          .catch(() => {});
      };
      poll();
      const t = setInterval(poll, 2500);
      const onVis = () => { if (document.visibilityState === "visible") poll(); };
      document.addEventListener("visibilitychange", onVis);
      return () => { stop = true; clearInterval(t); document.removeEventListener("visibilitychange", onVis); };
    }, [active, key]);

    return { quotes, ws, mktOpen };
  }

  // ── hook: nomi società (risoluzione async lato server, con retry) ─────────
  function useMeta(symbols) {
    const [meta, setMeta] = useState({});
    const key = symbols.join(",");
    useEffect(() => {
      if (!symbols.length) return;
      let stop = false, tries = 0;
      const load = () => {
        fetch(`/api/symbol-meta?symbols=${encodeURIComponent(symbols.join(","))}`)
          .then(r => r.json())
          .then(d => {
            if (stop) return;
            setMeta(d.meta || {});
            const pending = symbols.some(s => !d.meta?.[s]?.name || d.meta[s].name === s);
            tries++;
            if (pending && tries < 3) setTimeout(load, 3000);
          })
          .catch(() => {});
      };
      load();
      return () => { stop = true; };
    }, [key]);
    return meta;
  }

  // ── avatar: monogramma del ticker (ZERO rete, niente loghi) ───────────────
  function MonoAvatar({ symbol, size = 34 }) {
    const hue = [...symbol].reduce((a, c) => a + c.charCodeAt(0), 0) % 360;
    return (
      <div className="wl-avatar wl-avatar-mono"
           style={{ width: size, height: size, fontSize: size * 0.36,
                    background: `hsl(${hue} 42% 20%)`, color: `hsl(${hue} 72% 72%)` }}>
        {symbol.slice(0, 2)}
      </div>
    );
  }

  // ── modale di conferma in stile sito (sostituisce window.confirm) ─────────
  function WlConfirm({ title, text, confirmLabel = "Elimina", onCancel, onConfirm }) {
    return (
      <div className="wl-modal-backdrop" onClick={onCancel}>
        <div className="wl-modal card" onClick={e => e.stopPropagation()}>
          <div className="wl-modal-title">{title}</div>
          {text && <div className="wl-modal-text">{text}</div>}
          <div className="wl-modal-actions">
            <button className="btn btn-sm btn-ghost" onClick={onCancel}>Annulla</button>
            <button className="btn btn-sm btn-danger" onClick={onConfirm}>{confirmLabel}</button>
          </div>
        </div>
      </div>
    );
  }

  // ── pillola Δ% (verde/rosso) ──────────────────────────────────────────────
  function DeltaPill({ dp }) {
    if (dp == null) return <span className="wl-dp muted">—</span>;
    const cls = dp >= 0 ? "pos" : "neg";
    return <span className={`wl-dp ${cls}`}>{(dp >= 0 ? "▲ " : "▼ ") + Math.abs(dp).toFixed(2) + "%"}</span>;
  }

  // ── riga ticker (prezzo live + pulse) ─────────────────────────────────────
  function WlTickerRow({ symbol, name, q, onOpen, onRemove }) {
    const price = q?.price;
    const dp = q?.dp;
    const live = q?.live;
    const prev = useRef(price);
    const [flash, setFlash] = useState("");
    useEffect(() => {
      if (live && price != null && prev.current != null && price !== prev.current) {
        setFlash(price > prev.current ? "flash-up" : "flash-down");
        const t = setTimeout(() => setFlash(""), 650);
        prev.current = price;
        return () => clearTimeout(t);
      }
      prev.current = price;
    }, [price, live]);

    return (
      <div className="wl-trow">
        <div className="wl-trow-main" onClick={onOpen}>
          <MonoAvatar symbol={symbol} />
          <div className="wl-trow-id">
            <span className="wl-trow-name">{name || symbol}</span>
            <span className="wl-trow-sym">{symbol}{live && <span className="wl-livedot" title="Live" />}</span>
          </div>
        </div>
        <div className={`wl-trow-px ${flash}`} onClick={onOpen}>
          <span className="wl-trow-price">{price ? price.toFixed(2) : "—"}</span>
          <DeltaPill dp={dp} />
        </div>
        <button className="wl-trow-del" title="Rimuovi" onClick={onRemove}>
          <Icon name="x" size={13} />
        </button>
      </div>
    );
  }

  // ── lista ticker: ordinata per Δ% giornaliera (dal più alto al più basso),
  //    come "Relativa giornaliera" di Investing. Senza Δ% (quote non ancora
  //    arrivata) → in coda, nell'ordine di inserimento.
  function WlTickerList({ tickers, quotes, meta, onOpen, onRemove }) {
    const sorted = [...tickers].sort((a, b) => {
      const da = quotes[a] ? quotes[a].dp : null;
      const db = quotes[b] ? quotes[b].dp : null;
      if (da == null && db == null) return 0;
      if (da == null) return 1;
      if (db == null) return -1;
      return db - da;
    });
    return (
      <div className="wl-tlist">
        {sorted.map(sym => (
          <WlTickerRow
            key={sym} symbol={sym} name={meta[sym]?.name} q={quotes[sym]}
            onOpen={() => onOpen(sym)}
            onRemove={(e) => { e.stopPropagation(); onRemove(sym); }}
          />
        ))}
      </div>
    );
  }

  // ── pannello "aggiungi ticker" (ricerca con debounce) ─────────────────────
  function WlAddPanel({ gid, existing, full, onAdded, onClose }) {
    const [q, setQ] = useState("");
    const [results, setResults] = useState([]);
    const [busy, setBusy] = useState(false);
    const [err, setErr] = useState("");
    const inputRef = useRef(null);
    useEffect(() => { inputRef.current?.focus(); }, []);
    useEffect(() => {
      if (!q.trim()) { setResults([]); return; }
      const t = setTimeout(() => {
        fetch(`/api/symbol-search?q=${encodeURIComponent(q.trim())}`)
          .then(r => r.json()).then(d => setResults(d.results || [])).catch(() => setResults([]));
      }, 300);
      return () => clearTimeout(t);
    }, [q]);

    const add = (sym) => {
      setBusy(true); setErr("");
      _api("POST", `/api/watchlist/groups/${gid}/tickers`, { symbol: sym })
        .then(d => {
          if (d.groups) { onAdded(d.groups); setQ(""); }
          else if (d.error) setErr(d.error);
        })
        .finally(() => setBusy(false));
    };

    return (
      <div className="wl-addpanel card">
        <div className="wl-addpanel-head">
          <strong>Aggiungi ticker</strong>
          <button className="btn btn-sm btn-ghost" onClick={onClose}>✕ Chiudi</button>
        </div>
        {full ? (
          <div className="wl-full-hint">Gruppo pieno (50). Rimuovi un ticker per aggiungerne un altro.</div>
        ) : (
          <>
            <input ref={inputRef} className="input" placeholder="Ticker o nome: ARM, Bloom Energy, CoreWeave…"
                   value={q} onChange={e => setQ(e.target.value)} />
            {err && <div className="wl-full-hint">{err}</div>}
            <div className="wl-search-results">
              {results.map(r => {
                const have = existing.includes(r.symbol);
                return (
                  <button key={r.symbol} className="wl-search-row" disabled={busy || have}
                          onClick={() => add(r.symbol)}>
                    <MonoAvatar symbol={r.symbol} size={26} />
                    <div className="wl-search-id">
                      <span className="wl-search-sym">{r.symbol}</span>
                      <span className="wl-search-desc">{r.description}</span>
                    </div>
                    <span className="wl-search-add">{have ? "già presente" : "＋"}</span>
                  </button>
                );
              })}
              {q.trim() && results.length === 0 &&
                <div className="wl-full-hint">Nessun risultato per «{q.trim()}».</div>}
            </div>
          </>
        )}
      </div>
    );
  }

  // ── schermata: dettaglio ticker (grafico) ─────────────────────────────────
  const _TFS = ["1G", "1S", "1M", "1A", "Max"];

  function WlChart({ symbol, tf, livePrice, live }) {
    const containerRef = useRef(null);
    const chartRef = useRef(null);
    const seriesRef = useRef(null);
    const cacheRef = useRef({});
    const lastTimeRef = useRef(null);    // time dell'ultimo punto (per il live update)
    const firstValRef = useRef(null);    // primo valore (per ricolorare verde/rosso live)
    const [loading, setLoading] = useState(false);
    const [empty, setEmpty] = useState(false);

    useEffect(() => {
      if (!containerRef.current || !window.LightweightCharts) return;
      const container = containerRef.current;
      let chart;
      try {
        chart = LightweightCharts.createChart(container, {
          width: Math.max(100, container.clientWidth),
          height: Math.max(100, container.clientHeight),
          layout: { background: { color: "transparent" }, textColor: "#8892a4", fontFamily: "JetBrains Mono, monospace" },
          grid: { vertLines: { color: "rgba(255,255,255,0.02)" }, horzLines: { color: "rgba(255,255,255,0.05)" } },
          crosshair: {
            mode: LightweightCharts.CrosshairMode.Normal,
            vertLine: { color: "rgba(150,164,196,0.35)", width: 1, style: LightweightCharts.LineStyle.Dashed, labelBackgroundColor: "#2b3442" },
            horzLine: { color: "rgba(150,164,196,0.35)", width: 1, style: LightweightCharts.LineStyle.Dashed, labelBackgroundColor: "#2b3442" },
          },
          rightPriceScale: { borderColor: "rgba(255,255,255,0.07)" },
          timeScale: { borderColor: "rgba(255,255,255,0.07)", timeVisible: true, secondsVisible: false },
        });
      } catch (e) { console.warn("WlChart init failed", e); return; }
      chart.priceScale("right").applyOptions({ scaleMargins: { top: 0.12, bottom: 0.12 } });
      const series = chart.addAreaSeries({
        lineColor: "#3b82f6", topColor: "rgba(59,130,246,0.22)", bottomColor: "rgba(59,130,246,0)",
        lineWidth: 2, priceLineVisible: false, lastValueVisible: true,
      });
      chartRef.current = chart; seriesRef.current = series;
      const ro = new ResizeObserver(() => chart.applyOptions({ width: container.clientWidth, height: container.clientHeight }));
      ro.observe(container);
      return () => { ro.disconnect(); chart.remove(); chartRef.current = null; };
    }, []);

    const _recolor = (up) => {
      seriesRef.current.applyOptions({
        lineColor: up ? "#22c55e" : "#ef4444",
        topColor: up ? "rgba(34,197,94,0.20)" : "rgba(239,68,68,0.20)",
        bottomColor: up ? "rgba(34,197,94,0)" : "rgba(239,68,68,0)",
      });
    };

    useEffect(() => {
      if (!chartRef.current) return;
      let cancelled = false;
      const apply = (candles) => {
        if (!candles || !candles.length) { setEmpty(true); seriesRef.current.setData([]); return; }
        setEmpty(false);
        const data = candles.map(c => ({ time: c.time, value: c.close }));
        lastTimeRef.current = data[data.length - 1].time;
        firstValRef.current = data[0].value;
        _recolor(data[data.length - 1].value >= data[0].value);
        seriesRef.current.setData(data);
        chartRef.current.timeScale().fitContent();
      };
      const ck = `${symbol}|${tf}`;
      const load = (force) => {
        if (!force && cacheRef.current[ck]) { apply(cacheRef.current[ck]); return; }
        if (!force) setLoading(true);
        fetch(`/api/wl-candles/${encodeURIComponent(symbol)}?tf=${tf}`)
          .then(r => r.json())
          .then(d => {
            const c = d.candles || [];
            if (force && !c.length) return;   // hiccup yfinance: non svuotare il grafico
            cacheRef.current[ck] = c;
            if (!cancelled) apply(c);
          })
          .catch(e => { console.warn("wl-candles", e); if (!force) setEmpty(true); })
          .finally(() => { if (!force) setLoading(false); });
      };
      load(false);
      // refetch live: su 1G/1S nuove candele 5m/30m ogni minuto (TTL backend 60s)
      let t = null;
      if (tf === "1G" || tf === "1S") t = setInterval(() => load(true), 60000);
      return () => { cancelled = true; if (t) clearInterval(t); };
    }, [symbol, tf]);

    // live: l'ultimo punto della linea segue il prezzo del WebSocket (come Investing)
    useEffect(() => {
      if (!seriesRef.current || !live || livePrice == null || !lastTimeRef.current) return;
      try {
        seriesRef.current.update({ time: lastTimeRef.current, value: livePrice });
        if (firstValRef.current != null) _recolor(livePrice >= firstValRef.current);
      } catch (e) { console.warn("wl-chart live update:", e); }
    }, [livePrice, live]);

    return (
      <div ref={containerRef} style={{ position: "absolute", inset: 0 }}>
        {loading && <div className="wl-chart-msg">Caricamento…</div>}
        {empty && !loading && <div className="wl-chart-msg">Nessun dato disponibile per {symbol}.</div>}
      </div>
    );
  }

  function WlTickerScreen({ symbol, onBack }) {
    const [tf, setTf] = useState("1G");
    const { quotes } = useLiveQuotes([symbol], true);
    const meta = useMeta([symbol]);
    const name = meta[symbol]?.name;
    const q = quotes[symbol] || {};
    const dp = q.dp;
    return (
      <div className="view-scroll">
        <div className="wl-detail-head">
          <button className="btn btn-sm btn-ghost" onClick={onBack}>‹ Indietro</button>
        </div>
        <div className="wl-detail-id">
          <MonoAvatar symbol={symbol} size={44} />
          <div>
            <div className="wl-detail-name">{name || symbol}</div>
            <div className="wl-detail-sym">{symbol}</div>
          </div>
          <div className="wl-detail-px">
            <div className="wl-detail-price">{q.price ? q.price.toFixed(2) : "—"}</div>
            <DeltaPill dp={dp} />
          </div>
        </div>
        {dp != null && (
          <div className="wl-lev3-note">
            Con la tua <b>leva3</b> oggi: <b className={dp >= 0 ? "pos" : "neg"}>{_lev3(dp)}</b>
            <span className="muted"> (={Math.abs(dp).toFixed(2)}% × 3)</span>
          </div>
        )}
        <div className="wl-tf-row">
          {_TFS.map(t => (
            <button key={t} className={`wl-tf ${tf === t ? "active" : ""}`} onClick={() => setTf(t)}>{t}</button>
          ))}
        </div>
        <div className="wl-chart-box">
          <WlChart symbol={symbol} tf={tf} livePrice={q.live ? q.price : null} live={!!q.live} />
        </div>
        <div className="muted" style={{ fontSize: 11.5, marginTop: 8 }}>
          Prezzi USA in tempo reale durante l'orario di mercato (Finnhub). Grafico via yfinance.
        </div>
      </div>
    );
  }

  // ── schermata: gruppo (lista ticker) ──────────────────────────────────────
  function WlGroupScreen({ group, onBack, onOpenTicker, setGroups }) {
    const [showAdd, setShowAdd] = useState(false);
    const [editing, setEditing] = useState(false);
    const [nm, setNm] = useState(group.name);
    const tickers = group.tickers || [];
    const { quotes, ws, mktOpen } = useLiveQuotes(tickers, true);
    const meta = useMeta(tickers);

    const removeTicker = (sym) =>
      _api("DELETE", `/api/watchlist/groups/${group.id}/tickers/${encodeURIComponent(sym)}`)
        .then(d => d.groups && setGroups(d.groups));
    const rename = () => {
      const v = nm.trim();
      if (!v || v === group.name) { setEditing(false); return; }
      _api("PATCH", `/api/watchlist/groups/${group.id}`, { name: v })
        .then(d => { if (d.groups) setGroups(d.groups); setEditing(false); });
    };
    const wsLabel = !tickers.length ? "" :
      (ws === "connected" && mktOpen) ? "● live" :
      mktOpen ? "in connessione…" : "mercato chiuso · ultimo prezzo";

    return (
      <div className="view-scroll">
        <div className="wl-grp-head">
          <button className="btn btn-sm btn-ghost" onClick={onBack}>‹ Liste</button>
          {editing ? (
            <input className="input wl-rename" value={nm} autoFocus
                   onChange={e => setNm(e.target.value)}
                   onKeyDown={e => e.key === "Enter" && rename()} onBlur={rename} />
          ) : (
            <h3 className="wl-grp-title" onClick={() => { setNm(group.name); setEditing(true); }} title="Clicca per rinominare">
              {group.name} <span className="muted">({tickers.length})</span>
            </h3>
          )}
          <div className="wl-grp-actions">
            <span className={`wl-ws ${ws === "connected" && mktOpen ? "on" : ""}`}>{wsLabel}</span>
            <button className={"btn btn-sm " + (showAdd ? "btn-ghost" : "btn-primary")}
                    onClick={() => setShowAdd(v => !v)}>{showAdd ? "✕" : "＋ Ticker"}</button>
          </div>
        </div>

        {showAdd && (
          <WlAddPanel gid={group.id} existing={tickers} full={tickers.length >= 50}
                      onAdded={(g) => setGroups(g)} onClose={() => setShowAdd(false)} />
        )}

        {tickers.length === 0 ? (
          <div className="card muted wl-empty">
            Nessun ticker in questa lista. Premi <b>＋ Ticker</b> per cercarne e aggiungerne.
          </div>
        ) : (
          <>
            <WlTickerList tickers={tickers} quotes={quotes} meta={meta}
                          onOpen={onOpenTicker} onRemove={removeTicker} />
            <div className="muted" style={{ fontSize: 11.5, marginTop: 8 }}>
              Ordinati per variazione giornaliera (dal più alto al più basso).
            </div>
          </>
        )}
      </div>
    );
  }

  // ── schermata: elenco gruppi (riordinabili con drag&drop dalla maniglia) ──
  function WlGroupsScreen({ groups, onOpen, setGroups }) {
    const [newName, setNewName] = useState("");
    const [creating, setCreating] = useState(false);
    const [confirmDel, setConfirmDel] = useState(null);   // {id, name} | null
    const [cards, setCards] = useState(groups);
    const [dragIdx, setDragIdx] = useState(-1);
    const gridRef = useRef(null);
    useEffect(() => { if (dragIdx < 0) setCards(groups); }, [groups]);

    // ri-fetch di sicurezza: se la risposta di una mutazione si perde
    // (es. proxy che mangia il body della DELETE), lo stato si riallinea comunque
    const refresh = () =>
      _api("GET", "/api/watchlist/groups").then(d => d.groups && setGroups(d.groups)).catch(() => {});

    const create = () => {
      const v = newName.trim();
      if (!v || creating) return;
      setCreating(true);
      _api("POST", "/api/watchlist/groups", { name: v })
        .then(d => { if (d.groups) { setGroups(d.groups); setNewName(""); } })
        .finally(() => setCreating(false));
    };
    const doDelete = () => {
      const gid = confirmDel?.id;
      setConfirmDel(null);
      if (!gid) return;
      setCards(prev => prev.filter(g => g.id !== gid));   // ottimistico: sparisce subito
      _api("DELETE", `/api/watchlist/groups/${gid}`)
        .then(d => { if (d.groups) setGroups(d.groups); else refresh(); })
        .catch(refresh);
    };

    // drag&drop card gruppo: hit-test sui rect della grid (funziona anche multi-colonna)
    const down = (e, idx) => {
      e.preventDefault(); e.stopPropagation();
      setDragIdx(idx);
      try { e.currentTarget.setPointerCapture(e.pointerId); } catch (_) {}
    };
    const move = (e) => {
      if (dragIdx < 0) return;
      const els = gridRef.current ? [...gridRef.current.querySelectorAll(".wl-grp-card")] : [];
      let ti = -1;
      els.forEach((el, i) => {
        const r = el.getBoundingClientRect();
        if (e.clientX >= r.left && e.clientX <= r.right && e.clientY >= r.top && e.clientY <= r.bottom) ti = i;
      });
      if (ti >= 0 && ti !== dragIdx) {
        setCards(prev => {
          const a = prev.slice();
          const [it] = a.splice(dragIdx, 1);
          a.splice(ti, 0, it);
          return a;
        });
        setDragIdx(ti);
      }
    };
    const up = (e) => {
      if (dragIdx < 0) return;
      try { e.currentTarget.releasePointerCapture(e.pointerId); } catch (_) {}
      setDragIdx(-1);
      const ids = cards.map(g => g.id);
      if (ids.join(",") !== groups.map(g => g.id).join(","))
        _api("POST", "/api/watchlist/groups/reorder", { order: ids })
          .then(d => d.groups && setGroups(d.groups));
    };

    return (
      <div className="view-scroll">
        <div className="wl-groups-head">
          <span style={{ fontWeight: 600, fontSize: 16 }}>Le tue liste</span>
          <span className="muted" style={{ fontSize: 13 }}>{cards.length} {cards.length === 1 ? "lista" : "liste"}</span>
        </div>

        <div className="wl-groups-grid" ref={gridRef}>
          {cards.map((g, idx) => (
            <div key={g.id} className={"wl-grp-card" + (idx === dragIdx ? " dragging" : "")}
                 onClick={() => dragIdx < 0 && onOpen(g.id)}>
              <div className="wl-grp-card-top">
                <span className="wl-drag" title="Trascina per riordinare"
                      onClick={e => e.stopPropagation()}
                      onPointerDown={e => down(e, idx)} onPointerMove={move}
                      onPointerUp={up} onPointerCancel={up}>
                  <Icon name="menu" size={14} />
                </span>
                <span className="wl-grp-card-name">{g.name}</span>
                <button className="wl-grp-card-del" title="Elimina"
                        onClick={(e) => { e.stopPropagation(); setConfirmDel({ id: g.id, name: g.name }); }}>
                  <Icon name="x" size={12} />
                </button>
              </div>
              <div className="wl-grp-card-meta">
                {(g.tickers || []).length} {(g.tickers || []).length === 1 ? "titolo" : "titoli"}
              </div>
              <div className="wl-grp-card-syms">
                {(g.tickers || []).slice(0, 6).map(s => <span key={s} className="wl-grp-chip">{s}</span>)}
                {(g.tickers || []).length > 6 && <span className="wl-grp-chip more">+{g.tickers.length - 6}</span>}
              </div>
            </div>
          ))}
        </div>

        <div className="wl-newgroup card">
          <input className="input" placeholder="Nome nuova lista (es. USA Tech)"
                 value={newName} onChange={e => setNewName(e.target.value)}
                 onKeyDown={e => e.key === "Enter" && create()} />
          <button className="btn btn-primary" onClick={create} disabled={!newName.trim() || creating}>
            {creating ? "…" : "＋ Crea lista"}
          </button>
        </div>

        <div className="muted" style={{ fontSize: 12, marginTop: 12 }}>
          Watchlist personale: ogni utente ha le sue liste. Prezzi USA in tempo reale durante
          l'orario di mercato. Max 50 ticker per lista.
        </div>

        {confirmDel && (
          <WlConfirm title={`Eliminare la lista «${confirmDel.name}»?`}
                     text="I ticker contenuti verranno rimossi dalla watchlist. L'azione non è reversibile."
                     onCancel={() => setConfirmDel(null)} onConfirm={doDelete} />
        )}
      </div>
    );
  }

  // ── vista principale ──────────────────────────────────────────────────────
  window.WatchlistView = function WatchlistView() {
    const [groups, setGroups] = useState(null);   // null = caricamento
    const [screen, setScreen] = useState("groups");
    const [openGid, setOpenGid] = useState(null);
    const [openSym, setOpenSym] = useState(null);
    const [err, setErr] = useState(null);

    useEffect(() => {
      _api("GET", "/api/watchlist/groups")
        .then(d => { if (d.error) setErr(d.error); else setGroups(d.groups || []); })
        .catch(e => setErr(String(e)));
    }, []);

    if (err) return <div className="view-scroll"><div className="card">Errore: {err}</div></div>;
    if (groups === null) return <div className="view-scroll"><div className="card muted">Caricamento…</div></div>;

    const openGroup = groups.find(g => g.id === openGid);

    if (screen === "ticker" && openSym) {
      return <WlTickerScreen symbol={openSym}
                onBack={() => setScreen(openGid ? "group" : "groups")} />;
    }
    if (screen === "group" && openGroup) {
      return <WlGroupScreen group={openGroup}
                onBack={() => { setScreen("groups"); setOpenGid(null); }}
                onOpenTicker={(sym) => { setOpenSym(sym); setScreen("ticker"); }}
                setGroups={setGroups} />;
    }
    return <WlGroupsScreen groups={groups} setGroups={setGroups}
              onOpen={(gid) => { setOpenGid(gid); setScreen("group"); }} />;
  };
})();
