import { useState, useEffect, useCallback, useRef } from 'react'
import { useSearchParams, useNavigate } from 'react-router-dom'
import {
  Search, RefreshCw, Plus, Trash2, Edit3, Sparkles, ExternalLink,
  ChevronDown, ChevronUp, Loader2, CheckCircle, AlertCircle, X,
  SlidersHorizontal, BarChart2, Star, Users
} from 'lucide-react'
import clsx from 'clsx'

// ── Types ─────────────────────────────────────────────────────────────────────

interface SourceRef {
  title: string
  source: string
  url: string
  date: string
  original_score: number
}

interface InstrumentImpact {
  id: number
  instrument_id: string
  impact_score: number
  direction: 'bullish' | 'bearish' | 'neutral' | 'depends_on_outcome'
  rationale: string
  confidence: number
  ai_generated: number
  manually_adjusted: number
  adjusted_score: number | null
  adjusted_direction: string | null
  override_rationale: string
}

interface MarketEvent {
  id: number
  name: string
  start_date: string
  end_date: string | null
  level: 'long' | 'medium' | 'short'
  category: string
  sub_type: string
  description: string
  market_impact: string
  affected_assets: string[]
  impact_score: number
  absorption_pct: number | null
  source_refs: SourceRef[]
  origin?: string
  created_at?: string
  impacts?: InstrumentImpact[]
  evaluated?: boolean
}

function resolveFormula(formula: string, coefs: Record<string, GraphCoef>): string {
  return formula.replace(/\{\{(\w+)\}\}/g, (_, k) => {
    const v = coefs[k]?.value
    return v !== undefined ? String(v) : `[${k}]`
  })
}

interface CausalAnalysis {
  id: number
  market_event_id: number
  template_id: number
  template_name: string
  instrument: string
  activation_score: number | null
  analyzed_at: string
  inputs_json: Record<string, number>
  prediction_json: Record<string, number>
  actual_json: Record<string, number>
  drift_json?: Record<string, any>
  graph_json?: GraphData
}

interface TemplateRef {
  id: number
  name: string
  category: string
  instruments: string[]
}

interface GraphNode { id: string; label: string; type: string; x: number; y: number; unit?: string; instrument?: string; formula?: string }
interface GraphEdge { from: string; to: string; style?: string; sign?: string; label?: string }
interface GraphCoef { value: number; description?: string }
interface GraphData  { nodes: GraphNode[]; edges: GraphEdge[]; coefficients?: Record<string, GraphCoef> }

// ── Constants ─────────────────────────────────────────────────────────────────

const LEVEL_COLORS: Record<string, string> = {
  long:   'text-purple-400',
  medium: 'text-amber-400',
  short:  'text-emerald-400',
}
const ORIGIN_META: Record<string, { label: string; icon: string; color: string; description: string }> = {
  bootstrap_macro:     { label: 'Bootstrap macro',  icon: '📚', color: 'text-slate-400',  description: 'Données historiques macro/géopolitiques chargées au démarrage' },
  bootstrap_eco:       { label: 'Bootstrap éco',    icon: '📅', color: 'text-amber-400',  description: 'Calendrier économique FRED historique (FOMC, CPI, NFP…)' },
  bootstrap_ma:        { label: 'Bootstrap MA',      icon: '📈', color: 'text-cyan-400',   description: 'Signal technique MA détecté sur données historiques (yfinance)' },
  bootstrap_legacy:    { label: 'Bootstrap legacy',  icon: '🗄️', color: 'text-slate-500',  description: 'Données initiales (bootstrap, origine exacte inconnue)' },
  detector_news:       { label: 'News RSS',           icon: '📰', color: 'text-red-400',   description: 'Généré depuis un flux RSS — validé par IA (GPT-4o-mini)' },
  detector_eco:        { label: 'Surprise FRED',      icon: '📊', color: 'text-amber-400', description: 'Surprise économique FRED détectée par le cycle (z-score > seuil)' },
  detector_technical:  { label: 'Signal technique',   icon: '📐', color: 'text-cyan-400',  description: 'Croisement MA50/MA100/MA200 détecté automatiquement (yfinance)' },
  detector_report:     { label: 'Rapport instit.',    icon: '📋', color: 'text-blue-400',  description: 'Rapport institutionnel haute importance (COT, EIA, Fed…)' },
  manual:              { label: 'Manuel',             icon: '✏️', color: 'text-emerald-400', description: 'Créé manuellement dans l\'interface' },
  unknown:             { label: 'Inconnu',            icon: '❓', color: 'text-slate-600',  description: 'Origine non tracée (données antérieures au versioning)' },
}

const DIR_COLORS: Record<string, string> = {
  bullish:            'text-emerald-400',
  bearish:            'text-red-400',
  neutral:            'text-slate-400',
  depends_on_outcome: 'text-amber-400',
}
const DIR_ICONS: Record<string, string> = {
  bullish: '▲', bearish: '▼', neutral: '●', depends_on_outcome: '◆',
}

const LEVELS = ['long', 'medium', 'short']
const ALL_INSTRUMENTS = [
  'SPY','QQQ','IWM','EEM','EFA','GLD','SLV','USO','UNG','TLT',
  'HYG','EURUSD=X','USDJPY=X','GBPUSD=X','VXX','AAPL','NVDA','GS','XOM','BTC-USD',
]

// ── Helpers ───────────────────────────────────────────────────────────────────

function ScoreBar({ score, max = 1 }: { score: number; max?: number }) {
  const pct = Math.round((score / max) * 100)
  const color = score >= 0.7 ? 'bg-red-500' : score >= 0.5 ? 'bg-amber-500' : 'bg-emerald-500'
  return (
    <div className="flex items-center gap-2">
      <div className="flex-1 h-1.5 bg-slate-700 rounded-full overflow-hidden">
        <div className={`h-full rounded-full ${color}`} style={{ width: `${pct}%` }} />
      </div>
      <span className="text-xs text-slate-400 w-8 text-right">{score.toFixed(2)}</span>
    </div>
  )
}

// ── Instrument impact row (inline editable) ───────────────────────────────────

function ImpactRow({
  imp, eventId, onUpdated, onDeleted,
}: {
  imp: InstrumentImpact
  eventId: number
  onUpdated: () => void
  onDeleted: () => void
}) {
  const [editing, setEditing] = useState(false)
  const [score, setScore]     = useState(String(imp.adjusted_score ?? imp.impact_score))
  const [dir, setDir]         = useState(imp.adjusted_direction || imp.direction)
  const [note, setNote]       = useState(imp.override_rationale || '')
  const [saving, setSaving]   = useState(false)

  const effectiveScore = imp.adjusted_score ?? imp.impact_score
  const effectiveDir   = imp.adjusted_direction || imp.direction

  const save = async () => {
    setSaving(true)
    await fetch(`/api/market-events/${eventId}/impacts/${imp.id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ adjusted_score: +score, adjusted_direction: dir, override_rationale: note }),
    })
    setSaving(false)
    setEditing(false)
    onUpdated()
  }

  const del = async () => {
    if (!confirm(`Supprimer l'impact sur ${imp.instrument_id} ?`)) return
    await fetch(`/api/market-events/${eventId}/impacts/${imp.id}`, { method: 'DELETE' })
    onDeleted()
  }

  return (
    <div className="border-b border-slate-800/60 last:border-0">
      <div className="flex items-center gap-3 py-2 px-3 hover:bg-slate-800/20">
        {/* Ticker */}
        <span className="font-mono text-sm text-slate-200 w-24 shrink-0">{imp.instrument_id}</span>
        {/* Direction */}
        <span className={clsx('text-sm font-bold w-6 shrink-0', DIR_COLORS[effectiveDir])}>
          {DIR_ICONS[effectiveDir] ?? '●'}
        </span>
        {/* Score bar */}
        <div className="w-28 shrink-0">
          <ScoreBar score={effectiveScore} />
        </div>
        {/* Confidence */}
        <span className="text-xs text-slate-500 w-10 shrink-0">{Math.round(imp.confidence * 100)}%</span>
        {/* Rationale */}
        <span className="text-xs text-slate-400 flex-1 min-w-0 truncate">{imp.rationale}</span>
        {/* AI badge */}
        {imp.ai_generated ? (
          <span className="text-xs text-blue-400 shrink-0">AI</span>
        ) : (
          <span className="text-xs text-slate-600 shrink-0">manual</span>
        )}
        {imp.manually_adjusted ? <span className="text-xs text-amber-400 shrink-0">adj.</span> : null}
        {/* Actions */}
        <button onClick={() => setEditing(x => !x)} className="text-slate-500 hover:text-slate-300 shrink-0"><Edit3 size={12} /></button>
        <button onClick={del} className="text-slate-600 hover:text-red-400 shrink-0"><Trash2 size={12} /></button>
      </div>
      {editing && (
        <div className="px-3 pb-3 grid grid-cols-3 gap-2 bg-slate-800/30">
          <label className="flex flex-col gap-1">
            <span className="text-xs text-slate-500">Score ajusté</span>
            <input type="range" min="0" max="1" step="0.05"
              value={score} onChange={e => setScore(e.target.value)}
              className="w-full accent-amber-400" />
            <span className="text-xs text-center text-amber-300">{(+score).toFixed(2)}</span>
          </label>
          <label className="flex flex-col gap-1">
            <span className="text-xs text-slate-500">Direction</span>
            <select value={dir} onChange={e => setDir(e.target.value)}
              className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm">
              {['bullish','bearish','neutral','depends_on_outcome'].map(d => (
                <option key={d} value={d}>{d}</option>
              ))}
            </select>
          </label>
          <label className="flex flex-col gap-1">
            <span className="text-xs text-slate-500">Note</span>
            <input value={note} onChange={e => setNote(e.target.value)}
              className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm"
              placeholder="Raison de l'ajustement..." />
          </label>
          <div className="col-span-3 flex gap-2 mt-1">
            <button onClick={save} disabled={saving}
              className="flex items-center gap-1 bg-emerald-700 hover:bg-emerald-600 text-white text-xs rounded px-3 py-1">
              {saving ? <Loader2 size={11} className="animate-spin" /> : <CheckCircle size={11} />}
              Sauvegarder
            </button>
            <button onClick={() => setEditing(false)} className="text-xs text-slate-500 hover:text-slate-300 px-2">Annuler</button>
          </div>
        </div>
      )}
    </div>
  )
}

// ── Add instrument panel ──────────────────────────────────────────────────────

function AddInstrumentPanel({ eventId, onAdded }: { eventId: number; onAdded: () => void }) {
  const [open, setOpen]     = useState(false)
  const [ticker, setTicker] = useState('')
  const [score, setScore]   = useState(0.5)
  const [dir, setDir]       = useState('neutral')
  const [rationale, setRat] = useState('')
  const [saving, setSaving] = useState(false)

  const submit = async () => {
    if (!ticker) return
    setSaving(true)
    await fetch(`/api/market-events/${eventId}/impacts`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ instrument_id: ticker, impact_score: score, direction: dir, rationale, confidence: 0.8 }),
    })
    setSaving(false)
    setOpen(false)
    setTicker('')
    setRat('')
    onAdded()
  }

  if (!open) {
    return (
      <button onClick={() => setOpen(true)}
        className="flex items-center gap-1.5 text-xs text-slate-500 hover:text-emerald-400 py-2 px-3 border-t border-slate-800/60">
        <Plus size={12} /> Ajouter instrument
      </button>
    )
  }

  return (
    <div className="border-t border-slate-800/60 px-3 py-3 bg-slate-800/30 grid grid-cols-4 gap-2">
      <select value={ticker} onChange={e => setTicker(e.target.value)}
        className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm col-span-1">
        <option value="">Ticker...</option>
        {ALL_INSTRUMENTS.map(t => <option key={t} value={t}>{t}</option>)}
      </select>
      <select value={dir} onChange={e => setDir(e.target.value)}
        className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm">
        {['bullish','bearish','neutral','depends_on_outcome'].map(d => <option key={d} value={d}>{d}</option>)}
      </select>
      <div className="flex flex-col gap-0.5">
        <input type="range" min="0" max="1" step="0.05" value={score}
          onChange={e => setScore(+e.target.value)} className="accent-emerald-400" />
        <span className="text-xs text-center text-emerald-300">{score.toFixed(2)}</span>
      </div>
      <input value={rationale} onChange={e => setRat(e.target.value)} placeholder="Rationale..."
        className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm" />
      <div className="col-span-4 flex gap-2">
        <button onClick={submit} disabled={saving || !ticker}
          className="flex items-center gap-1 bg-emerald-700 hover:bg-emerald-600 disabled:bg-slate-700 disabled:text-slate-500 text-white text-xs rounded px-3 py-1">
          {saving ? <Loader2 size={11} className="animate-spin" /> : <Plus size={11} />} Ajouter
        </button>
        <button onClick={() => setOpen(false)} className="text-xs text-slate-500 hover:text-slate-300 px-2">Annuler</button>
      </div>
    </div>
  )
}

// ── CausalGraphMini — SVG inline du graphe instancié ─────────────────────────

function CausalGraphMini({ nodes, edges, nodeValues }: {
  nodes: GraphNode[]
  edges: GraphEdge[]
  nodeValues: Record<string, number>
}) {
  if (!nodes.length) return null
  const xs   = nodes.map(n => n.x)
  const ys   = nodes.map(n => n.y)
  const minX = Math.min(...xs) - 70
  const maxX = Math.max(...xs) + 70
  const minY = Math.min(...ys) - 30
  const maxY = Math.max(...ys) + 30
  const vw   = Math.max(maxX - minX, 200)
  const vh   = Math.max(maxY - minY, 100)
  const nodeMap = Object.fromEntries(nodes.map(n => [n.id, n]))
  const BOX_W = 90, BOX_H = 36, SHORTEN = 21

  return (
    <svg viewBox={`${minX} ${minY} ${vw} ${vh}`} className="w-full" style={{ maxHeight: 400, minHeight: 200 }}>
      <defs>
        {(['positive', 'negative', 'neutral'] as const).map(s => (
          <marker key={s} id={`cgm-arr-${s}`} markerWidth={7} markerHeight={7} refX={6} refY={3} orient="auto">
            <path d="M0,0 L0,6 L7,3 z" fill={s === 'positive' ? '#14b8a6' : s === 'negative' ? '#f97316' : '#64748b'} />
          </marker>
        ))}
      </defs>

      {edges.map((e, i) => {
        const from = nodeMap[e.from], to = nodeMap[e.to]
        if (!from || !to) return null
        const dx  = to.x - from.x, dy = to.y - from.y
        const len = Math.sqrt(dx * dx + dy * dy) || 1
        const sign  = e.sign || 'neutral'
        const color = sign === 'positive' ? '#14b8a6' : sign === 'negative' ? '#f97316' : '#64748b'
        return (
          <line key={i}
            x1={from.x + (dx / len) * SHORTEN}      y1={from.y + (dy / len) * SHORTEN}
            x2={to.x   - (dx / len) * (SHORTEN + 5)} y2={to.y   - (dy / len) * (SHORTEN + 5)}
            stroke={color} strokeWidth={1.5}
            strokeDasharray={e.style === 'dashed' ? '5,3' : undefined}
            markerEnd={`url(#cgm-arr-${sign})`} />
        )
      })}

      {nodes.map(n => {
        const val   = nodeValues[n.id]
        const isOut = n.type === 'market_asset' || n.type === 'output'
        const isIn  = n.type === 'input'
        const fill  = isOut ? (val > 0 ? '#052e16' : val < 0 ? '#450a0a' : '#1e293b') : '#0f172a'
        const stroke = isIn ? '#3b82f6'
          : isOut ? (val > 0 ? '#16a34a' : val < 0 ? '#dc2626' : '#64748b') : '#475569'
        const valColor = val > 0 ? '#4ade80' : val < 0 ? '#f87171' : '#94a3b8'
        const lbl    = n.label.length > 14 ? n.label.slice(0, 13) + '…' : n.label
        const valStr = val !== undefined
          ? `${val > 0 ? '+' : ''}${val.toFixed(n.unit === 'pips' ? 0 : 1)}${n.unit === 'pips' ? 'p' : n.unit === '%' ? '%' : ''}`
          : '—'
        return (
          <g key={n.id} transform={`translate(${n.x},${n.y})`}>
            <rect x={-BOX_W/2} y={-BOX_H/2} width={BOX_W} height={BOX_H} rx={4}
              fill={fill} stroke={stroke} strokeWidth={isOut ? 2 : 1} />
            <text y={-7} textAnchor="middle" fill="#94a3b8" fontSize={8} fontFamily="ui-monospace,monospace">{lbl}</text>
            <text y={9}  textAnchor="middle" fill={val !== undefined ? valColor : '#475569'}
              fontSize={10} fontWeight="bold" fontFamily="ui-monospace,monospace">{valStr}</text>
          </g>
        )
      })}
    </svg>
  )
}


// ── Event detail panel (right side) ──────────────────────────────────────────

function EventDetail({
  event, onClose, onUpdated, onDeleted,
}: {
  event: MarketEvent
  onClose: () => void
  onUpdated: (ev: MarketEvent) => void
  onDeleted: (id: number) => void
}) {
  const navigate = useNavigate()
  const [detail, setDetail]       = useState<MarketEvent>(event)
  const [editing, setEditing]     = useState(false)
  const [editData, setEditData]   = useState<Partial<MarketEvent>>({})
  const [saving, setSaving]       = useState(false)
  const [analyses, setAnalyses]   = useState<CausalAnalysis[]>([])
  const [expandedAnalysis, setExpandedAnalysis] = useState<number | null>(null)
  // Inline analysis
  const [templates, setTemplates]     = useState<TemplateRef[]>([])
  const [selTmpl, setSelTmpl]         = useState(0)
  const [loadingRec, setLoadingRec]   = useState(false)
  const [loadingInst, setLoadingInst] = useState(false)
  const [loadingAn,  setLoadingAn]    = useState(false)
  const [loadingCreate, setLoadingCreate] = useState(false)
  const [recMsg, setRecMsg]           = useState<string | null>(null)
  const [noTemplateFound, setNoTemplateFound] = useState(false)
  const [instInputs, setInstInputs]   = useState<Record<string, number>>({})
  const [instMsg, setInstMsg]         = useState<string | null>(null)
  const [anResult, setAnResult]       = useState<{
    score: number | null
    preds:    Record<string, number>
    actuals:  Record<string, number>
    drift?:   Record<string, { pre_pips: number | null; post_pips: number | null; lag_min: number; lag_days: number }>
    pricesMode?: string
    lagMin?: number
    lagDays?: number
    lagDebug?: { output_ids: string[]; edges_to_output: number; edges_with_lag_days: number; used_all_edges: boolean }
  } | null>(null)
  const [graphData, setGraphData]     = useState<GraphData | null>(null)

  const loadDetail = useCallback(async () => {
    const r = await fetch(`/api/market-events/${event.id}`)
    if (r.ok) {
      const d = await r.json()
      setDetail(d)
    }
  }, [event.id])

  const loadAnalyses = useCallback(async () => {
    const r = await fetch(`/api/causal-lab/analyses?market_event_id=${event.id}&limit=20`)
    if (r.ok) {
      const data: CausalAnalysis[] = await r.json()
      setAnalyses(data)
      if (data.length > 0) {
        const latest = data[0]
        if (latest.template_id) setSelTmpl(latest.template_id)
        if (Object.keys(latest.inputs_json || {}).length > 0) setInstInputs(latest.inputs_json)
        const driftRaw = latest.drift_json || {}
        // drift_json peut être { EURUSD: {...}, XAUUSD: {...} } (nouveau) ou { post_pips: ..., ... } (ancien)
        const isDriftByInst = driftRaw && !('post_pips' in driftRaw) && Object.keys(driftRaw).length > 0
        const driftByInst = isDriftByInst
          ? driftRaw
          : latest.instrument && Object.keys(driftRaw).length > 0
            ? { [latest.instrument]: driftRaw }
            : undefined
        setAnResult({
          score:      latest.activation_score,
          preds:      latest.prediction_json || {},
          actuals:    latest.actual_json || {},
          drift:      driftByInst,
          pricesMode: undefined,
          lagMin:     undefined,
          lagDays:    undefined,
          lagDebug:   undefined,
        })
        if (latest.graph_json?.nodes?.length) {
          setGraphData(latest.graph_json as GraphData)
        }
      }
    }
  }, [event.id])

  useEffect(() => {
    loadDetail()
    loadAnalyses()
    fetch('/api/causal-lab/templates').then(r => r.json()).then(d => setTemplates(Array.isArray(d) ? d : [])).catch(() => {})
  }, [loadDetail, loadAnalyses])

  const saveEdit = async () => {
    setSaving(true)
    const payload = { ...detail, ...editData, source_refs: detail.source_refs ?? [] }
    const r = await fetch(`/api/market-events/${event.id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    })
    setSaving(false)
    if (r.ok) {
      setEditing(false)
      await loadDetail()
      onUpdated({ ...detail, ...editData } as MarketEvent)
    }
  }

  const del = async () => {
    if (!confirm(`Supprimer "${detail.name}" ?`)) return
    await fetch(`/api/market-events/${event.id}`, { method: 'DELETE' })
    onDeleted(event.id)
    onClose()
  }

  const instantiate = async (tmplId: number) => {
    if (!tmplId) return
    setLoadingInst(true); setInstMsg(null); setInstInputs({})
    try {
      const r = await fetch('/api/causal-lab/instantiate', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ market_event_id: detail.id, template_id: tmplId }),
      })
      const d = await r.json()
      if (!r.ok) throw new Error(d.detail || r.statusText)
      setInstInputs(d.inputs || {})
      setInstMsg(d.rationale || null)
    } catch (e: any) { setInstMsg(`✗ ${(e as any).message}`) }
    finally { setLoadingInst(false) }
  }

  const recommendTemplate = async () => {
    setLoadingRec(true); setRecMsg(null); setAnResult(null); setGraphData(null)
    setInstInputs({}); setInstMsg(null); setNoTemplateFound(false)
    try {
      const r = await fetch('/api/causal-lab/recommend', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ market_event_id: detail.id }),
      })
      const d = await r.json()
      if (!r.ok) throw new Error(d.detail || r.statusText)
      const rec = d.recommendation
      if (rec?.template_id) {
        setSelTmpl(rec.template_id)
        setRecMsg(`✓ ${rec.template_name}${rec.confidence ? ` (${Math.round(rec.confidence * 100)}%)` : ''}`)
        await instantiate(rec.template_id)
      } else {
        // Aucun template adapté — création automatique d'un nouveau
        setRecMsg('Aucun template adapté — création en cours…')
        setNoTemplateFound(false)
        setLoadingCreate(true)
        try {
          const cr = await fetch('/api/causal-lab/create-from-event', {
            method: 'POST', headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ market_event_id: detail.id }),
          })
          const cd = await cr.json()
          if (!cr.ok) throw new Error(cd.detail || cr.statusText)
          const newTmplId = cd.id ?? cd.template?.id ?? cd.template_id
          if (newTmplId) {
            setTemplates(prev => [...prev, { id: newTmplId, name: cd.name, category: cd.category, graph_json: cd.graph_json, instruments: cd.instruments, calibration_json: {} }])
            setSelTmpl(newTmplId)
            setRecMsg(`✦ Nouveau template créé : ${cd.name ?? 'template'}`)
            await instantiate(newTmplId)
          } else {
            setRecMsg('Template créé mais ID manquant')
          }
        } catch (ce: any) {
          setRecMsg(`✗ Création échouée : ${ce.message}`)
          setNoTemplateFound(true)
        } finally {
          setLoadingCreate(false)
        }
      }
    } catch (e: any) { setRecMsg(`✗ ${(e as any).message}`) }
    finally { setLoadingRec(false) }
  }

  const createTemplateFromEvent = async () => {
    setLoadingCreate(true); setRecMsg(null); setNoTemplateFound(false)
    try {
      const r = await fetch('/api/causal-lab/create-from-event', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ market_event_id: detail.id }),
      })
      const d = await r.json()
      if (!r.ok) throw new Error(d.detail || r.statusText)
      const tmplRes  = await fetch('/api/causal-lab/templates')
      const tmplData = await tmplRes.json()
      setTemplates(Array.isArray(tmplData) ? tmplData : [])
      if (d.id) {
        setSelTmpl(d.id)
        setRecMsg(`✓ Template créé : ${d.name}`)
        await instantiate(d.id)
      }
    } catch (e: any) { setRecMsg(`✗ ${(e as any).message}`) }
    finally { setLoadingCreate(false) }
  }

  const deleteAnalysis = async (analysisId: number) => {
    if (!confirm('Supprimer cette instanciation ?')) return
    await fetch(`/api/causal-lab/analyses/${analysisId}`, { method: 'DELETE' })
    setAnResult(null); setGraphData(null); setInstInputs({})
    await loadAnalyses()
  }

  const runAnalysis = async () => {
    if (!selTmpl) return
    setLoadingAn(true); setAnResult(null); setGraphData(null)
    try {
      const r = await fetch('/api/causal-lab/analyze', {
        method: 'POST', headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ market_event_id: detail.id, template_id: selTmpl, inputs: instInputs, coef_overrides: {} }),
      })
      const d = await r.json()
      if (!r.ok) throw new Error(d.detail || r.statusText)
      const freshResult = {
        score:      d.activation?.score ?? null,
        preds:      d.node_values ?? {},
        actuals:    d.actual_moves ?? {},
        drift:      d.drift_by_inst ?? (d.drift ? { [d.instrument]: d.drift } : undefined),
        pricesMode: d.prices_mode,
        lagMin:     d.effective_lag_min,
        lagDays:    d.effective_lag_days,
        lagDebug:   d.lag_debug,
      }
      setAnResult(freshResult)
      if (d.graph_json?.nodes?.length) setGraphData(d.graph_json)
      await loadAnalyses()
      setAnResult(freshResult)  // loadAnalyses() écrase anResult — on restaure le résultat frais
    } catch (e: any) { setAnResult({ score: null, preds: {}, actuals: {} }) }
    finally { setLoadingAn(false) }
  }

  const evalScore = detail.impact_score

  return (
    <div className="flex flex-col h-full overflow-hidden">
      {/* Header */}
      <div className="flex items-start gap-3 px-5 py-4 border-b border-slate-700/40 shrink-0">
        <div className="flex-1 min-w-0">
          {editing ? (
            <input
              defaultValue={detail.name}
              onChange={e => setEditData(d => ({ ...d, name: e.target.value }))}
              className="bg-dark-900 border border-amber-500/50 rounded px-3 py-1.5 text-slate-100 font-bold text-sm w-full"
            />
          ) : (
            <h2 className="text-slate-100 font-bold text-sm leading-snug">{detail.name}</h2>
          )}
          <div className="flex items-center gap-2 mt-1.5 flex-wrap">
            <span className={clsx('text-xs font-semibold', LEVEL_COLORS[detail.level])}>{detail.level}</span>
            <span className="text-xs text-slate-500">{detail.start_date}</span>
            {detail.end_date && <span className="text-xs text-slate-600">→ {detail.end_date}</span>}
          </div>
        </div>
        <div className="flex items-center gap-1.5 shrink-0">
          <button onClick={() => { setEditing(x => !x); setEditData({}) }}
            className={clsx('p-1.5 rounded hover:bg-slate-700/50', editing ? 'text-amber-400' : 'text-slate-500 hover:text-slate-300')}>
            <Edit3 size={14} />
          </button>
          <button onClick={del} className="p-1.5 rounded text-slate-600 hover:text-red-400 hover:bg-red-900/20">
            <Trash2 size={14} />
          </button>
          <button onClick={onClose} className="p-1.5 rounded text-slate-500 hover:text-slate-300 hover:bg-slate-700/50">
            <X size={14} />
          </button>
        </div>
      </div>

      {/* Scrollable body */}
      <div className="flex-1 overflow-y-auto space-y-4 px-5 py-4">

        {/* Impact score + absorption */}
        <div className="grid grid-cols-2 gap-3">
          <div className="bg-slate-800/40 rounded-lg p-3">
            <div className="text-xs text-slate-500 mb-1">Score impact</div>
            <ScoreBar score={evalScore} />
          </div>
          {detail.absorption_pct != null && (
            <div className="bg-slate-800/40 rounded-lg p-3">
              <div className="text-xs text-slate-500 mb-1">Absorption marché</div>
              <div className="flex items-center gap-2">
                <div className="flex-1 h-1.5 bg-slate-700 rounded-full overflow-hidden">
                  <div className="h-full bg-blue-500 rounded-full" style={{ width: `${detail.absorption_pct}%` }} />
                </div>
                <span className="text-xs text-blue-300">{detail.absorption_pct}%</span>
              </div>
            </div>
          )}
        </div>

        {/* Description */}
        {editing ? (
          <textarea
            defaultValue={detail.description}
            onChange={e => setEditData(d => ({ ...d, description: e.target.value }))}
            rows={3}
            className="w-full bg-dark-900 border border-slate-700 rounded px-3 py-2 text-slate-300 text-sm resize-none"
          />
        ) : detail.description ? (
          <p className="text-sm text-slate-300 leading-relaxed">{detail.description}</p>
        ) : null}

        {editing ? (
          <div className="grid grid-cols-2 gap-2">
            <div>
              <div className="text-xs text-slate-500 mb-1">Niveau</div>
              <select defaultValue={detail.level}
                onChange={e => setEditData(d => ({ ...d, level: e.target.value as any }))}
                className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm">
                {LEVELS.map(l => <option key={l} value={l}>{l}</option>)}
              </select>
            </div>
            <div>
              <div className="text-xs text-slate-500 mb-1">Score impact</div>
              <input type="number" step="0.05" min="0" max="1" defaultValue={detail.impact_score}
                onChange={e => setEditData(d => ({ ...d, impact_score: +e.target.value }))}
                className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm" />
            </div>
          </div>
        ) : null}

        {editing && (
          <div className="flex gap-2">
            <button onClick={saveEdit} disabled={saving}
              className="flex items-center gap-1.5 bg-emerald-700 hover:bg-emerald-600 text-white text-sm rounded-lg px-4 py-1.5">
              {saving ? <Loader2 size={13} className="animate-spin" /> : <CheckCircle size={13} />} Sauvegarder
            </button>
            <button onClick={() => setEditing(false)} className="text-sm text-slate-500 hover:text-slate-300 px-3">Annuler</button>
          </div>
        )}

        {/* Origin + Sources */}
        <div>
          <div className="text-xs text-slate-500 uppercase tracking-wide mb-2">Provenance</div>
          {/* Origin badge */}
          {(() => {
            const meta = ORIGIN_META[detail.origin ?? 'unknown'] ?? ORIGIN_META['unknown']
            return (
              <div className="flex items-start gap-2 bg-slate-800/50 rounded-lg px-3 py-2 mb-2 border border-slate-700/30">
                <span className="text-base shrink-0">{meta.icon}</span>
                <div className="flex-1 min-w-0">
                  <div className={`text-xs font-semibold ${meta.color}`}>{meta.label}</div>
                  <div className="text-xs text-slate-500 mt-0.5">{meta.description}</div>
                </div>
              </div>
            )
          })()}
          {/* Source refs */}
          {(detail.source_refs?.length ?? 0) > 0 ? (
            <div className="space-y-1.5">
              {detail.source_refs!.map((ref, i) => (
                <div key={i} className="flex items-start gap-2 bg-slate-800/30 rounded-lg px-3 py-2 border border-slate-800/60">
                  <div className="flex-1 min-w-0">
                    <div className="text-xs text-slate-200 font-medium">{ref.title}</div>
                    <div className="flex items-center gap-2 mt-0.5 flex-wrap">
                      <span className="text-xs text-slate-500">{ref.source}</span>
                      <span className="text-xs text-slate-600">{ref.date}</span>
                      {ref.original_score > 0 && (
                        <span className="text-xs text-slate-600">
                          score brut: <span className="text-slate-400">{ref.original_score.toFixed(2)}</span>
                        </span>
                      )}
                    </div>
                  </div>
                  {ref.url && (
                    <a href={ref.url} target="_blank" rel="noreferrer"
                      className="text-slate-500 hover:text-blue-400 shrink-0 mt-0.5">
                      <ExternalLink size={12} />
                    </a>
                  )}
                </div>
              ))}
            </div>
          ) : (
            <div className="text-xs text-slate-600 italic px-3 py-2 bg-slate-800/20 rounded-lg border border-slate-800/40">
              Aucune source liée — cet événement ne référence pas de document ou d'article spécifique.
            </div>
          )}
        </div>

        {/* Causal graphs */}
        <div className="space-y-3">
          <div className="text-xs text-slate-500 uppercase tracking-wide">
            Graphes causaux
            {analyses.length > 0 && <span className="text-slate-600 ml-1.5 normal-case">({analyses.length})</span>}
          </div>

          {/* Existing analyses */}
          {analyses.length > 0 && (
            <div className="space-y-1.5">
              {analyses.map(a => {
                const score = a.activation_score != null ? Math.round(a.activation_score * 100) : null
                const topPreds = Object.entries(a.prediction_json || {})
                  .filter(([k]) => !k.includes('error'))
                  .sort(([, v1], [, v2]) => Math.abs(v2) - Math.abs(v1))
                  .slice(0, 3)
                const isExpanded = expandedAnalysis === a.id
                const gNodes = a.graph_json?.nodes || []
                const gEdges = a.graph_json?.edges || []
                const gCoefs = a.graph_json?.coefficients || {}
                const allVals: Record<string, number> = { ...(a.inputs_json || {}), ...(a.prediction_json || {}) }
                const inputNodes = gNodes.filter(n => n.type === 'input')
                const outputNodes = gNodes.filter(n => n.type === 'market_asset' || n.type === 'output')
                const midNodes = gNodes.filter(n => n.type !== 'input' && n.type !== 'market_asset' && n.type !== 'output')
                return (
                  <div key={a.id} className="space-y-0.5">
                    <div className="flex items-stretch gap-1">
                      <button
                        onClick={() => setExpandedAnalysis(isExpanded ? null : a.id)}
                        className={clsx(
                          'flex-1 text-left border rounded-lg px-3 py-2 transition-colors',
                          isExpanded
                            ? 'bg-slate-800/70 border-blue-700/50'
                            : 'bg-slate-800/40 hover:bg-slate-800/70 border-slate-700/40 hover:border-blue-700/40'
                        )}
                      >
                        <div className="flex items-center justify-between">
                          <span className="text-xs font-medium text-slate-200 truncate">{a.template_name}</span>
                          <div className="flex items-center gap-1.5 shrink-0 ml-2">
                            {score != null && (
                              <span className={clsx(
                                'text-xs font-mono px-1.5 py-0.5 rounded',
                                score >= 70 ? 'bg-emerald-500/20 text-emerald-400' :
                                score >= 40 ? 'bg-amber-500/20 text-amber-400' :
                                              'bg-slate-700/40 text-slate-400'
                              )}>{score}%</span>
                            )}
                            <span className="text-slate-600 text-[10px]">{isExpanded ? '▲' : '▼'}</span>
                          </div>
                        </div>
                        <div className="flex items-center gap-2 flex-wrap mt-0.5">
                          <span className="text-xs text-slate-500">{a.instrument}</span>
                          {topPreds.map(([k, v]) => (
                            <span key={k} className={clsx('text-xs font-mono', v > 0 ? 'text-emerald-500' : 'text-red-500')}>
                              {k}: {v > 0 ? '+' : ''}{Math.round(v)}
                            </span>
                          ))}
                        </div>
                      </button>
                      <button
                        onClick={() => deleteAnalysis(a.id)}
                        className="px-2 rounded-lg text-slate-600 hover:text-red-400 hover:bg-red-900/20 border border-slate-700/40 transition-colors shrink-0"
                        title="Supprimer cette instanciation"
                      >
                        <Trash2 size={11} />
                      </button>
                    </div>

                    {/* Détail du calcul */}
                    {isExpanded && (
                      <div className="rounded-lg border border-slate-700/40 bg-dark-900/80 p-2.5 space-y-2.5">
                        {/* Mini graphe */}
                        {gNodes.length > 0 && (
                          <CausalGraphMini nodes={gNodes} edges={gEdges} nodeValues={allVals} />
                        )}

                        {/* Breakdown par couche */}
                        <div className="space-y-1.5 text-xs">
                          {inputNodes.length > 0 && (
                            <div>
                              <div className="text-[10px] text-slate-500 uppercase tracking-wider mb-1">Entrées</div>
                              <div className="flex flex-wrap gap-1">
                                {inputNodes.map(n => {
                                  const v = allVals[n.id]
                                  if (v === undefined) return null
                                  return (
                                    <span key={n.id} className="bg-blue-900/20 border border-blue-700/30 text-blue-300 px-1.5 py-0.5 rounded font-mono text-[10px]">
                                      {n.label}: <span className={v > 0 ? 'text-blue-200' : 'text-red-300'}>{v > 0 ? '+' : ''}{v.toFixed(2)}</span>
                                    </span>
                                  )
                                })}
                              </div>
                            </div>
                          )}

                          {midNodes.length > 0 && (
                            <div>
                              <div className="text-[10px] text-slate-500 uppercase tracking-wider mb-1">Propagation</div>
                              <div className="space-y-0.5">
                                {midNodes.map(n => {
                                  const v = allVals[n.id]
                                  if (v === undefined) return null
                                  const fmt = n.unit === 'pips' ? Math.round(v).toString()
                                    : n.unit === '%' ? v.toFixed(3) + '%'
                                    : v.toFixed(4)
                                  const formula = n.formula ? resolveFormula(n.formula, gCoefs) : null
                                  return (
                                    <div key={n.id} className="flex items-baseline gap-2 flex-wrap">
                                      <span className="text-slate-400 text-[10px] font-medium shrink-0">{n.label}:</span>
                                      <span className={clsx('font-mono text-[10px] font-bold shrink-0', v > 0 ? 'text-teal-400' : v < 0 ? 'text-orange-400' : 'text-slate-500')}>
                                        {v > 0 ? '+' : ''}{fmt}
                                      </span>
                                      {formula && (
                                        <span className="text-[9px] text-slate-600 font-mono truncate">= {formula}</span>
                                      )}
                                    </div>
                                  )
                                })}
                              </div>
                            </div>
                          )}

                          {outputNodes.length > 0 && (
                            <div>
                              <div className="text-[10px] text-slate-500 uppercase tracking-wider mb-1">Prédictions vs Réel</div>
                              <div className="space-y-0.5">
                                {outputNodes.map(n => {
                                  const pred = allVals[n.id]
                                  const actual = (a.actual_json || {})[n.id]
                                    ?? (a.actual_json || {})[n.instrument || '']
                                    ?? (a.actual_json || {})[(n.instrument || '').toUpperCase()]
                                  const fmt = (x: number) => `${x > 0 ? '+' : ''}${Math.round(x)}${n.unit === 'pips' ? 'p' : n.unit === '$/oz' ? '$' : ''}`
                                  const formula = n.formula ? resolveFormula(n.formula, gCoefs) : null
                                  return (
                                    <div key={n.id} className="flex items-baseline gap-2 flex-wrap">
                                      <span className={clsx('border px-1.5 py-0.5 rounded font-mono text-[10px] shrink-0',
                                        pred > 0 ? 'bg-emerald-900/20 border-emerald-700/30 text-emerald-400' :
                                        pred < 0 ? 'bg-red-900/20 border-red-700/30 text-red-400' :
                                                   'bg-slate-800/60 border-slate-700/40 text-slate-400')}>
                                        {n.label}: {pred !== undefined ? fmt(pred) : '—'}
                                        {actual !== undefined && (
                                          <span className="text-slate-500 ml-1.5">/ réel {fmt(actual)}</span>
                                        )}
                                      </span>
                                      {formula && (
                                        <span className="text-[9px] text-slate-600 font-mono truncate">= {formula}</span>
                                      )}
                                    </div>
                                  )
                                })}
                              </div>
                            </div>
                          )}
                        </div>

                        <button
                          onClick={() => navigate(`/causal-lab?template=${a.template_id}`)}
                          className="text-[10px] text-blue-400 hover:text-blue-300 transition-colors"
                        >
                          → Ouvrir dans l'éditeur Causal Lab
                        </button>
                      </div>
                    )}
                  </div>
                )
              })}
            </div>
          )}

          {/* Inline analysis panel */}
          <div className="bg-slate-800/30 rounded-lg border border-slate-700/30 p-3 space-y-2">
            <div className="text-xs text-slate-400 font-medium">
              {analyses.length === 0 ? 'Aucun graphe — lancez une analyse' : 'Nouvelle analyse'}
            </div>

            {/* AI recommend + template picker */}
            <div className="flex gap-1.5">
              <button
                onClick={recommendTemplate}
                disabled={loadingRec}
                className="flex items-center gap-1 text-xs bg-purple-700/40 hover:bg-purple-700/60 border border-purple-700/40 text-purple-300 rounded px-2 py-1.5 shrink-0 disabled:opacity-50"
              >
                {loadingRec ? <Loader2 size={10} className="animate-spin" /> : <Sparkles size={10} />}
                IA
              </button>
              <select
                value={selTmpl}
                onChange={e => { setSelTmpl(Number(e.target.value)); setRecMsg(null); setAnResult(null) }}
                className="flex-1 bg-dark-900 border border-slate-700 rounded px-2 py-1.5 text-xs text-slate-300 min-w-0"
              >
                <option value={0}>— Choisir un template —</option>
                {templates.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
              </select>
            </div>

            {recMsg && (
              <div className={clsx('text-xs px-2 py-1 rounded', recMsg.startsWith('✓') ? 'text-emerald-400 bg-emerald-900/20' : recMsg.startsWith('✗') ? 'text-red-400 bg-red-900/20' : 'text-amber-400 bg-amber-900/20')}>
                {recMsg}
              </div>
            )}

            {noTemplateFound && (
              <button
                onClick={createTemplateFromEvent}
                disabled={loadingCreate}
                className="flex items-center gap-1.5 text-xs bg-purple-800/30 hover:bg-purple-700/50 border border-purple-700/40 text-purple-300 rounded px-2 py-1.5 w-full justify-center disabled:opacity-50"
              >
                {loadingCreate ? <Loader2 size={10} className="animate-spin" /> : <Sparkles size={10} />}
                {loadingCreate ? 'Création IA en cours…' : "L'IA peut créer un template"}
              </button>
            )}

            {/* Instantiation status + inputs preview */}
            {selTmpl > 0 && (
              <div className="space-y-1">
                <div className="flex items-center gap-2">
                  <button
                    onClick={() => instantiate(selTmpl)}
                    disabled={loadingInst}
                    className="flex items-center gap-1 text-xs text-slate-500 hover:text-purple-400 disabled:opacity-50 transition-colors"
                  >
                    {loadingInst ? <Loader2 size={9} className="animate-spin" /> : <Sparkles size={9} />}
                    {loadingInst ? 'Calibration IA…' : 'Calibrer (IA)'}
                  </button>
                  {instMsg && !instMsg.startsWith('✗') && (
                    <span className="text-xs text-slate-600 truncate">{instMsg}</span>
                  )}
                </div>
                {Object.keys(instInputs).length > 0 && (
                  <div className="space-y-1">
                    {Object.entries(instInputs).map(([k, v]) => (
                      <div key={k} className="flex items-center gap-1.5">
                        <span className="text-xs font-mono text-slate-500 flex-1 truncate" title={k}>{k}</span>
                        <input
                          type="number"
                          step="any"
                          value={v}
                          onChange={e => {
                            const n = parseFloat(e.target.value)
                            if (!isNaN(n)) setInstInputs(prev => ({ ...prev, [k]: n }))
                          }}
                          className={clsx(
                            'w-20 text-xs font-mono text-right rounded px-1.5 py-0.5 border bg-dark-900 focus:outline-none focus:ring-1 focus:ring-blue-500/50',
                            v > 0 ? 'text-emerald-400 border-emerald-800/60'
                                  : v < 0 ? 'text-red-400 border-red-800/60'
                                           : 'text-slate-400 border-slate-700/60'
                          )}
                        />
                      </div>
                    ))}
                  </div>
                )}
              </div>
            )}

            {/* Analyze + open in CausalLab */}
            <div className="flex gap-1.5">
              <button
                onClick={runAnalysis}
                disabled={!selTmpl || loadingAn}
                className="flex-1 flex items-center justify-center gap-1.5 text-xs bg-blue-700/50 hover:bg-blue-700/70 border border-blue-700/40 text-blue-200 rounded px-3 py-1.5 disabled:opacity-40"
              >
                {loadingAn ? <Loader2 size={10} className="animate-spin" /> : null}
                {loadingAn ? 'Analyse…' : 'Lancer l\'analyse'}
              </button>
              <button
                onClick={() => navigate(`/causal-lab?template=${selTmpl || ''}`)}
                className="text-xs text-slate-500 hover:text-blue-400 px-2 py-1.5 rounded hover:bg-slate-700/30 transition-colors"
                title="Ouvrir dans CausalLab"
              >
                <ExternalLink size={12} />
              </button>
            </div>

            {/* Bandeau de détail du calcul réel */}
            {anResult && (anResult.pricesMode || anResult.drift) && (() => {
              const mode    = anResult.pricesMode
              const lagMin  = anResult.lagMin ?? 0
              const lagDays = anResult.lagDays ?? 0
              const driftEntries = Object.entries(anResult.drift ?? {})
              const dbg = anResult.lagDebug
              return (
                <div className="bg-slate-900/70 border border-slate-700/40 rounded px-3 py-2 space-y-1.5 text-[10px] font-mono">
                  <div className="flex items-center gap-2 flex-wrap">
                    <span className={clsx('px-1.5 py-0.5 rounded font-semibold',
                      mode === 'intraday_5m' ? 'bg-cyan-900/40 text-cyan-400 border border-cyan-700/40'
                                             : 'bg-amber-900/40 text-amber-400 border border-amber-700/40')}>
                      {mode === 'intraday_5m' ? '5m intraday' : mode === 'daily' ? 'journalier' : mode ?? '—'}
                    </span>
                    {mode === 'intraday_5m' && lagMin > 0 && (
                      <span className="text-slate-400">lag <span className="text-cyan-300">{lagMin}min</span></span>
                    )}
                    {mode !== 'intraday_5m' && lagDays > 0 && (
                      <span className="text-slate-400">lag <span className="text-amber-300">{lagDays}j</span></span>
                    )}
                    {mode !== 'intraday_5m' && lagDays === 0 && (
                      <span className="text-slate-600 italic">lag J = 0 (jour J)</span>
                    )}
                    {dbg && dbg.used_all_edges && (
                      <span className="text-yellow-500/80 italic">⚠ fallback edges (output_ids vides)</span>
                    )}
                    {dbg && (
                      <span className="text-slate-600">
                        edges→out: <span className="text-slate-400">{dbg.edges_to_output}</span>
                        {' '}| edges lag_j: <span className="text-slate-400">{dbg.edges_with_lag_days}</span>
                      </span>
                    )}
                  </div>
                  {driftEntries.map(([inst, d]) => (
                    <div key={inst} className="flex items-center gap-2 flex-wrap border-t border-slate-800/60 pt-1.5">
                      <span className="text-slate-400 w-16 truncate">{inst}</span>
                      {d.pre_pips != null && (
                        <span className="text-slate-500">
                          pré: <span className={d.pre_pips > 0 ? 'text-emerald-500' : 'text-red-500'}>{d.pre_pips > 0 ? '+' : ''}{d.pre_pips}p</span>
                        </span>
                      )}
                      <span className="text-slate-500">
                        post: <span className={d.post_pips == null ? 'text-slate-600' : d.post_pips > 0 ? 'text-emerald-400' : 'text-red-400'}>
                          {d.post_pips == null ? '—' : `${d.post_pips > 0 ? '+' : ''}${d.post_pips}p`}
                        </span>
                      </span>
                      {d.post_pips == null && (
                        <span className="text-red-500 italic">données manquantes (yfinance)</span>
                      )}
                    </div>
                  ))}
                </div>
              )
            })()}

            {/* Inline result — full-width graph + left recap */}
            {anResult && graphData?.nodes?.length && (() => {
              const inputNodes  = graphData.nodes.filter(n => n.type === 'input')
              const outputNodes = graphData.nodes.filter(n => n.type === 'market_asset' || n.type === 'output')
              return (
                <div className="bg-slate-900/60 rounded p-2">
                  {/* Activation row */}
                  <div className="flex items-center gap-3 mb-2">
                    <span className="text-xs text-slate-500">Activation</span>
                    {anResult.score != null ? (
                      <span className={clsx(
                        'text-xs font-mono font-bold',
                        anResult.score >= 0.7 ? 'text-emerald-400' :
                        anResult.score >= 0.4 ? 'text-amber-400' : 'text-red-400'
                      )}>{Math.round(anResult.score * 100)}%</span>
                    ) : <span className="text-xs text-slate-500">données manquantes</span>}
                  </div>
                  {/* Side-by-side: recap left + graph right */}
                  <div className="flex gap-3">
                    {/* ── Recap panel ── */}
                    <div className="w-40 shrink-0 space-y-3 text-xs">
                      {inputNodes.length > 0 && (
                        <div>
                          <div className="text-[10px] text-slate-600 uppercase tracking-wide mb-1">Entrées</div>
                          {inputNodes.map(n => {
                            const val = anResult.preds[n.id] ?? instInputs[n.id]
                            return (
                              <div key={n.id} className="flex items-center justify-between gap-1 py-0.5 border-b border-slate-800/60 last:border-0">
                                <span className="text-slate-400 truncate max-w-[80px]" title={n.label}>{n.label}</span>
                                <span className={clsx('font-mono shrink-0', val == null ? 'text-slate-600' : val > 0 ? 'text-emerald-400' : val < 0 ? 'text-red-400' : 'text-slate-500')}>
                                  {val != null ? `${val > 0 ? '+' : ''}${val.toFixed(1)}` : '—'}
                                </span>
                              </div>
                            )
                          })}
                        </div>
                      )}
                      {outputNodes.length > 0 && (
                        <div>
                          <div className="text-[10px] text-slate-600 uppercase tracking-wide mb-1">Prédictions</div>
                          {(() => {
                            const nodeScores = outputNodes
                              .map(n => {
                                const pred = anResult.preds[n.id]
                                const act = anResult.actuals[n.instrument || n.id] ?? anResult.actuals[n.id]
                                if (pred == null || act == null) return null
                                if (pred === 0 && act === 0) return 100
                                if (pred === 0) return 40
                                const sameDir = pred * act > 0
                                if (!sameDir) return 0
                                const ratio = Math.min(Math.abs(act), Math.abs(pred)) / Math.max(Math.abs(act), Math.abs(pred))
                                return Math.round(50 + ratio * 50)
                              })
                              .filter((s): s is number => s != null)
                            const graphScore = nodeScores.length > 0
                              ? Math.round(nodeScores.reduce((a, b) => a + b, 0) / nodeScores.length)
                              : null
                            if (graphScore == null) return null
                            const barCls = graphScore >= 70 ? 'bg-emerald-500' : graphScore >= 40 ? 'bg-amber-500' : 'bg-red-500'
                            const txtCls = graphScore >= 70 ? 'text-emerald-400' : graphScore >= 40 ? 'text-amber-400' : 'text-red-400'
                            return (
                              <div className="mb-2 pb-2 border-b border-slate-800/60">
                                <div className="flex items-center justify-between text-[9px] text-slate-600 mb-0.5">
                                  <span>Précision</span>
                                  <span className={txtCls}>{graphScore}%</span>
                                </div>
                                <div className="h-1 bg-slate-800 rounded-full overflow-hidden">
                                  <div className={clsx('h-full rounded-full', barCls)} style={{ width: `${graphScore}%` }} />
                                </div>
                              </div>
                            )
                          })()}
                          {outputNodes.map(n => {
                            const pred = anResult.preds[n.id]
                            const actual = anResult.actuals[n.instrument || n.id] ?? anResult.actuals[n.id]
                            return (
                              <div key={n.id} className="py-0.5 border-b border-slate-800/60 last:border-0">
                                <div className="flex items-center justify-between gap-1">
                                  <span className="text-slate-400 truncate max-w-[70px]" title={n.label}>{n.label}</span>
                                  <span className={clsx('font-mono shrink-0', pred == null ? 'text-slate-600' : pred > 0 ? 'text-emerald-400' : pred < 0 ? 'text-red-400' : 'text-slate-500')}>
                                    {pred != null ? `${pred > 0 ? '+' : ''}${Math.round(pred)}${n.unit === 'pips' ? 'p' : n.unit === '%' ? '%' : ''}` : '—'}
                                  </span>
                                </div>
                                {actual != null && (
                                  <div className="flex items-center justify-between gap-1 mt-0.5 opacity-70">
                                    <span className="text-slate-600 text-[10px]">réel</span>
                                    <span className={clsx('font-mono text-[10px] shrink-0', actual > 0 ? 'text-emerald-300' : 'text-red-300')}>
                                      {actual > 0 ? '+' : ''}{Math.round(actual)}p
                                    </span>
                                  </div>
                                )}
                              </div>
                            )
                          })}
                        </div>
                      )}
                    </div>
                    {/* ── Graph ── */}
                    <div className="flex-1 min-w-0">
                      <CausalGraphMini
                        nodes={graphData.nodes}
                        edges={graphData.edges}
                        nodeValues={anResult.preds}
                      />
                    </div>
                  </div>
                </div>
              )
            })()}
            {anResult && !graphData?.nodes?.length && (
              <div className="bg-slate-900/60 rounded p-2 space-y-1">
                <div className="flex items-center gap-2">
                  <span className="text-xs text-slate-500">Activation</span>
                  {anResult.score != null ? (
                    <span className={clsx('text-xs font-mono font-bold',
                      anResult.score >= 0.7 ? 'text-emerald-400' : anResult.score >= 0.4 ? 'text-amber-400' : 'text-red-400'
                    )}>{Math.round(anResult.score * 100)}%</span>
                  ) : <span className="text-xs text-slate-500">données manquantes</span>}
                </div>
                {Object.entries(anResult.actuals).map(([inst, move]) => {
                  const pred = Object.entries(anResult.preds).find(([k]) => k.toLowerCase().includes(inst.toLowerCase()))?.[1]
                  return (
                    <div key={inst} className="flex items-center justify-between gap-2 text-xs">
                      <span className="font-mono text-slate-400">{inst}</span>
                      {pred !== undefined && <span className={clsx('font-mono', pred > 0 ? 'text-blue-400' : 'text-orange-400')}>pred {pred > 0 ? '+' : ''}{Math.round(pred)}p</span>}
                      <span className={clsx('font-mono font-bold ml-auto', move > 0 ? 'text-emerald-400' : 'text-red-400')}>{move > 0 ? '+' : ''}{Math.round(move)}p</span>
                    </div>
                  )
                })}
              </div>
            )}

            {templates.length === 0 && !noTemplateFound && (
              <p className="text-xs text-slate-600 italic">
                Aucun template disponible —{' '}
                <button onClick={() => navigate('/causal-lab')} className="text-blue-500 hover:text-blue-400 underline">
                  créer dans CausalLab
                </button>
              </p>
            )}
          </div>
        </div>
      </div>
    </div>
  )
}

// ── Create event modal ────────────────────────────────────────────────────────

function CreateModal({ onCreated, onClose }: { onCreated: () => void; onClose: () => void }) {
  const [form, setForm] = useState({
    name: '', start_date: new Date().toISOString().slice(0, 10),
    level: 'short', description: '', impact_score: 0.5,
  })
  const [saving, setSaving] = useState(false)

  const submit = async () => {
    if (!form.name || !form.start_date) return
    setSaving(true)
    await fetch('/api/market-events', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ ...form, source_refs: [] }),
    })
    setSaving(false)
    onCreated()
    onClose()
  }

  const f = (k: string) => (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) =>
    setForm(d => ({ ...d, [k]: e.target.value }))

  return (
    <div className="fixed inset-0 bg-black/60 z-50 flex items-center justify-center">
      <div className="bg-dark-800 border border-slate-700/50 rounded-xl w-full max-w-lg shadow-2xl">
        <div className="flex items-center justify-between px-5 py-4 border-b border-slate-700/40">
          <h3 className="text-slate-100 font-semibold">Nouvel événement marché</h3>
          <button onClick={onClose} className="text-slate-500 hover:text-slate-300"><X size={16} /></button>
        </div>
        <div className="p-5 space-y-3">
          <input value={form.name} onChange={f('name')} placeholder="Nom de l'événement *"
            className="w-full bg-dark-900 border border-slate-700 rounded px-3 py-2 text-slate-200 text-sm" />
          <div className="grid grid-cols-2 gap-3">
            <div>
              <div className="text-xs text-slate-500 mb-1">Date début</div>
              <input type="date" value={form.start_date} onChange={f('start_date')}
                className="w-full bg-dark-900 border border-slate-700 rounded px-3 py-2 text-slate-200 text-sm" />
            </div>
            <div>
              <div className="text-xs text-slate-500 mb-1">Niveau</div>
              <select value={form.level} onChange={f('level')}
                className="w-full bg-dark-900 border border-slate-700 rounded px-3 py-2 text-slate-200 text-sm">
                {LEVELS.map(l => <option key={l} value={l}>{l}</option>)}
              </select>
            </div>
          </div>
          <textarea value={form.description} onChange={f('description')} placeholder="Description..." rows={3}
            className="w-full bg-dark-900 border border-slate-700 rounded px-3 py-2 text-slate-200 text-sm resize-none" />
          <div>
            <div className="text-xs text-slate-500 mb-1">Score impact: {form.impact_score.toFixed(2)}</div>
            <input type="range" min="0" max="1" step="0.05" value={form.impact_score}
              onChange={e => setForm(d => ({ ...d, impact_score: +e.target.value }))}
              className="w-full accent-amber-400" />
          </div>
        </div>
        <div className="flex gap-2 px-5 pb-5">
          <button onClick={submit} disabled={saving || !form.name}
            className="flex items-center gap-2 bg-emerald-700 hover:bg-emerald-600 disabled:bg-slate-700 disabled:text-slate-500 text-white rounded-lg px-5 py-2 text-sm font-medium">
            {saving ? <Loader2 size={14} className="animate-spin" /> : <Plus size={14} />} Créer
          </button>
          <button onClick={onClose} className="text-sm text-slate-500 hover:text-slate-300 px-4">Annuler</button>
        </div>
      </div>
    </div>
  )
}

// ── Event list row ────────────────────────────────────────────────────────────

function EventRow({
  ev, selected, onClick,
}: { ev: MarketEvent; selected: boolean; onClick: () => void }) {
  return (
    <div
      onClick={onClick}
      className={clsx(
        'flex items-center gap-3 px-4 py-3 cursor-pointer border-b border-slate-800/50 hover:bg-slate-800/40 transition-colors',
        selected && 'bg-slate-800/60 border-l-2 border-l-blue-500',
      )}
    >
      <span className="w-1.5 h-1.5 rounded-full shrink-0 bg-slate-500" />
      <div className="flex-1 min-w-0">
        <div className="text-sm text-slate-200 truncate font-medium">{ev.name}</div>
        <div className="flex items-center gap-2 mt-0.5">
          <span className="text-xs text-slate-500">{ev.start_date}</span>
          {ev.sub_type && <span className="text-xs text-slate-600">{ev.sub_type}</span>}
          {ev.origin && (
            <span className="text-xs shrink-0" title={(ORIGIN_META[ev.origin] ?? ORIGIN_META['unknown']).description}>
              {(ORIGIN_META[ev.origin] ?? ORIGIN_META['unknown']).icon}
            </span>
          )}
          {ev.created_at && (
            <span className="text-[10px] text-slate-700 shrink-0" title={`Généré le ${ev.created_at}`}>
              ⚡{ev.created_at.slice(5, 10)}
            </span>
          )}
        </div>
      </div>
      <div className="shrink-0 text-right">
        <div className="text-xs font-bold" style={{ color: ev.impact_score >= 0.7 ? '#ef4444' : ev.impact_score >= 0.5 ? '#f59e0b' : '#10b981' }}>
          {ev.impact_score.toFixed(2)}
        </div>
        <div className={clsx('text-xs', LEVEL_COLORS[ev.level])}>{ev.level}</div>
      </div>
    </div>
  )
}

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

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

export default function MarketEvents() {
  const [searchParams]              = useSearchParams()
  const [events, setEvents]         = useState<MarketEvent[]>([])
  const [total, setTotal]           = useState(0)
  const [loading, setLoading]       = useState(false)
  const [selected, setSelected]     = useState<MarketEvent | null>(null)
  const [showCreate, setShowCreate] = useState(false)
  const [bulkIds, setBulkIds]       = useState<Set<number>>(new Set())
  const [bulking, setBulking]       = useState(false)
  const [bulkMsg, setBulkMsg]       = useState<string | null>(null)

  // ── Filter state ─────────────────────────────────────────────────────────
  const [search,     setSearch]     = useState('')
  const [category,   setCategory]   = useState('')
  const [level,      setLevel]      = useState('')
  const [minScore,   setMinScore]   = useState(0)
  const [evaluated,  setEvaluated]  = useState<'' | 'true' | 'false'>('')
  const [dateFrom,   setDateFrom]   = useState('')
  const [dateTo,     setDateTo]     = useState('')
  const [datePreset, setDatePreset] = useState<string>('all')   // 7d|30d|90d|6m|1y|all|custom
  // Generation date filter (created_at)
  const [genFrom,    setGenFrom]    = useState('')
  const [genTo,      setGenTo]      = useState('')
  // Instrument impact filter
  const [instTicker, setInstTicker] = useState('')
  const [instMinScore, setInstMin]  = useState(0.3)
  const [instDir,    setInstDir]    = useState('')
  const [showInstFilter, setShowInst] = useState(false)
  // Sort
  const [sortBy,  setSortBy]        = useState<'date'|'score'|'name'|'instrument_score'>('date')
  const [sortDir, setSortDir]       = useState<'desc'|'asc'>('desc')
  // UI
  const [showFilters, setShowFilters] = useState(false)

  // ── Date preset helper ────────────────────────────────────────────────────
  const applyPreset = useCallback((preset: string) => {
    setDatePreset(preset)
    if (preset === 'custom' || preset === 'all') {
      setDateFrom(''); setDateTo('')
      return
    }
    const now = new Date()
    const to  = now.toISOString().slice(0, 10)
    const from = new Date(now)
    if (preset === '7d')  from.setDate(now.getDate() - 7)
    if (preset === '30d') from.setDate(now.getDate() - 30)
    if (preset === '90d') from.setDate(now.getDate() - 90)
    if (preset === '6m')  from.setMonth(now.getMonth() - 6)
    if (preset === '1y')  from.setFullYear(now.getFullYear() - 1)
    setDateFrom(from.toISOString().slice(0, 10))
    setDateTo(to)
  }, [])

  const debounceRef  = useRef<ReturnType<typeof setTimeout> | null>(null)
  const abortRef     = useRef<AbortController | null>(null)

  const load = useCallback(async () => {
    // Cancel any in-flight fetch
    abortRef.current?.abort()
    abortRef.current = new AbortController()
    const signal = abortRef.current.signal

    setLoading(true)
    const params = new URLSearchParams()
    if (search)    params.set('search', search)
    if (category)  params.set('category', category)
    if (level)     params.set('level', level)
    if (minScore)  params.set('min_score', String(minScore))
    if (dateFrom)  params.set('date_from', dateFrom)
    if (dateTo)    params.set('date_to', dateTo)
    if (genFrom)   params.set('gen_date_from', genFrom)
    if (genTo)     params.set('gen_date_to', genTo)
    if (evaluated) params.set('evaluated', evaluated)
    if (instTicker) {
      params.set('instrument', instTicker)
      if (instMinScore > 0) params.set('min_instrument_score', String(instMinScore))
      if (instDir)          params.set('instrument_direction', instDir)
    }
    params.set('sort_by',  instTicker ? 'instrument_score' : sortBy)
    params.set('sort_dir', sortDir)
    params.set('limit', '500')
    try {
      const r = await fetch(`/api/market-events?${params}`, { signal })
      if (r.ok) {
        const d = await r.json()
        setEvents(d.events)
        setTotal(d.total)
      }
    } catch (e: any) {
      if (e.name === 'AbortError') return  // superseded by a newer load
    }
    setLoading(false)
  }, [search, category, level, minScore, dateFrom, dateTo, genFrom, genTo, evaluated,
      instTicker, instMinScore, instDir, sortBy, sortDir])

  useEffect(() => {
    if (debounceRef.current) clearTimeout(debounceRef.current)
    debounceRef.current = setTimeout(load, 250)
  }, [load])

  // Deep-link: ?event=ID — select that event once list is loaded
  useEffect(() => {
    const eventId = searchParams.get('event')
    if (!eventId || !events.length) return
    const found = events.find(e => e.id === parseInt(eventId))
    if (found && (!selected || selected.id !== found.id)) setSelected(found)
  }, [events, searchParams])

  const bulkEvaluate = async () => {
    if (bulkIds.size === 0) return
    setBulking(true)
    setBulkMsg(null)
    const r = await fetch('/api/market-events/bulk-evaluate', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify([...bulkIds]),
    })
    const d = await r.json()
    setBulkMsg(`✓ ${d.evaluated} évalués${d.errors > 0 ? `, ${d.errors} erreurs` : ''}`)
    setBulkIds(new Set())
    setBulking(false)
    load()
  }

  const unevaluatedIds = events.filter(e => !e.evaluated).map(e => e.id)

  return (
    <div className="flex flex-col h-full overflow-hidden">
      <div className="flex flex-1 overflow-hidden">
      {/* ── Left panel: filters + list ── */}
      <div className="w-96 shrink-0 flex flex-col border-r border-slate-700/40 overflow-hidden">
        {/* ── Search + actions ── */}
        <div className="px-3 pt-3 pb-2 border-b border-slate-700/30 space-y-2">
          <div className="flex items-center gap-2">
            <div className="relative flex-1">
              <Search size={13} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-500" />
              <input value={search} onChange={e => setSearch(e.target.value)}
                placeholder="Recherche nom, description..."
                className="w-full bg-slate-800/60 border border-slate-700/50 rounded-lg pl-8 pr-3 py-1.5 text-sm text-slate-200 placeholder:text-slate-600" />
            </div>
            <button onClick={() => setShowCreate(true)}
              className="flex items-center gap-1 bg-emerald-700 hover:bg-emerald-600 text-white text-xs rounded-lg px-2 py-1.5 shrink-0">
              <Plus size={12} />
            </button>
            <button onClick={load} disabled={loading}
              className="p-1.5 text-slate-500 hover:text-slate-300 rounded shrink-0">
              <RefreshCw size={13} className={loading ? 'animate-spin' : ''} />
            </button>
          </div>

          {/* ── Date presets ── */}
          <div className="flex items-center gap-1 flex-wrap">
            <span className="text-xs text-slate-600 mr-1">Période:</span>
            {[['7d','7j'],['30d','30j'],['90d','3m'],['6m','6m'],['1y','1an'],['all','Tout'],['custom','Perso']] .map(([v, lbl]) => (
              <button key={v} onClick={() => applyPreset(v)}
                className={clsx('px-2 py-0.5 rounded text-xs border transition-colors',
                  datePreset === v
                    ? 'bg-blue-600/30 border-blue-500/50 text-blue-300'
                    : 'border-slate-800 text-slate-600 hover:border-slate-600 hover:text-slate-400')}>
                {lbl}
              </button>
            ))}
          </div>
          {datePreset === 'custom' && (
            <div className="grid grid-cols-2 gap-2">
              <div>
                <div className="text-xs text-slate-600 mb-0.5">Depuis</div>
                <input type="date" value={dateFrom} onChange={e => setDateFrom(e.target.value)}
                  className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-300 text-xs" />
              </div>
              <div>
                <div className="text-xs text-slate-600 mb-0.5">Jusqu'à</div>
                <input type="date" value={dateTo} onChange={e => setDateTo(e.target.value)}
                  className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-300 text-xs" />
              </div>
            </div>
          )}

        </div>

        {/* ── Quick category filter ── */}
        <div className="px-3 py-1.5 border-b border-slate-800/40 flex items-center gap-1 flex-wrap">
          {([
            { key: '',               label: 'Tout',    dot: 'bg-slate-500'  },
            { key: 'event_calendar', label: '★ Cal.',  dot: 'bg-amber-500'  },
            { key: 'geopolitical',   label: '★ Géo.',  dot: 'bg-red-500'    },
            { key: 'fundamental',    label: '★ Fund.', dot: 'bg-emerald-500' },
            { key: 'report',         label: '★ Rep.',  dot: 'bg-blue-500'   },
            { key: 'sentiment',      label: '★ Sent.', dot: 'bg-violet-500' },
            { key: 'technical',      label: '★ Tech.', dot: 'bg-cyan-500'   },
          ] as const).map(({ key, label, dot }) => (
            <button
              key={key}
              onClick={() => setCategory(key)}
              className={clsx(
                'flex items-center gap-1 px-2 py-0.5 rounded text-[10px] border transition-colors',
                category === key
                  ? 'bg-slate-700/80 border-slate-500/60 text-slate-200'
                  : 'border-slate-800/60 text-slate-600 hover:border-slate-600 hover:text-slate-400'
              )}
            >
              <span className={clsx('w-1.5 h-1.5 rounded-full shrink-0', dot)} />
              {label}
            </button>
          ))}
        </div>

        {/* ── Sort + more filters toggle ── */}
        <div className="px-3 py-1.5 border-b border-slate-800/40 flex items-center gap-2">
          <span className="text-xs text-slate-600">Tri:</span>
          <select value={sortBy} onChange={e => setSortBy(e.target.value as any)}
            className="bg-transparent border-0 text-xs text-slate-400 cursor-pointer focus:outline-none">
            <option value="date">Date</option>
            <option value="score">Score global</option>
            <option value="name">Nom</option>
          </select>
          <button onClick={() => setSortDir(d => d === 'desc' ? 'asc' : 'desc')}
            className="text-xs text-slate-500 hover:text-slate-300 border border-slate-700 rounded px-1.5 py-0.5">
            {sortDir === 'desc' ? '↓' : '↑'}
          </button>
          <div className="flex-1" />
          <button onClick={() => setShowFilters(x => !x)}
            className={clsx('flex items-center gap-1 text-xs border rounded px-2 py-0.5 transition-colors',
              (showFilters || instTicker)
                ? 'bg-slate-700 border-slate-600 text-slate-300'
                : 'border-slate-800 text-slate-600 hover:border-slate-600')}>
            <SlidersHorizontal size={10} />
            Filtres {(category || level || minScore > 0 || evaluated || instTicker) ? '●' : ''}
          </button>
        </div>

        {/* ── Extended filters ── */}
        {showFilters && (
          <div className="px-3 py-2 border-b border-slate-800/40 space-y-2 bg-dark-900/50">
            <div className="grid grid-cols-2 gap-2">
              <div>
                <div className="text-xs text-slate-600 mb-0.5">Niveau</div>
                <select value={level} onChange={e => setLevel(e.target.value)}
                  className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-300 text-xs">
                  <option value="">Tous</option>
                  {LEVELS.map(l => <option key={l} value={l}>{l}</option>)}
                </select>
              </div>
              <div>
                <div className="text-xs text-slate-600 mb-0.5">Évaluation</div>
                <select value={evaluated} onChange={e => setEvaluated(e.target.value as any)}
                  className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-300 text-xs">
                  <option value="">Tous</option>
                  <option value="true">Évalués IA</option>
                  <option value="false">Non évalués</option>
                </select>
              </div>
            </div>
            <div>
              <div className="text-xs text-slate-600 mb-0.5">Score global min: <span className="text-slate-400">{minScore.toFixed(2)}</span></div>
              <input type="range" min="0" max="1" step="0.05" value={minScore}
                onChange={e => setMinScore(+e.target.value)} className="w-full accent-slate-400" />
            </div>

            {/* ── Instrument impact filter ── */}
            <div className="border-t border-slate-800/60 pt-2">
              <button onClick={() => setShowInst(x => !x)}
                className={clsx('flex items-center gap-1.5 text-xs font-semibold mb-2 transition-colors',
                  instTicker ? 'text-amber-400' : 'text-slate-500 hover:text-slate-300')}>
                <BarChart2 size={11} />
                Impact sur instrument {instTicker ? `(${instTicker})` : ''}
                {showInstFilter ? ' ▲' : ' ▼'}
              </button>
              {showInstFilter && (
                <div className="space-y-2">
                  <div className="grid grid-cols-2 gap-2">
                    <div>
                      <div className="text-xs text-slate-600 mb-0.5">Instrument</div>
                      <select value={instTicker} onChange={e => { setInstTicker(e.target.value); if (e.target.value) setSortBy('instrument_score') }}
                        className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-300 text-xs">
                        <option value="">Tous</option>
                        {ALL_INSTRUMENTS.map(t => <option key={t} value={t}>{t}</option>)}
                      </select>
                    </div>
                    <div>
                      <div className="text-xs text-slate-600 mb-0.5">Direction</div>
                      <select value={instDir} onChange={e => setInstDir(e.target.value)}
                        className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-300 text-xs">
                        <option value="">Toutes</option>
                        <option value="bullish">🔼 Bullish</option>
                        <option value="bearish">🔽 Bearish</option>
                        <option value="neutral">● Neutre</option>
                        <option value="depends_on_outcome">◆ Conditionnel</option>
                      </select>
                    </div>
                  </div>
                  <div>
                    <div className="text-xs text-slate-600 mb-0.5">
                      Score impact min: <span className="text-amber-300">{instMinScore.toFixed(2)}</span>
                    </div>
                    <input type="range" min="0" max="1" step="0.05" value={instMinScore}
                      onChange={e => setInstMin(+e.target.value)} className="w-full accent-amber-400" />
                  </div>
                  {instTicker && (
                    <div className="text-xs text-amber-300 bg-amber-900/20 rounded px-2 py-1 border border-amber-800/30">
                      ★ Tri automatique par impact sur {instTicker}
                    </div>
                  )}
                  {instTicker && (
                    <button onClick={() => { setInstTicker(''); setInstDir(''); setInstMin(0.3); setSortBy('date') }}
                      className="text-xs text-slate-500 hover:text-red-400">
                      × Effacer filtre instrument
                    </button>
                  )}
                </div>
              )}
            </div>

            {/* ── Generation date filter ── */}
            <div className="border-t border-slate-800/60 pt-2">
              <div className="flex items-center gap-1.5 text-xs font-semibold mb-2 text-slate-500">
                ⚡ Date de génération
                {(genFrom || genTo) && (
                  <button onClick={() => { setGenFrom(''); setGenTo('') }}
                    className="ml-auto text-slate-600 hover:text-red-400 font-normal">× effacer</button>
                )}
              </div>
              <div className="grid grid-cols-2 gap-2">
                <div>
                  <div className="text-xs text-slate-600 mb-0.5">Généré depuis</div>
                  <input type="date" value={genFrom} onChange={e => setGenFrom(e.target.value)}
                    className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-300 text-xs" />
                </div>
                <div>
                  <div className="text-xs text-slate-600 mb-0.5">Généré jusqu'à</div>
                  <input type="date" value={genTo} onChange={e => setGenTo(e.target.value)}
                    className="w-full bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-300 text-xs" />
                </div>
              </div>
              {(genFrom || genTo) && (
                <div className="text-xs text-blue-300 bg-blue-900/20 rounded px-2 py-1 border border-blue-800/30 mt-1.5">
                  ⚡ Filtre actif sur date de génération (created_at)
                </div>
              )}
            </div>
          </div>
        )}

        {/* ── Bulk evaluate ── */}
        {unevaluatedIds.length > 0 && (
          <div className="px-3 py-1.5 border-b border-slate-800/50 flex items-center gap-2 bg-blue-900/10">
            <span className="text-xs text-blue-300 flex-1">{unevaluatedIds.length} non évalués</span>
            {bulkMsg && <span className="text-xs text-emerald-400">{bulkMsg}</span>}
            <button onClick={() => { setBulkIds(new Set(unevaluatedIds)); setTimeout(bulkEvaluate, 50) }}
              disabled={bulking}
              className="flex items-center gap-1 text-xs bg-blue-700/50 hover:bg-blue-700/70 text-blue-200 rounded px-2 py-1">
              {bulking ? <Loader2 size={10} className="animate-spin" /> : <Sparkles size={10} />}
              Évaluer tous
            </button>
          </div>
        )}

        {/* ── Count bar ── */}
        <div className="px-3 py-1 text-xs text-slate-600 border-b border-slate-800/30 flex items-center gap-2">
          <span>{loading ? '...' : total} événement{total !== 1 ? 's' : ''}</span>
          {instTicker && <span className="text-amber-400">· impact {instTicker}</span>}
          {(level || search || minScore > 0 || evaluated || dateFrom || dateTo || genFrom || genTo || instTicker) && (
            <button onClick={() => {
              setSearch(''); setLevel(''); setMinScore(0)
              setEvaluated(''); setDateFrom(''); setDateTo(''); setDatePreset('all')
              setGenFrom(''); setGenTo('')
              setInstTicker(''); setInstDir(''); setInstMin(0.3); setSortBy('date')
            }} className="ml-auto text-xs text-slate-600 hover:text-red-400">× tout effacer</button>
          )}
        </div>

        {/* ── List ── */}
        <div className="flex-1 overflow-y-auto">
          {loading && events.length === 0 ? (
            <div className="flex items-center justify-center h-32 text-slate-500 text-sm">
              <Loader2 size={16} className="animate-spin mr-2" /> Chargement...
            </div>
          ) : events.length === 0 ? (
            <div className="text-center py-12 text-slate-600 text-sm">Aucun événement trouvé</div>
          ) : (
            events.map(ev => (
              <EventRow
                key={ev.id}
                ev={ev}
                selected={selected?.id === ev.id}
                onClick={() => setSelected(ev)}
              />
            ))
          )}
        </div>
      </div>

      {/* ── Right panel: detail ── */}
      <div className="flex-1 overflow-hidden bg-dark-900/40">
        {selected ? (
          <EventDetail
            key={selected.id}
            event={selected}
            onClose={() => setSelected(null)}
            onUpdated={updated => {
              setEvents(evs => evs.map(e => e.id === updated.id ? { ...e, ...updated } : e))
            }}
            onDeleted={id => {
              setEvents(evs => evs.filter(e => e.id !== id))
              setSelected(null)
            }}
          />
        ) : (
          <div className="flex flex-col items-center justify-center h-full text-slate-600 gap-3">
            <BarChart2 size={40} className="opacity-30" />
            <div className="text-sm">Sélectionne un événement pour voir le détail</div>
            <div className="text-xs opacity-60">Sources · Impacts par instrument · Analyse IA</div>
          </div>
        )}
      </div>

      {showCreate && <CreateModal onCreated={load} onClose={() => setShowCreate(false)} />}
      </div>
    </div>
  )
}
