/** AI Advisor Dashboard with live enrichments and detail drawer */
const { useEffect, useMemo, useState, useRef } = React;

const DATA_URL = "./data/dashboard.json";
const BASE = (() => (window.location.pathname.match(/^\/ai-advisor\//) ? "/ai-advisor" : ""))();
const LOCAL_API = `${window.location.protocol}//${window.location.hostname}:8765`;
// Optional client-side config for CORS-friendly providers (no backend required)
// Provide via window.AIADVISOR_CFG in index.html, or localStorage key 'aiadvisor.cfg'
// { iex:{token}, finnhub:{token}, useLocalApi:true, allowLegacy:false, allowYahooFallback:false }
const CONFIG = (window.AIADVISOR_CFG || (()=>{ try{ return JSON.parse(localStorage.getItem('aiadvisor.cfg')||'{}'); }catch(e){ return {}; } })());
const QUOTE_ENDPOINTS = [
  ...(CONFIG?.allowLegacy ? [`${BASE}/api/quote.php`] : []),
  ...(CONFIG?.useLocalApi ? [`${LOCAL_API}/quote`] : []),
];
const CHART_ENDPOINTS = [
  ...(CONFIG?.allowLegacy ? [`${BASE}/api/chart.php`] : []),
  ...(CONFIG?.useLocalApi ? [`${LOCAL_API}/chart`] : []),
];
const ANALYSIS_DIR = `./data/analysis`;
const REFRESH_MS = 60_000; // auto-refresh interval for quotes
const THEME_KEY = "aiadvisor.theme";

/* ---------- formatters ---------- */
const fmt = (n) => (n==null || isNaN(n) ? "—" : Number(n).toLocaleString(undefined,{maximumFractionDigits:2}));
const fmtd = (n) => (n==null || isNaN(n) ? "—" : "$"+Number(n).toLocaleString(undefined,{maximumFractionDigits:2}));
const fmtpct = (n) => (n==null || isNaN(n) ? "—" : Number(n).toFixed(2)+"%");
const fmtNumber = (n, digits = 0) => (n==null || isNaN(n) ? "—" : Number(n).toLocaleString(undefined,{maximumFractionDigits:digits}));

/* ---------- theme ---------- */
function useTheme(){
  const [theme,setTheme] = useState(()=>{
    const saved = localStorage.getItem(THEME_KEY);
    if (saved==="light"||saved==="dark") return saved;
    return window.matchMedia?.("(prefers-color-scheme: dark)").matches ? "dark":"light";
  });
  useEffect(()=>{ document.documentElement.setAttribute("data-theme",theme); localStorage.setItem(THEME_KEY,theme); },[theme]);
  return {theme,setTheme};
}

/* ---------- dashboard loader ---------- */
function useDashboard(){
  const [data,setData] = useState(null);
  const [err,setErr] = useState(null);
  const [lastAt,setLastAt] = useState(null);

  const load = async ()=>{
    try{
      const r = await fetch(`${DATA_URL}?ts=${Date.now()}`, {cache:"no-store"});
      if(!r.ok) throw new Error(`HTTP ${r.status} loading dashboard.json`);
      const j = await r.json();
      setData(j);
      setLastAt(Date.now());
    }catch(e){
      console.error("Dashboard load error:", e);
      setErr(e);
    }
  };

  useEffect(()=>{ load(); },[]);
  return {data, setData, err, lastAt, refreshNow: load};
}

/* ---------- quotes ---------- */
// Fetch latest quotes + metadata for symbols. Returns { TICKER: { price, changePct, volume } }
async function fetchQuotes(symbols){
  const uniq = [...new Set((symbols||[]).map(s=>s.toUpperCase()).filter(Boolean))];
  if (!uniq.length) return {};

  const out = {};

  // Polygon snapshot batches (preferred)
  const polyKey = "5kX56vuJtTKHtLaYz5PT4_4i6eQdxAIG";
  if (polyKey){
    const chunks = [];
    for (let i=0;i<uniq.length;i+=40) chunks.push(uniq.slice(i,i+40));
    for (const c of chunks){
      const url = `https://api.polygon.io/v2/snapshot/locale/us/markets/stocks/tickers?tickers=${encodeURIComponent(c.join(','))}&apiKey=${encodeURIComponent(polyKey)}`;
      try{
        const r = await fetch(url, {cache:'no-store'});
        if (r.ok){
          const j = await r.json();
          const arr = Array.isArray(j?.tickers) ? j.tickers : [];
          for (const item of arr){
            const sym = String(item?.ticker?.ticker ?? item?.ticker).toUpperCase();
            if (!sym) continue;
            const lastTrade = item?.lastTrade;
            const day = item?.day;
            const prev = item?.prevDay;
            const price = typeof lastTrade?.p === 'number' ? lastTrade.p : (typeof day?.c === 'number' ? day.c : (typeof prev?.c === 'number' ? prev.c : null));
            const changePct = typeof item?.todaysChangePerc === 'number' ? item.todaysChangePerc : (typeof day?.c === 'number' && typeof prev?.c === 'number' && prev.c !== 0 ? ((day.c / prev.c) - 1) * 100 : null);
            const volume = typeof day?.v === 'number' ? day.v : null;
            if (price != null) out[sym] = { price, changePct, volume };
          }
        }
      }catch(e){ console.warn('polygon snapshot fail:', e); }
    }
    if (Object.keys(out).length) return out;
  }

  // IEX Cloud batch
  const iexToken = CONFIG?.iex?.token;
  if (iexToken){
    const chunksIex = [];
    for (let i=0;i<uniq.length;i+=100) chunksIex.push(uniq.slice(i,i+100));
    for (const c of chunksIex){
      const url = `https://cloud.iexapis.com/stable/stock/market/batch?symbols=${encodeURIComponent(c.join(','))}&types=quote&token=${encodeURIComponent(iexToken)}`;
      try{
        const r = await fetch(url, {cache:'no-store'});
        if (r.ok){
          const j = await r.json();
          for (const sym of Object.keys(j||{})){
            const q = j[sym]?.quote;
            if (q && typeof q.latestPrice === 'number'){
              out[sym.toUpperCase()] = {
                price: q.latestPrice,
                changePct: typeof q.changePercent === 'number' ? q.changePercent * 100 : null,
                volume: typeof q.latestVolume === 'number' ? q.latestVolume : null
              };
            }
          }
        }
      }catch(e){ console.warn('iex batch fail:', e); }
    }
    if (Object.keys(out).length) return out;
  }

  // Finnhub fallback
  const finnhubToken = CONFIG?.finnhub?.token;
  if (finnhubToken){
    for (const sym of uniq){
      const url = `https://finnhub.io/api/v1/quote?symbol=${encodeURIComponent(sym)}&token=${encodeURIComponent(finnhubToken)}`;
      try{
        const r = await fetch(url, {cache:'no-store'});
        if (r.ok){
          const j = await r.json();
          if (typeof j?.c === 'number'){
            out[sym] = {
              price: j.c,
              changePct: typeof j?.dp === 'number' ? j.dp : null,
              volume: typeof j?.v === 'number' ? j.v : null
            };
          }
        }
      }catch(e){ console.warn('finnhub quote fail:', e); }
    }
    if (Object.keys(out).length) return out;
  }

  // Optional legacy endpoints
  const chunksLegacy = [];
  for (let i=0;i<uniq.length;i+=50) chunksLegacy.push(uniq.slice(i,i+50));
  for (const c of chunksLegacy){
    let got = false;
    for (const ep of QUOTE_ENDPOINTS){
      const url = `${ep}?symbols=${encodeURIComponent(c.join(","))}`;
      try{
        const r = await fetch(url, {cache:"no-store"});
        if (r.ok){
          const j = await r.json();
          if (j && j.quotes && typeof j.quotes === 'object'){
            for (const [sym,val] of Object.entries(j.quotes)){
              const up = String(sym||'').toUpperCase();
              const price = typeof val === 'number' ? val : (typeof val === 'object' && val !== null ? val.price : null);
              if (price != null) out[up] = { price, changePct: null, volume: null };
            }
            got = true;
            break;
          }
        } else {
          console.warn("quote HTTP", r.status, url);
        }
      }catch(e){ console.warn("quote fetch fail:", e); }
    }
    if (!got && CONFIG?.allowYahooFallback){
      const yurl = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=${encodeURIComponent(c.join(','))}`;
      try{
        const r2 = await fetch(yurl, {cache:"no-store"});
        if (r2.ok){
          const j2 = await r2.json();
          const arr = j2?.quoteResponse?.result || [];
          for (const q of arr){
            const t = String(q?.symbol||'').toUpperCase();
            const price = [q?.regularMarketPrice, q?.postMarketPrice, q?.preMarketPrice, q?.regularMarketPreviousClose].find(v=> typeof v === 'number');
            if (t && typeof price === 'number'){
              out[t] = {
                price,
                changePct: typeof q?.regularMarketChangePercent === 'number' ? q.regularMarketChangePercent : null,
                volume: typeof q?.regularMarketVolume === 'number' ? q.regularMarketVolume : null
              };
            }
          }
        }
      }catch(e){ /* ignore Yahoo CORS errors when disabled */ }
    }
  }

  return out;
}

/* ---------- apply live prices across all days ---------- */
function applyLivePricesToData(data, priceMap){
  if (!data) return data;
  const clone = JSON.parse(JSON.stringify(data));
  const sbd = clone.suggestions_by_day || {};
  for (const day of Object.keys(sbd)){
    sbd[day] = (sbd[day]||[]).map(rec=>{
      const t = (rec.ticker||"").toUpperCase();
      const meta = priceMap?.[t] || {};
      const live = meta.price != null ? Number(meta.price) : (rec.current_price ?? null);
      const orig = Number(rec.price);
      let pct = null;
      if (orig!=null && live!=null && !isNaN(orig) && !isNaN(live) && orig!==0){
        pct = (live/orig - 1)*100.0;
      }
      const metrics = {...(rec.metrics||{})};
      if (meta.changePct != null) metrics.poly_day_change_pct = meta.changePct;
      if (meta.volume != null) metrics.poly_day_volume = meta.volume;
      return {...rec, current_price: live, pct_change: pct, metrics};
    });
  }
  clone.suggestions_by_day = sbd;
  return clone;
}

/* ---------- small UI helpers ---------- */
const ageClass = (ms)=> (ms < 90_000 ? "ok" : ms < 5*60_000 ? "warn" : "bad");
function HealthDot({ lastAt }){ if(!lastAt) return null; const age=Date.now()-lastAt; return <span className={`health ${ageClass(age)}`} title={`Last data ${Math.round(age/1000)}s ago`} />; }
function ThemeToggle({theme,setTheme}){ const next = theme==="dark"?"light":"dark"; return <button className="btn theme" onClick={()=>setTheme(next)}>{theme==="dark"?"Dark":"Light"}</button>; }

/* ---------- Filters ---------- */
function FilterBar({ value, onChange, onSortChange }){
  const update = (patch)=>{
    const next = {...value, ...patch};
    onChange(next);
    return next;
  };
  const handleSortChange = (e)=>{
    const val = e.target.value;
    update({sort: val});
    if (onSortChange) onSortChange(val);
  };
  return (
    <div className="card toolbar toolbar-wide">
      <div className="title">Filters</div>
      <div className="toolbar-grid">
        <div className="ctl">
          <label>Search Ticker</label>
          <input className="search" placeholder="e.g., AMD" value={value.search} onChange={e=>update({search:e.target.value.toUpperCase()})}/>
        </div>
        <div className="ctl">
          <label>Min Score: <strong>{value.minScore}%</strong></label>
          <input type="range" min="0" max="100" step="1" value={value.minScore} onChange={e=>update({minScore:Number(e.target.value)})}/>
        </div>
        <div className="ctl">
          <label>Sort</label>
          <select value={value.sort} onChange={handleSortChange}>
            <option value="score">Score (high-low)</option>
            <option value="delta">Change % (high-low)</option>
            <option value="ticker">Ticker (A-Z)</option>
          </select>
        </div>
        <div className="ctl">
          <label>&nbsp;</label>
          <label className="check">
            <input type="checkbox" checked={value.updatesOnly} onChange={e=>update({updatesOnly:e.target.checked})}/>
            <span>Updates only</span>
          </label>
        </div>
      </div>
    </div>
  );
}

function applyFilters(rows, f){
  let out = Array.isArray(rows) ? [...rows] : [];
  if (f.search?.trim()) out = out.filter(r=> (r.ticker||"").toUpperCase().includes(f.search.trim()));
  if (f.updatesOnly) out = out.filter(r=> r.note==="Update");
  out = out.filter(r=> (r.score ?? 0) >= f.minScore);
  return out;
}

const SORT_DEFS = {
  ticker: { type: "string", defaultDir: "asc" },
  score: { type: "number", defaultDir: "desc" },
  price: { type: "number", defaultDir: "desc" },
  current_price: { type: "number", defaultDir: "desc" },
  pct_change: { type: "number", defaultDir: "desc" },
  stop_loss_price: { type: "number", defaultDir: "desc" },
  take_profit_price: { type: "number", defaultDir: "desc" },
  note: { type: "string", defaultDir: "asc" },
  version: { type: "number", defaultDir: "desc" }
};

function sortRows(rows, sort){
  if (!Array.isArray(rows)) return [];
  if (!sort || !sort.key) return [...rows];
  const def = SORT_DEFS[sort.key] || { type: "string", defaultDir: "asc" };
  const dir = sort.dir === "asc" ? 1 : -1;
  const accessor = def.accessor || ((r)=> r?.[sort.key]);
  const copy = [...rows];
  copy.sort((a,b)=>{
    const va = accessor(a);
    const vb = accessor(b);
    const isNullA = va === undefined || va === null || (def.type === "number" && (va === "" || isNaN(Number(va))));
    const isNullB = vb === undefined || vb === null || (def.type === "number" && (vb === "" || isNaN(Number(vb))));
    if (isNullA && isNullB) return 0;
    if (isNullA) return 1;
    if (isNullB) return -1;
    if (def.type === "number"){
      const diff = (Number(va) - Number(vb)) * dir;
      if (diff === 0) return String(a?.ticker||"").localeCompare(String(b?.ticker||""));
      return diff;
    }
    const aStr = String(va).toUpperCase();
    const bStr = String(vb).toUpperCase();
    const cmp = aStr.localeCompare(bStr) * dir;
    if (cmp === 0) return String(a?.ticker||"").localeCompare(String(b?.ticker||""));
    return cmp;
  });
  return copy;
}

function mapFilterSort(value){
  switch(value){
    case "ticker":
      return { key: "ticker", dir: "asc" };
    case "delta":
      return { key: "pct_change", dir: "desc" };
    case "score":
    default:
      return { key: "score", dir: "desc" };
  }
}

/* ---------- Chart/Analysis loader (details drawer) ---------- */
async function fetchChart(symbol, range="3mo", interval="1d"){
  for (const ep of CHART_ENDPOINTS){
    const url = `${ep}?symbol=${encodeURIComponent(symbol)}&range=${encodeURIComponent(range)}&interval=${encodeURIComponent(interval)}`;
    try{
      const r = await fetch(url, {cache:"no-store"});
      if (r.ok){
        return await r.json();
      }
    }catch(e){ /* try next */ }
  }
  // Polygon fallback
  const apiKey = CONFIG?.polygon?.apiKey;
  if (apiKey){
    try{
      const now = new Date();
      const start = new Date(now);
      if (range === '6mo') start.setMonth(start.getMonth()-6); else if (range==='1y') start.setFullYear(start.getFullYear()-1); else start.setMonth(start.getMonth()-3);
      const from = start.getTime();
      const to = now.getTime();
      const url = `https://api.polygon.io/v2/aggs/ticker/${encodeURIComponent(symbol)}/range/1/day/${from}/${to}?adjusted=true&sort=asc&limit=50000&apiKey=${encodeURIComponent(apiKey)}`;
      const r = await fetch(url, {cache:'no-store'});
      if (r.ok){
        const j = await r.json();
        const res = Array.isArray(j?.results) ? j.results : [];
        const t = []; const c = [];
        for (const it of res){
          const ts = typeof it?.t === 'number' ? Math.floor(it.t/1000) : null;
          const close = typeof it?.c === 'number' ? it.c : null;
          if (ts && close != null){ t.push(ts); c.push(close); }
        }
        return { t, c };
      }
    }catch{}
  }
  return { t: [], c: [] };
}
async function fetchAnalysis(symbol){
  const sym = String(symbol||"").toUpperCase();
  if (!sym) return null;
  const url = `${ANALYSIS_DIR}/${encodeURIComponent(sym)}.json?ts=${Date.now()}`;
  try{
    const r = await fetch(url, {cache:"no-store"});
    if(!r.ok) return null;
    return await r.json();
  }catch{ return null; }
}
function MiniChart({ data, id="miniChart" }){
  const ref = useRef(null);
  useEffect(()=>{
    if(!data || !data.t || !data.c || !data.c.length) return;
    const ctx = ref.current.getContext("2d");
    const labels = data.t.map(ts => new Date(ts*1000).toLocaleDateString());
    // Destroy previous instance if any
    if (ref.current._chart) { ref.current._chart.destroy(); }
    ref.current._chart = new Chart(ctx,{
      type:'line',
      data:{ labels, datasets:[{ label:'Close', data: data.c }] },
      options:{
        responsive:true,
        plugins:{ legend:{ display:false } },
        elements:{ point:{ radius:0 } },
        scales:{
          x:{ ticks:{ maxTicksLimit:6, color:getComputedStyle(document.documentElement).getPropertyValue("--mut") } },
          y:{ ticks:{ color:getComputedStyle(document.documentElement).getPropertyValue("--mut") } }
        }
      }
    });
    return ()=>{ if (ref.current && ref.current._chart) ref.current._chart.destroy(); };
  },[data]);
  return <canvas ref={ref} height="110"></canvas>;
}

function ProChart({ analysis, record }){
  const containerRef = useRef(null);
  const chartRef = useRef(null);
  const [tf, setTf] = useState('daily');
  const [ov, setOv] = useState({ vol:true, ema:true, sma:true, bb:true, keltner:true, donch:true, sr:true, patterns:true, sltp:true });
  const annModeRef = useRef(null);
  const annTmpRef = useRef(null);
  const annSeriesRef = useRef([]);
  useEffect(()=>{
    const root = containerRef.current;
    if (!root || !window.LightweightCharts) return;
    if (chartRef.current) { try{ chartRef.current.remove(); }catch{} chartRef.current=null; }
    const data = analysis?.chart_data?.[tf] || analysis?.chart_data?.daily || [];
    if (!Array.isArray(data) || !data.length) return;
    const { createChart } = window.LightweightCharts;
    const cs = getComputedStyle(document.documentElement);
    const textColor = cs.getPropertyValue('--mut') || '#9aa4b2';
    const chart = createChart(root, {
      width: root.clientWidth,
      height: 280,
      layout: { background: { type: 'solid', color: 'transparent' }, textColor },
      rightPriceScale: { borderVisible:false },
      timeScale: { borderVisible:false },
      grid: { vertLines: { color: 'rgba(0,0,0,0.2)' }, horzLines: { color: 'rgba(0,0,0,0.2)' } }
    });
    chartRef.current = chart;
    const candle = chart.addCandlestickSeries({ upColor:'#0bd199', downColor:'#e11d48', borderVisible:false, wickUpColor:'#0bd199', wickDownColor:'#e11d48' });
    const times = data.map(d=> Math.floor(new Date(d.time).getTime()/1000));
    candle.setData(data.map((d,i)=>({ time: times[i], open:d.open, high:d.high, low:d.low, close:d.close })));

    // Volume histogram on separate scale margin
    if (ov.vol){
      const vol = chart.addHistogramSeries({ priceScaleId: 'vol', color:'#94a3b8', base: 0 });
      vol.setData(data.map((d,i)=>({ time: times[i], value: (d.volume||0), color: (d.close>=d.open ? '#34d39980' : '#f8717180') })));
      chart.priceScale('vol').applyOptions({ scaleMargins: { top: 0.8, bottom: 0 } });
    }
    const addLine = (seriesPts, color, width=1)=>{
      if (!Array.isArray(seriesPts) || !seriesPts.length) return null;
      const s = chart.addLineSeries({ color, lineWidth: width, priceLineVisible:false });
      s.setData(seriesPts.map(p=>({ time: Math.floor(new Date(p.time).getTime()/1000), value:p.value })));
      return s;
    };
    const ind = analysis?.indicators || {};
    if (ov.ema){ addLine(ind.ema9,  '#8ab4ff', 1); addLine(ind.ema21, '#ffd666', 1); }
    if (ov.sma){ addLine(ind.sma50, '#f59e0b', 1); addLine(ind.sma200,'#94a3b8', 1); }
    if (ov.bb){ addLine(ind.bb_upper, '#86efac', 1); addLine(ind.bb_middle,'#86efac66', 1); addLine(ind.bb_lower, '#86efac', 1); }
    if (ov.keltner){ addLine(ind.keltner_upper, '#60a5fa', 1); addLine(ind.keltner_lower, '#60a5fa', 1); }
    if (ov.donch){ addLine(ind.donchian_high, '#f59e0b66', 1); addLine(ind.donchian_low, '#f59e0b66', 1); }
    if (ov.sr){
      const levels = analysis?.support_resistance || [];
      for (const lv of levels){
        const y = Number(lv?.level);
        if (!y || Number.isNaN(y)) continue;
        const lc = lv?.type==='support' ? '#34d399' : '#f87171';
        const width = Math.max(1, Math.min(4, Number(lv?.strength)||1));
        const s = chart.addLineSeries({ color: lc, lineWidth:width, priceLineVisible:false });
        const first = times[0]; const last = times[times.length-1];
        s.setData([{ time:first, value:y }, { time:last, value:y }]);
      }
    }
    // Stop/Target overlays from current record if present
    if (ov.sltp){
      const sl = Number(record?.stop_loss_price);
      const tp = Number(record?.take_profit_price);
      if (sl && !Number.isNaN(sl)){
        const s = chart.addLineSeries({ color:'#ef4444', lineWidth:1, lineStyle:2, priceLineVisible:false });
        const first = times[0]; const last = times[times.length-1];
        s.setData([{ time:first, value:sl }, { time:last, value:sl }]);
      }
      if (tp && !Number.isNaN(tp)){
        const s = chart.addLineSeries({ color:'#22c55e', lineWidth:1, lineStyle:2, priceLineVisible:false });
        const first = times[0]; const last = times[times.length-1];
        s.setData([{ time:first, value:tp }, { time:last, value:tp }]);
      }
    }

    if (ov.patterns){
      const pats = Array.isArray(analysis?.patterns) ? analysis.patterns : [];
      for (const p of pats){
        const pts = Array.isArray(p?.points) ? p.points : [];
        if (pts.length >= 2){
          const ls = chart.addLineSeries({ color:'#eab308', lineWidth:1, priceLineVisible:false });
          ls.setData(pts.map(pt=>({ time: Math.floor(new Date(pt.time).getTime()/1000), value: pt.value })));
        }
      }
    }
    const onResize = ()=>{ chart.applyOptions({ width: root.clientWidth }); };
    const ro = new ResizeObserver(onResize); ro.observe(root);
    // annotation clicks
    const onClick = (param)=>{
      const mode = annModeRef.current; if (!mode) return;
      if (param && param.time != null && param.point){
        const t = Math.floor(Number(param.time));
        const price = candle.coordinateToPrice(param.point.y);
        if (mode === 'hline'){
          const s = chart.addLineSeries({ color:'#93c5fd', lineStyle:2, lineWidth:1, priceLineVisible:false });
          s.setData([{ time: times[0], value: price }, { time: times[times.length-1], value: price }]);
          annSeriesRef.current.push(s);
          annModeRef.current = null;
        } else if (mode === 'trend'){
          if (!annTmpRef.current){ annTmpRef.current = { t, price }; }
          else {
            const p1 = annTmpRef.current; const p2 = { t, price };
            const s = chart.addLineSeries({ color:'#93c5fd', lineWidth:1, priceLineVisible:false });
            s.setData([
              { time: Math.min(p1.t, p2.t), value: p1.t <= p2.t ? p1.price : p2.price },
              { time: Math.max(p1.t, p2.t), value: p1.t <= p2.t ? p2.price : p1.price }
            ]);
            annSeriesRef.current.push(s);
            annTmpRef.current = null; annModeRef.current = null;
          }
        }
      }
    };
    chart.subscribeClick(onClick);
    return ()=>{ try{ ro.disconnect(); }catch{} if(chartRef.current){ try{ chartRef.current.remove(); }catch{} chartRef.current=null; } };
  }, [analysis, tf, ov]);
  const hasIntraday = Array.isArray(analysis?.chart_data?.intraday) && analysis.chart_data.intraday.length > 0;
  return (
    <>
      {hasIntraday && (
        <div className="toolbar" style={{marginBottom:8}}>
          <div className="title">Timeframe</div>
          <div>
            <button className={`tab ${tf==='daily'?'active':''}`} onClick={()=>setTf('daily')}>Daily</button>
            <button className={`tab ${tf==='intraday'?'active':''}`} onClick={()=>setTf('intraday')}>Intraday</button>
          </div>
        </div>
      )}
      <div className="overlays">
        {[
          ['vol','Vol'],['ema','EMA'],['sma','SMA'],['bb','BB'],['keltner','Keltner'],['donch','Donchian'],['sr','S/R'],['patterns','Patterns'],['sltp','SL/TP']
        ].map(([k,label])=> (
          <label className="chk" key={k}><input type="checkbox" checked={ov[k]} onChange={e=>setOv(prev=>({...prev,[k]: e.target.checked}))}/> {label}</label>
        ))}
        <div style={{marginLeft:'auto', display:'flex', gap:8}}>
          <button className="tab" onClick={()=>{ annModeRef.current='hline'; }}>Add HLine</button>
          <button className="tab" onClick={()=>{ annModeRef.current='trend'; annTmpRef.current=null; }}>Add Trendline</button>
          <button className="tab" onClick={()=>{ const chart = chartRef.current; if(!chart) return; for(const s of annSeriesRef.current){ try{ chart.removeSeries(s); }catch{} } annSeriesRef.current=[]; }}>Clear</button>
        </div>
      </div>
      <div className="prochart" ref={containerRef}></div>
      <div className="subcharts">
        <SubChartRSI analysis={analysis}/>
        <SubChartMACD analysis={analysis}/>
      </div>
    </>
  );
}

function IndicatorsPanel({ analysis }){
  const ind = analysis?.indicators || {};
  const last = (arr)=> (Array.isArray(arr) && arr.length ? arr[arr.length-1]?.value : null);
  const rows = [
    { label:'EMA9', value:last(ind.ema9) },
    { label:'EMA21', value:last(ind.ema21) },
    { label:'SMA50', value:last(ind.sma50) },
    { label:'SMA200', value:last(ind.sma200) },
    { label:'RSI14', value:last(ind.rsi14) },
    { label:'ATR14', value:last(ind.atr14) },
    { label:'BB Upper', value:last(ind.bb_upper) },
    { label:'BB Middle', value:last(ind.bb_middle) },
    { label:'BB Lower', value:last(ind.bb_lower) },
    { label:'Keltn Upper', value:last(ind.keltner_upper) },
    { label:'Keltn Lower', value:last(ind.keltner_lower) },
    { label:'Donch High', value:last(ind.donchian_high) },
    { label:'Donch Low', value:last(ind.donchian_low) }
  ];
  return (
    <div className="card inner">
      <div className="title">Indicators (latest)</div>
      <div className="metrics-grid">
        {rows.map((r,i)=> (
          <div key={i}><span className="muted small">{r.label}</span><div className="mono">{fmt(r.value)}</div></div>
        ))}
      </div>
    </div>
  );
}

function PatternsPanel({ analysis }){
  const pats = analysis?.patterns || [];
  const levels = analysis?.support_resistance || [];
  const drv = analysis?.derived || {};
  return (
    <div className="card inner">
      <div className="title">Patterns & Levels</div>
      {!pats.length && !levels.length ? <div className="muted small">No detections</div> : null}
      {pats.slice(0,3).map((p,idx)=> (
        <div key={idx} style={{marginBottom:8}}>
          <div><strong>{p.type}</strong> · {p.direction} · conf {fmtpct((p.confidence||0)*100)}</div>
          <div className="muted small">Target {fmtd(p.target_price)} · Stop {fmtd(p.stop_price)}</div>
        </div>
      ))}
      {!!levels.length && (
        <div style={{marginTop:8}}>
          <div className="muted small" style={{marginBottom:6}}>Top Levels</div>
          {levels.slice(0,6).map((lv, i)=> (
            <div key={i} className="small">{lv.type}: <span className="mono">{fmtd(lv.level)}</span> · strength {fmtNumber(lv.strength,0)}</div>
          ))}
        </div>
      )}
      {drv && (
        <div style={{marginTop:8}}>
          <div className="muted small">Derived</div>
          <div className="small">BB Position: {fmtNumber(drv.bb_pos,2)}</div>
          <div className="small">Keltner Upper Dist: {fmtpct(drv.keltner_upper_dist_pct)}</div>
          <div className="small">Keltner Lower Dist: {fmtpct(drv.keltner_lower_dist_pct)}</div>
          <div className="small">Nearest {drv.sr_nearest_type || '-'}: {fmtd(drv.sr_nearest_level)} ({fmtpct(drv.sr_nearest_dist_pct)})</div>
        </div>
      )}
    </div>
  );
}

function SubChartRSI({ analysis }){
  const ref = useRef(null);
  const chartRef = useRef(null);
  useEffect(()=>{
    const root = ref.current; if (!root || !window.LightweightCharts) return;
    if (chartRef.current){ try{ chartRef.current.remove(); }catch{} chartRef.current=null; }
    const data = analysis?.indicators?.rsi14 || [];
    if (!Array.isArray(data) || !data.length) return;
    const { createChart } = window.LightweightCharts;
    const cs = getComputedStyle(document.documentElement);
    const textColor = cs.getPropertyValue('--mut') || '#9aa4b2';
    const chart = createChart(root, { width: root.clientWidth, height: 160, layout:{ background:{type:'solid', color:'transparent'}, textColor }, rightPriceScale:{ borderVisible:false }, timeScale:{ borderVisible:false }, grid:{ vertLines:{ color:'rgba(0,0,0,0.2)'}, horzLines:{ color:'rgba(0,0,0,0.2)'} } });
    chartRef.current = chart;
    const s = chart.addLineSeries({ color:'#a78bfa', lineWidth:1, priceLineVisible:false });
    const pts = data.map(p=>({ time: Math.floor(new Date(p.time).getTime()/1000), value: p.value }));
    s.setData(pts);
    // 70/30 reference
    const t0 = pts[0]?.time, t1 = pts[pts.length-1]?.time;
    const ovr = chart.addLineSeries({ color:'#ef4444', lineWidth:1, priceLineVisible:false });
    ovr.setData([{time:t0, value:70},{time:t1, value:70}]);
    const und = chart.addLineSeries({ color:'#22c55e', lineWidth:1, priceLineVisible:false });
    und.setData([{time:t0, value:30},{time:t1, value:30}]);
    const ro = new ResizeObserver(()=> chart.applyOptions({width: root.clientWidth})); ro.observe(root);
    return ()=>{ try{ ro.disconnect(); }catch{} if(chartRef.current){ try{ chartRef.current.remove(); }catch{} chartRef.current=null; } };
  }, [analysis]);
  return <div className="prochart-small" ref={ref}></div>;
}

function SubChartMACD({ analysis }){
  const ref = useRef(null);
  const chartRef = useRef(null);
  useEffect(()=>{
    const root = ref.current; if (!root || !window.LightweightCharts) return;
    if (chartRef.current){ try{ chartRef.current.remove(); }catch{} chartRef.current=null; }
    const macd = analysis?.indicators?.macd || [];
    const sig = analysis?.indicators?.macd_signal || [];
    const hist = analysis?.indicators?.macd_hist || [];
    if (!Array.isArray(macd) || !macd.length) return;
    const { createChart } = window.LightweightCharts;
    const cs = getComputedStyle(document.documentElement);
    const textColor = cs.getPropertyValue('--mut') || '#9aa4b2';
    const chart = createChart(root, { width: root.clientWidth, height: 160, layout:{ background:{type:'solid', color:'transparent'}, textColor }, rightPriceScale:{ borderVisible:false }, timeScale:{ borderVisible:false }, grid:{ vertLines:{ color:'rgba(0,0,0,0.2)'}, horzLines:{ color:'rgba(0,0,0,0.2)'} } });
    chartRef.current = chart;
    const times = macd.map(p=> Math.floor(new Date(p.time).getTime()/1000));
    const l1 = chart.addLineSeries({ color:'#60a5fa', lineWidth:1, priceLineVisible:false });
    l1.setData(macd.map((p,i)=>({ time:times[i], value:p.value })));
    if (Array.isArray(sig) && sig.length){
      const l2 = chart.addLineSeries({ color:'#f59e0b', lineWidth:1, priceLineVisible:false });
      const t2 = sig.map(p=> Math.floor(new Date(p.time).getTime()/1000));
      l2.setData(sig.map((p,i)=>({ time:t2[i], value:p.value })));
    }
    if (Array.isArray(hist) && hist.length){
      const h = chart.addHistogramSeries({ base:0 });
      const th = hist.map(p=> Math.floor(new Date(p.time).getTime()/1000));
      h.setData(hist.map((p,i)=>({ time: th[i], value: p.value, color: (p.value>=0 ? '#34d39980' : '#f8717180') })));
    }
    const ro = new ResizeObserver(()=> chart.applyOptions({width: root.clientWidth})); ro.observe(root);
    return ()=>{ try{ ro.disconnect(); }catch{} if(chartRef.current){ try{ chartRef.current.remove(); }catch{} chartRef.current=null; } };
  }, [analysis]);
  return <div className="prochart-small" ref={ref}></div>;
}

function VersionHistory({ versions }){
  if (!Array.isArray(versions) || !versions.length) return null;
  const sorted = [...versions].sort((a,b)=>{
    const da = (a.date || "").toString();
    const db = (b.date || "").toString();
    const cmp = da.localeCompare(db);
    if (cmp !== 0) return cmp;
    return (a.version||0) - (b.version||0);
  });
  return (
    <div className="card inner versions">
      <div className="title">Version History</div>
      <div className="timeline">
        {sorted.map((v, idx)=>{
          const ver = v.version ?? idx + 1;
          const dateLabel = v.date ? String(v.date) : null;
          return (
            <div className="timeline-row" key={`${ver}-${idx}`}>
              <div className="mono">v{ver}</div>
              <div className="timeline-body">
                <div className="timeline-price">Price {fmtd(v.price)}{dateLabel ? ` · ${dateLabel}` : ''}</div>
                <div className="muted small">Stop {v.stop_loss_price ? fmtd(v.stop_loss_price) : '—'} · Target {v.take_profit_price ? fmtd(v.take_profit_price) : '—'} · Score {(v.score ?? 0)+'%'}{v.note ? ` · ${v.note}` : ''}</div>
                {v.reason ? <div className="muted small" style={{marginTop:4}}>{v.reason}</div> : null}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ---------- Detail Drawer ---------- */
function DetailDrawer({ open, onClose, record }){
  const [loading,setLoading] = useState(false);
  const [analysis,setAnalysis] = useState(null);
  const [mini,setMini] = useState(null);
  const [tab,setTab] = useState('overview');
  const [live, setLive] = useState(null);
  useEffect(()=>{
    if (!open || !record?.ticker) return;
    setLoading(true);
    setAnalysis(null); setMini(null);
    setLive(null);
    const embedded = record?.analysis;
    const apply = (a)=>{
      setAnalysis(a);
      const daily = a?.chart_data?.daily || [];
      if (daily.length){
        const t = []; const c = [];
        for (const p of daily){ t.push(Math.floor(new Date(p.time).getTime()/1000)); c.push(p.close); }
        setMini({ t, c });
      }
    };
    if (embedded && typeof embedded === 'object'){
      apply(embedded); setLoading(false); return;
    }
    fetchAnalysis(record.ticker).then(a=>{
      if(a){ apply(a); return; }
      // Fallback to legacy chart endpoint for mini only
      return fetchChart(record.ticker).then(j=> setMini(j)).catch(()=> setMini(null));
    }).finally(()=> setLoading(false));
  },[open, record?.ticker]);

  // Freshen per-symbol metrics client-side via Polygon (preferred), then IEX/Finnhub
  useEffect(()=>{
    if (!open || !record?.ticker) return;
    let abort = false;
    (async()=>{
      const sym = record.ticker;
      // Polygon snapshot + short interest + recent news counts
      const polyKey = CONFIG?.polygon?.apiKey;
      if (polyKey){
        try{
          const [snapR, siR, newsR] = await Promise.all([
            fetch(`https://api.polygon.io/v2/snapshot/locale/us/markets/stocks/tickers/${encodeURIComponent(sym)}?apiKey=${encodeURIComponent(polyKey)}`, {cache:'no-store'}),
            fetch(`https://api.polygon.io/v3/reference/short-interest?ticker=${encodeURIComponent(sym)}&limit=1&order=desc&apiKey=${encodeURIComponent(polyKey)}`, {cache:'no-store'}),
            (async()=>{
              const since7 = new Date(Date.now() - 7*24*3600*1000).toISOString();
              const url = `https://api.polygon.io/v2/reference/news?ticker=${encodeURIComponent(sym)}&published_utc.gte=${encodeURIComponent(since7)}&order=desc&limit=50&apiKey=${encodeURIComponent(polyKey)}`;
              return fetch(url, {cache:'no-store'});
            })()
          ]);
          let price=null, changePct=null, volume=null, shortPctFloat=null, daysToCover=null, news3d=0, newsSent=0;
          if (snapR.ok){
            const j = await snapR.json();
            const t = j?.ticker;
            price = (t?.lastTrade?.p ?? t?.day?.c ?? t?.prevDay?.c) ?? null;
            changePct = (typeof t?.todaysChangePerc === 'number' ? t.todaysChangePerc : null);
            volume = (typeof t?.day?.v === 'number' ? t.day.v : null);
          }
          if (siR.ok){
            const j = await siR.json();
            const last = Array.isArray(j?.results) ? j.results[0] : null;
            if (last){
              shortPctFloat = (typeof last?.short_interest_share_percent === 'number' ? last.short_interest_share_percent : null);
              daysToCover = (typeof last?.days_to_cover === 'number' ? last.days_to_cover : (typeof last?.dsi === 'number' ? last.dsi : null));
            }
          }
          if (newsR.ok){
            const j = await newsR.json();
            const items = Array.isArray(j?.results) ? j.results : [];
            const cutoff3 = Date.now() - 3*24*3600*1000;
            const kwPos = ["beat","beats","record","raises","upgrade","surge","growth","profit","guidance up","launch","contract","wins"]; 
            const kwNeg = ["miss","misses","downgrade","cut","guidance down","lawsuit","probe","recall","delay","bankruptcy","sec charges","fda halt"];
            for (const it of items){
              const ts = Date.parse(it?.published_utc || it?.timestamp || 0);
              if (!isNaN(ts) && ts >= cutoff3) news3d++;
              const title = String(it?.title||'').toLowerCase();
              let s=0; for (const w of kwPos) if (title.includes(w)) s++; for (const w of kwNeg) if (title.includes(w)) s--;
              newsSent += s;
            }
          }
          if (!abort) setLive({ price, changePct, volume, shortPctFloat, daysToCover, news_count_3d: news3d, news_sent_sum: newsSent });
          return;
        }catch{}
      }
      // IEX
      const iexToken = CONFIG?.iex?.token;
      if (iexToken){
        try{
          const url = `https://cloud.iexapis.com/stable/stock/${encodeURIComponent(sym)}/quote?token=${encodeURIComponent(iexToken)}`;
          const r = await fetch(url, {cache:'no-store'});
          if (r.ok){
            const q = await r.json();
            const payload = {
              price: (typeof q?.latestPrice === 'number' ? q.latestPrice : null),
              changePct: (typeof q?.changePercent === 'number' ? q.changePercent * 100 : null),
              volume: (typeof q?.latestVolume === 'number' ? q.latestVolume : null),
              shortPctFloat: null,
              daysToCover: null
            };
            if (!abort) setLive(payload);
            return;
          }
        }catch{}
      }
      // Finnhub
      const finnhubToken = CONFIG?.finnhub?.token;
      if (finnhubToken){
        try{
          const url = `https://finnhub.io/api/v1/quote?symbol=${encodeURIComponent(sym)}&token=${encodeURIComponent(finnhubToken)}`;
          const r = await fetch(url, {cache:'no-store'});
          if (r.ok){
            const q = await r.json();
            const payload = {
              price: (typeof q?.c === 'number' ? q.c : null),
              changePct: (typeof q?.dp === 'number' ? q.dp : null),
              volume: null, // not in /quote
              shortPctFloat: null,
              daysToCover: null
            };
            if (!abort) setLive(payload);
            return;
          }
        }catch{}
      }
      // Fallback to local API if explicitly enabled
      if (CONFIG?.useLocalApi){
        try{
          const url = `${LOCAL_API}/metrics?symbol=${encodeURIComponent(sym)}`;
          const r = await fetch(url, {cache:'no-store'});
          if (r.ok){
            const j = await r.json();
            if (!abort) setLive(j);
          }
        }catch{}
      }
    })();
    return ()=>{ abort = true; };
  }, [open, record?.ticker]);

  useEffect(()=>{
    const onKey = (e)=>{ if (e.key==="Escape") onClose(); };
    if (open) document.addEventListener("keydown", onKey);
    return ()=> document.removeEventListener("keydown", onKey);
  },[open,onClose]);

  if (!open || !record) return null;
  const currentPx = (typeof live?.price === 'number') ? live.price : record.current_price;
  const pctNow = (typeof currentPx === 'number' && typeof record.price === 'number' && record.price !== 0)
    ? ((currentPx/record.price - 1) * 100)
    : record.pct_change;
  const dir = Number(pctNow)>=0 ? "pos":"neg";
  let chartPlan = null;
  if (record.chart_plan){
    if (typeof record.chart_plan === 'string'){
      try { chartPlan = JSON.parse(record.chart_plan); }
      catch { chartPlan = { notes: record.chart_plan }; }
    } else if (typeof record.chart_plan === 'object'){
      chartPlan = record.chart_plan;
    }
  }
  const planTimeframes = Array.isArray(chartPlan?.timeframes) ? chartPlan.timeframes : [];
  const planOverlays = Array.isArray(chartPlan?.overlays) ? chartPlan.overlays : [];
  const planAnnotations = Array.isArray(chartPlan?.annotations) ? chartPlan.annotations : [];
  return (
    <div className="drawer">
      <div className="drawer-backdrop" onClick={onClose}/>
      <div className="drawer-panel">
        <div className="drawer-header">
          <div className="tkr">{record.ticker}</div>
          <button className="btn" onClick={onClose}>Close</button>
        </div>
        <div className="drawer-body">
          <div className="stats">
            <div><span className="muted small">Original</span><div className="mono">{fmtd(record.price)}</div></div>
            <div><span className="muted small">Current</span><div className={`mono ${dir}`}>{fmtd(currentPx)}</div></div>
            <div><span className="muted small">Change %</span><div className={dir}>{fmtpct(pctNow)}</div></div>
            <div><span className="muted small">Score</span><div>{(record.score??0)+'%'}</div></div>
            <div><span className="muted small">Stop</span><div>{record.stop_loss_price? fmtd(record.stop_loss_price) : "--"}</div></div>
            <div><span className="muted small">Target</span><div>{record.take_profit_price? fmtd(record.take_profit_price) : "--"}</div></div>
          </div>

          <div className="tabs">
            <button className={`tab ${tab==='overview'?'active':''}`} onClick={()=>setTab('overview')}>Overview</button>
            <button className={`tab ${tab==='pro'?'active':''}`} onClick={()=>setTab('pro')}>Pro Chart</button>
            <button className={`tab ${tab==='ind'?'active':''}`} onClick={()=>setTab('ind')}>Indicators</button>
            <button className={`tab ${tab==='pat'?'active':''}`} onClick={()=>setTab('pat')}>Patterns/Levels</button>
          </div>

          {tab==='overview' && (
            <div className="card inner">
              <div className="title">Price (last 3 months)</div>
              {loading ? <div className="muted small">Loading chart...</div> : <MiniChart data={mini}/>}
              <VersionHistory versions={record.history || record.versions}/>
            </div>
          )}
          {tab==='pro' && (
            <div className="card inner">
              <div className="title">Pro Chart</div>
              {!analysis ? <div className="muted small">No analysis available.</div> : <ProChart analysis={analysis} record={record}/>}
            </div>
          )}
          {tab==='ind' && (
            <IndicatorsPanel analysis={analysis}/>
          )}
          {tab==='pat' && (
            <PatternsPanel analysis={analysis}/>
          )}

          {record.metrics ? (
            <div className="card inner">
              <div className="title">Latest Metrics</div>
              <div className="metrics-grid">
                <div><span className="muted small">News Count (3d)</span><div>{(live?.news_count_3d != null) ? fmtNumber(live.news_count_3d) : fmtNumber(record.metrics.news_count_3d)}</div></div>
                <div><span className="muted small">News Sentiment</span><div>{(live?.news_sent_sum != null) ? fmtNumber(live.news_sent_sum) : fmtNumber(record.metrics.news_sent_sum)}</div></div>
                <div><span className="muted small">Day Change %</span><div>{(live?.changePct != null) ? fmtpct(live.changePct) : (record.metrics.poly_day_change_pct != null ? fmtpct(record.metrics.poly_day_change_pct) : "--")}</div></div>
                <div><span className="muted small">Day Volume</span><div>{(live?.volume != null) ? fmtNumber(live.volume) : (record.metrics.poly_day_volume != null ? fmtNumber(record.metrics.poly_day_volume) : "--")}</div></div>
                <div><span className="muted small">Short Interest</span><div>{(live?.shortPctFloat != null) ? fmtNumber(live.shortPctFloat, 2) : (record.metrics.poly_short_interest != null ? fmtNumber(record.metrics.poly_short_interest, 2) : "--")}</div></div>
                <div><span className="muted small">Days to Cover</span><div>{(live?.daysToCover != null) ? fmtNumber(live.daysToCover, 2) : (record.metrics.poly_days_to_cover != null ? fmtNumber(record.metrics.poly_days_to_cover, 2) : "--")}</div></div>
              </div>
            </div>
          ): null}

          {chartPlan ? (
            <div className="card inner">
              <div className="title">AI Chart Plan</div>
              {planTimeframes.length ? (
                <div className="muted small">Timeframes: <strong>{planTimeframes.join(', ')}</strong></div>
              ) : null}
              {planOverlays.length ? (
                <div style={{marginTop:8}}>
                  <div className="muted small" style={{marginBottom:4}}>Overlays</div>
                  <ul className="muted small" style={{paddingLeft:16, margin:0, display:'flex', flexDirection:'column', gap:4}}>
                    {planOverlays.map((ov, idx)=>(
                      <li key={idx}>
                        <strong>{ov?.type || 'overlay'}</strong>{ov?.params ? ` · ${JSON.stringify(ov.params)}` : ''}{ov?.why ? ` — ${ov.why}` : ''}
                      </li>
                    ))}
                  </ul>
                </div>
              ) : null}
              {planAnnotations.length ? (
                <div style={{marginTop:8}}>
                  <div className="muted small" style={{marginBottom:4}}>Annotations</div>
                  <ul className="muted small" style={{paddingLeft:16, margin:0, display:'flex', flexDirection:'column', gap:4}}>
                    {planAnnotations.map((ann, idx)=>(
                      <li key={idx}>
                        <strong>{ann?.label || ann?.type || `Level ${idx+1}`}</strong>{ann?.price != null ? ` @ ${fmtd(ann.price)}` : ''}{ann?.context ? ` — ${ann.context}` : ''}
                      </li>
                    ))}
                  </ul>
                </div>
              ) : null}
              {chartPlan?.notes ? (
                <div className="muted small" style={{marginTop:8, whiteSpace:'pre-wrap'}}>{chartPlan.notes}</div>
              ) : null}
            </div>
          ) : null}

          {record.reason ? (
            <div className="card inner">
              <div className="title">Why this was suggested</div>
              <div style={{whiteSpace:"pre-wrap"}}>{record.reason}</div>
            </div>
          ): null}

          {Array.isArray(record.news) && record.news.length ? (
            <div className="card inner">
              <div className="title">Latest News</div>
              <ul className="news-list">
                {record.news.map((n, idx)=>(
                  <li key={idx}>
                    <a href={n.url || '#'} target="_blank" rel="noreferrer">
                      {n.title || n.url || 'View article'}
                    </a>
                    {n.source ? <span className="muted small">{` - ${n.source}`}</span> : null}
                  </li>
                ))}
              </ul>
            </div>
          ): null}

          <VersionHistory versions={record.history || record.versions}/>
        </div>
      </div>
    </div>
  );
}

/* ---------- Hero + Tables ---------- */
function HeroRow({ title, items, onPick }){
  if(!items || !items.length) return null;
  return (
    <div className="card" style={{paddingTop:12}}>
      <div className="title" style={{marginBottom:12}}>{title}</div>
      <div className="hero-grid">
        {items.map((r,i)=>{
          const dir = Number(r.pct_change)>=0 ? "pos":"neg";
          return (
            <button className="hero-card" key={i} title={r.reason||""} onClick={()=>onPick(r)}>
              <div className="hc-top">
                <div className="tkr">{r.ticker}</div>
                {typeof r.score==="number" ? <div className="muted small">Score {(r.score|0)}%</div> : <div/>}
              </div>
              <div className="hc-row"><span className="muted small">Original</span><span className="mono">{fmtd(r.price)}</span></div>
              <div className="hc-row"><span className="muted small">Current</span><span className={`mono ${dir}`}>{fmtd(r.current_price)}</span></div>
              <div className="hc-bottom"><div className={`chg ${dir}`}>{fmtpct(r.pct_change)}</div>{r.note?<span className={`badge ${r.note==="Update"?"update":"new"}`}>{r.note}</span>:<span/>}</div>
            </button>
          );
        })}
      </div>
    </div>
  );
}

function SuggestionsTable({ title, items, onPick, onSort, sortState }){
  const rows = Array.isArray(items) ? items : [];
  const columns = [
    { key: "ticker", label: "Ticker", sortable: true },
    { key: "score", label: "Score", sortable: true },
    { key: "price", label: "Original", sortable: true },
    { key: "current_price", label: "Current", sortable: true },
    { key: "pct_change", label: "Change %", sortable: true },
    { key: "stop_loss_price", label: "Stop", sortable: true },
    { key: "take_profit_price", label: "Target", sortable: true },
    { key: "note", label: "Note", sortable: true },
    { key: "version", label: "Rev", sortable: true }
  ];
  const handleSort = (key)=>{ if (onSort) onSort(key); };
  return (
    <div className="card">
      {title ? <div className="title">{title}</div> : null}
      <div className="table-scroll">
        <table className="table">
          <thead>
            <tr>
              {columns.map(col=>{
                const isActive = sortState?.key === col.key;
                const dir = isActive ? sortState?.dir : null;
                const sortAria = isActive ? (dir === "asc" ? "ascending" : "descending") : "none";
                const btnClasses = ['sort-btn'];
                if (isActive){
                  btnClasses.push('active');
                  if (dir) btnClasses.push(dir);
                }
                return (
                  <th key={col.key} aria-sort={sortAria}>
                    {col.sortable && onSort ? (
                      <button type="button" className={btnClasses.join(' ')} onClick={()=>handleSort(col.key)}>
                        <span>{col.label}</span>
                        <span className="sort-indicator" aria-hidden="true" />
                      </button>
                    ) : col.label}
                  </th>
                );
              })}
            </tr>
          </thead>
          <tbody>
            {rows.length ? rows.map((r,i)=>{
              const cls = Number(r.pct_change)>=0 ? "pos" : "neg";
              const badge = r.note==='Update' ? <span className="badge update">Update</span> : <span className="badge new">New</span>;
              return (
                <tr key={`${r.ticker||'row'}-${i}`} onClick={()=>onPick(r)} style={{cursor:"pointer"}}>
                  <td className="mono">{r.ticker}</td>
                  <td>{(r.score ?? 0)+'%'}</td>
                  <td>{fmtd(r.price)}</td>
                  <td className={`mono ${cls}`}>{fmtd(r.current_price)}</td>
                  <td className={cls}>{fmtpct(r.pct_change)}</td>
                  <td>{r.stop_loss_price ? fmtd(r.stop_loss_price) : '-'}</td>
                  <td>{r.take_profit_price ? fmtd(r.take_profit_price) : '-'}</td>
                  <td>{badge}</td>
                  <td>{r.version ?? 1}</td>
                </tr>
              );
            }) : (
              <tr>
                <td colSpan={columns.length} className="muted small" style={{textAlign:"center", padding:'18px 0'}}>
                  No suggestions.
                </td>
              </tr>
            )}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function DayBrowser({ suggestionsByDay, onPick, active }){
  const [q,setQ] = useState("");
  const daysAll = useMemo(()=>Object.keys(suggestionsByDay||{}).sort((a,b)=>b.localeCompare(a)),[suggestionsByDay]);
  const days = useMemo(()=>daysAll.filter(d=>d.includes(q.trim())),[q,daysAll]);
  return (
    <div className="sidebar card">
      <div className="title">History</div>
      <input className="search" placeholder="Filter YYYY-MM-DD..." value={q} onChange={e=>setQ(e.target.value)} />
      <div className="daylist">
      {days.map(d=>
        <button key={d} className={`btn-day ${active===d?'active':''}`} onClick={()=>onPick(d)}>
          <div className="big">{d}</div>
          <div className="sub">{(suggestionsByDay[d]||[]).length} items</div>
        </button>
      )}
      </div>
    </div>
  );
}

/* ---------- App ---------- */
function App(){
  const {theme,setTheme} = useTheme();
  const {data, setData, err, lastAt, refreshNow} = useDashboard();
  const [activeDay,setActiveDay] = useState(null);
  const [loadingQuotes,setLoadingQuotes] = useState(false);
  const [drawer,setDrawer] = useState({open:false, record:null});

  const suggestionsByDay = data?.suggestions_by_day || {};
  const days = useMemo(()=>Object.keys(suggestionsByDay).sort((a,b)=>b.localeCompare(a)),[suggestionsByDay]);
  const latest = days[0] || null;
  const active = activeDay || latest;

  const latestData = suggestionsByDay[latest] || [];
  const activeData = suggestionsByDay[active] || [];

  // Top-5
  const todayTop5 = useMemo(()=>[...latestData].sort((a,b)=>(b.score||0)-(a.score||0)).slice(0,5),[latestData]);
  const allRecords = useMemo(()=>{
    const recs=[]; for(const d of Object.keys(suggestionsByDay)){ for(const r of suggestionsByDay[d]) recs.push({...r, date:d}); }
    return recs;
  },[suggestionsByDay]);
  const topAllTime = useMemo(()=>[...allRecords]
      .filter(r=>typeof r.pct_change==="number" && !isNaN(r.pct_change))
      .sort((a,b)=>(b.pct_change||-1e9)-(a.pct_change||-1e9))
      .slice(0,5),[allRecords]);

  // Collect all tickers for live quotes
  const allTickers = useMemo(()=>{
    if(!data) return [];
    const arr=[]; for(const d of Object.keys(suggestionsByDay)){ for(const r of suggestionsByDay[d]) if(r?.ticker) arr.push(r.ticker.toUpperCase()); }
    return [...new Set(arr)];
  },[data, suggestionsByDay]);

  // Load quotes once after dashboard.json arrives
  const refreshPrices = async ()=>{
    if (!data || !allTickers.length) return;
    const canLive = !!(CONFIG?.polygon?.apiKey || CONFIG?.iex?.token || CONFIG?.finnhub?.token || (QUOTE_ENDPOINTS && QUOTE_ENDPOINTS.length) || CONFIG?.allowYahooFallback);
    if (!canLive) return;
    setLoadingQuotes(true);
    try{
      const priceMap = await fetchQuotes(allTickers);
      if (priceMap && Object.keys(priceMap).length){
        const next = applyLivePricesToData(data, priceMap);
        setData(next);
      }
    }catch(e){
      console.warn('refreshPrices failed:', e);
    }finally{
      setLoadingQuotes(false);
    }
  };
  useEffect(()=>{ if (data) refreshPrices(); },[data]);

  // Optional periodic auto-refresh
  useEffect(()=>{
    const canLive = !!(CONFIG?.polygon?.apiKey || CONFIG?.iex?.token || CONFIG?.finnhub?.token || (QUOTE_ENDPOINTS && QUOTE_ENDPOINTS.length) || CONFIG?.allowYahooFallback);
    if (!REFRESH_MS || !canLive) return;
    const id = setInterval(()=>{ refreshPrices(); }, REFRESH_MS);
    return ()=> clearInterval(id);
  },[allTickers, data]);

  // Filters for tables
  const [filters,setFilters] = useState({search:"", minScore:0, updatesOnly:false, sort:"score"});
  const [tableSort,setTableSort] = useState(()=>mapFilterSort('score'));
  const latestFiltered = useMemo(()=>{
    const filtered = applyFilters(latestData, filters);
    return sortRows(filtered, tableSort);
  },[latestData, filters, tableSort]);
  const activeFiltered = useMemo(()=>{
    const filtered = applyFilters(activeData, filters);
    return sortRows(filtered, tableSort);
  },[activeData, filters, tableSort]);

  const handleSortSelect = (value)=>{
    setTableSort(mapFilterSort(value));
  };

  const handleColumnSort = (key)=>{
    setTableSort(prev=>{
      const current = prev || {};
      const def = SORT_DEFS[key] || { defaultDir: 'asc' };
      const defaultDir = def.defaultDir || 'asc';
      const nextDir = current.key === key ? (current.dir === 'asc' ? 'desc' : 'asc') : defaultDir;
      return { key, dir: nextDir };
    });
    const dropdownMap = { score: 'score', pct_change: 'delta', ticker: 'ticker' };
    if (dropdownMap[key]){
      setFilters(prev => prev.sort === dropdownMap[key] ? prev : {...prev, sort: dropdownMap[key]});
    }
  };

  const handleSetPolygonKey = ()=>{
    const existing = CONFIG?.polygon?.apiKey || '';
    const entered = window.prompt('Enter Polygon API key (stored locally in your browser). Leave blank to remove.', existing);
    if (entered === null) return;
    const trimmed = entered.trim();
    const nextCfg = {...CONFIG};
    if (trimmed){
      nextCfg.polygon = { apiKey: trimmed };
    } else {
      if (nextCfg.polygon) delete nextCfg.polygon;
    }
    localStorage.setItem('aiadvisor.cfg', JSON.stringify(nextCfg));
    window.AIADVISOR_CFG = nextCfg;
    window.location.reload();
  };

  const openDetails = (rec)=> setDrawer({open:true, record:rec});
  const closeDetails = ()=> setDrawer({open:false, record:null});

  if (err) return <div style={{padding:20,color:"#f87171"}}>Error loading dashboard.json.</div>;
  if (!data) return <div style={{padding:20}}>Loading...</div>;

  return (
    <>
      <div className="nav">
        <div style={{display:"flex",alignItems:"center",gap:10}}>
          <div className="brand">AI Advisor</div>
          <span className="muted small">Suggestions</span>
          <HealthDot lastAt={lastAt}/>
          <div className="muted small">Data {lastAt ? new Date(lastAt).toLocaleTimeString() : "--"}</div>
          {(() => {
            const src = CONFIG?.polygon?.apiKey ? 'Polygon' : (CONFIG?.iex?.token ? 'IEX' : (CONFIG?.finnhub?.token ? 'Finnhub' : null));
            const canLive = !!src || (QUOTE_ENDPOINTS && QUOTE_ENDPOINTS.length) || CONFIG?.allowYahooFallback;
            const label = canLive ? `${loadingQuotes ? 'updating...' : 'live'}${src ? ` · ${src}` : ''}` : 'off · add Polygon API key';
            return <div className="muted small">| Prices {label}</div>;
          })()}
        </div>
        <div className="nav-actions">
          <ThemeToggle theme={theme} setTheme={setTheme}/>
          <button className="btn" onClick={refreshNow} title="Reload dashboard.json">Reload Data</button>
          <button className="btn" onClick={refreshPrices} title="Refresh live prices">Refresh Prices</button>
          {!CONFIG?.polygon?.apiKey ? <button className="btn" onClick={handleSetPolygonKey}>Set Polygon Key</button> : null}
        </div>
      </div>

      <div className="container split">
        <div className="leftcol">
          <DayBrowser suggestionsByDay={suggestionsByDay} onPick={setActiveDay} active={active}/>
        </div>

        <div className="rightcol">
          <HeroRow title="Top 5 – Newest Top-Rated (Today)" items={todayTop5} onPick={openDetails}/>
          <div className="mt"></div>
          <HeroRow title="Top 5 – All-Time % Increasing" items={topAllTime} onPick={openDetails}/>

          <div className="mt">
            <FilterBar value={filters} onChange={setFilters} onSortChange={handleSortSelect}/>
          </div>

          <div className="latest mt">
            <SuggestionsTable title="Latest Suggestions (Today)" items={latestFiltered} onPick={openDetails} onSort={handleColumnSort} sortState={tableSort}/>
            <SuggestionsTable title={`Suggestions on ${active}`} items={activeFiltered} onPick={openDetails} onSort={handleColumnSort} sortState={tableSort}/>
          </div>
        </div>
      </div>

      <DetailDrawer open={drawer.open} onClose={closeDetails} record={drawer.record}/>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);
