import { useState, useMemo, useCallback, useEffect, useRef } from 'react'
import { Link } from 'react-router-dom'
import clsx from 'clsx'
import {
  TreePine, Search, ChevronRight, ChevronDown,
  TrendingUp, TrendingDown, Zap, BookOpen, ExternalLink, RefreshCw,
  Layers, GitMerge, Tag, Trash2, Pencil, Check, X, ScanSearch, AlertTriangle, ArrowRightLeft,
} from 'lucide-react'
import {
  useAllPatterns,
  usePatternsByInstrument,
  useLastScores,
  useDeletePattern,
  usePatchPattern,
  useFindSimilarPattern,
  useMergePatterns,
  usePatternCalibration,
  validateTicker,
  type SimilarityResult,
  type PatternCalibration,
} from '../hooks/useApi'
import { INSTRUMENTS, INSTRUMENT_CATEGORIES } from '../constants/instruments'

// ── Local types ───────────────────────────────────────────────────────────────

interface SuggestedTrade {
  strategy: string
  underlying: string
  rationale: string
  strike_guidance?: string
  expected_move_pct?: number
}

interface HistoricalInstance {
  date: string
  event: string
  [key: string]: unknown
}

interface Pattern {
  id: string
  name: string
  description: string
  triggers: string[]
  keywords: string[]
  historical_instances: HistoricalInstance[]
  suggested_trades: SuggestedTrade[]
  asset_class: string
  expected_move_pct: number
  probability: number
  horizon_days: number
  source: string
  category?: string
  signal_direction?: string
  taxonomy_path?: string[]
  ai_quality_score?: number
  is_active?: number
  regime_tag?: string
  counter_of?: string
  backtest_hits?: number
  backtest_runs_count?: number
}

interface ScoreResult {
  pattern_id: string
  score?: number
  [key: string]: unknown
}

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

const ASSET_CLASS_LABELS: Record<string, string> = {
  energy: '⚡ Energy',
  metals: '🥇 Metals',
  agriculture: '🌾 Agri',
  equities: '📈 Equities',
  indices: '📊 Indices',
  forex: '💱 Forex',
  rates: '📉 Rates',
}

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

function probStars(prob: number): string {
  const n = prob >= 0.8 ? 3 : prob >= 0.6 ? 2 : 1
  return '★'.repeat(n) + '☆'.repeat(3 - n)
}

function formatTaxPath(path: string[] | undefined): string {
  if (!path || path.length === 0) return ''
  return path.join(' › ')
}

// ── Signal Direction Badge ────────────────────────────────────────────────────

function SignalBadge({ direction }: { direction?: string }) {
  if (direction === 'bullish') {
    return (
      <span className="inline-flex items-center gap-1 text-[10px] font-semibold px-1.5 py-0.5 rounded bg-emerald-900/40 border border-emerald-700/40 text-emerald-400">
        <TrendingUp className="w-3 h-3" /> Bullish
      </span>
    )
  }
  if (direction === 'bearish') {
    return (
      <span className="inline-flex items-center gap-1 text-[10px] font-semibold px-1.5 py-0.5 rounded bg-red-900/40 border border-red-700/40 text-red-400">
        <TrendingDown className="w-3 h-3" /> Bearish
      </span>
    )
  }
  return (
    <span className="inline-flex items-center gap-1 text-[10px] font-semibold px-1.5 py-0.5 rounded bg-amber-900/40 border border-amber-700/40 text-amber-400">
      <Zap className="w-3 h-3" /> Vol
    </span>
  )
}

// ── Category Badge ────────────────────────────────────────────────────────────

const CATEGORY_COLORS: Record<string, string> = {
  geopolitical: 'bg-red-900/40 border-red-700/40 text-red-300',
  macro:        'bg-blue-900/40 border-blue-700/40 text-blue-300',
  volatility:   'bg-amber-900/40 border-amber-700/40 text-amber-300',
  'risk-off':   'bg-orange-900/40 border-orange-700/40 text-orange-300',
  supply:       'bg-teal-900/40 border-teal-700/40 text-teal-300',
  credit:       'bg-purple-900/40 border-purple-700/40 text-purple-300',
  'geo-eco':    'bg-emerald-900/40 border-emerald-700/40 text-emerald-300',
  sentiment:    'bg-yellow-900/40 border-yellow-700/40 text-yellow-300',
}

function CategoryBadge({ category }: { category: string }) {
  const cls = CATEGORY_COLORS[category] ?? 'bg-violet-900/40 border-violet-700/40 text-violet-300'
  return (
    <span className={clsx('text-[10px] font-semibold px-1.5 py-0.5 rounded border capitalize', cls)}>
      {category}
    </span>
  )
}

// ── Maturity / Calibration Badge ─────────────────────────────────────────────

function MaturityBadge({ cal }: { cal: PatternCalibration }) {
  const w = cal.calibration_weight_pct
  const n = cal.n_mature_trades
  const hasData = n > 0

  const sourceColor = {
    pure_ai:     'text-slate-500 border-slate-700/40',
    early:       'text-sky-400 border-sky-700/40',
    mixed:       'text-violet-400 border-violet-700/40',
    data_driven: 'text-emerald-400 border-emerald-700/40',
  }[cal.source]

  const sourceLabel = {
    pure_ai:     'Pure AI',
    early:       'Early data',
    mixed:       'Mixed',
    data_driven: 'Data-driven',
  }[cal.source]

  return (
    <div className="border-t border-slate-700/30 pt-2 mt-1 space-y-1.5">
      <div className="flex items-center justify-between text-[10px]">
        <span className={clsx('font-semibold px-1.5 py-0.5 rounded border', sourceColor)}>
          {sourceLabel}
        </span>
        <span className="text-slate-500">
          {hasData ? `${n} trade${n > 1 ? 's' : ''} matures` : 'Aucun trade mature'}
        </span>
      </div>

      {/* Blend bar */}
      <div className="space-y-0.5">
        <div className="flex justify-between text-[10px] text-slate-500">
          <span>IA pure</span>
          <span className="text-slate-400 font-mono">{w.toFixed(0)}% observé</span>
          <span>Observé</span>
        </div>
        <div className="h-1.5 rounded-full bg-slate-700/60 overflow-hidden">
          <div
            className="h-full rounded-full bg-gradient-to-r from-slate-500 to-violet-500 transition-all duration-500"
            style={{ width: `${Math.max(w, 2)}%` }}
          />
        </div>
      </div>

      {hasData && (
        <div className="grid grid-cols-3 gap-1 text-[10px]">
          <div className="bg-dark-700/60 rounded px-1.5 py-1 text-center">
            <div className="text-slate-400 font-mono">{(cal.bayes_win_rate * 100).toFixed(0)}%</div>
            <div className="text-slate-600">Win rate</div>
          </div>
          <div className="bg-dark-700/60 rounded px-1.5 py-1 text-center">
            <div className="text-slate-400 font-mono">
              {cal.ai_estimate != null ? `${cal.ai_estimate.toFixed(0)}%` : '—'}
            </div>
            <div className="text-slate-600">IA estim.</div>
          </div>
          <div className={clsx('rounded px-1.5 py-1 text-center', cal.calibrated_expected_move ? 'bg-violet-900/30' : 'bg-dark-700/60')}>
            <div className={clsx('font-mono', cal.calibrated_expected_move ? 'text-violet-300' : 'text-slate-600')}>
              {cal.calibrated_expected_move != null ? `${cal.calibrated_expected_move.toFixed(0)}%` : '—'}
            </div>
            <div className="text-slate-600">Calibré</div>
          </div>
        </div>
      )}
    </div>
  )
}

// ── Pattern Card ──────────────────────────────────────────────────────────────

function PatternCard({
  pattern,
  highlightTrades,
  calibration,
}: {
  pattern: Pattern
  highlightTrades?: string[]
  calibration?: PatternCalibration
}) {
  const [expanded,      setExpanded]      = useState(false)
  const [confirmDel,    setConfirmDel]    = useState(false)
  const [editing,       setEditing]       = useState(false)
  const [editName,      setEditName]      = useState(pattern.name)
  const [editDesc,      setEditDesc]      = useState(pattern.description)
  const [editCat,       setEditCat]       = useState(pattern.category ?? '')
  const [editDir,       setEditDir]       = useState(pattern.signal_direction ?? '')
  const [editRegime,    setEditRegime]    = useState(pattern.regime_tag ?? '')
  const [simResult,     setSimResult]     = useState<SimilarityResult | null>(null)
  const [confirmMerge,  setConfirmMerge]  = useState(false)

  const { mutate: deletePattern, isPending: deleting }  = useDeletePattern()
  const { mutate: patchPattern,  isPending: saving }    = usePatchPattern()
  const { mutate: findSimilar,   isPending: scanning }  = useFindSimilarPattern()
  const { mutate: mergePatterns, isPending: merging }   = useMergePatterns()

  const trades = pattern.suggested_trades ?? []
  const instances = pattern.historical_instances ?? []
  const assetLabel = ASSET_CLASS_LABELS[pattern.asset_class] ?? pattern.asset_class
  const isEditable = pattern.source !== 'builtin'

  const moveSign = pattern.expected_move_pct >= 0 ? '+' : ''

  const startEdit = () => {
    setEditName(pattern.name)
    setEditDesc(pattern.description)
    setEditCat(pattern.category ?? '')
    setEditDir(pattern.signal_direction ?? '')
    setEditRegime(pattern.regime_tag ?? '')
    setEditing(true)
  }

  const saveEdit = () => {
    patchPattern({
      id: pattern.id,
      name: editName.trim() || pattern.name,
      description: editDesc.trim() || pattern.description,
      category: editCat || undefined,
      signal_direction: editDir || undefined,
      regime_tag: editRegime.trim() || undefined,
    }, { onSuccess: () => setEditing(false) })
  }

  return (
    <div className={clsx(
      'card flex flex-col gap-2 transition-colors',
      editing ? 'border-blue-600/40' : 'hover:border-slate-600/50',
    )}>
      {/* Header row */}
      <div className="flex items-start justify-between gap-2">
        <div className="flex items-center gap-2 flex-wrap">
          <SignalBadge direction={editing ? editDir : pattern.signal_direction} />
          <span className="text-[10px] font-mono text-slate-500">{pattern.id}</span>
          <span className="text-[10px] text-amber-400 tracking-wider">{probStars(pattern.probability)}</span>
          {!editing && pattern.category && <CategoryBadge category={pattern.category} />}
          {!editing && pattern.regime_tag && (
            <span className="text-[10px] text-violet-400 font-mono">#{pattern.regime_tag}</span>
          )}
        </div>
        <div className="flex items-center gap-1.5 shrink-0">
          {pattern.ai_quality_score != null && !editing && (
            <span className="text-[10px] font-mono text-slate-500">
              AI <span className="text-slate-300 font-semibold">{pattern.ai_quality_score}</span>/100
            </span>
          )}
          {isEditable && !editing && !confirmDel && (
            <>
              <button
                onClick={() => {
                  setSimResult(null)
                  setConfirmMerge(false)
                  findSimilar(pattern.id, { onSuccess: setSimResult })
                }}
                disabled={scanning}
                className="text-slate-700 hover:text-violet-400 transition-colors p-0.5 rounded"
                title="Find Similar / Deduplicate"
              >
                {scanning ? <RefreshCw className="w-3.5 h-3.5 animate-spin" /> : <ScanSearch className="w-3.5 h-3.5" />}
              </button>
              <button onClick={startEdit} className="text-slate-700 hover:text-blue-400 transition-colors p-0.5 rounded" title="Edit">
                <Pencil className="w-3.5 h-3.5" />
              </button>
            </>
          )}
          {isEditable && editing && (
            <>
              <button onClick={saveEdit} disabled={saving}
                className="text-slate-700 hover:text-emerald-400 transition-colors p-0.5 rounded" title="Save">
                <Check className="w-3.5 h-3.5" />
              </button>
              <button onClick={() => setEditing(false)}
                className="text-slate-700 hover:text-slate-300 transition-colors p-0.5 rounded" title="Cancel">
                <X className="w-3.5 h-3.5" />
              </button>
            </>
          )}
          {isEditable && !editing && (
            confirmDel ? (
              <div className="flex items-center gap-1">
                <button onClick={() => deletePattern(pattern.id)} disabled={deleting}
                  className="text-[10px] px-1.5 py-0.5 rounded bg-red-900/50 border border-red-700/60 text-red-400 hover:bg-red-900/70 transition-colors">
                  {deleting ? '…' : 'Confirm'}
                </button>
                <button onClick={() => setConfirmDel(false)}
                  className="text-[10px] px-1.5 py-0.5 rounded border border-slate-700 text-slate-500 hover:text-slate-300 transition-colors">
                  Cancel
                </button>
              </div>
            ) : (
              <button onClick={() => setConfirmDel(true)}
                className="text-slate-700 hover:text-red-400 transition-colors p-0.5 rounded" title="Delete">
                <Trash2 className="w-3.5 h-3.5" />
              </button>
            )
          )}
        </div>
      </div>

      {/* Edit mode fields */}
      {editing ? (
        <div className="flex flex-col gap-2">
          <input
            value={editName}
            onChange={e => setEditName(e.target.value)}
            className="input text-sm font-semibold py-1.5 w-full"
            placeholder="Pattern name"
          />
          <textarea
            value={editDesc}
            onChange={e => setEditDesc(e.target.value)}
            rows={3}
            className="input text-xs py-1.5 w-full resize-none"
            placeholder="Description"
          />
          <div className="flex gap-2">
            <select value={editDir} onChange={e => setEditDir(e.target.value)} className="input text-xs py-1.5 flex-1">
              <option value="">Direction…</option>
              <option value="bullish">↑ Bullish</option>
              <option value="bearish">↓ Bearish</option>
              <option value="neutral">⚡ Vol / Neutral</option>
            </select>
            <input
              value={editCat}
              onChange={e => setEditCat(e.target.value)}
              className="input text-xs py-1.5 flex-1"
              placeholder="Category (geopolitical…)"
            />
            <input
              value={editRegime}
              onChange={e => setEditRegime(e.target.value)}
              className="input text-xs py-1.5 flex-1 font-mono"
              placeholder="#regime"
            />
          </div>
        </div>
      ) : (
        <>
          {/* Name + description */}
          <div>
            <div className="text-sm font-semibold text-white leading-snug">{pattern.name}</div>
            <div className="text-xs text-slate-400 mt-0.5 line-clamp-2">{pattern.description}</div>
          </div>
        </>
      )}

      {/* Stats row */}
      <div className="border-t border-slate-700/40 pt-2 flex flex-wrap gap-x-3 gap-y-1 text-xs text-slate-400">
        <span>
          Expected:{' '}
          <span className={clsx('font-mono font-medium', pattern.expected_move_pct >= 0 ? 'text-emerald-400' : 'text-red-400')}>
            {moveSign}{pattern.expected_move_pct}%
          </span>
        </span>
        <span>Horizon: <span className="text-slate-300">{pattern.horizon_days}d</span></span>
        <span>Asset: <span className="text-slate-300">{assetLabel}</span></span>
        <span>Prob: <span className="text-slate-300">{Math.round(pattern.probability * 100)}%</span></span>
      </div>

      {/* Trades row */}
      {trades.length > 0 && (
        <div className="border-t border-slate-700/40 pt-2">
          <div className="text-[10px] text-slate-500 mb-1 uppercase tracking-wider">Trades</div>
          <div className="flex flex-wrap gap-1">
            {trades.map((t, i) => {
              const isHighlighted =
                highlightTrades && highlightTrades.some(u =>
                  t.underlying.toLowerCase().includes(u.toLowerCase())
                )
              return (
                <span
                  key={i}
                  className={clsx(
                    'text-[10px] px-1.5 py-0.5 rounded border font-mono',
                    isHighlighted
                      ? 'bg-blue-900/40 border-blue-600/50 text-blue-300'
                      : 'bg-dark-700/60 border-slate-700/30 text-slate-400'
                  )}
                >
                  {t.strategy} {t.underlying}
                </span>
              )
            })}
          </div>
        </div>
      )}

      {/* Historical instances (collapsible) */}
      {instances.length > 0 && (
        <div className="border-t border-slate-700/40 pt-2">
          <button
            className="flex items-center gap-1 text-xs text-slate-500 hover:text-slate-300 transition-colors w-full text-left"
            onClick={() => setExpanded(v => !v)}
          >
            {expanded ? <ChevronDown className="w-3 h-3" /> : <ChevronRight className="w-3 h-3" />}
            Historical instances ({instances.length})
          </button>
          {expanded && (
            <ul className="mt-2 space-y-1">
              {instances.map((inst, i) => (
                <li key={i} className="text-xs text-slate-400 flex gap-2">
                  <span className="font-mono text-slate-500 shrink-0">{inst.date}</span>
                  <span>{inst.event}</span>
                </li>
              ))}
            </ul>
          )}
        </div>
      )}

      {/* Maturity & calibration */}
      {calibration && <MaturityBadge cal={calibration} />}

      {/* Similarity result panel */}
      {simResult && (
        <div className={clsx(
          'border-t pt-3 mt-1 space-y-2',
          simResult.recommendation === 'merge_as_instance' ? 'border-violet-700/40' :
          simResult.recommendation === 'counter_scenario'  ? 'border-amber-700/40'  :
                                                             'border-emerald-700/40',
        )}>
          <div className="flex items-start justify-between gap-2">
            <div className="flex items-center gap-2">
              {simResult.recommendation === 'merge_as_instance' && (
                <span className="inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded bg-violet-900/40 border border-violet-700/50 text-violet-300 uppercase tracking-wider">
                  <GitMerge className="w-3 h-3" /> Duplicate détecté
                </span>
              )}
              {simResult.recommendation === 'counter_scenario' && (
                <span className="inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded bg-amber-900/40 border border-amber-700/50 text-amber-300 uppercase tracking-wider">
                  <ArrowRightLeft className="w-3 h-3" /> Counter-scenario
                </span>
              )}
              {simResult.recommendation === 'new_pattern' && (
                <span className="inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded bg-emerald-900/40 border border-emerald-700/50 text-emerald-300 uppercase tracking-wider">
                  <Check className="w-3 h-3" /> Pattern unique
                </span>
              )}
              <span className="text-[10px] text-slate-500">confiance {simResult.confidence}%</span>
            </div>
            <button onClick={() => { setSimResult(null); setConfirmMerge(false) }}
              className="text-slate-600 hover:text-slate-400 p-0.5">
              <X className="w-3.5 h-3.5" />
            </button>
          </div>

          {simResult.match_name && (
            <div className="text-xs text-slate-400">
              Match : <span className="text-white font-medium">{simResult.match_name}</span>
              {simResult.suggested_regime_tag && (
                <span className="ml-2 text-violet-400 font-mono">#{simResult.suggested_regime_tag}</span>
              )}
            </div>
          )}

          <p className="text-xs text-slate-400 leading-relaxed">{simResult.reasoning}</p>

          {/* Actions */}
          {simResult.recommendation === 'merge_as_instance' && simResult.match_id && (
            confirmMerge ? (
              <div className="bg-red-900/20 border border-red-700/40 rounded p-2.5 space-y-2">
                <div className="flex items-start gap-2 text-xs text-red-300">
                  <AlertTriangle className="w-3.5 h-3.5 shrink-0 mt-0.5" />
                  <span>
                    <strong>"{pattern.name}"</strong> sera supprimé. Ses instances historiques, scores et trades seront fusionnés dans <strong>"{simResult.match_name}"</strong>. Cette action est irréversible.
                  </span>
                </div>
                <div className="flex gap-2">
                  <button
                    onClick={() => mergePatterns(
                      { keep_id: simResult.match_id!, discard_id: pattern.id },
                      { onSuccess: () => { setSimResult(null); setConfirmMerge(false) } }
                    )}
                    disabled={merging}
                    className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-violet-700/50 hover:bg-violet-600/60 border border-violet-600/50 text-violet-200 transition-colors disabled:opacity-50"
                  >
                    <GitMerge className="w-3 h-3" />
                    {merging ? 'Fusion…' : 'Confirmer la fusion'}
                  </button>
                  <button onClick={() => setConfirmMerge(false)}
                    className="px-3 py-1.5 text-xs rounded border border-slate-700 text-slate-500 hover:text-slate-300 transition-colors">
                    Annuler
                  </button>
                </div>
              </div>
            ) : (
              <button
                onClick={() => setConfirmMerge(true)}
                className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-violet-900/30 hover:bg-violet-800/40 border border-violet-700/50 text-violet-300 transition-colors"
              >
                <GitMerge className="w-3 h-3" />
                Fusionner dans "{simResult.match_name}"
              </button>
            )
          )}

          {simResult.recommendation === 'counter_scenario' && simResult.match_id && (
            <button
              onClick={() => patchPattern(
                { id: pattern.id, regime_tag: simResult.suggested_regime_tag || pattern.regime_tag },
                { onSuccess: () => setSimResult(null) }
              )}
              disabled={saving}
              className="flex items-center gap-1.5 px-3 py-1.5 text-xs rounded bg-amber-900/30 hover:bg-amber-800/40 border border-amber-700/50 text-amber-300 transition-colors"
            >
              <Tag className="w-3 h-3" />
              Appliquer le regime tag "{simResult.suggested_regime_tag}"
            </button>
          )}
        </div>
      )}
    </div>
  )
}

// ── Faceted Search ────────────────────────────────────────────────────────────

function FilterChip({
  active, label, onClick, color,
}: { active: boolean; label: string; onClick: () => void; color?: string }) {
  return (
    <button
      onClick={onClick}
      className={clsx(
        'text-xs px-2.5 py-1 rounded-full border transition-colors whitespace-nowrap',
        active
          ? color ?? 'bg-blue-600/30 border-blue-500/50 text-blue-300'
          : 'border-slate-700/40 text-slate-500 hover:text-slate-300 hover:border-slate-600',
      )}
    >
      {label}
    </button>
  )
}

const DIR_COLORS: Record<string, string> = {
  bullish: 'bg-emerald-900/40 border-emerald-600/50 text-emerald-300',
  up:      'bg-emerald-900/40 border-emerald-600/50 text-emerald-300',
  bearish: 'bg-red-900/40 border-red-600/50 text-red-300',
  down:    'bg-red-900/40 border-red-600/50 text-red-300',
  neutral: 'bg-amber-900/40 border-amber-600/50 text-amber-300',
}

function FacetedSearch({ patterns, calibrationMap = {} }: { patterns: Pattern[]; calibrationMap?: Record<string, PatternCalibration> }) {
  const [query,      setQuery]      = useState('')
  const [direction,  setDirection]  = useState('')
  const [assetClass, setAssetClass] = useState('')
  const [category,   setCategory]   = useState('')
  const [horizon,    setHorizon]    = useState('')
  const [source,     setSource]     = useState('')
  const [sortBy,     setSortBy]     = useState<'ai_score' | 'probability' | 'expected_move' | 'date'>('ai_score')

  const allDirections = useMemo(() => {
    const s = new Set(patterns.map(p => p.signal_direction).filter(Boolean) as string[])
    return Array.from(s).sort()
  }, [patterns])

  const allAssets = useMemo(() => {
    const s = new Set(patterns.map(p => p.asset_class).filter(Boolean))
    return Array.from(s).sort()
  }, [patterns])

  const allCategories = useMemo(() => {
    const s = new Set(patterns.map(p => p.category).filter(Boolean) as string[])
    return Array.from(s).sort()
  }, [patterns])

  const filtered = useMemo(() => {
    let list = patterns
    if (query.trim()) {
      const q = query.trim().toLowerCase()
      list = list.filter(p =>
        p.name.toLowerCase().includes(q) ||
        p.description.toLowerCase().includes(q) ||
        (p.regime_tag ?? '').toLowerCase().includes(q),
      )
    }
    if (direction)  list = list.filter(p => p.signal_direction === direction)
    if (assetClass) list = list.filter(p => p.asset_class === assetClass)
    if (category)   list = list.filter(p => p.category === category)
    if (horizon === 'short')  list = list.filter(p => p.horizon_days < 30)
    if (horizon === 'medium') list = list.filter(p => p.horizon_days >= 30 && p.horizon_days <= 90)
    if (horizon === 'long')   list = list.filter(p => p.horizon_days > 90)
    if (source) list = list.filter(p => p.source === source)
    return [...list].sort((a, b) => {
      if (sortBy === 'ai_score')     return (b.ai_quality_score ?? -1) - (a.ai_quality_score ?? -1)
      if (sortBy === 'probability')  return b.probability - a.probability
      if (sortBy === 'expected_move') return Math.abs(b.expected_move_pct) - Math.abs(a.expected_move_pct)
      return ((b as any).created_at ?? '').localeCompare((a as any).created_at ?? '')
    })
  }, [patterns, query, direction, assetClass, category, horizon, source, sortBy])

  const hasFilters = !!(query || direction || assetClass || category || horizon || source)
  const toggle = (cur: string, val: string, set: (v: string) => void) => set(cur === val ? '' : val)

  return (
    <div className="flex flex-col gap-4">
      {/* Search + sort */}
      <div className="flex items-center gap-3">
        <div className="relative flex-1 max-w-lg">
          <Search className="w-3.5 h-3.5 absolute left-3 top-1/2 -translate-y-1/2 text-slate-500" />
          <input
            type="text"
            placeholder="Search name, description, or #regime…"
            value={query}
            onChange={e => setQuery(e.target.value)}
            className="input text-sm pl-9 py-2 w-full"
          />
        </div>
        <select
          value={sortBy}
          onChange={e => setSortBy(e.target.value as typeof sortBy)}
          className="input text-xs py-2 pr-6 pl-2 shrink-0"
        >
          <option value="ai_score">AI Score ↓</option>
          <option value="probability">Probability ↓</option>
          <option value="expected_move">Expected Move ↓</option>
          <option value="date">Date Added ↓</option>
        </select>
      </div>

      {/* Filter panel */}
      <div className="flex flex-col gap-2.5 bg-dark-700/30 border border-slate-700/30 rounded-lg px-4 py-3">
        {/* Direction */}
        <div className="flex items-center gap-2 flex-wrap">
          <span className="text-[10px] uppercase tracking-widest text-slate-600 w-20 shrink-0">Direction</span>
          <FilterChip active={!direction} label="All" onClick={() => setDirection('')} />
          {allDirections.map(d => (
            <FilterChip key={d} active={direction === d}
              label={d === 'bullish' || d === 'up' ? `↑ ${d}` : d === 'bearish' || d === 'down' ? `↓ ${d}` : `⚡ ${d}`}
              onClick={() => toggle(direction, d, setDirection)}
              color={DIR_COLORS[d]} />
          ))}
        </div>

        {/* Asset class */}
        <div className="flex items-center gap-2 flex-wrap">
          <span className="text-[10px] uppercase tracking-widest text-slate-600 w-20 shrink-0">Asset</span>
          <FilterChip active={!assetClass} label="All" onClick={() => setAssetClass('')} />
          {allAssets.map(a => (
            <FilterChip key={a} active={assetClass === a}
              label={ASSET_CLASS_LABELS[a] ?? a}
              onClick={() => toggle(assetClass, a, setAssetClass)} />
          ))}
        </div>

        {/* Category */}
        {allCategories.length > 0 && (
          <div className="flex items-center gap-2 flex-wrap">
            <span className="text-[10px] uppercase tracking-widest text-slate-600 w-20 shrink-0">Category</span>
            <FilterChip active={!category} label="All" onClick={() => setCategory('')} />
            {allCategories.map(c => (
              <FilterChip key={c} active={category === c} label={c}
                onClick={() => toggle(category, c, setCategory)}
                color={CATEGORY_COLORS[c] ?? 'bg-violet-900/40 border-violet-600/50 text-violet-300'} />
            ))}
          </div>
        )}

        {/* Horizon */}
        <div className="flex items-center gap-2 flex-wrap">
          <span className="text-[10px] uppercase tracking-widest text-slate-600 w-20 shrink-0">Horizon</span>
          <FilterChip active={!horizon} label="All" onClick={() => setHorizon('')} />
          <FilterChip active={horizon === 'short'}  label="< 30d"  onClick={() => toggle(horizon, 'short',  setHorizon)} />
          <FilterChip active={horizon === 'medium'} label="30–90d" onClick={() => toggle(horizon, 'medium', setHorizon)} />
          <FilterChip active={horizon === 'long'}   label="> 90d"  onClick={() => toggle(horizon, 'long',   setHorizon)} />
        </div>

        {/* Source */}
        <div className="flex items-center gap-2 flex-wrap">
          <span className="text-[10px] uppercase tracking-widest text-slate-600 w-20 shrink-0">Source</span>
          <FilterChip active={!source} label="All" onClick={() => setSource('')} />
          <FilterChip active={source === 'builtin'}    label="Built-in"   onClick={() => toggle(source, 'builtin',    setSource)} />
          <FilterChip active={source === 'backtested'} label="Backtested" onClick={() => toggle(source, 'backtested', setSource)}
            color="bg-violet-900/40 border-violet-600/50 text-violet-300" />
        </div>
      </div>

      {/* Count + clear */}
      <div className="flex items-center gap-3">
        <span className="text-xs text-slate-500">{filtered.length} pattern{filtered.length !== 1 ? 's' : ''}</span>
        {hasFilters && (
          <button
            onClick={() => { setQuery(''); setDirection(''); setAssetClass(''); setCategory(''); setHorizon(''); setSource('') }}
            className="text-xs text-slate-600 hover:text-slate-400 underline transition-colors"
          >
            Clear all filters
          </button>
        )}
      </div>

      {/* Pattern grid */}
      {filtered.length === 0 ? (
        <div className="flex items-center justify-center py-24 text-slate-600 text-sm">
          No patterns match these filters
        </div>
      ) : (
        <div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-3">
          {filtered.map(p => <PatternCard key={p.id} pattern={p} calibration={calibrationMap[p.id]} />)}
        </div>
      )}
    </div>
  )
}

// ── Instrument Lens ───────────────────────────────────────────────────────────

function InstrumentRow({ pattern, searchTerm }: { pattern: Pattern; searchTerm: string }) {
  const matchingTrades = pattern.suggested_trades?.filter(t =>
    t.underlying.toLowerCase().includes(searchTerm.toLowerCase())
  ) ?? []

  return (
    <div className="card hover:border-slate-600/50 transition-colors">
      <div className="flex items-start justify-between gap-4 flex-wrap">
        {/* Left: name + path */}
        <div className="flex-1 min-w-0">
          <div className="flex items-center gap-2 flex-wrap mb-0.5">
            <SignalBadge direction={pattern.signal_direction} />
            <span className="text-sm font-semibold text-white">{pattern.name}</span>
          </div>
          {pattern.taxonomy_path && pattern.taxonomy_path.length > 0 && (
            <div className="text-[10px] text-slate-500 font-mono mt-0.5">
              {formatTaxPath(pattern.taxonomy_path)}
            </div>
          )}
          {/* Matching trades highlighted */}
          {matchingTrades.length > 0 && (
            <div className="mt-2 flex flex-wrap gap-1">
              {matchingTrades.map((t, i) => (
                <div
                  key={i}
                  className="text-xs px-2 py-1 rounded border bg-blue-900/30 border-blue-600/40 text-blue-300"
                >
                  <span className="font-semibold">{t.strategy}</span>
                  <span className="text-blue-400 mx-1">·</span>
                  <span className="font-mono">{t.underlying}</span>
                  {t.expected_move_pct != null && (
                    <span className="ml-2 text-slate-400">
                      {t.expected_move_pct >= 0 ? '+' : ''}{t.expected_move_pct}%
                    </span>
                  )}
                  {t.rationale && (
                    <span className="ml-2 text-slate-500 italic text-[10px]">{t.rationale}</span>
                  )}
                </div>
              ))}
            </div>
          )}
        </div>

        {/* Right: stats */}
        <div className="flex flex-col gap-1 items-end text-xs text-slate-400 shrink-0">
          <span>
            Prob:{' '}
            <span className="font-semibold text-slate-300">{Math.round(pattern.probability * 100)}%</span>
          </span>
          <span>
            Move:{' '}
            <span className={clsx('font-mono font-semibold', pattern.expected_move_pct >= 0 ? 'text-emerald-400' : 'text-red-400')}>
              {pattern.expected_move_pct >= 0 ? '+' : ''}{pattern.expected_move_pct}%
            </span>
          </span>
          <span>Horizon: <span className="text-slate-300">{pattern.horizon_days}d</span></span>
        </div>
      </div>
    </div>
  )
}

function InstrumentLens({ patterns }: { patterns: Pattern[] }) {
  const [searchTerm, setSearchTerm]     = useState('')
  const [activeCategory, setActiveCategory] = useState<string | null>(null)
  const [customInput, setCustomInput]     = useState('')
  const [validating, setValidating]       = useState(false)
  const [tickerError, setTickerError]     = useState<string | null>(null)
  const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)

  // Selected ticker — either clicked from grid or typed
  const [selectedTicker, setSelectedTicker] = useState('')

  const { data: instrumentData } = usePatternsByInstrument(selectedTicker)
  const instrumentPatterns = instrumentData as Pattern[] | undefined

  const results = useMemo(() => {
    if (!selectedTicker) return []
    const source: Pattern[] = (instrumentPatterns && instrumentPatterns.length > 0)
      ? instrumentPatterns
      : patterns.filter(p =>
          (p.suggested_trades ?? []).some(t =>
            t.underlying.toLowerCase().includes(selectedTicker.toLowerCase())
          )
        )
    return [...source].sort((a, b) => b.probability - a.probability)
  }, [selectedTicker, instrumentPatterns, patterns])

  // Deduplicate instruments list
  const uniqueInstruments = useMemo(() => {
    const seen = new Set<string>()
    return INSTRUMENTS.filter(i => { if (seen.has(i.ticker)) return false; seen.add(i.ticker); return true })
  }, [])

  const filteredInstruments = useMemo(() => {
    const byCategory = activeCategory
      ? uniqueInstruments.filter(i => i.category === activeCategory)
      : uniqueInstruments
    if (!searchTerm) return byCategory
    const q = searchTerm.toLowerCase()
    return byCategory.filter(i =>
      i.ticker.toLowerCase().includes(q) || i.name.toLowerCase().includes(q)
    )
  }, [uniqueInstruments, activeCategory, searchTerm])

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

  return (
    <div className="flex flex-col gap-4">
      {/* ── Category pills ── */}
      <div className="flex flex-wrap gap-1.5">
        <button onClick={() => setActiveCategory(null)}
          className={clsx('text-xs px-2.5 py-1 rounded-full border transition-colors', {
            'bg-blue-600 border-blue-500 text-white': activeCategory === null,
            'border-slate-700 text-slate-500 hover:text-slate-300': activeCategory !== null,
          })}>All</button>
        {INSTRUMENT_CATEGORIES.map(cat => (
          <button key={cat} onClick={() => setActiveCategory(activeCategory === cat ? null : cat)}
            className={clsx('text-xs px-2.5 py-1 rounded-full border transition-colors', {
              'bg-blue-600 border-blue-500 text-white': activeCategory === cat,
              'border-slate-700 text-slate-500 hover:text-slate-300': activeCategory !== cat,
            })}>{cat}</button>
        ))}
      </div>

      {/* ── Search + custom ── */}
      <div className="flex items-center gap-2">
        <div className="relative flex-1">
          <Search className="w-3.5 h-3.5 absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-500" />
          <input type="text" value={searchTerm} onChange={e => setSearchTerm(e.target.value)}
            placeholder="Search instruments…"
            className="w-full pl-8 pr-3 py-1.5 text-xs bg-dark-700 border border-slate-700/50 rounded text-slate-200 placeholder-slate-600 focus:outline-none focus:border-blue-600" />
        </div>
        <div className="flex flex-col gap-1">
          <div className="flex gap-1">
            <input type="text" value={customInput}
              onChange={e => { setCustomInput(e.target.value); setTickerError(null) }}
              onKeyDown={e => e.key === 'Enter' && handleCustom()}
              placeholder="Custom ticker…"
              className={clsx(
                'w-32 px-2 py-1.5 text-xs bg-dark-700 border rounded text-slate-200 placeholder-slate-600 focus:outline-none font-mono uppercase',
                tickerError ? 'border-red-500/70 focus:border-red-400' : 'border-slate-700/50 focus:border-blue-600'
              )} />
            <button onClick={handleCustom} disabled={!customInput.trim() || validating}
              className="px-2.5 py-1.5 text-xs bg-blue-700 hover:bg-blue-600 disabled:opacity-40 text-white rounded transition-colors flex items-center gap-1">
              {validating ? <><RefreshCw className="w-3 h-3 animate-spin" /> …</> : 'Go'}
            </button>
          </div>
          {tickerError && (
            <p className="text-[9px] text-red-400 max-w-[200px] leading-tight">{tickerError}</p>
          )}
        </div>
        {selectedTicker && (
          <button onClick={() => setSelectedTicker('')}
            className="text-xs text-slate-500 hover:text-slate-300 transition-colors">Clear</button>
        )}
      </div>

      {/* ── Instrument grid ── */}
      {!selectedTicker && (
        <div className="grid grid-cols-4 gap-1.5">
          {filteredInstruments.map(inst => (
            <button key={inst.ticker} onClick={() => setSelectedTicker(inst.ticker)}
              className="text-left px-2.5 py-2 rounded border border-slate-700/40 bg-dark-700/40 hover:border-blue-600/60 hover:bg-blue-900/20 transition-colors group">
              <div className="flex items-center gap-1 mb-0.5">
                {inst.flag && <span className="text-sm">{inst.flag}</span>}
                <span className="text-[10px] font-mono text-blue-400 group-hover:text-blue-300">{inst.ticker}</span>
              </div>
              <div className="text-[10px] text-slate-400 truncate">{inst.name}</div>
            </button>
          ))}
          {filteredInstruments.length === 0 && (
            <div className="col-span-4 text-center py-8 text-xs text-slate-600">No instruments match</div>
          )}
        </div>
      )}

      {/* ── Results ── */}
      {selectedTicker && (
        results.length === 0 ? (
          <div className="flex flex-col items-center justify-center py-16 text-slate-600 text-sm gap-2">
            <Zap className="w-6 h-6 opacity-30" />
            <p>No patterns found for <span className="font-mono text-slate-500">{selectedTicker}</span></p>
            <button onClick={() => setSelectedTicker('')} className="text-xs text-blue-500 hover:text-blue-400 mt-1">← Back to list</button>
          </div>
        ) : (
          <>
            <div className="flex items-center gap-3 text-sm text-slate-400">
              <button onClick={() => setSelectedTicker('')} className="text-xs text-blue-500 hover:text-blue-400">← Back</button>
              <span className="font-semibold text-slate-200">{results.length}</span> pattern{results.length !== 1 ? 's' : ''} influence{' '}
              <span className="font-mono text-blue-400">{selectedTicker}</span>, ranked by probability
            </div>
            <div className="flex flex-col gap-3">
              {results.map(p => (
                <InstrumentRow key={p.id} pattern={p} searchTerm={selectedTicker} />
              ))}
            </div>
          </>
        )
      )}
    </div>
  )
}

// ── Regime View ───────────────────────────────────────────────────────────────

function RegimeCard({ pattern, allPatterns }: { pattern: Pattern; allPatterns: Pattern[] }) {
  const [open, setOpen] = useState(false)
  const isCounter = !!pattern.counter_of
  const counterOf = isCounter ? allPatterns.find(p => p.id === pattern.counter_of) : null
  const instances = Array.isArray(pattern.historical_instances) ? pattern.historical_instances : []
  const hitRate = pattern.backtest_runs_count
    ? Math.round(((pattern.backtest_hits ?? 0) / pattern.backtest_runs_count) * 100)
    : null

  return (
    <div className={clsx(
      'border rounded-lg overflow-hidden transition-colors',
      isCounter ? 'border-orange-700/50 bg-orange-900/5' : 'border-slate-700/50 bg-dark-700/30'
    )}>
      <button className="w-full flex items-center gap-3 p-3 text-left hover:bg-slate-800/30 transition-colors"
        onClick={() => setOpen(v => !v)}>
        {open ? <ChevronDown className="w-3.5 h-3.5 text-slate-500 shrink-0" /> : <ChevronRight className="w-3.5 h-3.5 text-slate-500 shrink-0" />}
        {isCounter
          ? <Layers className="w-4 h-4 text-orange-400 shrink-0" />
          : <BookOpen className="w-4 h-4 text-violet-400 shrink-0" />}
        <div className="flex-1 min-w-0">
          <div className="flex items-center gap-2 flex-wrap">
            <span className="text-sm font-semibold text-slate-100 truncate">{pattern.name}</span>
            {isCounter && (
              <span className="text-[10px] border border-orange-600 text-orange-400 rounded px-1.5 py-0.5 shrink-0">
                counter of {counterOf?.name ?? pattern.counter_of}
              </span>
            )}
            {pattern.signal_direction && (
              <span className={clsx('text-[10px] rounded px-1.5 py-0.5 shrink-0',
                pattern.signal_direction === 'up' ? 'bg-emerald-900/40 text-emerald-400' :
                pattern.signal_direction === 'down' ? 'bg-red-900/40 text-red-400' : 'bg-slate-700 text-slate-400')}>
                {pattern.signal_direction === 'up' ? '↑' : pattern.signal_direction === 'down' ? '↓' : '↕'} {pattern.signal_direction}
              </span>
            )}
            {hitRate !== null && (
              <span className={clsx('text-[10px] font-mono shrink-0',
                hitRate >= 60 ? 'text-emerald-400' : hitRate >= 40 ? 'text-yellow-400' : 'text-red-400')}>
                {hitRate}% ({pattern.backtest_hits}/{pattern.backtest_runs_count})
              </span>
            )}
          </div>
          <p className="text-[10px] text-slate-500 truncate mt-0.5">{pattern.description}</p>
        </div>
        <span className="text-[10px] text-slate-600 shrink-0 font-mono">{instances.length} events</span>
      </button>
      {open && instances.length > 0 && (
        <div className="border-t border-slate-700/40 p-3 pt-2">
          <div className="text-[10px] text-slate-500 mb-2 font-semibold uppercase tracking-wide">Historical instances</div>
          <div className="flex flex-col gap-1.5">
            {instances.map((inst: any, i: number) => (
              <div key={i} className="flex items-center gap-3 text-[11px]">
                <span className="text-slate-500 font-mono w-24 shrink-0">{inst.date ?? '—'}</span>
                <span className="text-slate-300 flex-1 truncate">{inst.event_name ?? inst.event ?? inst.theme ?? '—'}</span>
                {inst.hit !== undefined && (
                  <span className={clsx('shrink-0', inst.hit_type === 'full' || inst.hit === true ? 'text-emerald-400' : inst.hit_type === 'partial' ? 'text-amber-400' : 'text-red-400')}>
                    {inst.hit_type === 'full' || inst.hit === true ? '✓' : inst.hit_type === 'partial' ? '~' : '✗'}
                  </span>
                )}
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  )
}

function RegimeView({ patterns }: { patterns: Pattern[] }) {
  const groups = useMemo(() => {
    const byRegime: Record<string, Pattern[]> = {}
    const untagged: Pattern[] = []
    for (const p of patterns) {
      if (p.source !== 'backtested' && p.source !== 'user') continue
      if (p.regime_tag) {
        ;(byRegime[p.regime_tag] ??= []).push(p)
      } else {
        untagged.push(p)
      }
    }
    return { byRegime, untagged }
  }, [patterns])

  const regimes = Object.entries(groups.byRegime).sort((a, b) => b[1].length - a[1].length)

  if (regimes.length === 0 && groups.untagged.length === 0) {
    return (
      <div className="flex flex-col items-center justify-center py-32 gap-3 text-slate-600">
        <Tag className="w-8 h-8" />
        <p className="text-sm">No saved patterns yet.</p>
        <p className="text-xs">Use "Find Matching" in Pattern Lab to classify and save patterns into regimes.</p>
      </div>
    )
  }

  return (
    <div className="flex flex-col gap-6">
      {regimes.map(([tag, pats]) => (
        <div key={tag} className="flex flex-col gap-2">
          <div className="flex items-center gap-2">
            <Tag className="w-3.5 h-3.5 text-violet-400" />
            <span className="text-sm font-semibold text-violet-300">#{tag}</span>
            <span className="text-xs text-slate-500">{pats.length} pattern{pats.length > 1 ? 's' : ''}</span>
          </div>
          <div className="flex flex-col gap-2 pl-4 border-l border-violet-900/40">
            {pats.map(p => <RegimeCard key={p.id} pattern={p} allPatterns={patterns} />)}
          </div>
        </div>
      ))}
      {groups.untagged.length > 0 && (
        <div className="flex flex-col gap-2">
          <div className="flex items-center gap-2">
            <Tag className="w-3.5 h-3.5 text-slate-500" />
            <span className="text-sm font-semibold text-slate-500">Untagged</span>
            <span className="text-xs text-slate-600">{groups.untagged.length} pattern{groups.untagged.length > 1 ? 's' : ''}</span>
          </div>
          <div className="flex flex-col gap-2 pl-4 border-l border-slate-700/40">
            {groups.untagged.map(p => <RegimeCard key={p.id} pattern={p} allPatterns={patterns} />)}
          </div>
        </div>
      )}
    </div>
  )
}

// ── Page root ─────────────────────────────────────────────────────────────────

type ViewMode = 'search' | 'instrument' | 'regime'

export default function PatternExplorer() {
  const [view, setView] = useState<ViewMode>('search')
  const { data: patternsData, isLoading } = useAllPatterns()
  const { data: _scores } = useLastScores()
  const { data: calibrationData } = usePatternCalibration()

  const calibrationMap = useMemo<Record<string, PatternCalibration>>(() => {
    if (!calibrationData) return {}
    return Object.fromEntries(calibrationData.map(c => [c.pattern_id, c]))
  }, [calibrationData])

  const patterns = useMemo<Pattern[]>(() => {
    if (!Array.isArray(patternsData)) return []
    return patternsData as Pattern[]
  }, [patternsData])

  return (
    <div className="p-6 flex flex-col gap-5 min-h-screen">
      {/* Page header */}
      <div className="flex items-start justify-between gap-4 flex-wrap">
        <div>
          <div className="flex items-center gap-3 mb-1">
            <TreePine className="w-5 h-5 text-emerald-400 shrink-0" />
            <h1 className="text-xl font-bold text-white">Pattern Library</h1>
            <Link
              to="/patterns/edit"
              className="inline-flex items-center gap-1.5 text-xs px-2.5 py-1 rounded border border-slate-700/50 text-slate-400 hover:border-blue-600/50 hover:text-blue-400 transition-colors ml-2"
            >
              <ExternalLink className="w-3 h-3" />
              Edit Library
            </Link>
          </div>
          <p className="text-sm text-slate-500">
            Browse {patterns.length > 0 ? patterns.length : '…'} geo-financial patterns by theme or by instrument
          </p>
        </div>

        {/* View toggle */}
        <div className="flex items-center gap-1 bg-dark-700/60 border border-slate-700/40 rounded-lg p-1">
          <button
            className={clsx(
              'flex items-center gap-1.5 px-3 py-1.5 rounded text-sm font-medium transition-colors',
              view === 'search'
                ? 'bg-blue-600/30 text-blue-300 border border-blue-500/30'
                : 'text-slate-400 hover:text-slate-200'
            )}
            onClick={() => setView('search')}
          >
            <Search className="w-3.5 h-3.5" />
            Search
          </button>
          <button
            className={clsx(
              'flex items-center gap-1.5 px-3 py-1.5 rounded text-sm font-medium transition-colors',
              view === 'instrument'
                ? 'bg-blue-600/30 text-blue-300 border border-blue-500/30'
                : 'text-slate-400 hover:text-slate-200'
            )}
            onClick={() => setView('instrument')}
          >
            <Search className="w-3.5 h-3.5" />
            Instrument
          </button>
          <button
            className={clsx(
              'flex items-center gap-1.5 px-3 py-1.5 rounded text-sm font-medium transition-colors',
              view === 'regime'
                ? 'bg-violet-600/30 text-violet-300 border border-violet-500/30'
                : 'text-slate-400 hover:text-slate-200'
            )}
            onClick={() => setView('regime')}
          >
            <Layers className="w-3.5 h-3.5" />
            By Regime
          </button>
        </div>
      </div>

      {/* Content area */}
      {isLoading ? (
        <div className="flex items-center justify-center py-32 text-slate-600 text-sm gap-2">
          <div className="w-4 h-4 border-2 border-slate-600 border-t-blue-500 rounded-full animate-spin" />
          Loading patterns…
        </div>
      ) : (
        <>
          {view === 'search' && <FacetedSearch patterns={patterns} calibrationMap={calibrationMap} />}
          {view === 'instrument' && <InstrumentLens patterns={patterns} />}
          {view === 'regime' && <RegimeView patterns={patterns} />}
        </>
      )}
    </div>
  )
}
