"use client";

import { useCallback, useEffect, useMemo, useState } from "react";

type Entry = { pm2: string; script: string; label: string };

const apiBase = () =>
  (typeof process !== "undefined" && process.env.NEXT_PUBLIC_API_URL) || "";

function stem(script: string) {
  const base = script.split(/[/\\]/).pop() || script;
  const i = base.lastIndexOf(".");
  return i > 0 ? base.slice(0, i) : base;
}

function isYoutubeScript(script: string) {
  if (!/\.py$/i.test(script)) return false;
  const s = stem(script).toLowerCase();
  return s.startsWith("youtube") || s.startsWith("douyin_youtube");
}
function isTiktokScript(script: string) {
  return /\.py$/i.test(script) && stem(script).toLowerCase().startsWith("tiktok");
}
function isObsScript(script: string) {
  return stem(script).toLowerCase().startsWith("obs");
}
function isWebScript(script: string) {
  return stem(script).toLowerCase() === "web";
}

function statusMeta(processStatus: string): {
  dot: string;
  text: string;
  pillClass: string;
} {
  switch (processStatus) {
    case "online":
      return {
        dot: "bg-emerald-400 shadow-[0_0_12px_rgba(52,211,153,0.55)]",
        text: "运行中",
        pillClass: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200",
      };
    case "stopped":
      return {
        dot: "bg-slate-500",
        text: "已停止",
        pillClass: "border-slate-500/30 bg-slate-500/10 text-slate-300",
      };
    case "errored":
      return {
        dot: "bg-rose-500 shadow-[0_0_10px_rgba(244,63,94,0.45)]",
        text: "异常",
        pillClass: "border-rose-500/30 bg-rose-500/10 text-rose-200",
      };
    case "launching":
    case "starting":
      return {
        dot: "bg-amber-400 animate-pulse-soft",
        text: "启动中",
        pillClass: "border-amber-500/30 bg-amber-500/10 text-amber-200",
      };
    case "waiting restart":
      return {
        dot: "bg-amber-400",
        text: "等待重启",
        pillClass: "border-amber-500/30 bg-amber-500/10 text-amber-200",
      };
    case "not_found":
      return {
        dot: "bg-violet-400",
        text: "未启动过",
        pillClass: "border-violet-500/30 bg-violet-500/10 text-violet-200",
      };
    case "invalid":
      return {
        dot: "bg-slate-600",
        text: "选择无效",
        pillClass: "border-slate-600/40 bg-slate-800/50 text-slate-400",
      };
    default:
      return {
        dot: "bg-slate-500",
        text: "检测中…",
        pillClass: "border-slate-500/25 bg-slate-800/40 text-slate-400",
      };
  }
}

function hintForEntry(script: string): string {
  if (isObsScript(script))
    return "用于从本机 SRS 拉流并在接好显示器的机器上全屏播放（HDMI）。请先保证 SRS 已启动，再点「启动」。";
  if (stem(script).toLowerCase().startsWith("douyin_youtube"))
    return "抖音直播转推到 YouTube：先填好右侧 youtube.ini（密钥）和 URL_config（直播间地址），保存后再启动。";
  if (isYoutubeScript(script))
    return "YouTube 相关推流：检查 youtube.ini 与 URL_config，保存后启动。";
  if (isTiktokScript(script))
    return "多平台录制/监测：在 URL_config 里填写直播间地址，保存后启动。";
  if (isWebScript(script))
    return "这是网页控制台本身。一般不要用这里停掉自己；其它机器上可用 PM2 管理。";
  return "选中后使用左侧按钮启动或停止，右侧查看日志。";
}

const obsDefault =
  "srt://127.0.0.1:10080?streamid=#!::r=live/livestream,m=request&latency=10";

export default function Home() {
  const [entries, setEntries] = useState<Entry[]>([]);
  const [backend, setBackend] = useState<string>("");
  const [platform, setPlatform] = useState<string>("");
  const [current, setCurrent] = useState("");
  const [processStatus, setProcessStatus] = useState("unknown");
  const [rawStatus, setRawStatus] = useState("");
  const [recentLog, setRecentLog] = useState("");
  const [recentErr, setRecentErr] = useState("");
  const [busy, setBusy] = useState<string | null>(null);
  const [toast, setToast] = useState<string | null>(null);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [bootLoading, setBootLoading] = useState(true);
  const [autoRefresh, setAutoRefresh] = useState(true);
  const [logTab, setLogTab] = useState<"out" | "err" | "split">("split");
  const [helpOpen, setHelpOpen] = useState(true);
  const [confirmStop, setConfirmStop] = useState(false);

  const [youtubeIni, setYoutubeIni] = useState("");
  const [urlIni, setUrlIni] = useState("");
  const [cfgYoutubeStatus, setCfgYoutubeStatus] = useState("就绪");
  const [cfgUrlStatus, setCfgUrlStatus] = useState("就绪");

  const base = apiBase();
  const ent = useMemo(
    () => entries.find((e) => e.pm2 === current) || null,
    [entries, current],
  );
  const script = ent?.script || "";

  const showYoutube = ent && isYoutubeScript(script);
  const showUrl = ent && (isYoutubeScript(script) || isTiktokScript(script));
  const showObs = ent && isObsScript(script);

  const showToast = useCallback((msg: string) => {
    setToast(msg);
    setTimeout(() => setToast(null), 3200);
  }, []);

  const fetchJson = useCallback(
    async (path: string) => {
      const res = await fetch(`${base}${path}`);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    },
    [base],
  );

  const refreshBackendInfo = useCallback(async () => {
    try {
      const info = await fetchJson("/server_info?refresh=true");
      setBackend(info.process_backend || "");
      setPlatform(info.platform || "");
      showToast(
        info.process_backend === "pm2"
          ? "已切换为 PM2 管理"
          : "已切换为本地进程模式（未检测到 PM2）",
      );
    } catch {
      showToast("刷新后端信息失败");
    }
  }, [fetchJson, showToast]);

  useEffect(() => {
    (async () => {
      setBootLoading(true);
      setLoadError(null);
      try {
        const [mon, info] = await Promise.all([
          fetchJson("/process_monitor"),
          fetchJson("/server_info"),
        ]);
        const list: Entry[] = (mon.entries || []).map((e: Entry) => ({
          script: e.script,
          label: e.label || e.script,
          pm2: e.pm2 || stem(e.script),
        }));
        setEntries(list);
        setBackend(info.process_backend || "");
        setPlatform(info.platform || "");
        setCurrent((prev) => prev || list[0]?.pm2 || "");
      } catch {
        setLoadError(
          "连不上后端。请确认已运行 web.sh / web.bat，并用浏览器打开同一地址（开发时可先起 uvicorn 再 npm run dev）。",
        );
      } finally {
        setBootLoading(false);
      }
    })();
  }, [fetchJson]);

  const refreshStatus = useCallback(async () => {
    if (!current) return;
    try {
      const data = await fetchJson(`/status?process=${encodeURIComponent(current)}`);
      setProcessStatus(data.process_status || "unknown");
      setRawStatus(data.raw_status || "");
      setRecentLog(data.recent_log || "");
      setRecentErr(data.recent_error || "");
    } catch {
      setProcessStatus("unknown");
    }
  }, [current, fetchJson]);

  useEffect(() => {
    if (!current || !autoRefresh) return;
    const t = setInterval(refreshStatus, 1000);
    refreshStatus();
    return () => clearInterval(t);
  }, [current, autoRefresh, refreshStatus]);

  useEffect(() => {
    if (current && !autoRefresh) void refreshStatus();
  }, [current, autoRefresh, refreshStatus]);

  const loadYoutube = useCallback(async () => {
    if (!current) return;
    setCfgYoutubeStatus("加载中…");
    try {
      const data = await fetchJson(`/get_config?process=${encodeURIComponent(current)}`);
      setYoutubeIni(data.content || "");
      setCfgYoutubeStatus("已从服务器加载");
    } catch (e) {
      setCfgYoutubeStatus(`失败: ${(e as Error).message}`);
    }
  }, [current, fetchJson]);

  const saveYoutube = async () => {
    if (!current) return;
    setCfgYoutubeStatus("保存中…");
    try {
      const res = await fetch(
        `${base}/save_config?process=${encodeURIComponent(current)}`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ content: youtubeIni }),
        },
      );
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || res.statusText);
      setCfgYoutubeStatus(data.message || "已保存到服务器");
      showToast("youtube.ini 已保存");
    } catch (e) {
      setCfgYoutubeStatus(`失败: ${(e as Error).message}`);
    }
  };

  const loadUrl = useCallback(async () => {
    if (!current) return;
    setCfgUrlStatus("加载中…");
    try {
      const data = await fetchJson(`/get_url_config?process=${encodeURIComponent(current)}`);
      setUrlIni(data.content || "");
      setCfgUrlStatus("已从服务器加载");
    } catch (e) {
      setCfgUrlStatus(`失败: ${(e as Error).message}`);
    }
  }, [current, fetchJson]);

  const saveUrl = async () => {
    if (!current) return;
    setCfgUrlStatus("保存中…");
    try {
      const res = await fetch(
        `${base}/save_url_config?process=${encodeURIComponent(current)}`,
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ content: urlIni }),
        },
      );
      const data = await res.json();
      if (!res.ok) throw new Error(data.error || res.statusText);
      setCfgUrlStatus(data.message || "已保存到服务器");
      showToast("URL 配置已保存");
    } catch (e) {
      setCfgUrlStatus(`失败: ${(e as Error).message}`);
    }
  };

  useEffect(() => {
    if (!current) return;
    if (showYoutube) void loadYoutube();
    if (showUrl) void loadUrl();
  }, [current, showYoutube, showUrl, loadYoutube, loadUrl]);

  const runAction = async (action: string) => {
    if (!current) return;
    setBusy(action);
    try {
      const res = await fetch(`${base}/${action}?process=${encodeURIComponent(current)}`);
      const data = await res.json();
      if (!res.ok) {
        showToast(String(data.output || data.message || "操作失败"));
        return;
      }
      if (action !== "status") {
        const msg =
          data.output ?? data.pm2_output ?? data.message ?? "已完成";
        showToast(typeof msg === "string" ? msg : JSON.stringify(msg));
        setTimeout(refreshStatus, 1200);
      } else {
        setProcessStatus(data.process_status || "unknown");
        setRawStatus(data.raw_status || "");
        setRecentLog(data.recent_log || "");
        setRecentErr(data.recent_error || "");
      }
    } catch (e) {
      showToast((e as Error).message);
    } finally {
      setBusy(null);
    }
  };

  const onStopClick = () => {
    if (isWebScript(script)) {
      showToast("这是控制台进程，停止后网页会失效，请谨慎操作");
    }
    setConfirmStop(true);
  };

  const confirmStopRun = async () => {
    setConfirmStop(false);
    await runAction("stop");
  };

  const copyObs = async () => {
    try {
      await navigator.clipboard.writeText(obsDefault);
      showToast("已复制到剪贴板");
    } catch {
      showToast("复制失败，请手动选中文字复制");
    }
  };

  const st = statusMeta(processStatus);

  return (
    <main className="mx-auto min-h-screen max-w-6xl px-4 pb-16 pt-8 sm:px-6 lg:px-10">
      {toast && (
        <div
          role="status"
          className="animate-toast-in fixed bottom-6 left-1/2 z-[100] max-w-[min(90vw,28rem)] -translate-x-1/2 rounded-2xl border border-cyan-500/25 bg-slate-950/95 px-5 py-3 text-center text-sm leading-snug text-cyan-50 shadow-xl shadow-cyan-950/40 backdrop-blur-md"
        >
          {toast}
        </div>
      )}

      {confirmStop && (
        <div
          className="fixed inset-0 z-[90] flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
          role="dialog"
          aria-modal="true"
          aria-labelledby="stop-title"
        >
          <div className="glass w-full max-w-md rounded-3xl p-6 shadow-2xl">
            <h2 id="stop-title" className="text-lg font-semibold text-white">
              确认停止该进程？
            </h2>
            <p className="mt-2 text-sm leading-relaxed text-slate-400">
              停止后推流或播放会中断。若进程卡住，停止后系统还会尝试结束相关 ffmpeg。
            </p>
            {ent && (
              <p className="mt-3 rounded-xl bg-white/5 px-3 py-2 font-mono text-xs text-slate-300">
                {ent.label} · {ent.script}
              </p>
            )}
            <div className="mt-6 flex flex-wrap justify-end gap-3">
              <button
                type="button"
                className="btn-focus rounded-xl border border-white/15 bg-white/5 px-5 py-2.5 text-sm text-slate-200 transition hover:bg-white/10"
                onClick={() => setConfirmStop(false)}
              >
                取消
              </button>
              <button
                type="button"
                className="btn-focus rounded-xl bg-gradient-to-r from-rose-600 to-rose-500 px-5 py-2.5 text-sm font-medium text-white shadow-lg shadow-rose-900/30 transition hover:from-rose-500 hover:to-rose-400"
                onClick={() => void confirmStopRun()}
              >
                确认停止
              </button>
            </div>
          </div>
        </div>
      )}

      <header className="mb-8 flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between">
        <div className="max-w-2xl">
          <p className="mb-2 text-[11px] font-semibold uppercase tracking-[0.22em] text-cyan-400/90">
            直播推流 · 小白友好
          </p>
          <h1 className="text-3xl font-bold tracking-tight text-white glow-text sm:text-4xl">
            控制台
          </h1>
          <p className="mt-3 text-sm leading-relaxed text-slate-400">
            选一个程序 → 需要时先改配置并保存 → 点「启动」。下面有图文说明。看不懂日志时，把「错误输出」截图发给技术支持。
          </p>
        </div>
        <div className="flex flex-col gap-3 sm:flex-row sm:items-center lg:flex-col lg:items-end">
          <div className="flex flex-wrap justify-end gap-2">
            {platform && (
              <span className="rounded-full border border-white/10 bg-white/[0.06] px-3 py-1.5 font-mono text-[11px] text-slate-400">
                系统 {platform}
              </span>
            )}
            {backend && (
              <span
                className="rounded-full border border-violet-500/25 bg-violet-500/10 px-3 py-1.5 text-xs text-violet-200"
                title="有 PM2 时用 PM2；没有则由网页直接拉起进程"
              >
                {backend === "pm2"
                  ? "PM2 管理"
                  : backend === "local"
                    ? "本地进程（无 PM2）"
                    : backend}
              </span>
            )}
          </div>
          <button
            type="button"
            onClick={() => void refreshBackendInfo()}
            className="btn-focus self-end rounded-full border border-white/12 bg-white/[0.06] px-4 py-2 text-xs text-slate-300 transition hover:border-cyan-500/30 hover:text-white"
            title="重装 PM2 后可点这里重新识别"
          >
            重新检测 PM2
          </button>
        </div>
      </header>

      <section className="mb-8 overflow-hidden rounded-3xl border border-cyan-500/15 bg-gradient-to-br from-cyan-950/40 via-slate-900/30 to-violet-950/25">
        <button
          type="button"
          onClick={() => setHelpOpen((o) => !o)}
          className="btn-focus flex w-full items-center justify-between gap-3 px-5 py-4 text-left transition hover:bg-white/[0.04]"
        >
          <span className="text-sm font-semibold text-cyan-100/95">
            新手必看：三步上手
          </span>
          <span className="text-cyan-400/80">{helpOpen ? "收起 ▲" : "展开 ▼"}</span>
        </button>
        {helpOpen && (
          <div className="space-y-4 border-t border-white/10 px-5 py-5 text-sm leading-relaxed text-slate-300">
            <div className="grid gap-4 md:grid-cols-3">
              <div className="rounded-2xl border border-white/8 bg-black/20 p-4">
                <p className="mb-2 flex h-8 w-8 items-center justify-center rounded-full bg-cyan-500/20 text-sm font-bold text-cyan-300">
                  1
                </p>
                <p className="font-medium text-white">打开本页</p>
                <p className="mt-1 text-xs text-slate-400">
                  服务器上先执行 <code className="rounded bg-black/40 px-1.5 py-0.5 text-cyan-200/90">web.sh</code>{" "}
                 （Windows 用 <code className="rounded bg-black/40 px-1.5 py-0.5">web.bat</code>
                  ）。浏览器地址栏要和启动时提示的端口一致。
                </p>
              </div>
              <div className="rounded-2xl border border-white/8 bg-black/20 p-4">
                <p className="mb-2 flex h-8 w-8 items-center justify-center rounded-full bg-violet-500/20 text-sm font-bold text-violet-300">
                  2
                </p>
                <p className="font-medium text-white">选进程、改配置</p>
                <p className="mt-1 text-xs text-slate-400">
                  左侧下拉框选「抖音→YouTube」或「TikTok」等。需要填密钥、直播间地址时，在右侧编辑后点「保存」。
                </p>
              </div>
              <div className="rounded-2xl border border-white/8 bg-black/20 p-4">
                <p className="mb-2 flex h-8 w-8 items-center justify-center rounded-full bg-emerald-500/20 text-sm font-bold text-emerald-300">
                  3
                </p>
                <p className="font-medium text-white">启动并看日志</p>
                <p className="mt-1 text-xs text-slate-400">
                  点「启动」，看右侧绿色「运行日志」。红色「错误输出」有内容时，多半要改配置或检查网络。
                </p>
              </div>
            </div>
            <p className="rounded-xl border border-amber-500/20 bg-amber-950/20 px-4 py-3 text-xs text-amber-100/90">
              <strong className="text-amber-200">HDMI / OBS：</strong>
              需本机已装 SRS，且显示器已接好。OBS 里选「自定义」推流，服务器填下面给出的 SRT 地址，密钥留空。
            </p>
          </div>
        )}
      </section>

      {bootLoading && (
        <div className="glass flex flex-col items-center justify-center gap-4 rounded-3xl py-24">
          <div className="h-10 w-10 rounded-full border-2 border-cyan-400/30 border-t-cyan-400 animate-spin" />
          <p className="text-sm text-slate-400">正在连接服务器…</p>
        </div>
      )}

      {!bootLoading && loadError && (
        <div className="glass rounded-3xl border border-rose-500/25 bg-rose-950/20 p-8 text-center">
          <p className="text-base font-medium text-rose-200">无法连接</p>
          <p className="mx-auto mt-3 max-w-lg text-sm leading-relaxed text-slate-400">
            {loadError}
          </p>
          <button
            type="button"
            className="btn-focus mt-6 rounded-full bg-cyan-600 px-6 py-2.5 text-sm font-medium text-white hover:bg-cyan-500"
            onClick={() => window.location.reload()}
          >
            重试刷新页面
          </button>
        </div>
      )}

      {!bootLoading && !loadError && !entries.length && (
        <div className="glass rounded-3xl p-10 text-center">
          <p className="text-slate-300">没有找到可管理的脚本</p>
          <p className="mx-auto mt-2 max-w-md text-sm text-slate-500">
            项目根目录应有 tiktok*.py、youtube*.py、douyin_youtube*.py 或 obs 脚本（Linux 用 .sh，Windows 用 .bat）。
          </p>
        </div>
      )}

      {!bootLoading && !loadError && entries.length > 0 && (
        <div className="grid gap-6 lg:grid-cols-12 lg:gap-8">
          <aside className="glass rounded-3xl p-6 lg:col-span-4 xl:col-span-3">
            <label className="text-xs font-semibold uppercase tracking-wider text-slate-500">
              要控制哪一个程序
            </label>
            <select
              className="btn-focus mt-2 w-full cursor-pointer rounded-2xl border border-white/12 bg-slate-950/60 px-4 py-3.5 text-sm text-white outline-none transition hover:border-cyan-500/35"
              value={current}
              onChange={(e) => setCurrent(e.target.value)}
            >
              {entries.map((e) => (
                <option key={e.pm2} value={e.pm2}>
                  {e.label} — {e.script}
                </option>
              ))}
            </select>

            {ent && (
              <p className="mt-4 rounded-2xl border border-white/6 bg-black/25 p-3 text-xs leading-relaxed text-slate-400">
                {hintForEntry(ent.script)}
              </p>
            )}

            <div className="mt-5 flex items-center gap-3">
              <span className={`h-3 w-3 shrink-0 rounded-full ${st.dot}`} aria-hidden />
              <div className="min-w-0 flex-1">
                <div className="flex flex-wrap items-center gap-2">
                  <span
                    className={`rounded-full border px-2.5 py-0.5 text-xs font-medium ${st.pillClass}`}
                  >
                    {st.text}
                  </span>
                  {ent && (
                    <span className="truncate font-mono text-[11px] text-slate-500">
                      {ent.pm2}
                    </span>
                  )}
                </div>
                <label className="mt-3 flex cursor-pointer items-center gap-2 text-xs text-slate-500">
                  <input
                    type="checkbox"
                    checked={autoRefresh}
                    onChange={(e) => setAutoRefresh(e.target.checked)}
                    className="h-3.5 w-3.5 rounded border-white/20 bg-slate-900 text-cyan-500 focus:ring-cyan-500/40"
                  />
                  自动刷新日志（每秒）
                </label>
              </div>
            </div>

            <div className="mt-6 grid grid-cols-2 gap-2">
              <button
                type="button"
                disabled={!!busy}
                title="启动当前选中的程序"
                onClick={() => void runAction("start")}
                className="btn-focus col-span-2 rounded-2xl bg-gradient-to-r from-emerald-600 to-cyan-600 py-3 text-sm font-semibold text-white shadow-lg shadow-cyan-900/25 transition hover:from-emerald-500 hover:to-cyan-500 disabled:opacity-45"
              >
                {busy === "start" ? "正在启动…" : "启动"}
              </button>
              <button
                type="button"
                disabled={!!busy}
                title="安全停止，会结束相关 ffmpeg"
                onClick={onStopClick}
                className="btn-focus rounded-2xl border border-rose-500/35 bg-rose-950/30 py-3 text-sm font-medium text-rose-100 transition hover:bg-rose-900/40 disabled:opacity-45"
              >
                {busy === "stop" ? "停止中…" : "停止"}
              </button>
              <button
                type="button"
                disabled={!!busy}
                title="先停再起"
                onClick={() => void runAction("restart")}
                className="btn-focus rounded-2xl border border-amber-500/30 bg-amber-950/20 py-3 text-sm font-medium text-amber-100 transition hover:bg-amber-900/30 disabled:opacity-45"
              >
                {busy === "restart" ? "重启中…" : "重启"}
              </button>
              <button
                type="button"
                disabled={!!busy}
                title="立即拉一次状态与日志"
                onClick={() => void runAction("status")}
                className="btn-focus col-span-2 rounded-2xl border border-white/12 bg-white/[0.05] py-2.5 text-sm text-slate-200 transition hover:bg-white/10 disabled:opacity-45"
              >
                {busy === "status" ? "刷新中…" : "手动刷新状态"}
              </button>
            </div>
          </aside>

          <section className="glass rounded-3xl p-6 lg:col-span-8 xl:col-span-9">
            <div className="mb-5 flex flex-col gap-3 border-b border-white/8 pb-5 sm:flex-row sm:items-center sm:justify-between">
              <h2 className="text-lg font-semibold text-white">配置与日志</h2>
              <div className="flex flex-wrap gap-2 rounded-2xl border border-white/10 bg-black/20 p-1">
                {(
                  [
                    ["split", "双栏"],
                    ["out", "仅运行"],
                    ["err", "仅错误"],
                  ] as const
                ).map(([id, lab]) => (
                  <button
                    key={id}
                    type="button"
                    onClick={() => setLogTab(id)}
                    className={`rounded-xl px-3 py-1.5 text-xs font-medium transition ${
                      logTab === id
                        ? "bg-cyan-500/25 text-cyan-100"
                        : "text-slate-500 hover:text-slate-300"
                    }`}
                  >
                    {lab}
                  </button>
                ))}
              </div>
            </div>

            {showObs && (
              <div className="mb-5 rounded-2xl border border-violet-500/25 bg-violet-950/25 p-5">
                <h3 className="text-sm font-semibold text-violet-100">
                  OBS 推流地址（接到 HDMI 前在 OBS 里填）
                </h3>
                <p className="mt-2 text-xs leading-relaxed text-slate-400">
                  OBS → 设置 → 推流 → 服务选「自定义」。下方整行就是「服务器」内容，「串流密钥」留空。若 SRS 不在本机，把{" "}
                  <code className="text-violet-200/90">127.0.0.1</code> 改成服务器 IP。
                </p>
                <div className="mt-4 flex flex-col gap-2 sm:flex-row sm:items-stretch">
                  <code className="log-scroll flex-1 overflow-x-auto rounded-xl border border-white/10 bg-black/45 px-4 py-3 font-mono text-[11px] leading-relaxed text-emerald-300/95">
                    {obsDefault}
                  </code>
                  <button
                    type="button"
                    onClick={() => void copyObs()}
                    className="btn-focus shrink-0 rounded-xl bg-violet-600 px-5 py-3 text-sm font-medium text-white hover:bg-violet-500 sm:w-auto"
                  >
                    一键复制
                  </button>
                </div>
              </div>
            )}

            {showYoutube && (
              <div className="mb-5 rounded-2xl border border-cyan-500/20 bg-cyan-950/15 p-5">
                <h3 className="text-sm font-semibold text-cyan-100">
                  YouTube 密钥（youtube.ini）
                </h3>
                <p className="mt-1 text-xs text-slate-500">
                  改完务必点「保存到服务器」，再回左侧点「启动」。
                </p>
                <textarea
                  value={youtubeIni}
                  onChange={(e) => setYoutubeIni(e.target.value)}
                  className="log-scroll mt-3 min-h-[8rem] w-full resize-y rounded-xl border border-white/10 bg-black/40 p-4 font-mono text-xs leading-relaxed text-slate-200 outline-none transition focus:border-cyan-500/40 focus:ring-1 focus:ring-cyan-500/30"
                  spellCheck={false}
                />
                <div className="mt-3 flex flex-wrap items-center gap-2">
                  <button
                    type="button"
                    onClick={() => void loadYoutube()}
                    className="btn-focus rounded-xl border border-cyan-500/35 bg-cyan-950/30 px-4 py-2 text-xs font-medium text-cyan-100 hover:bg-cyan-900/35"
                  >
                    重新加载
                  </button>
                  <button
                    type="button"
                    onClick={() => void saveYoutube()}
                    className="btn-focus rounded-xl bg-cyan-600 px-4 py-2 text-xs font-semibold text-white hover:bg-cyan-500"
                  >
                    保存到服务器
                  </button>
                  <span className="text-xs text-slate-500">{cfgYoutubeStatus}</span>
                </div>
              </div>
            )}

            {showUrl && (
              <div className="mb-5 rounded-2xl border border-violet-500/20 bg-violet-950/15 p-5">
                <h3 className="text-sm font-semibold text-violet-100">
                  直播间地址列表（URL_config.ini）
                </h3>
                <p className="mt-1 text-xs text-slate-500">
                  每行一个地址或按你原来文件的格式编写，保存后再启动脚本。
                </p>
                <textarea
                  value={urlIni}
                  onChange={(e) => setUrlIni(e.target.value)}
                  className="log-scroll mt-3 min-h-[7rem] w-full resize-y rounded-xl border border-white/10 bg-black/40 p-4 font-mono text-xs leading-relaxed text-slate-200 outline-none transition focus:border-violet-500/40 focus:ring-1 focus:ring-violet-500/30"
                  spellCheck={false}
                />
                <div className="mt-3 flex flex-wrap items-center gap-2">
                  <button
                    type="button"
                    onClick={() => void loadUrl()}
                    className="btn-focus rounded-xl border border-violet-500/35 bg-violet-950/30 px-4 py-2 text-xs font-medium text-violet-100 hover:bg-violet-900/35"
                  >
                    重新加载
                  </button>
                  <button
                    type="button"
                    onClick={() => void saveUrl()}
                    className="btn-focus rounded-xl bg-violet-600 px-4 py-2 text-xs font-semibold text-white hover:bg-violet-500"
                  >
                    保存到服务器
                  </button>
                  <span className="text-xs text-slate-500">{cfgUrlStatus}</span>
                </div>
              </div>
            )}

            <div
              className={
                logTab === "split"
                  ? "grid gap-4 lg:grid-cols-2"
                  : "grid gap-4"
              }
            >
              {(logTab === "split" || logTab === "out") && (
                <div>
                  <h3 className="mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-emerald-400/90">
                    <span className="h-1.5 w-1.5 rounded-full bg-emerald-400" />
                    运行日志
                  </h3>
                  <pre className="log-scroll max-h-[min(50vh,22rem)] overflow-auto rounded-2xl border border-emerald-500/15 bg-black/55 p-4 font-mono text-[11px] leading-relaxed text-emerald-100/95">
                    {recentLog?.trim() ? recentLog : "暂无输出（若刚启动，可等几秒或点「手动刷新状态」）"}
                  </pre>
                </div>
              )}
              {(logTab === "split" || logTab === "err") && (
                <div>
                  <h3 className="mb-2 flex items-center gap-2 text-xs font-semibold uppercase tracking-wider text-rose-400/90">
                    <span className="h-1.5 w-1.5 rounded-full bg-rose-400" />
                    错误输出
                  </h3>
                  <pre className="log-scroll max-h-[min(50vh,22rem)] overflow-auto rounded-2xl border border-rose-500/15 bg-black/55 p-4 font-mono text-[11px] leading-relaxed text-rose-100/95">
                    {recentErr?.trim() ? recentErr : "暂无错误（若推流失败，这里通常会有红字提示）"}
                  </pre>
                </div>
              )}
            </div>

            <details className="group mt-6 rounded-2xl border border-white/8 bg-black/20 open:border-white/15">
              <summary className="cursor-pointer list-none px-4 py-3 text-xs text-slate-500 transition group-open:text-slate-300 [&::-webkit-details-marker]:hidden">
                <span className="inline-flex items-center gap-2">
                  高级：查看完整进程列表（PM2 / 本地）
                  <span className="text-slate-600 group-open:hidden">▼</span>
                  <span className="hidden text-slate-600 group-open:inline">▲</span>
                </span>
              </summary>
              <pre className="log-scroll max-h-52 overflow-auto border-t border-white/8 p-4 font-mono text-[10px] leading-relaxed text-slate-500">
                {rawStatus || "—"}
              </pre>
            </details>
          </section>
        </div>
      )}

      <footer className="mt-12 border-t border-white/8 pt-8 text-center text-[11px] leading-relaxed text-slate-600">
        <p>
          遇到问题先看「错误输出」与「高级」里的进程表；仍无法解决请联系维护人员并带上日志截图。
        </p>
        <p className="mt-2 text-slate-700">
          运维一键启动：<span className="font-mono text-slate-500">./start.sh</span> 或{" "}
          <span className="font-mono text-slate-500">start.bat</span>；开发双端：{" "}
          <span className="font-mono text-slate-500">./dev.sh</span> /{" "}
          <span className="font-mono text-slate-500">npm run dev</span>
        </p>
      </footer>
    </main>
  );
}
