/**
 * InstrumentModels — graphes causaux exhaustifs par instrument.
 * Architecture 3 couches : inputs (events + manuels) → intermédiaires → output
 * Vues : DAG visuel | Tableau éditable | Timeline évolution
 */
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
import {
  RefreshCw, Edit3, X, Trash2, ChevronDown, ChevronUp,
  TrendingUp, TrendingDown, Minus, LineChart, Table2, Network, Activity,
  Plus, Zap,
} from 'lucide-react'
import clsx from 'clsx'
import axios from 'axios'

const api = axios.create({ baseURL: '/api' })

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

type NodeType = 'input_event' | 'input_manual' | 'intermediate' | 'output'

interface ModelNode {
  id: string
  label: string
  node_type: NodeType
  category: string
  unit: string
  description: string
  coefficient_to_pips?: number
  event_category?: string
  formula?: string
  display_col: number
  // computed
  computed_value: number
  pip_contribution: number
  raw_value?: number
  source: 'manual' | 'events' | 'neutral' | 'computed'
  override_note?: string
  override_set_at?: string
  // Phase 2
  pip_linear?: number
  pip_saturated?: number
  saturation_pct?: number
  regime_weight?: number
  // Phase 3 — event nodes: baseline (structurel) + surprise (événement)
  baseline_value?: number
  event_surprise?: number
}

interface RegimeInfo {
  regime: string
  label: string
  dominant_cat: string | null
  scores: Record<string, number>
  weights: Record<string, number>
}

interface ModelState {
  instrument: string
  name: string
  description: string
  at_date: string
  net_pips: number
  structural_pips: number
  event_pips: number
  price_intercept: number
  pip_to_price: number
  yf_ticker: string
  fundamental_level: number
  synthetic_price: number
  direction: 'bullish' | 'bearish' | 'neutral'
  nodes: ModelNode[]
  output_node: string
  regime: RegimeInfo
  event_details: Record<string, EventDetail[]>
}

interface TimelinePoint {
  date: string
  net_pips: number
  structural_pips: number
  event_pips: number
  fundamental_level: number
  synthetic_price: number
  regime?: string
  nodes: Record<string, number>
}

interface PricePoint {
  date: string
  close: number
}

interface VirtualEventForm {
  id: string
  date: string
  category: string
  pips: number
  label: string
  absorption_days: number
  rise_days: number
  plateau_days: number
  decay_type: string
}

interface EventDetail {
  analysis_id: number
  event_id: number
  title: string
  start_date: string
  days_since: number
  pip_prediction: number
  lifecycle_factor: number
  remaining_pips: number
  calibration: {
    absorption_days: number
    rise_days: number
    plateau_days: number
    decay_type: string
  }
}

interface CalendarEvent {
  analysis_id: number
  event_id: number
  title: string
  start_date: string
  category: string
  is_future: boolean
  days_offset: number
  pip_prediction: number
  lifecycle_factor: number
  remaining_pips: number
  calibration: {
    absorption_days: number
    rise_days: number
    plateau_days: number
    decay_type: string
  }
}

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

const INSTRUMENTS = ['EURUSD','USDJPY','XAUUSD','SP500','TLT','GBPUSD','EEM','QQQ']

const NODE_TYPE_META: Record<NodeType, { label: string; color: string; bg: string }> = {
  input_event:  { label: 'Events',        color: 'text-sky-400',     bg: 'bg-sky-900/30 border-sky-700/40'       },
  input_manual: { label: 'Manuel',        color: 'text-violet-400',  bg: 'bg-violet-900/30 border-violet-700/40' },
  intermediate: { label: 'Intermédiaire', color: 'text-amber-400',   bg: 'bg-amber-900/20 border-amber-700/40'   },
  output:       { label: 'Résultat',      color: 'text-emerald-400', bg: 'bg-emerald-900/20 border-emerald-700/40' },
}

const SOURCE_META: Record<string, { dot: string; label: string }> = {
  manual:   { dot: 'bg-violet-400', label: 'Override' },
  events:   { dot: 'bg-sky-400',    label: 'Events'   },
  neutral:  { dot: 'bg-slate-600',  label: 'Neutre'   },
  computed: { dot: 'bg-amber-400',  label: 'Calculé'  },
}

const PERIODS = ['5d','1mo','3mo','6mo','1y','2y'] as const
type Period = typeof PERIODS[number]

const LAYER_COLORS: Record<string, string> = {
  'net_pips':               'bg-emerald-500',
  'layer_monetary':         'bg-blue-400',
  'layer_rates':            'bg-blue-400',
  'layer_growth':           'bg-violet-400',
  'layer_uk_macro':         'bg-violet-400',
  'layer_risk':             'bg-red-400',
  'layer_risk_credit':      'bg-red-400',
  'layer_positioning':      'bg-orange-400',
  'layer_policy':           'bg-orange-400',
  'layer_flows':            'bg-orange-400',
  'layer_demand':           'bg-teal-400',
  'layer_refuge':           'bg-pink-400',
  'layer_tech_fundamental': 'bg-purple-400',
  'layer_supply':           'bg-slate-400',
  'layer_china':            'bg-yellow-400',
  'layer_dollar':           'bg-sky-400',
  'layer_global':           'bg-gray-400',
  'layer_macro':            'bg-lime-400',
}

const CANVAS_COLORS: Record<string, string> = {
  'net_pips':               '#10b981',
  'layer_monetary':         '#60a5fa',
  'layer_rates':            '#60a5fa',
  'layer_growth':           '#a78bfa',
  'layer_uk_macro':         '#a78bfa',
  'layer_risk':             '#f87171',
  'layer_risk_credit':      '#f87171',
  'layer_positioning':      '#fb923c',
  'layer_policy':           '#fb923c',
  'layer_flows':            '#fb923c',
  'layer_demand':           '#2dd4bf',
  'layer_refuge':           '#f472b6',
  'layer_tech_fundamental': '#c084fc',
  'layer_supply':           '#94a3b8',
  'layer_china':            '#fbbf24',
  'layer_dollar':           '#38bdf8',
  'layer_global':           '#cbd5e1',
  'layer_macro':            '#a3e635',
}

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

function pipColor(v: number) {
  if (v > 0.5)  return 'text-emerald-400'
  if (v < -0.5) return 'text-red-400'
  return 'text-slate-400'
}

function pipBg(v: number) {
  if (v > 0.5)  return 'bg-emerald-500'
  if (v < -0.5) return 'bg-red-500'
  return 'bg-slate-600'
}

function fmt(v: number) {
  const s = v > 0 ? '+' : ''
  return `${s}${v.toFixed(1)}`
}

function fmtPrice(v: number, pipToPrice: number): string {
  if (pipToPrice <= 0.0001) return v.toFixed(4)
  if (pipToPrice <= 0.01)   return v.toFixed(2)
  if (pipToPrice <= 0.1)    return v.toFixed(2)
  return v.toFixed(0)
}

const EVENT_CATEGORIES = [
  { value: 'central_bank',   label: 'Banque Centrale'       },
  { value: 'monetary_shock', label: 'Surprise Macro'        },
  { value: 'geopolitical',   label: 'Géopolitique'          },
  { value: 'trade_policy',   label: 'Commerce / Tarifs'     },
  { value: 'growth_shock',   label: 'Choc Croissance'       },
  { value: 'credit_stress',  label: 'Stress Crédit'         },
  { value: 'commodity',      label: 'Commodités'            },
  { value: 'sentiment',      label: 'Sentiment'             },
  { value: 'technical',      label: 'Technique'             },
  { value: 'unclassified',   label: 'Non classifié'         },
]

function DirectionBadge({ direction, pips }: { direction: string; pips: number }) {
  const icon =
    direction === 'bullish' ? <TrendingUp className="w-3.5 h-3.5"/> :
    direction === 'bearish' ? <TrendingDown className="w-3.5 h-3.5"/> :
    <Minus className="w-3.5 h-3.5"/>
  const cls =
    direction === 'bullish' ? 'text-emerald-400 border-emerald-700/50 bg-emerald-900/30' :
    direction === 'bearish' ? 'text-red-400 border-red-700/50 bg-red-900/30' :
    'text-slate-400 border-slate-700/40 bg-slate-800/50'
  return (
    <span className={clsx('inline-flex items-center gap-1.5 px-2.5 py-1 rounded border text-sm font-semibold', cls)}>
      {icon} {fmt(pips)} pips · {direction.toUpperCase()}
    </span>
  )
}

// ── Regime Badge ──────────────────────────────────────────────────────────────

const REGIME_META: Record<string, { color: string; bg: string; icon: string }> = {
  MONETARY_DOMINANCE: { color: 'text-blue-400',   bg: 'bg-blue-900/30 border-blue-700/40',   icon: '🏦' },
  GEOPOLITICAL_RISK:  { color: 'text-orange-400', bg: 'bg-orange-900/30 border-orange-700/40', icon: '⚠️' },
  CREDIT_STRESS:      { color: 'text-red-400',    bg: 'bg-red-900/30 border-red-700/40',      icon: '🔴' },
  GROWTH_SCARE:       { color: 'text-amber-400',  bg: 'bg-amber-900/30 border-amber-700/40',  icon: '📉' },
  COMMODITY_SHOCK:    { color: 'text-yellow-400', bg: 'bg-yellow-900/30 border-yellow-700/40','icon': '🛢️' },
  BALANCED:           { color: 'text-slate-400',  bg: 'bg-slate-800/50 border-slate-700/40',  icon: '⚖️' },
}

function RegimeBadge({ regime }: { regime: RegimeInfo }) {
  const meta = REGIME_META[regime.regime] || REGIME_META.BALANCED
  const topWeights = Object.entries(regime.weights)
    .filter(([, w]) => w > 1.0)
    .sort(([, a], [, b]) => b - a)
    .slice(0, 3)

  return (
    <div className={clsx('inline-flex flex-col gap-1 px-3 py-2 rounded-lg border text-xs', meta.bg)}>
      <div className="flex items-center gap-1.5">
        <span>{meta.icon}</span>
        <span className={clsx('font-semibold', meta.color)}>{regime.label}</span>
        {regime.dominant_cat && (
          <span className="text-slate-500">· {regime.dominant_cat}</span>
        )}
      </div>
      {topWeights.length > 0 && (
        <div className="flex gap-2 flex-wrap">
          {topWeights.map(([layer, w]) => (
            <span key={layer} className={clsx('font-mono', meta.color)}>
              {layer.replace('layer_', '')} ×{w.toFixed(2)}
            </span>
          ))}
        </div>
      )}
    </div>
  )
}

// ── Node Edit Modal ───────────────────────────────────────────────────────────

function NodeEditModal({ node, instrument, onClose, onSaved }: {
  node: ModelNode; instrument: string; onClose: () => void; onSaved: () => void
}) {
  const [val, setVal]       = useState<string>(node.raw_value !== undefined ? String(node.raw_value) : '')
  const [note, setNote]     = useState(node.override_note || '')
  const [saving, setSaving] = useState(false)
  const [clrg, setClrg]     = useState(false)

  const coeff   = node.coefficient_to_pips ?? 1.0
  const preview = node.node_type === 'input_manual'
    ? (parseFloat(val) || 0) * coeff
    : (parseFloat(val) || 0)

  async function save() {
    setSaving(true)
    try {
      await api.put(`/instrument-models/${instrument}/nodes/${node.id}/override`, {
        value: parseFloat(val) || 0, note
      })
      onSaved(); onClose()
    } finally { setSaving(false) }
  }

  async function clear() {
    setClrg(true)
    try {
      await api.delete(`/instrument-models/${instrument}/nodes/${node.id}/override`)
      onSaved(); onClose()
    } finally { setClrg(false) }
  }

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm"
      onClick={onClose}>
      <div className="bg-dark-800 border border-slate-700/60 rounded-xl shadow-2xl w-full max-w-md mx-4 p-6"
        onClick={e => e.stopPropagation()}>
        <div className="flex items-start justify-between mb-4">
          <div>
            <div className="text-white font-semibold text-sm">{node.label}</div>
            <div className="text-slate-500 text-xs mt-0.5 leading-relaxed max-w-xs">{node.description}</div>
          </div>
          <button onClick={onClose} className="text-slate-500 hover:text-white ml-2">
            <X className="w-4 h-4"/>
          </button>
        </div>

        <div className="space-y-3">
          <div>
            <label className="text-xs text-slate-400 mb-1 block">
              Valeur ({node.unit})
              {node.node_type === 'input_manual' && coeff !== 0 &&
                <span className="ml-1.5 text-slate-500">× {coeff} = <span className={pipColor(preview)}>{fmt(preview)} pips</span></span>}
              {node.node_type === 'input_event' && <span className="ml-1.5 text-slate-500">niveau structurel (les events s'y ajoutent)</span>}
            </label>
            <input
              type="number" step="any" value={val} onChange={e => setVal(e.target.value)}
              placeholder="0"
              className="w-full bg-dark-700 border border-slate-700/50 rounded px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500/60"
            />
          </div>
          <div>
            <label className="text-xs text-slate-400 mb-1 block">Note (optionnel)</label>
            <input
              type="text" value={note} onChange={e => setNote(e.target.value)}
              placeholder="Ex: NFP très fort, marché hawkish…"
              className="w-full bg-dark-700 border border-slate-700/50 rounded px-3 py-2 text-white text-sm focus:outline-none focus:border-blue-500/60"
            />
          </div>
        </div>

        {val !== '' && (
          <div className={clsx('mt-3 rounded px-3 py-2 text-sm font-semibold',
            preview > 0 ? 'bg-emerald-900/30 text-emerald-400'
            : preview < 0 ? 'bg-red-900/30 text-red-400'
            : 'bg-slate-800 text-slate-400')}>
            Impact estimé : {fmt(preview)} pips
          </div>
        )}

        <div className="flex gap-2 mt-4">
          <button onClick={save} disabled={saving || val === ''}
            className="flex-1 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white text-sm rounded px-3 py-2 font-medium transition-colors">
            {saving ? 'Enregistrement…' : 'Enregistrer'}
          </button>
          {node.source === 'manual' && (
            <button onClick={clear} disabled={clrg}
              className="flex items-center gap-1.5 bg-red-900/40 hover:bg-red-800/50 disabled:opacity-40 text-red-400 text-sm rounded px-3 py-2 transition-colors">
              <Trash2 className="w-3.5 h-3.5"/> {clrg ? '…' : 'Effacer'}
            </button>
          )}
          <button onClick={onClose} className="bg-dark-700 hover:bg-dark-600 text-slate-400 text-sm rounded px-3 py-2 transition-colors">
            Annuler
          </button>
        </div>
      </div>
    </div>
  )
}

// ── DAG View ──────────────────────────────────────────────────────────────────

const COL_LABELS = ['Events actifs', 'Variables manuelles', 'Couches intermédiaires', 'Résultat net']

function NodeCard({ node, onEdit, eventDetails }: {
  node: ModelNode; onEdit: (n: ModelNode) => void
  eventDetails?: Record<string, EventDetail[]>
}) {
  const meta    = NODE_TYPE_META[node.node_type]
  const v       = node.pip_contribution
  const canEdit = node.node_type === 'input_event' || node.node_type === 'input_manual'
  const isManual = node.node_type === 'input_manual'
  const hasValue = node.source === 'manual' && node.raw_value !== undefined && node.raw_value !== 0
  const coeff    = node.coefficient_to_pips ?? 1

  return (
    <div
      className={clsx(
        'relative rounded-lg border px-3 py-2 mb-1.5 transition-all',
        meta.bg,
        canEdit && 'cursor-pointer hover:border-blue-500/60 hover:shadow-md hover:shadow-blue-900/20',
        node.source === 'manual' && 'ring-1 ring-violet-500/30',
      )}
      onClick={() => canEdit && onEdit(node)}
      title={node.description}
    >
      {node.source === 'manual' && <span className="absolute top-1.5 right-1.5 w-1.5 h-1.5 rounded-full bg-violet-400"/>}
      {node.source === 'events' && <span className="absolute top-1.5 right-1.5 w-1.5 h-1.5 rounded-full bg-sky-400"/>}

      <div className="text-xs text-slate-300 font-medium leading-tight line-clamp-2 pr-3">{node.label}</div>

      {isManual ? (
        <div className="mt-1 flex items-center gap-1.5 flex-wrap">
          {hasValue ? (
            <span className={clsx('text-xs font-bold font-mono', pipColor(v))}>
              {node.raw_value! > 0 ? '+' : ''}{node.raw_value} {node.unit}
            </span>
          ) : (
            <span className="text-xs text-slate-600">— {node.unit}</span>
          )}
          <span className="text-xs text-slate-600 bg-slate-800/60 px-1.5 py-0.5 rounded font-mono">
            w:{coeff}
          </span>
          {hasValue && (
            <span className={clsx('text-xs font-mono', pipColor(v))}>
              ={fmt(v)}p
            </span>
          )}
        </div>
      ) : node.node_type === 'input_event' ? (() => {
        const cat         = node.event_category || ''
        const evList      = eventDetails?.[cat] || []
        const maxLf       = evList.length > 0 ? Math.max(...evList.map(e => e.lifecycle_factor)) : 0
        const barW        = Math.round(maxLf * 100)
        const baseline    = node.baseline_value ?? 0
        const surprise    = node.event_surprise ?? 0
        const hasBaseline = baseline !== 0
        return (
          <div className="mt-1">
            {hasBaseline ? (
              <div className="space-y-0.5">
                <div className="flex flex-wrap gap-x-2 gap-y-0">
                  <span className="text-xs text-violet-300/80 font-mono">base:{fmt(baseline)}p</span>
                  {surprise !== 0 && (
                    <span className={clsx('text-xs font-mono', pipColor(surprise))}>surp:{fmt(surprise)}p</span>
                  )}
                </div>
                <span className={clsx('text-xs font-bold font-mono', pipColor(v))}>={fmt(v)}p</span>
              </div>
            ) : (
              <div className="flex items-center gap-2">
                <span className={clsx('text-xs font-bold font-mono', pipColor(v))}>{fmt(v)} pips</span>
                {evList.length > 0 && <span className="text-xs text-slate-600">{evList.length} ev</span>}
              </div>
            )}
            {evList.length > 0 && (
              <div className="mt-0.5 flex items-center gap-1.5">
                <div className="flex-1 h-0.5 rounded-full bg-slate-700/60 overflow-hidden">
                  <div className={clsx('h-full rounded-full', pipBg(surprise !== 0 ? surprise : v))} style={{width:`${barW}%`}}/>
                </div>
                <span className="text-slate-600 text-xs font-mono">{barW}%</span>
              </div>
            )}
          </div>
        )
      })() : (
        <div className={clsx('text-xs font-bold mt-1', node.node_type === 'output' ? 'text-base' : '', pipColor(v))}>
          {fmt(v)} pips
        </div>
      )}

      {Math.abs(v) > 0.05 && !isManual && (
        <div className="mt-1 h-1 rounded-full bg-slate-700/40 overflow-hidden">
          <div className={clsx('h-full rounded-full', pipBg(v))} style={{width: `${Math.min(100, Math.abs(v / 50) * 100)}%`}}/>
        </div>
      )}
      {isManual && Math.abs(v) > 0.05 && (
        <div className="mt-1 h-0.5 rounded-full bg-slate-700/40 overflow-hidden">
          <div className={clsx('h-full rounded-full', pipBg(v))} style={{width: `${Math.min(100, Math.abs(v / 50) * 100)}%`}}/>
        </div>
      )}

      {node.node_type === 'intermediate' && (
        <div className="mt-1 flex items-center gap-2">
          {node.formula && <span className="text-slate-600 text-xs">{node.formula.split('+').length} sources</span>}
          {node.regime_weight !== undefined && node.regime_weight !== 1.0 && (
            <span className={clsx('text-xs font-mono font-bold',
              node.regime_weight > 1.0 ? 'text-amber-400' : 'text-slate-500')}>
              ×{node.regime_weight.toFixed(2)}
            </span>
          )}
        </div>
      )}
    </div>
  )
}

interface EdgeDraw {
  x1: number; y1: number; x2: number; y2: number
  coeff: number; fromId: string; toId: string; key: string
  fromType: NodeType
}

function DagView({ nodes, instrument, onEdit, eventDetails }: {
  nodes: ModelNode[]; instrument: string; onEdit: (n: ModelNode) => void
  eventDetails: Record<string, EventDetail[]>
}) {
  const wrapperRef = useRef<HTMLDivElement>(null)
  const nodeRefs   = useRef<Map<string, HTMLDivElement>>(new Map())
  const [svgEdges, setSvgEdges] = useState<EdgeDraw[]>([])
  const [hoveredId, setHoveredId] = useState<string | null>(null)
  const [svgH, setSvgH] = useState(600)

  const cols: ModelNode[][] = [[], [], [], []]
  for (const n of nodes) cols[Math.min(n.display_col ?? 0, 3)].push(n)

  const nodeTypeMap = useMemo(() => {
    const m = new Map<string, NodeType>()
    for (const n of nodes) m.set(n.id, n.node_type)
    return m
  }, [nodes])

  // Parse directed connections from intermediate/output formula strings
  const connections = useMemo(() => {
    const result: { from: string; to: string; coeff: number }[] = []
    for (const node of nodes) {
      if (!node.formula) continue
      for (const term of node.formula.split('+')) {
        const t  = term.trim()
        const m1 = t.match(/^([\d.]+)\s*\*\s*(\w+)$/)
        if (m1) {
          result.push({ from: m1[2], to: node.id, coeff: parseFloat(m1[1]) })
        } else if (/^\w+$/.test(t)) {
          result.push({ from: t, to: node.id, coeff: 1.0 })
        }
      }
    }
    return result
  }, [nodes])

  const recompute = useCallback(() => {
    const wrapper = wrapperRef.current
    if (!wrapper) return
    const rect0 = wrapper.getBoundingClientRect()
    const sL    = wrapper.scrollLeft
    const sT    = wrapper.scrollTop
    const edges: EdgeDraw[] = []
    for (const conn of connections) {
      const fromEl = nodeRefs.current.get(conn.from)
      const toEl   = nodeRefs.current.get(conn.to)
      if (!fromEl || !toEl) continue
      const fr = fromEl.getBoundingClientRect()
      const tr = toEl.getBoundingClientRect()
      edges.push({
        x1: fr.right - rect0.left + sL,
        y1: fr.top + fr.height / 2 - rect0.top + sT,
        x2: tr.left  - rect0.left + sL,
        y2: tr.top + tr.height / 2 - rect0.top + sT,
        coeff:    conn.coeff,
        fromId:   conn.from,
        toId:     conn.to,
        key:      `${conn.from}->${conn.to}`,
        fromType: nodeTypeMap.get(conn.from) ?? 'input_event',
      })
    }
    setSvgEdges(edges)
    setSvgH(wrapper.scrollHeight)
  }, [connections, nodeTypeMap])

  useEffect(() => {
    const t = setTimeout(recompute, 100)
    const ro = new ResizeObserver(() => setTimeout(recompute, 80))
    if (wrapperRef.current) ro.observe(wrapperRef.current)
    return () => { clearTimeout(t); ro.disconnect() }
  }, [recompute])

  function edgeStroke(e: EdgeDraw): string {
    if (e.fromType === 'input_event')  return '#38bdf8'   // sky — event source
    if (e.fromType === 'input_manual') return '#a78bfa'   // violet — manual source
    return '#f59e0b'                                      // amber — intermediate→output
  }

  return (
    <div ref={wrapperRef} className="relative overflow-x-auto" onScroll={recompute}>
      {/* SVG arêtes superposées */}
      <svg
        className="absolute top-0 left-0 pointer-events-none"
        style={{ width: '100%', height: svgH, zIndex: 5, overflow: 'visible' }}
        aria-hidden="true"
      >
        {svgEdges.map(e => {
          const isHov   = hoveredId === e.fromId || hoveredId === e.toId
          const opacity = isHov ? 0.80 : 0.13
          const stroke  = edgeStroke(e)
          const dx      = Math.min(50, (e.x2 - e.x1) * 0.38)
          return (
            <g key={e.key} opacity={opacity}>
              <path
                d={`M ${e.x1} ${e.y1} C ${e.x1 + dx} ${e.y1}, ${e.x2 - dx} ${e.y2}, ${e.x2} ${e.y2}`}
                fill="none" stroke={stroke}
                strokeWidth={e.coeff > 1.05 ? 2 : 1}
              />
              {/* Arrowhead */}
              <polygon
                points={`${e.x2},${e.y2} ${e.x2 - 5},${e.y2 - 3} ${e.x2 - 5},${e.y2 + 3}`}
                fill={stroke} fillOpacity={0.7}
              />
              {/* Coefficient label on hover */}
              {isHov && e.coeff !== 1.0 && (
                <text
                  x={(e.x1 + e.x2) / 2} y={(e.y1 + e.y2) / 2 - 4}
                  fill={stroke} fontSize="9" textAnchor="middle"
                  style={{ userSelect: 'none', fontFamily: 'monospace' }}
                >
                  ×{e.coeff.toFixed(2)}
                </text>
              )}
            </g>
          )
        })}
      </svg>

      {/* Grille 4 colonnes */}
      <div className="grid grid-cols-4 gap-3 min-w-[780px]" style={{ position: 'relative', zIndex: 10 }}>
        {COL_LABELS.map((lbl, ci) => (
          <div key={ci}>
            <div className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2 pb-1 border-b border-slate-700/30">
              {lbl}
              <span className="ml-1.5 text-slate-600 font-normal normal-case">({cols[ci].length})</span>
            </div>
            {cols[ci].map(n => (
              <div
                key={n.id}
                ref={el => { if (el) nodeRefs.current.set(n.id, el); else nodeRefs.current.delete(n.id) }}
                onMouseEnter={() => { setHoveredId(n.id); recompute() }}
                onMouseLeave={() => setHoveredId(null)}
              >
                <NodeCard node={n} onEdit={onEdit} eventDetails={eventDetails}/>
              </div>
            ))}
          </div>
        ))}
      </div>

      {/* Légende */}
      <div className="flex items-center gap-4 mt-4 pt-3 border-t border-slate-700/20 flex-wrap">
        <div className="flex items-center gap-1.5 text-xs text-sky-400">
          <span className="w-3 h-px bg-sky-400 inline-block rounded"/> Events → couches
        </div>
        <div className="flex items-center gap-1.5 text-xs text-violet-400">
          <span className="w-3 h-px bg-violet-400 inline-block rounded"/> Manuel → couches
        </div>
        <div className="flex items-center gap-1.5 text-xs text-amber-400">
          <span className="w-3 h-px bg-amber-400 inline-block rounded"/> Couches → output
        </div>
        <span className="text-xs text-slate-600 ml-1">· survol = highlight</span>
        <div className="ml-2 flex gap-3">
          {(Object.entries(SOURCE_META) as [string, { dot: string; label: string }][]).map(([src, m]) => (
            <div key={src} className="flex items-center gap-1.5 text-xs text-slate-500">
              <span className={clsx('w-2 h-2 rounded-full', m.dot)}/>{m.label}
            </div>
          ))}
        </div>
        <div className="flex items-center gap-1.5 text-xs text-slate-500 ml-auto">
          <Edit3 className="w-3 h-3"/> Cliquer pour modifier
        </div>
      </div>
    </div>
  )
}

// ── Table View ────────────────────────────────────────────────────────────────

type SortKey = 'label' | 'category' | 'pip_contribution' | 'source'

function TableView({ nodes, onEdit }: { nodes: ModelNode[]; onEdit: (n: ModelNode) => void }) {
  const [sort, setSort]         = useState<SortKey>('pip_contribution')
  const [sortDir, setSortDir]   = useState<1 | -1>(-1)
  const [filterType, setFilterType] = useState<string>('all')
  const [filterCat, setFilterCat]   = useState<string>('all')
  const [expanded, setExpanded]     = useState<string | null>(null)

  const categories = useMemo(() => {
    const s = new Set(nodes.map(n => n.category))
    return ['all', ...Array.from(s).sort()]
  }, [nodes])

  const maxAbs = useMemo(() =>
    Math.max(...nodes.map(n => Math.abs(n.pip_contribution)), 1), [nodes])

  const sorted = useMemo(() => {
    const filtered = nodes.filter(n =>
      (filterType === 'all' || n.node_type === filterType) &&
      (filterCat  === 'all' || n.category === filterCat)
    )
    return [...filtered].sort((a, b) => {
      if (sort === 'pip_contribution') return (a.pip_contribution - b.pip_contribution) * sortDir
      const av = sort === 'label' ? a.label : sort === 'category' ? a.category : a.source
      const bv = sort === 'label' ? b.label : sort === 'category' ? b.category : b.source
      return String(av).localeCompare(String(bv)) * sortDir
    })
  }, [nodes, filterType, filterCat, sort, sortDir])

  function toggle(key: SortKey) {
    if (sort === key) setSortDir(d => d === 1 ? -1 : 1)
    else { setSort(key); setSortDir(-1) }
  }

  function SortBtn({ k, label }: { k: SortKey; label: string }) {
    return (
      <button onClick={() => toggle(k)}
        className={clsx('flex items-center gap-1', sort === k ? 'text-blue-400' : 'text-slate-500 hover:text-slate-300')}>
        {label}
        {sort === k && (sortDir === -1 ? <ChevronDown className="w-3 h-3"/> : <ChevronUp className="w-3 h-3"/>)}
      </button>
    )
  }

  const TYPE_OPTS = [
    { value:'all',          label:'Tous' },
    { value:'input_event',  label:'Events' },
    { value:'input_manual', label:'Manuels' },
    { value:'intermediate', label:'Interméd.' },
    { value:'output',       label:'Output' },
  ]

  return (
    <div>
      <div className="flex flex-wrap gap-2 mb-3">
        {TYPE_OPTS.map(o => (
          <button key={o.value} onClick={() => setFilterType(o.value)}
            className={clsx('px-2.5 py-1 rounded text-xs font-medium transition-colors',
              filterType === o.value ? 'bg-blue-600 text-white' : 'bg-dark-700 text-slate-400 hover:text-white')}>
            {o.label}
          </button>
        ))}
        <select value={filterCat} onChange={e => setFilterCat(e.target.value)}
          className="bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-slate-400 focus:outline-none ml-2">
          {categories.map(c => <option key={c} value={c}>{c === 'all' ? 'Toutes catégories' : c}</option>)}
        </select>
      </div>

      <div className="rounded-lg border border-slate-700/40 overflow-hidden">
        <table className="w-full text-xs">
          <thead>
            <tr className="bg-dark-700/60 border-b border-slate-700/40 text-slate-500">
              <th className="px-3 py-2 text-left w-8">T</th>
              <th className="px-3 py-2 text-left"><SortBtn k="label" label="Nœud"/></th>
              <th className="px-3 py-2 text-left hidden sm:table-cell"><SortBtn k="category" label="Catégorie"/></th>
              <th className="px-3 py-2 text-right"><SortBtn k="pip_contribution" label="Pips"/></th>
              <th className="px-3 py-2 text-center"><SortBtn k="source" label="Source"/></th>
              <th className="px-3 py-2 text-center w-24">Bar</th>
              <th className="px-3 py-2 text-center w-8"/>
            </tr>
          </thead>
          <tbody>
            {sorted.map(node => {
              const meta    = NODE_TYPE_META[node.node_type]
              const v       = node.pip_contribution
              const isExp   = expanded === node.id
              const canEdit = node.node_type === 'input_event' || node.node_type === 'input_manual'
              return [
                <tr key={node.id}
                  className={clsx('border-b border-slate-700/20 hover:bg-dark-700/20 transition-colors cursor-pointer', isExp && 'bg-dark-700/10')}>
                  <td className="px-3 py-2">
                    <span className={clsx('font-bold text-xs', meta.color)}>
                      {node.node_type === 'input_event' ? 'EV' : node.node_type === 'input_manual' ? 'M' : node.node_type === 'intermediate' ? 'INT' : 'OUT'}
                    </span>
                  </td>
                  <td className="px-3 py-2" onClick={() => setExpanded(isExp ? null : node.id)}>
                    <span className="text-slate-300 font-medium hover:text-white">
                      {node.label}
                      {node.source === 'manual' && <span className="ml-1 text-violet-400">●</span>}
                    </span>
                  </td>
                  <td className="px-3 py-2 text-slate-500 hidden sm:table-cell">{node.category}</td>
                  <td className={clsx('px-3 py-2 text-right font-mono font-bold', pipColor(v))}>{fmt(v)}</td>
                  <td className="px-3 py-2 text-center">
                    <span className="flex items-center justify-center gap-1">
                      <span className={clsx('w-1.5 h-1.5 rounded-full', SOURCE_META[node.source]?.dot || 'bg-slate-600')}/>
                      <span className="text-slate-500">{SOURCE_META[node.source]?.label}</span>
                    </span>
                  </td>
                  <td className="px-3 py-2">
                    <div className="h-1.5 rounded-full bg-slate-700/40 overflow-hidden w-full">
                      <div className={clsx('h-full rounded-full', pipBg(v))}
                        style={{width: `${Math.min(100, Math.abs(v / maxAbs) * 100)}%`}}/>
                    </div>
                  </td>
                  <td className="px-3 py-2 text-center">
                    {canEdit && (
                      <button onClick={() => onEdit(node)} className="text-slate-600 hover:text-blue-400 transition-colors">
                        <Edit3 className="w-3.5 h-3.5"/>
                      </button>
                    )}
                  </td>
                </tr>,
                isExp && (
                  <tr key={`${node.id}-exp`} className="bg-dark-800/40 border-b border-slate-700/10">
                    <td/>
                    <td colSpan={6} className="px-3 py-2 text-xs text-slate-400 space-y-1">
                      <div>{node.description}</div>
                      {node.node_type === 'input_manual' && node.coefficient_to_pips !== undefined && (
                        <div className="text-slate-500">
                          Coeff : {node.coefficient_to_pips} pips/{node.unit}
                          {node.raw_value !== undefined ? ` · ${node.raw_value} ${node.unit}` : ''}
                          {node.pip_linear !== undefined && node.pip_saturated !== undefined && (
                            <span>
                              {' '}→ linéaire <span className="text-slate-400">{fmt(node.pip_linear)}</span>
                              {' '}/ saturé <span className={pipColor(node.pip_saturated)}>{fmt(node.pip_saturated)}</span>
                              {node.saturation_pct !== undefined && node.saturation_pct > 1 && (
                                <span className="text-amber-500/70"> (sat. {node.saturation_pct.toFixed(0)}%)</span>
                              )}
                            </span>
                          )}
                        </div>
                      )}
                      {node.node_type === 'intermediate' && node.formula && (
                        <div className="text-slate-500 font-mono text-xs">{node.formula}</div>
                      )}
                      {node.override_note && <div className="text-violet-400 italic">Note : {node.override_note}</div>}
                      {node.override_set_at && <div className="text-slate-600">Modifié : {node.override_set_at.slice(0,10)}</div>}
                    </td>
                  </tr>
                ),
              ].filter(Boolean)
            })}
          </tbody>
        </table>
        {sorted.length === 0 && (
          <div className="text-center py-6 text-slate-500 text-sm">Aucun nœud pour ce filtre</div>
        )}
      </div>
    </div>
  )
}

// ── Timeline Chart ─────────────────────────────────────────────────────────────

const REGIME_CANVAS_COLORS: Record<string, string> = {
  MONETARY_DOMINANCE: 'rgba(59,130,246,0.07)',
  GEOPOLITICAL_RISK:  'rgba(249,115,22,0.07)',
  CREDIT_STRESS:      'rgba(239,68,68,0.07)',
  GROWTH_SCARE:       'rgba(245,158,11,0.07)',
  COMMODITY_SHOCK:    'rgba(234,179,8,0.07)',
  BALANCED:           'rgba(0,0,0,0)',
}

function newVirtualEvent(): VirtualEventForm {
  return {
    id:             Math.random().toString(36).slice(2),
    date:           new Date().toISOString().slice(0, 10),
    category:       'central_bank',
    pips:           50,
    label:          'Event virtuel',
    absorption_days: 14,
    rise_days:       1,
    plateau_days:    0,
    decay_type:      'exp',
  }
}

function TimelineView({ instrument }: { instrument: string }) {
  const [period, setPeriod]         = useState<Period>('3mo')
  const [data, setData]             = useState<TimelinePoint[]>([])
  const [realPrices, setRealPrices] = useState<PricePoint[]>([])
  const [loading, setLoading]       = useState(false)
  const [showReal, setShowReal]     = useState(true)
  const [showFund, setShowFund]     = useState(true)
  const [showPips, setShowPips]     = useState(false)  // toggle: price vs pips
  const [layerKeys, setLayerKeys]   = useState<string[]>([])
  const [shownLayers, setShownLayers] = useState<Set<string>>(new Set())
  const [showVirtual, setShowVirtual] = useState(false)
  const [virtuals, setVirtuals]     = useState<VirtualEventForm[]>([])
  const [whatifLoading, setWhatifLoading] = useState(false)
  const [whatifData, setWhatifData] = useState<TimelinePoint[] | null>(null)
  const [importingCal, setImportingCal] = useState(false)
  const [calMsg, setCalMsg]             = useState<string | null>(null)
  const canvasRef                   = useRef<HTMLCanvasElement>(null)

  const activeData = whatifData ?? data

  // Load timeline
  useEffect(() => {
    setLoading(true)
    setWhatifData(null)
    api.get<TimelinePoint[]>(`/instrument-models/${instrument}/timeline?period=${period}`)
      .then(r => {
        const pts = r.data
        setData(pts)
        if (pts.length > 0) {
          const keys = Object.keys(pts[0].nodes).filter(k => k.startsWith('layer_'))
          setLayerKeys(keys)
        }
      })
      .catch(() => setData([]))
      .finally(() => setLoading(false))
  }, [instrument, period])

  // Load real price history
  useEffect(() => {
    api.get<{ prices: PricePoint[] }>(`/instrument-models/${instrument}/price-history?period=${period}`)
      .then(r => setRealPrices(r.data.prices || []))
      .catch(() => setRealPrices([]))
  }, [instrument, period])

  // Draw canvas
  useEffect(() => {
    const canvas = canvasRef.current
    if (!canvas || activeData.length === 0) return
    const ctx = canvas.getContext('2d')
    if (!ctx) return

    const W = canvas.width  = canvas.offsetWidth
    const H = canvas.height = 300
    ctx.clearRect(0, 0, W, H)

    // Build date→index map for real prices alignment
    const dateToIdx = new Map<string, number>()
    activeData.forEach((pt, i) => dateToIdx.set(pt.date, i))

    // Value extractor depending on mode
    const getV = (pt: TimelinePoint) => showPips ? pt.net_pips : pt.synthetic_price
    const getFundV = (pt: TimelinePoint) => showPips ? pt.structural_pips : pt.fundamental_level

    let minV = Infinity, maxV = -Infinity
    for (const pt of activeData) {
      const v = getV(pt)
      if (v < minV) minV = v
      if (v > maxV) maxV = v
      if (showFund) {
        const f = getFundV(pt)
        if (f < minV) minV = f
        if (f > maxV) maxV = f
      }
    }
    if (showReal && realPrices.length > 0 && !showPips) {
      for (const p of realPrices) {
        if (p.close < minV) minV = p.close
        if (p.close > maxV) maxV = p.close
      }
    }
    for (const k of shownLayers) {
      for (const pt of activeData) {
        const v = pt.nodes[k] ?? 0
        if (v < minV) minV = v
        if (v > maxV) maxV = v
      }
    }

    const pad = Math.max(showPips ? 5 : 0.001, 0.08 * (maxV - minV))
    minV -= pad; maxV += pad

    const mL = 64, mR = 16, mT = 12, mB = 28
    const cW = W - mL - mR, cH = H - mT - mB
    const toX = (i: number) => mL + (i / Math.max(activeData.length - 1, 1)) * cW
    const toY = (v: number) => mT + (1 - (v - minV) / (maxV - minV)) * cH

    // Regime bands
    let bandStart = 0, bandRegime = activeData[0]?.regime || 'BALANCED'
    for (let i = 1; i <= activeData.length; i++) {
      const r = i < activeData.length ? (activeData[i]?.regime || 'BALANCED') : null
      if (r !== bandRegime || i === activeData.length) {
        const color = REGIME_CANVAS_COLORS[bandRegime]
        if (color && color !== 'rgba(0,0,0,0)') {
          ctx.fillStyle = color
          ctx.fillRect(toX(bandStart), mT, toX(Math.max(i - 1, bandStart)) - toX(bandStart) + 1, cH)
        }
        bandStart = i; bandRegime = r || 'BALANCED'
      }
    }

    // Grid
    const nG = 5
    for (let i = 0; i <= nG; i++) {
      const v = minV + (i / nG) * (maxV - minV)
      const y = toY(v)
      ctx.strokeStyle = '#1e293b'; ctx.lineWidth = 1
      ctx.beginPath(); ctx.moveTo(mL, y); ctx.lineTo(W - mR, y); ctx.stroke()
      ctx.fillStyle = '#64748b'; ctx.font = '10px sans-serif'; ctx.textAlign = 'right'
      const lbl = showPips ? fmt(v) : v.toFixed(v > 100 ? 1 : 4)
      ctx.fillText(lbl, mL - 4, y + 3.5)
    }

    if (showPips && minV < 0 && maxV > 0) {
      const y0 = toY(0)
      ctx.strokeStyle = '#334155'; ctx.setLineDash([3,3]); ctx.lineWidth = 1
      ctx.beginPath(); ctx.moveTo(mL, y0); ctx.lineTo(W - mR, y0); ctx.stroke()
      ctx.setLineDash([])
    }

    // X labels
    const step = Math.max(1, Math.floor(activeData.length / Math.floor(cW / 60)))
    ctx.fillStyle = '#64748b'; ctx.font = '10px sans-serif'; ctx.textAlign = 'center'
    for (let i = 0; i < activeData.length; i += step) {
      ctx.fillText(activeData[i].date.slice(5), toX(i), H - 4)
    }

    // Virtual event markers
    if (virtuals.length > 0) {
      ctx.fillStyle = '#f59e0b'
      for (const ve of virtuals) {
        const idx = dateToIdx.get(ve.date)
        if (idx !== undefined) {
          const x = toX(idx)
          ctx.beginPath()
          ctx.moveTo(x, mT + cH)
          ctx.lineTo(x - 4, mT + cH + 6)
          ctx.lineTo(x + 4, mT + cH + 6)
          ctx.closePath()
          ctx.fill()
        }
      }
    }

    // Real price line (light grey)
    if (showReal && !showPips && realPrices.length > 0) {
      ctx.strokeStyle = 'rgba(148,163,184,0.5)'; ctx.lineWidth = 1.5; ctx.setLineDash([])
      ctx.beginPath()
      let started = false
      for (const p of realPrices) {
        const idx = dateToIdx.get(p.date)
        if (idx !== undefined) {
          const x = toX(idx); const y = toY(p.close)
          if (!started) { ctx.moveTo(x, y); started = true } else { ctx.lineTo(x, y) }
        }
      }
      ctx.stroke()
    }

    // Fundamental level (dashed amber)
    if (showFund) {
      ctx.strokeStyle = '#f59e0b'; ctx.lineWidth = 1; ctx.setLineDash([5, 4])
      ctx.beginPath()
      activeData.forEach((pt, i) => {
        const y = toY(getFundV(pt))
        i === 0 ? ctx.moveTo(toX(i), y) : ctx.lineTo(toX(i), y)
      })
      ctx.stroke()
      ctx.setLineDash([])
    }

    // Layer lines (dashed, thin)
    for (const k of shownLayers) {
      const color = CANVAS_COLORS[k] || '#94a3b8'
      ctx.strokeStyle = color; ctx.lineWidth = 1.2; ctx.setLineDash([4, 3])
      ctx.beginPath()
      activeData.forEach((pt, i) => {
        const v = showPips ? (pt.nodes[k] ?? 0) : (pt.synthetic_price + (pt.nodes[k] ?? 0) * (1 / Math.max(activeData.length, 1)))
        i === 0 ? ctx.moveTo(toX(i), toY(showPips ? v : (pt.nodes[k] ?? 0))) : ctx.lineTo(toX(i), toY(showPips ? v : (pt.nodes[k] ?? 0)))
      })
      ctx.stroke()
      ctx.setLineDash([])
    }

    // Synthetic price (main bold line — green/red)
    const lastPt = activeData[activeData.length - 1]
    const lastFirst = activeData[0]
    const trending = getV(lastPt) > getV(lastFirst)
    ctx.strokeStyle = trending ? '#10b981' : '#f87171'; ctx.lineWidth = 2.5; ctx.setLineDash([])
    ctx.beginPath()
    activeData.forEach((pt, i) => {
      i === 0 ? ctx.moveTo(toX(i), toY(getV(pt))) : ctx.lineTo(toX(i), toY(getV(pt)))
    })
    ctx.stroke()

    // What-if overlay (if exists, draw original in grey too)
    if (whatifData) {
      ctx.strokeStyle = '#6366f1'; ctx.lineWidth = 2; ctx.setLineDash([6, 3])
      ctx.beginPath()
      whatifData.forEach((pt, i) => {
        i === 0 ? ctx.moveTo(toX(i), toY(getV(pt))) : ctx.lineTo(toX(i), toY(getV(pt)))
      })
      ctx.stroke()
      ctx.setLineDash([])
    }

  }, [activeData, realPrices, showReal, showFund, showPips, shownLayers, virtuals, whatifData])

  async function runWhatIf() {
    if (virtuals.length === 0) return
    setWhatifLoading(true)
    try {
      const r = await api.post<TimelinePoint[]>(`/instrument-models/${instrument}/timeline-whatif`, {
        period,
        virtual_events: virtuals.map(ve => ({
          date:            ve.date,
          category:        ve.category,
          pips:            ve.pips,
          label:           ve.label,
          absorption_days: ve.absorption_days,
          rise_days:       ve.rise_days,
          plateau_days:    ve.plateau_days,
          decay_type:      ve.decay_type,
        })),
      })
      setWhatifData(r.data)
    } finally { setWhatifLoading(false) }
  }

  function toggleLayer(k: string) {
    setShownLayers(prev => { const n = new Set(prev); n.has(k) ? n.delete(k) : n.add(k); return n })
  }

  if (loading) return <div className="flex items-center justify-center h-64 text-slate-500 text-sm">Calcul timeline…</div>
  if (!loading && data.length === 0) return <div className="text-center py-8 text-slate-500 text-sm">Aucune donnée pour {instrument}</div>

  return (
    <div className="space-y-3">
      {/* Controls */}
      <div className="flex items-center gap-2 flex-wrap">
        {PERIODS.map(p => (
          <button key={p} onClick={() => setPeriod(p)}
            className={clsx('px-2 py-1 rounded text-xs font-medium transition-colors',
              period === p ? 'bg-blue-600 text-white' : 'bg-dark-700 text-slate-400 hover:text-white')}>
            {p}
          </button>
        ))}
        <div className="ml-2 flex gap-1.5">
          <button onClick={() => setShowPips(v => !v)}
            className={clsx('px-2 py-1 rounded text-xs border transition-all',
              showPips ? 'border-blue-600/50 text-blue-400 bg-blue-900/20' : 'border-slate-700/40 text-slate-400')}>
            {showPips ? 'Mode pips' : 'Mode prix'}
          </button>
          <button onClick={() => setShowReal(v => !v)}
            className={clsx('flex items-center gap-1 px-2 py-1 rounded text-xs border transition-all',
              showReal ? 'border-slate-600/50 text-slate-300' : 'border-slate-700/30 text-slate-600')}>
            <span className="w-2 h-0.5 bg-slate-400 inline-block"/> Réel
          </button>
          <button onClick={() => setShowFund(v => !v)}
            className={clsx('flex items-center gap-1 px-2 py-1 rounded text-xs border transition-all',
              showFund ? 'border-amber-600/50 text-amber-400' : 'border-slate-700/30 text-slate-600')}>
            <span className="w-2 h-0.5 bg-amber-400 inline-block" style={{borderTop:'1px dashed #f59e0b'}}/> Fondamental
          </button>
          <button onClick={() => setShowVirtual(v => !v)}
            className={clsx('flex items-center gap-1.5 px-2 py-1 rounded text-xs border transition-all',
              showVirtual ? 'border-violet-600/50 text-violet-400 bg-violet-900/20' : 'border-slate-700/30 text-slate-500')}>
            <Zap className="w-3 h-3"/> What-if {virtuals.length > 0 && `(${virtuals.length})`}
          </button>
        </div>
        <span className="ml-auto text-xs text-slate-500">{activeData.length} jours</span>
      </div>

      {/* Layer toggles */}
      {layerKeys.length > 0 && (
        <div className="flex flex-wrap gap-1.5">
          {layerKeys.map(k => (
            <button key={k} onClick={() => toggleLayer(k)}
              className={clsx('flex items-center gap-1.5 px-2 py-1 rounded text-xs font-medium border transition-all',
                shownLayers.has(k) ? 'border-slate-600/50 text-slate-300 bg-slate-800/40' : 'border-slate-700/30 text-slate-600')}>
              <span className={clsx('w-2 h-2 rounded-full', LAYER_COLORS[k] || 'bg-slate-500')}/>
              {k.replace('layer_', '')}
            </button>
          ))}
        </div>
      )}

      {/* Canvas */}
      <div className="rounded-lg bg-dark-900/50 border border-slate-700/30 p-2">
        <canvas ref={canvasRef} className="w-full" style={{height: 300, display: 'block'}}/>
        {/* Legend */}
        <div className="flex flex-wrap gap-3 mt-2 px-1">
          <div className="flex items-center gap-1.5 text-xs text-slate-500">
            <span className="w-4 h-0.5 bg-emerald-500 inline-block"/> Prix synthétique
          </div>
          {showReal && (
            <div className="flex items-center gap-1.5 text-xs text-slate-500">
              <span className="w-4 h-0.5 bg-slate-400 inline-block opacity-50"/> Prix réel
            </div>
          )}
          {showFund && (
            <div className="flex items-center gap-1.5 text-xs text-slate-500">
              <span className="w-4 h-0.5 border-t border-dashed border-amber-500 inline-block"/> Fondamental
            </div>
          )}
          {whatifData && (
            <div className="flex items-center gap-1.5 text-xs text-violet-400">
              <span className="w-4 h-0.5 border-t border-dashed border-violet-500 inline-block"/> What-if
              <button onClick={() => setWhatifData(null)} className="ml-1 text-slate-600 hover:text-slate-400">✕</button>
            </div>
          )}
        </div>
      </div>

      {/* Virtual Events Panel */}
      {showVirtual && (
        <div className="rounded-lg border border-violet-700/30 bg-violet-900/10 p-3 space-y-3">
          <div className="flex items-center justify-between gap-2 flex-wrap">
            <div className="text-xs font-semibold text-violet-400">Events virtuels (What-if)</div>
            <div className="flex gap-1.5">
              <button
                disabled={importingCal}
                onClick={async () => {
                  setImportingCal(true)
                  try {
                    const r = await api.get<CalendarEvent[]>(
                      `/instrument-models/${instrument}/calendar-events?days_back=365&days_forward=90`
                    )
                    const imported: VirtualEventForm[] = r.data.map(ev => ({
                      id:              ev.analysis_id.toString(),
                      date:            ev.start_date,
                      category:        ev.category,
                      pips:            ev.pip_prediction,
                      label:           ev.title || ev.category,
                      absorption_days: ev.calibration.absorption_days,
                      rise_days:       ev.calibration.rise_days,
                      plateau_days:    ev.calibration.plateau_days,
                      decay_type:      ev.calibration.decay_type,
                    }))
                    if (imported.length === 0) {
                      setCalMsg('Aucun event analysé pour cet instrument — utilisez CausalLab d\'abord')
                    } else {
                      setVirtuals(prev => {
                        const existingIds = new Set(prev.map(v => v.id))
                        const added = imported.filter(x => !existingIds.has(x.id))
                        setCalMsg(`${added.length} event${added.length > 1 ? 's' : ''} importé${added.length > 1 ? 's' : ''}`)
                        return [...prev, ...added]
                      })
                    }
                    setTimeout(() => setCalMsg(null), 3500)
                  } finally { setImportingCal(false) }
                }}
                className="flex items-center gap-1 text-xs text-sky-400 hover:text-sky-300 border border-sky-700/40 rounded px-2 py-0.5 transition-colors disabled:opacity-40">
                <Activity className="w-3 h-3"/> {importingCal ? '…' : 'Import calendrier'}
              </button>
              <button onClick={() => setVirtuals(v => [...v, newVirtualEvent()])}
                className="flex items-center gap-1 text-xs text-violet-400 hover:text-violet-300 border border-violet-700/40 rounded px-2 py-0.5 transition-colors">
                <Plus className="w-3 h-3"/> Ajouter
              </button>
            </div>
          </div>
          {calMsg && (
            <div className={clsx('text-xs px-2 py-1 rounded', calMsg.startsWith('Aucun') ? 'text-amber-400 bg-amber-900/20' : 'text-emerald-400 bg-emerald-900/20')}>
              {calMsg}
            </div>
          )}
          {virtuals.length === 0 && !calMsg && (
            <div className="text-xs text-slate-500 text-center py-2">
              Ajoute des events hypothétiques pour simuler leur impact sur la courbe synthétique.
            </div>
          )}
          {virtuals.map((ve, idx) => (
            <div key={ve.id} className="grid grid-cols-2 md:grid-cols-4 gap-2 items-end bg-dark-800/40 rounded p-2">
              <div>
                <label className="text-xs text-slate-500 block mb-0.5">Date</label>
                <input type="date" value={ve.date}
                  onChange={e => setVirtuals(v => v.map((x, i) => i === idx ? {...x, date: e.target.value} : x))}
                  className="w-full bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none"/>
              </div>
              <div>
                <label className="text-xs text-slate-500 block mb-0.5">Catégorie</label>
                <select value={ve.category}
                  onChange={e => setVirtuals(v => v.map((x, i) => i === idx ? {...x, category: e.target.value} : x))}
                  className="w-full bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none">
                  {EVENT_CATEGORIES.map(c => <option key={c.value} value={c.value}>{c.label}</option>)}
                </select>
              </div>
              <div>
                <label className="text-xs text-slate-500 block mb-0.5">Magnitude (pips)</label>
                <input type="number" step="any" value={ve.pips}
                  onChange={e => setVirtuals(v => v.map((x, i) => i === idx ? {...x, pips: parseFloat(e.target.value) || 0} : x))}
                  className="w-full bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none"/>
              </div>
              <div>
                <label className="text-xs text-slate-500 block mb-0.5">Absorption (j)</label>
                <div className="flex gap-1">
                  <input type="number" value={ve.absorption_days}
                    onChange={e => setVirtuals(v => v.map((x, i) => i === idx ? {...x, absorption_days: parseInt(e.target.value)||14} : x))}
                    className="flex-1 bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none"/>
                  <button onClick={() => setVirtuals(v => v.filter((_, i) => i !== idx))}
                    className="text-red-500/60 hover:text-red-400 px-1.5 transition-colors">
                    <X className="w-3 h-3"/>
                  </button>
                </div>
              </div>
              <div className="col-span-2 md:col-span-4">
                <input type="text" value={ve.label} placeholder="Label de l'event"
                  onChange={e => setVirtuals(v => v.map((x, i) => i === idx ? {...x, label: e.target.value} : x))}
                  className="w-full bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none"/>
              </div>
            </div>
          ))}
          {virtuals.length > 0 && (
            <div className="flex gap-2 justify-end">
              <button onClick={() => { setVirtuals([]); setWhatifData(null) }}
                className="text-xs text-slate-500 hover:text-slate-300 transition-colors">
                Effacer tout
              </button>
              <button onClick={runWhatIf} disabled={whatifLoading}
                className="flex items-center gap-1.5 px-3 py-1.5 bg-violet-600 hover:bg-violet-500 disabled:opacity-40 text-white text-xs rounded font-medium transition-colors">
                <Zap className="w-3 h-3"/>
                {whatifLoading ? 'Simulation…' : 'Simuler'}
              </button>
            </div>
          )}
        </div>
      )}
    </div>
  )
}

// ── Gauge Sync Panel ──────────────────────────────────────────────────────────

interface GaugeSuggestion {
  node_id: string
  label: string
  value: number
  unit: string
  source: string
  confidence: 'HIGH' | 'MEDIUM' | 'LOW'
  note: string
}

interface GaugeSuggestionsResponse {
  instrument: string
  gauge_date: string
  n_suggestions: number
  suggestions: GaugeSuggestion[]
  gauges_available: string[]
}

const CONF_META: Record<string, { color: string; label: string }> = {
  HIGH:   { color: 'text-emerald-400 bg-emerald-900/20 border-emerald-700/40', label: 'Direct'  },
  MEDIUM: { color: 'text-amber-400 bg-amber-900/20 border-amber-700/40',       label: 'Dérivé'  },
  LOW:    { color: 'text-slate-400 bg-slate-800/40 border-slate-700/30',        label: 'Proxy'   },
}

function SyncPanel({ instrument, onClose, onApplied }: {
  instrument: string; onClose: () => void; onApplied: (atDate?: string) => void
}) {
  const [data, setData]         = useState<GaugeSuggestionsResponse | null>(null)
  const [loading, setLoading]   = useState(true)
  const [selected, setSelected] = useState<Set<string>>(new Set())
  const [applying, setApplying] = useState(false)
  const [atDate, setAtDate]     = useState<string>('')

  function fetchSuggestions(date?: string) {
    setLoading(true)
    const qs = date ? `?at_date=${date}` : ''
    api.get<GaugeSuggestionsResponse>(`/instrument-models/${instrument}/gauge-suggestions${qs}`)
      .then(r => {
        setData(r.data)
        const highs = new Set(r.data.suggestions.filter(s => s.confidence === 'HIGH').map(s => s.node_id))
        setSelected(highs)
      })
      .catch(() => setData(null))
      .finally(() => setLoading(false))
  }

  useEffect(() => { fetchSuggestions() }, [instrument])

  function toggleAll(conf: string) {
    if (!data) return
    const ids = data.suggestions.filter(s => s.confidence === conf).map(s => s.node_id)
    const allSelected = ids.every(id => selected.has(id))
    setSelected(prev => {
      const n = new Set(prev)
      ids.forEach(id => allSelected ? n.delete(id) : n.add(id))
      return n
    })
  }

  async function apply() {
    if (!data) return
    setApplying(true)
    try {
      const overrides = data.suggestions
        .filter(s => selected.has(s.node_id))
        .map(s => ({ node_id: s.node_id, value: s.value, note: `[Auto${atDate ? ' @'+atDate : ''}] ${s.note}` }))
      await api.post(`/instrument-models/${instrument}/apply-suggestions`, { overrides })
      onApplied(atDate || undefined)
      onClose()
    } finally { setApplying(false) }
  }

  const selectedCount = selected.size

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 backdrop-blur-sm"
      onClick={onClose}>
      <div className="bg-dark-800 border border-slate-700/60 rounded-xl shadow-2xl w-full max-w-2xl mx-4 max-h-[85vh] flex flex-col"
        onClick={e => e.stopPropagation()}>

        {/* Header */}
        <div className="flex items-center justify-between p-4 border-b border-slate-700/40">
          <div className="flex-1">
            <div className="text-white font-semibold">Sync Marché — {instrument}</div>
            {data && (
              <div className="text-xs text-slate-500 mt-0.5">
                Snapshot: {data.gauge_date}
                {(data as any).requested_date && (data as any).requested_date !== data.gauge_date && (
                  <span className="text-amber-500/70 ml-1">(demandé: {(data as any).requested_date})</span>
                )}
                · {data.gauges_available.length} indicateurs
              </div>
            )}
            {/* Date picker to initialize at a historical date */}
            <div className="flex items-center gap-2 mt-2">
              <label className="text-xs text-slate-500">Date d'init :</label>
              <input
                type="date"
                value={atDate}
                onChange={e => setAtDate(e.target.value)}
                className="bg-dark-700 border border-slate-700/40 rounded px-2 py-0.5 text-xs text-white focus:outline-none focus:border-blue-500/60"
              />
              <button
                onClick={() => fetchSuggestions(atDate || undefined)}
                disabled={loading}
                className="px-2 py-0.5 bg-blue-600/20 border border-blue-700/40 rounded text-xs text-blue-400 hover:bg-blue-600/30 transition-colors disabled:opacity-40">
                {loading ? '…' : 'Charger'}
              </button>
              {atDate && (
                <button onClick={() => { setAtDate(''); fetchSuggestions() }}
                  className="text-xs text-slate-600 hover:text-slate-400">✕ Réinitialiser</button>
              )}
            </div>
          </div>
          <button onClick={onClose} className="text-slate-500 hover:text-white ml-4">
            <X className="w-4 h-4"/>
          </button>
        </div>

        {/* Content */}
        <div className="flex-1 overflow-y-auto p-4">
          {loading && (
            <div className="text-center py-8 text-slate-500 text-sm">Chargement des suggestions…</div>
          )}
          {!loading && !data && (
            <div className="text-center py-8 text-red-400 text-sm">
              Aucun snapshot de gauges disponible. Actualiser les données marché d'abord.
            </div>
          )}
          {data && (
            <div className="space-y-3">
              {/* Confidence group filters */}
              <div className="flex gap-2 flex-wrap">
                {(['HIGH','MEDIUM','LOW'] as const).map(conf => {
                  const items = data.suggestions.filter(s => s.confidence === conf)
                  const meta = CONF_META[conf]
                  return items.length > 0 ? (
                    <button key={conf} onClick={() => toggleAll(conf)}
                      className={clsx('flex items-center gap-1.5 px-2.5 py-1 rounded border text-xs font-medium transition-colors', meta.color)}>
                      <span>{items.every(s => selected.has(s.node_id)) ? '☑' : '☐'}</span>
                      {meta.label} ({items.length})
                    </button>
                  ) : null
                })}
                <span className="ml-auto text-xs text-slate-500 self-center">
                  {selectedCount} sélectionné{selectedCount > 1 ? 's' : ''}
                </span>
              </div>

              {/* Suggestion rows */}
              <div className="rounded-lg border border-slate-700/40 overflow-hidden">
                <table className="w-full text-xs">
                  <thead>
                    <tr className="bg-dark-700/60 border-b border-slate-700/40 text-slate-500">
                      <th className="px-3 py-2 w-8"/>
                      <th className="px-3 py-2 text-left">Variable</th>
                      <th className="px-3 py-2 text-right">Valeur</th>
                      <th className="px-3 py-2 text-center w-16">Conf.</th>
                      <th className="px-3 py-2 text-left text-slate-600">Détail</th>
                    </tr>
                  </thead>
                  <tbody>
                    {data.suggestions.map(s => {
                      const isSel = selected.has(s.node_id)
                      const meta  = CONF_META[s.confidence]
                      return (
                        <tr key={s.node_id}
                          onClick={() => setSelected(prev => { const n = new Set(prev); isSel ? n.delete(s.node_id) : n.add(s.node_id); return n })}
                          className={clsx('border-b border-slate-700/20 cursor-pointer transition-colors',
                            isSel ? 'bg-blue-900/10' : 'hover:bg-dark-700/20')}>
                          <td className="px-3 py-2 text-center">
                            <div className={clsx('w-3.5 h-3.5 rounded border mx-auto transition-colors',
                              isSel ? 'bg-blue-500 border-blue-400' : 'border-slate-600')}>
                              {isSel && <span className="text-white text-xs flex items-center justify-center h-full leading-none">✓</span>}
                            </div>
                          </td>
                          <td className="px-3 py-2 text-slate-300 font-medium">{s.label}</td>
                          <td className="px-3 py-2 text-right font-mono text-white">
                            {s.value > 0 ? '+' : ''}{s.value} <span className="text-slate-500">{s.unit}</span>
                          </td>
                          <td className="px-3 py-2 text-center">
                            <span className={clsx('px-1.5 py-0.5 rounded border text-xs', meta.color)}>
                              {meta.label}
                            </span>
                          </td>
                          <td className="px-3 py-2 text-slate-500 truncate max-w-[200px]" title={s.note}>
                            {s.note}
                          </td>
                        </tr>
                      )
                    })}
                  </tbody>
                </table>
              </div>

              {/* Warning LOW confidence */}
              {data.suggestions.some(s => s.confidence === 'LOW' && selected.has(s.node_id)) && (
                <div className="flex items-start gap-2 px-3 py-2 rounded bg-amber-900/20 border border-amber-700/30 text-xs text-amber-400">
                  <span className="shrink-0">⚠</span>
                  Certaines valeurs sélectionnées sont des proxies (Conf. Proxy) — revérifier avec les données réelles.
                </div>
              )}
            </div>
          )}
        </div>

        {/* Footer */}
        <div className="flex gap-2 p-4 border-t border-slate-700/40">
          <button onClick={apply} disabled={applying || selectedCount === 0 || !data}
            className="flex-1 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white text-sm rounded-lg px-4 py-2 font-medium transition-colors">
            {applying ? 'Import…' : `Importer ${selectedCount} valeur${selectedCount > 1 ? 's' : ''}`}
          </button>
          <button onClick={onClose} className="bg-dark-700 hover:bg-dark-600 text-slate-400 text-sm rounded-lg px-4 py-2 transition-colors">
            Annuler
          </button>
        </div>
      </div>
    </div>
  )
}

// ── Calibration View ──────────────────────────────────────────────────────────

const DECAY_OPTIONS = ['exp', 'linear', 'step'] as const

function CalibrationView({ instrument, eventDetails }: {
  instrument: string
  eventDetails: Record<string, EventDetail[]>
}) {
  const [localCalib, setLocalCalib] = useState<Record<number, EventDetail['calibration']>>({})
  const [saving, setSaving]   = useState<Record<number, boolean>>({})
  const [saved, setSaved]     = useState<Record<number, boolean>>({})

  const allEvents = Object.values(eventDetails).flat()
    .sort((a, b) => b.lifecycle_factor - a.lifecycle_factor)

  if (allEvents.length === 0) {
    return (
      <div className="text-center py-12 text-slate-500 text-sm">
        Aucun event actif actuellement — les paramètres d'arêtes s'affichent ici quand des events sont en cours d'absorption.
      </div>
    )
  }

  function getCalib(ev: EventDetail) {
    return localCalib[ev.analysis_id] ?? ev.calibration
  }

  function setField(id: number, field: keyof EventDetail['calibration'], val: string | number) {
    setLocalCalib(prev => ({
      ...prev,
      [id]: { ...(prev[id] ?? allEvents.find(e => e.analysis_id === id)!.calibration), [field]: val },
    }))
    setSaved(prev => ({ ...prev, [id]: false }))
  }

  async function saveCalib(ev: EventDetail) {
    const calib = getCalib(ev)
    setSaving(prev => ({ ...prev, [ev.analysis_id]: true }))
    try {
      await api.put(`/instrument-models/${instrument}/events/${ev.analysis_id}/calibration`, calib)
      setSaved(prev => ({ ...prev, [ev.analysis_id]: true }))
    } finally {
      setSaving(prev => ({ ...prev, [ev.analysis_id]: false }))
    }
  }

  async function resetCalib(ev: EventDetail) {
    await api.delete(`/instrument-models/${instrument}/events/${ev.analysis_id}/calibration`)
    setLocalCalib(prev => { const n = {...prev}; delete n[ev.analysis_id]; return n })
    setSaved(prev => ({ ...prev, [ev.analysis_id]: false }))
  }

  return (
    <div className="space-y-2">
      <div className="text-xs text-slate-500 mb-3">
        Paramètres de decay (arêtes) des events actifs — modifiez le lifecycle par event indépendamment du template.
      </div>
      {allEvents.map(ev => {
        const cal = getCalib(ev)
        const lf  = ev.lifecycle_factor
        const isSaving = saving[ev.analysis_id]
        const isDirty  = !!localCalib[ev.analysis_id]
        const wasSaved = saved[ev.analysis_id]
        return (
          <div key={ev.analysis_id}
            className="rounded-lg border border-slate-700/30 bg-dark-800/40 p-3 space-y-2">
            {/* Event header */}
            <div className="flex items-center justify-between gap-3 flex-wrap">
              <div className="flex items-center gap-2 min-w-0">
                <span className="text-xs text-slate-500 shrink-0">{ev.start_date}</span>
                <span className="text-xs font-medium text-slate-300 truncate">{ev.title || ev.analysis_id}</span>
                <span className="text-xs text-slate-600 bg-slate-800 rounded px-1.5 py-0.5 shrink-0">{ev.days_since}j</span>
              </div>
              <div className="flex items-center gap-3 shrink-0">
                <span className={clsx('text-xs font-bold font-mono', pipColor(ev.pip_prediction))}>
                  {fmt(ev.pip_prediction)}p
                </span>
                <span className={clsx('text-xs font-mono', pipColor(ev.remaining_pips))}>
                  →{fmt(ev.remaining_pips)}p
                </span>
                {/* Lifecycle bar */}
                <div className="flex items-center gap-1">
                  <div className="w-20 h-1.5 rounded-full bg-slate-700/60 overflow-hidden">
                    <div className={clsx('h-full rounded-full', lf > 0.5 ? 'bg-emerald-500' : lf > 0.2 ? 'bg-amber-500' : 'bg-red-500')}
                      style={{width:`${Math.round(lf*100)}%`}}/>
                  </div>
                  <span className="text-xs text-slate-500 font-mono">{Math.round(lf*100)}%</span>
                </div>
              </div>
            </div>

            {/* Calibration inputs */}
            <div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
              <div>
                <label className="text-xs text-slate-500 block mb-0.5">Absorption (j)</label>
                <input type="number" min={1} max={365} value={cal.absorption_days}
                  onChange={e => setField(ev.analysis_id, 'absorption_days', parseInt(e.target.value)||1)}
                  className="w-full bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none focus:border-blue-500/60"/>
              </div>
              <div>
                <label className="text-xs text-slate-500 block mb-0.5">Rise (j)</label>
                <input type="number" min={0} max={30} value={cal.rise_days}
                  onChange={e => setField(ev.analysis_id, 'rise_days', parseInt(e.target.value)||0)}
                  className="w-full bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none focus:border-blue-500/60"/>
              </div>
              <div>
                <label className="text-xs text-slate-500 block mb-0.5">Plateau (j)</label>
                <input type="number" min={0} max={30} value={cal.plateau_days}
                  onChange={e => setField(ev.analysis_id, 'plateau_days', parseInt(e.target.value)||0)}
                  className="w-full bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none focus:border-blue-500/60"/>
              </div>
              <div>
                <label className="text-xs text-slate-500 block mb-0.5">Decay type</label>
                <select value={cal.decay_type}
                  onChange={e => setField(ev.analysis_id, 'decay_type', e.target.value)}
                  className="w-full bg-dark-700 border border-slate-700/40 rounded px-2 py-1 text-xs text-white focus:outline-none">
                  {DECAY_OPTIONS.map(d => <option key={d} value={d}>{d}</option>)}
                </select>
              </div>
            </div>

            {/* Actions */}
            <div className="flex items-center gap-2 justify-end">
              {isDirty && !wasSaved && (
                <button onClick={() => resetCalib(ev)}
                  className="text-xs text-slate-600 hover:text-slate-400 transition-colors">
                  ↩ Réinitialiser template
                </button>
              )}
              <button onClick={() => saveCalib(ev)} disabled={isSaving}
                className={clsx('flex items-center gap-1 px-3 py-1 rounded text-xs font-medium transition-colors',
                  wasSaved ? 'bg-emerald-700/40 text-emerald-400 border border-emerald-700/40'
                  : isDirty ? 'bg-blue-600 hover:bg-blue-500 text-white'
                  : 'bg-dark-700 text-slate-500 border border-slate-700/30')}>
                {isSaving ? 'Sauvegarde…' : wasSaved ? '✓ Sauvegardé' : 'Sauvegarder'}
              </button>
            </div>
          </div>
        )
      })}
    </div>
  )
}

// ── Main Page ──────────────────────────────────────────────────────────────────

type ViewMode = 'dag' | 'table' | 'timeline' | 'calibration'

export default function InstrumentModels() {
  const [instrument, setInstrument] = useState('EURUSD')
  const [view, setView]             = useState<ViewMode>('dag')
  const [state, setState]           = useState<ModelState | null>(null)
  const [loading, setLoading]       = useState(false)
  const [editNode, setEditNode]     = useState<ModelNode | null>(null)
  const [refreshKey, setRefreshKey] = useState(0)
  const [showSync, setShowSync]     = useState(false)

  const load = useCallback(() => {
    setLoading(true)
    api.get<ModelState>(`/instrument-models/${instrument}`)
      .then(r => setState(r.data))
      .catch(() => setState(null))
      .finally(() => setLoading(false))
  }, [instrument])

  useEffect(() => { load() }, [load, refreshKey])

  function onSaved() { setRefreshKey(k => k + 1) }

  const layerSummary = useMemo(() => {
    if (!state) return []
    return state.nodes
      .filter(n => n.node_type === 'intermediate')
      .map(n => ({
        ...n,
        pct: state.net_pips !== 0
          ? Math.round((n.pip_contribution / Math.abs(state.net_pips)) * 100)
          : 0,
      }))
      .sort((a, b) => Math.abs(b.pip_contribution) - Math.abs(a.pip_contribution))
  }, [state])

  const VIEW_BTNS: { key: ViewMode; Icon: typeof Network; label: string }[] = [
    { key: 'dag',         Icon: Network,   label: 'DAG' },
    { key: 'table',       Icon: Table2,    label: 'Tableau' },
    { key: 'timeline',    Icon: LineChart, label: 'Timeline' },
    { key: 'calibration', Icon: Activity,  label: 'Calibration' },
  ]

  return (
    <div className="p-4 space-y-4">
      {/* Header */}
      <div className="flex items-center justify-between gap-4">
        <div>
          <h1 className="text-xl font-bold text-white">Modèles Instruments</h1>
          <p className="text-sm text-slate-500 mt-0.5">
            Graphes causaux exhaustifs — propagation DAG 3 couches par instrument
          </p>
        </div>
        <div className="flex gap-2">
          <button onClick={() => setShowSync(true)}
            className="flex items-center gap-2 px-3 py-2 bg-blue-600/20 border border-blue-700/40 rounded-lg text-blue-400 hover:bg-blue-600/30 text-sm transition-colors font-medium">
            <Activity className="w-4 h-4"/> Sync Marché
          </button>
          <button onClick={() => setRefreshKey(k => k+1)} disabled={loading}
            className="flex items-center gap-2 px-3 py-2 bg-dark-700 border border-slate-700/40 rounded-lg text-slate-400 hover:text-white text-sm transition-colors disabled:opacity-40">
            <RefreshCw className={clsx('w-4 h-4', loading && 'animate-spin')}/> Actualiser
          </button>
        </div>
      </div>

      {/* Instrument tabs */}
      <div className="flex gap-1.5 flex-wrap">
        {INSTRUMENTS.map(inst => (
          <button key={inst} onClick={() => setInstrument(inst)}
            className={clsx('px-3 py-1.5 rounded-lg text-sm font-medium transition-all border',
              instrument === inst
                ? 'bg-blue-600 border-blue-500 text-white shadow-lg shadow-blue-900/30'
                : 'bg-dark-700 border-slate-700/40 text-slate-400 hover:text-white hover:border-slate-600')}>
            {inst}
          </button>
        ))}
      </div>

      {/* Synthetic Price Banner */}
      {state && (
        <div className="rounded-xl border border-slate-700/40 bg-dark-800/60 p-4 space-y-3">
          {/* Row 1 — synthetic price + direction + regime */}
          <div className="flex items-start justify-between gap-4 flex-wrap">
            <div>
              <div className="text-xs font-semibold text-slate-500 uppercase tracking-wider mb-1">
                {state.name} — Prix Synthétique
              </div>
              <div className="flex items-baseline gap-3">
                <span className={clsx('text-3xl font-bold font-mono tracking-tight',
                  state.direction === 'bullish' ? 'text-emerald-400'
                  : state.direction === 'bearish' ? 'text-red-400'
                  : 'text-slate-300')}>
                  {fmtPrice(state.synthetic_price, state.pip_to_price)}
                </span>
                <DirectionBadge direction={state.direction} pips={state.net_pips}/>
              </div>
              {/* Structural vs event breakdown */}
              <div className="flex gap-4 mt-2 text-xs">
                <div>
                  <span className="text-slate-500">Fondamental </span>
                  <span className="text-amber-400 font-mono font-semibold">
                    {fmtPrice(state.fundamental_level, state.pip_to_price)}
                  </span>
                  <span className="text-slate-600 ml-1">({fmt(state.structural_pips)}p)</span>
                </div>
                <div>
                  <span className="text-slate-500">Surprises </span>
                  <span className={clsx('font-mono font-semibold', pipColor(state.event_pips))}>
                    {fmt(state.event_pips)}p
                  </span>
                </div>
                <div>
                  <span className="text-slate-600">Intercept </span>
                  <span className="text-slate-500 font-mono">
                    {fmtPrice(state.price_intercept, state.pip_to_price)}
                  </span>
                </div>
              </div>
            </div>

            {/* Layer contributions */}
            {layerSummary.length > 0 && (
              <div className="flex gap-4 flex-wrap">
                {layerSummary.map(l => (
                  <div key={l.id} className="text-center min-w-[72px]">
                    <div className="text-xs text-slate-500 leading-tight mb-0.5">
                      {l.label.replace('▶ ', '').split(' ').slice(0, 2).join(' ')}
                    </div>
                    <div className={clsx('text-sm font-bold font-mono', pipColor(l.pip_contribution))}>{fmt(l.pip_contribution)}</div>
                    <div className="text-xs text-slate-600">{l.pct > 0 ? '+' : ''}{l.pct}%</div>
                  </div>
                ))}
              </div>
            )}

            <div className="flex flex-col gap-2 self-start">
              {state.regime && <RegimeBadge regime={state.regime}/>}
              <div className="text-xs text-slate-600 space-y-0.5">
                <div>{state.nodes.filter(n => n.source === 'events').length} events actifs</div>
                <div>{state.nodes.filter(n => n.source === 'manual').length} overrides manuels</div>
                <div className="text-slate-700">{state.at_date}</div>
              </div>
              {state.nodes.some(n => n.source === 'manual') && (
                <button
                  onClick={async () => {
                    if (!confirm('Effacer tous les overrides manuels ?')) return
                    await api.delete(`/instrument-models/${instrument}/overrides`)
                    setRefreshKey(k => k + 1)
                  }}
                  className="text-xs text-red-500/60 hover:text-red-400 transition-colors text-left">
                  ✕ Reset toutes valeurs
                </button>
              )}
            </div>
          </div>
        </div>
      )}

      {/* View selector */}
      {state && (
        <div className="flex items-center gap-1.5 border-b border-slate-700/30 pb-3">
          {VIEW_BTNS.map(({ key, Icon, label }) => (
            <button key={key} onClick={() => setView(key)}
              className={clsx('flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm transition-colors',
                view === key
                  ? 'bg-blue-600/20 text-blue-400 border border-blue-700/40'
                  : 'text-slate-500 hover:text-white border border-transparent')}>
              <Icon className="w-4 h-4"/> {label}
            </button>
          ))}
          <span className="ml-auto text-xs text-slate-600">
            {state.nodes.filter(n=>n.node_type==='input_event').length} EV
            · {state.nodes.filter(n=>n.node_type==='input_manual').length} M
            · {state.nodes.filter(n=>n.node_type==='intermediate').length} INT
            · {state.nodes.length} nœuds total
          </span>
        </div>
      )}

      {/* Main content */}
      {state && (
        <div className="rounded-xl border border-slate-700/40 bg-dark-800/60 p-4">
          {view === 'dag'         && <DagView nodes={state.nodes} instrument={instrument} onEdit={setEditNode} eventDetails={state.event_details || {}}/>}
          {view === 'table'       && <TableView nodes={state.nodes} onEdit={setEditNode}/>}
          {view === 'timeline'    && <TimelineView instrument={instrument}/>}
          {view === 'calibration' && <CalibrationView instrument={instrument} eventDetails={state.event_details || {}}/>}
        </div>
      )}

      {/* Quick stats */}
      {state && view !== 'timeline' && (
        <div className="grid grid-cols-2 md:grid-cols-4 gap-3">
          {[
            { label: 'Prix synthétique',  value: fmtPrice(state.synthetic_price, state.pip_to_price), sub: state.direction, color: state.direction === 'bullish' ? 'text-emerald-400' : state.direction === 'bearish' ? 'text-red-400' : 'text-white' },
            { label: 'Niveau fondamental',value: fmtPrice(state.fundamental_level, state.pip_to_price), sub: `struct: ${fmt(state.structural_pips)}p`, color: 'text-amber-400' },
            { label: 'Surprises events',  value: `${fmt(state.event_pips)} pips`, sub: `sur ${state.nodes.filter(n=>n.source==='events').length} events actifs`, color: pipColor(state.event_pips) },
            { label: 'Variables manuelles',value: `${state.nodes.filter(n=>n.source==='manual').length} actives`, sub: `sur ${state.nodes.filter(n=>n.node_type==='input_manual').length} disponibles`, color: 'text-violet-400' },
          ].map(card => (
            <div key={card.label} className="rounded-lg border border-slate-700/30 bg-dark-800/50 p-3">
              <div className="text-xs text-slate-500 mb-1">{card.label}</div>
              <div className={clsx('text-lg font-bold font-mono', card.color)}>{card.value}</div>
              <div className="text-xs text-slate-600">{card.sub}</div>
            </div>
          ))}
        </div>
      )}

      {!state && !loading && (
        <div className="rounded-xl border border-slate-700/40 bg-dark-800/60 p-8 text-center text-slate-500">
          Modèle introuvable pour {instrument}. Vérifier que le backend est démarré.
        </div>
      )}

      {loading && !state && (
        <div className="rounded-xl border border-slate-700/40 bg-dark-800/60 p-8 text-center text-slate-500">
          Chargement du modèle…
        </div>
      )}

      {editNode && (
        <NodeEditModal
          node={editNode} instrument={instrument}
          onClose={() => setEditNode(null)} onSaved={onSaved}
        />
      )}

      {showSync && (
        <SyncPanel
          instrument={instrument}
          onClose={() => setShowSync(false)}
          onApplied={(atDate) => {
            setShowSync(false)
            setRefreshKey(k => k + 1)
          }}
        />
      )}
    </div>
  )
}
