import { useState, useMemo, useRef } from 'react'
import {
  useRunPatternLab, useEvaluatePatternLab, useSaveLabPattern,
  usePatternLabRuns, useDeleteLabRun,
  useInstrumentScan, useEvaluateInstrumentScan,
  useFindMatchingPattern, MatchResult,
  useDiscoverEvents, DiscoveredEvent,
  validateTicker,
} from '../hooks/useApi'
import {
  FlaskConical, Play, CheckCircle2, XCircle, MinusCircle,
  Trash2, Save, RefreshCw, Search, CalendarDays,
  TrendingUp, TrendingDown, Zap, BarChart2, ScanLine, DollarSign,
  GitMerge, Layers, Plus, Globe, Sparkles,
} from 'lucide-react'
import clsx from 'clsx'
import { INSTRUMENTS, INSTRUMENT_CATEGORIES } from '../constants/instruments'

// ── Preset catalogue (2015-2025, 34 events) ────────────────────────────────────

interface Preset {
  id: string; year: number; label: string; date: string
  category: string; horizon_days: number; assets: string[]; hint: string
}

const PRESETS: Preset[] = [
  { id: 'china-deval-2015',       year: 2015, label: 'China CNY Devaluation',           date: '2015-08-11', category: 'fx_macro',    horizon_days: 60,  assets: ['SPY','FXI','GLD','UUP','EEM'],          hint: 'China surprises markets with a yuan devaluation shock. Global risk-off, EM currencies crash, commodities sell. Shanghai composite already down 30% from peak.' },
  { id: 'oil-crash-2016',         year: 2016, label: 'Oil Crash to $26',                date: '2016-01-20', category: 'commodities', horizon_days: 90,  assets: ['USO','XLE','HYG','SPY','GLD'],          hint: 'WTI crude at 12-year lows near $26. Energy sector destruction, high-yield credit stress from E&P distress. Fed on hold as global deflation spreads.' },
  { id: 'brexit-2016',            year: 2016, label: 'Brexit Vote Shock',               date: '2016-06-24', category: 'geopolitical', horizon_days: 60, assets: ['GBPUSD=X','EWU','SPY','GLD','TLT'],     hint: 'UK votes Leave 52-48. GBP flash crash -10% overnight. European bank stocks collapse. Flight to safety — gilts, gold, USD. BoE emergency measures expected.' },
  { id: 'trump-elect-2016',       year: 2016, label: 'Trump Election 2016',             date: '2016-11-09', category: 'macro',       horizon_days: 90,  assets: ['SPY','TLT','GLD','UUP','XLF'],          hint: 'Trump wins US presidential election. Reflation trade ignites: banks surge, bonds sell, USD rallies sharply, gold dumps, infrastructure/defense stocks soar.' },
  { id: 'volmageddon-2018',       year: 2018, label: 'Volmageddon — XIV Collapse',      date: '2018-02-05', category: 'volatility',  horizon_days: 30,  assets: ['SPY','TLT','GLD','QQQ'],                hint: 'VIX spikes 115% intraday. Inverse-vol ETPs (XIV, SVXY) collapse -90%. Market liquidity evaporates. Fed hiking into froth. Flash crash with rapid recovery.' },
  { id: 'trade-war-2018',         year: 2018, label: 'US-China Trade War Start',        date: '2018-03-22', category: 'geopolitical', horizon_days: 90, assets: ['SPY','FXI','QQQ','UUP','EEM'],          hint: 'Trump announces $60B tariffs on China under Section 301. Trade war declared. Tech and industrials sell. China threatens retaliation on US agriculture and autos.' },
  { id: 'turkey-em-2018',         year: 2018, label: 'Turkey / EM Currency Crisis',     date: '2018-08-10', category: 'fx_macro',    horizon_days: 60,  assets: ['TUR','EEM','UUP','GLD','EWZ'],          hint: 'TRY crashes 20% in a day. Turkey in full currency crisis — Erdogan conflict with Fed. EM contagion to South Africa, Argentina, Brazil. USD wrecking ball.' },
  { id: 'q4-selloff-2018',        year: 2018, label: 'Q4 2018 Fed Overtightening',      date: '2018-10-10', category: 'macro',       horizon_days: 90,  assets: ['SPY','QQQ','HYG','TLT','GLD'],          hint: "Powell 'long way from neutral' comment. Growth scare, credit spread widening. SPX enters 20% drawdown into Christmas. Fed eventually pivots Jan 2019." },
  { id: 'fed-pivot-2019',         year: 2019, label: 'Fed Dovish Pivot',                date: '2019-01-04', category: 'macro',       horizon_days: 90,  assets: ['SPY','TLT','GLD','EEM','UUP'],          hint: 'Powell signals patience — hiking cycle pause. Risk assets explode higher. USD weakens. EM and gold rally. Goldilocks scenario emerges after Q4 2018 crash.' },
  { id: 'yield-inversion-2019',   year: 2019, label: 'Yield Curve Inversion',           date: '2019-03-22', category: 'macro',       horizon_days: 120, assets: ['TLT','XLF','SPY','GLD','IEF'],          hint: '3M-10Y US Treasury yield curve inverts for first time since 2007. Recession indicator flashing. Banks sell. Gold and long bonds rally as growth fears build.' },
  { id: 'aramco-attack-2019',     year: 2019, label: 'Saudi Aramco Facility Attack',    date: '2019-09-16', category: 'geopolitical', horizon_days: 30, assets: ['USO','GLD','SPY','XLE','UUP'],          hint: 'Drone attacks on Abqaiq and Khurais take out 5% of global oil supply. WTI gaps +15% at open. Geopolitical risk premium spikes. Then fades fast.' },
  { id: 'repo-stress-2019',       year: 2019, label: 'Repo Market Liquidity Stress',    date: '2019-09-17', category: 'credit',      horizon_days: 30,  assets: ['TLT','SPY','GLD','UUP'],                hint: 'Overnight repo rates spike to 10%, overwhelming the Fed funds target. Fed emergency repo operations. Banking system dollar liquidity stress. Precursor to QE4.' },
  { id: 'covid-crash-2020',       year: 2020, label: 'COVID Pandemic Crash',            date: '2020-02-24', category: 'macro',       horizon_days: 90,  assets: ['SPY','QQQ','GLD','TLT','USO'],          hint: 'COVID spreads globally. Italy in lockdown. Markets price pandemic. SPX enters fastest bear market in history — 35% in 33 days. VIX touches 85. Fed emergency cut.' },
  { id: 'oil-negative-2020',      year: 2020, label: 'Oil Goes Negative (-$37)',         date: '2020-04-20', category: 'commodities', horizon_days: 60, assets: ['USO','XLE','SPY','UUP','HYG'],          hint: 'WTI May futures settle at -$37.63. Physical storage full. Zero demand from lockdowns. Energy sector existential risk. OPEC+ emergency cut already announced.' },
  { id: 'covid-recovery-2020',    year: 2020, label: 'COVID Recovery / QE Infinity',    date: '2020-04-09', category: 'macro',       horizon_days: 90,  assets: ['SPY','QQQ','GLD','TLT','EEM'],          hint: 'Fed announces unlimited QE + corporate bond buying. $2.2T CARES Act. Markets stage massive recovery. Tech leads. Gold rallies to ATH. Mega-cap dominance begins.' },
  { id: 'us-election-2020',       year: 2020, label: 'Biden Election / Vaccine Rally',  date: '2020-11-04', category: 'macro',       horizon_days: 90,  assets: ['SPY','QQQ','XLE','GLD','IWM'],          hint: 'Biden wins, split Congress. Pfizer vaccine +90% efficacy announced same week. Massive rotation: value vs growth, energy vs tech. Small caps surge. Gold drops.' },
  { id: 'gme-squeeze-2021',       year: 2021, label: 'GME Short Squeeze / WSB',         date: '2021-01-27', category: 'credit',      horizon_days: 30,  assets: ['SPY','QQQ','IWM','HYG','TLT'],          hint: 'WallStreetBets coordinates GME squeeze to $480. Robinhood halts buying. Hedge fund forced deleveraging. Systemic risk fear. Silver squeeze attempt follows.' },
  { id: 'reflation-2021',         year: 2021, label: 'Reflation Trade / Bond Crash',    date: '2021-02-25', category: 'macro',       horizon_days: 60,  assets: ['TLT','QQQ','XLE','XLF','GLD'],          hint: '10Y yields spike to 1.6%. Bond massacre. Tech rotation as rates hurt valuations. Value/cyclicals surge. Energy, banks, materials outperform. Inflation trade.' },
  { id: 'taper-2021',             year: 2021, label: 'Fed Tapering Announcement',       date: '2021-11-03', category: 'macro',       horizon_days: 90,  assets: ['TLT','GLD','EEM','UUP','SPY'],          hint: 'Fed officially announces QE tapering. Hawkish pivot accelerates. EM selloff. USD strengthens. Gold initially weak. Growth stocks start multi-month decline.' },
  { id: 'russia-ukraine-2022',    year: 2022, label: 'Russia Invades Ukraine',          date: '2022-02-24', category: 'geopolitical', horizon_days: 90, assets: ['USO','GLD','WEAT','SPY','EWG'],         hint: 'Full-scale Russian invasion. Energy price shock — European gas 10x. Wheat/corn spike +30%. European recession fear. Germany energy vulnerability. NATO unity.' },
  { id: 'fed-rate-shock-2022',    year: 2022, label: 'Fed Rate Shock — CPI 9.1%',       date: '2022-06-13', category: 'macro',       horizon_days: 90,  assets: ['TLT','SPY','QQQ','GLD','UUP'],          hint: 'CPI prints 9.1%. Fed goes 75bps (first time since 1994). Rate shock across all assets. Everything sold — bonds, stocks, gold, crypto. DXY to 114. Bear market.' },
  { id: 'energy-crisis-eu-2022',  year: 2022, label: 'European Energy Crisis',          date: '2022-08-26', category: 'commodities', horizon_days: 90, assets: ['EURUSD=X','EWG','SPY','GLD','TLT'],     hint: 'EU nat gas prices at 10x historical average. EUR/USD breaks parity. Germany industry threatens shutdown. ECB caught between inflation and recession.' },
  { id: 'gilt-crisis-2022',       year: 2022, label: 'UK Gilt Crisis / LDI Implosion',  date: '2022-09-23', category: 'credit',      horizon_days: 30,  assets: ['GBPUSD=X','EWU','TLT','GLD','SPY'],    hint: 'Truss mini-budget — unfunded £45B tax cuts. GBP crashes to 1.035. 30Y gilt yields +100bps in hours. LDI pension funds margin calls. BoE emergency QE.' },
  { id: 'ftx-2022',               year: 2022, label: 'FTX Crypto Exchange Collapse',    date: '2022-11-08', category: 'credit',      horizon_days: 60,  assets: ['BTC-USD','ETH-USD','SPY','GLD','QQQ'],  hint: 'FTX halts withdrawals. $8B hole. Alameda Research insolvent. Crypto contagion: Genesis, BlockFi. Bitcoin -75% ytd. Regulatory crackdown imminent. Crypto winter.' },
  { id: 'china-zero-covid-2022',  year: 2022, label: 'China Zero-COVID Exit',           date: '2022-12-07', category: 'macro',       horizon_days: 90,  assets: ['FXI','EEM','USO','GLD','EWJ'],          hint: 'China abruptly abandons zero-COVID after Urumqi protests. Reopening trade ignites. Commodities demand surge expected. But wave of infections causes volatility.' },
  { id: 'boj-yyc-2023',           year: 2023, label: 'BoJ YCC Band Widening',           date: '2023-01-18', category: 'fx_macro',    horizon_days: 90,  assets: ['USDJPY=X','EWJ','TLT','GLD','SPY'],     hint: 'BoJ widens YCC band to ±50bps surprise. JPY surges 3%. JGB yields spike. Global carry trade unwind risk. BoJ forced into bond buying. Yields globally affected.' },
  { id: 'svb-2023',               year: 2023, label: 'SVB Banking Crisis',              date: '2023-03-10', category: 'credit',      horizon_days: 30,  assets: ['XLF','TLT','GLD','SPY','UUP'],          hint: 'Silicon Valley Bank fails — largest US bank failure since 2008. Signature Bank closes. Contagion to Credit Suisse. Fed BTFP emergency facility. Deposit flight fear.' },
  { id: 'opec-cut-2023',          year: 2023, label: 'OPEC+ Surprise Production Cut',   date: '2023-04-02', category: 'commodities', horizon_days: 60, assets: ['USO','XLE','SPY','GLD','UUP'],          hint: 'OPEC+ shocks with 1.66Mb/d voluntary cuts beyond existing deal. WTI +6% in a day. Energy stocks surge. Inflation re-acceleration fear. Saudi strategy shift.' },
  { id: 'debt-ceiling-2023',      year: 2023, label: 'US Debt Ceiling X-Date',          date: '2023-05-24', category: 'macro',       horizon_days: 30,  assets: ['TLT','SPY','GLD','UUP','BIL'],          hint: 'Treasury X-date imminent. T-bill rates spike on short-end. Dollar stress. CDS on US sovereign widen. Last-minute Bipartisan deal expected but tail risk real.' },
  { id: 'ai-mania-2023',          year: 2023, label: 'AI Mania — Nvidia Surge',         date: '2023-05-25', category: 'tech',        horizon_days: 90,  assets: ['QQQ','SPY','SMH','TLT','GLD'],          hint: 'Nvidia Q1 blowout — revenue guidance +50%. AI bubble narrative goes mainstream. Mag7 concentration soars. Breadth collapses. Tech leads while rest of market flat.' },
  { id: 'iran-israel-2024',       year: 2024, label: 'Iran Direct Attack on Israel',    date: '2024-04-14', category: 'geopolitical', horizon_days: 30, assets: ['GLD','USO','SPY','TLT','UUP'],          hint: 'Iran launches 300+ drones/missiles at Israel in first-ever direct attack. Israel intercepts ~99%. Gold spikes to ATH. Oil +3%. Then rapid de-escalation. Premium collapses.' },
  { id: 'carry-unwind-2024',      year: 2024, label: 'JPY Carry Trade Unwind',          date: '2024-08-05', category: 'fx_macro',    horizon_days: 30,  assets: ['USDJPY=X','EWJ','SPY','GLD','TLT'],     hint: 'BoJ surprise rate hike triggers unwinding of $4T yen carry trade. Nikkei -12% in one session. VIX spikes to 65. Global deleveraging. Rapid recovery within 2 weeks.' },
  { id: 'china-stimulus-2024',    year: 2024, label: 'China Stimulus Bazooka',          date: '2024-09-24', category: 'macro',       horizon_days: 60,  assets: ['FXI','EEM','KWEB','USO','GLD'],         hint: 'PBOC surprises with rate cuts + reserve ratio cut + property support. Chinese equities +25% in 2 weeks. Commodity demand surge expected. Copper, iron ore, oil up.' },
  { id: 'trump-elect-2024',       year: 2024, label: 'Trump Election 2024 / Red Sweep', date: '2024-11-06', category: 'macro',       horizon_days: 90,  assets: ['SPY','QQQ','TLT','UUP','BTC-USD'],      hint: 'Trump wins presidency + Republican Congress. Reflation 2.0: tax cuts, deregulation, tariffs. USD surges. Crypto explodes. Europe and EM underperform. Bonds sell.' },
  { id: 'trump-tariff-2025',      year: 2025, label: 'Trump Tariff Liberation Day',     date: '2025-04-02', category: 'geopolitical', horizon_days: 90, assets: ['SPY','QQQ','GLD','UUP','EEM'],          hint: 'Trump announces 145% tariffs on China + 10% universal tariff. Largest trade war since 1930. Recession fear. SPX -15% in 3 days. Gold ATH $3200. Treasury yields volatile.' },
]

const CATEGORIES: Record<string, { label: string; color: string }> = {
  macro:       { label: 'Macro',       color: 'bg-blue-600'    },
  geopolitical:{ label: 'Geopolitical',color: 'bg-red-700'     },
  commodities: { label: 'Commodities', color: 'bg-amber-600'   },
  fx_macro:    { label: 'FX/Macro',    color: 'bg-violet-600'  },
  credit:      { label: 'Credit',      color: 'bg-orange-600'  },
  volatility:  { label: 'Volatility',  color: 'bg-cyan-600'    },
  tech:        { label: 'Tech',        color: 'bg-emerald-600' },
}

const YEARS = [...new Set(PRESETS.map(p => p.year))].sort()

// ── Small components ───────────────────────────────────────────────────────────

function CategoryBadge({ cat }: { cat: string }) {
  const c = CATEGORIES[cat] ?? { label: cat, color: 'bg-slate-600' }
  return <span className={`text-[10px] font-semibold px-1.5 py-0.5 rounded ${c.color} text-white`}>{c.label}</span>
}

function MoveBadge({ move, dir }: { move: number; dir: string }) {
  const ok = dir === 'up' ? move > 0 : dir === 'down' ? move < 0 : Math.abs(move) >= 3
  return (
    <span className={clsx('text-xs font-mono font-semibold', ok ? 'text-emerald-400' : 'text-red-400')}>
      {move > 0 ? '+' : ''}{move.toFixed(1)}%
    </span>
  )
}

function OutcomeRow({ out }: { out: any }) {
  const hitType = out.hit_type ?? (out.hit ? 'full' : 'miss')
  const borderCls = hitType === 'full' ? 'border-emerald-700/30'
    : hitType === 'partial' ? 'border-amber-700/30' : 'border-red-700/30'
  return (
    <div className={clsx('mt-2 pt-2 border-t flex flex-wrap items-center gap-x-3 gap-y-1 text-[10px]', borderCls)}>
      {/* Hit type */}
      {hitType === 'full' && (
        <span className="flex items-center gap-1 text-emerald-300 font-semibold">
          <CheckCircle2 className="w-3.5 h-3.5" /> FULL HIT
        </span>
      )}
      {hitType === 'partial' && (
        <span className="flex items-center gap-1 text-amber-300 font-semibold">
          <MinusCircle className="w-3.5 h-3.5" /> PARTIAL
        </span>
      )}
      {hitType === 'miss' && (
        <span className="flex items-center gap-1 text-red-300 font-semibold">
          <XCircle className="w-3.5 h-3.5" /> MISS
        </span>
      )}

      {/* Direction */}
      <span className="text-slate-500">
        Dir: <span className={out.direction_correct ? 'text-emerald-400 font-semibold' : 'text-red-400 font-semibold'}>
          {out.direction_correct ? '✓' : '✗'}
        </span>
        {out.direction_correct && out.direction_ratio != null && (
          <span className="text-slate-500 ml-0.5">({Math.round(out.direction_ratio * 100)}% of target)</span>
        )}
      </span>

      {/* Actual move */}
      <span className="text-slate-400">Actual:</span>
      <MoveBadge move={out.actual_move_pct} dir={out.expected_direction} />
      <span className="text-slate-600">
        vs {out.expected_direction === 'up' ? '+' : out.expected_direction === 'down' ? '-' : '±'}{out.expected_move_pct}%
      </span>

      {/* Options P&L estimate */}
      {out.estimated_options_pnl_pct != null && (
        <span className={clsx(
          'flex items-center gap-0.5 font-semibold font-mono',
          out.estimated_options_pnl_pct >= 0 ? 'text-emerald-400' : 'text-red-400'
        )}>
          <DollarSign className="w-3 h-3" />
          {out.estimated_options_pnl_pct > 0 ? '+' : ''}{out.estimated_options_pnl_pct.toFixed(0)}% P&L
        </span>
      )}

      <span className="text-slate-600">{out.entry_date} → {out.exit_date}</span>
    </div>
  )
}

// ── Main page ──────────────────────────────────────────────────────────────────

// ── Shared instrument picker ───────────────────────────────────────────────────

function InstrumentPicker({ onSelect }: { onSelect: (ticker: string, name: string) => void }) {
  const [search, setSearch]         = useState('')
  const [cat, setCat]               = useState<string | null>(null)
  const [custom, setCustom]         = useState('')
  const [validating, setValidating] = useState(false)
  const [tickerError, setTickerError] = useState<string | null>(null)

  const seen = new Set<string>()
  const unique = INSTRUMENTS.filter(i => { if (seen.has(i.ticker)) return false; seen.add(i.ticker); return true })

  const filtered = unique.filter(i => {
    if (cat && i.category !== cat) return false
    if (search) {
      const q = search.toLowerCase()
      return i.ticker.toLowerCase().includes(q) || i.name.toLowerCase().includes(q)
    }
    return true
  })

  const handleCustom = async () => {
    const sym = custom.trim().toUpperCase()
    if (!sym) return
    setTickerError(null)
    setValidating(true)
    try {
      const res = await validateTicker(sym)
      if (res.valid) {
        onSelect(sym, res.name || sym)
        setCustom('')
      } else {
        setTickerError(res.reason || `Ticker '${sym}' not found`)
      }
    } catch {
      setTickerError(`Could not validate '${sym}' — check your connection`)
    } finally {
      setValidating(false)
    }
  }

  return (
    <div className="space-y-2">
      {/* Category pills */}
      <div className="flex flex-wrap gap-1">
        <button onClick={() => setCat(null)}
          className={clsx('text-[10px] px-2 py-0.5 rounded-full border transition-colors',
            cat === null ? 'bg-violet-600 border-violet-500 text-white' : 'border-slate-700 text-slate-500 hover:text-slate-300')}>
          All
        </button>
        {INSTRUMENT_CATEGORIES.map(c => (
          <button key={c} onClick={() => setCat(cat === c ? null : c)}
            className={clsx('text-[10px] px-2 py-0.5 rounded-full border transition-colors',
              cat === c ? 'bg-violet-600 border-violet-500 text-white' : 'border-slate-700 text-slate-500 hover:text-slate-300')}>
            {c}
          </button>
        ))}
      </div>
      {/* Search + custom */}
      <div className="flex gap-2">
        <div className="relative flex-1">
          <Search className="w-3 h-3 absolute left-2 top-1/2 -translate-y-1/2 text-slate-500" />
          <input value={search} onChange={e => setSearch(e.target.value)} placeholder="Search…"
            className="w-full pl-6 pr-2 py-1.5 text-xs bg-dark-700 border border-slate-700/50 rounded text-slate-200 placeholder-slate-600 focus:outline-none focus:border-violet-500" />
        </div>
        <div className="flex flex-col gap-1">
          <div className="flex gap-1">
            <input value={custom} onChange={e => { setCustom(e.target.value); setTickerError(null) }}
              onKeyDown={e => e.key === 'Enter' && handleCustom()}
              placeholder="Custom ticker"
              className={clsx(
                'w-28 px-2 py-1.5 text-xs bg-dark-700 border rounded font-mono text-slate-200 placeholder-slate-600 focus:outline-none uppercase',
                tickerError ? 'border-red-500/70 focus:border-red-400' : 'border-slate-700/50 focus:border-violet-500'
              )} />
            <button onClick={handleCustom} disabled={!custom.trim() || validating}
              className="px-2 py-1.5 text-xs bg-dark-700 border border-slate-700/50 rounded text-slate-400 hover:text-slate-200 hover:border-violet-500 disabled:opacity-40 transition-colors">
              {validating ? <RefreshCw className="w-3 h-3 animate-spin" /> : '→'}
            </button>
          </div>
          {tickerError && (
            <p className="text-[9px] text-red-400 max-w-[140px] leading-tight">{tickerError}</p>
          )}
        </div>
      </div>
      {/* Grid */}
      <div className="grid grid-cols-3 gap-1 max-h-60 overflow-y-auto pr-1">
        {filtered.map(inst => (
          <button key={inst.ticker} onClick={() => onSelect(inst.ticker, inst.name)}
            className="text-left px-2 py-1.5 rounded border border-slate-700/40 bg-dark-700/30 hover:border-violet-600/60 hover:bg-violet-900/20 transition-colors">
            <div className="text-[10px] font-mono text-violet-400">{inst.ticker}</div>
            <div className="text-[9px] text-slate-500 truncate">{inst.name}</div>
          </button>
        ))}
        {filtered.length === 0 && (
          <div className="col-span-3 text-center py-4 text-[10px] text-slate-600">No match</div>
        )}
      </div>
    </div>
  )
}

// ── Main page ──────────────────────────────────────────────────────────────────

export default function PatternLab() {
  const { data: runsData } = usePatternLabRuns()
  const { mutateAsync: runBacktest,    isPending: running }    = useRunPatternLab()
  const { mutateAsync: evaluateRun,    isPending: evaluating } = useEvaluatePatternLab()
  const { mutateAsync: savePattern }                           = useSaveLabPattern()
  const { mutate: deleteRun }                                  = useDeleteLabRun()
  const { mutateAsync: runInstScan,    isPending: scanning }   = useInstrumentScan()
  const { mutateAsync: evaluateInst,   isPending: evalInst }   = useEvaluateInstrumentScan()
  const { mutateAsync: findMatching }                          = useFindMatchingPattern()
  const { mutateAsync: discoverEvents, isPending: discovering } = useDiscoverEvents()

  const runs: any[] = runsData ?? []

  // ── Mode tabs
  const [mode, setMode] = useState<'events' | 'instrument'>('events')

  // ── Left panel tabs: presets vs discover
  const [leftTab, setLeftTab] = useState<'presets' | 'discover'>('presets')
  const [discoverQuery, setDiscoverQuery]     = useState('')
  const [discoverResults, setDiscoverResults] = useState<DiscoveredEvent[]>([])
  const discoverInputRef = useRef<HTMLInputElement>(null)

  // ── Event preset filters
  const [search, setSearch]         = useState('')
  const [yearFilter, setYearFilter] = useState<number | null>(null)
  const [catFilter, setCatFilter]   = useState<string | null>(null)

  // ── Active experiment (events mode)
  const [selected, setSelected]   = useState<Preset | null>(null)
  const [editDate, setEditDate]   = useState('')
  const [editHorizon, setEditHorizon] = useState(90)
  const [editAssets, setEditAssets]   = useState('')
  const [editHint, setEditHint]       = useState('')

  // ── Instrument scan mode
  const [instTicker,  setInstTicker]  = useState('')
  const [instName,    setInstName]    = useState('')
  const [instStart,   setInstStart]   = useState('2020-01-01')
  const [instEnd,     setInstEnd]     = useState('2023-12-31')
  const [instHorizon, setInstHorizon] = useState(90)
  const [instRun,     setInstRun]     = useState<any | null>(null)
  const [instSavedIdx, setInstSavedIdx] = useState<Set<number>>(new Set())

  // ── Current run state (events mode)
  const [activeRun, setActiveRun]   = useState<any | null>(null)
  const [savedIdx, setSavedIdx]     = useState<Set<number>>(new Set())
  const [toast, setToast]           = useState<{ msg: string; type: 'ok' | 'err' } | null>(null)
  const [matchResults, setMatchResults] = useState<Record<string, MatchResult>>({})
  const [matchLoading, setMatchLoading] = useState<Record<string, boolean>>({})

  const filteredPresets = useMemo(() => PRESETS.filter(p => {
    if (yearFilter && p.year !== yearFilter) return false
    if (catFilter  && p.category !== catFilter) return false
    if (search && !p.label.toLowerCase().includes(search.toLowerCase())) return false
    return true
  }), [search, yearFilter, catFilter])

  const showToast = (msg: string, type: 'ok' | 'err' = 'ok') => {
    setToast({ msg, type })
    setTimeout(() => setToast(null), 4000)
  }

  const handleSelectPreset = (p: Preset) => {
    setSelected(p)
    setEditDate(p.date)
    setEditHorizon(p.horizon_days)
    setEditAssets(p.assets.join(', '))
    setEditHint(p.hint)
    setActiveRun(null)
    setSavedIdx(new Set())
    setMatchResults({})
  }

  const handleSelectDiscovered = (ev: DiscoveredEvent) => {
    const synth: Preset = {
      id: `discovered-${Date.now()}`,
      year: parseInt(ev.date.slice(0, 4)),
      label: ev.label,
      date: ev.date,
      category: ev.category,
      horizon_days: ev.horizon_days,
      assets: ev.assets,
      hint: ev.hint,
    }
    handleSelectPreset(synth)
    setLeftTab('presets')
  }

  const handleDiscover = async () => {
    if (!discoverQuery.trim()) return
    try {
      const res = await discoverEvents({ query: discoverQuery.trim(), n: 6 })
      setDiscoverResults(res.events)
    } catch (e: any) {
      showToast(`Discover failed: ${e?.response?.data?.detail ?? e?.message}`, 'err')
    }
  }

  const handleRun = async () => {
    if (!selected && !editDate) return
    const assets = editAssets.split(',').map(s => s.trim()).filter(Boolean)
    try {
      const result = await runBacktest({
        preset_id:     selected?.id,
        theme:         selected?.label ?? 'Custom',
        analysis_date: editDate,
        horizon_days:  editHorizon,
        assets,
        theme_hint:    editHint,
      })
      setActiveRun(result)
      setSavedIdx(new Set())
    } catch (e: any) {
      showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`, 'err')
    }
  }

  const handleEvaluate = async () => {
    if (!activeRun?.run_id) return
    try {
      const result = await evaluateRun(activeRun.run_id)
      setActiveRun((prev: any) => ({ ...prev, outcome: result.outcomes }))
    } catch (e: any) {
      showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`, 'err')
    }
  }

  const matchKey = (idx: number) => `${activeRun?.run_id}-${idx}`

  const handleFindMatching = async (idx: number) => {
    if (!activeRun?.run_id) return
    const key = matchKey(idx)
    setMatchLoading(prev => ({ ...prev, [key]: true }))
    try {
      const result = await findMatching({ run_id: activeRun.run_id, pattern_index: idx })
      setMatchResults(prev => ({ ...prev, [key]: result }))
    } catch (e: any) {
      showToast(`Find matching failed: ${e?.response?.data?.detail ?? e?.message}`, 'err')
    } finally {
      setMatchLoading(prev => ({ ...prev, [key]: false }))
    }
  }

  const handleSave = async (
    idx: number,
    action: 'new' | 'instance' | 'counter' = 'new',
    target_id?: string,
    regime_tag?: string,
  ) => {
    if (!activeRun?.run_id) {
      showToast('Lancez d\'abord un backtest (bouton Run)', 'err')
      return
    }
    const pat = activeRun.ai_result?.patterns?.[idx]
    if (!pat) return
    try {
      await savePattern({
        run_id: activeRun.run_id,
        pattern_index: idx,
        name: pat.name,
        category: pat.category,
        signal_direction: pat.signal_direction,
        action,
        target_id,
        regime_tag,
      })
      setSavedIdx(prev => new Set([...prev, idx]))
      const actionLabel = action === 'instance' ? 'merged as instance' : action === 'counter' ? 'saved as counter-scenario' : 'saved'
      showToast(`"${pat.name}" ${actionLabel}`, 'ok')
    } catch (e: any) {
      showToast(`Save failed: ${e?.response?.data?.detail ?? e?.message}`, 'err')
    }
  }

  // ── Instrument scan handlers
  const handleInstScan = async () => {
    if (!instTicker) return
    try {
      const result = await runInstScan({
        ticker: instTicker, instrument_name: instName || instTicker,
        start_date: instStart, end_date: instEnd, horizon_days: instHorizon,
      })
      setInstRun(result)
      setInstSavedIdx(new Set())
    } catch (e: any) {
      showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`)
    }
  }

  const handleInstEvaluate = async () => {
    if (!instRun?.run_id) return
    try {
      const result = await evaluateInst(instRun.run_id)
      setInstRun((prev: any) => ({ ...prev, outcome: result.outcomes }))
    } catch (e: any) {
      showToast(`Error: ${e?.response?.data?.detail ?? e?.message ?? 'unknown'}`, 'err')
    }
  }

  const handleInstSave = async (idx: number) => {
    if (!instRun?.run_id) return
    const pat = instRun.ai_result?.patterns?.[idx]
    if (!pat) return
    try {
      await savePattern({ run_id: instRun.run_id, pattern_index: idx, name: pat.name, category: pat.category, signal_direction: pat.signal_direction })
      setInstSavedIdx(prev => new Set([...prev, idx]))
      showToast(`"${pat.name}" saved to Pattern Library`, 'ok')
    } catch (e: any) {
      showToast(`Save failed: ${e?.response?.data?.detail ?? e?.message}`, 'err')
    }
  }

  // ── Event mode helpers
  const aiPatterns: any[] = activeRun?.ai_result?.patterns ?? []
  const outcomes: any[]   = activeRun?.outcome ?? []
  const hasOutcomes       = outcomes.length > 0

  const outcomeForPattern = (name: string) =>
    outcomes.find((o: any) => o.pattern_name === name)

  const scoredOutcomes   = outcomes.filter((o: any) => 'hit_type' in o || 'hit' in o)
  const fullHits         = outcomes.filter((o: any) => (o.hit_type ?? (o.hit ? 'full' : 'miss')) === 'full').length
  const partialHits      = outcomes.filter((o: any) => (o.hit_type ?? '') === 'partial').length
  const hitRate          = scoredOutcomes.length > 0 ? fullHits / scoredOutcomes.length : null

  // ── Instrument mode helpers
  const instPatterns: any[] = instRun?.ai_result?.patterns ?? []
  const instOutcomes: any[] = instRun?.outcome ?? []
  const instHasOut = instOutcomes.length > 0
  const instOutcomeFor = (name: string) => instOutcomes.find((o: any) => o.pattern_name === name)
  const instScored   = instOutcomes.filter((o: any) => 'hit_type' in o || 'hit' in o)
  const instFull     = instOutcomes.filter((o: any) => (o.hit_type ?? (o.hit ? 'full' : 'miss')) === 'full').length
  const instPartial  = instOutcomes.filter((o: any) => (o.hit_type ?? '') === 'partial').length
  const instHitRate  = instScored.length > 0 ? instFull / instScored.length : null

  return (
    <div className="flex h-screen bg-dark-900 text-slate-200 overflow-hidden">
      {/* ── Left sidebar ────────────────────────────────────────────────────── */}
      <div className="w-72 flex-shrink-0 border-r border-slate-700/40 flex flex-col overflow-hidden">
        <div className="p-3 border-b border-slate-700/40">
          <div className="flex items-center gap-2 mb-2">
            <FlaskConical className="w-4 h-4 text-violet-400" />
            <span className="font-semibold text-sm text-slate-100">Pattern Lab</span>
            <span className="ml-auto text-[10px] bg-slate-700 rounded px-1.5 py-0.5 text-slate-400">{PRESETS.length} events</span>
          </div>
          {/* Mode tabs */}
          <div className="flex gap-1 mb-2 bg-dark-700 rounded p-0.5">
            <button onClick={() => setMode('events')}
              className={clsx('flex-1 text-xs py-1 rounded transition-colors flex items-center justify-center gap-1', {
                'bg-violet-700 text-white': mode === 'events',
                'text-slate-500 hover:text-slate-300': mode !== 'events',
              })}>
              <CalendarDays className="w-3 h-3" /> Events
            </button>
            <button onClick={() => setMode('instrument')}
              className={clsx('flex-1 text-xs py-1 rounded transition-colors flex items-center justify-center gap-1', {
                'bg-teal-700 text-white': mode === 'instrument',
                'text-slate-500 hover:text-slate-300': mode !== 'instrument',
              })}>
              <ScanLine className="w-3 h-3" /> Instrument
            </button>
          </div>
          {/* Left panel sub-tabs: Presets | Discover */}
          <div className="flex gap-0.5 mt-1 bg-dark-700 rounded p-0.5">
            <button onClick={() => setLeftTab('presets')}
              className={clsx('flex-1 text-[10px] py-1 rounded transition-colors flex items-center justify-center gap-1', {
                'bg-slate-600 text-slate-100': leftTab === 'presets',
                'text-slate-500 hover:text-slate-300': leftTab !== 'presets',
              })}>
              <CalendarDays className="w-3 h-3" /> Presets
            </button>
            <button onClick={() => { setLeftTab('discover'); setTimeout(() => discoverInputRef.current?.focus(), 50) }}
              className={clsx('flex-1 text-[10px] py-1 rounded transition-colors flex items-center justify-center gap-1', {
                'bg-violet-700 text-white': leftTab === 'discover',
                'text-slate-500 hover:text-slate-300': leftTab !== 'discover',
              })}>
              <Globe className="w-3 h-3" /> Discover
            </button>
          </div>
        </div>

        {/* ── Presets tab ── */}
        {leftTab === 'presets' && (
          <>
            <div className="px-3 pt-2 pb-1 border-b border-slate-700/30">
              <div className="relative">
                <Search className="w-3.5 h-3.5 absolute left-2 top-1/2 -translate-y-1/2 text-slate-500" />
                <input
                  value={search} onChange={e => setSearch(e.target.value)}
                  placeholder="Search events..."
                  className="w-full pl-7 pr-2 py-1.5 text-xs bg-dark-700 border border-slate-700/40 rounded text-slate-200 placeholder-slate-500 focus:outline-none focus:border-violet-600"
                />
              </div>
              <div className="flex flex-wrap gap-1 mt-2">
                {YEARS.map(y => (
                  <button key={y} onClick={() => setYearFilter(yearFilter === y ? null : y)}
                    className={clsx('text-[10px] px-1.5 py-0.5 rounded border transition-colors', {
                      'bg-violet-700 border-violet-600 text-white': yearFilter === y,
                      'border-slate-700 text-slate-500 hover:text-slate-300': yearFilter !== y,
                    })}>{y}</button>
                ))}
              </div>
              <div className="flex flex-wrap gap-1 mt-1">
                {Object.entries(CATEGORIES).map(([key, { label }]) => (
                  <button key={key} onClick={() => setCatFilter(catFilter === key ? null : key)}
                    className={clsx('text-[10px] px-1.5 py-0.5 rounded transition-colors', {
                      'bg-slate-600 text-white': catFilter === key,
                      'text-slate-500 hover:text-slate-300': catFilter !== key,
                    })}>{label}</button>
                ))}
              </div>
            </div>
            <div className="flex-1 overflow-y-auto py-1">
              {filteredPresets.map(p => (
                <button key={p.id} onClick={() => handleSelectPreset(p)}
                  className={clsx('w-full text-left px-3 py-2 border-b border-slate-700/20 hover:bg-dark-700/60 transition-colors', {
                    'bg-violet-900/30 border-l-2 border-l-violet-500': selected?.id === p.id,
                  })}>
                  <div className="flex items-center gap-1.5 mb-0.5">
                    <span className="text-[10px] text-slate-500 font-mono">{p.year}</span>
                    <CategoryBadge cat={p.category} />
                    <span className="text-[10px] text-slate-600 ml-auto">{p.horizon_days}d</span>
                  </div>
                  <div className="text-xs text-slate-200 leading-tight">{p.label}</div>
                  <div className="text-[10px] text-slate-500 mt-0.5">{p.date}</div>
                </button>
              ))}
              {filteredPresets.length === 0 && (
                <div className="p-4 text-center text-xs text-slate-600">No events match filters</div>
              )}
            </div>
          </>
        )}

        {/* ── Discover tab ── */}
        {leftTab === 'discover' && (
          <>
            <div className="px-3 pt-2 pb-2 border-b border-slate-700/30">
              <div className="flex gap-1.5">
                <div className="relative flex-1">
                  <Globe className="w-3 h-3 absolute left-2 top-1/2 -translate-y-1/2 text-violet-400" />
                  <input
                    ref={discoverInputRef}
                    value={discoverQuery}
                    onChange={e => setDiscoverQuery(e.target.value)}
                    onKeyDown={e => e.key === 'Enter' && handleDiscover()}
                    placeholder="e.g. Middle East crisis, dollar collapse…"
                    className="w-full pl-6 pr-2 py-1.5 text-xs bg-dark-700 border border-violet-700/40 rounded text-slate-200 placeholder-slate-500 focus:outline-none focus:border-violet-500"
                  />
                </div>
                <button onClick={handleDiscover} disabled={discovering || !discoverQuery.trim()}
                  className="flex items-center gap-1 bg-violet-700 hover:bg-violet-600 disabled:opacity-50 text-white px-2.5 py-1.5 rounded text-xs font-semibold transition-colors">
                  {discovering ? <RefreshCw className="w-3 h-3 animate-spin" /> : <Sparkles className="w-3 h-3" />}
                </button>
              </div>
              <p className="text-[10px] text-slate-600 mt-1.5">AI searches its knowledge for matching events → click to pre-fill the lab</p>
            </div>
            <div className="flex-1 overflow-y-auto py-1">
              {discovering && (
                <div className="flex flex-col items-center justify-center py-12 gap-2 text-slate-600">
                  <RefreshCw className="w-5 h-5 animate-spin" />
                  <span className="text-xs">Searching…</span>
                </div>
              )}
              {!discovering && discoverResults.length === 0 && (
                <div className="p-4 text-center text-xs text-slate-600">
                  {discoverQuery ? 'No results yet — press Enter or click search' : 'Type a query to discover events'}
                </div>
              )}
              {!discovering && discoverResults.map((ev, i) => (
                <button key={i} onClick={() => handleSelectDiscovered(ev)}
                  className="w-full text-left px-3 py-2.5 border-b border-slate-700/20 hover:bg-violet-900/20 transition-colors group">
                  <div className="flex items-center gap-1.5 mb-0.5">
                    <span className="text-[10px] text-slate-500 font-mono">{ev.date.slice(0, 4)}</span>
                    <CategoryBadge cat={ev.category} />
                    <span className="text-[10px] text-slate-600 ml-auto">{ev.horizon_days}d</span>
                    <span className={clsx('text-[10px] font-mono', ev.confidence >= 80 ? 'text-emerald-500' : 'text-slate-600')}>
                      {ev.confidence}%
                    </span>
                  </div>
                  <div className="text-xs text-slate-200 leading-tight group-hover:text-violet-300 transition-colors">{ev.label}</div>
                  <div className="text-[10px] text-slate-500 mt-0.5 truncate">{ev.date} · {ev.assets.slice(0, 3).join(', ')}</div>
                </button>
              ))}
            </div>
          </>
        )}

        {/* Run history count */}
        <div className="p-2 border-t border-slate-700/40 text-[10px] text-slate-600">
          {runs.length} past run{runs.length !== 1 ? 's' : ''} in history
        </div>
      </div>

      {/* ── Main content ────────────────────────────────────────────────────── */}
      <div className="flex-1 overflow-y-auto">

        {/* ══ INSTRUMENT SCAN MODE ══════════════════════════════════════════ */}
        {mode === 'instrument' && (
          <div className="p-5 space-y-5">
            <div className="flex items-center gap-2">
              <ScanLine className="w-4 h-4 text-teal-400" />
              <h1 className="text-lg font-bold text-slate-100">Instrument Historical Scan</h1>
            </div>
            <p className="text-xs text-slate-400">
              Pick an instrument — the AI scans a historical period, identifies the key patterns/events that drove it, and validates them against actual price data.
            </p>

            {/* Instrument picker */}
            <div className="bg-dark-800 border border-slate-700/40 rounded-lg p-4">
              <div className="text-xs font-semibold text-slate-400 uppercase tracking-wide mb-3">
                {instTicker
                  ? <span className="flex items-center gap-2">Selected: <span className="font-mono text-teal-300">{instTicker}</span> — {instName} <button onClick={() => { setInstTicker(''); setInstName(''); setInstRun(null) }} className="text-slate-500 hover:text-slate-300 text-[10px] ml-1">change</button></span>
                  : 'Select Instrument'}
              </div>
              {!instTicker && (
                <InstrumentPicker onSelect={(t, n) => { setInstTicker(t); setInstName(n) }} />
              )}
            </div>

            {/* Period + horizon */}
            {instTicker && (
              <div className="bg-dark-800 border border-slate-700/40 rounded-lg p-4">
                <div className="text-xs font-semibold text-slate-400 uppercase tracking-wide mb-3">Scan Parameters</div>
                <div className="grid grid-cols-3 gap-3">
                  <div>
                    <label className="text-[10px] text-slate-500 mb-1 block">Period Start</label>
                    <input type="date" value={instStart} onChange={e => setInstStart(e.target.value)}
                      className="w-full px-2 py-1.5 text-xs bg-dark-700 border border-slate-600/50 rounded text-slate-200 focus:outline-none focus:border-teal-500" />
                  </div>
                  <div>
                    <label className="text-[10px] text-slate-500 mb-1 block">Period End</label>
                    <input type="date" value={instEnd} onChange={e => setInstEnd(e.target.value)}
                      className="w-full px-2 py-1.5 text-xs bg-dark-700 border border-slate-600/50 rounded text-slate-200 focus:outline-none focus:border-teal-500" />
                  </div>
                  <div>
                    <label className="text-[10px] text-slate-500 mb-1 block">Horizon per pattern</label>
                    <div className="flex items-center gap-2">
                      <input type="range" min={14} max={365} value={instHorizon}
                        onChange={e => setInstHorizon(Number(e.target.value))}
                        className="flex-1 accent-teal-600" />
                      <span className="text-xs font-mono text-teal-300 w-8 text-right">{instHorizon}d</span>
                    </div>
                  </div>
                </div>
                <div className="mt-3">
                  <button onClick={handleInstScan} disabled={scanning}
                    className="flex items-center gap-2 bg-teal-700 hover:bg-teal-600 disabled:opacity-50 text-white px-4 py-2 rounded text-xs font-semibold transition-colors">
                    {scanning
                      ? <><RefreshCw className="w-3.5 h-3.5 animate-spin" /> Fetching data + AI analysis…</>
                      : <><ScanLine className="w-3.5 h-3.5" /> Scan History</>}
                  </button>
                </div>
              </div>
            )}

            {/* Instrument scan results */}
            {instPatterns.length > 0 && (
              <div className="bg-dark-800 border border-slate-700/40 rounded-lg p-4">
                <div className="flex items-center justify-between mb-3">
                  <div className="text-xs font-semibold text-slate-400 uppercase tracking-wide">
                    {instPatterns.length} Pattern Instances — <span className="text-teal-300 font-mono">{instTicker}</span>
                    <span className="text-slate-600 ml-2">{instStart} → {instEnd}</span>
                  </div>
                  <div className="flex items-center gap-2">
                    {instHasOut && instHitRate !== null && (
                      <div className="flex items-center gap-2">
                        <span className={clsx('text-sm font-bold', instHitRate >= 0.6 ? 'text-emerald-400' : instHitRate >= 0.4 ? 'text-yellow-400' : 'text-red-400')}>
                          {Math.round(instHitRate * 100)}% full
                        </span>
                        {instPartial > 0 && (
                          <span className="text-xs text-amber-400">+{instPartial} partial</span>
                        )}
                        <span className="text-xs text-slate-600">({instFull}/{instScored.length})</span>
                      </div>
                    )}
                    {!instHasOut && (
                      <button onClick={handleInstEvaluate} disabled={evalInst}
                        className="flex items-center gap-1.5 bg-teal-700 hover:bg-teal-600 disabled:opacity-50 text-white px-3 py-1.5 rounded text-xs font-semibold transition-colors">
                        {evalInst
                          ? <><RefreshCw className="w-3 h-3 animate-spin" /> Evaluating…</>
                          : <><BarChart2 className="w-3 h-3" /> Evaluate outcomes</>}
                      </button>
                    )}
                  </div>
                </div>
                <div className="space-y-3">
                  {instPatterns.map((pat: any, idx: number) => {
                    const out    = instOutcomeFor(pat.name)
                    const isSaved = instSavedIdx.has(idx)
                    const DirIcon = pat.signal_direction === 'bullish' ? TrendingUp : pat.signal_direction === 'bearish' ? TrendingDown : Zap
                    const dirCol  = pat.signal_direction === 'bullish' ? 'text-emerald-400' : pat.signal_direction === 'bearish' ? 'text-red-400' : 'text-violet-400'
                    return (
                      <div key={idx} className={clsx('border rounded-lg p-3',
                        (() => { const ht = out?.hit_type ?? (out?.hit ? 'full' : out ? 'miss' : null)
                          return ht === 'full' ? 'border-emerald-700/60 bg-emerald-900/10'
                            : ht === 'partial' ? 'border-amber-700/40 bg-amber-900/10'
                            : ht === 'miss' ? 'border-red-700/40 bg-red-900/10'
                            : 'border-slate-700/40 bg-dark-700/30' })())}>
                        <div className="flex items-start gap-3">
                          <div className="flex-1">
                            <div className="flex items-center gap-2 mb-0.5">
                              <DirIcon className={`w-3.5 h-3.5 ${dirCol}`} />
                              <span className="text-xs font-semibold text-slate-100">{pat.name}</span>
                              <span className="text-[10px] bg-teal-900/40 border border-teal-700/30 text-teal-300 rounded px-1.5 font-mono">{pat.analysis_date}</span>
                              {pat.category && <span className="text-[10px] text-slate-500">{pat.category}</span>}
                            </div>
                            <p className="text-xs text-slate-400 mb-1.5">{pat.description}</p>
                            <div className="flex items-center gap-4 text-[10px] text-slate-500">
                              <span>Strategy: <span className="text-slate-300">{pat.strategy}</span></span>
                              <span>Expected: <span className={clsx('font-semibold', dirCol)}>
                                {pat.expected_direction === 'up' ? '+' : pat.expected_direction === 'down' ? '-' : '±'}{pat.expected_move_pct}%
                              </span></span>
                              <span>Horizon: <span className="text-slate-300">{pat.horizon_days}d</span></span>
                              <span>Confidence: <span className={clsx('font-semibold', pat.confidence >= 70 ? 'text-emerald-400' : pat.confidence >= 50 ? 'text-yellow-400' : 'text-slate-400')}>{pat.confidence}</span></span>
                            </div>
                            {pat.rationale && <p className="text-[10px] text-slate-500 mt-1 italic">{pat.rationale}</p>}
                            {out && <OutcomeRow out={out} />}
                          </div>
                          <div className="flex-shrink-0">
                            {isSaved
                              ? <span className="flex items-center gap-1 text-emerald-400 text-xs"><CheckCircle2 className="w-3.5 h-3.5" /> Saved</span>
                              : <button onClick={() => handleInstSave(idx)}
                                  className="flex items-center gap-1.5 border border-slate-600 hover:border-teal-500 hover:text-teal-300 text-slate-400 px-2.5 py-1.5 rounded text-xs transition-colors">
                                  <Save className="w-3 h-3" /> Save
                                </button>
                            }
                          </div>
                        </div>
                      </div>
                    )
                  })}
                </div>
              </div>
            )}
          </div>
        )}

        {/* ══ EVENT PRESET MODE ════════════════════════════════════════════ */}
        {mode === 'events' && !selected && (
          <div className="flex items-center justify-center h-full text-center">
            <div>
              <FlaskConical className="w-12 h-12 text-slate-700 mx-auto mb-3" />
              <p className="text-slate-500 text-sm">Select a historical event on the left</p>
              <p className="text-slate-600 text-xs mt-1">The AI will analyse what it would have done at that date using real market data</p>
            </div>
          </div>
        )}
        {mode === 'events' && selected && (
          <div className="p-5 space-y-5">
            {/* ── Header ── */}
            <div className="flex items-start gap-3">
              <div className="flex-1">
                <div className="flex items-center gap-2 mb-1">
                  <CalendarDays className="w-4 h-4 text-violet-400" />
                  <h1 className="text-lg font-bold text-slate-100">{selected.label}</h1>
                  <CategoryBadge cat={selected.category} />
                </div>
                <p className="text-xs text-slate-400 leading-relaxed max-w-2xl">{selected.hint}</p>
              </div>
            </div>

            {/* ── Setup card ── */}
            <div className="bg-dark-800 border border-slate-700/40 rounded-lg p-4">
              <div className="text-xs font-semibold text-slate-400 uppercase tracking-wide mb-3">Experiment Setup</div>
              <div className="grid grid-cols-3 gap-3">
                <div>
                  <label className="text-[10px] text-slate-500 mb-1 block">Analysis Date</label>
                  <input type="date" value={editDate} onChange={e => setEditDate(e.target.value)}
                    className="w-full px-2 py-1.5 text-xs bg-dark-700 border border-slate-600/50 rounded text-slate-200 focus:outline-none focus:border-violet-500" />
                </div>
                <div>
                  <label className="text-[10px] text-slate-500 mb-1 block">Horizon (days)</label>
                  <div className="flex items-center gap-2">
                    <input type="range" min={14} max={365} value={editHorizon}
                      onChange={e => setEditHorizon(Number(e.target.value))}
                      className="flex-1 accent-violet-600" />
                    <span className="text-xs font-mono text-violet-300 w-8 text-right">{editHorizon}d</span>
                  </div>
                </div>
                <div>
                  <label className="text-[10px] text-slate-500 mb-1 block">Assets (comma-separated)</label>
                  <input value={editAssets} onChange={e => setEditAssets(e.target.value)}
                    className="w-full px-2 py-1.5 text-xs bg-dark-700 border border-slate-600/50 rounded text-slate-200 focus:outline-none focus:border-violet-500 font-mono" />
                </div>
              </div>
              <div className="mt-3">
                <label className="text-[10px] text-slate-500 mb-1 block">Context hint for AI</label>
                <textarea value={editHint} onChange={e => setEditHint(e.target.value)} rows={2}
                  className="w-full px-2 py-1.5 text-xs bg-dark-700 border border-slate-600/50 rounded text-slate-300 focus:outline-none focus:border-violet-500 resize-none" />
              </div>
              <div className="mt-3 flex items-center gap-3">
                <button onClick={handleRun} disabled={running || !editDate}
                  className="flex items-center gap-2 bg-violet-700 hover:bg-violet-600 disabled:opacity-50 text-white px-4 py-2 rounded text-xs font-semibold transition-colors">
                  {running
                    ? <><RefreshCw className="w-3.5 h-3.5 animate-spin" /> Building context + running AI…</>
                    : <><Play className="w-3.5 h-3.5" /> Run Backtest</>}
                </button>
                {running && <span className="text-xs text-slate-500">Fetching {editDate} market data + GPT-4o analysis…</span>}
              </div>
            </div>

            {/* ── Context snapshot ── */}
            {activeRun?.context?.assets && (
              <div className="bg-dark-800 border border-slate-700/40 rounded-lg p-4">
                <div className="text-xs font-semibold text-slate-400 uppercase tracking-wide mb-3">
                  Market Data — {activeRun.context.analysis_date}
                </div>
                <div className="overflow-x-auto">
                  <table className="w-full text-xs">
                    <thead>
                      <tr className="text-[10px] text-slate-600 uppercase tracking-wide border-b border-slate-700/40">
                        <th className="text-left py-1 pr-3">Ticker</th>
                        <th className="text-right pr-3">Price</th>
                        <th className="text-right pr-3">1W</th>
                        <th className="text-right pr-3">1M</th>
                        <th className="text-right pr-3">3M</th>
                        <th className="text-right pr-3">RSI</th>
                        <th className="text-right">vs MA200</th>
                      </tr>
                    </thead>
                    <tbody>
                      {Object.entries(activeRun.context.assets).map(([ticker, d]: [string, any]) => (
                        <tr key={ticker} className="border-b border-slate-700/20">
                          <td className="py-1 pr-3 font-mono text-slate-200">{ticker}</td>
                          <td className="text-right pr-3 font-mono">{d.price ?? '—'}</td>
                          <td className="text-right pr-3">
                            {d.ret_1w != null ? (
                              <span className={d.ret_1w >= 0 ? 'text-emerald-400' : 'text-red-400'}>
                                {d.ret_1w > 0 ? '+' : ''}{d.ret_1w.toFixed(1)}%
                              </span>
                            ) : '—'}
                          </td>
                          <td className="text-right pr-3">
                            {d.ret_1m != null ? (
                              <span className={d.ret_1m >= 0 ? 'text-emerald-400' : 'text-red-400'}>
                                {d.ret_1m > 0 ? '+' : ''}{d.ret_1m.toFixed(1)}%
                              </span>
                            ) : '—'}
                          </td>
                          <td className="text-right pr-3">
                            {d.ret_3m != null ? (
                              <span className={d.ret_3m >= 0 ? 'text-emerald-400' : 'text-red-400'}>
                                {d.ret_3m > 0 ? '+' : ''}{d.ret_3m.toFixed(1)}%
                              </span>
                            ) : '—'}
                          </td>
                          <td className="text-right pr-3">
                            {d.rsi_14 != null ? (
                              <span className={clsx({
                                'text-red-400': d.rsi_14 > 70,
                                'text-emerald-400': d.rsi_14 < 30,
                                'text-slate-300': d.rsi_14 >= 30 && d.rsi_14 <= 70,
                              })}>{d.rsi_14.toFixed(0)}</span>
                            ) : '—'}
                          </td>
                          <td className="text-right">
                            {d.pct_from_ma200 != null ? (
                              <span className={d.pct_from_ma200 >= 0 ? 'text-emerald-400' : 'text-red-400'}>
                                {d.pct_from_ma200 > 0 ? '+' : ''}{d.pct_from_ma200.toFixed(1)}%
                              </span>
                            ) : '—'}
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              </div>
            )}

            {/* ── AI pattern results ── */}
            {aiPatterns.length > 0 && (
              <div className="bg-dark-800 border border-slate-700/40 rounded-lg p-4">
                <div className="flex items-center justify-between mb-3">
                  <div className="text-xs font-semibold text-slate-400 uppercase tracking-wide">
                    AI Pattern Analysis — {aiPatterns.length} pattern{aiPatterns.length !== 1 ? 's' : ''}
                  </div>
                  {!hasOutcomes && (
                    <button onClick={handleEvaluate} disabled={evaluating}
                      className="flex items-center gap-1.5 bg-teal-700 hover:bg-teal-600 disabled:opacity-50 text-white px-3 py-1.5 rounded text-xs font-semibold transition-colors">
                      {evaluating
                        ? <><RefreshCw className="w-3 h-3 animate-spin" /> Fetching outcomes…</>
                        : <><BarChart2 className="w-3 h-3" /> Evaluate at T+{editHorizon}d</>}
                    </button>
                  )}
                  {hasOutcomes && hitRate !== null && (
                    <div className="flex items-center gap-2">
                      <span className="text-xs text-slate-500">Full hits:</span>
                      <span className={clsx('text-sm font-bold', hitRate >= 0.6 ? 'text-emerald-400' : hitRate >= 0.4 ? 'text-yellow-400' : 'text-red-400')}>
                        {Math.round(hitRate * 100)}%
                      </span>
                      {partialHits > 0 && (
                        <span className="text-xs text-amber-400">+{partialHits} partial</span>
                      )}
                      <span className="text-xs text-slate-600">({fullHits}/{scoredOutcomes.length})</span>
                    </div>
                  )}
                </div>

                <div className="space-y-3">
                  {aiPatterns.map((pat: any, idx: number) => {
                    const out = outcomeForPattern(pat.name)
                    const isSaved = savedIdx.has(idx)
                    const dirIcon = pat.signal_direction === 'bullish' ? TrendingUp
                                  : pat.signal_direction === 'bearish' ? TrendingDown
                                  : Zap
                    const DirIcon = dirIcon
                    const dirColor = pat.signal_direction === 'bullish' ? 'text-emerald-400'
                                   : pat.signal_direction === 'bearish' ? 'text-red-400' : 'text-violet-400'

                    return (
                      <div key={idx} className={clsx(
                        'border rounded-lg p-3 transition-colors',
                        (() => { const ht = out?.hit_type ?? (out?.hit ? 'full' : out ? 'miss' : null)
                          return ht === 'full' ? 'border-emerald-700/60 bg-emerald-900/10'
                            : ht === 'partial' ? 'border-amber-700/40 bg-amber-900/10'
                            : ht === 'miss' ? 'border-red-700/40 bg-red-900/10'
                            : 'border-slate-700/40 bg-dark-700/30' })()
                      )}>
                        <div className="flex items-start gap-3">
                          <div className="flex-1">
                            <div className="flex items-center gap-2 mb-1">
                              <DirIcon className={`w-3.5 h-3.5 ${dirColor}`} />
                              <span className="text-sm font-semibold text-slate-100">{pat.name}</span>
                              <span className="text-[10px] bg-slate-700 rounded px-1.5 text-slate-400 font-mono">{pat.underlying}</span>
                              {pat.category && <CategoryBadge cat={pat.category} />}
                            </div>
                            <p className="text-xs text-slate-400 mb-2">{pat.description}</p>
                            <div className="flex items-center gap-4 text-[10px] text-slate-500">
                              <span>Strategy: <span className="text-slate-300">{pat.strategy}</span></span>
                              <span>Expected: <span className={clsx('font-semibold', dirColor)}>
                                {pat.expected_direction === 'up' ? '+' : pat.expected_direction === 'down' ? '-' : '±'}{pat.expected_move_pct}%
                              </span></span>
                              <span>Horizon: <span className="text-slate-300">{pat.horizon_days}d</span></span>
                              <span>Confidence: <span className={clsx('font-semibold', pat.confidence >= 70 ? 'text-emerald-400' : pat.confidence >= 50 ? 'text-yellow-400' : 'text-slate-400')}>{pat.confidence}</span></span>
                            </div>
                            {pat.rationale && (
                              <p className="text-[10px] text-slate-500 mt-1.5 italic">{pat.rationale}</p>
                            )}
                            {/* Outcome row */}
                            {out && <OutcomeRow out={out} />}
                          </div>

                          {/* Save / Find Matching buttons */}
                          <div className="flex-shrink-0 flex flex-col items-end gap-2">
                            {isSaved ? (
                              <span className="flex items-center gap-1 text-emerald-400 text-xs">
                                <CheckCircle2 className="w-3.5 h-3.5" /> Saved
                              </span>
                            ) : (() => {
                              const key = matchKey(idx)
                              const mr = matchResults[key]
                              const loading = matchLoading[key]
                              if (mr) {
                                const isInstance = mr.recommendation === 'merge_as_instance'
                                const isCounter  = mr.recommendation === 'counter_scenario'
                                const badgeColor = isInstance ? 'text-emerald-400 border-emerald-600' : isCounter ? 'text-orange-400 border-orange-600' : 'text-slate-400 border-slate-600'
                                return (
                                  <div className="flex flex-col items-end gap-1.5">
                                    <div className={`text-[10px] border rounded px-1.5 py-0.5 ${badgeColor}`}>
                                      {isInstance ? <><GitMerge className="w-3 h-3 inline mr-1" />Match: {mr.match_name} ({mr.confidence}%)</>
                                       : isCounter ? <><Layers className="w-3 h-3 inline mr-1" />Counter of: {mr.match_name} ({mr.confidence}%)</>
                                       : <><Plus className="w-3 h-3 inline mr-1" />New pattern</>}
                                    </div>
                                    {mr.suggested_regime_tag && (
                                      <div className="text-[10px] text-violet-400 font-mono">#{mr.suggested_regime_tag}</div>
                                    )}
                                    <div className="flex gap-1.5">
                                      {isInstance && mr.match_id && (
                                        <button onClick={() => handleSave(idx, 'instance', mr.match_id, mr.suggested_regime_tag)}
                                          className="flex items-center gap-1 border border-emerald-600 hover:border-emerald-400 text-emerald-400 px-2 py-1 rounded text-[10px] transition-colors">
                                          <GitMerge className="w-3 h-3" /> Merge
                                        </button>
                                      )}
                                      {isCounter && mr.match_id && (
                                        <button onClick={() => handleSave(idx, 'counter', mr.match_id, mr.suggested_regime_tag)}
                                          className="flex items-center gap-1 border border-orange-600 hover:border-orange-400 text-orange-400 px-2 py-1 rounded text-[10px] transition-colors">
                                          <Layers className="w-3 h-3" /> Save counter
                                        </button>
                                      )}
                                      <button onClick={() => handleSave(idx, 'new', undefined, mr.suggested_regime_tag)}
                                        className="flex items-center gap-1 border border-slate-600 hover:border-slate-400 text-slate-400 px-2 py-1 rounded text-[10px] transition-colors">
                                        <Save className="w-3 h-3" /> Save new
                                      </button>
                                    </div>
                                  </div>
                                )
                              }
                              return (
                                <div className="flex gap-1.5">
                                  <button onClick={() => handleFindMatching(idx)} disabled={loading}
                                    className="flex items-center gap-1.5 border border-violet-700 hover:border-violet-400 text-violet-400 px-2.5 py-1.5 rounded text-xs transition-colors disabled:opacity-50">
                                    {loading ? <RefreshCw className="w-3 h-3 animate-spin" /> : <GitMerge className="w-3 h-3" />}
                                    Find matching
                                  </button>
                                  <button onClick={() => handleSave(idx)}
                                    className="flex items-center gap-1.5 border border-slate-600 hover:border-violet-500 hover:text-violet-300 text-slate-400 px-2.5 py-1.5 rounded text-xs transition-colors">
                                    <Save className="w-3 h-3" /> Save
                                  </button>
                                </div>
                              )
                            })()}
                          </div>
                        </div>
                      </div>
                    )
                  })}
                </div>
              </div>
            )}
          </div>
        )}
      </div>

      {/* ── History panel ───────────────────────────────────────────────────── */}
      {runs.length > 0 && (
        <div className="w-64 flex-shrink-0 border-l border-slate-700/40 overflow-y-auto">
          <div className="p-3 border-b border-slate-700/40 text-xs font-semibold text-slate-400 uppercase tracking-wide">
            Run History
          </div>
          {runs.map((run: any) => (
            <div key={run.id}
              className="px-3 py-2 border-b border-slate-700/20 hover:bg-dark-700/40 cursor-pointer group"
              onClick={() => {
                const preset = PRESETS.find(p => p.id === run.preset_id)
                // Always set selected — fall back to a synthetic preset from run data
                setSelected(preset ?? {
                  id: `run-${run.id}`,
                  year: parseInt((run.analysis_date ?? '2000').slice(0, 4)),
                  label: run.theme ?? 'Custom event',
                  date: run.analysis_date,
                  category: 'custom',
                  horizon_days: run.horizon_days,
                  assets: Array.isArray(run.assets) ? run.assets : [],
                  hint: run.theme_hint ?? run.theme ?? '',
                })
                setEditDate(run.analysis_date)
                setEditHorizon(run.horizon_days)
                setEditAssets((Array.isArray(run.assets) ? run.assets : []).join(', '))
                setEditHint(run.theme_hint ?? '')
                setActiveRun({
                  run_id:    run.id,
                  context:   run.context_snapshot,
                  ai_result: run.ai_result,
                  outcome:   run.outcome,
                })
                setSavedIdx(new Set())
                setMatchResults({})
                setMode('events')
              }}>
              <div className="flex items-center gap-1.5 mb-0.5">
                <span className={clsx('w-1.5 h-1.5 rounded-full flex-shrink-0', {
                  'bg-emerald-500': run.status === 'evaluated',
                  'bg-teal-500':    run.status === 'done',
                  'bg-yellow-500':  run.status === 'running',
                  'bg-red-500':     run.status === 'error',
                  'bg-slate-500':   run.status === 'pending',
                })} />
                <span className="text-[10px] text-slate-400 font-mono">{run.analysis_date}</span>
                <button
                  onClick={e => { e.stopPropagation(); deleteRun(run.id) }}
                  className="ml-auto opacity-0 group-hover:opacity-100 text-slate-600 hover:text-red-400 transition-all">
                  <Trash2 className="w-3 h-3" />
                </button>
              </div>
              <div className="text-xs text-slate-300 leading-tight truncate">{run.theme}</div>
              <div className="flex items-center gap-2 mt-0.5">
                <span className="text-[10px] text-slate-600">{run.horizon_days}d</span>
                {run.status === 'evaluated' && run.outcome && (() => {
                  const outs: any[] = Array.isArray(run.outcome) ? run.outcome : []
                  const scored  = outs.filter(o => 'hit_type' in o || 'hit' in o)
                  const full    = outs.filter(o => (o.hit_type ?? (o.hit ? 'full' : 'miss')) === 'full').length
                  const partial = outs.filter(o => (o.hit_type ?? '') === 'partial').length
                  return scored.length > 0 ? (
                    <span className={clsx('text-[10px] font-semibold', full / scored.length >= 0.6 ? 'text-emerald-400' : 'text-red-400')}>
                      {Math.round(full / scored.length * 100)}%{partial > 0 ? ` +${partial}p` : ''}
                    </span>
                  ) : null
                })()}
              </div>
            </div>
          ))}
        </div>
      )}

      {/* ── Toast ── */}
      {toast && (
        <div className={clsx(
          'fixed bottom-5 left-1/2 -translate-x-1/2 rounded-lg px-4 py-2 text-sm shadow-xl z-50 border',
          toast.type === 'ok'
            ? 'bg-emerald-900/90 border-emerald-600 text-emerald-100'
            : 'bg-red-900/90 border-red-600 text-red-100'
        )}>
          {toast.msg}
        </div>
      )}
    </div>
  )
}
