// tr-trade-of-day.jsx — TradeRadar "Trade of the Day" card.
//
// Shows ONE specific actionable options trade generated by the LLM consensus,
// built from the latest drivers snapshot (window.TR_LAST_PREDS) + watchlist.
// This is a directive, not a dashboard.
//
// Exposes:
//   window.TRTradeOfDay(T)   — React component. Pass T (color tokens) as prop.
//                              Returns the card. Intended to render at the top
//                              of the Drivers screen.
//
// LLM: AIAnalysis.runMulti([{ source:'TradeRadar', title: PROMPT }])
// Cache key: localStorage 'tr_trade_of_day_v1'
// Cache TTL: 1h (refresh button ignores cache); auto-regen on mount if >6h old.
//
// 7 required JSON fields from the LLM:
//   1. thesis         — 1 sentence
//   2. instrument     — e.g. "NVDA 145C 2026-05-16 weekly"
//   3. entry_premium  — { mid, bid, ask }  (all as $-strings or numbers)
//   4. max_loss       — { dollars, pct }
//   5. target_exit    — 1 sentence
//   6. stop_rule      — 1 sentence
//   7. conviction     — "HIGH" | "MEDIUM" | "LOW" | "PASS" (+ reasoning)

(function () {
  const CACHE_KEY = 'tr_trade_of_day_v1';
  const CACHE_FRESH_MS   = 60 * 60 * 1000;       // < 1h → reuse, skip refresh
  const CACHE_STALE_MS   = 6  * 60 * 60 * 1000;  // > 6h → auto-regen on mount

  // --------------------------------------------------------------------
  // helpers
  // --------------------------------------------------------------------
  function loadCache() {
    try {
      const raw = localStorage.getItem(CACHE_KEY);
      if (!raw) return null;
      const p = JSON.parse(raw);
      if (!p || !p.trade || !p.generatedAt) return null;
      return p;
    } catch { return null; }
  }
  function saveCache(obj) {
    try { localStorage.setItem(CACHE_KEY, JSON.stringify(obj)); } catch {}
  }
  function ageMs(ts) { return Date.now() - (Number(ts) || 0); }

  function fmtTime(ts) {
    if (!ts) return '—';
    try {
      const d = new Date(ts);
      if (!isFinite(d.getTime())) return '—';
      const today = new Date();
      const sameDay = d.toDateString() === today.toDateString();
      const hm = d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
      return sameDay ? hm : (d.toISOString().slice(5, 10) + ' ' + hm);
    } catch { return '—'; }
  }

  function hasAnyLLMKey() {
    try {
      const tr = (window.TR_SETTINGS && window.TR_SETTINGS.keys) || {};
      let legacy = {};
      try { legacy = JSON.parse(localStorage.getItem('oilradar_ai_keys') || '{}'); } catch {}
      return !!(tr.claude || tr.openai || tr.gemini || tr.grok
             || legacy.claude || legacy.openai || legacy.gemini || legacy.grok);
    } catch { return false; }
  }

  // Tolerant JSON extractor — strips ```json fences, grabs first {...}
  function extractJSON(text) {
    if (!text || typeof text !== 'string') return null;
    let t = text.trim();
    // strip ```json ... ``` or ``` ... ```
    t = t.replace(/^```(?:json)?\s*/i, '').replace(/```\s*$/, '').trim();
    // try direct parse first
    try { return JSON.parse(t); } catch {}
    // regex-extract first balanced-ish {...}
    const m = t.match(/\{[\s\S]*\}/);
    if (!m) return null;
    try { return JSON.parse(m[0]); } catch {}
    // last-ditch: trim trailing junk after last }
    const last = t.lastIndexOf('}');
    if (last > 0) {
      const slice = t.slice(t.indexOf('{'), last + 1);
      try { return JSON.parse(slice); } catch {}
    }
    return null;
  }

  // Build driver + watchlist context for the prompt
  function buildContext() {
    const ctx = { drivers: null, watchlist: [], at: new Date().toISOString() };
    // Drivers: prefer window.TR_LAST_PREDS, else localStorage tr_last_predictions
    try {
      if (window.TR_LAST_PREDS) ctx.drivers = window.TR_LAST_PREDS;
      else {
        const raw = localStorage.getItem('tr_last_predictions');
        if (raw) ctx.drivers = JSON.parse(raw);
      }
    } catch {}
    // Watchlist
    try {
      const raw = localStorage.getItem('tr_watchlist');
      if (raw) {
        const w = JSON.parse(raw);
        if (w && Array.isArray(w.tickers)) ctx.watchlist = w.tickers.slice(0, 3);
      }
    } catch {}
    return ctx;
  }

  // Summarize drivers into a compact string the LLM can use
  function driversSummary(drivers) {
    if (!drivers) return 'No driver snapshot available.';
    try {
      const parts = [];
      ['claude', 'gpt', 'gemini', 'grok'].forEach(k => {
        const r = drivers[k];
        if (!r) return;
        const bits = [];
        if (r.bitcoin_year_end_usd) bits.push(`BTC YE=$${r.bitcoin_year_end_usd}`);
        if (r.sp500_year_end)       bits.push(`SPX YE=${r.sp500_year_end}`);
        if (r.oil_year_end_usd)     bits.push(`WTI YE=$${r.oil_year_end_usd}`);
        if (r.sentiment)            bits.push(`bias=${r.sentiment}`);
        if (bits.length) parts.push(`${k}: ${bits.join(', ')}`);
      });
      return parts.length ? parts.join(' | ') : JSON.stringify(drivers).slice(0, 400);
    } catch { return 'Driver snapshot unreadable.'; }
  }

  function buildPrompt(ctx) {
    const watch = ctx.watchlist.length ? ctx.watchlist.join(', ') : 'SPY, QQQ, NVDA';
    const drv = driversSummary(ctx.drivers);
    return [
      'You are a disciplined options strategist. Propose ONE specific, actionable',
      'weekly or monthly US-equity options trade — not a watchlist, a directive.',
      '',
      'CONTEXT:',
      `- Timestamp: ${ctx.at}`,
      `- Driver snapshot (latest LLM consensus):\n  ${drv}`,
      `- Top watchlist tickers: ${watch}`,
      '',
      'REQUIREMENTS:',
      '- Use a liquid underlying (SPY, QQQ, IWM, or a mega-cap).',
      '- Expiry 7-45 days out. Strike within ~5% of spot unless defined spread.',
      '- If no high-quality setup exists today, return conviction="PASS".',
      '- Premium may be estimated if no live quote — label as estimate.',
      '',
      'Respond with RAW JSON ONLY (no markdown, no code fences) in this exact shape:',
      '{',
      '  "thesis": "one sentence",',
      '  "instrument": "e.g. NVDA 145C 2026-05-16 weekly",',
      '  "entry_premium": { "mid": "$3.20", "bid": "$3.10", "ask": "$3.30" },',
      '  "max_loss": { "dollars": "$320", "pct": "100%" },',
      '  "target_exit": "e.g. Close at $5.50 or +50%",',
      '  "stop_rule": "e.g. Cut at $1.80 or if NVDA breaks below 140",',
      '  "conviction": "HIGH | MEDIUM | LOW | PASS",',
      '  "reasoning": "brief — cite which drivers / catalysts support this"',
      '}',
    ].join('\n');
  }

  // Extract usable trade object from runMulti result
  function parseTradeFromMulti(res) {
    if (!res) return null;
    const order = ['claude', 'gpt', 'gemini', 'grok', 'perplexity'];
    for (const k of order) {
      const r = res[k];
      if (!r || !r.raw) continue;
      const obj = extractJSON(r.raw);
      if (obj && (obj.instrument || obj.thesis || obj.conviction)) {
        return { trade: obj, model: r.model || k };
      }
    }
    return null;
  }

  function normalizeTrade(t) {
    const out = { ...(t || {}) };
    // entry_premium tolerant of string OR object
    if (typeof out.entry_premium === 'string') {
      out.entry_premium = { mid: out.entry_premium, bid: '', ask: '' };
    }
    out.entry_premium = out.entry_premium || { mid: '—', bid: '', ask: '' };
    if (typeof out.max_loss === 'string') {
      out.max_loss = { dollars: out.max_loss, pct: '' };
    }
    out.max_loss = out.max_loss || { dollars: '—', pct: '' };
    out.conviction = String(out.conviction || 'LOW').toUpperCase();
    if (!['HIGH', 'MEDIUM', 'LOW', 'PASS'].includes(out.conviction)) out.conviction = 'LOW';
    return out;
  }

  function convictionStyle(T, c) {
    if (c === 'HIGH')   return { fg: '#07090C', bg: T.bull, border: T.bull, dashed: false };
    if (c === 'MEDIUM') return { fg: '#07090C', bg: T.signal, border: T.signal, dashed: false };
    if (c === 'LOW')    return { fg: T.text, bg: 'rgba(255,255,255,0.04)', border: T.textMid, dashed: false };
    return { fg: T.textDim, bg: 'transparent', border: T.textDim, dashed: true }; // PASS
  }

  // --------------------------------------------------------------------
  // TRTradeOfDay component
  // --------------------------------------------------------------------
  function TRTradeOfDay(props) {
    // Accept T as prop; fall back to minimal defaults.
    const T = (props && props.T) || {
      ink100: '#0B0E13', ink200: '#10141B',
      edge: 'rgba(255,255,255,0.06)',
      text: '#ffffff', textMid: 'rgba(180,188,200,0.75)', textDim: 'rgba(130,138,150,0.55)',
      signal: '#c9a227', bull: '#6FCF8E', bear: '#D96B6B',
      mono: '"JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace',
    };
    const MONO = T.mono || '"JetBrains Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace';

    const [cache, setCache]     = React.useState(() => loadCache());
    const [loading, setLoading] = React.useState(false);
    const [error, setError]     = React.useState(null);
    const autoRef = React.useRef(false);

    const hasKey = hasAnyLLMKey();

    const generate = React.useCallback(async () => {
      if (!hasAnyLLMKey()) {
        setError('Add an LLM API key in Settings ⚙ to generate.');
        return;
      }
      if (loading) return;
      setLoading(true); setError(null);
      try {
        const ctx = buildContext();
        const prompt = buildPrompt(ctx);
        if (!window.AIAnalysis || !window.AIAnalysis.runMulti) {
          throw new Error('AIAnalysis.runMulti unavailable');
        }
        const res = await window.AIAnalysis.runMulti(
          [{ source: 'TradeRadar', title: prompt }],
          {}
        );
        const parsed = parseTradeFromMulti(res);
        if (!parsed) {
          // All models failed → fabricate a PASS card with reasoning
          const fallback = {
            trade: normalizeTrade({
              thesis: 'No high-conviction setup surfaced from LLM consensus.',
              instrument: 'N/A',
              entry_premium: { mid: '—', bid: '', ask: '' },
              max_loss: { dollars: '—', pct: '' },
              target_exit: 'Stand aside.',
              stop_rule: 'Stand aside.',
              conviction: 'PASS',
              reasoning: 'LLMs returned no usable JSON — treating as pass rather than force a trade.',
            }),
            model: 'fallback',
          };
          const next = {
            generatedAt: Date.now(),
            trade: fallback.trade,
            model: fallback.model,
            contextBrief: {
              drivers: !!ctx.drivers,
              watchlistCount: ctx.watchlist.length,
            },
          };
          saveCache(next);
          setCache(next);
          return;
        }
        const next = {
          generatedAt: Date.now(),
          trade: normalizeTrade(parsed.trade),
          model: parsed.model,
          contextBrief: {
            drivers: !!ctx.drivers,
            watchlistCount: ctx.watchlist.length,
          },
        };
        saveCache(next);
        setCache(next);
      } catch (e) {
        setError((e && e.message) || 'Generation failed.');
      } finally {
        setLoading(false);
      }
    }, [loading]);

    // Refresh button: respects 1h freshness
    const onRefresh = React.useCallback(() => {
      const c = loadCache();
      if (c && ageMs(c.generatedAt) < CACHE_FRESH_MS) {
        // Still fresh — just re-display from cache, do not re-bill LLMs
        setCache(c);
        return;
      }
      generate();
    }, [generate]);

    // Auto-run once on mount if cache missing / >6h old
    React.useEffect(() => {
      if (autoRef.current) return;
      autoRef.current = true;
      const c = loadCache();
      if (!hasAnyLLMKey()) return;
      if (!c || ageMs(c.generatedAt) > CACHE_STALE_MS) {
        generate();
      }
    }, [generate]);

    // ----------------------------------------------------------------
    // styles
    // ----------------------------------------------------------------
    const cardStyle = {
      background: T.ink100,
      border: '1px solid rgba(201,162,39,0.28)',
      borderRadius: 12,
      padding: '16px 18px',
      color: T.text,
      fontFamily: 'InterTight, -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, sans-serif',
      marginBottom: 14,
    };
    const headerRow = {
      display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10,
    };
    const kicker = {
      fontFamily: MONO, fontSize: 10, letterSpacing: 1.4,
      color: T.signal, fontWeight: 700, textTransform: 'uppercase',
    };
    const tsStyle = {
      fontFamily: MONO, fontSize: 10, color: T.textDim, letterSpacing: 0.4,
    };
    const btnStyle = {
      marginLeft: 'auto',
      padding: '5px 12px', fontFamily: MONO, fontSize: 10.5, fontWeight: 600,
      background: T.ink200, color: T.textMid,
      border: `1px solid ${T.edge || 'rgba(255,255,255,0.06)'}`,
      borderRadius: 5, cursor: loading ? 'wait' : 'pointer',
      letterSpacing: 0.4, opacity: loading ? 0.6 : 1,
    };

    // ----------------------------------------------------------------
    // render: placeholder (no key OR no cache)
    // ----------------------------------------------------------------
    const t = cache && cache.trade;
    if (!t) {
      return (
        <div style={cardStyle}>
          <div style={headerRow}>
            <div style={kicker}>Trade of the Day</div>
            <div style={tsStyle}>· {loading ? 'GENERATING…' : 'not yet generated'}</div>
            <div
              onClick={hasKey ? onRefresh : undefined}
              style={{
                ...btnStyle,
                cursor: hasKey && !loading ? 'pointer' : 'not-allowed',
                opacity: hasKey ? (loading ? 0.6 : 1) : 0.4,
              }}
            >
              {loading ? '…' : (hasKey ? 'GENERATE' : 'NO LLM KEY')}
            </div>
          </div>
          <div
            onClick={hasKey && !loading ? onRefresh : undefined}
            style={{
              padding: '14px 16px',
              border: `1px dashed ${T.textDim}`,
              borderRadius: 8,
              fontFamily: MONO, fontSize: 11.5,
              color: T.textMid,
              textAlign: 'center',
              cursor: hasKey && !loading ? 'pointer' : 'default',
              letterSpacing: 0.3,
            }}
          >
            {error
              ? error
              : hasKey
                ? 'Click to generate today\'s options trade from the LLM consensus.'
                : 'Add an LLM API key in Settings to generate today\'s trade.'}
          </div>
        </div>
      );
    }

    // ----------------------------------------------------------------
    // render: full card
    // ----------------------------------------------------------------
    const cs = convictionStyle(T, t.conviction);
    const premMid = (t.entry_premium && (t.entry_premium.mid || t.entry_premium.last)) || '—';
    const premBid = (t.entry_premium && t.entry_premium.bid) || '';
    const premAsk = (t.entry_premium && t.entry_premium.ask) || '';
    const lossD   = (t.max_loss && (t.max_loss.dollars || t.max_loss.usd)) || '—';
    const lossP   = (t.max_loss && (t.max_loss.pct || t.max_loss.percent)) || '';

    const fieldLabel = {
      fontFamily: MONO, fontSize: 9, letterSpacing: 1.0,
      color: T.textDim, fontWeight: 600, textTransform: 'uppercase',
      marginBottom: 4,
    };
    const fieldVal = {
      fontFamily: MONO, fontSize: 12, color: T.text, fontWeight: 600,
      letterSpacing: 0.3,
    };
    const row = { display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, marginTop: 10 };

    return (
      <div style={cardStyle}>
        {/* Header */}
        <div style={headerRow}>
          <div style={kicker}>Trade of the Day</div>
          <div style={tsStyle}>· {fmtTime(cache.generatedAt)}</div>
          <div
            title={`Model: ${cache.model || 'unknown'} — click to refresh (respects 1h cache)`}
            onClick={onRefresh}
            style={btnStyle}
          >
            {loading ? 'REFRESHING…' : 'REFRESH'}
          </div>
        </div>

        {/* Thesis + conviction chip */}
        <div style={{
          display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 4,
        }}>
          <div style={{
            flex: 1, fontSize: 13.5, lineHeight: 1.45, color: T.text,
          }}>
            {t.thesis || '—'}
          </div>
          <div style={{
            padding: '4px 10px',
            fontFamily: MONO, fontSize: 10, fontWeight: 700, letterSpacing: 1.0,
            background: cs.bg, color: cs.fg,
            border: `${cs.dashed ? '1px dashed' : '1px solid'} ${cs.border}`,
            borderRadius: 5, whiteSpace: 'nowrap',
          }}>
            {t.conviction}
          </div>
        </div>

        {/* Instrument headline */}
        <div style={{
          marginTop: 10,
          padding: '10px 12px',
          background: T.ink200,
          border: `1px solid ${T.edge || 'rgba(255,255,255,0.06)'}`,
          borderRadius: 8,
          fontFamily: MONO, fontSize: 14, fontWeight: 700, letterSpacing: 0.5,
          color: T.text,
        }}>
          {t.instrument || '—'}
        </div>

        {/* 4-field grid */}
        <div style={row}>
          <div>
            <div style={fieldLabel}>Entry premium</div>
            <div style={{ ...fieldVal, color: T.signal }}>
              {premMid}
              {(premBid || premAsk) && (
                <span style={{ color: T.textDim, fontWeight: 400, marginLeft: 6 }}>
                  · bid {premBid || '—'} / ask {premAsk || '—'}
                </span>
              )}
            </div>
          </div>
          <div>
            <div style={fieldLabel}>Max loss</div>
            <div style={{ ...fieldVal, color: T.bear }}>
              {lossD}{lossP ? <span style={{ color: T.textMid, fontWeight: 400 }}> · {lossP}</span> : null}
            </div>
          </div>
          <div>
            <div style={fieldLabel}>Target exit</div>
            <div style={{ ...fieldVal, color: T.bull }}>
              {t.target_exit || '—'}
            </div>
          </div>
          <div>
            <div style={fieldLabel}>Stop / kill rule</div>
            <div style={{ ...fieldVal, color: T.bear }}>
              {t.stop_rule || '—'}
            </div>
          </div>
        </div>

        {/* Reasoning (small) */}
        {t.reasoning && (
          <div style={{
            marginTop: 12,
            fontFamily: MONO, fontSize: 10.5, lineHeight: 1.5,
            color: T.textMid, letterSpacing: 0.2,
          }}>
            {t.reasoning}
          </div>
        )}

        {/* Footer */}
        <div style={{
          marginTop: 12, paddingTop: 10,
          borderTop: `1px solid ${T.edge || 'rgba(255,255,255,0.06)'}`,
          display: 'flex', alignItems: 'center', gap: 10,
          fontFamily: MONO, fontSize: 9.5, color: T.textDim, letterSpacing: 0.3,
        }}>
          <span>
            based on {cache.contextBrief && cache.contextBrief.drivers ? 'latest' : '0'} drivers
            {' · '}
            {(cache.contextBrief && cache.contextBrief.watchlistCount) || 0} watchlist tickers
          </span>
          <span style={{ color: T.textDim }}>· {cache.model || 'llm'}</span>
          <div
            onClick={() => {
              try { window.dispatchEvent(new CustomEvent('tr:open-trade-ticket', { detail: t })); } catch {}
            }}
            style={{
              marginLeft: 'auto',
              padding: '4px 10px',
              fontFamily: MONO, fontSize: 10, fontWeight: 600,
              background: 'transparent', color: T.signal,
              border: `1px solid ${T.signal}`,
              borderRadius: 5, cursor: 'pointer', letterSpacing: 0.4,
            }}
          >
            Open in Trade Ticket →
          </div>
        </div>
      </div>
    );
  }

  window.TRTradeOfDay = TRTradeOfDay;
})();
