import { useCallback, useEffect, useState } from "react";
import { apiFetch, clearSession, getToken, type NodeCard } from "../api";

function pickStrategy(n: NodeCard) {
  const fleet = (n.fleet || {}) as Record<string, unknown>;
  const health = (n.health || {}) as Record<string, unknown>;
  const strat =
    (fleet.strategy as Record<string, unknown> | undefined) ||
    (health.strategy as Record<string, unknown> | undefined) ||
    {};
  const position =
    (fleet.position as Record<string, unknown> | undefined) || {};
  return {
    mode: String(fleet.mode || health.mode || "-"),
    running: strat.running,
    phase: String(strat.phase ?? "-"),
    rounds: strat.rounds_done,
    market: fleet.market_connected ?? health.market_connected,
    exchange: String(fleet.exchange || health.exchange || "-"),
    strat,
    position,
    pair: fleet.pair as Record<string, unknown> | null | undefined,
    index_px: fleet.index_px,
  };
}

function fmt(v: unknown, digits = 4): string {
  if (v == null || v === "") return "—";
  const n = Number(v);
  if (!Number.isFinite(n)) return String(v);
  return n.toFixed(digits);
}

/** 价格按交易所常见精度：永续/指数 0.01；期权权利金 0.1 */
function fmtExPx(kind: unknown, px: unknown): string {
  if (px == null || px === "") return "—";
  const n = Number(px);
  if (!Number.isFinite(n)) return String(px);
  if (String(kind || "").toLowerCase() === "option") {
    return (Math.round(n * 10) / 10).toFixed(1);
  }
  return (Math.round(n * 100) / 100).toFixed(2);
}

function pnlClass(v: unknown): string {
  const n = Number(v);
  if (!Number.isFinite(n) || n === 0) return "pnl-flat";
  return n > 0 ? "pnl-pos" : "pnl-neg";
}

const OPEN_STATUSES = new Set([
  "open",
  "half_open",
  "option_closed_perp_pending",
  "opening",
]);

function hasOpenPosition(position: Record<string, unknown>): boolean {
  if (position.has_position === true) return true;
  const st = String(position.status || "").toLowerCase();
  return OPEN_STATUSES.has(st);
}

function unitLabel(v: unknown): string {
  const n = Number(v);
  if (!Number.isFinite(n)) return "—";
  if (Number.isInteger(n)) return String(n);
  return n.toFixed(2).replace(/\.?0+$/, "");
}

function leveragePair(strat: Record<string, unknown>): string {
  const lev = Number(strat.leverage);
  const opt = Number(strat.min_option_leverage);
  const a = Number.isFinite(lev) ? `${Math.round(lev)}x` : "—";
  const b = Number.isFinite(opt) ? `${Math.round(opt)}x` : "—";
  return `${a}/${b}`;
}

type ResidualRow = {
  key: string;
  nodeId: number;
  nodeName: string;
  group_id: string;
  option_inst_id: string;
  option_qty_eth: number | null;
  initial_premium: number | null;
  bid_px: number | null;
  bid_sz_eth: number | null;
  current_premium: number | null;
  recovery_pct: number | null;
  liquidity_ok: boolean;
};

function fmtBidLiquidity(
  bidPx: number | null,
  bidSzEth: number | null,
): string {
  if (bidPx == null && bidSzEth == null) return "—";
  const px = bidPx == null ? "—" : fmtExPx("option", bidPx);
  const sz = bidSzEth == null ? "—" : fmt(bidSzEth, 2);
  return `${px} / ${sz}`;
}

function collectResiduals(nodes: NodeCard[]): ResidualRow[] {
  const out: ResidualRow[] = [];
  for (const n of nodes) {
    const fleet = (n.fleet || {}) as Record<string, unknown>;
    const list = fleet.residuals;
    if (!Array.isArray(list)) continue;
    for (const raw of list) {
      if (!raw || typeof raw !== "object") continue;
      const r = raw as Record<string, unknown>;
      const gid = String(r.group_id || "");
      if (!gid) continue;
      const numOrNull = (v: unknown) => {
        if (v == null || v === "") return null;
        const x = Number(v);
        return Number.isFinite(x) ? x : null;
      };
      out.push({
        key: `${n.id}:${gid}`,
        nodeId: n.id,
        nodeName: n.name,
        group_id: gid,
        option_inst_id: String(r.option_inst_id || "—"),
        option_qty_eth: numOrNull(r.option_qty_eth),
        initial_premium: numOrNull(r.initial_premium),
        bid_px: numOrNull(r.bid_px),
        bid_sz_eth: numOrNull(r.bid_sz_eth),
        current_premium: numOrNull(r.current_premium),
        recovery_pct: numOrNull(r.recovery_pct),
        liquidity_ok: r.liquidity_ok === true,
      });
    }
  }
  return out;
}

type NodeStats = {
  mode?: string;
  show_slip?: boolean;
  groups: number;
  wins: number;
  win_rate: number;
  total_pnl: number;
  fees_perp?: number;
  fees_option?: number;
  total_fees: number;
  total_slip?: number;
  latest_funds?: number;
  max_single_loss?: number;
  loss_streak?: number;
  close_reasons: Record<string, number>;
  equity_curve: {
    group_id: string;
    realized_pnl: number;
    close_at_ms?: number | null;
  }[];
};

const CLOSE_REASON_ZH: Record<string, string> = {
  fixed_usdt: "固定净盈利达标·双腿全平",
  premium_multiple: "权利金倍数达标·双腿全平",
  target_perp_only: "净盈利达标·只平永续(期权归档)",
  expiry: "到期结算",
  emergency: "紧急全平",
  manual: "手动平仓",
  liquidity_retry: "等待流动性后全平",
  unknown: "未知",
};

function closeReasonZh(v: string): string {
  return CLOSE_REASON_ZH[v] || v;
}

type RiskLines = {
  riskBased: boolean;
  isOo: boolean;
  sizing: string;
  sizingShort: string;
  lossPct: string | null;
  exit: string;
  openRatio: string | null;
  /** 表格「风险/开仓」列：永期 5%/1:2/12；期期 ≤2%·12h/C:P/≥24 */
  riskOrOpen: string;
  riskOrOpenTitle: string;
  minHours: string;
  leverage: string;
  leverageTitle: string;
};

function isOptionOption(strat: Record<string, unknown>): boolean {
  return (
    String(strat.hedge_mode || "")
      .trim()
      .toLowerCase() === "option_option"
  );
}

function riskLines(strat: Record<string, unknown>): RiskLines {
  const isOo = isOptionOption(strat);
  const riskBased =
    strat.sizing_mode === "risk_based" || strat.risk_based === true;

  if (isOo) {
    const ampN = Number(strat.oo_amplitude_pct);
    const ampH = Number(strat.oo_amplitude_hours);
    const minH = Number(
      strat.oo_min_option_hours ?? strat.min_option_hours,
    );
    const amp =
      Number.isFinite(ampN) && Number.isFinite(ampH)
        ? `≤${fmt(ampN, ampN % 1 === 0 ? 0 : 1)}%·${fmt(ampH, 0)}h`
        : "≤—";
    const callQ =
      strat.risk_sizing_preview &&
      typeof strat.risk_sizing_preview === "object" &&
      (strat.risk_sizing_preview as Record<string, unknown>).call_qty_eth !=
        null
        ? (strat.risk_sizing_preview as Record<string, unknown>).call_qty_eth
        : strat.option_qty_eth;
    const putQ =
      strat.risk_sizing_preview &&
      typeof strat.risk_sizing_preview === "object" &&
      (strat.risk_sizing_preview as Record<string, unknown>).put_qty_eth != null
        ? (strat.risk_sizing_preview as Record<string, unknown>).put_qty_eth
        : strat.oo_put_qty_eth ?? strat.option_qty_eth;
    const openRatio = `C${unitLabel(callQ)}:P${unitLabel(putQ)}`;
    const minHours = Number.isFinite(minH) ? `≥${unitLabel(minH)}` : "≥—";
    const ratioN = Number(strat.oo_reward_ratio);
    const target = strat.exit_target_usdt ?? strat.net_profit_target;
    let exit: string;
    if (target != null && Number.isFinite(Number(target))) {
      exit = `目标${fmt(target, 2)}U`;
    } else if (Number.isFinite(ratioN)) {
      exit = `预算×${fmt(ratioN, ratioN % 1 === 0 ? 0 : 2)}`;
    } else {
      exit = "预算×—";
    }
    const ooLev = Number(strat.oo_min_leverage);
    const leverage = Number.isFinite(ooLev)
      ? `≥${Math.round(ooLev)}x`
      : "≥—x";
    return {
      riskBased,
      isOo: true,
      sizing: riskBased ? "以损定仓(A)" : "手动开仓(B)",
      sizingShort: riskBased ? "A" : "B",
      lossPct: Number.isFinite(ampN)
        ? `≤${fmt(ampN, ampN % 1 === 0 ? 0 : 1)}%`
        : null,
      exit,
      openRatio,
      riskOrOpen: `${amp}/${openRatio}/${minHours}`,
      riskOrOpenTitle: "振幅上限·回看 / Call:Put 数量 / 最短剩余(h)",
      minHours,
      leverage,
      leverageTitle: "期期单腿最低杠杆",
    };
  }

  const exitMode = String(strat.exit_mode || "fixed_usdt");
  let exit: string;
  if (riskBased) {
    exit =
      strat.risk_exit_unit != null && Number.isFinite(Number(strat.risk_exit_unit))
        ? `基数${unitLabel(strat.risk_exit_unit)}`
        : "基数—";
  } else if (exitMode === "premium_multiple") {
    exit =
      strat.premium_exit_multiple != null
        ? `权利金×${fmt(strat.premium_exit_multiple, 2)}`
        : "权利金×—";
  } else {
    const t = strat.net_profit_target ?? strat.exit_target_usdt;
    exit =
      t != null && Number.isFinite(Number(t))
        ? `固定 ${fmt(t, 2)}U`
        : "固定 —";
  }
  let lossPct: string | null = null;
  if (riskBased) {
    const lossN = Number(strat.risk_loss_pct);
    lossPct = Number.isFinite(lossN)
      ? `${fmt(lossN, lossN % 1 === 0 ? 0 : 2)}%`
      : "—";
  }
  const openRatio = riskBased
    ? `${unitLabel(strat.risk_perp_unit)}:${unitLabel(strat.risk_option_unit)}`
    : `${unitLabel(strat.perp_qty_eth)}:${unitLabel(strat.option_qty_eth)}`;
  const hoursN = Number(strat.min_option_hours);
  const minHours = Number.isFinite(hoursN) ? unitLabel(hoursN) : "—";
  const riskOrOpen = `${lossPct || "—"}/${openRatio}/${minHours}`;
  return {
    riskBased,
    isOo: false,
    sizing: riskBased ? "以损定仓(A)" : "手动开仓(B)",
    sizingShort: riskBased ? "A" : "B",
    lossPct,
    exit,
    openRatio,
    riskOrOpen,
    riskOrOpenTitle: "风险比例 / 开仓比例 / 最小剩余时长(h)",
    minHours,
    leverage: leveragePair(strat),
    leverageTitle: "永续杠杆 / 期权最低杠杆",
  };
}

function hedgeModeShort(strat: Record<string, unknown>): string {
  const hm = String(strat.hedge_mode || "perp_option")
    .trim()
    .toLowerCase();
  return hm === "option_option" ? "期期" : "永期";
}

function modeParts(
  mode: string,
  strat: Record<string, unknown>,
): { head: string; hedge: string; isOo: boolean } {
  const m = String(mode || "-").toUpperCase();
  const short = m.includes("LIVE") ? "LIVE" : m.includes("SIM") ? "SIM" : m || "-";
  const hedge = hedgeModeShort(strat);
  return {
    head: `${short}/${riskLines(strat).sizingShort}`,
    hedge,
    isOo: hedge === "期期",
  };
}

function modeLabel(mode: string, strat: Record<string, unknown>): string {
  const p = modeParts(mode, strat);
  return `${p.head}/${p.hedge}`;
}

function ModeLabelView({
  mode,
  strat,
}: {
  mode: string;
  strat: Record<string, unknown>;
}) {
  const p = modeParts(mode, strat);
  return (
    <>
      {p.head}/
      <span className={p.isOo ? "mode-hedge-oo" : "mode-hedge-po"}>
        {p.hedge}
      </span>
    </>
  );
}

function modeLabelTitle(mode: string, strat: Record<string, unknown>): string {
  const label = modeLabel(mode, strat);
  const riskBased =
    strat.sizing_mode === "risk_based" || strat.risk_based === true;
  const hedge =
    hedgeModeShort(strat) === "期期" ? "期期=双期权对冲" : "永期=永续+期权";
  return `${label}（${riskBased ? "A=以损定仓" : "B=手动开仓"}；${hedge}）`;
}

function tokenDotClass(n: {
  token_configured?: boolean;
  fleet_ok?: boolean | null;
}): string {
  if (!n.token_configured) return "dot muted";
  if (n.fleet_ok === false) return "dot warn";
  return "dot ok";
}

function tokenDotTitle(n: {
  token_configured?: boolean;
  fleet_ok?: boolean | null;
}): string {
  if (!n.token_configured) return "Token 未配对";
  if (n.fleet_ok === false) return "Token 配对失败";
  return "Token 已配对";
}

function RiskParamsBox({ strat }: { strat: Record<string, unknown> }) {
  const r = riskLines(strat);
  return (
    <div className="risk-box">
      <div className="risk-box-title">风控参数</div>
      <dl className="kv risk-kv">
        <div>
          <dt>定仓</dt>
          <dd>{r.sizing}</dd>
        </div>
        {r.isOo ? (
          <>
            <div>
              <dt>振幅</dt>
              <dd>{r.lossPct || "—"}</dd>
            </div>
            <div>
              <dt>Call:Put</dt>
              <dd>{r.openRatio || "—"}</dd>
            </div>
            <div>
              <dt>剩余</dt>
              <dd>{r.minHours}</dd>
            </div>
          </>
        ) : (
          <>
            {r.lossPct != null ? (
              <div>
                <dt>风险比例</dt>
                <dd>{r.lossPct}</dd>
              </div>
            ) : null}
            {r.openRatio ? (
              <div>
                <dt>开仓比例</dt>
                <dd>{r.openRatio}</dd>
              </div>
            ) : null}
          </>
        )}
        <div>
          <dt>出场</dt>
          <dd>{r.exit}</dd>
        </div>
        <div>
          <dt>{r.isOo ? "期权杠杆" : "杠杆"}</dt>
          <dd title={r.leverageTitle}>{r.leverage}</dd>
        </div>
      </dl>
    </div>
  );
}

/** 页面级快照：整页刷新前保留上次卡片，避免空白等待 */
let cachedNodes: NodeCard[] = [];
let cachedSseSec = 1;

export default function MonitorPage() {
  const [nodes, setNodes] = useState<NodeCard[]>(() => cachedNodes);
  const [err, setErr] = useState("");
  const [busy, setBusy] = useState<Record<number, string>>({});
  const [batchBusy, setBatchBusy] = useState(false);
  const [selected, setSelected] = useState<Set<number>>(new Set());
  const [sseSec, setSseSec] = useState(cachedSseSec);
  const [liveState, setLiveState] = useState<"connecting" | "live" | "retry">(
    cachedNodes.length ? "live" : "connecting",
  );
  const [detailId, setDetailId] = useState<number | null>(null);
  const [detailStats, setDetailStats] = useState<NodeStats | null>(null);
  const [detailStatsErr, setDetailStatsErr] = useState("");
  const [detailStatsLoading, setDetailStatsLoading] = useState(false);
  const [equityPage, setEquityPage] = useState(0);
  const [residualBusy, setResidualBusy] = useState<string>("");
  const EQUITY_PAGE_SIZE = 5;

  const applyNodes = useCallback((list: NodeCard[]) => {
    cachedNodes = list;
    setNodes(list);
  }, []);

  useEffect(() => {
    if (detailId == null) {
      setDetailStats(null);
      setDetailStatsErr("");
      setDetailStatsLoading(false);
      setEquityPage(0);
      return;
    }
    let cancelled = false;
    setDetailStats(null);
    setDetailStatsErr("");
    setDetailStatsLoading(true);
    setEquityPage(0);
    apiFetch<NodeStats>(`/api/nodes/${detailId}/stats`)
      .then((s) => {
        if (!cancelled) setDetailStats(s);
      })
      .catch((ex) => {
        if (!cancelled) {
          setDetailStatsErr(ex instanceof Error ? ex.message : String(ex));
        }
      })
      .finally(() => {
        if (!cancelled) setDetailStatsLoading(false);
      });
    return () => {
      cancelled = true;
    };
  }, [detailId]);

  const refresh = useCallback(async () => {
    try {
      const r = await apiFetch<{ nodes: NodeCard[] }>("/api/nodes/status/all");
      applyNodes(r.nodes || []);
      setErr("");
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    }
  }, [applyNodes]);

  useEffect(() => {
    apiFetch<{ sse_interval_sec?: number }>("/api/auth/me")
      .then((m) => {
        if (m.sse_interval_sec && Number(m.sse_interval_sec) > 0) {
          const sec = Number(m.sse_interval_sec);
          cachedSseSec = sec;
          setSseSec(sec);
        }
      })
      .catch(() => undefined);
  }, []);

  useEffect(() => {
    let cancelled = false;
    let retryMs = 1000;
    let timer: number | undefined;
    const ac = new AbortController();

    // 无缓存时先拉一次；有缓存则等 SSE，避免切页/重挂载双倍打满策略机
    if (!cachedNodes.length) {
      void refresh();
    }

    async function runStream() {
      while (!cancelled) {
        setLiveState(retryMs > 1000 ? "retry" : "connecting");
        try {
          const token = getToken();
          const headers = new Headers({ Accept: "text/event-stream" });
          if (token) headers.set("Authorization", `Bearer ${token}`);
          const res = await fetch("/api/nodes/status/stream", {
            headers,
            signal: ac.signal,
          });
          if (res.status === 401) {
            clearSession();
            setErr("登录已失效，请重新登录");
            setLiveState("retry");
            break;
          }
          if (!res.ok || !res.body) {
            throw new Error(`SSE HTTP ${res.status}`);
          }
          setLiveState("live");
          setErr("");
          retryMs = 1000;
          const reader = res.body.getReader();
          const decoder = new TextDecoder();
          let buf = "";
          let eventName = "message";
          while (!cancelled) {
            const { done, value } = await reader.read();
            if (done) break;
            buf += decoder.decode(value, { stream: true });
            const parts = buf.split("\n");
            buf = parts.pop() || "";
            for (const rawLine of parts) {
              const line = rawLine.replace(/\r$/, "");
              if (line.startsWith("event:")) {
                eventName = line.slice(6).trim() || "message";
                continue;
              }
              if (line.startsWith("data:")) {
                const data = line.slice(5).trim();
                if (!data) continue;
                try {
                  const parsed = JSON.parse(data) as {
                    nodes?: NodeCard[];
                    detail?: string;
                  };
                  if (eventName === "error") {
                    setErr(parsed.detail || "SSE 错误");
                  } else if (parsed.nodes) {
                    applyNodes(parsed.nodes);
                    setErr("");
                  }
                } catch {
                  /* ignore malformed chunk */
                }
                eventName = "message";
                continue;
              }
              if (line === "") {
                eventName = "message";
              }
            }
          }
        } catch (ex) {
          if (cancelled || (ex instanceof DOMException && ex.name === "AbortError")) {
            break;
          }
          setLiveState("retry");
          setErr(ex instanceof Error ? ex.message : String(ex));
        }
        if (cancelled) break;
        await new Promise<void>((resolve) => {
          timer = window.setTimeout(() => resolve(), retryMs);
        });
        retryMs = Math.min(retryMs * 2, 15000);
      }
    }

    void runStream();
    return () => {
      cancelled = true;
      ac.abort();
      if (timer != null) window.clearTimeout(timer);
    };
  }, [refresh, applyNodes]);

  async function act(id: number, action: "start" | "pause" | "update" | "login") {
    if (action === "update") {
      const node = nodes.find((x) => x.id === id);
      const name = node?.name || `#${id}`;
      const ok = window.confirm(
        `确认更新「${name}」代码？\n\n将 git pull 并 reload 策略进程，短暂中断 API；不会自动启动策略。\n请勿误点。`,
      );
      if (!ok) return;
    }
    setBusy((b) => ({ ...b, [id]: action }));
    setErr("");
    try {
      if (action === "login") {
        const r = await apiFetch<{ url: string }>(`/api/nodes/${id}/login-url`, {
          method: "POST",
        });
        window.open(r.url, "_blank", "noopener,noreferrer");
      } else {
        await apiFetch(`/api/nodes/${id}/${action === "pause" ? "pause" : action}`, {
          method: "POST",
        });
        await refresh();
      }
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    } finally {
      setBusy((b) => {
        const n = { ...b };
        delete n[id];
        return n;
      });
    }
  }

  async function batchUpdate(idsOverride?: number[]) {
    const ids = idsOverride ?? [...selected];
    if (!ids.length) return;
    const names = nodes
      .filter((n) => ids.includes(n.id))
      .map((n) => n.name)
      .join("、");
    const ok = window.confirm(
      `确认批量更新 ${ids.length} 台策略机？\n\n${names}\n\n将分别 git pull 并 reload，短暂中断；不会自动启动策略。`,
    );
    if (!ok) return;
    setErr("");
    try {
      await apiFetch("/api/nodes/update-batch", {
        method: "POST",
        body: JSON.stringify({ ids }),
      });
      await refresh();
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    }
  }

  function toggle(id: number) {
    setSelected((prev) => {
      const n = new Set(prev);
      if (n.has(id)) n.delete(id);
      else n.add(id);
      return n;
    });
  }

  const updatableIds = nodes
    .filter((n) => n.token_configured)
    .map((n) => n.id);
  const allSelected =
    updatableIds.length > 0 && updatableIds.every((id) => selected.has(id));

  function toggleSelectAll() {
    setSelected((prev) => {
      if (updatableIds.length === 0) return prev;
      if (updatableIds.every((id) => prev.has(id))) return new Set();
      return new Set(updatableIds);
    });
  }

  async function updateAll() {
    if (!updatableIds.length) {
      setErr("没有已配对 Token 的策略机可更新");
      return;
    }
    setSelected(new Set(updatableIds));
    await batchUpdate(updatableIds);
  }

  const startableIds = nodes
    .filter((n) => {
      if (!n.token_configured) return false;
      const s = pickStrategy(n);
      const running = s.running === true || s.running === 1;
      return !running;
    })
    .map((n) => n.id);

  async function closeResidual(row: ResidualRow) {
    const ok = window.confirm(
      `确认平掉残留期权？\n\n机器：${row.nodeName}\n组：${row.group_id}\n合约：${row.option_inst_id}\n\n仅校验买一流动性，不要求权利金回收比例。`,
    );
    if (!ok) return;
    setResidualBusy(row.key);
    setErr("");
    try {
      await apiFetch(`/api/nodes/${row.nodeId}/residual/close`, {
        method: "POST",
        body: JSON.stringify({ group_id: row.group_id }),
      });
      await refresh();
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    } finally {
      setResidualBusy("");
    }
  }

  async function startAll() {
    if (!startableIds.length) {
      setErr("没有可启动的策略机（需已配对且当前未运行）");
      return;
    }
    const names = nodes
      .filter((n) => startableIds.includes(n.id))
      .map((n) => n.name)
      .join("、");
    const ok = window.confirm(
      `确认全部启动 ${startableIds.length} 台策略机？\n\n${names}\n\n将并行调用各机 Fleet 启动（已在运行的不会列入）。`,
    );
    if (!ok) return;
    setErr("");
    setBatchBusy(true);
    try {
      const r = await apiFetch<{
        started?: number;
        total?: number;
        results?: { id: number; name?: string; ok: boolean; detail?: string }[];
      }>("/api/nodes/start-batch", {
        method: "POST",
        body: JSON.stringify({ ids: startableIds }),
      });
      await refresh();
      const fails = (r.results || []).filter((x) => !x.ok);
      if (fails.length) {
        setErr(
          `启动完成 ${r.started ?? 0}/${r.total ?? startableIds.length}；失败：` +
            fails
              .map((x) => `${x.name || x.id}${x.detail ? `(${x.detail})` : ""}`)
              .join("；"),
        );
      }
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    } finally {
      setBatchBusy(false);
    }
  }

  const detailNode = detailId != null ? nodes.find((x) => x.id === detailId) : null;
  const detail = detailNode ? pickStrategy(detailNode) : null;
  const detailRunning =
    detail != null && (detail.running === true || detail.running === 1);
  const detailLegs = Array.isArray(detail?.position?.legs)
    ? (detail!.position.legs as Record<string, unknown>[])
    : [];
  const equityNewestFirst = detailStats?.equity_curve?.length
    ? [...detailStats.equity_curve].reverse()
    : [];
  const equityPageCount = Math.max(
    1,
    Math.ceil(equityNewestFirst.length / EQUITY_PAGE_SIZE),
  );
  const equityPageSafe = Math.min(equityPage, equityPageCount - 1);
  const equityPageRows = equityNewestFirst.slice(
    equityPageSafe * EQUITY_PAGE_SIZE,
    equityPageSafe * EQUITY_PAGE_SIZE + EQUITY_PAGE_SIZE,
  );
  const residualRows = collectResiduals(nodes);

  return (
    <div>
      <div className="toolbar">
        <h2>监控区</h2>
        <div className="toolbar-actions">
          <span
            className={`live-pill ${
              liveState === "live" ? "ok" : liveState === "retry" ? "bad" : ""
            }`}
            title="中控 SSE 秒级推送"
          >
            {liveState === "live"
              ? `实时 · ${sseSec}s`
              : liveState === "retry"
                ? "重连中…"
                : "连接中…"}
          </span>
          <button type="button" className="btn ghost" onClick={() => void refresh()}>
            刷新
          </button>
          <button
            type="button"
            className="btn"
            disabled={batchBusy || !startableIds.length}
            onClick={() => void startAll()}
            title="并行启动全部已配对且未运行的策略机"
          >
            {batchBusy ? "启动中…" : `全部启动 (${startableIds.length})`}
          </button>
          <button
            type="button"
            className="btn ghost"
            disabled={!updatableIds.length}
            onClick={() => void updateAll()}
            title="选中全部已配对策略机并批量更新代码"
          >
            全选更新 ({updatableIds.length})
          </button>
          <button
            type="button"
            className="btn"
            disabled={!selected.size}
            onClick={() => void batchUpdate()}
          >
            勾选更新 ({selected.size})
          </button>
        </div>
      </div>
      {err ? <div className="err">{err}</div> : null}
      {nodes.length ? (
        <div className="monitor-table-wrap">
          <table className="monitor-table">
            <thead>
              <tr>
                <th className="col-check">
                  <input
                    type="checkbox"
                    checked={allSelected}
                    disabled={!updatableIds.length}
                    onChange={toggleSelectAll}
                    aria-label="全选"
                    title="全选已配对机器"
                  />
                </th>
                <th className="col-status">状态</th>
                <th>机器名字</th>
                <th>模式</th>
                <th>交易所</th>
                <th>持仓</th>
                <th>轮次</th>
                <th>风险/开仓</th>
                <th>出场</th>
                <th>杠杆</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              {nodes.map((n) => {
                const s = pickStrategy(n);
                const running = s.running === true || s.running === 1;
                const inPos = hasOpenPosition(s.position);
                const risk = riskLines(s.strat);
                return (
                  <tr
                    key={n.id}
                    className={`monitor-row ${running ? "is-running" : "is-stopped"}`}
                    onClick={() => setDetailId(n.id)}
                  >
                    <td
                      className="col-check"
                      onClick={(e) => e.stopPropagation()}
                    >
                      <input
                        type="checkbox"
                        checked={selected.has(n.id)}
                        onChange={() => toggle(n.id)}
                        aria-label={`选择 ${n.name}`}
                      />
                    </td>
                    <td className="col-status">
                      <div
                        className="status-dots"
                        title={`在线:${n.online ? "是" : "否"} · ${tokenDotTitle(n)} · ${running ? "运行中" : "已停"}`}
                      >
                        <span
                          className={`dot pulse d1 ${n.online ? "ok" : "bad"}`}
                          title={n.online ? "在线" : "离线"}
                        />
                        <span
                          className={`dot pulse d2 ${tokenDotClass(n).replace("dot ", "")}`}
                          title={tokenDotTitle(n)}
                        />
                        <span
                          className={`dot pulse d3 ${running ? "ok" : "bad"}`}
                          title={running ? "运行中" : "已停"}
                        />
                      </div>
                    </td>
                    <td>
                      <strong className="machine-name">{n.name}</strong>
                    </td>
                    <td
                      className="mono"
                      title={modeLabelTitle(s.mode, s.strat)}
                    >
                      <ModeLabelView mode={s.mode} strat={s.strat} />
                    </td>
                    <td className="mono">{s.exchange}</td>
                    <td className={inPos ? "pnl-pos" : "pnl-neg"}>
                      {inPos ? "持仓中" : "无持仓"}
                    </td>
                    <td className="mono">{s.rounds ?? "—"}</td>
                    <td
                      className="mono"
                      title={risk.riskOrOpenTitle}
                    >
                      {risk.riskOrOpen}
                    </td>
                    <td className="mono">{risk.exit}</td>
                    <td className="mono" title={risk.leverageTitle}>
                      {risk.leverage}
                    </td>
                    <td
                      className="col-actions"
                      onClick={(e) => e.stopPropagation()}
                    >
                      <div className="row-actions">
                        <button
                          type="button"
                          className={`btn btn-sm ${running ? "btn-running" : ""}`}
                          disabled={
                            !!busy[n.id] || !n.token_configured || running
                          }
                          onClick={() => void act(n.id, "start")}
                        >
                          {running ? "运行中" : "启动"}
                        </button>
                        <button
                          type="button"
                          className="btn btn-sm ghost"
                          disabled={!!busy[n.id] || !n.token_configured}
                          onClick={() => void act(n.id, "pause")}
                        >
                          停止
                        </button>
                        <button
                          type="button"
                          className="btn btn-sm"
                          disabled={!!busy[n.id] || !n.token_configured}
                          onClick={() => void act(n.id, "login")}
                        >
                          登录
                        </button>
                        <button
                          type="button"
                          className="btn btn-sm ghost"
                          disabled={!!busy[n.id] || !n.token_configured}
                          onClick={() => void act(n.id, "update")}
                        >
                          更新
                        </button>
                      </div>
                    </td>
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      ) : (
        <p className="meta">暂无策略机。请到「系统设置」添加并生成 Token。</p>
      )}

      {residualRows.length ? (
        <>
          <div className="toolbar residual-toolbar">
            <h2>残留期权</h2>
            <span className="meta">
              自动平仓仍要求权利金≥设定比例；下方「平仓」仅验流动性
            </span>
          </div>
          <div className="monitor-table-wrap">
            <table className="monitor-table residual-table">
              <thead>
                <tr>
                  <th>机器名</th>
                  <th>组名</th>
                  <th>期权名</th>
                  <th>数量</th>
                  <th>开仓权利金</th>
                  <th title="最新买一价格 / 买一数量(ETH)">买一流动性</th>
                  <th title="买一价 × 持仓数量">买一权利金</th>
                  <th title="买一权利金 ÷ 开仓权利金">占比</th>
                  <th>操作</th>
                </tr>
              </thead>
              <tbody>
                {residualRows.map((r) => (
                  <tr key={r.key}>
                    <td>{r.nodeName}</td>
                    <td className="mono">{r.group_id}</td>
                    <td className="mono">{r.option_inst_id}</td>
                    <td className="mono">{fmt(r.option_qty_eth, 2)}</td>
                    <td className="mono">{fmt(r.initial_premium, 2)}</td>
                    <td
                      className="mono"
                      title={
                        r.liquidity_ok
                          ? "买一深度可覆盖持仓"
                          : "买一不足或盘口不可用"
                      }
                    >
                      {fmtBidLiquidity(r.bid_px, r.bid_sz_eth)}
                    </td>
                    <td className="mono">{fmt(r.current_premium, 2)}</td>
                    <td className="mono">
                      {r.recovery_pct == null
                        ? "—"
                        : `${fmt(r.recovery_pct, 1)}%`}
                    </td>
                    <td className="col-actions">
                      <button
                        type="button"
                        className="btn btn-sm"
                        disabled={!!residualBusy || !r.liquidity_ok}
                        title={
                          r.liquidity_ok
                            ? "按最新买一 IOC 平仓（不验权利金比例）"
                            : "买一流动性不足或盘口不可用"
                        }
                        onClick={() => void closeResidual(r)}
                      >
                        {residualBusy === r.key ? "平仓中…" : "平仓"}
                      </button>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </>
      ) : null}

      {detailNode && detail ? (
        <div
          className="modal-backdrop"
          onClick={() => setDetailId(null)}
          role="presentation"
        >
          <div
            className={`modal-panel ${detailRunning ? "running" : ""}`}
            onClick={(e) => e.stopPropagation()}
            role="dialog"
            aria-modal="true"
            aria-label={`${detailNode.name} 详情`}
          >
            <header className="modal-head">
              <div>
                <h3>{detailNode.name}</h3>
                <div className="mono meta">{detailNode.base_url}</div>
              </div>
              <button
                type="button"
                className="btn ghost"
                onClick={() => setDetailId(null)}
              >
                关闭
              </button>
            </header>

            <section className="modal-section">
              <h4>策略详情</h4>
              {(() => {
                const r = riskLines(detail.strat);
                return (
                  <div className="legs-table-wrap">
                    <table className="legs-table">
                      <thead>
                        <tr>
                          <th>定仓</th>
                          {r.lossPct != null ? (
                            <th>{r.isOo ? "振幅" : "风险比例"}</th>
                          ) : null}
                          <th>出场</th>
                          {r.openRatio ? (
                            <th>{r.isOo ? "Call:Put" : "开仓比例"}</th>
                          ) : null}
                          <th>{r.isOo ? "期权杠杆" : "杠杆"}</th>
                          <th>状态</th>
                          <th>模式/交易所</th>
                          <th>轮次</th>
                          <th>组 ID</th>
                          {!r.isOo ? <th>保证金</th> : null}
                          <th>{r.isOo ? "数量 Call/Put" : "名义永续/期权"}</th>
                          <th>出场目标</th>
                          <th>指数</th>
                          <th>合约对</th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr>
                          <td>{r.sizing}</td>
                          {r.lossPct != null ? <td>{r.lossPct}</td> : null}
                          <td>{r.exit}</td>
                          {r.openRatio ? <td>{r.openRatio}</td> : null}
                          <td title={r.leverageTitle}>{r.leverage}</td>
                          <td>
                            {detailRunning ? "运行中" : "已停"} · {detail.phase}
                          </td>
                          <td>
                            <ModeLabelView
                              mode={detail.mode}
                              strat={detail.strat}
                            />{" "}
                            / {detail.exchange}
                          </td>
                          <td>{detail.rounds ?? "—"}</td>
                          <td className="mono">
                            {String(
                              detail.strat.group_id ||
                                detail.position.group_id ||
                                "—",
                            )}
                          </td>
                          {!r.isOo ? (
                            <td>
                              {String(detail.strat.perp_margin_mode || "—")}
                            </td>
                          ) : null}
                          <td>
                            {r.isOo
                              ? `${fmt(detail.strat.option_qty_eth, 2)} / ${fmt(
                                  detail.strat.oo_put_qty_eth ??
                                    detail.strat.option_qty_eth,
                                  2,
                                )} ETH`
                              : `${fmt(detail.strat.perp_qty_eth, 4)} / ${fmt(
                                  detail.strat.option_qty_eth,
                                  4,
                                )} ETH`}
                          </td>
                          <td>{fmt(detail.strat.exit_target_usdt, 2)} USDT</td>
                          <td className="mono">
                            {fmtExPx("perp", detail.index_px)}
                          </td>
                          <td className="mono">
                            {detail.pair
                              ? `${detail.pair.expiry_ymd || "?"} @ ${
                                  detail.pair.strike != null
                                    ? Math.round(Number(detail.pair.strike))
                                    : "?"
                                }`
                              : "—"}
                          </td>
                        </tr>
                      </tbody>
                    </table>
                  </div>
                );
              })()}
              {detail.strat.last_error ? (
                <p className="err soft" style={{ marginTop: 8 }}>
                  最近错误：{String(detail.strat.last_error)}
                </p>
              ) : null}
            </section>

            <section className="modal-section">
              <h4>整体统计</h4>
              {detailStatsLoading ? (
                <p className="meta">正在从策略机拉取统计…</p>
              ) : null}
              {detailStatsErr ? (
                <p className="err soft">{detailStatsErr}</p>
              ) : null}
              {detailStats ? (
                <div className="legs-table-wrap">
                  <table className="legs-table">
                    <thead>
                      <tr>
                        <th>组数/胜率</th>
                        <th>总盈亏</th>
                        <th>永续手续费</th>
                        <th>期权手续费</th>
                        <th>手续费合计</th>
                        {(detailStats.show_slip ??
                        detailStats.mode !== "LIVE") ? (
                          <th>滑点合计</th>
                        ) : null}
                        <th>平仓原因</th>
                      </tr>
                    </thead>
                    <tbody>
                      <tr>
                        <td className="mono">
                          {detailStats.groups} /{" "}
                          {(detailStats.win_rate * 100).toFixed(1)}%
                        </td>
                        <td className={pnlClass(detailStats.total_pnl)}>
                          {fmt(detailStats.total_pnl, 2)} USDT
                        </td>
                        <td className="mono">
                          {fmt(detailStats.fees_perp ?? 0, 4)}
                        </td>
                        <td className="mono">
                          {fmt(detailStats.fees_option ?? 0, 4)}
                        </td>
                        <td className="mono">
                          {fmt(detailStats.total_fees, 4)}
                        </td>
                        {(detailStats.show_slip ??
                        detailStats.mode !== "LIVE") ? (
                          <td className="mono">
                            {fmt(detailStats.total_slip ?? 0, 4)}
                          </td>
                        ) : null}
                        <td className="mono">
                          {Object.keys(detailStats.close_reasons || {})
                            .length === 0
                            ? "—"
                            : Object.entries(detailStats.close_reasons)
                                .sort((a, b) => b[1] - a[1])
                                .map(([k, n]) => `${closeReasonZh(k)}×${n}`)
                                .join(" · ")}
                        </td>
                      </tr>
                    </tbody>
                  </table>
                </div>
              ) : !detailStatsLoading && !detailStatsErr ? (
                <p className="meta">暂无统计</p>
              ) : null}
            </section>

            <section className="modal-section">
              <h4>按组盈亏</h4>
              {detailStatsLoading ? (
                <p className="meta">加载中…</p>
              ) : equityNewestFirst.length ? (
                <>
                  <div className="legs-table-wrap">
                    <table className="legs-table">
                      <thead>
                        <tr>
                          <th>组</th>
                          <th>盈亏</th>
                        </tr>
                      </thead>
                      <tbody>
                        {equityPageRows.map((x) => (
                          <tr key={x.group_id}>
                            <td className="mono">{x.group_id}</td>
                            <td className={pnlClass(x.realized_pnl)}>
                              {fmt(x.realized_pnl, 2)}
                            </td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                  </div>
                  <div className="pager">
                    <span className="pager-meta">
                      第 {equityPageSafe + 1}/{equityPageCount} 页 · 每页{" "}
                      {EQUITY_PAGE_SIZE} 行 · 共 {equityNewestFirst.length} 组
                    </span>
                    <div className="pager-actions">
                      <button
                        type="button"
                        className="btn ghost"
                        disabled={equityPageSafe <= 0}
                        onClick={() =>
                          setEquityPage(Math.max(0, equityPageSafe - 1))
                        }
                      >
                        上一页
                      </button>
                      <button
                        type="button"
                        className="btn ghost"
                        disabled={equityPageSafe >= equityPageCount - 1}
                        onClick={() =>
                          setEquityPage(
                            Math.min(equityPageCount - 1, equityPageSafe + 1),
                          )
                        }
                      >
                        下一页
                      </button>
                    </div>
                  </div>
                </>
              ) : (
                <p className="meta">暂无已平仓组</p>
              )}
            </section>

            <section className="modal-section">
              <h4>持仓</h4>
              {(() => {
                const pos = detail.position;
                const isOo =
                  String(pos.hedge_mode || detail.strat.hedge_mode || "")
                    .toLowerCase() === "option_option" ||
                  detailLegs.some(
                    (lg) =>
                      String(lg.side || "").toLowerCase() === "put" &&
                      String(lg.kind || "") === "option",
                  );
                const prem1 = Number(pos.initial_premium);
                const prem2 = Number(pos.initial_premium2);
                const premTotal =
                  (Number.isFinite(prem1) ? prem1 : 0) +
                  (Number.isFinite(prem2) ? prem2 : 0);
                const strikeLabel = isOo
                  ? `C@${
                      pos.strike != null ? Math.round(Number(pos.strike)) : "—"
                    } / P@${
                      pos.strike2 != null
                        ? Math.round(Number(pos.strike2))
                        : "—"
                    }`
                  : pos.strike != null
                    ? String(Math.round(Number(pos.strike)))
                    : "—";
                return (
                  <p className="meta">
                    状态：{String(pos.status || "flat")}
                    {isOo ? (
                      <>
                        {" · "}
                        <span className="mode-hedge-oo">期期</span>
                      </>
                    ) : null}
                    {pos.net_pnl != null ? (
                      <>
                        {" · 净浮盈 "}
                        <span className={pnlClass(pos.net_pnl)}>
                          {fmt(pos.net_pnl, 2)} USDT
                        </span>
                      </>
                    ) : null}
                    {pos.perp_margin != null && !isOo ? (
                      <>
                        {" · 保证金 "}
                        <span className="mono">
                          {fmt(pos.perp_margin, 2)} U
                        </span>
                      </>
                    ) : null}
                    {premTotal > 0 || pos.initial_premium != null ? (
                      <>
                        {" · 权利金 "}
                        <span className="mono">
                          {isOo && Number.isFinite(prem2)
                            ? `${fmt(prem1, 2)}+${fmt(prem2, 2)}=${fmt(premTotal, 2)}`
                            : fmt(pos.initial_premium, 2)}{" "}
                          U
                        </span>
                      </>
                    ) : null}
                    {pos.expiry_ymd
                      ? ` · 到期 ${String(pos.expiry_ymd)} ${strikeLabel}`
                      : ""}
                  </p>
                );
              })()}
              {detailLegs.length ? (
                <div className="legs-table-wrap">
                  <table className="legs-table">
                    <thead>
                      <tr>
                        <th>腿</th>
                        <th>方向</th>
                        <th>合约</th>
                        <th>数量</th>
                        <th>均价</th>
                        <th>标记</th>
                        <th>保证金</th>
                        <th>权利金</th>
                        <th>浮盈</th>
                      </tr>
                    </thead>
                    <tbody>
                      {detailLegs.map((lg, i) => {
                        const kind = String(lg.kind || "");
                        const side = String(lg.side || "").toLowerCase();
                        const legLabel =
                          kind === "perp"
                            ? "永续"
                            : side === "call"
                              ? "Call"
                              : side === "put"
                                ? "Put"
                                : kind || "—";
                        return (
                          <tr key={`${lg.inst_id || i}`}>
                            <td>
                              {side === "put" ? (
                                <span className="mode-hedge-oo">{legLabel}</span>
                              ) : (
                                legLabel
                              )}
                            </td>
                            <td>{String(lg.side || "—")}</td>
                            <td className="mono">
                              {String(lg.inst_id || "—")}
                            </td>
                            <td className="mono">{fmt(lg.qty, 2)}</td>
                            <td className="mono">
                              {fmtExPx(lg.kind, lg.avg_px)}
                            </td>
                            <td className="mono">
                              {fmtExPx(lg.kind, lg.mark_px)}
                            </td>
                            <td className="mono">
                              {kind === "perp"
                                ? fmt(
                                    lg.margin ?? detail.position.perp_margin,
                                    2,
                                  )
                                : "—"}
                            </td>
                            <td className="mono">
                              {kind === "option" || kind === "option2"
                                ? fmt(lg.premium, 2)
                                : "—"}
                            </td>
                            <td className={`mono ${pnlClass(lg.upl)}`}>
                              {fmt(lg.upl, 2)}
                            </td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                </div>
              ) : (
                <p className="meta">当前无持仓腿</p>
              )}
            </section>

            <div className="node-actions">
              <button
                type="button"
                className={`btn ${detailRunning ? "btn-running" : ""}`}
                disabled={
                  !!busy[detailNode.id] ||
                  !detailNode.token_configured ||
                  detailRunning
                }
                onClick={() => void act(detailNode.id, "start")}
              >
                {detailRunning ? "运行中" : "启动"}
              </button>
              <button
                type="button"
                className="btn ghost"
                disabled={!!busy[detailNode.id] || !detailNode.token_configured}
                onClick={() => void act(detailNode.id, "pause")}
              >
                停止
              </button>
              <button
                type="button"
                className="btn"
                disabled={!!busy[detailNode.id] || !detailNode.token_configured}
                onClick={() => void act(detailNode.id, "login")}
              >
                登录策略机
              </button>
            </div>
          </div>
        </div>
      ) : null}
    </div>
  );
}
