import { useEffect, useMemo, useState } from "react";
import {
  LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend, CartesianGrid,
  AreaChart, Area, BarChart, Bar
} from "recharts";
import {
  apiGetLatestDataset,
  apiUpsertDataset,
  apiGetAnnotation,
  apiUpsertAnnotation,
  DataDict,
} from "./api";

type SeriesPoint = { date: string; close: number[] };
type ViewMode = "daily" | "intraday"; // 移除 multi

const START_DAY = 7;

/* --------------------- utils --------------------- */
function latestClose(p: SeriesPoint): number {
  const arr = p.close;
  if (!Array.isArray(arr) || arr.length === 0) throw new Error(`Empty close array at ${p.date}`);
  const last = arr[arr.length - 1];
  if (!Number.isFinite(last)) throw new Error(`Invalid close value at ${p.date}`);
  return last;
}
function uuidv4() {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, c => {
    const r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}
function getOrCreateUserId() {
  const k = "annot_user_id";
  let uid = localStorage.getItem(k);
  if (!uid) { uid = uuidv4(); localStorage.setItem(k, uid); }
  return uid;
}
function stableHash(str: string): string {
  let h = 5381;
  for (let i = 0; i < str.length; i++) h = (h * 33) ^ str.charCodeAt(i);
  return (h >>> 0).toString(16);
}

/** Example data in the new format (close is a rolling intraday array up to 500) */
function generateFakeJSON(maxLen = 500): DataDict {
  const randTicker = () => Math.random().toString(36).substring(2, 6).toUpperCase();
  const tickers = Array.from({ length: 5 }, () => randTicker());
  const start = new Date("2024-01-02T00:00:00Z");
  const dates = Array.from({ length: 67 }, (_, i) => {
    const d = new Date(start); d.setDate(start.getDate() + i);
    return d.toISOString().slice(0, 10);
  });
  const out: DataDict = {};
  for (let a = 0; a < 5; a++) {
    const ticker = tickers[a];
    let price = 80 + Math.random() * 40;
    const mu = (Math.random() * 0.1 - 0.05) / 252;
    const sigma = 0.15 + Math.random() * 0.35;
    const series: SeriesPoint[] = [];
    for (let i = 0; i < dates.length; i++) {
      // Fake minute series (varying length)
      const mins = 200 + Math.floor(Math.random() * 100);
      const intraday: number[] = [];
      let p = price;
      for (let m = 0; m < mins; m++) {
        const dz = (Math.random() - 0.5) * 0.004;
        p = Math.max(0.01, p * (1 + dz));
        intraday.push(Number(p.toFixed(2)));
      }
      series.push({ date: dates[i], close: intraday });
      const drift = mu + (sigma / Math.sqrt(252)) * ((Math.random() - 0.5) * 2.0);
      price = intraday[intraday.length - 1] * (1 + drift);
    }
    out[ticker] = series;
  }
  return out;
}

/* --------------------- component --------------------- */
export default function App() {
  const userId = getOrCreateUserId();

  const [datasetId, setDatasetId] = useState<string | null>(null);
  const [datasetName, setDatasetName] = useState<string>("");

  const [rawData, setRawData] = useState<DataDict>({});
  const [assets, setAssets] = useState<string[]>([]);
  const [dates, setDates] = useState<string[]>([]);

  // Dynamic caps
  const maxDays = dates.length || 67;
  const maxSteps = Math.max(0, maxDays - START_DAY);

  const [step, setStep] = useState(1);
  const [windowLen, setWindowLen] = useState(START_DAY);
  const [selectedAsset, setSelectedAsset] = useState<string | null>(null);
  const [hoverAsset, setHoverAsset] = useState<string | null>(null);
  const [selections, setSelections] = useState<{ step: number; date: string; asset: string; ret: number }[]>([]);
  const [message, setMessage] = useState("");
  const [confirming, setConfirming] = useState(false);
  const [finalSaved, setFinalSaved] = useState(false);

  const [viewMode, setViewMode] = useState<ViewMode>("daily");
  const [intradayDayIdx, setIntradayDayIdx] = useState<number | null>(null);

  /* ---------- localStorage keys ---------- */
  const LS_DATASET_KEY = "annot_dataset_meta";
  const LS_DATA_PREFIX = (id: string) => `annot_dataset_${id}`;
  const LS_ANN_PREFIX = (id: string, uid: string) => `annot_user_${uid}_ds_${id}`;

  /* ---------- alias map ---------- */
  const aliasMap = useMemo(() => {
    const m: Record<string, string> = {};
    assets.forEach((a, i) => { m[a] = `Ticker ${i + 1}`; });
    return m;
  }, [assets]);
  const aliasOf = (a?: string | null) => (a && aliasMap[a]) || a || "";

  /* ---------- boot ---------- */
  useEffect(() => {
    const boot = async () => {
      try {
        const metaRaw = localStorage.getItem(LS_DATASET_KEY);
        if (metaRaw) {
          const meta = JSON.parse(metaRaw);
          if (meta?.datasetId) {
            const dJson = localStorage.getItem(LS_DATA_PREFIX(meta.datasetId));
            if (dJson) {
              const payload = JSON.parse(dJson) as { data: DataDict; assets: string[]; dates: string[]; name?: string };
              loadDatasetIntoState(meta.datasetId, payload.data, payload.assets, payload.dates, payload.name || "");
              await loadUserAnnotation(meta.datasetId);
              return;
            }
          }
        }
        try {
          const ds = await apiGetLatestDataset();
          const id = (ds.id ?? ds.dataset_id) as string;
          const name = ds.name || "";
          const data = ds.data as DataDict;
          const dsAssets = ds.assets as string[];
          const dsDates = ds.dates as string[];
          persistDatasetLocal(id, data, dsAssets, dsDates, name);
          loadDatasetIntoState(id, data, dsAssets, dsDates, name);
          await loadUserAnnotation(id);
          return;
        } catch {}
        const example = generateFakeJSON();
        const keys = Object.keys(example);
        const dsDates = example[keys[0]].map(d => d.date);
        const id = stableHash(JSON.stringify({ assets: keys, dates: dsDates }));
        persistDatasetLocal(id, example, keys, dsDates, "Example");
        loadDatasetIntoState(id, example, keys, dsDates, "Example");
      } catch (e) {
        console.warn("Boot failed:", e);
      }
    };
    boot();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  function persistDatasetLocal(id: string, data: DataDict, a: string[], d: string[], name: string) {
    localStorage.setItem(LS_DATASET_KEY, JSON.stringify({ datasetId: id, name }));
    localStorage.setItem(LS_DATA_PREFIX(id), JSON.stringify({ data, assets: a, dates: d, name }));
  }
  function loadDatasetIntoState(id: string, data: DataDict, a: string[], d: string[], name: string) {
    setDatasetId(id);
    setDatasetName(name || "");
    setRawData(data);
    setAssets(a);
    setDates(d);
    setStep(1);
    setWindowLen(Math.min(START_DAY, Math.max(1, d.length)));
    setSelections([]);
    setSelectedAsset(null);
    setViewMode("daily");
    setIntradayDayIdx(null);
    setMessage(`Loaded dataset ${name || id}: ${a.length} assets, ${d.length} days.`);
    setFinalSaved(false);
  }
  async function loadUserAnnotation(id: string) {
    const localRaw = localStorage.getItem(LS_ANN_PREFIX(id, userId));
    if (localRaw) {
      try {
        const ann = JSON.parse(localRaw);
        setSelections(ann.selections || []);
        setStep(ann.step || 1);
        setWindowLen(ann.window_len || START_DAY);
        setIntradayDayIdx(null);
      } catch {}
    }
    try {
      const ann = await apiGetAnnotation(id, userId);
      setSelections(ann.selections || []);
      setStep(ann.step || 1);
      setWindowLen(ann.window_len || START_DAY);
      setIntradayDayIdx(null);
      localStorage.setItem(LS_ANN_PREFIX(id, userId), JSON.stringify(ann));
    } catch {}
  }
  function persistAnnotationLocal() {
    if (!datasetId) return;
    const ann = { user_id: userId, dataset_id: datasetId, selections, step, window_len: windowLen };
    localStorage.setItem(LS_ANN_PREFIX(datasetId, userId), JSON.stringify(ann));
  }
  async function upsertAnnotationCloud() {
    if (!datasetId) return;
    try {
      await apiUpsertAnnotation({ dataset_id: datasetId, user_id: userId, selections, step, window_len: windowLen });
    } catch (e) { console.warn("Upsert annotation failed:", e); }
  }
  useEffect(() => {
    persistAnnotationLocal();
    upsertAnnotationCloud();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selections, step, windowLen]);

  /* ---------- tie intraday selected day to window ---------- */
  useEffect(() => {
    if (!dates.length) return;
    const lastIdx = Math.min(windowLen - 1, dates.length - 1);
    if (intradayDayIdx === null || intradayDayIdx > lastIdx) setIntradayDayIdx(lastIdx);
  }, [dates, windowLen, intradayDayIdx]);

  /* ---------- upload handler ---------- */
  async function onFile(e: any) {
    const f = e.target.files?.[0];
    if (!f) return;
    const reader = new FileReader();
    reader.onload = async () => {
      try {
        const json: DataDict = JSON.parse(String(reader.result));
        const keys = Object.keys(json);
        if (keys.length === 0) throw new Error("Empty dataset");
        const firstArr = json[keys[0]];
        if (!Array.isArray(firstArr) || !firstArr[0]?.date || !Array.isArray(firstArr[0]?.close)) {
          throw new Error("Invalid series format. Need [{date, close:number[]}]");
        }
        const refDates = firstArr.map(p => p.date);
        const checkPoint = (p: SeriesPoint) => {
          if (!p?.date) throw new Error("Missing date");
          if (!Array.isArray(p.close) || p.close.length === 0) throw new Error(`Empty close array at ${p.date}`);
          if (p.close.length > 500) throw new Error(`close array exceeds 500 at ${p.date}`);
          for (const v of p.close) if (typeof v !== "number" || !Number.isFinite(v)) throw new Error(`Non numeric close in array at ${p.date}`);
        };
        for (const k of keys) {
          const arr = json[k];
          if (!Array.isArray(arr) || arr.length !== firstArr.length) throw new Error("All series must have the same length");
          for (let i = 0; i < arr.length; i++) {
            const p = arr[i]; checkPoint(p);
            if (p.date !== refDates[i]) throw new Error("Date misalignment across assets");
          }
        }
        const dsDates = firstArr.map(d => d.date);
        const id = stableHash(JSON.stringify({ assets: keys, dates: dsDates }));
        const name = f.name.replace(/\.[^.]+$/, "");
        persistDatasetLocal(id, json, keys, dsDates, name);
        loadDatasetIntoState(id, json, keys, dsDates, name);

        try {
          await apiUpsertDataset({ dataset_id: id, name, data: json, assets: keys, dates: dsDates });
          await apiUpsertAnnotation({ dataset_id: id, user_id: userId, selections: [], step: 1, window_len: Math.min(START_DAY, Math.max(1, dsDates.length)) });
        } catch (err: any) {
          console.warn("Upload to backend failed:", err?.message || err);
        }
      } catch (err: any) {
        setMessage("Failed to parse JSON: " + err.message);
      }
    };
    reader.readAsText(f);
  }

  /* ---------- computed ---------- */
  const isFinal = windowLen >= maxDays || selections.length >= maxSteps;

  // Daily (normalized to day 0)
  const windowData = useMemo(() => {
    if (!dates.length || windowLen < 2) return [] as any[];
    const sliceDates = dates.slice(0, windowLen);
    return sliceDates.map((date, idx) => {
      const row: Record<string, any> = { date };
      assets.forEach((a) => {
        const base = rawData[a]?.[0] ? latestClose(rawData[a][0]) : 1;
        const val = rawData[a]?.[idx] ? latestClose(rawData[a][idx]) : base;
        row[a] = base ? val / base : 1;
      });
      return row;
    });
  }, [assets, dates, windowLen, rawData]);

  // Intraday (single day, normalized to first tick of the day)
  const visibleDates = useMemo(() => dates.slice(0, Math.max(0, windowLen)), [dates, windowLen]);
  const activeIntradayIdx = intradayDayIdx ?? Math.min(windowLen - 1, dates.length - 1);
  const intradayData = useMemo(() => {
    if (!dates.length || activeIntradayIdx == null || activeIntradayIdx < 0) return [] as any[];
    const maxBars = assets.reduce((m, a) => Math.max(m, rawData[a]?.[activeIntradayIdx]?.close?.length ?? 0), 0);
    const dayFirstPrice: Record<string, number> = {};
    assets.forEach(a => {
      const arr = rawData[a]?.[activeIntradayIdx]?.close ?? [];
      dayFirstPrice[a] = arr.length ? arr[0] : 1;
    });
    const rows: any[] = [];
    for (let i = 0; i < maxBars; i++) {
      const row: Record<string, any> = { idx: i + 1 };
      assets.forEach(a => {
        const arr = rawData[a]?.[activeIntradayIdx]?.close ?? [];
        const val = arr[i];
        row[a] = typeof val === "number" ? (dayFirstPrice[a] ? val / dayFirstPrice[a] : null) : null;
      });
      rows.push(row);
    }
    return rows;
  }, [assets, rawData, dates.length, activeIntradayIdx]);

  function realizedNextDayReturn(asset: string) {
    const t = windowLen - 1;
    if (t + 1 >= dates.length) return null as any;
    const series = rawData[asset];
    const ret = latestClose(series[t + 1]) / latestClose(series[t]) - 1;
    return { date: dates[t + 1], ret };
  }

  function loadExample() {
    const example = generateFakeJSON();
    const keys = Object.keys(example);
    const first = example[keys[0]];
    const dsDates = first.map((d) => d.date);
    const id = stableHash(JSON.stringify({ assets: keys, dates: dsDates }));
    persistDatasetLocal(id, example, keys, dsDates, "Example");
    loadDatasetIntoState(id, example, keys, dsDates, "Example");
  }

  function resetSession() {
    setSelections([]);
    setSelectedAsset(null);
    setStep(1);
    setWindowLen(Math.min(START_DAY, Math.max(1, dates.length)));
    setMessage("Session reset.");
    setViewMode("daily");
    setIntradayDayIdx(null);
    if (datasetId) localStorage.removeItem(LS_ANN_PREFIX(datasetId, userId));
  }

  function exportLog() {
    const blob = new Blob([JSON.stringify(selections, null, 2)], { type: "application/json" });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `selections_${new Date().toISOString().slice(0,10)}_${userId}.json`;
    a.click();
    URL.revokeObjectURL(url);
  }

  function confirmSelection() {
    if (confirming) return;
    if (!selectedAsset) { setMessage("Select a line first."); return; }
    setConfirming(true);
    const res = realizedNextDayReturn(selectedAsset);
    if (!res) { setMessage("No more data available."); setConfirming(false); return; }
    const entry = { step, date: res.date, asset: selectedAsset, ret: res.ret };
    setSelections((prev) => [...prev, entry]);
    setWindowLen((w) => Math.min(w + 1, maxDays));
    setStep((s) => s + 1);
    setSelectedAsset(null);
    setConfirming(false);
    setMessage(`Pick ${step}: ${aliasOf(selectedAsset)} → next-day return ${(res.ret * 100).toFixed(2)}%`);
  }

  const portfolioSeries = useMemo(() => {
    let value = 1;
    const pts = selections.map((s) => { value *= 1 + s.ret; return { step: s.step, date: s.date, value }; });
    return [{ step: 0, date: "start", value: 1 }, ...pts];
  }, [selections]);

  const stats = useMemo(() => {
    const rets = selections.map((s) => s.ret);
    const N = rets.length;
    const cum = portfolioSeries.at(-1)?.value ?? 1;
    const mean = N ? rets.reduce((a, b) => a + b, 0) / N : 0;
    const variance = N ? rets.reduce((a, b) => a + (b - mean) ** 2, 0) / N : 0;
    const stdev = Math.sqrt(variance);
    const sharpe = stdev ? (mean * 252) / (stdev * Math.sqrt(252)) : 0;
    const wins = rets.filter((r) => r > 0).length;
    return { cumRet: cum - 1, stdev, sharpe, wins, N };
  }, [portfolioSeries, selections]);

  function buildFinalPayload() {
    const lastStep = selections.reduce((m, s) => Math.max(m, s.step), 0);
    const start30 = Math.max(1, lastStep - 30 + 1);
    const countsAll = assets.reduce((acc: Record<string, number>, a: string) => { acc[a] = 0; return acc; }, {} as Record<string, number>);
    selections.forEach((s) => { countsAll[s.asset] = (countsAll[s.asset] || 0) + 1; });
    const rankAll = assets.map((a) => ({ asset: a, votes: countsAll[a] || 0 })).sort((x, y) => y.votes - x.votes);
    let value = 1;
    const portfolio = selections.map((s) => { value *= 1 + s.ret; return { step: s.step, date: s.date, value }; });
    const lastStep2 = selections.reduce((m, s) => Math.max(m, s.step), 0);
    const start30_ = Math.max(1, lastStep2 - 30 + 1);
    const lastCols = Array.from({ length: Math.min(30, lastStep2 ? lastStep2 - start30_ + 1 : 0) }, (_, i) => start30_ + i);
    const heatGrid = assets.map((a) => ({ asset: a, cells: lastCols.map((c) => (selections.some((s) => s.asset === a && s.step === c) ? 1 : 0)) }));
    const rets = selections.map((s) => s.ret);
    const N = rets.length;
    const cum = portfolio.at(-1)?.value ?? 1;
    const mean = N ? rets.reduce((a, b) => a + b, 0) / N : 0;
    const variance = N ? rets.reduce((a, b) => a + (b - mean) ** 2, 0) / N : 0;
    const stdev = Math.sqrt(variance);
    const sharpe = stdev ? (mean * 252) / (stdev * Math.sqrt(252)) : 0;
    const wins = rets.filter((r) => r > 0).length;

    return {
      meta: { saved_at: new Date().toISOString(), start_day: START_DAY, max_days: maxDays, max_steps: maxSteps, dataset_id: datasetId, dataset_name: datasetName },
      assets,
      dates,
      selections,
      portfolio,
      stats: { cumRet: (cum - 1), stdev, sharpe, wins, N },
      preference_all: rankAll,
      heatmap_last30: { cols: lastCols, grid: heatGrid },
    };
  }

  useEffect(() => {
    const final = windowLen >= maxDays || selections.length >= maxSteps;
    if (final && !finalSaved) {
      try {
        const payload = buildFinalPayload();
        localStorage.setItem("asset_experiment_final", JSON.stringify(payload));
        const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = `run_summary_${new Date().toISOString().slice(0, 10)}_${userId}.json`;
        a.click();
        URL.revokeObjectURL(url);
        setFinalSaved(true);
      } catch (e) { console.warn("Failed to save final JSON:", e); }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [windowLen, selections.length, finalSaved, maxDays, maxSteps]);

  useEffect(() => {
    function onKey(e: KeyboardEvent) {
      const tag = (e.target && (e.target as HTMLElement).tagName) || "";
      if (tag === "INPUT" || tag === "TEXTAREA") return;
      const idx = parseInt((e as any).key, 10) - 1;
      if (!Number.isNaN(idx) && idx >= 0 && idx < assets.length) setSelectedAsset(assets[idx]);
      if ((e as any).key === "Enter" && Boolean(selectedAsset) && windowLen < maxDays) confirmSelection();
    }
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [assets, selectedAsset, windowLen, maxDays]);

  if (!datasetId) {
    return (
      <div className="p-6">
        <h1 className="text-xl font-semibold mb-3">Asset Choice Simulation</h1>
        <p className="text-sm text-gray-600 mb-2">Upload a dataset (new format) or load example. Data persists locally & on the Space backend.</p>
        <input type="file" accept="application/json" onChange={onFile} className="text-sm" />
        <div className="mt-4">
          <button onClick={loadExample} className="text-sm px-3 py-1.5 rounded-xl bg-blue-100 text-blue-800 hover:bg-blue-200">Load Example</button>
        </div>
      </div>
    );
  }

  const IntradayControls = () => (
    <div className="flex items-center gap-2">
      <span className="text-xs px-2 py-1 rounded bg-indigo-50 text-indigo-700">
        Intraday • {visibleDates[intradayDayIdx ?? 0] || "-"}
      </span>
      <button onClick={() => setIntradayDayIdx(i => (i == null ? 0 : Math.max(0, i - 1)))} className="text-xs px-2 py-1 rounded-xl border border-gray-200 hover:bg-gray-50" disabled={(intradayDayIdx ?? 0) <= 0} title="Prev day">‹</button>
      <select className="text-xs px-2 py-1 rounded-xl border border-gray-200" value={intradayDayIdx ?? 0} onChange={(e) => setIntradayDayIdx(Number(e.target.value))}>
        {visibleDates.map((d, idx) => <option key={d} value={idx}>{d}</option>)}
      </select>
      <button onClick={() => setIntradayDayIdx(i => (i == null ? 0 : Math.min(visibleDates.length - 1, i + 1)))} className="text-xs px-2 py-1 rounded-xl border border-gray-200 hover:bg-gray-50" disabled={(intradayDayIdx ?? 0) >= visibleDates.length - 1} title="Next day">›</button>
    </div>
  );

  return (
    <div className="p-6 bg-gray-50 min-h-screen flex flex-col gap-6">
      <div className="flex flex-wrap justify-between items-center gap-3">
        <div className="flex items-center gap-2">
          <h1 className="text-xl font-semibold">Asset Choice Simulation</h1>
          <span className="text-[10px] ml-2 px-1.5 py-0.5 rounded bg-amber-200 text-amber-900">BUILD_TAG: 2025-10-28</span>
          <span className="text-xs text-gray-500">Dataset: {datasetName || datasetId}</span>
          <span className="text-xs text-gray-500">User: {userId.slice(0,8)}…</span>
          <span className="text-xs text-gray-500">Day {windowLen} / {maxDays}</span>
        </div>
        <div className="flex flex-wrap items-center gap-2">
          <input type="file" accept="application/json" onChange={onFile} className="text-sm" />
          <button onClick={loadExample} className="text-sm px-3 py-1.5 rounded-xl bg-blue-100 text-blue-800 hover:bg-blue-200">Load Example</button>
          <button onClick={resetSession} className="text-sm px-3 py-1.5 rounded-xl bg-gray-200 hover:bg-gray-300">Reset (Keep data)</button>

          <div className="flex rounded-xl overflow-hidden border border-gray-200">
            <button className={`text-xs px-3 py-1.5 ${viewMode==="daily" ? "bg-gray-900 text-white" : "bg-white text-gray-700 hover:bg-gray-50"}`} onClick={()=>setViewMode("daily")}>Daily</button>
            <button className={`text-xs px-3 py-1.5 ${viewMode==="intraday" ? "bg-gray-900 text-white" : "bg-white text-gray-700 hover:bg-gray-50"}`} onClick={()=>setViewMode("intraday")}>Intraday (1 day)</button>
          </div>

          <button onClick={exportLog} className="text-sm px-3 py-1.5 rounded-xl bg-gray-900 text-white hover:bg-black">Export My Log</button>
        </div>
      </div>

      <div className="bg-white p-4 rounded-2xl shadow">
        <div className="flex flex-wrap items-center gap-2 mb-3">
          {assets.map((a, i) => (
            <button
              key={`pick-${a}`}
              onClick={() => setSelectedAsset(a)}
              className={`text-xs px-2 py-1 rounded-xl border transition ${selectedAsset===a?"bg-blue-600 text-white border-blue-600":"bg-white text-gray-700 border-gray-200 hover:bg-gray-50"}`}
              title={`Hotkey ${i+1}`}
            >
              <span className="inline-block w-2.5 h-2.5 rounded-full mr-2" style={{backgroundColor: `hsl(${(360/assets.length)*i},70%,50%)`}} />
              {i+1}. {aliasOf(a)}
            </button>
          ))}
          {assets.length>0 && <span className="text-xs text-gray-500 ml-1">Hotkeys: 1..{assets.length}, Enter confirm</span>}
        </div>

        <div className="h-80 relative">
          <ResponsiveContainer width="100%" height="100%">
            {viewMode === "intraday" ? (
              <LineChart data={intradayData}>
                <CartesianGrid strokeDasharray="3 3" />
                <XAxis dataKey="idx" tick={{ fontSize: 10 }} />
                <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
                <Tooltip contentStyle={{ fontSize: 12 }} formatter={(v: any, n: any) => [v, aliasMap[n] || n]} />
                <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} formatter={(v: any) => aliasMap[v] || v} />
                {assets.map((a, i) => (
                  <Line
                    key={a}
                    type="linear"
                    dataKey={a}
                    name={aliasMap[a] || a}
                    strokeWidth={selectedAsset === a ? 5 : hoverAsset === a ? 4 : 2.5}
                    strokeOpacity={selectedAsset && selectedAsset !== a ? 0.3 : 1}
                    dot={false}
                    isAnimationActive={false}
                    stroke={`hsl(${(360 / assets.length) * i},70%,50%)`}
                    onMouseEnter={() => setHoverAsset(a)}
                    onMouseLeave={() => setHoverAsset(null)}
                    onClick={() => setSelectedAsset((p) => (p === a ? null : a))}
                    connectNulls={false}
                  />
                ))}
              </LineChart>
            ) : (
              <LineChart data={windowData}>
                <CartesianGrid strokeDasharray="3 3" />
                <XAxis dataKey="date" tick={{ fontSize: 10 }} />
                <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
                <Tooltip contentStyle={{ fontSize: 12 }} formatter={(v: any, n: any) => [v, aliasMap[n] || n]} />
                <Legend onClick={(o: any) => setSelectedAsset(o.value)} wrapperStyle={{ cursor: "pointer" }} formatter={(v: any) => aliasMap[v] || v} />
                {assets.map((a, i) => (
                  <Line
                    key={a}
                    type="natural"
                    dataKey={a}
                    name={aliasMap[a] || a}
                    strokeWidth={selectedAsset === a ? 5 : hoverAsset === a ? 4 : 2.5}
                    strokeOpacity={selectedAsset && selectedAsset !== a ? 0.3 : 1}
                    dot={false}
                    isAnimationActive={false}
                    stroke={`hsl(${(360 / assets.length) * i},70%,50%)`}
                    onMouseEnter={() => setHoverAsset(a)}
                    onMouseLeave={() => setHoverAsset(null)}
                    onClick={() => setSelectedAsset((p) => (p === a ? null : a))}
                  />
                ))}
              </LineChart>
            )}
          </ResponsiveContainer>
        </div>

        {viewMode==="intraday" && (
          <div className="mt-3">
            <IntradayControls />
          </div>
        )}

        {viewMode!=="intraday" && (
          <div className="flex justify-between items-center mt-3">
            <div className="text-sm text-gray-600">
              Selected: {selectedAsset ? aliasOf(selectedAsset) : "(none)"} {message && <span className="ml-2 text-gray-500">{message}</span>}
            </div>
            <button onClick={confirmSelection} disabled={!selectedAsset || windowLen >= maxDays} className={`px-4 py-2 rounded-xl ${selectedAsset && windowLen < maxDays ? "bg-blue-600 text-white" : "bg-gray-200 text-gray-500"}`}>
              Confirm & Next Day →
            </button>
          </div>
        )}
      </div>

      <div className="bg-white p-4 rounded-2xl shadow">
        <h2 className="font-medium mb-2">Portfolio</h2>
        <div className="h-64">
          <ResponsiveContainer width="100%" height="100%">
            <AreaChart data={portfolioSeries}>
              <CartesianGrid strokeDasharray="3 3" />
              <XAxis dataKey="step" tick={{ fontSize: 10 }} />
              <YAxis domain={["auto", "auto"]} tick={{ fontSize: 10 }} />
              <Tooltip />
              <Area type="monotone" dataKey="value" stroke="#2563eb" fill="#bfdbfe" />
            </AreaChart>
          </ResponsiveContainer>
        </div>
        <ul className="text-sm text-gray-700 mt-2">
          <li>Cumulative Return: {(stats.cumRet * 100).toFixed(2)}%</li>
          <li>Volatility: {(stats.stdev * 100).toFixed(2)}%</li>
          <li>Sharpe: {stats.sharpe.toFixed(2)}</li>
          <li>Winning Days: {stats.wins}/{stats.N}</li>
        </ul>
      </div>

      <div className="bg-white p-4 rounded-2xl shadow">
        <h2 className="font-medium mb-2">Daily Selections</h2>
        <div className="overflow-auto rounded-xl border border-gray-100">
          <table className="min-w-full text-xs">
            <thead className="bg-gray-50 text-gray-500">
              <tr>
                <th className="px-2 py-1 text-left">Step</th>
                <th className="px-2 py-1 text-left">Date (t+1)</th>
                <th className="px-2 py-1 text-left">Asset</th>
                <th className="px-2 py-1 text-right">Return</th>
              </tr>
            </thead>
            <tbody>
              {selections.slice().reverse().map((s) => (
                <tr key={`${s.step}-${s.asset}-${s.date}`} className="odd:bg-white even:bg-gray-50">
                  <td className="px-2 py-1">{s.step}</td>
                  <td className="px-2 py-1">{s.date}</td>
                  <td className="px-2 py-1">{aliasOf(s.asset)}</td>
                  <td className={`px-2 py-1 text-right ${s.ret>=0?"text-green-600":"text-red-600"}`}>{(s.ret*100).toFixed(2)}%</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

/** Final summary component (UI shows aliases, data keeps real tickers) */
export function FinalSummary({ assets, selections }: { assets: string[]; selections: { step: number; date: string; asset: string; ret: number }[] }) {
  const rets = selections.map(s=>s.ret);
  const N = rets.length;
  const cum = rets.reduce((v,r)=> v*(1+r), 1) - 1;
  const mean = N ? rets.reduce((a,b)=>a+b,0)/N : 0;
  const variance = N ? rets.reduce((a,b)=> a + (b-mean)**2, 0)/N : 0;
  const stdev = Math.sqrt(variance);
  const sharpe = stdev ? (mean*252)/(stdev*Math.sqrt(252)) : 0;
  const wins = rets.filter(r=>r>0).length;

  const countsAll: Record<string, number> = assets.reduce((acc: any,a: string)=>{acc[a]=0;return acc;},{} as Record<string, number>);
  selections.forEach(s=>{ countsAll[s.asset] = (countsAll[s.asset]||0)+1; });
  const rankAll = assets.map(a=>({ asset:a, votes: countsAll[a]||0 })).sort((x,y)=> y.votes - x.votes);

  const aliasMap = useMemo(() => {
    const m: Record<string, string> = {};
    assets.forEach((a, i) => { m[a] = `Ticker ${i + 1}`; });
    return m;
  }, [assets]);

  const rankAllForChart = useMemo(() => {
    return rankAll.map(r => ({ asset: aliasMap[r.asset] || r.asset, votes: r.votes }));
  }, [rankAll, aliasMap]);

  const lastStep = selections.reduce((m,s)=>Math.max(m,s.step),0);
  const start30 = Math.max(1, lastStep - 30 + 1);
  const cols = Array.from({length: Math.min(30,lastStep ? lastStep - start30 + 1 : 0)}, (_,i)=> start30 + i);
  const grid = assets.map(a=>({ asset: aliasMap[a] || a, cells: cols.map(c => selections.some(s=> s.asset===a && s.step===c) ? 1 : 0) }));

  return (
    <div className="p-6 bg-gray-50 min-h-screen flex flex-col gap-6">
      <h1 className="text-xl font-semibold">Final Summary</h1>
      <div className="bg-white p-4 rounded-2xl shadow">
        <h2 className="font-medium mb-2">Overall Metrics</h2>
        <ul className="text-sm text-gray-700 space-y-1">
          <li>Total Picks: {N}</li>
          <li>Win Rate: {(N? (wins/N*100):0).toFixed(1)}%</li>
          <li>Cumulative Return: {(cum*100).toFixed(2)}%</li>
          <li>Volatility: {(stdev*100).toFixed(2)}%</li>
          <li>Sharpe (rough): {sharpe.toFixed(2)}</li>
          <li>Top Preference: {aliasMap[rankAll[0]?.asset ?? ""] ?? "-" } ({rankAll[0]?.votes ?? 0})</li>
        </ul>
      </div>
      <div className="bg-white p-4 rounded-2xl shadow">
        <h2 className="font-medium mb-2">Selection Preference Ranking</h2>
        <div className="h-56">
          <ResponsiveContainer width="100%" height="100%">
            <BarChart data={rankAllForChart} margin={{ left: 8, right: 8, top: 8, bottom: 8 }}>
              <CartesianGrid strokeDasharray="3 3" />
              <XAxis dataKey="asset" tick={{ fontSize: 10 }} />
              <YAxis allowDecimals={false} tick={{ fontSize: 10 }} />
              <Tooltip />
              <Bar dataKey="votes" fill="#60a5fa" />
            </BarChart>
          </ResponsiveContainer>
        </div>
      </div>
      <div className="bg-white p-4 rounded-2xl shadow">
        <h2 className="font-medium mb-2">Selection Heatmap (Last 30 Steps)</h2>
        <div className="overflow-auto">
          <table className="text-xs border-collapse">
            <thead>
              <tr>
                <th className="p-1 pr-2 text-left sticky left-0 bg-white">Asset</th>
                {cols.map(c=> (<th key={c} className="px-1 py-1 text-center">{c}</th>))}
              </tr>
            </thead>
            <tbody>
              {grid.map(row => (
                <tr key={row.asset}>
                  <td className="p-1 pr-2 font-medium sticky left-0 bg-white">{row.asset}</td>
                  {row.cells.map((v,j)=> (
                    <td key={j} className="w-6 h-6" style={{ background: v? "#2563eb" : "#e5e7eb", opacity: v? 0.9 : 1, border: "1px solid #ffffff" }} />
                  ))}
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}
