import { FormEvent, ReactNode, useEffect, useState } from "react";
import {
  changeCredentials,
  getUsername,
  setSession,
  apiFetch,
  StrategySettings,
  RuntimeSettings,
  NotifySettings,
  BackupStatus,
  downloadBackup,
  uploadRestoreBackup,
} from "../api/client";

type Tab = "strategy" | "runtime" | "funds" | "account" | "backup";
type StratSub =
  | "position"
  | "select"
  | "exit"
  | "pace";

function RulesFold({
  open,
  onToggle,
  children,
}: {
  open: boolean;
  onToggle: () => void;
  children: ReactNode;
}) {
  return (
    <div className={`settings-rules ${open ? "open" : ""}`}>
      <button
        type="button"
        className="settings-rules-toggle"
        aria-expanded={open}
        onClick={onToggle}
      >
        <span>规则说明</span>
        <span className="settings-rules-chevron" aria-hidden>
          {open ? "▾" : "▸"}
        </span>
      </button>
      {open ? <div className="settings-rules-body">{children}</div> : null}
    </div>
  );
}

export default function SettingsPage() {
  const [tab, setTab] = useState<Tab>("strategy");
  const [stratSub, setStratSub] = useState<StratSub>("position");
  const [stratRulesOpen, setStratRulesOpen] = useState(false);
  const [runtimeRulesOpen, setRuntimeRulesOpen] = useState(false);
  const [backupRulesOpen, setBackupRulesOpen] = useState(false);

  const [newUsername, setNewUsername] = useState(getUsername() || "admin");
  const [currentPassword, setCurrentPassword] = useState("");
  const [newPassword, setNewPassword] = useState("");
  const [confirmPassword, setConfirmPassword] = useState("");
  const [fleetConfigured, setFleetConfigured] = useState(false);
  const [fleetTokenInput, setFleetTokenInput] = useState("");
  const [fleetHint, setFleetHint] = useState("");
  const [err, setErr] = useState("");
  const [ok, setOk] = useState("");
  const [loading, setLoading] = useState(false);

  const [fee, setFee] = useState(0.0005);
  const [exitMode, setExitMode] = useState<"fixed_usdt" | "premium_multiple">(
    "fixed_usdt",
  );
  const [netTarget, setNetTarget] = useState(15);
  const [premMult, setPremMult] = useState(1);
  const [rest, setRest] = useState(300);
  const [orderInterval, setOrderInterval] = useState(1);
  const [skipWeekends, setSkipWeekends] = useState(true);
  const [oneExpiryPerDay, setOneExpiryPerDay] = useState(true);
  const [leverage, setLeverage] = useState(3);
  const [perpMarginMode, setPerpMarginMode] = useState<"cross" | "isolated">(
    "cross",
  );
  const [minHours, setMinHours] = useState(12);
  const [minOptLev, setMinOptLev] = useState(100);
  const [atmOffOn, setAtmOffOn] = useState(false);
  const [maxAtmOff, setMaxAtmOff] = useState(3);
  const [fixedDirOn, setFixedDirOn] = useState(false);
  const [fixedPerpSide, setFixedPerpSide] = useState<"long" | "short">("long");
  const [closeDevPct, setCloseDevPct] = useState(30);
  const [residualMinPremPct, setResidualMinPremPct] = useState(20);
  const [perpQty, setPerpQty] = useState(1);
  const [optQty, setOptQty] = useState(2);
  const [showManualTrade, setShowManualTrade] = useState(false);
  const [sizingMode, setSizingMode] = useState<"manual" | "risk_based">("manual");
  const [riskLeverageBasis, setRiskLeverageBasis] = useState<
    "actual" | "selection"
  >("selection");
  const [riskLossMode, setRiskLossMode] = useState<"percent" | "absolute">(
    "percent",
  );
  const [riskLossPct, setRiskLossPct] = useState(1);
  const [riskLossUsdt, setRiskLossUsdt] = useState(15);
  const [riskCapitalSource, setRiskCapitalSource] = useState<
    "trading_account" | "manual"
  >("trading_account");
  const [riskManualCapital, setRiskManualCapital] = useState(10000);
  const [riskPerpUnit, setRiskPerpUnit] = useState(1);
  const [riskOptUnit, setRiskOptUnit] = useState(2);
  const [riskExitUnit, setRiskExitUnit] = useState(15);
  const [riskPreview, setRiskPreview] = useState<Record<string, unknown> | null>(
    null,
  );
  const [initialEquity, setInitialEquity] = useState(10000);
  const [exchange, setExchange] = useState<"okx" | "binance">("okx");
  const [stratOk, setStratOk] = useState("");

  const [runtime, setRuntime] = useState<RuntimeSettings | null>(null);
  const [mode, setMode] = useState<"SIM" | "LIVE">("SIM");
  const [okxKey, setOkxKey] = useState("");
  const [okxSecret, setOkxSecret] = useState("");
  const [okxPass, setOkxPass] = useState("");
  const [bnKey, setBnKey] = useState("");
  const [bnSecret, setBnSecret] = useState("");
  const [runtimeOk, setRuntimeOk] = useState("");
  const [wecomEnabled, setWecomEnabled] = useState(false);
  const [wecomWebhook, setWecomWebhook] = useState("");
  const [wecomMachineName, setWecomMachineName] = useState("");
  const [wecomMeta, setWecomMeta] = useState<NotifySettings | null>(null);
  const [wecomOk, setWecomOk] = useState("");

  const [backup, setBackup] = useState<BackupStatus | null>(null);
  const [bakAuto, setBakAuto] = useState(true);
  const [bakInterval, setBakInterval] = useState(24);
  const [bakKeep, setBakKeep] = useState(14);
  const [bakOk, setBakOk] = useState("");
  const [bakBusy, setBakBusy] = useState("");
  const [restoreFile, setRestoreFile] = useState<File | null>(null);
  const [restorePhrase, setRestorePhrase] = useState("");

  const [fundsOk, setFundsOk] = useState("");
  const [fundsErr, setFundsErr] = useState("");
  const [convertDir, setConvertDir] = useState<"usdt_to_usdc" | "usdc_to_usdt">(
    "usdt_to_usdc",
  );
  const [convertAmt, setConvertAmt] = useState(100);
  const [xferCcy, setXferCcy] = useState<"USDT" | "USDC">("USDC");
  const [xferAmt, setXferAmt] = useState(50);
  const [xferFrom, setXferFrom] = useState("funding");
  const [xferTo, setXferTo] = useState("trading");
  const [usdcRate, setUsdcRate] = useState<number | null>(null);
  const [fundsRulesOpen, setFundsRulesOpen] = useState(false);

  function loadRuntime() {
    apiFetch<RuntimeSettings>("/api/settings/runtime")
      .then((r) => {
        setRuntime(r);
        setMode(r.mode === "LIVE" ? "LIVE" : "SIM");
      })
      .catch(() => undefined);
    apiFetch<NotifySettings>("/api/settings/notify")
      .then((n) => {
        setWecomMeta(n);
        setWecomEnabled(n.enabled === true);
        setWecomMachineName(n.machine_name || "");
      })
      .catch(() => undefined);
  }

  function loadBackup() {
    apiFetch<BackupStatus>("/api/backup/status")
      .then((s) => {
        setBackup(s);
        setBakAuto(s.auto_enabled !== false);
        setBakInterval(s.interval_hours ?? 24);
        setBakKeep(s.keep_count ?? 14);
      })
      .catch(() => undefined);
  }

  useEffect(() => {
    apiFetch<StrategySettings>("/api/settings/strategy")
      .then((s) => {
        setFee(s.fee_rate);
        setExitMode(s.exit_mode === "premium_multiple" ? "premium_multiple" : "fixed_usdt");
        setNetTarget(s.net_profit_target ?? 15);
        setPremMult(s.premium_exit_multiple ?? 1);
        setRest(s.rest_seconds);
        setOrderInterval(s.live_order_interval_sec ?? 1);
        setSkipWeekends(s.skip_weekends !== false);
        setOneExpiryPerDay(s.one_expiry_per_day !== false);
        setLeverage(s.leverage ?? 3);
        setPerpMarginMode(
          s.perp_margin_mode === "isolated" ? "isolated" : "cross",
        );
        setMinHours(s.min_option_hours ?? 12);
        setMinOptLev(s.min_option_leverage ?? 100);
        setAtmOffOn(s.atm_open_offset_enabled === true);
        setMaxAtmOff(s.max_atm_open_offset ?? 3);
        setFixedDirOn(s.fixed_direction_enabled === true);
        setFixedPerpSide(s.fixed_perp_side === "short" ? "short" : "long");
        setCloseDevPct(s.close_bid_mark_max_pct ?? 30);
        setResidualMinPremPct(s.residual_min_premium_pct ?? 20);
        setPerpQty(s.perp_qty_eth ?? 1);
        setOptQty(s.option_qty_eth ?? 2);
        setShowManualTrade(s.show_manual_trade_buttons === true);
        setSizingMode(s.sizing_mode === "risk_based" ? "risk_based" : "manual");
        setRiskLeverageBasis(
          s.risk_leverage_basis === "actual" ? "actual" : "selection",
        );
        setRiskLossMode(s.risk_loss_mode === "absolute" ? "absolute" : "percent");
        setRiskLossPct(s.risk_loss_pct ?? 1);
        setRiskLossUsdt(s.risk_loss_usdt ?? 15);
        setRiskCapitalSource(
          s.risk_capital_source === "manual" ? "manual" : "trading_account",
        );
        setRiskManualCapital(s.risk_manual_capital_usdt ?? 10000);
        setRiskPerpUnit(s.risk_perp_unit ?? 1);
        setRiskOptUnit(s.risk_option_unit ?? 2);
        setRiskExitUnit(s.risk_exit_unit ?? 15);
        setRiskPreview(
          s.risk_sizing_preview && typeof s.risk_sizing_preview === "object"
            ? s.risk_sizing_preview
            : null,
        );
        setInitialEquity(s.initial_equity ?? 10000);
        setExchange(s.exchange === "binance" ? "binance" : "okx");
      })
      .catch(() => undefined);
    loadRuntime();
  }, []);

  async function onSaveBackupSettings(e: FormEvent) {
    e.preventDefault();
    setErr("");
    setBakOk("");
    try {
      const s = await apiFetch<BackupStatus>("/api/backup/settings", {
        method: "PUT",
        body: JSON.stringify({
          auto_enabled: bakAuto,
          interval_hours: bakInterval,
          keep_count: bakKeep,
        }),
      });
      setBackup(s);
      setBakOk("备份设置已保存");
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    }
  }

  async function onBackupNow() {
    setErr("");
    setBakOk("");
    setBakBusy("备份中");
    try {
      const r = await apiFetch<{ name: string; backup_dir: string }>(
        "/api/backup/now",
        { method: "POST", body: "{}" },
      );
      setBakOk(`已备份 ${r.name} → ${r.backup_dir}`);
      loadBackup();
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    } finally {
      setBakBusy("");
    }
  }

  async function onDownload(name: string) {
    setErr("");
    setBakBusy("下载中");
    try {
      await downloadBackup(name);
      setBakOk(`已下载 ${name}`);
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    } finally {
      setBakBusy("");
    }
  }

  async function onRestore() {
    setErr("");
    setBakOk("");
    if (!restoreFile) {
      setErr("请先选择备份 zip");
      return;
    }
    if (restorePhrase.trim() !== "RESTORE") {
      setErr("请输入确认串 RESTORE（大写）");
      return;
    }
    if (
      !window.confirm(
        "将用备份覆盖本机数据库与 .env，服务会自动重启。新服务器迁移请确认包来自可信来源。继续？",
      )
    ) {
      return;
    }
    setBakBusy("恢复中");
    try {
      const r = await uploadRestoreBackup(restoreFile, restorePhrase.trim());
      setBakOk(r.detail || "恢复成功，请等待重启后刷新");
      window.setTimeout(() => {
        window.location.reload();
      }, 2500);
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    } finally {
      setBakBusy("");
    }
  }

  async function onSaveCreds(e: FormEvent) {
    e.preventDefault();
    setErr("");
    setOk("");
    if (newPassword !== confirmPassword) {
      setErr("两次输入的新密码不一致");
      return;
    }
    if (newPassword.length < 4) {
      setErr("新密码至少 4 位");
      return;
    }
    setLoading(true);
    try {
      const res = await changeCredentials({
        current_password: currentPassword,
        new_username: newUsername.trim(),
        new_password: newPassword,
      });
      setSession(res.token, res.username, res.expires_in);
      setCurrentPassword("");
      setNewPassword("");
      setConfirmPassword("");
      setOk("用户名/密码已更新");
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    } finally {
      setLoading(false);
    }
  }

  async function onSaveStrategy(e: FormEvent) {
    e.preventDefault();
    setStratOk("");
    setErr("");
    const isLive = mode === "LIVE";
    try {
      const body: Record<string, unknown> = {
        fee_rate: fee,
        exit_mode: sizingMode === "risk_based" ? "fixed_usdt" : exitMode,
        premium_exit_multiple: premMult,
        rest_seconds: rest,
        live_order_interval_sec: orderInterval,
        skip_weekends: skipWeekends,
        one_expiry_per_day: oneExpiryPerDay,
        leverage,
        perp_margin_mode: perpMarginMode,
        min_option_hours: minHours,
        min_option_leverage: minOptLev,
        atm_open_offset_enabled: atmOffOn,
        max_atm_open_offset: maxAtmOff,
        fixed_direction_enabled: fixedDirOn,
        fixed_perp_side: fixedPerpSide,
        close_bid_mark_max_pct: closeDevPct,
        residual_min_premium_pct: residualMinPremPct,
        show_manual_trade_buttons: showManualTrade,
        sizing_mode: sizingMode,
        risk_leverage_basis: riskLeverageBasis,
        risk_loss_mode: riskLossMode,
        risk_loss_pct: riskLossPct,
        risk_loss_usdt: riskLossUsdt,
        risk_capital_source: riskCapitalSource,
        risk_manual_capital_usdt: riskManualCapital,
        risk_perp_unit: riskPerpUnit,
        risk_option_unit: riskOptUnit,
        risk_exit_unit: riskExitUnit,
        exchange,
      };
      // 以损定仓不提交手填名义/出场，避免禁用输入框脏值导致 422
      if (sizingMode === "manual") {
        body.net_profit_target = netTarget;
        body.perp_qty_eth = perpQty;
        body.option_qty_eth = optQty;
      }
      // LIVE 不改模拟资金，避免误重置本地账本
      if (!isLive) {
        body.initial_equity = initialEquity;
      }
      const saved = await apiFetch<StrategySettings>("/api/settings/strategy", {
        method: "PUT",
        body: JSON.stringify(body),
      });
      if (saved?.risk_sizing_preview) {
        setRiskPreview(saved.risk_sizing_preview);
      }
      if (saved?.perp_qty_eth != null) setPerpQty(saved.perp_qty_eth);
      if (saved?.option_qty_eth != null) setOptQty(saved.option_qty_eth);
      if (saved?.net_profit_target != null) setNetTarget(saved.net_profit_target);
      if (saved?.exit_mode) {
        setExitMode(
          saved.exit_mode === "premium_multiple" ? "premium_multiple" : "fixed_usdt",
        );
      }
      setStratOk(
        isLive
          ? "策略参数已保存（实盘模式不改模拟资金）"
          : "策略参数已保存（切换交易所/改模拟资金需无持仓；切换后会重连行情）",
      );
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    }
  }

  async function onSaveRuntime(e: FormEvent) {
    e.preventDefault();
    setErr("");
    setRuntimeOk("");
    const goingLive = mode === "LIVE" && runtime?.mode !== "LIVE";
    if (goingLive) {
      const typed = window.prompt('切换到 LIVE 实盘：请输入 LIVE 确认（将真实下单）');
      if (typed !== "LIVE") {
        setErr("已取消：须输入 LIVE 才能切换到实盘");
        return;
      }
    }
    setLoading(true);
    try {
      const body: Record<string, unknown> = {
        mode,
        confirm_live: goingLive,
        confirm_live_phrase: goingLive ? "LIVE" : undefined,
      };
      if (okxKey.trim()) body.okx_api_key = okxKey.trim();
      if (okxSecret.trim()) body.okx_api_secret = okxSecret.trim();
      if (okxPass.trim()) body.okx_api_passphrase = okxPass.trim();
      if (bnKey.trim()) body.binance_api_key = bnKey.trim();
      if (bnSecret.trim()) body.binance_api_secret = bnSecret.trim();
      const r = await apiFetch<RuntimeSettings>("/api/settings/runtime", {
        method: "PUT",
        body: JSON.stringify(body),
      });
      setRuntime(r);
      setMode(r.mode === "LIVE" ? "LIVE" : "SIM");
      setOkxKey("");
      setOkxSecret("");
      setOkxPass("");
      setBnKey("");
      setBnSecret("");
      setRuntimeOk(
        r.mode === "LIVE"
          ? r.live_ready
            ? "已切换 LIVE，密钥已写入 .env"
            : `已切 LIVE，但未就绪：${r.live_ready_reason}`
          : "已切换 SIM，配置已写入 .env",
      );
      window.dispatchEvent(new Event("funds-refresh"));
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    } finally {
      setLoading(false);
    }
  }

  async function onSaveWecom() {
    setErr("");
    setWecomOk("");
    try {
      const body: Record<string, unknown> = {
        enabled: wecomEnabled,
        machine_name: wecomMachineName.trim(),
      };
      if (wecomWebhook.trim()) body.webhook_url = wecomWebhook.trim();
      const n = await apiFetch<NotifySettings>("/api/settings/notify", {
        method: "PUT",
        body: JSON.stringify(body),
      });
      setWecomMeta(n);
      setWecomEnabled(n.enabled === true);
      setWecomMachineName(n.machine_name || "");
      setWecomWebhook("");
      setWecomOk(
        n.enabled
          ? n.webhook_configured
            ? "企业微信通知已保存并开启"
            : "已开启，但尚未配置 Webhook"
          : "企业微信通知已关闭",
      );
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    }
  }

  async function onTestWecom() {
    setErr("");
    setWecomOk("");
    try {
      const n = await apiFetch<NotifySettings & { detail?: string }>(
        "/api/settings/notify/test",
        { method: "POST", body: "{}" },
      );
      setWecomMeta(n);
      setWecomOk(n.detail || "测试消息已发送，请查看企业微信群");
      if (n.machine_name != null) setWecomMachineName(n.machine_name || "");
    } catch (ex) {
      setErr(ex instanceof Error ? ex.message : String(ex));
    }
  }

  return (
    <div className="settings-page">
      <h2 style={{ marginTop: 0 }}>系统设置</h2>
      <div className="tabs">
        <button
          type="button"
          className={tab === "strategy" ? "tab active" : "tab"}
          onClick={() => {
            setTab("strategy");
            loadRuntime();
          }}
        >
          策略设置
        </button>
        <button
          type="button"
          className={tab === "runtime" ? "tab active" : "tab"}
          onClick={() => {
            setTab("runtime");
            loadRuntime();
          }}
        >
          运行模式
        </button>
        <button
          type="button"
          className={tab === "funds" ? "tab active" : "tab"}
          onClick={() => {
            setTab("funds");
            setFundsErr("");
            setFundsOk("");
            apiFetch<{ usdc_usdt_rate?: number }>("/api/funds/summary")
              .then((r) => setUsdcRate(r.usdc_usdt_rate ?? null))
              .catch(() => undefined);
          }}
        >
          资金
        </button>
        <button
          type="button"
          className={tab === "account" ? "tab active" : "tab"}
          onClick={() => {
            setTab("account");
            apiFetch<{ configured: boolean; hint?: string }>("/api/fleet/meta")
              .then((r) => {
                setFleetConfigured(!!r.configured);
                setFleetHint(r.hint || "");
              })
              .catch(() => undefined);
          }}
        >
          登录账户
        </button>
        <button
          type="button"
          className={tab === "backup" ? "tab active" : "tab"}
          onClick={() => {
            setTab("backup");
            loadBackup();
          }}
        >
          备份恢复
        </button>
      </div>

      {tab === "strategy" ? (
        <div className="card settings-card">
          {stratOk ? <div className="settings-ok">{stratOk}</div> : null}
          {err && tab === "strategy" ? <div className="err">{err}</div> : null}

          <div className="settings-subtabs" role="tablist" aria-label="策略子分类">
            {(
              [
                ["position", "仓位"],
                ["select", "选约"],
                ["exit", "出场"],
                ["pace", "节奏"],
              ] as const
            ).map(([id, label]) => (
              <button
                key={id}
                type="button"
                role="tab"
                aria-selected={stratSub === id}
                className={
                  stratSub === id ? "settings-subtab active" : "settings-subtab"
                }
                onClick={() => setStratSub(id)}
              >
                {label}
              </button>
            ))}
          </div>

          <form onSubmit={onSaveStrategy}>
            {stratSub === "position" ? (
              <section className="settings-section">
                <h3>交易所与仓位</h3>
                <div className="settings-fields">
                  <div className="field">
                    <label htmlFor="exch">交易所</label>
                    <select
                      id="exch"
                      className="mono"
                      value={exchange}
                      onChange={(e) =>
                        setExchange(
                          e.target.value === "binance" ? "binance" : "okx",
                        )
                      }
                    >
                      <option value="okx">OKX（USDC 期权族）</option>
                      <option value="binance">
                        币安（USDT 永续 + 欧洲期权）
                      </option>
                    </select>
                  </div>
                  {mode !== "LIVE" ? (
                    <div className="field">
                      <label htmlFor="equity">模拟资金（USDT）</label>
                      <input
                        id="equity"
                        className="mono"
                        type="number"
                        step="100"
                        min="1000"
                        value={initialEquity}
                        onChange={(e) =>
                          setInitialEquity(Number(e.target.value))
                        }
                      />
                    </div>
                  ) : null}
                  <div className="field">
                    <label htmlFor="lev">永续杠杆倍数</label>
                    <input
                      id="lev"
                      className="mono"
                      type="number"
                      step="1"
                      min="1"
                      value={leverage}
                      onChange={(e) => setLeverage(Number(e.target.value))}
                    />
                  </div>
                  <div className="field">
                    <label htmlFor="perpMm">永续保证金模式</label>
                    <select
                      id="perpMm"
                      className="mono"
                      value={perpMarginMode}
                      onChange={(e) =>
                        setPerpMarginMode(
                          e.target.value === "isolated" ? "isolated" : "cross",
                        )
                      }
                    >
                      <option value="cross">全仓（默认）</option>
                      <option value="isolated">逐仓</option>
                    </select>
                  </div>
                  <div className="field">
                    <label htmlFor="sizingMode">定仓模式</label>
                    <select
                      id="sizingMode"
                      className="mono"
                      value={sizingMode}
                      onChange={(e) =>
                        setSizingMode(
                          e.target.value === "risk_based"
                            ? "risk_based"
                            : "manual",
                        )
                      }
                    >
                      <option value="manual">手动仓位（填 ETH 名义）</option>
                      <option value="risk_based">以损定仓（按亏损预算反推）</option>
                    </select>
                  </div>
                  {sizingMode === "risk_based" ? (
                    <>
                      <div className="field">
                        <label htmlFor="riskLevBasis">节奏杠杆（定仓口径）</label>
                        <select
                          id="riskLevBasis"
                          className="mono"
                          value={riskLeverageBasis}
                          onChange={(e) =>
                            setRiskLeverageBasis(
                              e.target.value === "actual"
                                ? "actual"
                                : "selection",
                            )
                          }
                        >
                          <option value="selection">
                            选约杠杆（推荐控节奏）
                          </option>
                          <option value="actual">实际杠杆（盘口卖一）</option>
                        </select>
                        <p className="hint" style={{ margin: "0.35rem 0 0" }}>
                          选约杠杆：按「指数÷期权杠杆≥」估权利金算 k，权利金再便宜也不放大仓位。
                          实际杠杆：按盘口卖一定仓，便宜时 k 会变大。
                        </p>
                      </div>
                      <div className="field">
                        <label htmlFor="riskLossMode">亏损约束</label>
                        <select
                          id="riskLossMode"
                          className="mono"
                          value={riskLossMode}
                          onChange={(e) =>
                            setRiskLossMode(
                              e.target.value === "absolute"
                                ? "absolute"
                                : "percent",
                            )
                          }
                        >
                          <option value="percent">亏损幅度（占本金 %）</option>
                          <option value="absolute">亏损值（USDT）</option>
                        </select>
                      </div>
                      {riskLossMode === "percent" ? (
                        <>
                          <div className="field">
                            <label htmlFor="riskCapSrc">本金来源</label>
                            <select
                              id="riskCapSrc"
                              className="mono"
                              value={riskCapitalSource}
                              onChange={(e) =>
                                setRiskCapitalSource(
                                  e.target.value === "manual"
                                    ? "manual"
                                    : "trading_account",
                                )
                              }
                            >
                              <option value="trading_account">
                                交易账户总资金
                              </option>
                              <option value="manual">单独设置本金</option>
                            </select>
                          </div>
                          {riskCapitalSource === "manual" ? (
                            <div className="field">
                              <label htmlFor="riskManCap">单独本金（USDT）</label>
                              <input
                                id="riskManCap"
                                className="mono"
                                type="number"
                                step="100"
                                min="1"
                                value={riskManualCapital}
                                onChange={(e) =>
                                  setRiskManualCapital(Number(e.target.value))
                                }
                              />
                            </div>
                          ) : null}
                          <div className="field">
                            <label htmlFor="riskPct">亏损幅度（%）</label>
                            <input
                              id="riskPct"
                              className="mono"
                              type="number"
                              step="0.01"
                              min="0.01"
                              value={riskLossPct}
                              onChange={(e) =>
                                setRiskLossPct(Number(e.target.value))
                              }
                            />
                          </div>
                        </>
                      ) : (
                        <div className="field">
                          <label htmlFor="riskUsdt">亏损值（USDT）</label>
                          <input
                            id="riskUsdt"
                            className="mono"
                            type="number"
                            step="0.1"
                            min="0.1"
                            value={riskLossUsdt}
                            onChange={(e) =>
                              setRiskLossUsdt(Number(e.target.value))
                            }
                          />
                        </div>
                      )}
                      <div className="field" style={{ gridColumn: "1 / -1" }}>
                        <label>以损定仓预览（保存后刷新；开仓前再算一次）</label>
                        <div className="mono" style={{ fontSize: 12, opacity: 0.9 }}>
                          {(() => {
                            if (riskPreview == null) return "—";
                            if (riskPreview.ok === false) {
                              return String(riskPreview.detail || "预览不可用");
                            }
                            const k = Number(riskPreview.k);
                            const bud = Number(riskPreview.budget);
                            const mx = Number(riskPreview.max_loss);
                            const pk = Number.isFinite(k) ? k : null;
                            const perp =
                              pk != null
                                ? Number((riskPerpUnit * pk).toFixed(4))
                                : riskPreview.perp_qty_eth;
                            const opt =
                              pk != null
                                ? Number((riskOptUnit * pk).toFixed(4))
                                : riskPreview.option_qty_eth;
                            const exit =
                              pk != null
                                ? Number((riskExitUnit * pk).toFixed(2))
                                : riskPreview.net_profit_target;
                            const budS = Number.isFinite(bud)
                              ? bud.toFixed(2)
                              : "—";
                            const mxS = Number.isFinite(mx) ? mx.toFixed(2) : "—";
                            const basis =
                              riskPreview.leverage_basis === "actual"
                                ? "实际杠杆"
                                : riskPreview.leverage_basis === "selection"
                                  ? "选约杠杆"
                                  : "";
                            const basisS = basis ? ` · ${basis}` : "";
                            return `k=${pk ?? "—"} · 预算=${budS}U · 估亏=${mxS}U · 永续=${perp ?? "—"} · 期权=${opt ?? "—"} · 出场=${exit ?? "—"}${basisS}`;
                          })()}
                        </div>
                      </div>
                      <div className="field">
                        <label htmlFor="riskPerpU">永续比例（k=1）</label>
                        <input
                          id="riskPerpU"
                          className="mono"
                          type="number"
                          step="0.01"
                          min="0.01"
                          value={riskPerpUnit}
                          onChange={(e) => {
                            const v = Number(e.target.value);
                            setRiskPerpUnit(v);
                            const k = Number(riskPreview?.k);
                            if (Number.isFinite(k) && k > 0) {
                              setPerpQty(Number((v * k).toFixed(4)));
                            }
                          }}
                        />
                      </div>
                      <div className="field">
                        <label htmlFor="riskOptU">期权比例（k=1）</label>
                        <input
                          id="riskOptU"
                          className="mono"
                          type="number"
                          step="0.01"
                          min="0.01"
                          value={riskOptUnit}
                          onChange={(e) => {
                            const v = Number(e.target.value);
                            setRiskOptUnit(v);
                            const k = Number(riskPreview?.k);
                            if (Number.isFinite(k) && k > 0) {
                              setOptQty(Number((v * k).toFixed(4)));
                            }
                          }}
                        />
                      </div>
                      <div className="field">
                        <label htmlFor="riskExitU">
                          出场资金（k=1；改后按比例联动）
                        </label>
                        <input
                          id="riskExitU"
                          className="mono"
                          type="number"
                          step="0.1"
                          min="0.1"
                          value={riskExitUnit}
                          onChange={(e) => {
                            const v = Number(e.target.value);
                            setRiskExitUnit(v);
                            const k = Number(riskPreview?.k);
                            if (Number.isFinite(k) && k > 0) {
                              setNetTarget(Number((v * k).toFixed(2)));
                              setPerpQty(Number((riskPerpUnit * k).toFixed(4)));
                              setOptQty(Number((riskOptUnit * k).toFixed(4)));
                            }
                          }}
                        />
                      </div>
                      <div className="field">
                        <label htmlFor="riskExitAbs">
                          当前出场（= 基数×k，可改后反推基数）
                        </label>
                        <input
                          id="riskExitAbs"
                          className="mono"
                          type="number"
                          step="0.01"
                          min="0.1"
                          value={
                            (() => {
                              const k = Number(riskPreview?.k);
                              if (Number.isFinite(k) && k > 0) {
                                return Number((riskExitUnit * k).toFixed(2));
                              }
                              return Number(riskExitUnit.toFixed(2));
                            })()
                          }
                          onChange={(e) => {
                            const abs = Number(e.target.value);
                            const k = Number(riskPreview?.k);
                            if (Number.isFinite(k) && k > 0) {
                              const unit = Number((abs / k).toFixed(4));
                              setRiskExitUnit(unit);
                              setNetTarget(Number(abs.toFixed(2)));
                              setPerpQty(Number((riskPerpUnit * k).toFixed(4)));
                              setOptQty(Number((riskOptUnit * k).toFixed(4)));
                            } else {
                              setRiskExitUnit(abs);
                              setNetTarget(abs);
                            }
                          }}
                        />
                      </div>
                    </>
                  ) : null}
                  <div className="field">
                    <label htmlFor="perp">
                      永续 ETH 数量
                      {sizingMode === "risk_based" ? "（随比例×k）" : ""}
                    </label>
                    <input
                      id="perp"
                      className="mono"
                      type="number"
                      step="0.01"
                      min="0.01"
                      disabled={sizingMode === "risk_based"}
                      value={
                        sizingMode === "risk_based" &&
                        Number(riskPreview?.k) > 0
                          ? Number(
                              (riskPerpUnit * Number(riskPreview?.k)).toFixed(4),
                            )
                          : perpQty
                      }
                      onChange={(e) => setPerpQty(Number(e.target.value))}
                    />
                  </div>
                  <div className="field">
                    <label htmlFor="opt">
                      期权 ETH 数量
                      {sizingMode === "risk_based" ? "（随比例×k）" : ""}
                    </label>
                    <input
                      id="opt"
                      className="mono"
                      type="number"
                      step="0.01"
                      min="0.01"
                      disabled={sizingMode === "risk_based"}
                      value={
                        sizingMode === "risk_based" &&
                        Number(riskPreview?.k) > 0
                          ? Number(
                              (riskOptUnit * Number(riskPreview?.k)).toFixed(4),
                            )
                          : optQty
                      }
                      onChange={(e) => setOptQty(Number(e.target.value))}
                    />
                  </div>
                </div>
              </section>
            ) : null}

            {stratSub === "select" ? (
              <section className="settings-section">
                <h3>选约条件</h3>
                <div className="settings-fields">
                  <div className="field">
                    <label htmlFor="hours">期权最小剩余小时</label>
                    <input
                      id="hours"
                      className="mono"
                      type="number"
                      step="1"
                      min="1"
                      value={minHours}
                      onChange={(e) => setMinHours(Number(e.target.value))}
                    />
                  </div>
                  <div className="field">
                    <label htmlFor="olev">
                      期权杠杆下限（现价÷卖一权利金）
                    </label>
                    <input
                      id="olev"
                      className="mono"
                      type="number"
                      step="1"
                      min="1"
                      value={minOptLev}
                      onChange={(e) => setMinOptLev(Number(e.target.value))}
                    />
                  </div>
                  <div className="field">
                    <label htmlFor="fixedDirOn">固定方向</label>
                    <select
                      id="fixedDirOn"
                      className="mono"
                      value={fixedDirOn ? "1" : "0"}
                      onChange={(e) => setFixedDirOn(e.target.value === "1")}
                    >
                      <option value="0">关闭（默认，按现有规则）</option>
                      <option value="1">开启</option>
                    </select>
                  </div>
                  <div className="field">
                    <label htmlFor="fixedPerp">固定永续方向</label>
                    <select
                      id="fixedPerp"
                      className="mono"
                      disabled={!fixedDirOn}
                      value={fixedPerpSide}
                      onChange={(e) =>
                        setFixedPerpSide(
                          e.target.value === "short" ? "short" : "long",
                        )
                      }
                    >
                      <option value="long">永续多 · 买 Put（实值/平值）</option>
                      <option value="short">永续空 · 买 Call（实值/平值）</option>
                    </select>
                  </div>
                  <div className="field">
                    <label htmlFor="atmoffOn">开仓 ATM 偏差限制</label>
                    <select
                      id="atmoffOn"
                      className="mono"
                      disabled={fixedDirOn}
                      value={atmOffOn ? "1" : "0"}
                      onChange={(e) => setAtmOffOn(e.target.value === "1")}
                    >
                      <option value="0">关闭（默认）</option>
                      <option value="1">开启</option>
                    </select>
                  </div>
                  <div className="field">
                    <label htmlFor="atmoff">开仓 ATM 最大偏差（点）</label>
                    <input
                      id="atmoff"
                      className="mono"
                      type="number"
                      step="0.5"
                      min="0"
                      disabled={!atmOffOn || fixedDirOn}
                      value={maxAtmOff}
                      onChange={(e) => setMaxAtmOff(Number(e.target.value))}
                    />
                  </div>
                </div>
              </section>
            ) : null}

            {stratSub === "exit" ? (
              <section className="settings-section">
                <h3>出场与平仓</h3>
                <div className="settings-fields">
                  <div className="field">
                    <label htmlFor="exitMode">出场规则</label>
                    <select
                      id="exitMode"
                      className="mono"
                      disabled={sizingMode === "risk_based"}
                      value={
                        sizingMode === "risk_based" ? "fixed_usdt" : exitMode
                      }
                      onChange={(e) =>
                        setExitMode(
                          e.target.value === "premium_multiple"
                            ? "premium_multiple"
                            : "fixed_usdt",
                        )
                      }
                    >
                      <option value="fixed_usdt">固定净盈利（USDT）</option>
                      <option value="premium_multiple">权利金倍数</option>
                    </select>
                  </div>
                  {sizingMode === "risk_based" || exitMode === "fixed_usdt" ? (
                    <div className="field">
                      <label htmlFor="netTarget">
                        {sizingMode === "risk_based"
                          ? "出场资金（k=1 基数）"
                          : "净盈利出场目标（USDT）"}
                      </label>
                      <input
                        id="netTarget"
                        className="mono"
                        type="number"
                        step="0.1"
                        min="0.1"
                        value={
                          sizingMode === "risk_based" ? riskExitUnit : netTarget
                        }
                        onChange={(e) => {
                          const v = Number(e.target.value);
                          if (sizingMode === "risk_based") {
                            setRiskExitUnit(v);
                            const k = Number(riskPreview?.k);
                            if (Number.isFinite(k) && k > 0) {
                              setNetTarget(Number((v * k).toFixed(2)));
                              setPerpQty(
                                Number((riskPerpUnit * k).toFixed(4)),
                              );
                              setOptQty(Number((riskOptUnit * k).toFixed(4)));
                            }
                          } else {
                            setNetTarget(v);
                          }
                        }}
                      />
                    </div>
                  ) : (
                    <div className="field">
                      <label htmlFor="premMult">
                        权利金倍数（1 = 一倍权利金）
                      </label>
                      <input
                        id="premMult"
                        className="mono"
                        type="number"
                        step="0.1"
                        min="0.1"
                        value={premMult}
                        onChange={(e) => setPremMult(Number(e.target.value))}
                      />
                    </div>
                  )}
                  <div className="field">
                    <label htmlFor="closeDev">平仓买一/标记最大偏差（%）</label>
                    <input
                      id="closeDev"
                      className="mono"
                      type="number"
                      step="1"
                      min="1"
                      value={closeDevPct}
                      onChange={(e) => setCloseDevPct(Number(e.target.value))}
                    />
                  </div>
                  <div className="field">
                    <label htmlFor="residualPrem">
                      残留期权回收：当前权利金 / 初始权利金 ≥（%）
                    </label>
                    <input
                      id="residualPrem"
                      className="mono"
                      type="number"
                      step="1"
                      min="1"
                      max="100"
                      value={residualMinPremPct}
                      onChange={(e) =>
                        setResidualMinPremPct(Number(e.target.value))
                      }
                    />
                  </div>
                </div>
              </section>
            ) : null}

            {stratSub === "pace" ? (
              <section className="settings-section">
                <h3>节奏与费用</h3>
                <div className="settings-fields">
                  <div className="field">
                    <label htmlFor="rest">轮间休息秒数</label>
                    <input
                      id="rest"
                      className="mono"
                      type="number"
                      step="1"
                      value={rest}
                      onChange={(e) => setRest(Number(e.target.value))}
                    />
                  </div>
                  <div className="field">
                    <label htmlFor="orderInterval">
                      实盘下单最小间隔（秒）
                    </label>
                    <input
                      id="orderInterval"
                      className="mono"
                      type="number"
                      step="0.1"
                      min="0.2"
                      max="30"
                      value={orderInterval}
                      onChange={(e) =>
                        setOrderInterval(Number(e.target.value))
                      }
                    />
                  </div>
                  <div className="field">
                    <label htmlFor="skipWe">周六日跳过开仓（上海时区）</label>
                    <select
                      id="skipWe"
                      className="mono"
                      value={skipWeekends ? "1" : "0"}
                      onChange={(e) => setSkipWeekends(e.target.value === "1")}
                    >
                      <option value="1">开启（推荐）</option>
                      <option value="0">关闭</option>
                    </select>
                  </div>
                  <div className="field">
                    <label htmlFor="oneExpDay">
                      每个到期只开一次（跨日）
                    </label>
                    <select
                      id="oneExpDay"
                      className="mono"
                      value={oneExpiryPerDay ? "1" : "0"}
                      onChange={(e) =>
                        setOneExpiryPerDay(e.target.value === "1")
                      }
                    >
                      <option value="1">开启（推荐）</option>
                      <option value="0">关闭</option>
                    </select>
                    <p className="hint" style={{ margin: "0.35rem 0 0" }}>
                      开启后：历史上已开过某到期日（如 0803）后，不论今天还是明天都不能再开同到期，只能监控/开下一到期（如 0804）。
                    </p>
                  </div>
                  <div className="field">
                    <label htmlFor="fee">手续费率</label>
                    <input
                      id="fee"
                      className="mono"
                      type="number"
                      step="0.0001"
                      value={fee}
                      onChange={(e) => setFee(Number(e.target.value))}
                    />
                  </div>
                  <div className="field">
                    <label htmlFor="showManual">显示手动开仓</label>
                    <select
                      id="showManual"
                      className="mono"
                      value={showManualTrade ? "1" : "0"}
                      onChange={(e) =>
                        setShowManualTrade(e.target.value === "1")
                      }
                    >
                      <option value="0">不显示（默认）</option>
                      <option value="1">显示「手动开一组 / 手动全平」</option>
                    </select>
                  </div>
                </div>
              </section>
            ) : null}

            <RulesFold
              open={stratRulesOpen}
              onToggle={() => setStratRulesOpen((v) => !v)}
            >
              <ul className="settings-rules-list">
                <li>
                  {mode === "LIVE"
                    ? "当前 LIVE：仓位/选约/出场影响真下单；模拟资金已隐藏。"
                    : "当前 SIM：本地撮合；可改模拟资金与交易所行情源。"}
                </li>
                {stratSub === "position" ? (
                  <>
                    <li>交易所：有持仓时不可切换。切换后套用该所合约并重连行情。</li>
                    {mode !== "LIVE" ? (
                      <li>
                        模拟资金：仅 SIM 显示。保存后重置权益（须无持仓）。默认
                        10000。
                      </li>
                    ) : null}
                    <li>
                      永续保证金模式：仅作用于永续（全仓/逐仓，默认全仓）。OKX
                      期权始终为现金/逐仓（cash），不受此选项影响。
                    </li>
                  </>
                ) : null}
                {stratSub === "select" ? (
                  <>
                    <li>
                      固定方向：默认关。开启后固定永续多→买 Put，或永续空→买
                      Call；期权只选实值或平值，不买虚值。关闭时仍按 ATM/卖一比价规则。
                    </li>
                    <li>
                      开仓 ATM 偏差限制：固定方向开启时不适用。关闭时不限制 |ATM −
                      现价|；开启后超过下方点数则不开仓。
                    </li>
                    <li>
                      开仓 ATM 最大偏差：仅开关开启且未开固定方向时生效。币安行权价较粗时可能长时间无开仓机会。
                    </li>
                  </>
                ) : null}
                {stratSub === "exit" ? (
                  <>
                    <li>
                      平仓买一/标记最大偏差：主要用于 SIM
                      流动性闸门；LIVE 以交易所能否成交为准。
                    </li>
                    <li>
                      残留期权回收比例：只平永续后，当买一权利金回升到初始权利金的该比例及以上时，按最新买一
                      IOC 限价卖掉归档期权（不扫市价）；默认 20%。未达标或未完全成交则等到下次巡检或到期结算。
                    </li>
                  </>
                ) : null}
                {stratSub === "pace" ? (
                  <>
                    <li>
                      实盘下单最小间隔：LIVE 私有下单/查单间隔，默认 1s。范围
                      0.2–30。
                    </li>
                    <li>
                      显示手动开仓：默认关闭。开启后计划页才出现「手动开一组 /
                      手动全平」。策略自动运行中禁止手动开仓（须先暂停）；紧急全平始终可用。
                    </li>
                  </>
                ) : null}
              </ul>
            </RulesFold>

            <div className="settings-actions">
              <button className="btn" type="submit">
                保存策略
              </button>
            </div>
          </form>
        </div>
      ) : null}

      {tab === "runtime" ? (
        <div className="card settings-card">
          {runtimeOk ? <div className="settings-ok">{runtimeOk}</div> : null}
          {err && tab === "runtime" ? <div className="err">{err}</div> : null}
          <form onSubmit={onSaveRuntime}>
            <section className="settings-section">
              <h3>模式</h3>
              <div className="settings-fields">
                <div className="field">
                  <label htmlFor="mode">运行模式</label>
                  <select
                    id="mode"
                    className="mono"
                    value={mode}
                    onChange={(e) =>
                      setMode(e.target.value === "LIVE" ? "LIVE" : "SIM")
                    }
                  >
                    <option value="SIM">SIM 本地模拟</option>
                    <option value="LIVE">LIVE 实盘</option>
                  </select>
                </div>
              </div>
            </section>

            <section className="settings-section">
              <h3>OKX API</h3>
              <div className="settings-fields">
                <div className="field">
                  <label htmlFor="okxKey">API Key</label>
                  <input
                    id="okxKey"
                    className="mono"
                    type="password"
                    autoComplete="off"
                    placeholder={
                      runtime?.okx_api_key_masked
                        ? `已配置 ${runtime.okx_api_key_masked}`
                        : "未配置"
                    }
                    value={okxKey}
                    onChange={(e) => setOkxKey(e.target.value)}
                  />
                </div>
                <div className="field">
                  <label htmlFor="okxSecret">Secret</label>
                  <input
                    id="okxSecret"
                    className="mono"
                    type="password"
                    autoComplete="off"
                    placeholder={
                      runtime?.okx_api_secret_masked
                        ? `已配置 ${runtime.okx_api_secret_masked}`
                        : "未配置"
                    }
                    value={okxSecret}
                    onChange={(e) => setOkxSecret(e.target.value)}
                  />
                </div>
                <div className="field">
                  <label htmlFor="okxPass">Passphrase</label>
                  <input
                    id="okxPass"
                    className="mono"
                    type="password"
                    autoComplete="off"
                    placeholder={
                      runtime?.okx_api_passphrase_masked
                        ? `已配置 ${runtime.okx_api_passphrase_masked}`
                        : "未配置"
                    }
                    value={okxPass}
                    onChange={(e) => setOkxPass(e.target.value)}
                  />
                </div>
              </div>
            </section>

            <section className="settings-section">
              <h3>币安 API</h3>
              <div className="settings-fields">
                <div className="field">
                  <label htmlFor="bnKey">API Key</label>
                  <input
                    id="bnKey"
                    className="mono"
                    type="password"
                    autoComplete="off"
                    placeholder={
                      runtime?.binance_api_key_masked
                        ? `已配置 ${runtime.binance_api_key_masked}`
                        : "未配置"
                    }
                    value={bnKey}
                    onChange={(e) => setBnKey(e.target.value)}
                  />
                </div>
                <div className="field">
                  <label htmlFor="bnSecret">Secret</label>
                  <input
                    id="bnSecret"
                    className="mono"
                    type="password"
                    autoComplete="off"
                    placeholder={
                      runtime?.binance_api_secret_masked
                        ? `已配置 ${runtime.binance_api_secret_masked}`
                        : "未配置"
                    }
                    value={bnSecret}
                    onChange={(e) => setBnSecret(e.target.value)}
                  />
                </div>
              </div>
            </section>

            <section className="settings-section">
              <h3>企业微信通知</h3>
              <div className="settings-fields">
                <div className="field">
                  <label htmlFor="wecomMachine">机器名称</label>
                  <input
                    id="wecomMachine"
                    className="mono"
                    autoComplete="off"
                    maxLength={64}
                    placeholder="例如 云A / 云B（推送标题前缀）"
                    value={wecomMachineName}
                    onChange={(e) => setWecomMachineName(e.target.value)}
                  />
                </div>
                <div className="field">
                  <label htmlFor="wecomEn">启用通知</label>
                  <select
                    id="wecomEn"
                    className="mono"
                    value={wecomEnabled ? "1" : "0"}
                    onChange={(e) => setWecomEnabled(e.target.value === "1")}
                  >
                    <option value="0">关闭</option>
                    <option value="1">开启</option>
                  </select>
                </div>
                <div className="field">
                  <label htmlFor="wecomUrl">Webhook URL</label>
                  <input
                    id="wecomUrl"
                    className="mono"
                    type="password"
                    autoComplete="off"
                    placeholder={
                      wecomMeta?.webhook_configured
                        ? `已配置 ${wecomMeta.webhook_url_masked || "********"}`
                        : "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=..."
                    }
                    value={wecomWebhook}
                    onChange={(e) => setWecomWebhook(e.target.value)}
                  />
                </div>
              </div>
              {wecomOk ? <div className="settings-ok">{wecomOk}</div> : null}
              <div className="settings-actions" style={{ gap: 8 }}>
                <button className="btn ghost" type="button" onClick={() => void onSaveWecom()}>
                  保存企业微信
                </button>
                <button
                  className="btn ghost"
                  type="button"
                  onClick={() => void onTestWecom()}
                >
                  发送测试
                </button>
              </div>
            </section>

            <RulesFold
              open={runtimeRulesOpen}
              onToggle={() => setRuntimeRulesOpen((v) => !v)}
            >
              <ul className="settings-rules-list">
                <li>
                  SIM = 本地撮合；LIVE =
                  当前所选交易所真下单（OKX / 币安均已接入）。密钥只写入服务器{" "}
                  <span className="mono">.env</span>
                  ，接口不回传明文。OKX 期权常需账户有 USDC；币安多为 USDT。
                </li>
                <li>
                  当前：{runtime?.mode || "—"} · 交易所{" "}
                  {runtime?.exchange || "—"} ·{" "}
                  {runtime?.mode === "LIVE"
                    ? runtime.live_ready
                      ? "LIVE 就绪"
                      : `未就绪(${runtime.live_ready_reason})`
                    : "SIM"}
                  。有持仓时不可切换模式。
                </li>
                <li>
                  密钥留空保存 = 不覆盖已有配置。交易所选币安且 LIVE
                  时用币安密钥真下单（无 Passphrase）。
                </li>
                <li>
                  企业微信：群机器人 Webhook，推送 Markdown，带标识（OPEN/CLOSE/START/PAUSE/FAULT）。
                  可填「机器名称」区分多机，标题形如【云A】。【实盘·OKX】仅 LIVE 显示。
                  当前机器：
                  <span className="mono">
                    {wecomMachineName || wecomMeta?.machine_name || "未命名"}
                  </span>
                  ；盘口标签：
                  <span className="mono">
                    {wecomMeta?.venue_label ||
                      (runtime?.mode === "LIVE"
                        ? `实盘·${(runtime.exchange || "okx").toUpperCase()}`
                        : "模拟盘")}
                  </span>
                  。开仓/平仓/启动/暂停/故障均推送。
                </li>
                <li>
                  企业微信：在群里添加「自定义机器人」复制 Webhook。留空保存不覆盖已有
                  URL。故障同类消息 5 分钟内去重。
                </li>
              </ul>
            </RulesFold>

            <div className="settings-actions">
              <button className="btn" type="submit" disabled={loading}>
                {loading ? "保存中…" : "保存模式与密钥"}
              </button>
            </div>
          </form>
        </div>
      ) : null}

      {tab === "funds" ? (
        <div className="card settings-card">
          {fundsErr ? <div className="err">{fundsErr}</div> : null}
          {fundsOk ? <div className="settings-ok">{fundsOk}</div> : null}

          <RulesFold
            open={fundsRulesOpen}
            onToggle={() => setFundsRulesOpen((v) => !v)}
          >
            <p>
              币种兑换：对齐 OKX 现货市价 USDC-USDT，在<strong>交易账户</strong>
              内完成（不走资金账户）。参考价{" "}
              {usdcRate != null ? `1 USDC ≈ ${usdcRate.toFixed(4)} USDT` : "—"}。
            </p>
            <p>
              账户划转：仅<strong>资金账户 ↔ 交易账户</strong>
              （USDT/USDC）。OKX 永续与期权均在交易账户，无单独期权资金/期权交易账户。
            </p>
            <p>
              自动兑 USDC（仅 OKX SIM/LIVE）：开仓前若<strong>交易账户</strong> USDC
              不够当前期权所需，则在交易账户内市价 USDT→USDC，目标约「所需×2」；已够开仓则不兑；并预留永续保证金。币安不生效。
            </p>
          </RulesFold>

          <section className="settings-section">
            <h3>币种兑换（交易账户 · USDT ↔ USDC）</h3>
            <div className="settings-fields">
              <div className="field">
                <label htmlFor="convertDir">方向</label>
                <select
                  id="convertDir"
                  value={convertDir}
                  onChange={(e) =>
                    setConvertDir(
                      e.target.value === "usdc_to_usdt"
                        ? "usdc_to_usdt"
                        : "usdt_to_usdc",
                    )
                  }
                >
                  <option value="usdt_to_usdc">USDT → USDC</option>
                  <option value="usdc_to_usdt">USDC → USDT</option>
                </select>
              </div>
              <div className="field">
                <label htmlFor="convertAmt">数量</label>
                <input
                  id="convertAmt"
                  type="number"
                  min={0}
                  step="0.01"
                  value={convertAmt}
                  onChange={(e) => setConvertAmt(Number(e.target.value))}
                />
              </div>
            </div>
            <div className="settings-actions">
              <button
                className="btn"
                type="button"
                disabled={loading}
                onClick={async () => {
                  setFundsErr("");
                  setFundsOk("");
                  setLoading(true);
                  try {
                    await apiFetch("/api/funds/convert", {
                      method: "POST",
                      body: JSON.stringify({
                        direction: convertDir,
                        amount: convertAmt,
                      }),
                    });
                    setFundsOk("兑换成功，顶部资金条将刷新");
                    window.dispatchEvent(new Event("funds-refresh"));
                  } catch (e) {
                    setFundsErr(e instanceof Error ? e.message : String(e));
                  } finally {
                    setLoading(false);
                  }
                }}
              >
                兑换
              </button>
            </div>
          </section>

          <section className="settings-section">
            <h3>账户划转</h3>
            <div className="settings-xfer-row">
              <div className="field">
                <label htmlFor="xferCcy">币种</label>
                <select
                  id="xferCcy"
                  value={xferCcy}
                  onChange={(e) =>
                    setXferCcy(e.target.value === "USDT" ? "USDT" : "USDC")
                  }
                >
                  <option value="USDC">USDC</option>
                  <option value="USDT">USDT</option>
                </select>
              </div>
              <div className="field">
                <label htmlFor="xferFrom">从</label>
                <select
                  id="xferFrom"
                  value={xferFrom}
                  onChange={(e) => {
                    const v = e.target.value === "trading" ? "trading" : "funding";
                    setXferFrom(v);
                    setXferTo(v === "funding" ? "trading" : "funding");
                  }}
                >
                  <option value="funding">资金账户</option>
                  <option value="trading">交易账户</option>
                </select>
              </div>
              <div className="field">
                <label htmlFor="xferTo">到</label>
                <select
                  id="xferTo"
                  value={xferTo}
                  onChange={(e) => {
                    const v = e.target.value === "trading" ? "trading" : "funding";
                    setXferTo(v);
                    setXferFrom(v === "funding" ? "trading" : "funding");
                  }}
                >
                  <option value="funding">资金账户</option>
                  <option value="trading">交易账户</option>
                </select>
              </div>
              <div className="field">
                <label htmlFor="xferAmt">数量</label>
                <input
                  id="xferAmt"
                  type="number"
                  min={0}
                  step="0.01"
                  value={xferAmt}
                  onChange={(e) => setXferAmt(Number(e.target.value))}
                />
              </div>
              <div className="settings-xfer-action">
                <button
                  className="btn"
                  type="button"
                  disabled={loading}
                  onClick={async () => {
                    setFundsErr("");
                    setFundsOk("");
                    setLoading(true);
                    try {
                      await apiFetch("/api/funds/transfer", {
                        method: "POST",
                        body: JSON.stringify({
                          ccy: xferCcy,
                          amount: xferAmt,
                          from_account: xferFrom,
                          to_account: xferTo,
                        }),
                      });
                      setFundsOk("划转成功，顶部资金条将刷新");
                      window.dispatchEvent(new Event("funds-refresh"));
                    } catch (e) {
                      setFundsErr(e instanceof Error ? e.message : String(e));
                    } finally {
                      setLoading(false);
                    }
                  }}
                >
                  划转
                </button>
              </div>
            </div>
          </section>
        </div>
      ) : null}

      {tab === "account" ? (
        <div className="card settings-card">
          {err ? <div className="err">{err}</div> : null}
          {ok ? <div className="settings-ok">{ok}</div> : null}
          <section className="settings-section">
            <h3>中控 API Token</h3>
            <p className="meta">
              {fleetHint ||
                "在中控生成 Token 后粘贴保存。用于远程启停、更新与免密登录。"}
            </p>
            <p className="meta">
              状态：{fleetConfigured ? "已配置" : "未配置"}
            </p>
            <div className="settings-fields">
              <div className="field">
                <label htmlFor="fleetTok">Token</label>
                <input
                  id="fleetTok"
                  type="password"
                  value={fleetTokenInput}
                  onChange={(e) => setFleetTokenInput(e.target.value)}
                  placeholder="粘贴中控生成的 Token"
                  autoComplete="off"
                />
              </div>
            </div>
            <div className="settings-actions">
              <button
                className="btn"
                type="button"
                disabled={loading || !fleetTokenInput.trim()}
                onClick={async () => {
                  setErr("");
                  setOk("");
                  setLoading(true);
                  try {
                    const r = await apiFetch<{ configured: boolean }>(
                      "/api/fleet/token",
                      {
                        method: "PUT",
                        body: JSON.stringify({ token: fleetTokenInput.trim() }),
                      },
                    );
                    setFleetConfigured(!!r.configured);
                    setFleetTokenInput("");
                    setOk("中控 Token 已保存");
                  } catch (ex) {
                    setErr(ex instanceof Error ? ex.message : String(ex));
                  } finally {
                    setLoading(false);
                  }
                }}
              >
                保存 Token
              </button>
              <button
                className="btn ghost"
                type="button"
                disabled={loading || !fleetConfigured}
                onClick={async () => {
                  setErr("");
                  setOk("");
                  setLoading(true);
                  try {
                    await apiFetch("/api/fleet/token", { method: "DELETE" });
                    setFleetConfigured(false);
                    setOk("已清除中控 Token");
                  } catch (ex) {
                    setErr(ex instanceof Error ? ex.message : String(ex));
                  } finally {
                    setLoading(false);
                  }
                }}
              >
                清除
              </button>
            </div>
          </section>
          <form onSubmit={onSaveCreds} className="settings-account-form">
            <section className="settings-section">
              <h3>登录账号</h3>
            <div className="settings-fields">
              <div className="field">
                <label htmlFor="user">新用户名</label>
                <input
                  id="user"
                  value={newUsername}
                  onChange={(e) => setNewUsername(e.target.value)}
                  required
                />
              </div>
              <div className="field">
                <label htmlFor="cur">当前密码</label>
                <input
                  id="cur"
                  type="password"
                  value={currentPassword}
                  onChange={(e) => setCurrentPassword(e.target.value)}
                  required
                />
              </div>
              <div className="field">
                <label htmlFor="np">新密码</label>
                <input
                  id="np"
                  type="password"
                  value={newPassword}
                  onChange={(e) => setNewPassword(e.target.value)}
                  required
                />
              </div>
              <div className="field">
                <label htmlFor="cp">确认新密码</label>
                <input
                  id="cp"
                  type="password"
                  value={confirmPassword}
                  onChange={(e) => setConfirmPassword(e.target.value)}
                  required
                />
              </div>
            </div>
            <div className="settings-actions">
              <button className="btn" type="submit" disabled={loading}>
                {loading ? "保存中…" : "保存账号"}
              </button>
            </div>
            </section>
          </form>
        </div>
      ) : null}

      {tab === "backup" ? (
        <div className="card settings-card">
          {bakOk ? <div className="settings-ok">{bakOk}</div> : null}
          {err && tab === "backup" ? <div className="err">{err}</div> : null}
          {bakBusy ? <div className="meta">{bakBusy}…</div> : null}

          <form onSubmit={onSaveBackupSettings}>
            <section className="settings-section">
              <h3>自动备份</h3>
              <div className="settings-fields">
                <div className="field">
                  <label htmlFor="bakAuto">自动备份</label>
                  <select
                    id="bakAuto"
                    className="mono"
                    value={bakAuto ? "1" : "0"}
                    onChange={(e) => setBakAuto(e.target.value === "1")}
                  >
                    <option value="1">开启（默认）</option>
                    <option value="0">关闭</option>
                  </select>
                </div>
                <div className="field">
                  <label htmlFor="bakInt">间隔（小时）</label>
                  <input
                    id="bakInt"
                    className="mono"
                    type="number"
                    min={1}
                    max={168}
                    value={bakInterval}
                    onChange={(e) => setBakInterval(Number(e.target.value))}
                  />
                </div>
                <div className="field">
                  <label htmlFor="bakKeep">保留份数</label>
                  <input
                    id="bakKeep"
                    className="mono"
                    type="number"
                    min={1}
                    max={90}
                    value={bakKeep}
                    onChange={(e) => setBakKeep(Number(e.target.value))}
                  />
                </div>
              </div>
              <div className="settings-actions" style={{ gap: 8 }}>
                <button className="btn" type="submit">
                  保存备份设置
                </button>
                <button
                  className="btn ghost"
                  type="button"
                  disabled={!!bakBusy}
                  onClick={() => void onBackupNow()}
                >
                  立即备份
                </button>
              </div>
            </section>
          </form>

          <section className="settings-section">
            <h3>服务器备份列表</h3>
            {backup?.items && backup.items.length > 0 ? (
              <div className="backup-list" role="list">
                {backup.items.map((it) => (
                  <div key={it.name} className="backup-list-row" role="listitem">
                    <span className="mono backup-list-name">
                      {it.name}
                      <span className="backup-list-meta">
                        {" "}
                        · {(it.size_bytes / 1024).toFixed(1)} KB ·{" "}
                        {new Date(it.mtime_ms).toLocaleString()}
                      </span>
                    </span>
                    <button
                      className="btn ghost"
                      type="button"
                      disabled={!!bakBusy}
                      onClick={() => void onDownload(it.name)}
                    >
                      下载
                    </button>
                  </div>
                ))}
              </div>
            ) : (
              <p style={{ color: "var(--muted)" }}>暂无备份，可点「立即备份」。</p>
            )}
          </section>

          <section className="settings-section">
            <h3>上传恢复（新服务器迁移）</h3>
            <div className="settings-fields">
              <div className="field">
                <label htmlFor="bakFile">备份 zip</label>
                <input
                  id="bakFile"
                  type="file"
                  accept=".zip,application/zip"
                  onChange={(e) =>
                    setRestoreFile(e.target.files?.[0] ?? null)
                  }
                />
              </div>
              <div className="field">
                <label htmlFor="bakPhrase">确认串</label>
                <input
                  id="bakPhrase"
                  className="mono"
                  value={restorePhrase}
                  placeholder="RESTORE"
                  onChange={(e) => setRestorePhrase(e.target.value)}
                />
              </div>
            </div>
            <div className="settings-actions">
              <button
                className="btn danger"
                type="button"
                disabled={!!bakBusy}
                onClick={() => void onRestore()}
              >
                上传并恢复
              </button>
            </div>
          </section>

          <RulesFold
            open={backupRulesOpen}
            onToggle={() => setBackupRulesOpen((v) => !v)}
          >
            <ul className="settings-rules-list">
              <li>
                备份内容：SQLite 数据库 +{" "}
                <span className="mono">.env</span>
                （含登录与交易所密钥）。服务器目录：
                <span className="mono">
                  {backup?.backup_dir || "/root/eth_hedge_backups"}
                </span>
                。
              </li>
              <li>
                上传恢复：在新机器部署后，上传旧机下载的 zip，将覆盖数据库与{" "}
                <span className="mono">.env</span>
                。须无持仓；确认串填写{" "}
                <span className="mono">RESTORE</span>
                。恢复后服务自动重启。
              </li>
            </ul>
          </RulesFold>
        </div>
      ) : null}
    </div>
  );
}
