// app.jsx — main App composition

const { useState: uS, useEffect: uE, useMemo: uM, useCallback: uC, useRef: uR } = React;

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "matrixIntensity": "heavy",
  "cardStyle": "full",
  "layout": "grid",
  "soundOn": false,
  "codeRain": true,
  "scanline": true,
  "dataSource": "live",
  "liveProvider": "yahoo",
  "refreshSec": 60,
  "showLowConf": false
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [showIntro, setShowIntro] = uS(true);
  const [rotation, setRotation] = uS(0);
  const simStocks = uM(() => buildStocks(rotation), [rotation]);
  const [liveStocks, setLiveStocks] = uS(null);
  const [liveStatus, setLiveStatus] = uS({ phase: 'idle', done: 0, total: 0, errors: 0, lastUpdate: null, dataAge: null, stale: false, nextPollMs: null });
  const stocks = (t.dataSource === 'live' && liveStocks) ? liveStocks : simStocks;
  const nextPollRef = uR(null);

  // live data fetcher
  const refetchLive = uC(async () => {
    if (!window.buildStocksLive) return;
    setLiveStatus(s => ({ ...s, phase: 'loading', done: 0, total: window.TICKERS.length, errors: 0 }));
    try {
      const result = await window.buildStocksLive(
        t.liveProvider,
        ({ done, total }) => setLiveStatus(s => ({ ...s, done, total }))
      );
      setLiveStocks(result.stocks);
      nextPollRef.current = result.nextPollMs || null;
      setLiveStatus({
        phase: 'ready',
        done: result.stocks.length,
        total: window.TICKERS.length,
        errors: result.errors.length,
        lastUpdate: result.stale ? null : new Date(),
        dataAge: result.dataAge,
        stale: result.stale || false,
        nextPollMs: result.nextPollMs || null,
      });
    } catch (err) {
      setLiveStatus(s => ({ ...s, phase: 'error', errMsg: String(err) }));
    }
  }, [t.liveProvider]);

  // kick off live fetch when data source is switched to 'live'
  uE(() => {
    if (t.dataSource !== 'live') return;
    if (showIntro) return;
    refetchLive();
    const schedule = () => {
      const ms = nextPollRef.current || Math.max(15, t.refreshSec) * 1000;
      return setTimeout(() => { refetchLive().then(() => { timerId = schedule(); }); }, ms);
    };
    let timerId = schedule();
    return () => clearTimeout(timerId);
  }, [t.dataSource, t.liveProvider, t.refreshSec, showIntro, refetchLive]);
  const [feed, setFeed] = uS([]);
  const [journal, setJournal] = uS([]);
  const [analyzing, setAnalyzing] = uS(null); // stock to analyze
  const [autonomous, setAutonomous] = uS(false);
  const [scanIdx, setScanIdx] = uS(-1); // which card index is being scanned
  const [now, setNow] = uS(new Date());
  const [query, setQuery] = uS('');
  const [sectorFilter, setSectorFilter] = uS('ALL');
  const [dirFilter, setDirFilter] = uS('ALL');
  const [visibleCount, setVisibleCount] = uS(36);
  const audioCtxRef = uR(null);

  // clock
  uE(() => {
    const id = setInterval(() => setNow(new Date()), 1000);
    return () => clearInterval(id);
  }, []);

  // sound
  const beep = uC((dir) => {
    if (!t.soundOn) return;
    if (!audioCtxRef.current) {
      try { audioCtxRef.current = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) { return; }
    }
    const ctx = audioCtxRef.current;
    const osc = ctx.createOscillator();
    const gain = ctx.createGain();
    osc.frequency.value = dir === 'BUY' ? 880 : 440;
    osc.type = 'square';
    gain.gain.value = 0.04;
    osc.connect(gain); gain.connect(ctx.destination);
    osc.start();
    gain.gain.exponentialRampToValueAtTime(0.0001, ctx.currentTime + 0.15);
    osc.stop(ctx.currentTime + 0.16);
  }, [t.soundOn]);

  // sectors
  const sectors = uM(() => ['ALL', ...Array.from(new Set(stocks.map(s => s.sec)))], [stocks]);

  // filtered list
  const confThreshold = t.showLowConf ? 50 : 60;
  const filtered = uM(() => {
    const q = query.trim().toUpperCase();
    return stocks.filter(s => {
      if (sectorFilter !== 'ALL' && s.sec !== sectorFilter) return false;
      if (dirFilter !== 'ALL' && s.pattern.dir !== dirFilter) return false;
      if (q && !s.sym.includes(q) && !s.name.toUpperCase().includes(q)) return false;
      if (s.confidence < confThreshold) return false;
      return true;
    });
  }, [stocks, query, sectorFilter, dirFilter, confThreshold]);

  const visible = uM(() => filtered.slice(0, visibleCount), [filtered, visibleCount]);

  // autonomous: scan + emit signals (across full universe, not just visible)
  uE(() => {
    if (!autonomous) { setScanIdx(-1); return; }
    let i = 0;
    setScanIdx(0);
    const id = setInterval(() => {
      const stock = stocks[Math.floor(Math.random() * stocks.length)];
      const ts = new Date();
      const line = generateLogLine(stock, ts);
      setFeed(f => [{ ...line, id: Date.now() + Math.random() }, ...f].slice(0, 80));
      beep(stock.pattern.dir);
      i++;
      setScanIdx(i % Math.max(1, visible.length));
    }, 1100);
    return () => clearInterval(id);
  }, [autonomous, stocks, beep, visible.length]);

  // initial feed seed when first loaded
  uE(() => {
    if (showIntro) return;
    if (feed.length > 0) return;
    const seeded = stocks.slice(0, 5).map((s, i) => {
      const ts = new Date(Date.now() - (5 - i) * 30000);
      return { ...generateLogLine(s, ts), id: Date.now() + i };
    }).reverse();
    setFeed(seeded);
  }, [showIntro, stocks]);

  const handleAnalyze = (stock) => setAnalyzing(stock);
  const handleLogTrade = (stock) => {
    const ts = new Date();
    const pad = (n) => String(n).padStart(2, '0');
    setJournal(j => [{
      time: `${pad(ts.getHours())}:${pad(ts.getMinutes())}`,
      sym: stock.sym,
      dir: stock.pattern.dir,
      px: stock.px,
      pattern: stock.pattern.name,
    }, ...j]);
    setAnalyzing(null);
  };

  const handleRescan = () => {
    setRotation(r => r + 1);
    setFeed([]);
  };

  const pad = (n) => String(n).padStart(2, '0');
  const clock = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;

  if (showIntro) {
    return (
      <>
        {t.codeRain && <CodeRain intensity={t.matrixIntensity} />}
        <GlitchIntro onDone={() => setShowIntro(false)} />
      </>
    );
  }

  return (
    <>
      {t.codeRain && <CodeRain intensity={t.matrixIntensity} />}
      {t.scanline && <Scanline intensity={t.matrixIntensity} />}

      <div className="app">
        <TickerTape stocks={stocks} />

        <div className="header">
          <div className="brand">
            <div className="brand-logo">
              <GlitchText>SIGNAL_HUB</GlitchText>
            </div>
            <div className="brand-sub">v2.6 · MATRIX TERMINAL</div>
          </div>
          <div className="header-status">
            <span className="live">FEED ACTIVE</span>
            <span>UTC {clock}</span>
            <span>NYSE · NASDAQ · ARCA</span>
            <span>{stocks.length} SYMBOLS · {filtered.length} SHOWN</span>
            <span className={t.dataSource === 'live' ? (liveStatus.stale ? '' : 'live') : ''}>
              {t.dataSource === 'live'
                ? (liveStatus.phase === 'loading'
                    ? (t.liveProvider === 'yahoo' ? `LIVE · FETCHING…` : `LIVE · LOADING ${liveStatus.done}/${liveStatus.total}`)
                    : liveStatus.stale
                      ? `LIVE · ${t.liveProvider.toUpperCase()} (stale${liveStatus.lastUpdate ? ', last ok ' + liveStatus.lastUpdate.toLocaleTimeString() : ''})`
                      : `LIVE · ${t.liveProvider.toUpperCase()}`)
                : 'SIMULATED'}
            </span>
          </div>
          <div className="header-actions">
            <button className="btn" onClick={t.dataSource === 'live' ? refetchLive : handleRescan}>↻ RESCAN</button>
            <button className="btn" onClick={() => handleAnalyze(stocks[0])}>▸ ANALYZE</button>
            <button className={`btn ${autonomous ? 'active' : 'primary'}`} onClick={() => setAutonomous(a => !a)}>
              {autonomous ? '◉ AUTONOMOUS · ON' : '◉ AUTONOMOUS'}
            </button>
          </div>
        </div>

        <div className="left-rail">
          <PatternLegend />
          <Journal entries={journal} />
        </div>

        <div className="main">
          <div className="toolbar">
            <div className="search-wrap">
              <span className="search-prompt">▸</span>
              <input
                className="search-input"
                type="text"
                placeholder="grep ticker or name..."
                value={query}
                onChange={e => { setQuery(e.target.value); setVisibleCount(36); }}
              />
              {query && <button className="search-clear" onClick={() => setQuery('')}>✕</button>}
            </div>
            <div className="chip-row">
              <span className="chip-label">DIR</span>
              {['ALL', 'BUY', 'SELL'].map(d => (
                <button key={d}
                  className={`chip ${dirFilter === d ? 'on' : ''} ${d === 'BUY' ? 'buy' : d === 'SELL' ? 'sell' : ''}`}
                  onClick={() => { setDirFilter(d); setVisibleCount(36); }}>{d}</button>
              ))}
            </div>
            <div className="chip-row sectors">
              <span className="chip-label">SEC</span>
              {sectors.map(sc => (
                <button key={sc}
                  className={`chip ${sectorFilter === sc ? 'on' : ''}`}
                  onClick={() => { setSectorFilter(sc); setVisibleCount(36); }}>{sc}</button>
              ))}
            </div>
          </div>
          <div className={`grid ${t.layout === 'dense' ? 'dense' : ''}`}>
            {visible.map((s, i) => (
              <StockCard
                key={s.sym}
                stock={s}
                style={t.cardStyle}
                onAnalyze={() => handleAnalyze(s)}
                scanning={autonomous && scanIdx === i}
              />
            ))}
          </div>
          {visible.length < filtered.length && (
            <div className="load-more-wrap">
              <button className="btn" onClick={() => setVisibleCount(c => c + 36)}>
                ▼ LOAD MORE · {filtered.length - visible.length} REMAINING
              </button>
            </div>
          )}
          {filtered.length === 0 && (
            <div className="empty-grid">// no matches. clear filters or refine your query.</div>
          )}
        </div>

        <div className="right-rail">
          <SignalFeed feed={feed} />
        </div>

        <div className="footer">
          <div className="footer-left">
            <span className="ok">● MARKET OPEN</span>
            <span>SESSION: REGULAR</span>
            <span>LATENCY: 12ms</span>
            <span>UPTIME: 99.97%</span>
          </div>
          <div className="footer-right">
            <span>SIGNALS TODAY: {feed.length}</span>
            <span>JOURNALED: {journal.length}</span>
            <span className="ok">CONN ESTABLISHED</span>
          </div>
        </div>
      </div>

      {analyzing && (
        <AnalyzeOverlay
          stock={analyzing}
          onClose={() => setAnalyzing(null)}
          onLogTrade={handleLogTrade}
        />
      )}

      <TweaksPanel>
        <TweakSection label="Matrix" />
        <TweakRadio label="Intensity" value={t.matrixIntensity}
          options={['subtle', 'heavy', 'full']}
          onChange={v => setTweak('matrixIntensity', v)} />
        <TweakToggle label="Code rain" value={t.codeRain} onChange={v => setTweak('codeRain', v)} />
        <TweakToggle label="Scanlines" value={t.scanline} onChange={v => setTweak('scanline', v)} />

        <TweakSection label="Cards" />
        <TweakRadio label="Style" value={t.cardStyle}
          options={['full', 'compact', 'log']}
          onChange={v => setTweak('cardStyle', v)} />
        <TweakRadio label="Layout" value={t.layout}
          options={['grid', 'dense']}
          onChange={v => setTweak('layout', v)} />

        <TweakSection label="Detector" />
        <TweakToggle label="Show low-confidence (50%+)" value={t.showLowConf} onChange={v => setTweak('showLowConf', v)} />

        <TweakSection label="Audio" />
        <TweakToggle label="Sound on signal" value={t.soundOn} onChange={v => setTweak('soundOn', v)} />

        <TweakSection label="Data Source" />
        <TweakRadio label="Mode" value={t.dataSource}
          options={['simulated', 'live']}
          onChange={v => setTweak('dataSource', v)} />
        {t.dataSource === 'live' && (
          <>
            <TweakRadio label="Provider" value={t.liveProvider}
              options={['yahoo', 'proxy']}
              onChange={v => setTweak('liveProvider', v)} />
            <TweakSlider label="Refresh (sec)" value={t.refreshSec}
              min={15} max={300} step={5}
              onChange={v => setTweak('refreshSec', v)} />
            <div style={{padding:'8px 4px', fontSize:11, color:'#7a9a7a', lineHeight:1.5}}>
              {liveStatus.phase === 'loading' && (t.liveProvider === 'yahoo' ? `Fetching all symbols…` : `Loading ${liveStatus.done}/${liveStatus.total}…`)}
              {liveStatus.phase === 'ready' && `${liveStatus.done} signals · ${liveStatus.errors} skipped${liveStatus.stale ? ' · STALE' : ''} · upd ${liveStatus.lastUpdate?.toLocaleTimeString() || '–'}`}
              {liveStatus.phase === 'ready' && liveStatus.dataAge != null && ` · age ${liveStatus.dataAge}s`}
              {liveStatus.phase === 'error' && `Error: ${liveStatus.errMsg}`}
              {liveStatus.phase === 'idle' && 'Switch to live to fetch real market data.'}
            </div>
            <div style={{padding:'4px 4px 8px', fontSize:10, color:'#5a7a5a', lineHeight:1.5}}>
              {t.liveProvider === 'yahoo'
                ? `Yahoo server-side proxy · ${liveStatus.nextPollMs ? `next poll ${Math.round(liveStatus.nextPollMs/1000)}s` : 'market-hours aware'}`
                : 'Finnhub proxy · /api/quote + /api/candles'}
            </div>
          </>
        )}
      </TweaksPanel>
    </>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />);
