// Data, hooks, icons — shared across all pages.
const { useState, useEffect, useRef, useMemo, useCallback } = React;

// ============================================================
// ICONS — Lucide-style, hand-drawn paths
// ============================================================
const Icon = ({ d, size = 18, stroke = 1.6, fill = "none", style }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill={fill} stroke="currentColor" strokeWidth={stroke} strokeLinecap="round" strokeLinejoin="round" style={style}>
    {d}
  </svg>
);
const IconHome    = (p) => <Icon {...p} d={<><path d="M3 11l9-7 9 7" /><path d="M5 10v10h14V10" /></>} />;
const IconSignal  = (p) => <Icon {...p} d={<><path d="M3 20h.01M7 20v-4M11 20v-8M15 20v-6M19 20V8" /></>} />;
const IconChart   = (p) => <Icon {...p} d={<><path d="M3 3v18h18" /><path d="M7 14l3-4 3 3 5-7" /></>} />;
const IconShield  = (p) => <Icon {...p} d={<><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6l8-3z" /><path d="M8 13l3 3 5-6" /></>} />;
const IconClock   = (p) => <Icon {...p} d={<><circle cx="12" cy="12" r="9" /><path d="M12 7v5l3 2" /></>} />;
const IconBell    = (p) => <Icon {...p} d={<><path d="M6 8a6 6 0 1 1 12 0c0 7 3 7 3 9H3c0-2 3-2 3-9Z" /><path d="M10 21a2 2 0 0 0 4 0" /></>} />;
const IconList    = (p) => <Icon {...p} d={<><path d="M8 6h12M8 12h12M8 18h12M4 6h0M4 12h0M4 18h0" /></>} />;
const IconTerm    = (p) => <Icon {...p} d={<><rect x="3" y="4" width="18" height="16" rx="2" /><path d="m7 9 3 3-3 3M13 15h4" /></>} />;
const IconGear    = (p) => <Icon {...p} d={<><circle cx="12" cy="12" r="3" /><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z" /></>} />;
const IconSearch  = (p) => <Icon {...p} d={<><circle cx="11" cy="11" r="7" /><path d="m20 20-3.5-3.5" /></>} />;
const IconPlus    = (p) => <Icon {...p} d={<><path d="M12 5v14M5 12h14" /></>} />;
const IconPause   = (p) => <Icon {...p} d={<><rect x="6" y="5" width="4" height="14" rx="1" /><rect x="14" y="5" width="4" height="14" rx="1" /></>} />;
const IconPlay    = (p) => <Icon {...p} d={<><path d="M6 4l14 8-14 8V4Z" /></>} />;
const IconEdit    = (p) => <Icon {...p} d={<><path d="M4 20h4l10-10-4-4L4 16v4Z" /><path d="m14 6 4 4" /></>} />;
const IconStop    = (p) => <Icon {...p} d={<><rect x="5" y="5" width="14" height="14" rx="2" /></>} />;
const IconChevron = (p) => <Icon {...p} d={<><path d="m9 6 6 6-6 6" /></>} />;
const IconUpgrade = (p) => <Icon {...p} d={<><path d="m4 20 8-16 8 16" /><path d="M8 14h8" /></>} />;
const IconCheck   = (p) => <Icon {...p} d={<><path d="m5 12 4 4L19 6" /></>} />;
const IconX       = (p) => <Icon {...p} d={<><path d="M6 6l12 12M18 6 6 18" /></>} />;
const IconCopy    = (p) => <Icon {...p} d={<><rect x="9" y="9" width="11" height="11" rx="2" /><path d="M5 15V5a2 2 0 0 1 2-2h10" /></>} />;
const IconEye     = (p) => <Icon {...p} d={<><path d="M2 12s4-7 10-7 10 7 10 7-4 7-10 7-10-7-10-7Z" /><circle cx="12" cy="12" r="3" /></>} />;
const IconEyeOff  = (p) => <Icon {...p} d={<><path d="M3 3l18 18M10.5 6.2A10 10 0 0 1 12 6c6 0 10 6 10 6a16 16 0 0 1-2.6 3.2M6.3 6.3A16 16 0 0 0 2 12s4 6 10 6a10 10 0 0 0 4.2-1M9.5 9.5a3 3 0 0 0 4 4" /></>} />;
const IconExternal= (p) => <Icon {...p} d={<><path d="M14 4h6v6M20 4l-9 9M19 13v6H5V5h6" /></>} />;
const IconFilter  = (p) => <Icon {...p} d={<><path d="M3 4h18l-7 9v6l-4 2v-8L3 4Z" /></>} />;
const IconRefresh = (p) => <Icon {...p} d={<><path d="M3 12a9 9 0 0 1 15-6.7L21 8M21 3v5h-5M21 12a9 9 0 0 1-15 6.7L3 16M3 21v-5h5" /></>} />;
const IconDown    = (p) => <Icon {...p} d={<><path d="M12 5v14M5 12l7 7 7-7" /></>} />;
const IconAlert   = (p) => <Icon {...p} d={<><path d="M12 3 2 21h20L12 3Z" /><path d="M12 10v5M12 18h.01" /></>} />;
const IconKey     = (p) => <Icon {...p} d={<><circle cx="7.5" cy="15.5" r="4.5" /><path d="m21 2-9.6 9.6M15 6l6 6" /></>} />;
const IconBolt    = (p) => <Icon {...p} d={<><path d="M13 2 3 14h7l-1 8 10-12h-7l1-8Z" /></>} />;
const IconRocket  = (p) => <Icon {...p} d={<><path d="M5 13c-1.5 1.5-1 5 0 6 1 1 4.5 1.5 6 0M16 13a5 5 0 0 0 5-5V3h-5a5 5 0 0 0-5 5"/><path d="m9 11 4 4M14 6c-3 1-5 3-7 8l5 5c5-2 7-4 8-7"/><circle cx="15" cy="9" r="1"/></>} />;
const IconSwap    = (p) => <Icon {...p} d={<><path d="M3 7h14l-3-3M21 17H7l3 3"/></>} />;
const IconLayers  = (p) => <Icon {...p} d={<><path d="m12 2 9 5-9 5-9-5 9-5z"/><path d="m3 12 9 5 9-5M3 17l9 5 9-5"/></>} />;
const IconLogo    = ({ size = 22 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
    <path d="M5 4 C 15 8, 9 16, 19 20" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
    <path d="M19 4 C 9 8, 15 16, 5 20" stroke="currentColor" strokeWidth="2" strokeLinecap="round" opacity="0.5" />
    <circle cx="5" cy="4" r="1.4" fill="currentColor" />
    <circle cx="19" cy="20" r="1.4" fill="currentColor" />
  </svg>
);
// Asset glyphs
const IconBinance  = ({ size = 18 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="#F0B90B"><path d="M12 2 5 9l2 2 5-5 5 5 2-2-7-7Zm0 20-7-7 2-2 5 5 5-5 2 2-7 7ZM2 12l2-2 2 2-2 2-2-2Zm18 0 2-2 2 2-2 2-2-2Zm-10 0 2-2 2 2-2 2-2-2Z"/></svg>
);
const IconTV       = ({ size = 18 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="#2962FF" strokeWidth="2"><path d="M4 5h16M4 5v14h16V5M9 11h2v6m-2-6h6m0 0v6"/></svg>
);
const IconTelegram = ({ size = 18 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="#26A5E4"><path d="M21 4 2 11l5 2 2 6 3-4 5 4 4-15Zm-4 3-8 6-2-.7 11-6.3-1 1Z"/></svg>
);
// Kraken — stylised tentacle/spiral mark in brand purple
const IconKraken = ({ size = 18 }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill="none">
    <circle cx="12" cy="12" r="10" fill="#5741D9"/>
    <path d="M7 16V9.5a1.5 1.5 0 0 1 3 0V16M11 16V9.5a1.5 1.5 0 0 1 3 0V16M15 16V9.5a1.5 1.5 0 0 1 3 0V16" stroke="#fff" strokeWidth="1.6" strokeLinecap="round"/>
  </svg>
);

// ============================================================
// EXCHANGES — config per supported venue (spec v3)
// ============================================================
const EXCHANGES = {
  binance: {
    id: "binance",
    name: "Binance",
    full: "Binance.com (international)",
    Icon: IconBinance,
    color: "#F0B90B",
    ccxtId: "binance",
    nativeSymbol: "BTCUSDT",
    fees: { maker: 0.10, taker: 0.10 },
    rateLimit: "1200 weight/min · 10 orders/sec",
    oco: true,
    stopLoss: "OCO order",
    stopLossNote: "exchange-side · survives bot crashes",
    testnet: true,
    testnetUrl: "testnet.binance.vision",
    docs: "binance-docs.github.io/apidocs/spot",
    rttMs: 47,
    quirks: "Native BTCUSDT symbol · CCXT normalises to BTC/USDT",
  },
  kraken: {
    id: "kraken",
    name: "Kraken",
    full: "Kraken Spot",
    Icon: IconKraken,
    color: "#5741D9",
    ccxtId: "kraken",
    nativeSymbol: "XXBTZUSD",
    fees: { maker: 0.16, taker: 0.26 },
    rateLimit: "15 calls / 3s · counter decays 1/3s",
    oco: false,
    stopLoss: "Conditional close",
    stopLossNote: "attached to opening order via close param",
    testnet: false,
    testnetUrl: "no public testnet — paper-trade mode",
    docs: "docs.kraken.com/api",
    rttMs: 88,
    quirks: "Uses XBT internally · CCXT maps to BTC · stricter rate limit",
  },
};

// ============================================================
// LIVE-TICKER + PRNG + HOOKS
// ============================================================
function useTicker(initial, { volatility = 0.0008, intervalMs = 1400, bias = 0 } = {}) {
  const [value, setValue] = useState(initial);
  const [dir, setDir] = useState(0);
  useEffect(() => {
    let stop = false;
    const tick = () => {
      if (stop) return;
      setValue((v) => {
        const drift = (Math.random() - 0.5 + bias) * volatility * v;
        const next = Math.max(0.01, v + drift);
        setDir(drift > 0 ? 1 : drift < 0 ? -1 : 0);
        return next;
      });
    };
    const id = setInterval(tick, intervalMs + Math.random() * 600);
    return () => { stop = true; clearInterval(id); };
  }, [volatility, intervalMs, bias]);
  return [value, dir];
}

function seedRand(seed) {
  let s = seed;
  return () => { s = (s * 9301 + 49297) % 233280; return s / 233280; };
}

function generateSeries(n, { seed = 1, start = 100, drift = 0.0008, vol = 0.012 } = {}) {
  const rand = seedRand(seed);
  const out = [start];
  for (let i = 1; i < n; i++) {
    const d = (rand() - 0.5) * vol + drift;
    out.push(Math.max(0.01, out[i - 1] * (1 + d)));
  }
  return out;
}

function useCountUp(target, duration = 900) {
  const [val, setVal] = useState(target);
  const fromRef = useRef(target);
  useEffect(() => {
    const from = fromRef.current;
    const start = performance.now();
    let raf;
    const step = (t) => {
      const p = Math.min(1, (t - start) / duration);
      const eased = 1 - Math.pow(1 - p, 3);
      setVal(from + (target - from) * eased);
      if (p < 1) raf = requestAnimationFrame(step);
      else fromRef.current = target;
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [target, duration]);
  return val;
}

// ============================================================
// SCENARIOS (Tweaks-driven)
// ============================================================
const SCENARIOS = {
  winning:  { tone: "profit", equity: 11842.30, change24h: 132.18,  changePct: 1.13,  todayTrades: 7,  todayWin: 6, dailyLoss: 0.42, drawdown: 2.3 },
  losing:   { tone: "loss",   equity: 9680.12,  change24h: -204.50, changePct: -2.07, todayTrades: 9,  todayWin: 3, dailyLoss: 2.71, drawdown: 8.4 },
  volatile: { tone: "warn",   equity: 10520.74, change24h: 18.24,   changePct: 0.17,  todayTrades: 14, todayWin: 7, dailyLoss: 1.18, drawdown: 5.6 },
};

// ============================================================
// BOT — the MVP is a single bot. Source of truth.
// ============================================================
const BOT = {
  name: "BTC/USDT EMA Cross",
  pair: "BTC/USDT",
  activeExchange: "binance", // spec v3: single-active via ACTIVE_EXCHANGE
  market: "Spot",
  timeframe: "5m",
  strategy: "EMA(9/21) Cross + RSI(14) > 50",
  source: "TradingView Pine v5",
  mode: "testnet", // testnet | live | paper
  status: "Running",
  uptimeSec: 184302, // ~2d 3h
  region: "Dubai · Hetzner",
  startBalance: 10000,
};

// ============================================================
// SIGNALS — TradingView webhook log
// ============================================================
const SIGNALS = [
  { t: -12,    id: "sig-0a7c", side: "BUY",  price: 64210.50, status: "executed",     reason: null,                          latencyMs: 412 },
  { t: -184,   id: "sig-0a7b", side: "SELL", price: 64055.40, status: "executed",     reason: null,                          latencyMs: 538 },
  { t: -640,   id: "sig-0a7a", side: "BUY",  price: 63980.10, status: "executed",     reason: null,                          latencyMs: 689 },
  { t: -1020,  id: "sig-0a79", side: "BUY",  price: 63920.00, status: "deduplicated", reason: "Duplicate signal_id (< 60s)", latencyMs: 24  },
  { t: -1810,  id: "sig-0a78", side: "SELL", price: 63810.20, status: "executed",     reason: null,                          latencyMs: 461 },
  { t: -2440,  id: "sig-0a77", side: "BUY",  price: 63702.00, status: "rejected",     reason: "Position already open",       latencyMs: 18  },
  { t: -3110,  id: "sig-0a76", side: "SELL", price: 63812.50, status: "stopped_out",  reason: "Stop loss hit @ 1.98%",       latencyMs: 488 },
  { t: -4290,  id: "sig-0a75", side: "BUY",  price: 64104.00, status: "executed",     reason: null,                          latencyMs: 612 },
  { t: -5640,  id: "sig-0a74", side: "BUY",  price: 64012.10, status: "rejected",     reason: "Min notional $10 not met",    latencyMs: 22  },
  { t: -7100,  id: "sig-0a73", side: "SELL", price: 64198.40, status: "executed",     reason: null,                          latencyMs: 384 },
  { t: -9200,  id: "sig-0a72", side: "BUY",  price: 63972.00, status: "executed",     reason: null,                          latencyMs: 502 },
  { t: -12400, id: "sig-0a71", side: "BUY",  price: 63810.00, status: "rejected",     reason: "Daily loss limit reached",    latencyMs: 14  },
];

// ============================================================
// TRADES — full history
// ============================================================
const TRADES = [
  { t: -12,    exchange: "binance", side: "BUY",  qty: 0.0084, entry: 64210.50, exit: null,     pnl: null, pnlPct: null, stop: 62926.30, closedBy: null,         status: "open"    },
  { t: -184,   exchange: "binance", side: "SELL", qty: 0.0082, entry: 63702.00, exit: 64055.40, pnl: 28.97,  pnlPct: 0.55,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -640,   exchange: "binance", side: "BUY",  qty: 0.0085, entry: 63702.00, exit: 63980.10, pnl: 23.64,  pnlPct: 0.44,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -1810,  exchange: "binance", side: "SELL", qty: 0.0083, entry: 64104.00, exit: 63810.20, pnl: 24.39,  pnlPct: 0.46,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -3110,  exchange: "binance", side: "SELL", qty: 0.0080, entry: 65085.30, exit: 63812.50, pnl: -101.82,pnlPct: -1.96, stop: 63812.50, closedBy: "stop_loss",status: "stopped_out" },
  { t: -4290,  exchange: "binance", side: "BUY",  qty: 0.0086, entry: 64104.00, exit: 65085.30, pnl: 84.39,  pnlPct: 1.53,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -7100,  exchange: "kraken",  side: "SELL", qty: 0.0082, entry: 63972.00, exit: 64198.40, pnl: 18.56,  pnlPct: 0.35,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -9200,  exchange: "kraken",  side: "BUY",  qty: 0.0084, entry: 63972.00, exit: 64198.40, pnl: 19.02,  pnlPct: 0.35,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -14800, exchange: "binance", side: "BUY",  qty: 0.0080, entry: 63540.00, exit: 64104.00, pnl: 45.12,  pnlPct: 0.89,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -22400, exchange: "binance", side: "SELL", qty: 0.0079, entry: 63820.00, exit: 63540.00, pnl: 22.12,  pnlPct: 0.44,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -29600, exchange: "kraken",  side: "BUY",  qty: 0.0083, entry: 63820.00, exit: 64210.00, pnl: 32.37,  pnlPct: 0.61,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -38800, exchange: "kraken",  side: "SELL", qty: 0.0082, entry: 64198.00, exit: 64822.00, pnl: -51.17, pnlPct: -0.97, stop: 64822.00, closedBy: "stop_loss",status: "stopped_out" },
  { t: -52400, exchange: "binance", side: "BUY",  qty: 0.0080, entry: 64198.00, exit: 65010.00, pnl: 64.96,  pnlPct: 1.26,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -68000, exchange: "binance", side: "SELL", qty: 0.0078, entry: 64850.00, exit: 64198.00, pnl: 50.86,  pnlPct: 1.01,  stop: null, closedBy: "signal",       status: "closed"  },
  { t: -86400, exchange: "binance", side: "BUY",  qty: 0.0081, entry: 64124.00, exit: 64850.00, pnl: 58.81,  pnlPct: 1.13,  stop: null, closedBy: "signal",       status: "closed"  },
];

// ============================================================
// LOGS — structured JSON entries
// ============================================================
const LOGS = [
  { t: -12,   level: "INFO",  type: "trade_executed",    msg: "Market BUY 0.0084 BTC/USDT @ 64210.50 · OCO stop @ 62926.30", ctx: { signal_id: "sig-0a7c", order_id: "B-4419821", latency_ms: 412 } },
  { t: -45,   level: "DEBUG", type: "risk_check_passed", msg: "All 6 pre-trade checks passed", ctx: { signal_id: "sig-0a7c" } },
  { t: -60,   level: "INFO",  type: "signal_received",   msg: "Webhook BUY BTC/USDT @ 64210.50", ctx: { signal_id: "sig-0a7c", source_ip: "52.89.214.238" } },
  { t: -184,  level: "INFO",  type: "trade_executed",    msg: "Market SELL 0.0082 BTC/USDT @ 64055.40 · PnL +$28.97", ctx: { signal_id: "sig-0a7b", order_id: "B-4419742" } },
  { t: -1020, level: "WARN",  type: "signal_deduplicated", msg: "Dropped duplicate signal_id within 60s window", ctx: { signal_id: "sig-0a79", age_s: 14 } },
  { t: -2440, level: "WARN",  type: "risk_check_failed", msg: "Rejected: position already open (max_concurrent_positions=1)", ctx: { signal_id: "sig-0a77" } },
  { t: -3110, level: "INFO",  type: "stop_loss_triggered", msg: "OCO stop filled @ 63812.50 · PnL -$101.82", ctx: { signal_id: "sig-0a76" } },
  { t: -5640, level: "WARN",  type: "risk_check_failed", msg: "Position size $4.20 below Binance min notional $10", ctx: { signal_id: "sig-0a74" } },
  { t: -7220, level: "DEBUG", type: "exchange_api_call", msg: "GET /api/v3/account · 47ms", ctx: { endpoint: "/api/v3/account" } },
  { t: -12400, level: "ERROR",type: "risk_check_failed", msg: "Daily loss limit reached: -3.02% (cap -3.00%)", ctx: { signal_id: "sig-0a71" } },
  { t: -12500, level: "ERROR",type: "daily_limit_hit",   msg: "Circuit breaker tripped · halting all trading", ctx: {} },
  { t: -14200, level: "INFO",  type: "bot_startup",      msg: "Bot started · mode=testnet · exchange=binance · balance=$10,000.00", ctx: { mode: "testnet" } },
  { t: -14210, level: "INFO",  type: "state_recovery",   msg: "Reconciled 1 open position from exchange · 0 orphans", ctx: { open: 1 } },
];

// ============================================================
// Daily P&L for the last 14 days (for bar chart)
// ============================================================
const DAILY_PNL = [
  { d: -13, pnl:  82.40,  trades: 6, wr: 0.83 },
  { d: -12, pnl: -34.10,  trades: 5, wr: 0.40 },
  { d: -11, pnl: 124.50,  trades: 8, wr: 0.75 },
  { d: -10, pnl:  18.20,  trades: 4, wr: 0.50 },
  { d:  -9, pnl: -88.40,  trades: 9, wr: 0.33 },
  { d:  -8, pnl: 162.30,  trades: 7, wr: 0.86 },
  { d:  -7, pnl: 204.18,  trades:11, wr: 0.73 },
  { d:  -6, pnl:  56.40,  trades: 5, wr: 0.60 },
  { d:  -5, pnl: -42.10,  trades: 6, wr: 0.33 },
  { d:  -4, pnl: 128.30,  trades: 9, wr: 0.78 },
  { d:  -3, pnl:  96.80,  trades: 7, wr: 0.71 },
  { d:  -2, pnl: -18.40,  trades: 4, wr: 0.50 },
  { d:  -1, pnl: 184.20,  trades: 8, wr: 0.75 },
  { d:   0, pnl: 132.18,  trades: 7, wr: 0.86 },
];

// Backtest monthly returns (rows = year, cols = month)
const MONTHLY = [
  { year: 2024, vals: [1.2, -0.8,  3.4, 2.1,  0.9, -1.4,  4.2,  1.8,  2.9, -2.1,  3.6,  4.4] },
  { year: 2025, vals: [2.4,  1.1, -1.8, 3.2,  4.1,  2.7,  1.4, -0.6,  3.8,  2.1,  1.9,  2.8] },
  { year: 2026, vals: [3.1,  2.6,  1.9, 4.2,  2.1, null, null, null, null, null, null, null] },
];

// ============================================================
// BUILD PROGRESS — 37 tasks across 8 categories (spec v3 task_status)
// ============================================================
const TASK_CATEGORIES = [
  { id: "foundation", label: "Foundation", color: "var(--info)" },
  { id: "database",   label: "Database",   color: "#A78BFA" },
  { id: "risk",       label: "Risk",       color: "var(--accent)" },
  { id: "execution",  label: "Execution",  color: "var(--profit)" },
  { id: "api",        label: "API",        color: "#60A5FA" },
  { id: "alerts",     label: "Alerts",     color: "#F472B6" },
  { id: "recovery",   label: "Recovery",   color: "var(--warn)" },
  { id: "deployment", label: "Deployment", color: "var(--text-2)" },
];
const TASKS = [
  { id: "T01", cat: "foundation", task: "Initialize project structure and folder layout",            status: "done" },
  { id: "T02", cat: "foundation", task: "Create .env.example with all environment variables",         status: "done" },
  { id: "T03", cat: "foundation", task: "Build config.py with Pydantic settings loader",              status: "done" },
  { id: "T04", cat: "foundation", task: "Set up requirements.txt with pinned dependencies",           status: "done" },
  { id: "T05", cat: "foundation", task: "Create .gitignore",                                          status: "done" },
  { id: "T06", cat: "database",   task: "Set up SQLAlchemy async engine with WAL mode",               status: "done" },
  { id: "T07", cat: "database",   task: "Create Trade model (with exchange column)",                  status: "done" },
  { id: "T08", cat: "database",   task: "Create Signal model",                                        status: "done" },
  { id: "T09", cat: "database",   task: "Create DailyStats model",                                    status: "done" },
  { id: "T10", cat: "risk",       task: "Build RiskManager with pre-trade validation",                status: "done" },
  { id: "T11", cat: "risk",       task: "Implement position sizing (fixed fraction)",                 status: "done" },
  { id: "T12", cat: "risk",       task: "Build circuit breaker",                                      status: "done" },
  { id: "T13", cat: "risk",       task: "Implement max drawdown kill switch",                         status: "done" },
  { id: "T14", cat: "risk",       task: "Write tests for risk manager",                               status: "in-progress" },
  { id: "T15", cat: "execution",  task: "Build AbstractExchangeClient base class (ABC interface)",    status: "done" },
  { id: "T16", cat: "execution",  task: "Build ExchangeFactory (reads ACTIVE_EXCHANGE → client)",     status: "done" },
  { id: "T17", cat: "execution",  task: "Build BinanceClient (OCO orders, testnet support)",          status: "done" },
  { id: "T18", cat: "execution",  task: "Build KrakenClient (conditional close, paper-trade mode)",   status: "in-progress" },
  { id: "T19", cat: "execution",  task: "Implement testnet / live / paper mode toggle",               status: "in-progress" },
  { id: "T20", cat: "execution",  task: "Build order manager (exchange-agnostic dispatch)",           status: "done" },
  { id: "T21", cat: "execution",  task: "Add retry logic with exponential backoff (3 retries)",       status: "in-progress" },
  { id: "T22", cat: "execution",  task: "Write tests for exchange factory + both clients (mocked)",   status: "pending" },
  { id: "T23", cat: "api",        task: "Create FastAPI app with Uvicorn entry point",                status: "done" },
  { id: "T24", cat: "api",        task: "Build POST /webhook with secret validation",                 status: "done" },
  { id: "T25", cat: "api",        task: "Implement signal deduplication (60s window)",                status: "done" },
  { id: "T26", cat: "api",        task: "Build GET /health endpoint (includes active_exchange)",      status: "in-progress" },
  { id: "T27", cat: "api",        task: "Add rate limiting via SlowAPI",                              status: "done" },
  { id: "T28", cat: "api",        task: "Create Pydantic models for webhook payload",                 status: "done" },
  { id: "T29", cat: "alerts",     task: "Build Telegram bot with async sender",                       status: "done" },
  { id: "T30", cat: "alerts",     task: "Create message templates for all events",                    status: "done" },
  { id: "T31", cat: "alerts",     task: "Implement /stop /resume /status /balance /today commands",   status: "in-progress" },
  { id: "T32", cat: "alerts",     task: "Build daily summary scheduler (23:59 UTC)",                  status: "pending" },
  { id: "T33", cat: "recovery",   task: "Build state reconciler (exchange-aware)",                    status: "pending" },
  { id: "T34", cat: "deployment", task: "Set up structured JSON logging with rotation",               status: "in-progress" },
  { id: "T35", cat: "deployment", task: "Create Dockerfile",                                          status: "pending" },
  { id: "T36", cat: "deployment", task: "Create docker-compose.yml with health checks",               status: "pending" },
  { id: "T37", cat: "deployment", task: "Write README.md with setup guide (both exchanges)",          status: "pending" },
];

// ============================================================
// ROADMAP — v1 / v2 / v3 (spec v3)
// ============================================================
const ROADMAP = [
  {
    v: "v1", name: "Reliable Execution", timeline: "Weeks 1–3", current: true,
    status: "in-progress",
    features: [
      "Single pair BTC/USDT",
      "TradingView webhook → FastAPI",
      "Signal dedup + validation",
      "Risk manager with pre-trade checks",
      "Binance + Kraken via CCXT (single-active)",
      "Exchange factory pattern",
      "OCO stop / conditional close",
      "Position sizing (fixed fraction)",
      "Daily loss circuit breaker",
      "Telegram alerts + /commands",
      "State recovery on restart",
      "Health check endpoint",
      "Docker deployment",
      "Testnet / paper mode default",
    ],
  },
  {
    v: "v2", name: "Professional Trading System", timeline: "Months 2–3",
    status: "planned",
    features: [
      "Multi-pair support",
      "Futures / perpetual trading",
      "Trailing stop loss",
      "Take profit (fixed + trailing)",
      "Web dashboard (FastAPI + React)",
      "PostgreSQL migration",
      "Trade history + analytics",
      "Multi-exchange (Bybit, OKX)",
      "Docker Compose + NGINX + HTTPS",
      "Grafana + Prometheus monitoring",
      "Auto-restart with systemd",
    ],
  },
  {
    v: "v3", name: "AI Quant Platform", timeline: "Months 4–6+",
    status: "future",
    features: [
      "Market regime detection (ML)",
      "Sentiment analysis (news + social)",
      "AI strategy optimization",
      "Reinforcement learning signals",
      "Adaptive position sizing",
      "Portfolio balancing",
      "Order book analytics",
      "Volatility engine",
      "Backtesting cluster",
    ],
  },
];

// Changelog distilled from spec
const CHANGELOG = [
  {
    date: "2026-05-15 01:30 UTC", agent: "claude_chat", version: "spec v3.0 · MVP V1.1",
    summary: "Added Kraken as second supported exchange",
    highlights: [
      "Exchange factory pattern (AbstractExchangeClient → BinanceClient, KrakenClient)",
      "Single-active per deploy via ACTIVE_EXCHANGE env var",
      "Kraken specifics: conditional close, paper-trade mode (no public testnet), stricter rate limit",
      "Added 3 new tasks (T15–T17 factory split) · 34 → 37 total",
      "Added ACTIVE_EXCHANGE, KRAKEN_API_KEY, KRAKEN_API_SECRET, TRADING_PAIR env vars",
      "trades table gains `exchange` column",
      "Health endpoint returns active_exchange + trading_mode (testnet | live | paper)",
    ],
  },
  {
    date: "2026-05-15 00:00 UTC", agent: "claude_chat", version: "spec v2.0",
    summary: "Project spec finalized · 34-task plan",
    highlights: [
      "Framework: Flask → FastAPI (async-native)",
      "Max daily loss: 5% → 3%",
      "Stop loss expanded: boolean → full OCO config",
      "Removed Codex API · deferred Bybit/OKX/BingX to v2",
      "Added testnet default, signal dedup (60s), state recovery, /health, rate limiting",
      "Added 8 clarifying questions for build kickoff",
    ],
  },
];

// Hourly heatmap — wins per hour-of-day (UTC) over 30d
const HOURLY = (() => {
  const r = seedRand(7);
  return Array.from({ length: 24 }, (_, h) => ({
    hour: h,
    pnl: (r() - 0.4) * 90 + (h >= 13 && h <= 17 ? 40 : 0), // London/NY overlap edge
    trades: Math.floor(r() * 8) + 1,
  }));
})();

// Format helpers
function fmtUsd(n, { dec = 2, sign = false } = {}) {
  if (n == null) return "—";
  const s = Math.abs(n).toLocaleString("en-US", { minimumFractionDigits: dec, maximumFractionDigits: dec });
  return (sign ? (n >= 0 ? "+" : "−") : (n < 0 ? "−" : "")) + "$" + s;
}
function fmtPct(n, { dec = 2, sign = true } = {}) {
  if (n == null) return "—";
  return (sign ? (n >= 0 ? "+" : "") : "") + n.toFixed(dec) + "%";
}
function fmtRel(secAgo) {
  const s = -secAgo;
  if (s < 60) return s + "s ago";
  if (s < 3600) return Math.round(s / 60) + "m ago";
  if (s < 86400) return Math.round(s / 3600) + "h ago";
  return Math.round(s / 86400) + "d ago";
}
function fmtDuration(sec) {
  const d = Math.floor(sec / 86400);
  const h = Math.floor((sec % 86400) / 3600);
  const m = Math.floor((sec % 3600) / 60);
  if (d) return d + "d " + h.toString().padStart(2, "0") + "h " + m.toString().padStart(2, "0") + "m";
  if (h) return h + "h " + m.toString().padStart(2, "0") + "m";
  return m + "m";
}
function fmtTime(secAgo) {
  const dt = new Date(Date.now() + secAgo * 1000);
  return dt.toISOString().slice(11, 19); // HH:MM:SS UTC
}

Object.assign(window, {
  Icon, IconHome, IconSignal, IconChart, IconShield, IconClock, IconBell, IconList, IconTerm, IconGear,
  IconSearch, IconPlus, IconPause, IconPlay, IconEdit, IconStop, IconChevron, IconUpgrade,
  IconCheck, IconX, IconCopy, IconEye, IconEyeOff, IconExternal, IconFilter, IconRefresh, IconDown, IconAlert, IconKey, IconBolt,
  IconRocket, IconSwap, IconLayers,
  IconLogo, IconBinance, IconKraken, IconTV, IconTelegram,
  useTicker, useCountUp, generateSeries, seedRand,
  EXCHANGES, TASK_CATEGORIES, TASKS, ROADMAP, CHANGELOG,
  SCENARIOS, BOT, SIGNALS, TRADES, LOGS, DAILY_PNL, MONTHLY, HOURLY,
  fmtUsd, fmtPct, fmtRel, fmtDuration, fmtTime,
});
