// Calendar, Report, Evaluate, Config, Logs, Admin views
// Note: FilterSelect, FilterDateChips, FilterDateInput are defined in shell.jsx (loads first)

// ────────── Calendar ──────────
function sentimentArrows(s) {
  if (s >= 3) return { text: "↑↑↑", color: "var(--green)" };
  if (s === 2) return { text: "↑↑",  color: "var(--green)" };
  if (s === 1) return { text: "↑",   color: "var(--green)" };
  if (s === 0) return { text: "—",   color: "var(--text-faint)" };
  if (s === -1) return { text: "↓",  color: "var(--red)" };
  if (s === -2) return { text: "↓↓", color: "var(--red)" };
  return { text: "↓↓↓", color: "var(--red)" };
}

// Posiziona il tooltip evitando sidebar fissa (240px o 64px collassata)
// e fuori-schermo. Se nessun lato ha spazio (caso agenda full-width),
// posiziona SOTTO la riga centrato e clampato.
function positionTooltip(rect, tipW = 290, tipH = 240) {
  const vw = window.innerWidth;
  const vh = window.innerHeight;
  const sidebarEl = document.querySelector(".sidebar");
  const sidebarW = (sidebarEl && vw > 760) ? (sidebarEl.offsetWidth || 240) : 0;
  const margin = 12;

  // Prova: a destra della riga
  if (rect.right + 8 + tipW <= vw - margin) {
    return { x: rect.right + 8, y: Math.min(rect.top, vh - tipH - 16) };
  }
  // Prova: a sinistra (rispettando sidebar)
  if (rect.left - 8 - tipW >= sidebarW + margin) {
    return { x: rect.left - tipW - 8, y: Math.min(rect.top, vh - tipH - 16) };
  }
  // Fallback: sotto la riga, centrato e clampato
  const xCentered = rect.left + rect.width / 2 - tipW / 2;
  const xClamped  = Math.max(sidebarW + margin, Math.min(xCentered, vw - tipW - margin));
  const yBelow    = rect.bottom + 8;
  // Se sotto non c'è spazio verticale, mettilo sopra
  const y = yBelow + tipH > vh - 16 ? Math.max(16, rect.top - tipH - 8) : yBelow;
  return { x: xClamped, y };
}

function CalendarView() {
  const today = new Date();
  const [view, setView]               = useState({ y: today.getFullYear(), m: today.getMonth() });
  const [clock, setClock]             = useState(new Date());
  const [selectedDay, setSelectedDay] = useState(null); // { label, key, evs }
  const [tooltip, setTooltip]         = useState(null); // { event, dateKey, x, y }
  const [calEvents, setCalEvents]     = useState(window.CALENDAR_EVENTS || {});
  const [refreshStatus, setRefreshStatus] = useState(null); // null | "loading" | "ok" | "err"
  const [showDiscovery, setShowDiscovery] = useState(false); // mostra notizie IPO/Spinoff

  const refreshCalendar = async () => {
    setRefreshStatus("loading");
    try {
      // 1. Avvia re-fetch Tool5 in background (IPO/spinoff news + eventi ticker)
      await fetch("/api/calendar/run-now", { method: "POST" });
      // 2. Aspetta ~35s poi ricarica i dati dal DB
      setTimeout(async () => {
        try {
          const r = await fetch("/api/calendar-events").then(r => r.json());
          if (r.events) {
            window.CALENDAR_EVENTS = r.events;
            setCalEvents(r.events);
          }
          setRefreshStatus("ok");
          setTimeout(() => setRefreshStatus(null), 3000);
        } catch { setRefreshStatus("err"); }
      }, 35000);
    } catch { setRefreshStatus("err"); }
  };

  // Vista: Griglia mensile (overview) o Agenda cronologica (lista).
  // Sotto i 1200px la griglia diventa illeggibile → forziamo agenda automaticamente.
  const [viewMode, setViewMode]       = useState(() => {
    try {
      const saved = localStorage.getItem("calViewMode");
      return saved === "agenda" ? "agenda" : "grid";
    } catch { return "grid"; }
  });
  const [forcedAgenda, setForcedAgenda] = useState(
    typeof window !== "undefined" && window.innerWidth < 1500
  );

  useEffect(() => {
    const t = setInterval(() => setClock(new Date()), 1000);
    return () => clearInterval(t);
  }, []);

  useEffect(() => {
    const onResize = () => setForcedAgenda(window.innerWidth < 1500);
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, []);

  const effectiveMode = forcedAgenda ? "agenda" : viewMode;
  const onSetViewMode = (m) => {
    setViewMode(m);
    try { localStorage.setItem("calViewMode", m); } catch {}
  };

  // Close day panel when month changes
  useEffect(() => { setSelectedDay(null); }, [view]);

  const firstDay  = new Date(view.y, view.m, 1);
  const lastDay   = new Date(view.y, view.m + 1, 0);
  const startWd   = (firstDay.getDay() + 6) % 7;
  const monthName = firstDay.toLocaleDateString("it-IT", { month: "long", year: "numeric" });

  const cells = [];
  const prevLast = new Date(view.y, view.m, 0).getDate();
  for (let i = startWd - 1; i >= 0; i--) cells.push({ d: prevLast - i, dim: true,  y: view.y, m: view.m - 1 });
  for (let d = 1; d <= lastDay.getDate(); d++) cells.push({ d, dim: false, y: view.y, m: view.m });
  while (cells.length % 7 !== 0) cells.push({ d: cells.length - (startWd + lastDay.getDate()) + 1, dim: true, y: view.y, m: view.m + 1 });

  const dayKey = c => {
    const rm = ((c.m % 12) + 12) % 12;
    const ry = c.y + Math.floor(c.m / 12);
    return `${ry}-${String(rm+1).padStart(2,"0")}-${String(c.d).padStart(2,"0")}`;
  };
  const isToday = c => !c.dim && c.d === today.getDate() && c.m === today.getMonth() && c.y === today.getFullYear();

  const rawEvents = calEvents;

  // Filtra eventi: se showDiscovery=false, nasconde notizie IPO/Spinoff (sim. senza data reale)
  const _DISCOVERY_SYMS = new Set(["IPO", "SPINOFF"]);
  const events = {};
  for (const [k, evs] of Object.entries(rawEvents)) {
    const filtered = evs.filter(e => showDiscovery || !_DISCOVERY_SYMS.has(e.sym));
    if (filtered.length) events[k] = filtered;
  }

  const TYPE_CONFIG = {
    earnings:     { color: "var(--teal)",        label: "Risultati" },
    ex_dividend:  { color: "var(--amber)",       label: "Stacco Div." },
    dividend:     { color: "var(--green)",       label: "Dividendo" },
    split:        { color: "var(--violet)",      label: "Frazionamento" },
    ipo:          { color: "var(--red)",         label: "IPO" },
    spinoff:      { color: "#f97316",            label: "Spin-off" },
    macro:        { color: "#8b9cf4",            label: "Macro/Fed" },
    other:        { color: "var(--text-faint)",  label: "Altro" },
  };
  const typeColor = (t) => (TYPE_CONFIG[t] || TYPE_CONFIG.other).color;

  const openDay = (c) => {
    const key = dayKey(c);
    const evs = events[key] || [];
    if (!evs.length) return;
    const rm = ((c.m % 12) + 12) % 12;
    const ry = c.y + Math.floor(c.m / 12);
    const label = new Date(ry, rm, c.d).toLocaleDateString("it-IT", {
      weekday: "long", day: "numeric", month: "long"
    });
    setSelectedDay({ label, key, evs });
  };

  return (
    <div className="cal-page" data-screen-label="03 Calendario">

      {/* ── Top bar ── */}
      <div className="cal-topbar">
        <div className="cal-brand">
          <div className="sb-mark" style={{width:26,height:26,fontSize:12}}><Icon name="calendar" size={13}/></div>
          <span style={{fontWeight:600, fontSize:13.5}}>BVF Financial Calendar</span>
        </div>
        {!showDiscovery && (
          <div className="cal-nav">
            <button className="icon-btn" onClick={()=>setView(v=>({y:v.y,m:v.m-1}))}><Icon name="arrowLeft" size={14}/></button>
            <span className="cal-month-label">{monthName}</span>
            <button className="icon-btn" onClick={()=>setView(v=>({y:v.y,m:v.m+1}))}><Icon name="arrowRight" size={14}/></button>
          </div>
        )}
        <div className="cal-topbar-right">
          {!forcedAgenda && !showDiscovery && (
            <div className="cal-view-toggle">
              <button className={effectiveMode === "grid"   ? "active" : ""} onClick={() => onSetViewMode("grid")}>Griglia</button>
              <button className={effectiveMode === "agenda" ? "active" : ""} onClick={() => onSetViewMode("agenda")}>Agenda</button>
            </div>
          )}
          {/* Toggle notizie senza data specifica (IPO / Spin-off discovery) */}
          <button
            className={"btn btn-sm" + (showDiscovery ? " active" : "")}
            onClick={() => setShowDiscovery(v => !v)}
            title={showDiscovery ? "Nascondi notizie IPO/Spin-off" : "Mostra notizie IPO/Spin-off imminenti"}
            style={showDiscovery ? {background:"var(--accent)", color:"#fff", borderColor:"var(--accent)"} : {}}>
            <Icon name="spark" size={13}/>
            {showDiscovery ? " ← Calendario classico" : " IPO / Spin-off"}
          </button>
          <button className="btn btn-sm" onClick={()=>setView({y:today.getFullYear(),m:today.getMonth()})}>Oggi</button>
          <button className="btn btn-sm" onClick={refreshCalendar} disabled={refreshStatus === "loading"}
            title="Ri-fetcha eventi Tool5 (IPO, spin-off, earnings) e aggiorna il calendario">
            <Icon name="refresh" size={13}/>
            {refreshStatus === "loading" ? " …" : refreshStatus === "ok" ? " ✓" : " Aggiorna"}
          </button>
          <span className="mono" style={{fontSize:12, color:"var(--text-muted)", minWidth:54}}>{clock.toLocaleTimeString("it-IT")}</span>
        </div>
      </div>

      {/* ── Vista Discovery (IPO / Spin-off) ── */}
      {showDiscovery && (() => {
        const discoveryEvs = Object.values(calEvents).flat()
          .filter(e => _DISCOVERY_SYMS.has(e.sym));
        const ipoEvs     = discoveryEvs.filter(e => e.type === "ipo");
        const spinoffEvs = discoveryEvs.filter(e => e.type === "spinoff");

        const renderGroup = (label, color, evs) => evs.length === 0 ? null : (
          <div style={{marginBottom: 24}}>
            <div style={{display:"flex", alignItems:"center", gap: 8, marginBottom: 10}}>
              <span style={{width:10, height:10, borderRadius:"50%", background: color, flexShrink:0, display:"inline-block"}}/>
              <span style={{fontWeight:700, fontSize:14}}>{label}</span>
              <span style={{fontSize:12, color:"var(--text-muted)"}}>({evs.length})</span>
            </div>
            <div style={{display:"flex", flexDirection:"column", gap:6}}>
              {evs.map((e, i) => {
                let detail = {};
                try { detail = JSON.parse(e.detail || "{}"); } catch {}
                return (
                  <div key={i} style={{
                    background:"var(--surface)", border:"1px solid var(--border)",
                    borderRadius:8, padding:"10px 14px",
                    borderLeft: `3px solid ${color}`,
                  }}>
                    <div style={{fontSize:13, fontWeight:600, lineHeight:1.4, marginBottom:4}}>
                      {e.title}
                    </div>
                    <div style={{fontSize:11, color:"var(--text-muted)", display:"flex", gap:12, flexWrap:"wrap"}}>
                      {detail.publisher && <span>{detail.publisher}</span>}
                      {detail.url && (
                        <a href={detail.url} target="_blank" rel="noopener noreferrer"
                           style={{color:"var(--accent)", textDecoration:"none"}}
                           onClick={ev => ev.stopPropagation()}>
                          Leggi articolo →
                        </a>
                      )}
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        );

        return (
          <div style={{padding:"20px 20px 32px", overflowY:"auto", flex:1, minHeight:0}}>
            <p style={{fontSize:12, color:"var(--text-muted)", marginBottom:20}}>
              Notizie recenti su IPO imminenti e spin-off — aggiornate giornalmente da Yahoo Finance. Senza data precisa di quotazione.
            </p>
            {discoveryEvs.length === 0 ? (
              <div style={{textAlign:"center", color:"var(--text-muted)", padding:32}}>
                Nessuna notizia disponibile. Premi <strong>Aggiorna</strong> per caricare.
              </div>
            ) : (
              <>
                {renderGroup("IPO imminenti", TYPE_CONFIG.ipo.color, ipoEvs)}
                {renderGroup("Spin-off annunciati", TYPE_CONFIG.spinoff.color, spinoffEvs)}
              </>
            )}
          </div>
        );
      })()}

      {/* ── Calendario classico ── */}
      {!showDiscovery && <>

      {/* ── Legend ── */}
      <div className="cal-legend">
        {Object.entries(TYPE_CONFIG).map(([t, cfg]) => (
          <span key={t} className="cal-legend-item">
            <span className="cal-dot" style={{background: cfg.color}}></span>
            {cfg.label}
          </span>
        ))}
      </div>

      {/* ── Calendar grid (headers + cells in one grid) — solo in modalità grid ── */}
      {effectiveMode === "grid" && (
      <div className="cal-grid cal-grid-body">
        {[["Lun","L"],["Mar","M"],["Mer","M"],["Gio","G"],["Ven","V"],["Sab","S"],["Dom","D"]].map(([full, short], i)=>(
          <div key={`h${i}`} className="cal-head">
            <span className="cal-head-full">{full}</span>
            <span className="cal-head-short">{short}</span>
          </div>
        ))}
        {cells.map((c, i) => {
          const key  = dayKey(c);
          const evs  = events[key] || [];
          const hasEvs = evs.length > 0;
          return (
            <div key={i}
              className={`cal-cell ${c.dim?"dim":""} ${isToday(c)?"today":""} ${!c.dim&&hasEvs?"has-events":""}`}
              onClick={() => !c.dim && window.innerWidth <= 760 && openDay(c)}
            >
              {!c.dim && <span className="cal-num" style={{
                color: isToday(c) ? "var(--accent)" : undefined,
                fontWeight: isToday(c) ? 700 : undefined,
              }}>{c.d}</span>}

              {/* Mobile: colored dots only (not for dim cells) */}
              {!c.dim && hasEvs && (
                <div className="cal-dots">
                  {evs.slice(0, 8).map((e, j) => (
                    <span key={j} className="cal-dot" style={{background: typeColor(e.type)}}/>
                  ))}
                </div>
              )}

              {/* Desktop: text rows (not for dim cells) */}
              {!c.dim && evs.map((e, j) => {
                const sa = e.arrows
                  ? { text: e.arrows, color: e.arrows.startsWith("↑") ? "var(--green)" : "var(--red)" }
                  : sentimentArrows(e.sentiment);
                return (
                  <div key={j} className="cal-event-row"
                    onMouseEnter={ev => {
                      const rect = ev.currentTarget.getBoundingClientRect();
                      setTooltip({ event: e, dateKey: key, ...positionTooltip(rect) });
                    }}
                    onMouseLeave={() => setTooltip(null)}
                  >
                    <span className="cal-event-dot" style={{background: typeColor(e.type), flexShrink:0}}/>
                    <span className="cal-event-name">
                      {sa.text && sa.text !== "—" && <span style={{color: sa.color, fontWeight:700}}>{sa.text}{" "}</span>}
                      <strong className="mono">{e.sym}</strong>
                      {" "}<span style={{color:"var(--text-muted)", fontWeight:400}}>{TYPE_CONFIG[e.type]?.label || e.type}</span>
                      {e.time && <span className="cal-event-time mono">{" · "}{e.time}</span>}
                    </span>
                  </div>
                );
              })}
            </div>
          );
        })}
      </div>
      )}

      {/* ── Event list (below grid only, chronological) ── */}
      {effectiveMode === "grid" && (() => {
        const monthPrefix = `${view.y}-${String(view.m+1).padStart(2,"0")}`;
        const list = Object.entries(events)
          .filter(([date]) => date.startsWith(monthPrefix))
          .flatMap(([date, evs]) => evs.map(e => ({ ...e, date })))
          .sort((a, b) => {
            const dc = a.date.localeCompare(b.date);
            return dc !== 0 ? dc : (a.time || "99:99").localeCompare(b.time || "99:99");
          });

        if (!list.length) return (
          <div className="cal-evlist">
            <div className="cal-evlist-head">
              <Icon name="calendar" size={12}/> Nessun evento in {monthName}
            </div>
          </div>
        );

        return (
          <div className="cal-evlist">
            <div className="cal-evlist-head">
              <Icon name="calendar" size={12}/>
              {list.length} eventi · {monthName}
            </div>
            <div className="cal-evlist-scroll">
              {list.map((e, i) => {
                const cfg  = TYPE_CONFIG[e.type] || TYPE_CONFIG.other;
                const d    = new Date(e.date + "T12:00:00");
                const dLabel = d.toLocaleDateString("it-IT", { weekday:"short", day:"2-digit", month:"short" });
                const isUpcoming = e.date >= `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,"0")}-${String(today.getDate()).padStart(2,"0")}`;
                return (
                  <div key={i} className={`cal-evlist-row${isUpcoming ? "" : " past"}`}
                       style={{"--ev-color": cfg.color}}>
                    <span className="cal-evlist-bar"/>
                    <div className="cal-evlist-main">
                      <div className="cal-evlist-top">
                        <span className="cal-evlist-company">{e.company || e.sym}</span>
                        <span className="cal-evlist-sym mono">{e.sym}</span>
                        <span className="cal-evlist-badge" style={{color: cfg.color, background: cfg.color + "22"}}>
                          {cfg.label}
                        </span>
                        {e.confirmed && <span title="Confermato da 2 fonti (yfinance + Finnhub)"
                          style={{color:"var(--green)", fontWeight:700, fontSize:11}}>✓✓</span>}
                      </div>
                      <div className="cal-evlist-bottom">
                        <span className="cal-evlist-date mono">{dLabel}</span>
                        {e.time
                          ? <span className="cal-evlist-time mono">· {e.time} (Roma)</span>
                          : <span className="cal-evlist-time" style={{opacity:0.4}}>· orario n/d</span>
                        }
                        {e.arrows && e.arrows !== "—" && (
                          <span className="cal-evlist-sent"
                                style={{color: e.arrows.startsWith("↑") ? "var(--green)" : "var(--red)"}}>
                            {e.arrows} {e.analyst_label || ""}
                            {e.analyst_count != null && <span className="mono" style={{opacity:0.7}}> ({e.analyst_count})</span>}
                          </span>
                        )}
                      </div>
                    </div>
                  </div>
                );
              })}
            </div>
          </div>
        );
      })()}

      {/* ── Agenda view (lista cronologica giorno per giorno, full-width) ── */}
      {effectiveMode === "agenda" && (() => {
        const monthPrefix = `${view.y}-${String(view.m+1).padStart(2,"0")}`;
        const monthEvents = Object.entries(events)
          .filter(([date]) => date.startsWith(monthPrefix))
          .sort(([a], [b]) => a.localeCompare(b));

        if (!monthEvents.length) return (
          <div className="cal-agenda">
            <div className="cal-agenda-empty">
              <Icon name="calendar" size={14}/> Nessun evento in {monthName}
            </div>
          </div>
        );

        const todayKey = `${today.getFullYear()}-${String(today.getMonth()+1).padStart(2,"0")}-${String(today.getDate()).padStart(2,"0")}`;

        return (
          <div className="cal-agenda">
            {monthEvents.map(([date, evs]) => {
              const d = new Date(date + "T12:00:00");
              const weekday = d.toLocaleDateString("it-IT", { weekday: "long" });
              const dateLabel = d.toLocaleDateString("it-IT", { day: "numeric", month: "long" });
              const sortedEvs = [...evs].sort((a, b) =>
                (a.time || "99:99").localeCompare(b.time || "99:99")
              );
              const isPast    = date < todayKey;
              const isToday_  = date === todayKey;
              return (
                <section key={date}
                  className={`cal-agenda-day${isPast ? " past" : ""}${isToday_ ? " today" : ""}`}>
                  <header className="cal-agenda-day-header">
                    <span className="cal-agenda-day-weekday">{weekday}</span>
                    <span className="cal-agenda-day-date">{dateLabel}</span>
                    {isToday_ && <span className="cal-agenda-day-badge">Oggi</span>}
                    <span className="cal-agenda-day-count">{sortedEvs.length} event{sortedEvs.length === 1 ? "o" : "i"}</span>
                  </header>
                  <div className="cal-agenda-events">
                    {sortedEvs.map((e, j) => {
                      const cfg = TYPE_CONFIG[e.type] || TYPE_CONFIG.other;
                      const sa  = e.arrows
                        ? { text: e.arrows, color: e.arrows.startsWith("↑") ? "var(--green)" : "var(--red)" }
                        : sentimentArrows(e.sentiment);
                      return (
                        <div key={j} className="cal-agenda-event"
                          onMouseEnter={ev => {
                            const rect = ev.currentTarget.getBoundingClientRect();
                            setTooltip({ event: e, dateKey: date, ...positionTooltip(rect) });
                          }}
                          onMouseLeave={() => setTooltip(null)}
                          onClick={() => window.innerWidth <= 760 && openDay({ d: d.getDate(), dim: false, y: d.getFullYear(), m: d.getMonth() })}
                        >
                          <span className="cal-agenda-event-dot" style={{background: typeColor(e.type)}}/>
                          <span className="cal-agenda-event-sentiment" style={{color: sa.color}}>
                            {sa.text && sa.text !== "—" ? sa.text : ""}
                          </span>
                          <span className="cal-agenda-event-name">{e.company || e.sym}</span>
                          <span className="cal-agenda-event-type" style={{color: cfg.color, background: cfg.color + "22"}}>
                            {cfg.label}
                          </span>
                          {e.confirmed && <span title="Confermato da 2 fonti (yfinance + Finnhub)"
                            style={{color:"var(--green)", fontWeight:700, fontSize:11}}>✓✓</span>}
                          <span className="cal-agenda-event-time mono">{e.time || "—"}</span>
                        </div>
                      );
                    })}
                  </div>
                </section>
              );
            })}
          </div>
        );
      })()}

      {/* ── Tooltip (desktop hover) ── */}
      {tooltip && (() => {
        const e  = tooltip.event;
        const sa = e.arrows
          ? { text: e.arrows, color: e.arrows.startsWith("↑") ? "var(--green)" : "var(--red)" }
          : sentimentArrows(e.sentiment);
        const sentLabel = e.analyst_label || (sa.color === "var(--green)" ? "Strong Buy" : "Strong Sell");
        return (
          <div className="cal-tooltip" style={{left: tooltip.x, top: tooltip.y}}>
            <div className="cal-tooltip-title">
              <span className="cal-dot" style={{background: typeColor(e.type), width:10, height:10, flexShrink:0, marginTop:2}}/>
              {e.title || e.company || `${TYPE_CONFIG[e.type]?.label || e.type} ${e.sym}`}
            </div>
            <div className="cal-tooltip-grid">
              <span className="tt-label">Data</span>
              <span className="tt-val mono">{tooltip.dateKey}</span>
              <span className="tt-label">Simbolo</span>
              <span className="tt-val mono">{e.sym}</span>
              {e.company && e.company !== e.sym && <>
                <span className="tt-label">Società</span>
                <span className="tt-val">{e.company}</span>
              </>}
              {e.time && <>
                <span className="tt-label">Orario Roma</span>
                <span className="tt-val mono">{e.time}</span>
              </>}
              <span className="tt-label">Tipo</span>
              <span className="tt-val" style={{color: typeColor(e.type)}}>{TYPE_CONFIG[e.type]?.label || e.type}</span>
              {e.eps_est != null && <>
                <span className="tt-label">EPS stimato</span>
                <span className="tt-val mono">${e.eps_est}</span>
              </>}
              {sa.text && sa.text !== "—" && <>
                <span className="tt-label">Analisti</span>
                <span className="tt-val" style={{color: sa.color, fontWeight:700}}>
                  {sa.text} {e.analyst_label || sentLabel}
                  {e.analyst_count != null && <span className="mono" style={{fontWeight:400, marginLeft:4}}>({e.analyst_count})</span>}
                </span>
              </>}
              {e.sources && e.sources.length > 0 && <>
                <span className="tt-label">Fonte</span>
                <span className="tt-val">
                  {e.sources.join(" + ")}
                  {e.confirmed
                    ? <span style={{color:"var(--green)", fontWeight:700, marginLeft:6}}>✓ confermato (2 fonti)</span>
                    : <span className="muted" style={{marginLeft:6}}>· singola fonte</span>}
                </span>
              </>}
            </div>
          </div>
        );
      })()}

      </>} {/* fine !showDiscovery */}

      {/* ── Day detail panel (slide-in) ── */}
      {selectedDay && (
        <div className="cal-day-overlay">
          <div className="cal-day-header">
            <h3>{selectedDay.label}</h3>
            <button className="icon-btn" onClick={() => setSelectedDay(null)}>
              <Icon name="x" size={16}/>
            </button>
          </div>
          <div className="cal-day-list">
            {selectedDay.evs.map((e, i) => {
              const sa = e.arrows
                ? { text: e.arrows, color: e.arrows.startsWith("↑") ? "var(--green)" : "var(--red)" }
                : sentimentArrows(e.sentiment);
              return (
                <div key={i} className="cal-day-item">
                  <span className="cal-dot" style={{background: typeColor(e.type), width:10, height:10, flexShrink:0, marginTop:5}}/>
                  <div className="cal-day-item-body">
                    <div className="cal-day-item-title">
                      <strong>{e.company || e.sym}</strong>
                      <span className="mono" style={{color:"var(--text-faint)", fontSize:11}}>({e.sym})</span>
                      {sa.text && sa.text !== "—" && (
                        <span style={{color: sa.color, fontWeight:700, fontSize:14}}>{sa.text}</span>
                      )}
                    </div>
                    <div className="cal-day-item-meta">
                      <span style={{color: typeColor(e.type), fontWeight:600}}>{TYPE_CONFIG[e.type]?.label || e.type}</span>
                      {e.time && <span>· {e.time} (Roma)</span>}
                    </div>
                    {e.eps_est != null && (
                      <div className="cal-day-item-detail">EPS stimato: <span className="mono">${e.eps_est}</span></div>
                    )}
                    {sa.text && sa.text !== "—" && (
                      <div className="cal-day-item-detail">
                        Analisti: <strong style={{color: sa.color}}>{sa.text}
                          {e.analyst_label ? ` ${e.analyst_label}` : ""}
                        </strong>
                        {e.analyst_count != null && <span className="mono" style={{marginLeft:4}}>({e.analyst_count})</span>}
                      </div>
                    )}
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}

// ────────── Report ──────────
const rThStyle = {
  padding:"9px 12px", textAlign:"left", fontSize:11, fontWeight:600,
  color:"var(--text-muted)", textTransform:"uppercase", letterSpacing:".06em",
  background:"var(--bg)", borderBottom:"1px solid var(--border)",
  whiteSpace:"nowrap", position:"sticky", top:0, zIndex:2,
};
const rTdStyle = {
  padding:"8px 12px", fontSize:12.5, borderBottom:"1px solid var(--border-soft)",
  whiteSpace:"nowrap", verticalAlign:"middle",
};

function PctCell({ v }) {
  if (v === undefined || v === null) return <td style={rTdStyle}>—</td>;
  const n = parseFloat(v);
  if (isNaN(n)) return <td style={rTdStyle}>—</td>;
  const c = n > 0 ? "var(--green)" : n < 0 ? "var(--red)" : "var(--text-muted)";
  return <td style={{...rTdStyle, color:c, fontFamily:"var(--mono)", fontWeight:500}}>
    {n > 0 ? "+" : ""}{n.toFixed(2)}%
  </td>;
}

function PriceCell({ v }) {
  if (v === undefined || v === null) return <td style={rTdStyle}>—</td>;
  const n = parseFloat(v);
  return <td style={{...rTdStyle, fontFamily:"var(--mono)"}}>{isNaN(n) ? "—" : n.toFixed(2)}</td>;
}

function SignalBadge({ sig }) {
  if (!sig) return <span style={{color:"var(--text-muted)"}}>—</span>;
  const s = sig.toUpperCase().replace(/ /g,"_");
  const map = {
    BUY:         ["var(--green)",  "var(--green-soft)"],
    STRONG_BUY:  ["var(--green)",  "var(--green-soft)"],
    SELL:        ["var(--red)",    "var(--red-soft)"],
    STRONG_SELL: ["var(--red)",    "var(--red-soft)"],
    HOLD:        ["var(--amber)",  "var(--amber-soft)"],
  };
  const [c,bg] = map[s] || ["var(--text-muted)","var(--surface)"];
  return (
    <span style={{display:"inline-flex",alignItems:"center",padding:"3px 9px",borderRadius:4,background:bg,color:c,fontWeight:700,fontSize:11,letterSpacing:".04em"}}>
      {sig.replace(/_/g," ")}
    </span>
  );
}

function ConfCell({ v }) {
  if (v === undefined || v === null) return <td style={rTdStyle}>—</td>;
  const n = parseInt(v);
  const c = n >= 70 ? "var(--green)" : n >= 50 ? "var(--text)" : "var(--red)";
  return <td style={{...rTdStyle, color:c, fontFamily:"var(--mono)", fontWeight:600}}>{n}%</td>;
}

function TrendCell({ v }) {
  if (!v) return <td style={{...rTdStyle, color:"var(--text-muted)"}}>—</td>;
  const up = v.toUpperCase().includes("BULL");
  const dn = v.toUpperCase().includes("BEAR");
  return (
    <td style={{...rTdStyle, color: up ? "var(--green)" : dn ? "var(--red)" : "var(--text-muted)"}}>
      {up ? "▲ BULL" : dn ? "▼ BEAR" : v}
    </td>
  );
}

// ts helper: "2026-05-14 09:20:31" → "09:20"
const _ts2time = ts => (ts || "").slice(11,16) || "—";

function ReportView() {
  const [tool, setTool] = useState("tool2");

  // Real data from API (date-keyed). Falls back to empty if not loaded yet.
  const byDate = window.REPORT_DATA_REAL || {};
  const allDates = Object.keys(byDate).sort().reverse(); // newest first

  // Default: yesterday
  const _yesterday = (() => {
    const d = new Date(); d.setDate(d.getDate()-1);
    return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,"0")}-${String(d.getDate()).padStart(2,"0")}`;
  })();
  const [dateFilter, setDateFilter] = useState(allDates[0] || _yesterday);
  const [tickerFilter, setTickerFilter] = useState("Tutti");

  // All tickers in the selected day (across all tools)
  const dayData = byDate[dateFilter] || { tool2:[], tool3:[], tool4:[] };
  const allSymbols = [...new Set([
    ...((dayData.tool2)||[]).map(r=>r.symbol),
    ...((dayData.tool3)||[]).map(r=>r.symbol),
    ...((dayData.tool4)||[]).map(r=>r.symbol),
  ])].sort();
  const tickers = ["Tutti", ...allSymbols];

  const applyFilter = rows => (rows||[]).filter(r =>
    tickerFilter === "Tutti" || r.symbol === tickerFilter
  );
  const rows2 = applyFilter(dayData.tool2);
  const rows3 = applyFilter(dayData.tool3);
  const rows4 = applyFilter(dayData.tool4);

  // CSV export
  const exportCsv = () => {
    const rows = tool === "tool2" ? rows2 : tool === "tool3" ? rows3 : rows4;
    if (!rows.length) return;
    const headers = Object.keys(rows[0]);
    const esc = v => { const s = String(v??''); return (s.includes(',')||s.includes('"')||s.includes('\n')) ? `"${s.replace(/"/g,'""')}"` : s; };
    const lines = [headers.join(','), ...rows.map(r => headers.map(h => esc(r[h])).join(','))];
    const a = document.createElement('a');
    a.href = 'data:text/csv;charset=utf-8,' + encodeURIComponent(lines.join('\r\n'));
    a.download = `report_${tool}_${dateFilter}.csv`;
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
  };

  const counts = `${rows2.length} AI | ${rows3.length} TA | ${rows4.length} News`;
  const noData = allDates.length === 0;

  return (
    <div style={{flex:1, display:"flex", flexDirection:"column", overflow:"hidden"}} data-screen-label="04 Report">
      {/* Toolbar */}
      <div style={{
        padding:"10px 18px", borderBottom:"1px solid var(--border)",
        display:"flex", alignItems:"center", gap:10, flexWrap:"wrap",
        background:"var(--bg)", flexShrink:0,
      }}>
        <FilterCalendar
          value={dateFilter}
          onChange={setDateFilter}
          availableDates={allDates}
          placeholder="Tutte le date"
          showClear={false}
        />
        <FilterSelect
          value={tickerFilter}
          onChange={setTickerFilter}
          options={tickers.map(t => ({ value: t, label: t }))}
          placeholder="Tutti"
        />
        <button className="btn btn-sm" onClick={exportCsv}><Icon name="download" size={13}/> Esporta CSV</button>
        <div style={{marginLeft:"auto", fontSize:11.5, color:"var(--text-muted)", fontFamily:"var(--mono)"}}>
          {noData ? "Nessun dato — avvia il daemon" : `${dateFilter} — ${counts}`}
        </div>
      </div>

      {noData ? (
        <div style={{display:"flex",alignItems:"center",justifyContent:"center",height:"100%",flexDirection:"column",gap:8,color:"var(--text-muted)"}}>
          <Icon name="candles" size={28} strokeWidth={1.2}/>
          <span>Nessun dato nel database. Avvia il daemon e attendi il primo ciclo completo.</span>
        </div>
      ) : (
        <>
          {/* Tool tabs */}
          <div style={{display:"flex", borderBottom:"1px solid var(--border)", background:"var(--bg-elevated)", flexShrink:0}}>
            {[
              { id:"tool2", label:"Tool2 — AI Agent" },
              { id:"tool3", label:"Tool3 — TA Analyzer" },
              { id:"tool4", label:"Tool4 — News" },
            ].map(t => (
              <button key={t.id} onClick={()=>setTool(t.id)} style={{
                padding:"10px 18px", fontSize:13, fontWeight:500, background:"none", border:"none",
                cursor:"pointer", color: tool===t.id ? "var(--text)" : "var(--text-muted)",
                borderBottom: tool===t.id ? "2px solid var(--accent)" : "2px solid transparent",
                transition:"color 120ms ease",
              }}>{t.label}</button>
            ))}
          </div>

          {/* Table */}
          <div style={{flex:1, overflow:"auto"}}>
            {tool === "tool2" && (
              rows2.length === 0
                ? <ReportEmpty/>
                : <table style={{width:"100%", borderCollapse:"collapse", minWidth:980}}>
                    <thead><tr>{["GIORNO","SYMBOL","ORA","MODE","SIGNAL","CONF%","CONF★%","TREND 1H","CLOSE","+1H","+3H","+6H","+24H","+2W","TARGET","STOP"].map(h=><th key={h} style={rThStyle}>{h}</th>)}</tr></thead>
                    <tbody>
                      {rows2.map((r,i)=>(
                        <tr key={i} style={{background: i%2===0?"transparent":"oklch(0.175 0.012 250 / 0.5)"}}>
                          <td style={{...rTdStyle,color:"var(--text-muted)",fontFamily:"var(--mono)"}}>{(r.timestamp||"").slice(0,10)}</td>
                          <td style={{...rTdStyle,fontWeight:700,letterSpacing:".02em"}}>{r.symbol}</td>
                          <td style={{...rTdStyle,fontFamily:"var(--mono)",color:"var(--text-muted)"}}>{_ts2time(r.timestamp)}</td>
                          <td style={{...rTdStyle,color:"var(--text-muted)",fontSize:11}}>{r.mode}</td>
                          <td style={rTdStyle}><SignalBadge sig={r.signal}/></td>
                          <ConfCell v={r.confidence}/>
                          <ConfCell v={r.confidence_real}/>
                          <TrendCell v={r.trend_1h}/>
                          <PriceCell v={r.close_price}/>
                          <PctCell v={r.expected_1h}/>
                          <PctCell v={r.expected_3h}/>
                          <PctCell v={r.expected_6h}/>
                          <PctCell v={r.expected_24h}/>
                          <PctCell v={r.expected_2w}/>
                          <PriceCell v={r.target_price}/>
                          <PriceCell v={r.stop_loss}/>
                        </tr>
                      ))}
                    </tbody>
                  </table>
            )}

            {tool === "tool3" && (
              rows3.length === 0
                ? <ReportEmpty/>
                : <table style={{width:"100%", borderCollapse:"collapse", minWidth:900}}>
                    <thead><tr>{["GIORNO","SYMBOL","ORA","SIGNAL","SCORE","METODO","N.","+1H","+3H","+6H","+24H","+2W"].map(h=><th key={h} style={rThStyle}>{h}</th>)}</tr></thead>
                    <tbody>
                      {rows3.map((r,i)=>(
                        <tr key={i} style={{background: i%2===0?"transparent":"oklch(0.175 0.012 250 / 0.5)"}}>
                          <td style={{...rTdStyle,color:"var(--text-muted)",fontFamily:"var(--mono)"}}>{(r.timestamp||"").slice(0,10)}</td>
                          <td style={{...rTdStyle,fontWeight:700}}>{r.symbol}</td>
                          <td style={{...rTdStyle,fontFamily:"var(--mono)",color:"var(--text-muted)"}}>{_ts2time(r.timestamp)}</td>
                          <td style={rTdStyle}><SignalBadge sig={r.signal}/></td>
                          <td style={{...rTdStyle,fontFamily:"var(--mono)",color:r.score_total>=65?"var(--green)":r.score_total<50?"var(--red)":"var(--text)",fontWeight:600}}>{r.score_total ?? "—"}</td>
                          <td style={{...rTdStyle,color:"var(--text-muted)",fontSize:11.5}}>{r.forecast_method}</td>
                          <td style={{...rTdStyle,fontFamily:"var(--mono)",color:"var(--text-muted)"}}>{r.sample_size ?? "—"}</td>
                          <PctCell v={r.h01}/><PctCell v={r.h03}/><PctCell v={r.h06}/><PctCell v={r.h24}/><PctCell v={r.h2w}/>
                        </tr>
                      ))}
                    </tbody>
                  </table>
            )}

            {tool === "tool4" && (
              rows4.length === 0
                ? <ReportEmpty/>
                : <table style={{width:"100%", borderCollapse:"collapse", minWidth:980}}>
                    <thead><tr>{["GIORNO","SYMBOL","ORA","SIGNAL","IMPACT","SENT.","ALERT","+1H%","p(1h)","+3H%","p(3h)","+6H%","p(6h)","+24H%","p(24h)"].map(h=><th key={h} style={rThStyle}>{h}</th>)}</tr></thead>
                    <tbody>
                      {rows4.map((r,i)=>{
                        const sent = parseFloat(r.overall_sentiment);
                        const imp  = (r.news_impact||"").toUpperCase();
                        const impColor = imp==="HIGH"||imp==="CRITICAL" ? "var(--red)" : imp==="MEDIUM" ? "var(--amber)" : "var(--text-muted)";
                        return (
                          <tr key={i} style={{background: i%2===0?"transparent":"oklch(0.175 0.012 250 / 0.5)"}}>
                            <td style={{...rTdStyle,color:"var(--text-muted)",fontFamily:"var(--mono)"}}>{(r.timestamp||"").slice(0,10)}</td>
                            <td style={{...rTdStyle,fontWeight:700}}>{r.symbol}</td>
                            <td style={{...rTdStyle,fontFamily:"var(--mono)",color:"var(--text-muted)"}}>{_ts2time(r.timestamp)}</td>
                            <td style={rTdStyle}><SignalBadge sig={r.signal_combined}/></td>
                            <td style={{...rTdStyle,color:impColor,fontWeight:600,fontSize:11}}>{r.news_impact||"—"}</td>
                            <td style={{...rTdStyle,fontFamily:"var(--mono)",color:!isNaN(sent)&&sent>0?"var(--green)":!isNaN(sent)&&sent<0?"var(--red)":"var(--text-muted)",fontWeight:500}}>
                              {isNaN(sent) ? "—" : (sent>0?"+":"")+sent.toFixed(2)}
                            </td>
                            <td style={{...rTdStyle,color:parseInt(r.breaking_alert)===1?"var(--red)":"var(--text-muted)",fontWeight:600,fontSize:11}}>
                              {parseInt(r.breaking_alert)===1 ? "⚡ SI" : "no"}
                            </td>
                            <PctCell v={r.fc_1h_pct}/>
                            <td style={{...rTdStyle,fontFamily:"var(--mono)",color:"var(--text-muted)",fontSize:11.5}}>{r.fc_1h_prob != null ? parseFloat(r.fc_1h_prob).toFixed(2) : "—"}</td>
                            <PctCell v={r.fc_3h_pct}/>
                            <td style={{...rTdStyle,fontFamily:"var(--mono)",color:"var(--text-muted)",fontSize:11.5}}>{r.fc_3h_prob != null ? parseFloat(r.fc_3h_prob).toFixed(2) : "—"}</td>
                            <PctCell v={r.fc_6h_pct}/>
                            <td style={{...rTdStyle,fontFamily:"var(--mono)",color:"var(--text-muted)",fontSize:11.5}}>{r.fc_6h_prob != null ? parseFloat(r.fc_6h_prob).toFixed(2) : "—"}</td>
                            <PctCell v={r.fc_24h_pct}/>
                            <td style={{...rTdStyle,fontFamily:"var(--mono)",color:"var(--text-muted)",fontSize:11.5}}>{r.fc_24h_prob != null ? parseFloat(r.fc_24h_prob).toFixed(2) : "—"}</td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
            )}
          </div>
        </>
      )}
    </div>
  );
}

function ReportEmpty() {
  return (
    <div style={{display:"flex",alignItems:"center",justifyContent:"center",height:200,flexDirection:"column",gap:8,color:"var(--text-muted)"}}>
      <Icon name="candles" size={24} strokeWidth={1.2}/>
      <span style={{fontSize:13}}>Nessun dato per la data/ticker selezionati.</span>
    </div>
  );
}

// ────────── Evaluate ──────────
function EvaluateView() {
  const [fileObjs, setFileObjs] = useState({ tool2: null, tool3: null, tool4: null });
  const [status,   setStatus]   = useState("");
  const [loading,  setLoading]  = useState(false);
  const [results,  setResults]  = useState(null);

  const setFile = (k, file) => setFileObjs(f => ({ ...f, [k]: file }));
  const ready = Object.values(fileObjs).some(Boolean);

  const ZONES = [
    { k: "tool2", icon: "brain",     name: "Tool 2 — AI Agent",  emoji: "🤖" },
    { k: "tool3", icon: "trending",  name: "Tool 3 — Tecnico",   emoji: "📐" },
    { k: "tool4", icon: "newspaper", name: "Tool 4 — News",      emoji: "📰" },
  ];

  const handleDrop = (k) => (e) => {
    e.preventDefault();
    const file = e.dataTransfer?.files[0];
    if (file && file.name.endsWith(".csv")) setFile(k, file);
  };

  const handleChange = (k) => (e) => {
    const file = e.target.files[0];
    if (file) setFile(k, file);
  };

  const handleClear = (k, e) => {
    e.stopPropagation();
    setFile(k, null);
  };

  const runEvaluate = async () => {
    setLoading(true);
    setStatus("⏳ Elaborazione in corso...");
    setResults(null);
    const fd = new FormData();
    let count = 0;
    for (const { k } of ZONES) {
      if (fileObjs[k]) { fd.append("csv_" + k, fileObjs[k], fileObjs[k].name); count++; }
    }
    if (!count) { setLoading(false); return; }
    try {
      const r = await fetch("/api/evaluate", { method: "POST", body: fd });
      const d = await r.json();
      if (d.error) throw new Error(d.error);
      setResults(d.results);
      const n = d.results.filter(x => !x.error).length;
      setStatus(`✓ Completato — ${n} tool analizzati`);
    } catch (e) {
      setStatus("✗ Errore: " + e.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="panel" data-screen-label="05 Evaluate">
      <div className="card">
        <h3>Valutazione previsioni</h3>
        <div className="help">
          Carica i CSV esportati dalla sezione Report (idealmente 24–48h dopo l'esecuzione).
          Il sistema arricchisce ogni riga con le variazioni reali di prezzo e genera un report con
          istruzioni specifiche per Claude Code.
        </div>

        <div className="eval-grid" style={{marginTop:14}}>
          {ZONES.map(z => {
            const f = fileObjs[z.k];
            return (
              <label
                key={z.k}
                className={`eval-zone ${f ? "loaded" : ""}`}
                onDragOver={e => { e.preventDefault(); e.currentTarget.classList.add("drag-over"); }}
                onDragLeave={e => e.currentTarget.classList.remove("drag-over")}
                onDrop={e => { e.currentTarget.classList.remove("drag-over"); handleDrop(z.k)(e); }}
              >
                <input type="file" accept=".csv" style={{display:"none"}} onChange={handleChange(z.k)}/>
                <div className="eval-icon"><Icon name={z.icon} size={20}/></div>
                <strong>{z.name}</strong>
                <small className="mono" style={{wordBreak:"break-all", maxWidth:"100%", color: f ? "var(--green)" : "var(--text-muted)"}}>
                  {f ? f.name : "Clicca o trascina CSV"}
                </small>
                {f && (
                  <span
                    style={{fontSize:10, color:"var(--text-faint)", textDecoration:"underline", cursor:"pointer"}}
                    onClick={e => handleClear(z.k, e)}
                  >rimuovi</span>
                )}
              </label>
            );
          })}
        </div>

        <div style={{display:"flex", alignItems:"center", gap:12, marginTop:16}}>
          <button className="btn btn-primary" disabled={!ready || loading} onClick={runEvaluate}>
            <Icon name="play" size={13}/> Analizza previsioni
          </button>
          <span style={{color: status.startsWith("✗") ? "var(--red)" : status.startsWith("✓") ? "var(--green)" : "var(--text-muted)", fontSize:12}}>
            {status || (ready ? `${Object.values(fileObjs).filter(Boolean).length} file pronto/i` : "Nessun file selezionato")}
          </span>
        </div>
      </div>

      {results && results.map((r, idx) => <EvaluateResult key={idx} r={r} idx={idx} allResults={results}/>)}
    </div>
  );
}

function EvaluateResult({ r, idx, allResults }) {
  const [copied, setCopied] = useState(false);
  const toolNames = { tool2: "🤖 Tool 2 — AI Agent", tool3: "📐 Tool 3 — Tecnico", tool4: "📰 Tool 4 — News" };

  if (r.error) {
    return (
      <div className="card">
        <h3 style={{color:"var(--red)"}}>⚠ Errore — {r.filename}</h3>
        <pre style={{color:"var(--text-muted)", fontSize:12, marginTop:8, whiteSpace:"pre-wrap"}}>{r.error}</pre>
      </div>
    );
  }

  const toolName = toolNames[r.tool] || r.tool;
  const validMsg = r.n_valid === 0
    ? <span style={{color:"var(--amber)"}}>⚠ 0 prezzi reali (DB vecchio o tutto pending)</span>
    : <span style={{color:"var(--green)"}}>✓ {r.n_valid}/{r.n_total} record con prezzi reali</span>;

  const horizons = (r.analysis || {}).horizons || {};
  const priorityColor = { ALTA: "var(--red)", MEDIA: "var(--amber)", BASSA: "var(--green)" };

  const copyReport = () => {
    navigator.clipboard.writeText(r.report_md || "")
      .then(() => { setCopied(true); setTimeout(() => setCopied(false), 2000); })
      .catch(() => {});
  };

  const downloadCsv = () => {
    const blob = new Blob([r.enriched_csv], { type: "text/csv;charset=utf-8;" });
    const url  = URL.createObjectURL(blob);
    const a    = document.createElement("a");
    a.href = url; a.download = "eval_enriched_" + r.filename;
    document.body.appendChild(a); a.click();
    document.body.removeChild(a); URL.revokeObjectURL(url);
  };

  const downloadMd = () => {
    const blob = new Blob([r.report_md || ""], { type: "text/markdown;charset=utf-8;" });
    const url  = URL.createObjectURL(blob);
    const a    = document.createElement("a");
    a.href = url; a.download = "eval_report_" + r.filename.replace(/\.csv$/i, "") + ".md";
    document.body.appendChild(a); a.click();
    document.body.removeChild(a); URL.revokeObjectURL(url);
  };

  return (
    <div className="card">
      <div className="eval-result-header">
        <h3 style={{margin:0}}>{toolName}</h3>
        <div style={{fontSize:12}}>{validMsg}&nbsp;·&nbsp;<span style={{color:"var(--text-muted)"}}>{r.filename}</span></div>
      </div>

      <h4 className="eval-section-label">Accuracy Summary</h4>
      <div className="eval-table-wrap">
        <table className="eval-table">
          <thead><tr><th>Orizzonte</th><th>n</th><th>MAE</th><th>Bias (MBE)</th><th>Dir.Acc</th><th>RMSE</th></tr></thead>
          <tbody>
            {["1h","3h","6h","24h"].map(label => {
              const h = horizons[label] || {};
              const mbe = h.mbe != null
                ? <span style={{color: h.mbe >= 0 ? "var(--red)" : "var(--green)"}}>{h.mbe >= 0 ? "+" : ""}{h.mbe.toFixed(3)}%</span>
                : "—";
              return (
                <tr key={label}>
                  <td>+{label}</td>
                  <td>{h.n ?? 0}</td>
                  <td>{h.mae != null ? h.mae.toFixed(3) + "%" : "—"}</td>
                  <td>{mbe}</td>
                  <td>{h.dir_acc != null ? h.dir_acc.toFixed(1) + "%" : "—"}</td>
                  <td>{h.rmse != null ? h.rmse.toFixed(3) : "—"}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>

      <h4 className="eval-section-label">Pattern Identificati</h4>
      <div className="eval-patterns">
        {(!r.patterns || r.patterns.length === 0) ? (
          <p style={{color:"var(--text-muted)", fontSize:12}}>✅ Nessun pattern critico (dati insufficienti o accuratezza già buona)</p>
        ) : r.patterns.map((p, i) => (
          <div key={i} className="eval-pattern-card">
            <div className="eval-pattern-header">
              <span className="eval-pattern-badge" style={{background: priorityColor[p.priority] || "var(--text-muted)"}}>{p.priority}</span>
              <strong>{p.title}</strong>
            </div>
            <div style={{color:"var(--text-muted)", fontSize:12, marginTop:4}}>{p.evidence || ""}</div>
          </div>
        ))}
      </div>

      <h4 className="eval-section-label">Report per Claude Code</h4>
      <div className="eval-report-wrap">
        <div className="eval-report-toolbar">
          <span style={{fontSize:11, color:"var(--text-muted)"}}>Copia e incolla direttamente in Claude Code</span>
          <button className="btn btn-sm" onClick={copyReport}>{copied ? "✓ Copiato" : "Copia"}</button>
        </div>
        <pre className="eval-report-pre">{r.report_md || ""}</pre>
      </div>

      <div style={{marginTop:12, display:"flex", gap:8, flexWrap:"wrap"}}>
        <button className="btn btn-sm" onClick={downloadCsv}><Icon name="download" size={12}/> CSV arricchito</button>
        <button className="btn btn-sm" onClick={downloadMd}><Icon name="download" size={12}/> Report .md</button>
      </div>
    </div>
  );
}

// ────────── Config ──────────
function ConfigView({ user }) {
  const [configJson, setConfigJson] = useState("");
  const [loading, setLoading]       = useState(true);
  const [saveStatus, setSaveStatus] = useState(null); // null | "ok" | "error"
  const [parsed, setParsed]         = useState(null);
  const [newTicker, setNewTicker]   = useState("");
  const [restartStatus, setRestartStatus] = useState(null); // null | "loading" | "ok" | "err"
  const [starState, setStarState]   = useState({}); // {symbol: bool} — stellina Tool4
  const isAdmin = user?.is_admin || false;

  const loadStars = () => {
    fetch("/api/watchlist/star").then(r => r.json())
      .then(d => { if (d.state) setStarState(d.state); }).catch(() => {});
  };
  useEffect(loadStars, []);

  const toggleStar = (sym) => {
    const isOn = starState[sym] !== false;   // default true (stellato)
    const want = !isOn;
    setStarState(s => ({ ...s, [sym]: want }));   // ottimistico
    fetch("/api/watchlist/star", {
      method: "POST", headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ symbol: sym, starred: want }),
    }).then(r => r.json()).then(loadStars).catch(loadStars);
  };

  const restartAnalyzer = async () => {
    setRestartStatus("loading");
    try {
      const r = await fetch("/api/restart-analyzer", { method: "POST" }).then(r => r.json());
      setRestartStatus(r.ok ? "ok" : "err");
      setTimeout(() => setRestartStatus(null), 4000);
    } catch { setRestartStatus("err"); setTimeout(() => setRestartStatus(null), 4000); }
  };

  const fetchConfig = () => {
    setLoading(true);
    fetch("/api/config")
      .then(r => r.json())
      .then(data => {
        const pretty = JSON.stringify(data, null, 2);
        setConfigJson(pretty);
        setParsed(data);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  };

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

  const toggleInConfig = (path, value) => {
    try {
      const obj = JSON.parse(configJson);
      const keys = path.split(".");
      let ref = obj;
      for (let i = 0; i < keys.length - 1; i++) ref = ref[keys[i]];
      ref[keys[keys.length - 1]] = value;
      const pretty = JSON.stringify(obj, null, 2);
      setConfigJson(pretty);
      setParsed(obj);
    } catch {}
  };

  const saveConfig = async () => {
    try {
      const body = JSON.parse(configJson);
      const res  = await fetch("/api/config", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(body),
      });
      const data = await res.json();
      setSaveStatus(data.ok ? "ok" : "error");
      if (data.ok) window.refreshDashboard?.();
      setTimeout(() => setSaveStatus(null), 3000);
    } catch {
      setSaveStatus("error");
      setTimeout(() => setSaveStatus(null), 3000);
    }
  };

  const addTicker = () => {
    const sym = newTicker.trim().toUpperCase();
    if (!sym) return;
    const list = parsed?.watchlist || [];
    if (list.includes(sym)) { setNewTicker(""); return; }
    toggleInConfig("watchlist", [...list, sym]);
    setNewTicker("");
  };

  const sched = parsed?.schedule || {};
  const toggles = [
    { key: "schedule.tool1.enabled",  name: "Tool 1 — Collector",  desc: "Fetch OHLCV 5m, ogni 5 min",        val: sched?.tool1?.enabled },
    { key: "schedule.tool3.enabled",  name: "Tool 3 — Tecnico",    desc: "EMA, RSI, MACD · 10 min",            val: sched?.tool3?.enabled },
    { key: "schedule.tool4.enabled",  name: "Tool 4 — News",       desc: "Finnhub + Perplexity",               val: sched?.tool4?.enabled },
    { key: "schedule.tool5.enabled",  name: "Tool 5 — Calendario", desc: "Earnings + eventi · digest mattutino", val: sched?.tool5?.enabled },
    { key: "schedule.tool7.enabled",  name: "Tool 7 — Settori",    desc: "Rotazione settori + temi · 60 min",  val: sched?.tool7?.enabled },
    { key: "schedule.tool8.enabled",  name: "Tool 8 — Pre-market", desc: "Gap scanner pre-apertura",           val: sched?.tool8?.enabled },
    { key: "schedule.tool9.enabled",  name: "Tool 9 — Top Picks",  desc: "Discovery daily 03:00 · Claude Haiku", val: sched?.tool9?.enabled },
    { key: "schedule.tool10.enabled", name: "Tool 10 — Chain",     desc: "Lead-lag discovery daily 04:00 · gratis", val: sched?.tool10?.enabled },
    { key: "schedule.tool11.enabled", name: "Tool 11 — Simulatore", desc: "Paper-trading settimanale · denaro fittizio", val: sched?.tool11?.enabled },
  ];
  const dash = parsed?.dashboard || {};
  const dashToggles = [
    { key: "dashboard.show_tool2", name: "Linea Tool 2 — AI (blu)",         val: dash.show_tool2 !== false },
    { key: "dashboard.show_tool3", name: "Linea Tool 3 — Tecnico (arancio)", val: dash.show_tool3 !== false },
    { key: "dashboard.show_tool4", name: "Linea Tool 4 — News (viola)",      val: dash.show_tool4 !== false },
  ];
  const forcePx = (parsed?.tool4?.perplexity_force) === true;

  return (
    <div className="panel" data-screen-label="06 Config">
      <div className="card">
        <div style={{display:"flex", alignItems:"center", justifyContent:"space-between", flexWrap:"wrap", gap:8, marginBottom:4}}>
          <h3 style={{margin:0}}>Attiva / disattiva tool</h3>
          {isAdmin && (
            <button
              className="btn btn-sm"
              onClick={restartAnalyzer}
              disabled={restartStatus === "loading"}
              title="Riavvia il daemon bvf-analyzer per applicare modifiche al codice Python o agli schedule"
              style={restartStatus === "ok" ? {borderColor:"var(--green)", color:"var(--green)"} :
                     restartStatus === "err" ? {borderColor:"var(--red)", color:"var(--red)"} : {}}>
              <Icon name="refresh" size={13}/>
              {restartStatus === "loading" ? " Riavvio in corso…"
                : restartStatus === "ok"  ? " ✓ Riavviato"
                : restartStatus === "err" ? " Errore"
                : " Riavvia Analyzer"}
            </button>
          )}
        </div>
        <div className="help">Controlla quali tool girano nello scheduler del daemon.</div>
        <div className="toggles">
          {toggles.map(t => (
            <div className="toggle-row" key={t.key}>
              <div className="tn"><strong>{t.name}</strong><small>{t.desc}</small></div>
              <button className={`switch ${t.val ? "on":""}`}
                      onClick={() => toggleInConfig(t.key, !t.val)}
                      disabled={loading}/>
            </div>
          ))}
        </div>
      </div>

      <div className="card">
        <h3>Visibilità nei grafici</h3>
        <div className="help">Nasconde la linea del tool nei grafici della Dashboard.</div>
        <div className="toggles">
          {dashToggles.map(t => (
            <div className="toggle-row" key={t.key}>
              <div className="tn"><strong>{t.name}</strong></div>
              <button className={`switch ${t.val ? "on":""}`}
                      onClick={() => toggleInConfig(t.key, !t.val)}
                      disabled={loading}/>
            </div>
          ))}
        </div>
      </div>

      <div className="card">
        <h3>Opzioni avanzate Tool 4 (News)</h3>
        <div className="help">Diagnostica. Le modifiche si applicano dopo <b>Salva configurazione</b> (hot-reload, niente restart).</div>
        <div className="toggles">
          <div className="toggle-row">
            <div className="tn">
              <strong>Forza Perplexity su tutti i ticker</strong>
              <small style={{color: forcePx ? "var(--amber)" : undefined}}>
                Bypassa breaking-gate e cache: 1 chiamata Perplexity per ticker a ogni ciclo Tool 4.
                {forcePx ? " ⚠️ ATTIVO — costoso, ricordati di rispegnerlo dopo i test." : " Solo per test/diagnostica."}
              </small>
            </div>
            <button className={`switch ${forcePx ? "on":""}`}
                    onClick={() => toggleInConfig("tool4.perplexity_force", !forcePx)}
                    disabled={loading}/>
          </div>
        </div>
      </div>

      <div className="card">
        <h3>Watchlist</h3>
        <div className="help">Aggiungi o rimuovi ticker dal watchlist principale. La <b>stellina ★</b> attiva l'analisi news con IA (Tool4): i ticker stellati pagano l'AI sulle news; quelli senza stella si fermano al Tool3 (Tool4 derivato, costo zero). Salva per applicare le altre modifiche.</div>
        <div style={{display:"flex", flexWrap:"wrap", gap:6, marginBottom:14}}>
          {(parsed?.watchlist || []).map(sym => {
            const on = starState[sym] !== false; // default stellato
            return (
            <span key={sym} style={{
              display:"inline-flex", alignItems:"center", gap:5,
              padding:"4px 8px 4px 8px",
              background:"var(--surface)", border:"1px solid var(--border)",
              borderRadius:"var(--radius-sm)", fontSize:12.5,
            }}>
              <button
                onClick={() => toggleStar(sym)}
                title={on ? `${sym}: analisi news IA attiva — clicca per fermarti al Tool3` : `${sym}: solo Tool3 — clicca per attivare l'IA news`}
                style={{background:"none", border:"none", cursor:"pointer", padding:0,
                        fontSize:14, lineHeight:1, color: on ? "var(--amber, #f59e0b)" : "var(--text-faint)"}}>
                {on ? "★" : "☆"}
              </button>
              <span className="mono" style={{fontWeight:600, color:"var(--text)"}}>{sym}</span>
              <button
                className="icon-btn"
                style={{width:16, height:16, flexShrink:0}}
                title={`Rimuovi ${sym}`}
                onClick={() => toggleInConfig("watchlist", (parsed?.watchlist || []).filter(s => s !== sym))}
                disabled={loading}
              >
                <Icon name="x" size={10}/>
              </button>
            </span>
            );
          })}
          {(parsed?.watchlist || []).length === 0 && (
            <span style={{color:"var(--text-faint)", fontSize:12}}>Nessun ticker nel watchlist.</span>
          )}
        </div>
        <div style={{display:"flex", gap:6, alignItems:"center", flexWrap:"wrap"}}>
          <input
            className="mono"
            placeholder="Es. TSLA"
            value={newTicker}
            onChange={e => setNewTicker(e.target.value.toUpperCase())}
            onKeyDown={e => e.key === "Enter" && addTicker()}
            disabled={loading}
            style={{
              padding:"6px 10px", background:"var(--surface)",
              border:"1px solid var(--border)", borderRadius:"var(--radius-sm)",
              color:"var(--text)", fontSize:12.5, width:120, outline:"none",
              transition:"border-color 120ms",
            }}
            onFocus={e => e.target.style.borderColor = "var(--accent)"}
            onBlur={e => e.target.style.borderColor = "var(--border)"}
          />
          <button className="btn btn-sm" onClick={addTicker} disabled={loading || !newTicker.trim()}>
            <Icon name="check" size={13}/> Aggiungi
          </button>
        </div>
      </div>

      <div className="card">
        <h3>Editor JSON</h3>
        <div className="help">Modifica diretta della configurazione. Salva per applicare al prossimo ciclo del daemon.</div>
        <textarea className="editor" spellCheck={false}
                  value={configJson}
                  onChange={e => setConfigJson(e.target.value)}
                  style={{opacity: loading ? 0.5 : 1}}/>
        <div style={{display:"flex", gap:8, marginTop:12, alignItems:"center"}}>
          <button className="btn btn-primary" onClick={saveConfig} disabled={loading}>
            <Icon name="check" size={13}/> Salva configurazione
          </button>
          <button className="btn" onClick={fetchConfig} disabled={loading}>
            <Icon name="refresh" size={13}/> Ricarica
          </button>
          <span style={{marginLeft:"auto", fontSize:12,
            color: saveStatus === "ok" ? "var(--green)" : saveStatus === "error" ? "var(--red)" : "var(--text-faint)"}}>
            {saveStatus === "ok" ? "✓ Salvato" : saveStatus === "error" ? "✗ Errore" : ""}
          </span>
        </div>
      </div>
    </div>
  );
}

// ────────── Logs ──────────
const _LOG_TOOLS = [
  { id: "tool1", label: "Tool1",  sub: "Collector"  },
  { id: "tool2", label: "Tool2",  sub: "AI Agent"   },
  { id: "tool3", label: "Tool3",  sub: "Analisi TA" },
  { id: "tool4", label: "Tool4",  sub: "News"       },
  { id: "tool5", label: "Tool5",  sub: "Calendario" },
  { id: "tool7", label: "Tool7",  sub: "Settori"    },
  { id: "tool8", label: "Tool8",  sub: "Pre-market" },
  { id: "tool9",  label: "Tool9",  sub: "Top Picks"  },
  { id: "tool10", label: "Tool10", sub: "Chain"      },
  { id: "tool11", label: "Tool11", sub: "Simul.+PM"  },
];

function LogLine({ line }) {
  const cls = line.level === "ERROR" ? "log-line log-line-error"
            : line.level === "WARN"  ? "log-line log-line-warn"
            : line.level === "DEBUG" ? "log-line log-line-debug"
            : "log-line log-line-info";
  const badgeCls = line.level === "ERROR" ? "log-badge log-badge-error"
                 : line.level === "WARN"  ? "log-badge log-badge-warn"
                 : line.level === "DEBUG" ? "log-badge log-badge-debug"
                 : "log-badge log-badge-info";
  return (
    <div className={cls}>
      <span className="log-ts">{line.ts || "··:··:··"}</span>
      <span className={badgeCls}>{line.level}</span>
      <span className="log-msg">{line.msg}</span>
    </div>
  );
}

function LogsView() {
  const [data, setData]           = useState({});
  const [activeTool, setActiveTool] = useState("tool2");
  const [filter, setFilter]       = useState("all"); // "all" | "errors"
  const [loading, setLoading]     = useState(true);
  const containerRef = React.useRef(null);

  const fetchLogs = () => {
    fetch("/api/logs/all")
      .then(r => r.json())
      .then(d => { setData(d); setLoading(false); })
      .catch(() => setLoading(false));
  };

  useEffect(() => {
    fetchLogs();
    const t = setInterval(fetchLogs, 30000);
    return () => clearInterval(t);
  }, []);

  // scroll to bottom when tool or data changes
  useEffect(() => {
    if (containerRef.current) {
      containerRef.current.scrollTop = containerRef.current.scrollHeight;
    }
  }, [activeTool, data]);

  const current  = data[activeTool] || { lines: [], error_count: 0, last_updated: null };
  const lines    = filter === "errors"
    ? (current.lines || []).filter(l => l.level === "ERROR" || l.level === "WARN")
    : (current.lines || []);

  // download current tool log as plain text
  const downloadLog = () => {
    const txt = (current.lines || []).map(l => `${l.ts} [${l.level}] ${l.msg}`).join("\n");
    const blob = new Blob([txt], { type: "text/plain" });
    const a = document.createElement("a"); a.href = URL.createObjectURL(blob);
    a.download = `bvf-${activeTool}.log`; a.click();
  };

  return (
    <div className="content log-view" data-screen-label="07 Log">

      {/* ── Tool tabs ── */}
      <div className="log-tool-tabs">
        {_LOG_TOOLS.map(t => {
          const td = data[t.id] || {};
          const errs = td.error_count || 0;
          return (
            <button
              key={t.id}
              className={"log-tool-tab" + (activeTool === t.id ? " active" : "")}
              onClick={() => { setActiveTool(t.id); setFilter("all"); }}
            >
              <span className="log-tab-label">{t.label}</span>
              <span className="log-tab-sub">{t.sub}</span>
              {errs > 0 && <span className="log-err-badge">{errs}</span>}
            </button>
          );
        })}
      </div>

      {/* ── Toolbar ── */}
      <div className="log-toolbar">
        <div className="log-filter-group">
          <button
            className={"btn btn-sm" + (filter === "all" ? " active" : "")}
            onClick={() => setFilter("all")}
          >
            Tutti
            <span className="log-count-chip">{(current.lines || []).length}</span>
          </button>
          <button
            className={"btn btn-sm" + (filter === "errors" ? " active" : "")}
            onClick={() => setFilter("errors")}
          >
            <span style={{color: "var(--red)"}}>● </span>Warning / Errori
            {(current.error_count || 0) > 0 &&
              <span className="log-err-badge">{current.error_count}</span>}
          </button>
        </div>
        <div style={{marginLeft:"auto", display:"flex", gap:8, alignItems:"center"}}>
          {current.last_updated &&
            <span className="log-last-updated">Ultimo run: {current.last_updated}</span>}
          <button className="btn btn-sm" onClick={fetchLogs}>
            <Icon name="refresh" size={13}/> Aggiorna
          </button>
          <button className="btn btn-sm" onClick={downloadLog}>
            <Icon name="download" size={13}/> Scarica
          </button>
        </div>
      </div>

      {/* ── Log lines ── */}
      <div className="log-lines" ref={containerRef}>
        {loading ? (
          <div className="log-empty">Caricamento log…</div>
        ) : lines.length === 0 ? (
          <div className="log-empty">
            {filter === "errors"
              ? "✓ Nessun errore nei log recenti"
              : "Nessun log disponibile per questo tool."}
          </div>
        ) : (
          lines.map((line, i) => <LogLine key={i} line={line}/>)
        )}
      </div>

    </div>
  );
}

// ────────── Admin ──────────
function AdminView({ user }) {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);

  const fetchUsers = () => {
    fetch("/api/admin/users")
      .then(r => r.json())
      .then(d => { setUsers(d.users || []); setLoading(false); })
      .catch(() => setLoading(false));
  };

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

  return (
    <div className="panel" data-screen-label="08 Admin">
      <div className="card" style={{padding:0, overflow:"hidden"}}>
        <div style={{padding:"14px 18px", borderBottom:"1px solid var(--border)", display:"flex", alignItems:"center"}}>
          <h3 style={{margin:0}}>Account ({users.length})</h3>
          <div style={{marginLeft:"auto", display:"flex", gap:8}}>
            <button className="btn btn-sm"><Icon name="search" size={13}/> Cerca</button>
            <button className="btn btn-sm btn-primary"><Icon name="user" size={13}/> Aggiungi utente</button>
          </div>
        </div>
        <table className="tbl">
          <thead>
            <tr>
              <th style={{width:50}}>ID</th>
              <th>Username</th>
              <th>Ruolo</th>
              <th>2FA</th>
              <th title="Copre le spese API: il costo totale viene diviso per il numero di paganti">Pagante</th>
              <th>Telegram Chat ID</th>
              <th>Creato il</th>
              <th></th>
            </tr>
          </thead>
          <tbody>
            {users.map(u => (
              <tr key={u.id}>
                <td className="mono" style={{color:"var(--text-muted)"}}>#{u.id}</td>
                <td>
                  <div style={{display:"flex", alignItems:"center", gap:10}}>
                    <div className="sb-avatar" style={{width:24, height:24, fontSize:10.5}}>{u.username.slice(0,1).toUpperCase()}</div>
                    <strong>{u.username}</strong>
                  </div>
                </td>
                <td>
                  <span className={`badge ${u.is_admin ? "badge-admin" : "badge-user"}`}>
                    {u.is_admin ? "Admin" : "Utente"}
                  </span>
                </td>
                <td>
                  {u.totp_enabled
                    ? <span className="badge badge-on"><Icon name="check" size={11}/> Attivo</span>
                    : <span className="badge badge-off">Disattivato</span>}
                </td>
                <td>
                  <button className={`badge ${u.pays_costs ? "badge-on" : "badge-off"}`}
                    style={{cursor:"pointer", border:"none", font:"inherit"}}
                    title="Clic per cambiare: se è pagante, concorre a dividere la spesa totale"
                    onClick={async () => {
                      const r = await fetch("/api/admin/set-pays", {
                        method:"POST", headers:{"Content-Type":"application/json"},
                        body: JSON.stringify({ username: u.username, pays: !u.pays_costs }),
                      }).then(r => r.json()).catch(() => null);
                      if (r && r.users) setUsers(r.users); else fetchUsers();
                    }}>
                    {u.pays_costs ? <><Icon name="check" size={11}/> Sì</> : "No"}
                  </button>
                </td>
                <td className="mono" style={{color: u.telegram_chat_id ? "var(--text)" : "var(--text-faint)"}}>
                  {u.telegram_chat_id || "—"}
                </td>
                <td className="mono" style={{color:"var(--text-muted)"}}>{u.created_at}</td>
                <td>
                  {u.username !== user.username ? (
                    <button className="btn btn-sm btn-danger" onClick={async () => {
                      if (!confirm(`Eliminare utente ${u.username}?`)) return;
                      const fd = new FormData();
                      fd.append("username", u.username);
                      await fetch("/admin/users/delete", { method: "POST", body: fd });
                      fetchUsers();
                    }}>
                      <Icon name="trash" size={12}/>
                    </button>
                  ) : (
                    <span style={{color:"var(--text-faint)", fontSize:12}}>(tu)</span>
                  )}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>

      <div className="card">
        <h3>Come trovare il Chat ID di Telegram</h3>
        <ol style={{paddingLeft:20, color:"var(--text-muted)", fontSize:13, lineHeight:1.85}}>
          <li>Apri Telegram e manda un qualsiasi messaggio al bot configurato</li>
          <li>Visita <span className="mono" style={{color:"var(--accent)"}}>https://api.telegram.org/bot&lt;TOKEN&gt;/getUpdates</span></li>
          <li>Cerca il campo <span className="mono" style={{color:"var(--accent)"}}>"id"</span> dentro <span className="mono">"chat"</span> — è il tuo Chat ID</li>
          <li>Incollalo nella colonna corrispondente e salva</li>
        </ol>
      </div>
    </div>
  );
}

// ────────── Screener ──────────

const _SCREENER_SIG_COLOR = {
  BUY:         { color:"var(--green)",      bg:"var(--green-soft)" },
  STRONG_BUY:  { color:"var(--green)",      bg:"var(--green-soft)" },
  SELL:        { color:"var(--red)",        bg:"var(--red-soft)" },
  STRONG_SELL: { color:"var(--red)",        bg:"var(--red-soft)" },
  HOLD:        { color:"var(--amber)",      bg:"var(--amber-soft)" },
  "—":         { color:"var(--text-faint)", bg:"transparent" },
};

function ScreenerSigBadge({ sig }) {
  if (!sig || sig === "—") return <span style={{color:"var(--text-faint)"}}>—</span>;
  const sc = _SCREENER_SIG_COLOR[sig] || { color:"var(--text-muted)", bg:"var(--surface)" };
  return (
    <span style={{
      display:"inline-flex", padding:"2px 7px", borderRadius:3,
      background: sc.bg, color: sc.color,
      fontWeight:700, fontSize:11, letterSpacing:".03em", whiteSpace:"nowrap",
    }}>
      {sig.replace(/_/g," ")}
    </span>
  );
}

function ScreenerConvDots({ buy, sell, consensus }) {
  const n = Math.max(buy, sell);
  const color = consensus === "BUY" ? "var(--green)" : consensus === "SELL" ? "var(--red)" : "var(--amber)";
  const sc = _SCREENER_SIG_COLOR[consensus] || _SCREENER_SIG_COLOR["HOLD"];
  // 2 pallini: i tool che firmano il consenso ora sono TA + News (Tool2 rimosso)
  return (
    <span style={{display:"flex", gap:4, alignItems:"center"}}>
      {[0,1].map(j => (
        <span key={j} className="screener-conv-dot" style={{
          background: j < n ? color : "var(--surface)",
          border: `1.5px solid ${j < n ? color : "var(--border)"}`,
        }}/>
      ))}
      <span style={{marginLeft:5, fontSize:11, color: sc.color, fontWeight:700}}>
        {consensus}
      </span>
    </span>
  );
}

const _SORT_OPTIONS = [
  { key:"convergence", label:"Convergenza" },
  { key:"avg24",       label:"+24h" },
  { key:"deltaPct",    label:"Δ%" },
  { key:"t3score",     label:"T3 Score" },
  { key:"symbol",      label:"Ticker" },
  { key:"price",       label:"Prezzo" },
];

// Sparkline della SESSIONE di oggi (come l'1G di Investing) dalle candele 5m
// già in memoria — zero fetch. "Oggi" = ultimo blocco CONTIGUO di candele
// (gap > 40min = notte/cambio sessione, stesso trucco della Dashboard);
// l'ultimo punto segue il prezzo live quando il WebSocket è attivo.
function Spark24h({ candles, livePrice }) {
  if (!candles || candles.length < 2)
    return <span style={{color:"var(--text-faint)", fontSize:11}}>—</span>;
  let start = candles.length - 1;
  while (start > 0 && (candles[start].time - candles[start - 1].time) <= 2400) start--;
  let pts = candles.slice(start);
  if (pts.length < 2) {
    // sessione appena iniziata: ripiega sulle ultime 24h per non mostrare il vuoto
    const cutoff = candles[candles.length - 1].time - 86400;
    pts = candles.filter(c => c.time >= cutoff);
  }
  if (pts.length < 2)
    return <span style={{color:"var(--text-faint)", fontSize:11}}>—</span>;
  const vals = pts.map(c => c.close);
  if (livePrice) vals.push(livePrice);
  const min = Math.min(...vals), max = Math.max(...vals);
  const W = 96, H = 34, P = 2;
  const span = (max - min) || 1;
  const X = i => P + i * (W - 2 * P) / (vals.length - 1);
  const Y = v => H - P - (v - min) * (H - 2 * P) / span;
  const line = vals.map((v, i) => `${i ? "L" : "M"}${X(i).toFixed(1)},${Y(v).toFixed(1)}`).join("");
  const up = vals[vals.length - 1] >= vals[0];
  const col = up ? "var(--green)" : "var(--red)";
  const fill = `${line}L${X(vals.length - 1).toFixed(1)},${H}L${X(0).toFixed(1)},${H}Z`;
  return (
    <svg width="100%" height={H} viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" style={{display:"block"}}>
      <path d={fill} fill={col} opacity="0.13"/>
      <path d={line} fill="none" stroke={col} strokeWidth="1.5" vectorEffect="non-scaling-stroke"/>
    </svg>
  );
}

function ScreenerCard({ r, onNavigate }) {
  const scoreColor = s => s >= 65 ? "var(--green)" : s < 50 ? "var(--red)" : "var(--text-muted)";
  const impColor = { CRITICAL:"var(--red)", HIGH:"var(--amber)", MEDIUM:"var(--text-muted)" };
  const imp = r.t4.news_impact && r.t4.news_impact !== "—" ? r.t4.news_impact : null;
  return (
    <div className="screener-card" onClick={() => onNavigate(r.symbol)}>
      <div className="screener-card-head">
        <div style={{minWidth:0}}>
          <span className="screener-card-sym">{r.symbol}</span>
          {r.name && r.name !== r.symbol &&
            <div className="screener-card-name">{r.name}</div>}
        </div>
        <div className="screener-card-price-wrap">
          <span className="screener-card-price">{r.price > 0 ? r.price.toFixed(2) : "—"}</span>
          <span className={`screener-card-delta ${r.deltaPct >= 0 ? "pos" : "neg"}`}>
            {r.deltaPct >= 0 ? "+" : ""}{r.deltaPct.toFixed(2)}%{r.live ? " ●" : ""}
          </span>
        </div>
      </div>

      <div className="screener-card-sigs">
        <div className="screener-card-sig-col">
          <span className="screener-card-sig-label">TA</span>
          <ScreenerSigBadge sig={r.t3.signal}/>
          <span className="screener-card-sig-sub" style={{color: scoreColor(r.t3.score_total||0)}}>
            {r.t3.score_total != null ? r.t3.score_total : "—"}
          </span>
        </div>
        <div className="screener-card-sig-col">
          <span className="screener-card-sig-label">News</span>
          <ScreenerSigBadge sig={r.t4.signal}/>
          <span className="screener-card-sig-sub"
                style={{color: imp ? (impColor[imp] || "var(--text-muted)") : "var(--text-faint)"}}>
            {imp ? imp.toLowerCase() : "—"}
          </span>
        </div>
        <div className="screener-card-sig-col screener-card-spark">
          <span className="screener-card-sig-label">Oggi</span>
          <Spark24h candles={r.candles} livePrice={r.live ? r.price : null}/>
        </div>
      </div>

      <div className="screener-card-foot">
        <ScreenerConvDots buy={r.buy} sell={r.sell} consensus={r.consensus}/>
        <span className={`screener-card-avg24 ${r.avg24 >= 0 ? "pos" : "neg"}`}>
          {r.avg24 >= 0 ? "+" : ""}{r.avg24.toFixed(2)}% 24h
        </span>
      </div>
    </div>
  );
}

function ScreenerView({ onNavigate }) {
  const allData   = window.ALL_DATA  || {};
  const watchlist = window.WATCHLIST || [];

  const [sortKey, setSortKey] = useState("convergence");
  const [sortDir, setSortDir] = useState("desc");
  const [liveQ, setLiveQ]     = useState({});      // {SYM: {price, dp, live}}
  const [wsOn, setWsOn]       = useState(false);

  // Prezzi live (stesso feed della tab Watchlist): poll leggero finché la Home
  // è visibile; a mercato chiuso/WS giù i valori restano quelli del daemon.
  useEffect(() => {
    const syms = (window.WATCHLIST || []).map(w => w.symbol);
    if (!syms.length) return;
    let stop = false;
    const poll = () => {
      if (document.visibilityState !== "visible") return;
      fetch(`/api/quotes?symbols=${encodeURIComponent(syms.join(","))}`)
        .then(r => r.json())
        .then(d => {
          if (stop) return;
          setLiveQ(d.quotes || {});
          setWsOn(d.ws === "connected" && !!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); };
  }, []);

  const rows = watchlist.map(w => {
    const d  = allData[w.symbol] || {};
    const t3 = d.tool3_info || {};
    const t4 = d.tool4_info || {};
    const lq = liveQ[w.symbol];
    const sigs = [t3.signal, t4.signal].filter(Boolean);
    const buy  = sigs.filter(s => s === "BUY"  || s === "STRONG_BUY").length;
    const sell = sigs.filter(s => s === "SELL" || s === "STRONG_SELL").length;
    const consensus = buy > sell ? "BUY" : sell > buy ? "SELL" : "HOLD";
    // media solo sui forecast presenti (Tool2 rimosso: dividere per 3 sottostimava)
    const fcs = [t3.h24, t4.fc_24h].filter(v => v != null && v !== 0);
    const avg24 = fcs.length ? fcs.reduce((a, b) => a + b, 0) / fcs.length : 0;
    return {
      symbol: w.symbol, name: w.name || "",
      price: (lq && lq.live && lq.price) ? lq.price : (w.price || 0),
      deltaPct: (lq && lq.dp != null) ? lq.dp : (w.deltaPct || 0),
      live: !!(lq && lq.live),
      candles: d.candles || [],
      t3, t4, buy, sell, consensus,
      convergence: Math.max(buy, sell),
      avg24,
      t3score: t3.score_total || 0,
    };
  });

  const toggleSort = key => {
    if (sortKey === key) setSortDir(d => d === "desc" ? "asc" : "desc");
    else { setSortKey(key); setSortDir("desc"); }
  };

  const sorted = [...rows].sort((a, b) => {
    const va = a[sortKey]; const vb = b[sortKey];
    if (va === vb) return 0;
    const cmp = typeof va === "string" ? va.localeCompare(vb) : va - vb;
    return sortDir === "desc" ? -cmp : cmp;
  });

  return (
    <div className="screener-wrap" data-screen-label="02 Screener">
      <div className="screener-toolbar">
        <span style={{fontSize:12, color:"var(--text-muted)", flexShrink:0}}>
          <strong style={{color:"var(--text)"}}>{watchlist.length}</strong> ticker
          {wsOn && <span style={{color:"var(--green)", marginLeft:8}}>● live</span>}
        </span>
        <div className="screener-sort-chips">
          {_SORT_OPTIONS.map(o => (
            <button key={o.key}
              className={`btn btn-sm${sortKey === o.key ? " btn-primary" : ""}`}
              onClick={() => toggleSort(o.key)}>
              {o.label}{sortKey === o.key ? (sortDir === "desc" ? " ↓" : " ↑") : ""}
            </button>
          ))}
        </div>
      </div>

      {watchlist.length === 0 ? (
        <div style={{display:"flex", alignItems:"center", justifyContent:"center", flex:1,
          flexDirection:"column", gap:8, color:"var(--text-muted)"}}>
          <Icon name="candles" size={28} strokeWidth={1.2}/>
          <span style={{fontSize:13}}>Nessun dato disponibile. Avvia il daemon.</span>
        </div>
      ) : (
        <div className="screener-grid">
          {sorted.map(r => <ScreenerCard key={r.symbol} r={r} onNavigate={onNavigate}/>)}
        </div>
      )}
    </div>
  );
}

// ────────── Sectors (Tool7) ──────────

const _SECTOR_TIMEFRAMES = [
  { key: "pct_1h", label: "1h" },
  { key: "pct_1d", label: "1d" },
  { key: "pct_5d", label: "5d" },
  { key: "pct_1m", label: "1m" },
];

const _SECTOR_ALERT_LABEL = {
  intraday_move: "Mossa giornaliera",
  weekly_move:   "Mossa settimanale",
  rank_jump:     "Rotazione veloce",
  daily_summary: "Riepilogo",
};

// Colori per le 11 linee del trend chart
const _ETF_COLORS = [
  "#60a5fa","#34d399","#f87171","#fbbf24","#a78bfa",
  "#38bdf8","#fb923c","#4ade80","#f472b6","#818cf8","#2dd4bf",
];

function _sectorHeatColor(pct) {
  if (pct == null || isNaN(pct)) return "rgba(120,120,120,0.18)";
  const c = Math.max(-3, Math.min(3, parseFloat(pct)));
  const hue   = 60 + c * 20;
  const light = 28 + Math.min(18, Math.abs(c) * 5);
  return `hsl(${hue}, 65%, ${light}%)`;
}

function _sectorFmtPct(v) {
  if (v == null || isNaN(v)) return "—";
  const f = parseFloat(v);
  return `${f >= 0 ? "+" : ""}${f.toFixed(2)}%`;
}

function _sectorFmtTs(ts) {
  if (!ts) return "—";
  try {
    const d = new Date(ts.includes("T") ? ts : ts.replace(" ", "T"));
    return d.toLocaleString("it-IT", { day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit" });
  } catch { return ts; }
}

// Tronca un nome per stare in ~maxW px a una data fontSize (stima ~0.58·fs per char).
function _clipName(str, maxW, fs) {
  const max = Math.max(2, Math.floor(maxW / (fs * 0.58)));
  return str.length > max ? str.slice(0, max - 1) + "…" : str;
}

// ── Bar chart (ranking) con accordion drill-down a 3 livelli ─────────────────
// Settore (depth 0) → click → sottosettori (1) indentati. Sottosettore CON
// specializzazioni → click → specializzazioni (2). Foglia (sottosettore senza spec,
// o specializzazione) → click → HoldingsPanel (società) a destra. has_children pilota
// l'espansione; l'indentazione segue la profondità.
function SectorsBarChart({
  snapshot, timeframe,
  expandedParent, expandedRows, expandedLoading,
  expandedChild, expandedChildRows, expandedChildLoading,
  activeLeaf,
  onToggleParent, onToggleSub, onPickLeaf,
}) {
  const sortByTf = (arr) => [...(arr || [])].sort((a, b) => {
    const av = a[timeframe], bv = b[timeframe];
    if (av == null) return 1; if (bv == null) return -1;
    return parseFloat(bv) - parseFloat(av);
  });
  const sorted = sortByTf(snapshot);

  // Righe renderizzate: settore (0) → sottosettore (1) → specializzazione (2).
  const displayRows = [];
  sorted.forEach(sec => {
    displayRows.push({ kind: "node", depth: 0, data: sec });
    if (sec.etf === expandedParent) {
      if (expandedLoading) displayRows.push({ kind: "loading", depth: 1, key: `l1-${sec.etf}` });
      else if (expandedRows && expandedRows.length) {
        sortByTf(expandedRows).forEach(sub => {
          displayRows.push({ kind: "node", depth: 1, data: sub });
          if (sub.etf === expandedChild) {
            if (expandedChildLoading) displayRows.push({ kind: "loading", depth: 2, key: `l2-${sub.etf}` });
            else if (expandedChildRows && expandedChildRows.length)
              sortByTf(expandedChildRows).forEach(sp => displayRows.push({ kind: "node", depth: 2, data: sp }));
            else displayRows.push({ kind: "empty", depth: 2, key: `e2-${sub.etf}` });
          }
        });
      } else displayRows.push({ kind: "empty", depth: 1, key: `e1-${sec.etf}` });
    }
  });

  // Scala unificata su tutte le righe visibili (settori + figli + nipoti espansi).
  const maxAbs = Math.max(
    ...displayRows.filter(r => r.data).map(r => Math.abs(parseFloat(r.data[timeframe]) || 0)),
    0.01
  );

  // ── Responsive: misura la larghezza con ResizeObserver (BAR_MAX dinamico). ──
  const containerRef = useRef(null);
  const [containerW, setContainerW] = useState(680);
  useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const measure = () => { const w = el.clientWidth; if (w > 0) setContainerW(w); };
    measure();
    if (typeof ResizeObserver !== "undefined") {
      const ro = new ResizeObserver(measure);
      ro.observe(el);
      return () => ro.disconnect();
    }
    window.addEventListener("resize", measure);
    return () => window.removeEventListener("resize", measure);
  }, []);

  const ROW_H = 34, PAD = 4, INDENT = 20;
  const narrow  = containerW < 540;
  const LABEL_W = narrow ? 172 : 232;
  const VAL_W   = narrow ? 58 : 64;
  const BAR_MAX = Math.max(70, containerW - LABEL_W - VAL_W);
  const W = LABEL_W + BAR_MAX + VAL_W;
  const H = displayRows.length * ROW_H;

  return (
    <div ref={containerRef} style={{width:"100%"}}>
      <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`}
           preserveAspectRatio="xMinYMin meet"
           style={{display:"block", width:"100%"}}>
        {displayRows.map((row, i) => {
          const y = i * ROW_H;
          const indent = row.depth * INDENT;

          if (row.kind === "loading" || row.kind === "empty") {
            const msg = row.kind === "loading" ? "Caricamento…" : "Nessun sotto-livello configurato";
            return (
              <g key={row.key || `m-${i}`}>
                <rect x={0} y={y} width={W} height={ROW_H} fill="rgba(255,255,255,0.02)"/>
                <text x={PAD+22+indent} y={y+ROW_H/2+4} fontSize={11}
                  fill="var(--text-faint)" fontStyle="italic">{msg}</text>
              </g>
            );
          }

          const m          = row.data;
          const v          = parseFloat(m[timeframe]);
          const barW       = isNaN(v) ? 0 : Math.round((Math.abs(v) / maxAbs) * BAR_MAX * 0.9);
          const isPos      = v >= 0;
          const barColor   = isNaN(v) ? "rgba(120,120,120,0.3)" : isPos ? "#22c55e" : "#ef4444";
          const isParent   = row.depth === 0;
          const isSub      = row.depth === 1;
          const isActive   = m.etf === activeLeaf;
          const isExpanded = (isParent && m.etf === expandedParent) || (isSub && m.etf === expandedChild);
          // settori sempre cliccabili (espandono, o aprono holdings per XLU);
          // sottosettori espandibili solo con specializzazioni; le spec sono foglie.
          const expandable = isParent ? true : (isSub ? (m.has_children === true) : false);

          const bgFill = isActive
            ? "rgba(96,165,250,0.16)"
            : isExpanded
              ? "rgba(96,165,250,0.06)"
              : row.depth > 0
                ? "rgba(255,255,255,0.015)"
                : (i % 2 === 0 ? "rgba(255,255,255,0.025)" : "transparent");

          const handleClick = isParent
            ? () => onToggleParent && onToggleParent(m)
            : isSub
              ? () => onToggleSub && onToggleSub(m)
              : () => onPickLeaf && onPickLeaf(m);

          let glyph;
          if (isParent) glyph = isExpanded ? "▾" : (m.rank ?? "—");
          else if (expandable) glyph = isExpanded ? "▾" : "▸";
          else glyph = "·";

          const labelFs  = isParent ? 13 : isSub ? 11.5 : 10.5;
          const nameFs   = isParent ? 11 : 10.5;
          const nameX    = PAD + 64 + indent;
          const nameMaxW = Math.max(16, LABEL_W - nameX - 4);

          return (
            <g key={`${m.etf}-${row.depth}-${i}`} style={{cursor:"pointer"}} onClick={handleClick}>
              <rect x={0} y={y} width={W} height={ROW_H} fill={bgFill}/>
              {row.depth > 0 && (
                <line x1={PAD+6+(indent-INDENT)} x2={PAD+13+(indent-INDENT)}
                  y1={y+ROW_H/2} y2={y+ROW_H/2} stroke="rgba(255,255,255,0.18)" strokeWidth={1}/>
              )}
              <text x={PAD+4+indent} y={y+ROW_H/2+4} fontSize={11}
                fill={isParent ? "var(--text-faint)" : "var(--text-muted)"}>{glyph}</text>
              <text x={PAD+19+indent} y={y+ROW_H/2+4}
                fontSize={labelFs} fontWeight={isParent ? 700 : 600} fontFamily="monospace"
                fill={isParent ? "var(--text)" : "var(--text-muted)"}>{m.etf}</text>
              <text x={nameX} y={y+ROW_H/2+4} fontSize={nameFs} fill="var(--text-faint)">
                {_clipName(m.name || "", nameMaxW, nameFs)}
              </text>
              <rect x={LABEL_W} y={y+8} width={barW} height={ROW_H-16} fill={barColor} rx={3}
                opacity={isParent ? 0.9 : 0.7}/>
              <text x={LABEL_W+BAR_MAX+4} y={y+ROW_H/2+4}
                fontSize={isParent ? 12.5 : 11} fontWeight={600} fontFamily="monospace"
                fill={isNaN(v) ? "var(--text-faint)" : isPos ? "#22c55e" : "#ef4444"}>
                {isNaN(v) ? "—" : `${isPos?"+":""}${v.toFixed(2)}%`}
              </text>
            </g>
          );
        })}
      </svg>
    </div>
  );
}

// ── Multi-line trend chart ────────────────────────────────────────────────────
function SectorsTrendChart({ history, loading }) {
  const [hovered, setHovered] = useState(null); // {etf, date, pct}

  if (loading) return (
    <div style={{color:"var(--text-muted)", textAlign:"center", padding:32}}>
      Caricamento dati storici…
    </div>
  );

  const etfs = Object.keys(history);
  if (!etfs.length) return (
    <div style={{color:"var(--text-muted)", textAlign:"center", padding:32}}>
      Nessun dato storico disponibile ancora. I grafici si popoleranno con i run giornalieri.
    </div>
  );

  // Raggruppa per data (una entry per giorno = ultima del giorno)
  const byEtfByDate = {};
  for (const etf of etfs) {
    byEtfByDate[etf] = {};
    for (const row of (history[etf] || [])) {
      const d = (row.timestamp || "").slice(0, 10);
      if (d) byEtfByDate[etf][d] = parseFloat(row.pct_1d);
    }
  }

  // Date univoche ordinate
  const allDates = [...new Set(
    etfs.flatMap(e => Object.keys(byEtfByDate[e] || {}))
  )].sort();

  if (!allDates.length) return (
    <div style={{color:"var(--text-muted)", textAlign:"center", padding:32}}>
      Dati insufficienti per il grafico. Attendi qualche run.
    </div>
  );

  // Bounds Y
  const allVals = etfs.flatMap(e => Object.values(byEtfByDate[e] || {}).filter(v => !isNaN(v)));
  const yMin = Math.min(-1, ...allVals) - 0.3;
  const yMax = Math.max(1,  ...allVals) + 0.3;

  const PAD_L = 44, PAD_R = 8, PAD_T = 16, PAD_B = 28;
  const W = 580, H = 260;
  const plotW = W - PAD_L - PAD_R;
  const plotH = H - PAD_T - PAD_B;

  const xScale = i => PAD_L + (allDates.length > 1 ? (i / (allDates.length - 1)) * plotW : plotW / 2);
  const yScale = v  => PAD_T + ((yMax - v) / (yMax - yMin)) * plotH;

  // Grid Y values
  const yRange = yMax - yMin;
  const step = yRange > 10 ? 4 : yRange > 5 ? 2 : yRange > 2 ? 1 : 0.5;
  const gridYs = [];
  for (let y = Math.ceil(yMin / step) * step; y <= yMax; y = Math.round((y + step) * 10) / 10) {
    gridYs.push(y);
  }

  // X labels (ogni ~5 date se molte)
  const xStep = allDates.length > 20 ? Math.ceil(allDates.length / 8) : 1;
  const xLabels = allDates.filter((_, i) => i % xStep === 0 || i === allDates.length - 1);

  return (
    <div>
      <div style={{overflowX:"auto"}}>
        <svg width={W} height={H} style={{display:"block", minWidth:320}}>
          {/* Grid */}
          {gridYs.map(y => (
            <g key={y}>
              <line x1={PAD_L} x2={W-PAD_R} y1={yScale(y)} y2={yScale(y)}
                stroke="rgba(255,255,255,0.07)" strokeWidth={1}/>
              <text x={PAD_L-4} y={yScale(y)+4} fontSize={9.5} fill="var(--text-faint)"
                textAnchor="end">
                {y > 0 ? `+${y.toFixed(1)}` : y.toFixed(1)}%
              </text>
            </g>
          ))}
          {/* Zero line */}
          <line x1={PAD_L} x2={W-PAD_R} y1={yScale(0)} y2={yScale(0)}
            stroke="rgba(255,255,255,0.2)" strokeWidth={1} strokeDasharray="4,3"/>

          {/* X labels */}
          {xLabels.map(d => {
            const i = allDates.indexOf(d);
            const x = xScale(i);
            const parts = d.split("-");
            const label = parts.length === 3 ? `${parts[2]}/${parts[1]}` : d;
            return (
              <text key={d} x={x} y={H-PAD_B+14} fontSize={9} fill="var(--text-faint)"
                textAnchor="middle">{label}</text>
            );
          })}

          {/* Lines */}
          {etfs.map((etf, ei) => {
            const color = _ETF_COLORS[ei % _ETF_COLORS.length];
            const points = allDates.map((d, i) => {
              const v = byEtfByDate[etf][d];
              if (v == null || isNaN(v)) return null;
              return [xScale(i), yScale(v)];
            }).filter(Boolean);

            if (points.length < 2) return null;

            const path = points.map((p, i) => `${i === 0 ? "M" : "L"}${p[0].toFixed(1)},${p[1].toFixed(1)}`).join(" ");
            return (
              <g key={etf}>
                <path d={path} stroke={color} strokeWidth={1.8} fill="none" opacity={
                  hovered ? (hovered.etf === etf ? 1 : 0.2) : 0.85
                }/>
                {/* Dots on hover */}
                {hovered?.etf === etf && points.map(([px, py], pi) => (
                  <circle key={pi} cx={px} cy={py} r={3} fill={color}/>
                ))}
              </g>
            );
          })}

          {/* Transparent overlay for hover */}
          {allDates.map((d, i) => (
            <rect key={d}
              x={xScale(i) - (plotW / allDates.length / 2)}
              y={PAD_T} width={plotW / allDates.length} height={plotH}
              fill="transparent"
              style={{cursor:"crosshair"}}
              onMouseEnter={() => {
                const hovEtf = etfs.reduce((best, etf) => {
                  const v = byEtfByDate[etf][d];
                  if (v == null) return best;
                  if (!best) return etf;
                  return Math.abs(v) > Math.abs(byEtfByDate[best][d] || 0) ? etf : best;
                }, null);
                if (hovEtf) setHovered({ etf: hovEtf, date: d, pct: byEtfByDate[hovEtf][d] });
              }}
              onMouseLeave={() => setHovered(null)}
            />
          ))}
        </svg>
      </div>

      {/* Legenda */}
      <div style={{display:"flex", flexWrap:"wrap", gap:"8px 16px", marginTop:12, padding:"0 4px"}}>
        {etfs.map((etf, ei) => {
          const color = _ETF_COLORS[ei % _ETF_COLORS.length];
          const lastDate = Object.keys(byEtfByDate[etf] || {}).sort().pop();
          const lastVal  = lastDate ? byEtfByDate[etf][lastDate] : null;
          return (
            <div key={etf}
              style={{
                display:"flex", alignItems:"center", gap:6, cursor:"pointer",
                opacity: hovered && hovered.etf !== etf ? 0.4 : 1, transition:"opacity 120ms",
              }}
              onMouseEnter={() => setHovered({ etf, date: lastDate, pct: lastVal })}
              onMouseLeave={() => setHovered(null)}
            >
              <span style={{width:16, height:2.5, background:color, borderRadius:2, flexShrink:0, display:"inline-block"}}/>
              <span style={{fontSize:11.5, fontFamily:"monospace", fontWeight:600}}>{etf}</span>
              {lastVal != null && (
                <span style={{
                  fontSize:11, color: lastVal >= 0 ? "#22c55e" : "#ef4444",
                  fontFamily:"monospace",
                }}>
                  {lastVal >= 0 ? "+" : ""}{lastVal.toFixed(2)}%
                </span>
              )}
            </div>
          );
        })}
      </div>
      {hovered && (
        <div style={{marginTop:8, fontSize:12, color:"var(--text-muted)"}}>
          {hovered.date} — <strong style={{color:"var(--text)"}}>{hovered.etf}</strong>{" "}
          <span style={{fontFamily:"monospace", color: hovered.pct >= 0 ? "#22c55e" : "#ef4444"}}>
            {hovered.pct != null ? `${hovered.pct >= 0 ? "+" : ""}${hovered.pct.toFixed(2)}%` : "—"}
          </span>
        </div>
      )}
    </div>
  );
}

// ── Sparkline SVG ─────────────────────────────────────────────────────────────
function Sparkline({ data, color }) {
  if (!data || data.length < 2) return <span style={{color:"var(--text-faint)", fontSize:11}}>—</span>;
  const vals = data.map(d => parseFloat(d.pct_1d)).filter(v => !isNaN(v));
  if (vals.length < 2) return <span style={{color:"var(--text-faint)", fontSize:11}}>—</span>;
  const mn = Math.min(...vals), mx = Math.max(...vals);
  const range = mx - mn || 0.001;
  const W = 60, H = 20;
  const points = vals.map((v, i) => {
    const x = Math.round((i / (vals.length - 1)) * (W - 2) + 1);
    const y = Math.round(H - 2 - ((v - mn) / range) * (H - 4));
    return `${x},${y}`;
  }).join(" ");
  return (
    <svg width={W} height={H} style={{display:"inline-block", verticalAlign:"middle"}}>
      <polyline points={points} stroke={color} strokeWidth={1.5} fill="none"/>
    </svg>
  );
}

// ── Table with sparklines ─────────────────────────────────────────────────────
function SectorsTableView({ sorted, timeframe, history }) {
  return (
    <div style={{overflowX:"auto"}}>
      <table className="data-table" style={{width:"100%", fontSize:13}}>
        <thead>
          <tr>
            <th style={{textAlign:"left", padding:"6px 8px"}}>#</th>
            <th style={{textAlign:"left", padding:"6px 8px"}}>ETF</th>
            <th style={{textAlign:"left", padding:"6px 8px"}}>Settore</th>
            <th style={{textAlign:"right", padding:"6px 8px"}}>1h</th>
            <th style={{textAlign:"right", padding:"6px 8px"}}>1d</th>
            <th style={{textAlign:"right", padding:"6px 8px"}}>5g</th>
            <th style={{textAlign:"right", padding:"6px 8px"}}>1m</th>
            <th style={{textAlign:"right", padding:"6px 8px"}}>Δrank</th>
            <th style={{textAlign:"center", padding:"6px 8px"}}>30gg</th>
          </tr>
        </thead>
        <tbody>
          {sorted.map((m, idx) => {
            const hist = history[m.etf] || [];
            const etfColor = _ETF_COLORS[idx % _ETF_COLORS.length];
            return (
              <tr key={m.etf} style={{borderTop:"1px solid var(--border-soft)"}}>
                <td style={{padding:"6px 8px", color:"var(--text-muted)"}}>{m.rank ?? "—"}</td>
                <td style={{padding:"6px 8px", fontFamily:"monospace", fontWeight:600}}>{m.etf}</td>
                <td style={{padding:"6px 8px"}}>{m.name}</td>
                {["pct_1h","pct_1d","pct_5d","pct_1m"].map(k => (
                  <td key={k} style={{
                    padding:"6px 8px", textAlign:"right", fontFamily:"monospace",
                    color: m[k] == null ? "var(--text-faint)" : (m[k] > 0 ? "var(--green)" : m[k] < 0 ? "var(--red)" : "var(--text-muted)"),
                    fontWeight: k === timeframe ? 700 : 400,
                  }}>{_sectorFmtPct(m[k])}</td>
                ))}
                <td style={{padding:"6px 8px", textAlign:"right", color:"var(--text-muted)"}}>
                  {m.rank_delta == null ? "—" : (m.rank_delta > 0 ? `↑${m.rank_delta}` : m.rank_delta < 0 ? `↓${Math.abs(m.rank_delta)}` : "=")}
                </td>
                <td style={{padding:"6px 8px", textAlign:"center"}}>
                  <Sparkline data={hist} color={etfColor}/>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}

// ── Heatmap grid: 3 livelli (settori → sub-settori → holdings) ───────────────
// Click su una card → drill-down al livello successivo.
function HeatmapGrid({ items, level, timeframe, loading, breadcrumb, onDrill }) {
  if (loading) {
    return (
      <div style={{color:"var(--text-muted)", textAlign:"center", padding:32}}>
        Caricamento {level === "holdings" ? "holdings" : "sub-settori"}…
      </div>
    );
  }
  if (!items || items.length === 0) {
    return (
      <div style={{color:"var(--text-faint)", textAlign:"center", padding:32, fontStyle:"italic"}}>
        {level === "sub" && breadcrumb?.parent
          ? `Nessun sub-settore configurato per ${breadcrumb.parent}`
          : level === "holdings"
            ? `Holdings non disponibili per ${breadcrumb?.sub || "questo ETF"}`
            : "Nessun dato disponibile"}
      </div>
    );
  }

  // ── Livello 2: holdings ─ card colorate verde/rosso in base al pct del timeframe
  //   • Numero grande al centro = h[timeframe] (es. -1.05% per NVDA su 1d)
  //   • Peso = piccolo, in alto a destra, sempre visibile (mostra l'importanza nel ETF)
  if (level === "holdings") {
    return (
      <div style={{
        display:"grid", gridTemplateColumns:"repeat(auto-fill, minmax(165px, 1fr))", gap:10,
      }}>
        {items.map(h => {
          const v  = h[timeframe];      // pct del timeframe corrente per il ticker
          const bg = _sectorHeatColor(v); // verde/rosso intensity-based come per i settori
          return (
            <div key={h.ticker} style={{
              background:bg, borderRadius:10, padding:"14px 12px",
              color:"#fff", display:"flex", flexDirection:"column", gap:4, minHeight:90,
              border:"1px solid rgba(255,255,255,0.08)",
            }}>
              <div style={{display:"flex", alignItems:"baseline", justifyContent:"space-between"}}>
                <strong style={{fontSize:17, fontFamily:"monospace"}}>{h.ticker}</strong>
                <span style={{fontSize:11, opacity:0.85}}>peso {(h.weight_pct || 0).toFixed(1)}%</span>
              </div>
              <div style={{fontSize:11, opacity:0.88, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}>
                {h.name || ""}
              </div>
              <div style={{fontSize:21, fontWeight:700, marginTop:3}}>
                {_sectorFmtPct(v)}
              </div>
            </div>
          );
        })}
      </div>
    );
  }

  // ── Livello 0/1: settori o sub-settori (card colorate cliccabili) ──
  return (
    <div style={{
      display:"grid", gridTemplateColumns:"repeat(auto-fill, minmax(165px, 1fr))", gap:10,
    }}>
      {items.map(m => {
        const v = m[timeframe];
        const bg = _sectorHeatColor(v);
        const rd = m.rank_delta;
        return (
          <div key={m.etf} style={{
            background:bg, borderRadius:10, padding:"14px 12px",
            color:"#fff", display:"flex", flexDirection:"column", gap:4, minHeight:90,
            border:"1px solid rgba(255,255,255,0.08)",
            cursor:"pointer", transition:"transform 120ms, box-shadow 120ms",
          }}
            onClick={() => onDrill && onDrill(m)}
            onMouseEnter={e => {
              e.currentTarget.style.transform = "translateY(-2px)";
              e.currentTarget.style.boxShadow = "0 4px 14px rgba(0,0,0,0.25)";
            }}
            onMouseLeave={e => {
              e.currentTarget.style.transform = "translateY(0)";
              e.currentTarget.style.boxShadow = "none";
            }}
          >
            <div style={{display:"flex", alignItems:"baseline", justifyContent:"space-between"}}>
              <strong style={{fontSize:17, fontFamily:"monospace"}}>{m.etf}</strong>
              {level === "sectors" && (
                <span style={{fontSize:11, opacity:0.85}}>
                  #{m.rank ?? "—"}
                  {rd != null && rd !== 0 && (
                    <span style={{marginLeft:3}}>{rd > 0 ? `↑${rd}` : `↓${Math.abs(rd)}`}</span>
                  )}
                </span>
              )}
            </div>
            <div style={{fontSize:11, opacity:0.88, overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}>
              {m.name || ""}
            </div>
            <div style={{fontSize:21, fontWeight:700, marginTop:3}}>
              {_sectorFmtPct(v)}
            </div>
          </div>
        );
      })}
    </div>
  );
}

// ── Holdings side panel (per il Ranking) ─────────────────────────────────────
function HoldingsPanel({ parentEtf, subEtf, rows, loading, onClose, timeframe }) {
  const maxWeight = Math.max(...((rows || []).map(r => r.weight_pct || 0)), 1);
  const isSelf = parentEtf && subEtf && parentEtf === subEtf;  // skip-level (XLU diretto)
  return (
    <div className="card" style={{padding:12}}>
      <div style={{display:"flex", alignItems:"center", justifyContent:"space-between", marginBottom:10, gap:8}}>
        <div style={{minWidth:0}}>
          {!isSelf && (
            <div style={{fontSize:11, color:"var(--text-faint)"}}>{parentEtf || "—"} ›</div>
          )}
          <div style={{fontWeight:700, fontFamily:"monospace"}}>{subEtf} — Top holdings</div>
        </div>
        <button onClick={onClose} className="btn btn-sm"
          style={{padding:"3px 9px", fontSize:14, lineHeight:1}} title="Chiudi">×</button>
      </div>
      {loading ? (
        <div style={{color:"var(--text-muted)", textAlign:"center", padding:"24px 8px", fontSize:13}}>
          Caricamento holdings…
        </div>
      ) : !rows || rows.length === 0 ? (
        <div style={{color:"var(--text-faint)", textAlign:"center", padding:"24px 8px", fontSize:13, fontStyle:"italic"}}>
          Holdings non disponibili per {subEtf}
        </div>
      ) : (
        <div style={{display:"flex", flexDirection:"column", gap:4}}>
          {rows.map((r, i) => {
            const wFrac = Math.min(1, (r.weight_pct || 0) / maxWeight);
            const v = r[timeframe];
            const isPos = v != null && !isNaN(v) && parseFloat(v) >= 0;
            const pctColor = (v == null || isNaN(v))
              ? "var(--text-faint)"
              : isPos ? "#22c55e" : "#ef4444";
            const pctLabel = (v == null || isNaN(v))
              ? "—"
              : `${isPos?"+":""}${parseFloat(v).toFixed(2)}%`;
            return (
              <div key={`${r.ticker}-${i}`} style={{
                position:"relative", padding:"6px 8px", borderRadius:6,
                background:"rgba(255,255,255,0.025)",
                overflow:"hidden",
              }}>
                {/* Barra di peso (sfondo) */}
                <div style={{
                  position:"absolute", left:0, top:0, bottom:0, width:`${wFrac*100}%`,
                  background:"linear-gradient(90deg, rgba(96,165,250,0.22), rgba(96,165,250,0.04))",
                  borderRadius:6,
                }}/>
                {/* Contenuto sopra la barra */}
                <div style={{position:"relative", display:"flex", alignItems:"center", gap:8, fontSize:12.5}}>
                  <strong style={{fontFamily:"monospace", minWidth:54, color:"var(--text)"}}>{r.ticker}</strong>
                  <span style={{flex:1, color:"var(--text-muted)", overflow:"hidden", textOverflow:"ellipsis", whiteSpace:"nowrap"}}>
                    {r.name}
                  </span>
                  {/* Pct del timeframe corrente (verde/rosso) */}
                  <span style={{
                    fontFamily:"monospace", color: pctColor, fontWeight:600,
                    minWidth:56, textAlign:"right",
                  }}>
                    {pctLabel}
                  </span>
                  {/* Peso statico */}
                  <span style={{fontFamily:"monospace", color:"var(--text-muted)", minWidth:50, textAlign:"right"}}>
                    {(r.weight_pct || 0).toFixed(2)}%
                  </span>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── Main SectorsView ──────────────────────────────────────────────────────────
function SectorsView() {
  const [snapshot, setSnapshot]   = useState([]);
  const [themes, setThemes]       = useState([]);
  const [timeframe, setTimeframe] = useState("pct_1d");
  const [chartView, setChartView] = useState("heatmap");
  const [loading, setLoading]     = useState(true);
  const [error, setError]         = useState(null);
  const [runStatus, setRunStatus] = useState(null);

  // Heatmap: drill ricorsivo a profondità variabile
  // (settore → sottosettore → specializzazione → società). `path` = nodi attraversati;
  // ogni entry porta le proprie righe → back/jump senza refetch.
  const [heatmapNav, setHeatmapNav] = useState({ path: [] });  // { path:[{etf,name,rows,isLeaf,loading}] }

  // Ranking: accordion a 2 livelli espandibili (settore → sottosettore → specializzazione)
  // + pannello holdings (società) a destra sulla foglia.
  const [rankExpanded, setRankExpanded]           = useState(null);  // { parentEtf, rows, loading } — sottosettori
  const [rankExpandedChild, setRankExpandedChild] = useState(null);  // { parentEtf, childEtf, rows, loading } — specializzazioni
  const [rankHoldings, setRankHoldings]           = useState(null);  // { parentEtf, subEtf, leafEtf, rows, loading }

  // Responsive
  const [isMobile, setIsMobile] = useState(typeof window !== "undefined" && window.innerWidth < 760);
  useEffect(() => {
    const update = () => setIsMobile(window.innerWidth < 760);
    window.addEventListener("resize", update);
    return () => window.removeEventListener("resize", update);
  }, []);

  const abortRef = useRef(null);

  const load = async () => {
    try {
      const sr = await fetch("/api/sectors").then(r => r.json());
      if (sr.snapshot) setSnapshot(sr.snapshot);
      if (sr.themes) setThemes(sr.themes);
      setError(null);
    } catch(e) { setError(String(e)); }
    finally { setLoading(false); }
  };

  useEffect(() => {
    load();
    const t = setInterval(load, 60000);
    return () => clearInterval(t);
  }, []);

  // Reset drill-down quando cambio vista (heatmap ↔ ranking)
  useEffect(() => {
    setHeatmapNav({ path: [] });
    setRankExpanded(null);
    setRankExpandedChild(null);
    setRankHoldings(null);
    if (abortRef.current) { abortRef.current.abort(); abortRef.current = null; }
  }, [chartView]);

  // ── Fetch helpers con AbortController ──
  // Drill generico: figli del nodo (sottosettori o specializzazioni) — ognuno con
  // has_children per pilotare il livello successivo. Ritorna direttamente l'array.
  const fetchChildren = async (parentEtf) => {
    if (abortRef.current) abortRef.current.abort();
    const ctrl = new AbortController();
    abortRef.current = ctrl;
    const r = await fetch(`/api/sectors/drilldown?etf=${encodeURIComponent(parentEtf)}`, { signal: ctrl.signal });
    const j = await r.json();
    return j.children || j.subsectors || [];
  };

  const fetchHoldings = async (etf) => {
    if (abortRef.current) abortRef.current.abort();
    const ctrl = new AbortController();
    abortRef.current = ctrl;
    const r = await fetch(`/api/sectors/holdings?etf=${encodeURIComponent(etf)}`, { signal: ctrl.signal });
    return await r.json();
  };

  // ── Heatmap navigation (drill ricorsivo, profondità variabile) ──
  // Regola: se il nodo ha figli (settore→has_subsectors, livelli interni→has_children)
  // si scende di un livello (fetch children); altrimenti è una foglia → società (holdings).
  const _isLeafItem = (item, atRoot) =>
    atRoot ? (item.has_subsectors === false) : (item.has_children === false);

  const onHeatmapDrill = async (item) => {
    const atRoot = heatmapNav.path.length === 0;
    const leaf = _isLeafItem(item, atRoot);
    setHeatmapNav(s => ({ path: [...s.path, { etf: item.etf, name: item.name, rows: null, isLeaf: leaf, loading: true }] }));
    const patch = (rows) => setHeatmapNav(s => {
      const path = [...s.path];
      const last = path[path.length - 1];
      if (last && last.etf === item.etf) path[path.length - 1] = { ...last, rows, loading: false };
      return { path };
    });
    try {
      const rows = leaf ? ((await fetchHoldings(item.etf)).holdings || []) : (await fetchChildren(item.etf));
      patch(rows);
    } catch (e) {
      if (e.name !== "AbortError") patch([]);
    }
  };

  // Torna al livello con `keepLen` nodi nel path (0 = settori root).
  const onHeatmapJump = (keepLen) => {
    if (abortRef.current) { abortRef.current.abort(); abortRef.current = null; }
    setHeatmapNav(s => ({ path: s.path.slice(0, Math.max(0, keepLen)) }));
  };
  const onHeatmapBack = () => onHeatmapJump(heatmapNav.path.length - 1);

  // ── Ranking accordion a 3 livelli (settore → sottosettore → specializzazione) ──
  //    + pannello holdings (società) a destra sulla foglia cliccata.
  const _openHoldings = async (leafEtf, parentEtf) => {
    setRankHoldings({ parentEtf, subEtf: leafEtf, leafEtf, rows: null, loading: true });
    try {
      const data = await fetchHoldings(leafEtf);
      setRankHoldings(s => s && s.leafEtf === leafEtf ? { ...s, rows: data.holdings || [], loading: false } : s);
    } catch (e) {
      if (e.name !== "AbortError") setRankHoldings(s => s && s.leafEtf === leafEtf ? { ...s, rows: [], loading: false } : s);
    }
  };

  // Livello 1: click su un SETTORE
  const onRankToggle = async (sector) => {
    // Settore senza sottosettori (XLU) → apre/chiude direttamente le società
    if (sector.has_subsectors === false) {
      if (rankHoldings && rankHoldings.leafEtf === sector.etf) { setRankHoldings(null); return; }
      setRankExpanded(null); setRankExpandedChild(null);
      _openHoldings(sector.etf, sector.etf);
      return;
    }
    if (rankExpanded && rankExpanded.parentEtf === sector.etf) {
      setRankExpanded(null); setRankExpandedChild(null); setRankHoldings(null); return;
    }
    setRankExpanded({ parentEtf: sector.etf, rows: null, loading: true });
    setRankExpandedChild(null); setRankHoldings(null);
    try {
      const rows = await fetchChildren(sector.etf);
      setRankExpanded({ parentEtf: sector.etf, rows, loading: false });
    } catch (e) {
      if (e.name !== "AbortError") setRankExpanded({ parentEtf: sector.etf, rows: [], loading: false });
    }
  };

  // Livello 2: click su un SOTTOSETTORE
  const onRankToggleSub = async (sub) => {
    // Sottosettore foglia (niente specializzazioni) → società direttamente
    if (sub.has_children === false) {
      if (rankHoldings && rankHoldings.leafEtf === sub.etf) { setRankHoldings(null); return; }
      setRankExpandedChild(null);
      _openHoldings(sub.etf, rankExpanded?.parentEtf);
      return;
    }
    // Ha specializzazioni → toggle accordion livello 3
    if (rankExpandedChild && rankExpandedChild.childEtf === sub.etf) {
      setRankExpandedChild(null); setRankHoldings(null); return;
    }
    setRankExpandedChild({ parentEtf: rankExpanded?.parentEtf, childEtf: sub.etf, rows: null, loading: true });
    setRankHoldings(null);
    try {
      const rows = await fetchChildren(sub.etf);
      setRankExpandedChild({ parentEtf: rankExpanded?.parentEtf, childEtf: sub.etf, rows, loading: false });
    } catch (e) {
      if (e.name !== "AbortError") setRankExpandedChild({ parentEtf: rankExpanded?.parentEtf, childEtf: sub.etf, rows: [], loading: false });
    }
  };

  // Livello 3: click su una SPECIALIZZAZIONE (foglia) → società
  const onRankPickLeaf = async (spec) => {
    if (rankHoldings && rankHoldings.leafEtf === spec.etf) { setRankHoldings(null); return; }
    _openHoldings(spec.etf, rankExpandedChild?.childEtf);
  };

  const runNow = async () => {
    setRunStatus("running");
    try {
      await fetch("/api/sectors/run-now", { method: "POST" });
      setTimeout(() => { load(); setRunStatus("done"); setTimeout(() => setRunStatus(null), 2500); }, 5000);
    } catch { setRunStatus(null); }
  };

  const lastUpdate = snapshot.length ? snapshot[0].timestamp : null;
  const sorted = [...snapshot].sort((a, b) => {
    const av = a[timeframe], bv = b[timeframe];
    if (av == null) return 1; if (bv == null) return -1;
    return parseFloat(bv) - parseFloat(av);
  });

  const _VIEWS = [
    { key:"heatmap", label:"Heatmap" },
    { key:"ranking", label:"Ranking" },
  ];

  // Heatmap (drill ricorsivo): il nodo corrente è l'ultimo del path; le sue righe
  // sono già caricate nell'entry. A root (path vuoto) mostra i settori (`sorted`).
  const _hmLast  = heatmapNav.path[heatmapNav.path.length - 1] || null;
  const _hmLevel = !_hmLast ? "sectors" : (_hmLast.isLeaf ? "holdings" : "sub");
  const _hmLoad  = _hmLast ? !!_hmLast.loading : false;
  let heatmapItems = !_hmLast ? sorted : (_hmLast.rows || []);
  if (_hmLast && !_hmLast.isLeaf) {
    heatmapItems = [...heatmapItems].sort((a, b) => {
      const av = a[timeframe], bv = b[timeframe];
      if (av == null) return 1; if (bv == null) return -1;
      return parseFloat(bv) - parseFloat(av);
    });
  }
  const showHeatmapBack = chartView === "heatmap" && heatmapNav.path.length > 0;

  return (
    <div className="sectors-view" style={{
      display:"flex", flexDirection:"column", gap:14,
      flex:1, minHeight:0, overflowY:"auto",
      padding:"20px 20px 40px",
    }}>

      {/* ── Toolbar ── */}
      <div style={{display:"flex", gap:10, alignItems:"center", flexWrap:"wrap"}}>
        {/* Timeframe */}
        <div style={{display:"flex", gap:4}}>
          {_SECTOR_TIMEFRAMES.map(t => (
            <button key={t.key}
              className={`btn btn-sm ${timeframe === t.key ? "btn-primary" : ""}`}
              onClick={() => setTimeframe(t.key)}>
              {t.label}
            </button>
          ))}
        </div>
        {/* Chart view */}
        <div style={{display:"flex", gap:4, marginLeft:8, paddingLeft:8, borderLeft:"1px solid var(--border)"}}>
          {_VIEWS.map(v => (
            <button key={v.key}
              className={`btn btn-sm ${chartView === v.key ? "btn-primary" : ""}`}
              onClick={() => setChartView(v.key)}>
              {v.label}
            </button>
          ))}
        </div>
        {/* Back (heatmap drill > 0) */}
        {showHeatmapBack && (
          <button className="btn btn-sm" onClick={onHeatmapBack}
            style={{marginLeft:8}}>← Indietro</button>
        )}
        <div style={{flex:1}}/>
        <span style={{fontSize:12, color:"var(--text-muted)"}}>
          {lastUpdate ? `Agg: ${_sectorFmtTs(lastUpdate)}` : "—"}
        </span>
        <button className="btn btn-sm" onClick={runNow} disabled={runStatus === "running"}>
          {runStatus === "running" ? "Agg…" : runStatus === "done" ? "✓" : "Aggiorna ora"}
        </button>
      </div>

      {/* I Temi non hanno più una card separata: sono diventati il livello
          "specializzazione" della tassonomia (settore → sottosettore → tema → società),
          navigabile sia in heatmap (drill) sia nel ranking (accordion). */}

      {/* ── Breadcrumb heatmap (path-based, segmenti cliccabili per risalire) ── */}
      {showHeatmapBack && (
        <div style={{fontSize:13, color:"var(--text-muted)", padding:"0 4px"}}>
          <span style={{cursor:"pointer"}} onClick={() => onHeatmapJump(0)}>Settori</span>
          {heatmapNav.path.map((n, i) => {
            const isLast = i === heatmapNav.path.length - 1;
            return (
              <span key={`${n.etf}-${i}`}>
                {' › '}
                <strong style={{color: isLast ? "var(--text)" : "var(--accent)", fontFamily:"monospace", cursor:"pointer"}}
                  onClick={() => onHeatmapJump(i + 1)}>{n.etf}</strong>
                {n.name && <span style={{color:"var(--text-faint)", marginLeft:6}}>{n.name}</span>}
              </span>
            );
          })}
        </div>
      )}

      {/* ── Stati vuoti ── */}
      {loading && !snapshot.length && (
        <div style={{color:"var(--text-muted)", textAlign:"center", padding:32}}>Caricamento…</div>
      )}
      {error && (
        <div style={{color:"var(--red)", padding:12, background:"var(--red-soft)", borderRadius:8}}>
          Errore: {error}
        </div>
      )}
      {!loading && !snapshot.length && !error && (
        <div style={{textAlign:"center", padding:32, color:"var(--text-muted)"}}>
          Nessuno snapshot disponibile. Abilita Tool7 in Configurazione e attendi il primo run.
        </div>
      )}

      {/* ── Contenuto principale ── */}
      {snapshot.length > 0 && chartView === "heatmap" && (
        <div className="card" style={{padding:12}}>
          <HeatmapGrid
            items={heatmapItems}
            level={_hmLevel}
            timeframe={timeframe}
            loading={_hmLoad}
            breadcrumb={{parent: _hmLast ? _hmLast.etf : null, sub: _hmLast ? _hmLast.name : null}}
            onDrill={onHeatmapDrill}
          />
        </div>
      )}

      {snapshot.length > 0 && chartView === "ranking" && (
        <div style={{
          display:"flex", flexDirection: isMobile ? "column" : "row",
          gap:12, alignItems:"flex-start",
        }}>
          <div className="card" style={{padding:12, flex:1, minWidth:0}}>
            <SectorsBarChart
              snapshot={sorted}
              timeframe={timeframe}
              expandedParent={rankExpanded?.parentEtf || null}
              expandedRows={rankExpanded?.rows || null}
              expandedLoading={rankExpanded?.loading || false}
              expandedChild={rankExpandedChild?.childEtf || null}
              expandedChildRows={rankExpandedChild?.rows || null}
              expandedChildLoading={rankExpandedChild?.loading || false}
              activeLeaf={rankHoldings?.leafEtf || null}
              onToggleParent={onRankToggle}
              onToggleSub={onRankToggleSub}
              onPickLeaf={onRankPickLeaf}
            />
          </div>
          {rankHoldings && (
            <div style={{
              width: isMobile ? "100%" : 340,
              flexShrink: 0,
              alignSelf: isMobile ? "stretch" : "flex-start",
            }}>
              <HoldingsPanel
                parentEtf={rankHoldings.parentEtf}
                subEtf={rankHoldings.subEtf}
                rows={rankHoldings.rows}
                loading={rankHoldings.loading}
                timeframe={timeframe}
                onClose={() => setRankHoldings(null)}
              />
            </div>
          )}
        </div>
      )}
    </div>
  );
}

// ────────── Pre-market (Tool8) ──────────

const _PM_VERDICT_CFG = {
  GAP_CRAP: { label:"GAP‑CRAP",  color:"var(--red)",   bg:"var(--red-soft)",   icon:"🚨" },
  WATCH:    { label:"SORVEGLIA", color:"var(--amber)",  bg:"var(--amber-soft)", icon:"👁" },
  NEUTRAL:  { label:"NEUTRALE",  color:"var(--green)",  bg:"var(--green-soft)", icon:"✓" },
};

function _pmFmtPct(v) {
  if (v == null || isNaN(v)) return "—";
  const f = parseFloat(v);
  return `${f >= 0 ? "+" : ""}${f.toFixed(2)}%`;
}

function ScoreBar({ score }) {
  const s = Math.max(0, Math.min(100, score || 0));
  const color = s >= 60 ? "var(--red)" : s >= 40 ? "var(--amber)" : "var(--green)";
  return (
    <div style={{display:"flex", alignItems:"center", gap:7}}>
      <div style={{
        width:80, height:7, background:"var(--surface)", borderRadius:4, overflow:"hidden",
      }}>
        <div style={{width:`${s}%`, height:"100%", background:color, borderRadius:4, transition:"width 300ms"}}/>
      </div>
      <span style={{fontFamily:"monospace", fontSize:12, fontWeight:700, color, minWidth:26}}>{s}</span>
    </div>
  );
}

function VerdictBadge({ verdict }) {
  const cfg = _PM_VERDICT_CFG[verdict] || _PM_VERDICT_CFG.NEUTRAL;
  return (
    <span style={{
      display:"inline-flex", alignItems:"center", gap:4,
      padding:"2px 8px", borderRadius:4,
      background:cfg.bg, color:cfg.color,
      fontWeight:700, fontSize:11.5, letterSpacing:".03em",
    }}>
      {cfg.icon} {cfg.label}
    </span>
  );
}

function FuturesBar({ futures }) {
  if (!futures) return null;
  const items = [
    { label:"ES",  val: futures.es_pct },
    { label:"NQ",  val: futures.nq_pct },
    { label:"YM",  val: futures.ym_pct },
    { label:"VIX", val: futures.vix, abs: true },
  ];
  return (
    <div style={{
      display:"flex", gap:14, alignItems:"center", flexWrap:"wrap",
      padding:"10px 14px", background:"var(--surface)", borderRadius:8,
      fontSize:13,
    }}>
      <span style={{fontWeight:600, color:"var(--text-muted)", fontSize:12, marginRight:4}}>
        Futures
      </span>
      {items.map(({ label, val, abs }) => {
        if (val == null) return null;
        const isPos = val >= 0;
        const color = abs ? (val > 25 ? "var(--red)" : val > 20 ? "var(--amber)" : "var(--text-muted)")
                          : (isPos ? "var(--green)" : "var(--red)");
        return (
          <div key={label} style={{display:"flex", gap:5, alignItems:"center"}}>
            <span style={{color:"var(--text-muted)", fontSize:11}}>{label}</span>
            <span style={{fontFamily:"monospace", fontWeight:700, color}}>
              {abs ? val.toFixed(1) : `${isPos?"+":""}${val.toFixed(2)}%`}
            </span>
          </div>
        );
      })}
    </div>
  );
}

function AccuracyPanel({ accuracy }) {
  if (!accuracy) return null;
  const items = [
    { key:"gap_crap", label:"GAP‑CRAP", color:"var(--red)" },
    { key:"watch",    label:"WATCH",    color:"var(--amber)" },
    { key:"neutral",  label:"NEUTRAL",  color:"var(--green)" },
  ];
  const hasData = Object.values(accuracy).some(a => (a?.total || 0) > 0);
  if (!hasData) return (
    <div className="card" style={{padding:12}}>
      <div style={{fontWeight:600, fontSize:13, marginBottom:6}}>Accuratezza previsioni (30gg)</div>
      <div style={{color:"var(--text-muted)", fontSize:13}}>
        Nessun dato di validazione ancora. Si popola automaticamente dopo le 16:00 IT.
      </div>
    </div>
  );
  return (
    <div className="card" style={{padding:12}}>
      <div style={{fontWeight:600, fontSize:13, marginBottom:10}}>Accuratezza previsioni (30gg)</div>
      <div style={{display:"flex", gap:20, flexWrap:"wrap"}}>
        {items.map(({ key, label, color }) => {
          const a = accuracy[key] || { total:0, correct:0, accuracy:null };
          if (!a.total) return null;
          return (
            <div key={key} style={{textAlign:"center", minWidth:90}}>
              <div style={{fontSize:22, fontWeight:700, color, fontFamily:"monospace"}}>
                {a.accuracy != null ? `${a.accuracy}%` : "—"}
              </div>
              <div style={{fontSize:11.5, color:"var(--text-muted)", marginTop:2}}>{label}</div>
              <div style={{fontSize:10.5, color:"var(--text-faint)"}}>
                {a.correct}/{a.total} corretti
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function PremarketView() {
  const [data, setData]         = useState([]);
  const [futures, setFutures]   = useState(null);
  const [accuracy, setAccuracy] = useState(null);
  const [loading, setLoading]   = useState(true);
  const [runStatus, setRunStatus] = useState(null);
  const [lastTs, setLastTs]     = useState(null);

  const load = async () => {
    try {
      const [pr, fr] = await Promise.all([
        fetch("/api/premarket").then(r => r.json()),
        fetch("/api/premarket/futures").then(r => r.json()),
      ]);
      if (pr.rows)     { setData(pr.rows); setAccuracy(pr.accuracy); }
      if (fr.futures)  setFutures(fr.futures);
      if (pr.rows && pr.rows.length > 0) setLastTs(pr.rows[0].timestamp);
    } catch(e) {}
    setLoading(false);
  };

  useEffect(() => {
    load();
    const t = setInterval(load, 60000);
    return () => clearInterval(t);
  }, []);

  const runNow = async () => {
    setRunStatus("running");
    try {
      await fetch("/api/premarket/run-now", { method: "POST" });
      setTimeout(() => { load(); setRunStatus("done"); setTimeout(() => setRunStatus(null), 2500); }, 5000);
    } catch { setRunStatus(null); }
  };

  // Verifica finestra pre-market (14:00-15:30 IT, approssimata lato client)
  const now = new Date();
  const etHour = (now.getUTCHours() - 4 + 24) % 24;
  const etMin  = now.getMinutes();
  const isPremarket = now.getDay() >= 1 && now.getDay() <= 5
    && ((etHour === 8) || (etHour === 9 && etMin < 30));

  const gapCrap = data.filter(r => r.verdict === "GAP_CRAP");
  const watch   = data.filter(r => r.verdict === "WATCH");

  return (
    <div className="panel" style={{display:"flex", flexDirection:"column", gap:14}}>

      {/* Toolbar */}
      <div style={{display:"flex", gap:10, alignItems:"center", flexWrap:"wrap"}}>
        <div style={{fontSize:13, fontWeight:600}}>
          Pre-market Gap Detector
          {isPremarket && (
            <span style={{
              marginLeft:10, fontSize:11, padding:"2px 8px",
              background:"var(--green-soft)", color:"var(--green)", borderRadius:4,
            }}>● Attivo</span>
          )}
        </div>
        <div style={{flex:1}}/>
        {lastTs && (
          <span style={{fontSize:12, color:"var(--text-muted)"}}>
            Agg: {lastTs.slice(11,16)}
          </span>
        )}
        <button className="btn btn-sm" onClick={runNow} disabled={runStatus === "running"}>
          {runStatus === "running" ? "Analisi…" : runStatus === "done" ? "✓" : "Analizza ora"}
        </button>
      </div>

      {/* Futures bar */}
      <FuturesBar futures={futures}/>

      {/* Highlights: GAP_CRAP */}
      {gapCrap.length > 0 && (
        <div style={{
          padding:"12px 14px", background:"rgba(239,68,68,0.08)",
          border:"1px solid rgba(239,68,68,0.25)", borderRadius:8,
        }}>
          <div style={{fontWeight:700, color:"var(--red)", marginBottom:8, fontSize:13}}>
            🚨 Gap-and-Crap potenziale ({gapCrap.length} ticker)
          </div>
          <div style={{display:"flex", gap:10, flexWrap:"wrap"}}>
            {gapCrap.map(r => (
              <div key={r.symbol} style={{
                padding:"8px 12px", background:"rgba(239,68,68,0.12)",
                borderRadius:6, fontSize:13,
              }}>
                <strong style={{fontFamily:"monospace"}}>{r.symbol}</strong>
                {" "}
                <span style={{color:"var(--green)", fontFamily:"monospace"}}>
                  {_pmFmtPct(r.pre_market_pct)} PM
                </span>
                {" · "}
                <span>Score: <strong style={{color:"var(--red)"}}>{r.score}</strong></span>
              </div>
            ))}
          </div>
        </div>
      )}

      {/* Tabella principale */}
      {loading ? (
        <div style={{color:"var(--text-muted)", textAlign:"center", padding:32}}>
          Caricamento…
        </div>
      ) : data.length === 0 ? (
        <div className="card" style={{textAlign:"center", padding:32, color:"var(--text-muted)"}}>
          <div style={{marginBottom:8, fontSize:15}}>🌅</div>
          <div>Nessun dato per oggi.</div>
          <div style={{fontSize:12, marginTop:4}}>
            Tool8 è attivo nella finestra 14:00–15:30 IT (pre-market USA).
          </div>
        </div>
      ) : (
        <div className="card" style={{padding:0, overflow:"hidden"}}>
          <div style={{overflowX:"auto"}}>
            <table style={{width:"100%", borderCollapse:"collapse", fontSize:13}}>
              <thead>
                <tr style={{background:"var(--bg)"}}>
                  {["Ticker","PM %","Futures avg","VIX","News","Score","Verdict","Open reale","Corretto"].map(h => (
                    <th key={h} style={{
                      padding:"9px 10px", textAlign: h==="Score"||h==="PM %"||h==="Futures avg"||h==="VIX"||h==="Open reale" ? "right" : "left",
                      fontSize:11, fontWeight:600, color:"var(--text-muted)",
                      textTransform:"uppercase", letterSpacing:".05em",
                      borderBottom:"1px solid var(--border)", whiteSpace:"nowrap",
                    }}>{h}</th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {data.map((r, i) => {
                  const vc = _PM_VERDICT_CFG[r.verdict] || _PM_VERDICT_CFG.NEUTRAL;
                  const rowBg = r.verdict === "GAP_CRAP"
                    ? "rgba(239,68,68,0.06)"
                    : r.verdict === "WATCH"
                    ? "rgba(245,158,11,0.05)"
                    : "transparent";
                  return (
                    <tr key={r.symbol} style={{background: i%2===0 ? rowBg : "oklch(0.175 0.012 250 / 0.5)"}}>
                      <td style={{padding:"8px 10px", fontFamily:"monospace", fontWeight:700}}>{r.symbol}</td>
                      <td style={{
                        padding:"8px 10px", textAlign:"right", fontFamily:"monospace",
                        fontWeight:600, color: r.pre_market_pct == null ? "var(--text-faint)"
                          : r.pre_market_pct > 0 ? "var(--green)" : "var(--red)",
                      }}>{_pmFmtPct(r.pre_market_pct)}</td>
                      <td style={{
                        padding:"8px 10px", textAlign:"right", fontFamily:"monospace",
                        color: r.futures_avg == null ? "var(--text-faint)"
                          : r.futures_avg < 0 ? "var(--red)" : "var(--green)",
                      }}>{_pmFmtPct(r.futures_avg)}</td>
                      <td style={{
                        padding:"8px 10px", textAlign:"right", fontFamily:"monospace",
                        color: r.vix_level == null ? "var(--text-faint)"
                          : r.vix_level > 25 ? "var(--red)" : r.vix_level > 20 ? "var(--amber)" : "var(--text-muted)",
                      }}>{r.vix_level != null ? r.vix_level.toFixed(1) : "—"}</td>
                      <td style={{
                        padding:"8px 10px", fontSize:11,
                        color: r.news_impact === "HIGH" || r.news_impact === "CRITICAL" ? "var(--red)"
                          : r.news_impact === "MEDIUM" ? "var(--amber)" : "var(--text-muted)",
                      }}>{r.news_impact || "—"}</td>
                      <td style={{padding:"8px 10px", textAlign:"right"}}>
                        <ScoreBar score={r.score}/>
                      </td>
                      <td style={{padding:"8px 10px"}}><VerdictBadge verdict={r.verdict}/></td>
                      <td style={{
                        padding:"8px 10px", textAlign:"right", fontFamily:"monospace",
                        color: r.actual_open_pct == null ? "var(--text-faint)"
                          : r.actual_open_pct > 0 ? "var(--green)" : "var(--red)",
                      }}>{_pmFmtPct(r.actual_open_pct)}</td>
                      <td style={{padding:"8px 10px", textAlign:"center", fontSize:14}}>
                        {r.correct == null ? <span style={{color:"var(--text-faint)"}}>⏳</span>
                          : r.correct === 1 ? <span style={{color:"var(--green)"}}>✓</span>
                          : <span style={{color:"var(--red)"}}>✗</span>}
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        </div>
      )}

      {/* Accuratezza previsioni */}
      <AccuracyPanel accuracy={accuracy}/>

      {/* Spiegazione algoritmo */}
      <div className="card" style={{padding:12}}>
        <div style={{fontWeight:600, fontSize:13, marginBottom:6}}>Come funziona il detector</div>
        <div style={{fontSize:12.5, color:"var(--text-muted)", lineHeight:1.7}}>
          <strong>Gap-and-Crap</strong>: ticker con pre-market molto verde ma senza un catalyst fondamentale
          reale tende ad aprire in rosso o a cedere nelle prime 30 min di contrattazione.
        </div>
        <div style={{display:"grid", gridTemplateColumns:"repeat(auto-fill,minmax(200px,1fr))", gap:10, marginTop:10}}>
          {[
            { label:"PM euforia", desc:"pre-market >+3%", max:40 },
            { label:"Futures rossi", desc:"ES/NQ negativi", max:30 },
            { label:"Nessuna news", desc:"Tool4 = NONE/LOW", max:20 },
            { label:"VIX elevato", desc:"VIX > 18 amplifica", max:10 },
          ].map(c => (
            <div key={c.label} style={{
              padding:"8px 10px", background:"var(--surface)",
              borderRadius:6, fontSize:12,
            }}>
              <div style={{fontWeight:600}}>{c.label} <span style={{color:"var(--text-faint)"}}>max {c.max}pt</span></div>
              <div style={{color:"var(--text-muted)", fontSize:11.5, marginTop:2}}>{c.desc}</div>
            </div>
          ))}
        </div>
        <div style={{marginTop:10, fontSize:12, color:"var(--text-muted)"}}>
          Soglie: <strong style={{color:"var(--red)"}}>≥60 GAP_CRAP</strong> ·{" "}
          <strong style={{color:"var(--amber)"}}>40–59 WATCH</strong> ·{" "}
          <strong style={{color:"var(--green)"}}>&lt;40 NEUTRAL</strong>
        </div>
      </div>
    </div>
  );
}

// ── TopPicksView (Tool9 — Stock Discovery) ───────────────────────────────────
function TopPicksView() {
  const [rows, setRows]         = useState([]);
  const [rankChanges, setRanks] = useState([]);
  const [date, setDate]         = useState("");
  const [loading, setLoading]   = useState(true);
  const [error, setError]       = useState(null);
  const [expanded, setExpanded] = useState(null);
  const [runStatus, setRunStatus] = useState(null);
  const [showLegend, setShowLegend] = useState(false);
  const firstLoad = useRef(true);

  const load = async (showSpinner) => {
    if (showSpinner) setLoading(true);
    try {
      const r = await fetch("/api/picks").then(r => r.json());
      setRows(r.rows || []);
      setRanks(r.rank_changes || []);
      setDate(r.date || "");
      setError(null);
    } catch(e) { setError(String(e)); }
    finally {
      if (showSpinner) setLoading(false);
      firstLoad.current = false;
    }
  };

  useEffect(() => {
    load(true);
    // (Le notizie IPO/Spin-off sono ora nel tab News, non più qui.)
    // Background refresh ogni 5 min — senza spinner
    const t = setInterval(() => load(false), 5 * 60 * 1000);
    return () => clearInterval(t);
  }, []);

  const runNow = async () => {
    setRunStatus("running");
    try {
      await fetch("/api/picks/run-now", { method: "POST" });
      setRunStatus("ok");
      setTimeout(() => setRunStatus(null), 4000);
    } catch { setRunStatus("err"); }
  };

  // Mappa rank_changes: symbol → {rank_delta, days_in_top10}
  const changesMap = {};
  for (const c of rankChanges) {
    if (!changesMap[c.symbol] || c.date > changesMap[c.symbol].date)
      changesMap[c.symbol] = c;
  }

  const RISK_IT = {
    "no_clear_catalyst":      "Nessun catalyst identificato",
    "extended_from_ath":      "Vicino al massimo 52w",
    "high_volatility":        "Volatilità elevata",
    "earnings_gap_risk_5d":   "Earnings entro 5 giorni (gap risk)",
    "small_cap_liquidity":    "Small cap — liquidità ridotta",
    "sector_rotation_risk":   "Rischio rotazione settoriale",
    "low_momentum":           "Momentum basso",
    "earnings_miss_risk":     "Rischio miss earnings",
    "analyst_downgrade_risk": "Rischio downgrade analisti",
    "stretched_valuation":    "Valutazione elevata",
  };

  const LEGEND_COLS = [
    { name: "Rank",              desc: "Posizione nel ranking finale (1 = migliore)." },
    { name: "Ticker",            desc: "Codice del titolo in borsa." },
    { name: "Final",             desc: "Score finale 0–100: 60% quant + 40% catalyst AI, con risk overlay." },
    { name: "Quant",             desc: "Score quantitativo 0–100: media pesata di 5 factor (momentum 35%, earnings 30%, analyst 15%, insider 10%, tecnico 10%)." },
    { name: "Cat",               desc: "Catalyst score 0–10 assegnato da Claude AI analizzando news, earnings imminenti e insider filing." },
    { name: "Mom 1m",            desc: "Rendimento effettivo del titolo nell'ultimo mese (performance reale, non previsione)." },
    { name: "Δ ATH",             desc: "Distanza percentuale dal massimo degli ultimi 52 settimane. Negativo = sotto l'ATH." },
    { name: "Earn",              desc: "Giorni al prossimo earnings report. Arancione se < 14 giorni." },
    { name: "Catalyst principale", desc: "Primo catalyst identificato da Claude: breakout tecnico, earnings imminenti, upgrade analisti, ecc." },
    { name: "Top10",             desc: "Giorni consecutivi in cui il titolo è rimasto in top 10. I triangolini ▲▼ indicano variazione di rank rispetto al giorno prima." },
    { name: "🃏 WILD",           desc: "Titolo ripescato dalla corsia wildcard: non era nella top 50 per momentum, ma ha mostrato un'anomalia di news (spike articoli Finnhub o presenza WSB). Ha passato lo Stage 3 catalyst AI come tutti gli altri." },
  ];

  const scoreColor = (v) => {
    if (v == null) return "var(--text-muted)";
    if (v >= 70) return "var(--green)";
    if (v >= 50) return "var(--amber)";
    return "var(--text-muted)";
  };

  const catScore = (v) => {
    if (v == null) return <span style={{color:"var(--text-muted)"}}>—</span>;
    const c = v >= 7 ? "var(--green)" : v >= 5 ? "var(--amber)" : "var(--text-muted)";
    return <span style={{color: c, fontWeight: 600}}>{v}</span>;
  };

  const deltaBadge = (sym) => {
    const ch = changesMap[sym];
    if (!ch || ch.rank_delta == null) return null;
    const d = ch.rank_delta;
    if (d === 0) return null;
    const up = d > 0;
    return (
      <span style={{
        fontSize: 10, fontWeight: 700, marginLeft: 4,
        color: up ? "var(--green)" : "var(--red)",
      }}>
        {up ? "▲" : "▼"}{Math.abs(d)}
      </span>
    );
  };

  const top10  = rows.filter(r => r.in_top10);
  const others = rows.filter(r => !r.in_top10).slice(0, 15);

  if (loading) return (
    <div style={{padding: 32, color: "var(--text-muted)", textAlign: "center"}}>
      Caricamento picks…
    </div>
  );

  if (error) return (
    <div style={{padding: 32, color: "var(--red)"}}>
      Errore: {error}
    </div>
  );

  if (!rows.length) return (
    <div style={{padding: 32, color: "var(--text-muted)", textAlign: "center"}}>
      <p>Nessuno snapshot disponibile.</p>
      <p style={{fontSize: 13, marginTop: 4}}>Avvia il primo run dal pulsante qui sotto (richiede ~5 min) oppure da CLI con <code>python -m tool9_discovery.picks</code></p>
      <div style={{marginTop: 16}}>
        <button className="btn" onClick={runNow} disabled={runStatus === "running"}>
          <Icon name="spark" size={14}/>
          {runStatus === "running" ? " In corso…" : runStatus === "ok" ? " Avviato! Aggiorna tra ~5 min" : runStatus === "err" ? " Errore (sei admin?)" : " Avvia primo run"}
        </button>
      </div>
    </div>
  );

  const renderRow = (r, isTop) => {
    const isOpen = expanded === r.symbol;
    const ch = changesMap[r.symbol] || {};
    return (
      <React.Fragment key={r.symbol}>
        <tr
          className={`picks-row ${isOpen ? "open" : ""} ${isTop ? "top10" : ""}`}
          onClick={() => setExpanded(isOpen ? null : r.symbol)}
          style={{cursor: "pointer"}}
        >
          <td style={{whiteSpace: "nowrap"}}>
            <span style={{display: "inline-flex", alignItems: "center", gap: 5}}>
              <span style={{fontWeight: 700, color: isTop ? "var(--accent)" : "inherit"}}>#{r.rank}</span>
              {deltaBadge(r.symbol)}
            </span>
          </td>
          <td style={{fontWeight: 700, fontFamily: "monospace"}}>
            {r.symbol}
            {r.is_wildcard ? (
              <span title={`Wildcard: ${r.wildcard_reason || "news spike"}`} style={{
                marginLeft: 5, fontSize: 10, fontWeight: 700,
                background: "var(--amber-soft, rgba(245,158,11,0.15))",
                color: "var(--amber, #f59e0b)",
                padding: "1px 4px", borderRadius: 3,
              }}>🃏</span>
            ) : null}
          </td>
          <td style={{textAlign: "right", color: scoreColor(r.final_score), fontWeight: 600}}>
            {r.final_score != null ? r.final_score.toFixed(1) : "—"}
          </td>
          <td style={{textAlign: "right", color: scoreColor(r.quant_score)}}>
            {r.quant_score != null ? r.quant_score.toFixed(1) : "—"}
          </td>
          <td style={{textAlign: "right"}}>{catScore(r.catalyst_score)}</td>
          <td style={{textAlign: "right", color: (r.return_1m_pct || 0) >= 0 ? "var(--green)" : "var(--red)"}}>
            {r.return_1m_pct != null ? `${r.return_1m_pct > 0 ? "+" : ""}${r.return_1m_pct.toFixed(1)}%` : "—"}
          </td>
          <td style={{textAlign: "right", color: "var(--text-muted)", fontSize: 12}}>
            {r.dist_52w_high != null ? `${r.dist_52w_high.toFixed(1)}%` : "—"}
          </td>
          <td style={{textAlign: "right", fontSize: 12, color: r.earnings_in_days != null && r.earnings_in_days <= 14 ? "var(--amber)" : "inherit"}}>
            {r.earnings_in_days != null ? `${r.earnings_in_days}d` : "—"}
          </td>
          <td style={{fontSize: 11, color: "var(--text-muted)", maxWidth: 160, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap"}}>
            {(r.catalysts || []).slice(0,1).map(c => c.description || c.type).join("") || "—"}
          </td>
          <td style={{fontSize: 11, textAlign: "center"}}>
            {ch.days_in_top10 > 0 && (
              <span style={{background:"var(--accent-soft)", color:"var(--accent)", padding:"1px 5px", borderRadius:4}}>
                {ch.days_in_top10}d
              </span>
            )}
          </td>
        </tr>
        {isOpen && (
          <tr className="picks-expand">
            <td colSpan={10}>
              <div style={{padding: "12px 16px", background: "var(--surface2)", borderRadius: 6, margin: "4px 0"}}>
                {/* Header azienda */}
                {(r.company_name || r.sector || r.business_summary_it || r.business_summary) && (
                  <div style={{marginBottom: 10, paddingBottom: 8, borderBottom: "1px solid var(--border)"}}>
                    {r.company_name && (
                      <div style={{fontSize: 15, fontWeight: 700}}>{r.company_name}</div>
                    )}
                    {r.sector && (
                      <div style={{fontSize: 12, color: "var(--accent)", marginTop: 2}}>{r.sector}</div>
                    )}
                    {(r.business_summary_it || r.business_summary) && (
                      <div style={{fontSize: 12, color: "var(--text-muted)", marginTop: 6, lineHeight: 1.5}}>
                        {r.business_summary_it || r.business_summary}
                      </div>
                    )}
                  </div>
                )}
                {r.reasoning && (
                  <div style={{marginBottom: 10}}>
                    <strong style={{fontSize: 12, color: "var(--text-muted)"}}>Analisi AI</strong>
                    <p style={{margin: "4px 0 0", fontSize: 13}}>{r.reasoning}</p>
                  </div>
                )}
                {(r.catalysts || []).length > 0 && (
                  <div style={{marginBottom: 8}}>
                    <strong style={{fontSize: 12, color: "var(--text-muted)"}}>Catalyst</strong>
                    <ul style={{margin: "4px 0 0", paddingLeft: 18, fontSize: 13}}>
                      {r.catalysts.map((c, i) => (
                        <li key={i}>
                          <span style={{
                            background: "var(--accent-soft)", color: "var(--accent)",
                            padding: "1px 5px", borderRadius: 4, fontSize: 11, marginRight: 6,
                          }}>{c.type}</span>
                          {c.description}
                          {c.confidence && <span style={{color:"var(--text-muted)", marginLeft:6, fontSize:11}}>[{c.confidence}]</span>}
                        </li>
                      ))}
                    </ul>
                  </div>
                )}
                {(r.risks || []).length > 0 && (
                  <div>
                    <strong style={{fontSize: 12, color: "var(--red)"}}>Rischi</strong>
                    <div style={{marginTop: 4, display: "flex", flexWrap: "wrap", gap: 4}}>
                      {r.risks.map((rk, i) => (
                        <span key={i} style={{
                          background: "var(--red-soft)", color: "var(--red)",
                          padding: "2px 8px", borderRadius: 4, fontSize: 11,
                        }}>{RISK_IT[rk] || rk}</span>
                      ))}
                    </div>
                  </div>
                )}
                <div style={{marginTop: 10, display: "flex", gap: 16, fontSize: 12, color: "var(--text-muted)", flexWrap: "wrap"}}>
                  {r.market_cap && <span>Cap: ${(r.market_cap / 1e9).toFixed(1)}B</span>}
                  {r.avg_vol_usd && <span>Vol: ${(r.avg_vol_usd / 1e6).toFixed(0)}M/gg</span>}
                  {r.return_3m_pct != null && <span>3m: {r.return_3m_pct > 0 ? "+" : ""}{r.return_3m_pct.toFixed(1)}%</span>}
                  {r.close_price && <span>Prezzo: ${r.close_price.toFixed(2)}</span>}
                </div>
                <div style={{marginTop: 8, display: "flex", gap: 16, fontSize: 12, color: "var(--text-muted)", flexWrap: "wrap"}}>
                  {r.momentum_score != null && <span>Mom: {r.momentum_score.toFixed(0)}</span>}
                  {r.technical_score != null && <span>Tech: {r.technical_score.toFixed(0)}</span>}
                  {r.earnings_score != null && <span>Earn: {r.earnings_score.toFixed(0)}</span>}
                  {r.analyst_score != null && <span>Anal: {r.analyst_score.toFixed(0)}</span>}
                  {r.insider_score != null && <span>Ins: {r.insider_score.toFixed(0)}</span>}
                  {r.fundamentals_score != null && <span>Fond: {r.fundamentals_score.toFixed(0)}</span>}
                  {r.valuation_score != null && <span title="Valore relativo al settore (P/E vs pari)">Val: {r.valuation_score.toFixed(0)}</span>}
                </div>
              </div>
            </td>
          </tr>
        )}
      </React.Fragment>
    );
  };

  return (
    <div style={{flex: 1, minHeight: 0, overflowY: "auto", padding: "16px 20px"}}>
      {/* Header */}
      <div style={{display: "flex", alignItems: "center", gap: 12, marginBottom: 16, flexWrap: "wrap"}}>
        <div>
          <span style={{fontSize: 13, color: "var(--text-muted)"}}>Snapshot: </span>
          <span style={{fontSize: 13, fontWeight: 600}}>{date || "—"}</span>
        </div>
        <div style={{marginLeft: "auto", display: "flex", gap: 8, alignItems: "center"}}>
          {/* Bottone info "i" */}
          <div style={{position: "relative"}}>
            <button className="btn btn-sm" onClick={() => setShowLegend(v => !v)} title="Legenda colonne">
              <Icon name="info" size={13}/>
            </button>
            {showLegend && (
              <>
                {/* Backdrop per chiudere cliccando fuori */}
                <div onClick={() => setShowLegend(false)}
                  style={{position:"fixed", inset:0, zIndex:199}}/>
                <div style={{
                  position: window.innerWidth <= 760 ? "fixed" : "absolute",
                  ...(window.innerWidth <= 760
                    ? { bottom: 70, left: "50%", transform: "translateX(-50%)", width: "calc(100vw - 32px)", maxWidth: 400 }
                    : { top: "calc(100% + 6px)", right: 0, width: 360 }),
                  zIndex: 200,
                  background: "var(--bg-elevated)",
                  border: "1px solid var(--border)",
                  borderRadius: "var(--radius-lg)",
                  padding: "12px 14px",
                  boxShadow: "var(--shadow-lg)",
                  fontSize: 12,
                  maxHeight: "70vh", overflowY: "auto",
                }}>
                  <div style={{display:"flex", justifyContent:"space-between", alignItems:"center", marginBottom: 10}}>
                    <span style={{fontWeight: 700, fontSize: 13}}>Legenda colonne</span>
                    <button className="icon-btn" onClick={() => setShowLegend(false)}>✕</button>
                  </div>
                  {LEGEND_COLS.map(c => (
                    <div key={c.name} style={{display: "flex", gap: 8, marginBottom: 7}}>
                      <span style={{fontFamily:"monospace", fontWeight:600, minWidth:92, color:"var(--accent)", flexShrink:0, fontSize:11}}>{c.name}</span>
                      <span style={{color:"var(--text-muted)", lineHeight:1.4}}>{c.desc}</span>
                    </div>
                  ))}
                </div>
              </>
            )}
          </div>
          <button className="btn btn-sm" onClick={() => load(false)} title="Aggiorna">
            <Icon name="refresh" size={13}/> Aggiorna
          </button>
          <button className="btn btn-sm" onClick={runNow} disabled={runStatus === "running"}
            title="Avvia un nuovo run Tool9">
            <Icon name="spark" size={13}/>
            {runStatus === "running" ? " In corso…" : runStatus === "ok" ? " Avviato!" : " Run now"}
          </button>
        </div>
      </div>

      {/* Tabella */}
      <div style={{overflowX: "auto"}}>
        <table className="picks-table" style={{width: "100%", borderCollapse: "collapse", fontSize: 13}}>
          <thead>
            <tr style={{borderBottom: "1px solid var(--border)", color: "var(--text-muted)", fontSize: 11}}>
              <th style={{padding: "6px 8px", textAlign: "left", width: 76}}>Rank</th>
              <th style={{padding: "6px 8px", textAlign: "left"}}>Ticker</th>
              <th style={{padding: "6px 8px", textAlign: "right"}} title="Final score (quant + catalyst + risk)">Final</th>
              <th style={{padding: "6px 8px", textAlign: "right"}} title="Quant score (5 factor)">Quant</th>
              <th style={{padding: "6px 8px", textAlign: "right"}} title="Catalyst score 0-10">Cat</th>
              <th style={{padding: "6px 8px", textAlign: "right"}}>Mom 1m</th>
              <th style={{padding: "6px 8px", textAlign: "right"}} title="Distanza da massimo 52w">Δ ATH</th>
              <th style={{padding: "6px 8px", textAlign: "right"}} title="Giorni al prossimo earnings">Earn</th>
              <th style={{padding: "6px 8px", textAlign: "left"}}>Catalyst principale</th>
              <th style={{padding: "6px 8px", textAlign: "center"}} title="Giorni consecutivi in top 10">Top10</th>
            </tr>
          </thead>
          <tbody>
            {top10.map(r => renderRow(r, true))}
            {others.length > 0 && (
              <tr>
                <td colSpan={10} style={{padding: "8px 0 4px", color: "var(--text-muted)", fontSize: 11, borderTop: "1px solid var(--border)"}}>
                  — fuori top 10 —
                </td>
              </tr>
            )}
            {others.map(r => renderRow(r, false))}
          </tbody>
        </table>
      </div>

      <style>{`
        .picks-row:hover td { background: var(--surface2); }
        .picks-row.top10 td:first-child { border-left: 2px solid var(--accent); }
        .picks-row td { padding: 7px 8px; border-bottom: 1px solid var(--border); transition: background 0.1s; }
        .picks-expand td { padding: 0 8px 8px; }
      `}</style>

      {/* ── Sezione IPO / Spin-off da Tool5 ── */}
    </div>
  );
}

window.CalendarView   = CalendarView;
window.ReportView     = ReportView;
window.EvaluateView   = EvaluateView;
window.ConfigView     = ConfigView;
window.LogsView       = LogsView;
window.AdminView      = AdminView;
window.ScreenerView   = ScreenerView;
window.SectorsView    = SectorsView;
window.PremarketView  = PremarketView;
window.TopPicksView   = TopPicksView;
