"use client";

import { Ban, Loader2, MonitorPlay, Plus, Radio, RefreshCw, Trash2 } from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import {
  buildYoutubeIniFromFields,
  extractDouyinLiveUrls,
  managedUrlConfigRelPathForPm2,
  managedYoutubeIniRelPathForPm2,
  parseYoutubeIniFields,
  parseYoutubeStreamKeySuffix,
  type YoutubeIniFields,
} from "@/lib/youtubeConfigUtils";
import type { PortalNavTarget } from "@/components/live-product/LivePipelineStrip";
import { LivePipelineStrip } from "@/components/live-product/LivePipelineStrip";
import { LiveProcessLiveLogCard } from "@/components/live-product/LiveProcessLiveLogCard";
import {
  defaultPm2NameForChannel,
  fetchYoutubeProChannelsRemote,
  loadYoutubeProChannels,
  newChannelId,
  pushYoutubeProChannelsRemote,
  resolveYoutubeProChannelPm2,
  saveYoutubeProChannels,
  sanitizeYoutubePm2Name,
  type YoutubeProChannel,
} from "@/lib/youtubeProChannels";
import { normalizeBrowserReachableHttpUrl } from "@/lib/panelUrls";

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

type TFn = (zh: string, en: string) => string;

function isYoutubeProcess(e: ProcessEntry) {
  return /youtube/i.test(e.script) || /youtube/i.test(e.pm2);
}

function scriptStem(script: string): string {
  const base = script.split("/").pop() || script;
  return base.replace(/\.[^.]+$/, "");
}

function isStaticYoutubeControlEntry(entry: ProcessEntry): boolean {
  return isYoutubeProcess(entry) && entry.pm2.trim() === scriptStem(entry.script).trim();
}

function singleModeProcessLabel(entry: ProcessEntry, t: TFn): string {
  const stem = scriptStem(entry.script).toLowerCase();
  if (stem.startsWith("douyin_youtube")) return t("抖音→YouTube", "Douyin → YouTube");
  if (stem.startsWith("youtube")) return t("YouTube", "YouTube");
  return entry.label;
}

function preferredSingleProcessPm2(entries: ProcessEntry[]): string {
  const exact = entries.find((entry) => entry.pm2.trim().toLowerCase() === "youtube");
  if (exact) return exact.pm2;
  const byScript = entries.find((entry) => scriptStem(entry.script).trim().toLowerCase() === "youtube");
  if (byScript) return byScript.pm2;
  return entries[0]?.pm2 ?? "";
}

function isYoutubeRow(row: LiveProcRow | undefined): boolean {
  if (!row) return false;
  return /youtube/i.test(row.pm2) || /youtube/i.test(row.script ?? "");
}

function singleProcessPriority(entry: ProcessEntry, row: LiveProcRow | undefined): number {
  const business = (row?.business_status || "").trim().toLowerCase();
  if (row?.is_pushing || business === "streaming") return 0;
  if (pm2StatusOnline(row?.process_status)) return 1;
  if (entry.pm2.trim().toLowerCase() === "youtube") return 2;
  if (scriptStem(entry.script).trim().toLowerCase() === "youtube") return 3;
  return 10;
}

function buildSingleProcessOptions(entries: ProcessEntry[], liveRows: LiveProcRow[]): ProcessEntry[] {
  const youtubeEntries = entries.filter(isYoutubeProcess);
  const staticYoutubeEntries = youtubeEntries.filter((entry) => isStaticYoutubeControlEntry(entry));
  const base = staticYoutubeEntries.length
    ? [...staticYoutubeEntries]
    : youtubeEntries.length
      ? [...youtubeEntries]
      : [...entries];

  const seen = new Set(base.map((entry) => entry.pm2.trim()));
  for (const row of liveRows.filter(isYoutubeRow)) {
    const pm2 = row.pm2.trim();
    if (!pm2 || seen.has(pm2)) continue;
    const business = (row.business_status || "").trim().toLowerCase();
    if (!row.is_pushing && business !== "streaming" && !pm2StatusOnline(row.process_status)) continue;
    const matched = youtubeEntries.find((entry) => entry.pm2.trim() === pm2);
    base.push(
      matched ?? {
        pm2,
        script: row.script ?? youtubeEntries[0]?.script ?? "youtube.py",
        label: row.label ?? pm2,
      },
    );
    seen.add(pm2);
  }
  return base;
}

function preferredSingleProcessPm2FromRuntime(entries: ProcessEntry[], liveRows: LiveProcRow[]): string {
  if (!entries.length) return "";
  const rowMap = new Map(
    liveRows
      .filter(isYoutubeRow)
      .map((row) => [row.pm2.trim(), row] as const),
  );
  let best = entries[0];
  let bestScore = singleProcessPriority(best, rowMap.get(best.pm2.trim()));
  for (const entry of entries.slice(1)) {
    const score = singleProcessPriority(entry, rowMap.get(entry.pm2.trim()));
    if (score < bestScore) {
      best = entry;
      bestScore = score;
    }
  }
  return best.pm2;
}

function resolveChannelPm2(channel: YoutubeProChannel): string {
  return resolveYoutubeProChannelPm2(channel);
}

const MODE_KEY = "live-hub-youtube-mode-v1";
const PRO_PM2_RESERVED = new Set(["tiktok", "obs", "web"]);

/** UI 暂时隐藏「多频道 Pro」切换（DOM 与逻辑保留，改 false 即可恢复） */
const HIDE_YOUTUBE_MULTI_PRO_TOGGLE = false;

type QuickLinkMap = Record<string, string>;
type ProLaneDraft = {
  name: string;
  streamKey: string;
  pm2Name: string;
  notes: string;
};

function laneDraftFromChannel(channel: YoutubeProChannel): ProLaneDraft {
  return {
    name: channel.name,
    streamKey: channel.streamKey ?? "",
    pm2Name: channel.pm2Name ?? defaultPm2NameForChannel(channel.id),
    notes: channel.notes ?? "",
  };
}

type LiveProcRow = {
  pm2: string;
  script?: string;
  label?: string;
  process_status?: string;
  business_status?: string;
  business_note?: string;
  is_pushing?: boolean;
};

function pm2StatusOnline(st?: string) {
  return /^(online|running)$/i.test((st || "").trim());
}

function businessStatusLabel(row: LiveProcRow | undefined, t: TFn): string {
  const business = (row?.business_status || "").trim().toLowerCase();
  if (business === "ready") return t("已配置", "Ready");
  if (business === "streaming") return t("推流中", "Pushing");
  if (business === "waiting_source") return t("等待上游", "Waiting source");
  if (business === "source_live") return t("源已开播，转推未起", "Source live, relay down");
  if (business === "monitoring") return t("监控中", "Monitoring");
  if (business === "misconfigured") return t("密钥异常", "Key invalid");
  if (business === "stale") return t("心跳过期", "Stale");
  if (business === "error") return t("异常", "Error");
  if (pm2StatusOnline(row?.process_status)) return t("运行中", "Running");
  return (row?.process_status || "").trim() || t("未运行", "Idle");
}

function businessStatusBadgeClass(row: LiveProcRow | undefined): string {
  const business = (row?.business_status || "").trim().toLowerCase();
  if (business === "ready") return "bg-sky-500/15 text-sky-100 ring-sky-500/25";
  if (business === "streaming") return "bg-emerald-500/15 text-emerald-200 ring-emerald-500/30";
  if (business === "waiting_source" || business === "monitoring") {
    return "bg-amber-500/15 text-amber-100 ring-amber-500/25";
  }
  if (business === "source_live") return "bg-sky-500/15 text-sky-100 ring-sky-500/25";
  if (business === "misconfigured" || business === "error" || business === "stale") {
    return "bg-rose-500/15 text-rose-100 ring-rose-500/25";
  }
  if (pm2StatusOnline(row?.process_status)) return "bg-emerald-500/15 text-emerald-200 ring-emerald-500/30";
  return "bg-zinc-600/20 text-zinc-400 ring-zinc-500/25";
}

function nonCommentUrlLinesOf(text: string): string[] {
  return text
    .split(/\r?\n/)
    .map((line) => line.trim())
    .filter((line) => line && !line.startsWith("#"));
}

function shouldPreserveLocalUrlDraft(localText: string, remoteText: string): boolean {
  return nonCommentUrlLinesOf(localText).length > 0 && nonCommentUrlLinesOf(remoteText).length === 0;
}

function parseProcBusy(busy: string | null): { action: string; pm2: string } | null {
  if (!busy?.startsWith("proc:")) return null;
  const rest = busy.slice(5);
  const idx = rest.indexOf(":");
  if (idx <= 0) return null;
  const action = rest.slice(0, idx);
  const pm2 = rest.slice(idx + 1);
  if (!pm2 || !["start", "stop", "restart"].includes(action)) return null;
  return { action, pm2 };
}

function LiveStreamActionBtn({
  kind,
  busyKind,
  targetPm2,
  busy,
  online,
  label,
  className,
  baseLock,
  onRun,
  notify,
  t,
  compact,
}: {
  kind: "start" | "stop" | "restart";
  busyKind?: "start" | "stop" | "restart";
  targetPm2: string;
  busy: string | null;
  online: boolean;
  label: string;
  className: string;
  baseLock: boolean;
  onRun: () => void;
  notify: (s: string) => void;
  t: TFn;
  compact?: boolean;
}) {
  const pb = parseProcBusy(busy);
  const forThisProc = pb && pb.pm2 === targetPm2;
  const actionForBusy = busyKind ?? kind;
  const loading = !!(forThisProc && pb.action === actionForBusy);
  const siblingBusy = !!(forThisProc && pb.action !== actionForBusy);
  const blockStart = kind === "start" && online;
  const blockStop = kind === "stop" && !online;
  const blockRestart = kind === "restart" && !online;
  const blocked = blockStart || blockStop || blockRestart;
  const hardLock = baseLock || loading || siblingBusy;

  const onClick = () => {
    if (loading) return;
    if (siblingBusy) {
      notify(t("请等待当前操作完成", "Wait for the current action to finish"));
      return;
    }
    if (blockStart) {
      notify(t("已在直播中，无需重复开始", "Already live"));
      return;
    }
    if (blockStop) {
      notify(t("当前未在直播，无法停止", "Not live — cannot stop"));
      return;
    }
    if (blockRestart) {
      notify(t("进程未运行时请使用「开始直播」", "Use Start when the process is not running"));
      return;
    }
    if (baseLock) return;
    onRun();
  };

  const sizeCls = compact
    ? "min-h-[2rem] px-3 py-1.5 text-xs font-medium"
    : "min-h-[2.75rem] px-4 py-3 text-sm font-medium";

  return (
    <button
      type="button"
      onClick={onClick}
      className={`relative overflow-hidden rounded-xl ring-1 transition ${sizeCls} ${className} ${
        blocked || hardLock ? "cursor-not-allowed" : ""
      } ${blocked && !loading ? "opacity-45" : ""} ${siblingBusy ? "opacity-35" : ""} ${loading ? "ring-violet-400/40" : ""}`}
    >
      {loading ? (
        <span className="absolute inset-0 flex items-center justify-center bg-black/25 backdrop-blur-[2px]">
          <Loader2 className={`${compact ? "h-4 w-4" : "h-6 w-6"} animate-spin text-white`} />
        </span>
      ) : null}
      <span className={`inline-flex items-center justify-center gap-1.5 ${loading ? "invisible" : ""}`}>
        {blocked ? <Ban className={`${compact ? "h-3 w-3" : "h-4 w-4"} shrink-0 opacity-90`} aria-hidden /> : null}
        {label}
      </span>
    </button>
  );
}

type Props = {
  t: TFn;
  busy: string | null;
  entries: ProcessEntry[];
  currentProc: string;
  setCurrentProc: (v: string) => void;
  urlConfig: string;
  setUrlConfig: (v: string) => void;
  onRunProcess: (a: "start" | "stop" | "restart", processPm2?: string) => void;
  onSaveUrlConfig: (content?: string, processPm2?: string) => void | Promise<void>;
  fetchJson: <T,>(path: string, init?: RequestInit) => Promise<T>;
  liveProcesses: LiveProcRow[];
  notify: (msg: string) => void;
  onNavigate?: (target: PortalNavTarget) => void;
  quickLinks?: QuickLinkMap;
};

export function LiveYoutubeUnmannedView({
  t,
  busy,
  entries,
  currentProc,
  setCurrentProc,
  urlConfig,
  setUrlConfig,
  onRunProcess,
  onSaveUrlConfig,
  fetchJson,
  liveProcesses,
  notify,
  onNavigate,
  quickLinks,
}: Props) {
  const ytEntries = useMemo(() => entries.filter(isYoutubeProcess), [entries]);
  const staticYoutubeEntries = useMemo(() => ytEntries.filter((entry) => isStaticYoutubeControlEntry(entry)), [ytEntries]);
  const singleProcessOptions = useMemo(
    () => buildSingleProcessOptions(ytEntries.length ? ytEntries : entries, liveProcesses),
    [entries, liveProcesses, ytEntries],
  );
  const preferredSingleProc = useMemo(
    () => preferredSingleProcessPm2FromRuntime(singleProcessOptions, liveProcesses) || preferredSingleProcessPm2(singleProcessOptions),
    [liveProcesses, singleProcessOptions],
  );

  const [mode, setMode] = useState<"single" | "pro">("single");
  const [channels, setChannels] = useState<YoutubeProChannel[]>([]);
  const [activeCh, setActiveCh] = useState<string>("");
  const [youtubeIniText, setYoutubeIniText] = useState("");
  const [ytFields, setYtFields] = useState<YoutubeIniFields>({
    key: "",
    rtmps: true,
    bitrate: "",
    fastAudio: false,
  });
  const [ytIniStatus, setYtIniStatus] = useState<string | null>(null);
  const [ytIniLoading, setYtIniLoading] = useState(false);
  const [proUrlDraft, setProUrlDraft] = useState("");
  const [urlAutoRefresh, setUrlAutoRefresh] = useState(true);
  const [urlReloading, setUrlReloading] = useState(false);
  const [urlOptimizing, setUrlOptimizing] = useState(false);
  const [urlFetchNote, setUrlFetchNote] = useState<string | null>(null);
  const [laneDrafts, setLaneDrafts] = useState<Record<string, ProLaneDraft>>({});
  const [laneSaving, setLaneSaving] = useState(false);
  const [laneStatus, setLaneStatus] = useState<string | null>(null);
  const [activeViewRefreshToken, setActiveViewRefreshToken] = useState(0);
  const urlListDirtyRef = useRef(false);
  const singleUrlDraftRef = useRef(urlConfig);
  const proUrlDraftRef = useRef(proUrlDraft);
  const currentProcRef = useRef(currentProc);
  const urlRequestSeqRef = useRef(0);
  const ytIniRequestSeqRef = useRef(0);

  const applySingleUrlDraft = useCallback(
    (next: string) => {
      singleUrlDraftRef.current = next;
      setUrlConfig(next);
    },
    [setUrlConfig],
  );

  const applyProUrlDraft = useCallback((next: string) => {
    proUrlDraftRef.current = next;
    setProUrlDraft(next);
  }, []);

  useEffect(() => {
    urlListDirtyRef.current = false;
  }, [currentProc]);

  useEffect(() => {
    currentProcRef.current = currentProc;
  }, [currentProc]);

  useEffect(() => {
    singleUrlDraftRef.current = urlConfig;
  }, [urlConfig]);

  useEffect(() => {
    proUrlDraftRef.current = proUrlDraft;
  }, [proUrlDraft]);

  const pullUrlConfigFromServer = useCallback(async () => {
    const targetProc = currentProcRef.current.trim();
    if (!targetProc) return;
    const requestId = ++urlRequestSeqRef.current;
    try {
      const data = await fetchJson<{ content?: string }>(
        `/get_url_config?process=${encodeURIComponent(targetProc)}`,
      );
      if (requestId !== urlRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
      const c = data.content ?? "";
      if (urlListDirtyRef.current) return;
      if (mode === "single") applySingleUrlDraft(c);
      else {
        const localDraft = channels.find((row) => row.id === activeCh)?.urlLines ?? "";
        if (shouldPreserveLocalUrlDraft(localDraft, c)) {
          applyProUrlDraft(localDraft);
          return;
        }
        applyProUrlDraft(c);
        if (activeCh) {
          setChannels((prev) => {
            const next = prev.map((x) => (x.id === activeCh ? { ...x, urlLines: c } : x));
            saveYoutubeProChannels(next);
            return next;
          });
        }
      }
    } catch {
      /* ignore */
    }
  }, [activeCh, applyProUrlDraft, applySingleUrlDraft, channels, fetchJson, mode]);

  useEffect(() => {
    if (!currentProc || !urlAutoRefresh) return;
    const id = window.setInterval(() => {
      if (urlListDirtyRef.current) return;
      void pullUrlConfigFromServer();
    }, 8000);
    return () => clearInterval(id);
  }, [currentProc, urlAutoRefresh, pullUrlConfigFromServer]);

  const reloadUrlManual = async () => {
    const targetProc = currentProcRef.current.trim();
    if (!targetProc) return;
    setUrlReloading(true);
    setUrlFetchNote(null);
    urlListDirtyRef.current = false;
    const requestId = ++urlRequestSeqRef.current;
    try {
      const data = await fetchJson<{ content?: string }>(
        `/get_url_config?process=${encodeURIComponent(targetProc)}`,
      );
      if (requestId !== urlRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
      const c = data.content ?? "";
      if (urlListDirtyRef.current) {
        setUrlFetchNote(t("检测到本地未保存修改，已保留当前草稿", "Kept the local unsaved draft"));
        return;
      }
      if (mode === "single") applySingleUrlDraft(c);
      else {
        const localDraft = channels.find((row) => row.id === activeCh)?.urlLines ?? "";
        if (shouldPreserveLocalUrlDraft(localDraft, c)) {
          applyProUrlDraft(localDraft);
          setUrlFetchNote(t("服务器暂无已保存 URL，已保留本地草稿", "Server has no saved URLs, kept the local draft"));
          return;
        }
        applyProUrlDraft(c);
        if (activeCh) {
          setChannels((prev) => {
            const next = prev.map((x) => (x.id === activeCh ? { ...x, urlLines: c } : x));
            saveYoutubeProChannels(next);
            return next;
          });
        }
      }
      setUrlFetchNote(t("已从服务器读取 URL_config", "Reloaded URL_config from server"));
    } catch (e) {
      if (requestId !== urlRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
      setUrlFetchNote((e as Error).message);
    } finally {
      if (requestId === urlRequestSeqRef.current && targetProc === currentProcRef.current.trim()) {
        setUrlReloading(false);
      }
    }
  };

  useEffect(() => {
    try {
      if (HIDE_YOUTUBE_MULTI_PRO_TOGGLE) {
        setMode("single");
        return;
      }
      const m = window.localStorage.getItem(MODE_KEY);
      if (m === "pro" || m === "single") setMode(m);
    } catch {
      /* ignore */
    }
  }, []);

  useEffect(() => {
    let cancelled = false;
    void (async () => {
      try {
        const remote = await fetchYoutubeProChannelsRemote(fetchJson);
        if (remote.fetched && !cancelled) {
          setChannels(remote.channels);
          saveYoutubeProChannels(remote.channels);
          return;
        }
      } catch {
        /* 使用本机缓存 */
      }
      if (cancelled) return;
      const list = loadYoutubeProChannels();
      const initial =
        list.length > 0
          ? list
          : (() => {
              const id = newChannelId();
              return [
                {
                  id,
                  name: "线路 1",
                  streamKey: "",
                  urlLines: "",
                  pm2Name: defaultPm2NameForChannel(id),
                  notes: "",
                },
              ];
            })();
      setChannels(initial);
      if (!list.length) saveYoutubeProChannels(initial);
    })();
    return () => {
      cancelled = true;
    };
  }, [fetchJson]);

  useEffect(() => {
    if (channels.length && !activeCh) setActiveCh(channels[0].id);
  }, [channels, activeCh]);

  const rowForPm2 = useCallback((pm2: string) => liveProcesses.find((p) => p.pm2 === pm2), [liveProcesses]);
  const statusForPm2 = useCallback(
    (pm2: string) => rowForPm2(pm2)?.process_status,
    [rowForPm2],
  );

  useEffect(() => {
    if (mode !== "single" || !preferredSingleProc) return;
    if (!singleProcessOptions.some((entry) => entry.pm2 === currentProc)) {
      setCurrentProc(preferredSingleProc);
      return;
    }
    const currentEntry = singleProcessOptions.find((entry) => entry.pm2 === currentProc);
    const currentRow = rowForPm2(currentProc);
    const preferredEntry = singleProcessOptions.find((entry) => entry.pm2 === preferredSingleProc);
    const preferredRow = rowForPm2(preferredSingleProc);
    if (
      currentProc !== preferredSingleProc &&
      currentEntry &&
      preferredEntry &&
      singleProcessPriority(preferredEntry, preferredRow) < singleProcessPriority(currentEntry, currentRow)
    ) {
      setCurrentProc(preferredSingleProc);
    }
  }, [currentProc, mode, preferredSingleProc, rowForPm2, setCurrentProc, singleProcessOptions]);

  const applyChannelsLocal = useCallback((next: YoutubeProChannel[]) => {
    setChannels(next);
    saveYoutubeProChannels(next);
  }, []);

  const syncChannelsRemote = useCallback(
    async (next: YoutubeProChannel[]) => {
      await pushYoutubeProChannelsRemote(fetchJson, next);
    },
    [fetchJson],
  );

  const persistChannels = useCallback(
    (next: YoutubeProChannel[]) => {
      applyChannelsLocal(next);
      void syncChannelsRemote(next).catch((e) => {
        notify(
          `${t("多频道列表未同步到服务器", "Channel list not saved on server")}: ${(e as Error).message}`,
        );
      });
    },
    [applyChannelsLocal, notify, syncChannelsRemote, t],
  );

  const active = channels.find((c) => c.id === activeCh) || channels[0];
  const activeLaneDraft = useMemo(
    () => (active ? laneDrafts[active.id] ?? laneDraftFromChannel(active) : null),
    [active, laneDrafts],
  );

  useEffect(() => {
    if (!active) return;
    setLaneDrafts((prev) => {
      if (prev[active.id]) return prev;
      return { ...prev, [active.id]: laneDraftFromChannel(active) };
    });
  }, [active]);

  useEffect(() => {
    setLaneStatus(null);
  }, [activeCh]);

  const setLaneDraftField = useCallback(
    (key: keyof ProLaneDraft, value: string) => {
      if (!active) return;
      setLaneDrafts((prev) => ({
        ...prev,
        [active.id]: {
          ...(prev[active.id] ?? laneDraftFromChannel(active)),
          [key]: value,
        },
      }));
      setLaneStatus(null);
    },
    [active],
  );

  const activeSavedPm2 = active ? resolveChannelPm2(active) : "";
  const activeDraftPm2 = active && activeLaneDraft
    ? resolveYoutubeProChannelPm2({ id: active.id, pm2Name: activeLaneDraft.pm2Name })
    : "";
  const activePm2PreviewChanged = !!active && !!activeLaneDraft && activeDraftPm2 !== activeSavedPm2;

  const activeLaneValidation = useMemo(() => {
    if (!active || !activeLaneDraft) {
      return {
        ok: false,
        message: t("请选择线路", "Pick a lane"),
        normalizedName: "",
        normalizedPm2: "",
        normalizedStreamKey: "",
        normalizedNotes: "",
      };
    }
    const normalizedName = activeLaneDraft.name.trim() || `${t("线路", "Ch")} ${channels.findIndex((row) => row.id === active.id) + 1}`;
    const normalizedPm2 = resolveYoutubeProChannelPm2({ id: active.id, pm2Name: activeLaneDraft.pm2Name });
    if (!normalizedPm2) {
      return {
        ok: false,
        message: t("进程名不能为空", "PM2 name is required"),
        normalizedName,
        normalizedPm2,
        normalizedStreamKey: activeLaneDraft.streamKey.trim(),
        normalizedNotes: activeLaneDraft.notes.trim(),
      };
    }
    if (PRO_PM2_RESERVED.has(normalizedPm2)) {
      return {
        ok: false,
        message: t("该进程名保留给系统模块，请换一个", "This PM2 name is reserved"),
        normalizedName,
        normalizedPm2,
        normalizedStreamKey: activeLaneDraft.streamKey.trim(),
        normalizedNotes: activeLaneDraft.notes.trim(),
      };
    }
    const duplicate = channels.find((row) => row.id !== active.id && resolveChannelPm2(row) === normalizedPm2);
    if (duplicate) {
      return {
        ok: false,
        message: t("该进程名已被其它线路占用", "Another lane already uses this PM2 name"),
        normalizedName,
        normalizedPm2,
        normalizedStreamKey: activeLaneDraft.streamKey.trim(),
        normalizedNotes: activeLaneDraft.notes.trim(),
      };
    }
    return {
      ok: true,
      message: "",
      normalizedName,
      normalizedPm2,
      normalizedStreamKey: activeLaneDraft.streamKey.trim(),
      normalizedNotes: activeLaneDraft.notes.trim(),
    };
  }, [active, activeLaneDraft, channels, t]);

  const activeLaneDirty = useMemo(() => {
    if (!active || !activeLaneDraft) return false;
    const savedDraft = laneDraftFromChannel(active);
    return (
      activeLaneDraft.name !== savedDraft.name ||
      activeLaneDraft.streamKey !== savedDraft.streamKey ||
      sanitizeYoutubePm2Name(activeLaneDraft.pm2Name) !== sanitizeYoutubePm2Name(savedDraft.pm2Name) ||
      activeLaneDraft.notes !== savedDraft.notes
    );
  }, [active, activeLaneDraft]);

  const setModePersist = useCallback(
    (m: "single" | "pro") => {
      if (m === "single" && mode === "pro" && active) {
        const latestProUrlDraft = proUrlDraftRef.current;
        if (latestProUrlDraft !== (active.urlLines ?? "").trim()) {
          persistChannels(channels.map((x) => (x.id === active.id ? { ...x, urlLines: latestProUrlDraft } : x)));
        }
      }
      if (m === "pro" && channels.length) {
        const first = channels[0];
        const latestSingleUrlDraft = singleUrlDraftRef.current.trim();
        const youtubePm2Ok = staticYoutubeEntries.some((e) => e.pm2 === currentProc);
        const pm2Name =
          youtubePm2Ok && currentProc.trim()
            ? currentProc.trim()
            : resolveChannelPm2(first);
        const next: YoutubeProChannel[] = [
          {
            ...first,
            streamKey: ytFields.key.trim() || first.streamKey || "",
            urlLines: latestSingleUrlDraft || first.urlLines || "",
            pm2Name,
          },
          ...channels.slice(1),
        ];
        persistChannels(next);
        const ch = next.find((c) => c.id === activeCh) ?? next[0];
        applyProUrlDraft((ch?.urlLines ?? "").trim() || latestSingleUrlDraft);
      }
      setMode(m);
      try {
        window.localStorage.setItem(MODE_KEY, m);
      } catch {
        /* ignore */
      }
    },
    [
      mode,
      active,
      channels,
      applyProUrlDraft,
      ytFields.key,
      currentProc,
      staticYoutubeEntries,
      activeCh,
      persistChannels,
    ],
  );

  const selectProChannel = useCallback(
    (c: YoutubeProChannel) => {
      const latestProUrlDraft = proUrlDraftRef.current;
      if (active && mode === "pro" && latestProUrlDraft !== (active.urlLines ?? "").trim()) {
        persistChannels(channels.map((x) => (x.id === active.id ? { ...x, urlLines: latestProUrlDraft } : x)));
      }
      const u = (c.urlLines ?? "").trim();
      applyProUrlDraft(u);
      setActiveCh(c.id);
      setCurrentProc(resolveChannelPm2(c));
    },
    [active, applyProUrlDraft, mode, channels, setCurrentProc, persistChannels],
  );

  useEffect(() => {
    if (mode !== "pro" || !activeCh) return;
    const ch = channels.find((x) => x.id === activeCh);
    if (!ch) return;
    const want = resolveChannelPm2(ch);
    setCurrentProc(want);
  }, [mode, activeCh, channels, setCurrentProc]);

  const loadYoutubeIni = useCallback(async () => {
    const targetProc = currentProcRef.current.trim();
    if (!targetProc) return;
    const requestId = ++ytIniRequestSeqRef.current;
    setYtIniLoading(true);
    setYtIniStatus(null);
    try {
      const data = await fetchJson<{ content?: string }>(
        `/get_config?process=${encodeURIComponent(targetProc)}`,
      );
      if (requestId !== ytIniRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
      const text = data.content ?? "";
      setYoutubeIniText(text);
      setYtFields(parseYoutubeIniFields(text));
    } catch (e) {
      if (requestId !== ytIniRequestSeqRef.current || targetProc !== currentProcRef.current.trim()) return;
      setYtIniStatus((e as Error).message);
    } finally {
      if (requestId === ytIniRequestSeqRef.current && targetProc === currentProcRef.current.trim()) {
        setYtIniLoading(false);
      }
    }
  }, [fetchJson]);

  useEffect(() => {
    if (!currentProc) return;
    void pullUrlConfigFromServer();
    void loadYoutubeIni();
    setActiveViewRefreshToken((prev) => prev + 1);
  }, [activeCh, currentProc, loadYoutubeIni, mode, pullUrlConfigFromServer]);

  const persistActiveLaneDraft = useCallback(
    async () => {
      if (!active || !activeLaneDraft) {
        throw new Error(t("请选择线路", "Pick a lane"));
      }
      if (!activeLaneValidation.ok) {
        throw new Error(activeLaneValidation.message);
      }
      const latestProUrlDraft = proUrlDraftRef.current;
      const next = channels.map((row) =>
        row.id === active.id
          ? {
              ...row,
              name: activeLaneValidation.normalizedName,
              streamKey: activeLaneValidation.normalizedStreamKey,
              pm2Name: activeLaneValidation.normalizedPm2,
              notes: activeLaneValidation.normalizedNotes,
              urlLines: latestProUrlDraft,
            }
          : row,
      );
      applyChannelsLocal(next);
      await syncChannelsRemote(next);
      setLaneDrafts((prev) => ({
        ...prev,
        [active.id]: {
          name: activeLaneValidation.normalizedName,
          streamKey: activeLaneValidation.normalizedStreamKey,
          pm2Name: activeLaneValidation.normalizedPm2,
          notes: activeLaneValidation.normalizedNotes,
        },
      }));
      return {
        next,
        savedLane:
          next.find((row) => row.id === active.id) ??
          ({
            ...active,
            name: activeLaneValidation.normalizedName,
            streamKey: activeLaneValidation.normalizedStreamKey,
            pm2Name: activeLaneValidation.normalizedPm2,
            notes: activeLaneValidation.normalizedNotes,
            urlLines: latestProUrlDraft,
          } satisfies YoutubeProChannel),
        pm2: activeLaneValidation.normalizedPm2,
      };
    },
    [
      active,
      activeLaneDraft,
      activeLaneValidation,
      applyChannelsLocal,
      channels,
      syncChannelsRemote,
      t,
    ],
  );

  const saveProLaneSettings = useCallback(async () => {
    if (!active) return;
    setLaneSaving(true);
    setLaneStatus(null);
    try {
      const saved = await persistActiveLaneDraft();
      setCurrentProc(saved.pm2);
      setLaneStatus(t("线路设置已保存", "Lane settings saved"));
    } catch (e) {
      const message = (e as Error).message;
      setLaneStatus(message);
      notify(message);
    } finally {
      setLaneSaving(false);
    }
  }, [active, notify, persistActiveLaneDraft, setCurrentProc, t]);

  const saveYoutubeIniToServer = async (content: string, processPm2 = currentProc) => {
    const targetPm2 = processPm2.trim();
    if (!targetPm2) {
      setYtIniStatus(t("请选择进程", "Pick a process"));
      return;
    }
    setYtIniLoading(true);
    setYtIniStatus(null);
    try {
      const params = new URLSearchParams({ process: targetPm2 });
      await fetchJson(`/save_config?${params}`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ content }),
      });
      setYoutubeIniText(content);
      setYtIniStatus(t("youtube.ini 已保存", "youtube.ini saved"));
    } catch (e) {
      setYtIniStatus((e as Error).message);
    } finally {
      setYtIniLoading(false);
    }
  };

  const applyFieldsAndSave = async () => {
    const body = buildYoutubeIniFromFields(ytFields);
    await saveYoutubeIniToServer(body);
  };

  const applyYoutubeBalancedPreset = async () => {
    setYtIniLoading(true);
    setYtIniStatus(null);
    try {
      const source = youtubeIniText || buildYoutubeIniFromFields(ytFields);
      const data = await fetchJson<{ message?: string; content?: string }>("/config_optimize", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          root: "app-config",
          path: managedYoutubeIniRelPathForPm2(currentProc),
          content: source,
          action: "youtube_balanced_preset",
        }),
      });
      const next = data.content ?? source;
      setYoutubeIniText(next);
      setYtFields(parseYoutubeIniFields(next));
      setYtIniStatus(data.message || t("已应用 YouTube 预设", "Applied YouTube preset"));
    } catch (e) {
      setYtIniStatus((e as Error).message);
    } finally {
      setYtIniLoading(false);
    }
  };

  const syncFieldsFromText = () => {
    setYtFields(parseYoutubeIniFields(youtubeIniText));
    setYtIniStatus(t("已从下方原文同步到表单", "Synced from raw"));
  };

  const keySuffixUi = useMemo(() => {
    if (mode === "single") return parseYoutubeStreamKeySuffix(`key = ${ytFields.key}`);
    const k = activeLaneDraft?.streamKey ?? active?.streamKey ?? "";
    return parseYoutubeStreamKeySuffix(`key = ${k}`);
  }, [mode, ytFields.key, active?.streamKey, activeLaneDraft?.streamKey]);

  const saveProUrlAndServer = async () => {
    if (!active) return;
    try {
      setLaneSaving(true);
      setLaneStatus(null);
      const latestProUrlDraft = proUrlDraftRef.current;
      const saved = await persistActiveLaneDraft();
      setCurrentProc(saved.pm2);
      await Promise.resolve(onSaveUrlConfig(latestProUrlDraft, saved.pm2));
      urlListDirtyRef.current = false;
      setLaneStatus(t("线路与地址已保存", "Lane settings and URLs saved"));
    } catch (e) {
      const message = e instanceof Error ? e.message : String(e);
      setLaneStatus(message);
      notify(message);
    } finally {
      setLaneSaving(false);
    }
  };

  const saveSingleUrlAndClearDirty = async () => {
    const targetPm2 = currentProc.trim();
    if (!targetPm2) {
      notify(t("请选择进程", "Pick a process"));
      return;
    }
    try {
      await Promise.resolve(onSaveUrlConfig(singleUrlDraftRef.current, targetPm2));
      urlListDirtyRef.current = false;
    } catch {
      /* parent 已 toast */
    }
  };

  const dedupeUrlDraft = async () => {
    const source = mode === "single" ? singleUrlDraftRef.current : proUrlDraftRef.current;
    setUrlOptimizing(true);
    setUrlFetchNote(null);
    try {
      const data = await fetchJson<{ message?: string; content?: string }>("/config_optimize", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          root: "app-config",
          path: managedUrlConfigRelPathForPm2(currentProc),
          content: source,
          action: "dedupe_urls",
        }),
      });
      const next = data.content ?? source;
      if (mode === "single") applySingleUrlDraft(next);
      else {
        applyProUrlDraft(next);
      }
      urlListDirtyRef.current = true;
      setUrlFetchNote(data.message || t("已去重当前 URL 列表", "Deduplicated current URL list"));
    } catch (e) {
      setUrlFetchNote((e as Error).message);
    } finally {
      setUrlOptimizing(false);
    }
  };

  const pushProStreamKeyToServer = async () => {
    if (!active || !activeLaneDraft) return;
    const base = parseYoutubeIniFields(youtubeIniText || buildYoutubeIniFromFields(ytFields));
    const f: YoutubeIniFields = {
      key: activeLaneDraft.streamKey.trim(),
      rtmps: base.rtmps,
      bitrate: base.bitrate,
      fastAudio: base.fastAudio,
    };
    const body = buildYoutubeIniFromFields(f);
    setLaneSaving(true);
    setLaneStatus(null);
    try {
      const saved = await persistActiveLaneDraft();
      setCurrentProc(saved.pm2);
      await saveYoutubeIniToServer(body, saved.pm2);
      setLaneStatus(t("线路设置与 youtube.ini 已保存", "Lane settings and youtube.ini saved"));
    } catch (e) {
      const message = (e as Error).message;
      setLaneStatus(message);
      notify(message);
    } finally {
      setLaneSaving(false);
    }
  };

  const currentUrlDraft = mode === "single" ? urlConfig : proUrlDraft;
  const currentUrlCount = useMemo(() => nonCommentUrlLinesOf(currentUrlDraft).length, [currentUrlDraft]);
  const currentDouyinCount = useMemo(() => extractDouyinLiveUrls(currentUrlDraft).length, [currentUrlDraft]);
  const laneQuickLinks = useMemo(
    () =>
      [
        quickLinks?.neko_browser,
        quickLinks?.chromium_remote_debug,
        quickLinks?.srs_api,
      ].filter((value): value is string => !!value),
    [quickLinks],
  );

  const flushProChannelsBeforeRun = useCallback(
    async (targetPm2: string) => {
      const next = channels;
      const activePm2 = active ? resolveChannelPm2(active) : "";
      if (active && targetPm2 === activePm2) {
        const saved = await persistActiveLaneDraft();
        return saved;
      }
      applyChannelsLocal(next);
      await syncChannelsRemote(next);
      return {
        next,
        savedLane: active ?? null,
        pm2: targetPm2,
      };
    },
    [active, applyChannelsLocal, channels, persistActiveLaneDraft, syncChannelsRemote],
  );

  const runProProcess = useCallback(
    async (action: "start" | "stop" | "restart", targetPm2: string) => {
      const targetMatchesActiveLane = !!active && resolveChannelPm2(active) === targetPm2;
      if (action !== "stop") {
        let next = channels;
        let effectivePm2 = targetPm2;
        try {
          const saved = await flushProChannelsBeforeRun(targetPm2);
          next = saved.next;
          effectivePm2 = saved.pm2;
        } catch (e) {
          notify(
            `${t("当前线路保存失败", "Failed to save the active lane")}: ${(e as Error).message}`,
          );
          return;
        }
        const target = next.find(
          (row) => resolveChannelPm2(row) === effectivePm2,
        );
        const urlText = target?.urlLines ?? "";
        if (!((target?.streamKey ?? "").trim())) {
          notify(t("请先填写当前线路的推流密钥", "Fill the stream key before starting"));
          return;
        }
        if (!nonCommentUrlLinesOf(urlText).length) {
          notify(t("请先填写当前线路的直播地址", "Fill the source URLs before starting"));
          return;
        }
        targetPm2 = effectivePm2;
      }
      if (targetMatchesActiveLane) {
        setCurrentProc(targetPm2);
      }
      onRunProcess(action, targetPm2);
    },
    [active, channels, flushProChannelsBeforeRun, notify, onRunProcess, setCurrentProc, t],
  );

  const lock = !!busy || ytIniLoading || urlReloading || urlOptimizing || laneSaving;

  const singleRow = rowForPm2(currentProc);
  const singleOnline = pm2StatusOnline(statusForPm2(currentProc));

  const urlToolbar = (
    <div className="mt-2 flex flex-wrap items-center gap-4 text-xs text-zinc-400">
      <label className="flex cursor-pointer items-center gap-2">
        <input
          type="checkbox"
          className="rounded border-white/20 bg-black/40"
          checked={urlAutoRefresh}
          onChange={(e) => setUrlAutoRefresh(e.target.checked)}
        />
        {t("每 8 秒自动从服务器刷新", "Auto-refresh from server every 8s")}
      </label>
      <button
        type="button"
        disabled={lock}
        onClick={() => void reloadUrlManual()}
        className="rounded-lg border border-white/10 px-3 py-1.5 text-zinc-200 hover:bg-white/5"
      >
        {t("手动重新读取", "Reload from server")}
      </button>
      <button
        type="button"
        disabled={lock}
        onClick={() => void dedupeUrlDraft()}
        className="rounded-lg border border-cyan-500/20 bg-cyan-500/10 px-3 py-1.5 text-cyan-100 hover:bg-cyan-500/15"
      >
        {urlOptimizing ? t("去重中...", "Deduplicating...") : t("智能去重", "Deduplicate")}
      </button>
    </div>
  );

  return (
    <div className="mx-auto max-w-6xl space-y-6">
      {onNavigate ? (
        <LivePipelineStrip
          t={t}
          title={t("YouTube 转播链路", "YouTube relay chain")}
          subtitle={t(
            "与「网络」页相同风格的链路示意：代理 → 编码推流 → 浏览器侧操作工作室。",
            "Same visual language as Network: proxy → encode → browser studio.",
          )}
          onNavigate={onNavigate}
          steps={[
            {
              label: "ShellCrash",
              sub: t("透明代理", "Proxy"),
              gradient: "from-violet-500 to-fuchsia-500",
              target: "network",
            },
            {
              label: "FFmpeg",
              sub: t("推流 / 转码", "Stream"),
              gradient: "from-cyan-400 to-blue-500",
              target: "live_youtube",
            },
            {
              label: "Neko",
              sub: t("Chromium 工作室", "Chromium"),
              gradient: "from-emerald-400 to-teal-500",
              openUrl: normalizeBrowserReachableHttpUrl(quickLinks?.neko_browser || "http://live.local:9200/"),
            },
          ]}
        />
      ) : null}
      <div className="rounded-2xl border border-violet-500/25 bg-gradient-to-br from-violet-500/[0.12] via-zinc-950/80 to-cyan-500/5 p-6 shadow-xl shadow-violet-900/10">
        <div className="flex flex-wrap items-start justify-between gap-4">
          <div className="flex items-center gap-3">
            <div className="flex h-11 w-11 items-center justify-center rounded-xl bg-gradient-to-br from-red-500/30 to-violet-600/30 ring-1 ring-white/10">
              <MonitorPlay className="h-5 w-5 text-red-200" />
            </div>
            <div>
              <h2 className="text-lg font-semibold tracking-tight text-white">
                {t("YouTube 无人直播", "YouTube unmanned live")}
              </h2>
              <p className="mt-0.5 text-xs text-zinc-500">
                {t(
                  "单路一条流；多路时每条线路独立保存自己的密钥、URL 列表和 PM2 任务。填好后保存，再点「开始」。Neko 浏览器仅作辅助工作室，不再阻塞推流。",
                  "Single lane or multi-lane: every lane keeps its own key, URL list, and PM2 task. Save, then Start. Neko is optional browser tooling and no longer blocks streaming.",
                )}
              </p>
            </div>
          </div>
          <div
            className={`flex rounded-xl border border-white/10 bg-black/30 p-1 ${
              HIDE_YOUTUBE_MULTI_PRO_TOGGLE ? "hidden" : ""
            }`}
            aria-hidden={HIDE_YOUTUBE_MULTI_PRO_TOGGLE}
          >
            <button
              type="button"
              onClick={() => setModePersist("single")}
              className={`rounded-lg px-4 py-2 text-xs font-medium transition ${
                mode === "single" ? "bg-violet-500/25 text-white" : "text-zinc-500 hover:text-zinc-300"
              }`}
            >
              {t("单频道", "Single")}
            </button>
            <button
              type="button"
              onClick={() => setModePersist("pro")}
              className={`rounded-lg px-4 py-2 text-xs font-medium transition ${
                mode === "pro" ? "bg-violet-500/25 text-white" : "text-zinc-500 hover:text-zinc-300"
              }`}
            >
              {t("多频道 Pro", "Multi Pro")}
            </button>
          </div>
        </div>
      </div>

      {laneQuickLinks.length ? (
        <div className="flex flex-wrap gap-2">
          {quickLinks?.neko_browser ? (
            <a
              href={normalizeBrowserReachableHttpUrl(quickLinks.neko_browser)}
              target="_blank"
              rel="noreferrer"
              className="rounded-lg border border-emerald-500/20 bg-emerald-500/10 px-3 py-2 text-xs text-emerald-100 ring-1 ring-emerald-500/20"
            >
              {t("打开 Neko 工作室", "Open Neko studio")}
            </a>
          ) : null}
          {quickLinks?.chromium_remote_debug ? (
            <a
              href={normalizeBrowserReachableHttpUrl(quickLinks.chromium_remote_debug)}
              target="_blank"
              rel="noreferrer"
              className="rounded-lg border border-cyan-500/20 bg-cyan-500/10 px-3 py-2 text-xs text-cyan-100 ring-1 ring-cyan-500/20"
            >
              {t("打开 Chromium 调试", "Open Chromium debug")}
            </a>
          ) : null}
          {quickLinks?.srs_api ? (
            <a
              href={normalizeBrowserReachableHttpUrl(quickLinks.srs_api)}
              target="_blank"
              rel="noreferrer"
              className="rounded-lg border border-violet-500/20 bg-violet-500/10 px-3 py-2 text-xs text-violet-100 ring-1 ring-violet-500/20"
            >
              {t("打开 SRS API", "Open SRS API")}
            </a>
          ) : null}
        </div>
      ) : null}

      <div className="rounded-2xl border border-amber-500/20 bg-zinc-950/60 p-6 ring-1 ring-white/[0.04]">
        <div className="flex items-center gap-2 text-amber-200/90">
          <Radio className="h-4 w-4" />
          <h3 className="text-sm font-semibold text-white">{t("直播开关", "Live control")}</h3>
        </div>

        {mode === "single" ? (
          <>
            <label className="mt-4 block text-xs font-semibold uppercase tracking-wider text-zinc-500">
              {t("转播任务", "Relay task")}
            </label>
            <select
              className="mt-2 w-full rounded-xl border border-white/10 bg-black/40 px-4 py-3 text-sm text-white"
              value={currentProc}
              onChange={(e) => setCurrentProc(e.target.value)}
            >
              {singleProcessOptions.map((e) => (
                <option key={e.pm2} value={e.pm2}>
                  {singleModeProcessLabel(e, t)} ({e.pm2})
                </option>
              ))}
            </select>
            <div className="mt-4 grid grid-cols-2 gap-2 sm:grid-cols-3">
              <LiveStreamActionBtn
                kind="start"
                targetPm2={currentProc}
                busy={busy}
                online={singleOnline}
                label={t("开始直播", "Start")}
                className="bg-emerald-500/20 text-emerald-200 ring-emerald-500/30"
                baseLock={false}
                onRun={() => onRunProcess("start", currentProc)}
                notify={notify}
                t={t}
              />
              <LiveStreamActionBtn
                kind="stop"
                targetPm2={currentProc}
                busy={busy}
                online={singleOnline}
                label={t("停止直播", "Stop")}
                className="bg-rose-500/15 text-rose-200 ring-rose-500/25"
                baseLock={false}
                onRun={() => onRunProcess("stop", currentProc)}
                notify={notify}
                t={t}
              />
              <LiveStreamActionBtn
                kind="restart"
                targetPm2={currentProc}
                busy={busy}
                online={singleOnline}
                label={t("重新开始", "Restart")}
                className="bg-amber-500/15 text-amber-200 ring-amber-500/25"
                baseLock={false}
                onRun={() => onRunProcess("restart", currentProc)}
                notify={notify}
                t={t}
              />
            </div>
            <p className="mt-3 text-center text-xs text-zinc-400">
              {t("当前状态", "Current state")}: {businessStatusLabel(singleRow, t)}
            </p>
            <p className="mt-3 text-center font-mono text-[11px] text-zinc-600">
              key {t("尾码", "suffix")}: {keySuffixUi || "—"}
            </p>
          </>
        ) : (
          <>
            <div className="mt-4 rounded-xl border border-cyan-500/25 bg-gradient-to-br from-cyan-500/[0.1] via-zinc-950/50 to-violet-500/[0.06] p-4 ring-1 ring-white/[0.04]">
              <h4 className="text-xs font-semibold uppercase tracking-wider text-cyan-200/90">
                {t("Pro 转播实况", "Pro relay status")}
              </h4>
              <p className="mt-1 text-[11px] text-zinc-500">
                {t("对齐桌面端：每路独立 URL / youtube 配置文件；点左侧信息切换编辑，右侧控制进程。", "Per-lane config files like desktop; click row info to edit, use right-side controls for PM2.")}
              </p>
              <div className="mt-3 max-h-[min(24rem,55vh)] space-y-2 overflow-y-auto pr-1">
                {channels.map((c) => {
                  const pm = resolveChannelPm2(c);
                  const row = rowForPm2(pm);
                  const st = statusForPm2(pm);
                  const laneUrls = nonCommentUrlLinesOf((c.urlLines ?? "").trim());
                  const douyinUrls = extractDouyinLiveUrls((c.urlLines ?? "").trim());
                  const dUrl = douyinUrls[0];
                  const suf = parseYoutubeStreamKeySuffix(`key = ${c.streamKey ?? ""}`);
                  const sel = activeCh === c.id;
                  const rowOnline = pm2StatusOnline(st);
                  return (
                    <div
                      key={c.id}
                      className={`flex flex-wrap items-center gap-2 rounded-lg border px-3 py-2.5 text-left text-xs transition sm:flex-nowrap ${
                        sel ? "border-violet-500/45 bg-violet-500/[0.1]" : "border-white/10 bg-black/30 hover:border-white/18"
                      }`}
                    >
                      <button
                        type="button"
                        className="min-w-0 flex-1 text-left"
                        onClick={() => selectProChannel(c)}
                      >
                        <div className="flex flex-wrap items-center gap-2">
                          <span className="font-medium text-zinc-100">{c.name}</span>
                          <span
                            className={`shrink-0 rounded-md px-2 py-0.5 text-[10px] font-medium ring-1 ${
                              businessStatusBadgeClass(row)
                            }`}
                          >
                            {businessStatusLabel(row, t)}
                          </span>
                        </div>
                        <div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 font-mono text-[10px] text-zinc-500">
                          <span>
                            key …{suf || "—"}
                          </span>
                          <span className="text-zinc-600">·</span>
                          <span className="truncate" title={pm}>
                            {pm}
                          </span>
                          <span className="text-zinc-600">·</span>
                          <span>
                            URL {laneUrls.length}
                          </span>
                          <span className="text-zinc-600">·</span>
                          <span>
                            DY {douyinUrls.length}
                          </span>
                        </div>
                        {dUrl ? (
                          <a
                            href={dUrl}
                            target="_blank"
                            rel="noreferrer"
                            className="mt-1 block max-w-full truncate text-cyan-300/90 underline-offset-2 hover:underline"
                            onClick={(e) => e.stopPropagation()}
                            title={dUrl}
                          >
                            {dUrl.replace(/^https?:\/\//, "")}
                          </a>
                        ) : (
                          <span className="mt-1 block text-zinc-600">—</span>
                        )}
                      </button>
                      <div
                        className="flex w-full shrink-0 flex-wrap items-center justify-end gap-1.5 sm:w-auto"
                        onClick={(e) => e.stopPropagation()}
                        onKeyDown={(e) => e.stopPropagation()}
                      >
                        <LiveStreamActionBtn
                          kind="start"
                          targetPm2={pm}
                          busy={busy}
                          online={rowOnline}
                          label={t("开始", "Start")}
                          className="rounded-lg bg-emerald-500/20 text-emerald-100 ring-emerald-500/30"
                          baseLock={false}
                          onRun={() => void runProProcess("start", pm)}
                          notify={notify}
                          t={t}
                          compact
                        />
                        <LiveStreamActionBtn
                          kind="stop"
                          targetPm2={pm}
                          busy={busy}
                          online={rowOnline}
                          label={t("停止", "Stop")}
                          className="rounded-lg bg-rose-500/15 text-rose-100 ring-rose-500/25"
                          baseLock={false}
                          onRun={() => void runProProcess("stop", pm)}
                          notify={notify}
                          t={t}
                          compact
                        />
                        <LiveStreamActionBtn
                          kind="restart"
                          targetPm2={pm}
                          busy={busy}
                          online={rowOnline}
                          label={t("重新开始", "Restart")}
                          className="rounded-lg bg-amber-500/15 text-amber-100 ring-amber-500/25"
                          baseLock={false}
                          onRun={() => void runProProcess("restart", pm)}
                          notify={notify}
                          t={t}
                          compact
                        />
                      </div>
                    </div>
                  );
                })}
              </div>
            </div>
          </>
        )}
      </div>

      {mode === "single" ? (
        <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
          <h3 className="text-sm font-semibold text-white">{t("YouTube 相关设置", "YouTube settings")}</h3>
          <p className="mt-1 text-xs text-zinc-500">
            {t("串流参数写进 youtube.ini，无需 Google 登录。", "Stream settings go to youtube.ini — no Google login.")}
          </p>

          <div className="mt-4 space-y-3">
            <label className="block text-xs text-zinc-400">{t("串流密钥", "Stream key")}</label>
            <input
              className="w-full rounded-xl border border-white/10 bg-black/40 px-4 py-3 text-sm font-mono text-zinc-200"
              spellCheck={false}
              autoComplete="off"
              value={ytFields.key}
              onChange={(e) => setYtFields((f) => ({ ...f, key: e.target.value }))}
              placeholder="xxxx-xxxx-xxxx-xxxx-xxxx"
            />
            <div className="grid gap-3 sm:grid-cols-3">
              <label className="flex flex-col gap-1 text-xs text-zinc-400">
                RTMPS
                <select
                  className="rounded-lg border border-white/10 bg-black/40 px-3 py-2 text-sm text-white"
                  value={ytFields.rtmps ? "1" : "0"}
                  onChange={(e) => setYtFields((f) => ({ ...f, rtmps: e.target.value === "1" }))}
                >
                  <option value="1">{t("是（默认）", "Yes")}</option>
                  <option value="0">{t("否 → RTMP", "No")}</option>
                </select>
              </label>
              <label className="flex flex-col gap-1 text-xs text-zinc-400">
                {t("比特率 kbps", "Bitrate kbps")}
                <input
                  className="rounded-lg border border-white/10 bg-black/40 px-3 py-2 font-mono text-sm"
                  value={ytFields.bitrate}
                  onChange={(e) => setYtFields((f) => ({ ...f, bitrate: e.target.value }))}
                  placeholder="4000"
                />
              </label>
              <label className="flex cursor-pointer items-center gap-2 pt-6 text-xs text-zinc-300">
                <input
                  type="checkbox"
                  className="rounded border-white/20 bg-black/40"
                  checked={ytFields.fastAudio}
                  onChange={(e) => setYtFields((f) => ({ ...f, fastAudio: e.target.checked }))}
                />
                fast_audio（{t("轻量音频", "lighter audio")}）
              </label>
            </div>
          </div>

          <div className="mt-4 flex flex-wrap gap-2">
            <button
              type="button"
              disabled={lock}
              onClick={() => void loadYoutubeIni()}
              className="inline-flex items-center gap-2 rounded-xl border border-white/10 bg-white/[0.05] px-4 py-2 text-sm text-zinc-200"
            >
              <RefreshCw className={`h-3.5 w-3.5 ${ytIniLoading ? "animate-spin" : ""}`} />
              {t("重新读取", "Reload")}
            </button>
            <button
              type="button"
              disabled={lock}
              onClick={() => void applyFieldsAndSave()}
              className="inline-flex items-center gap-2 rounded-xl bg-violet-500/25 px-4 py-2 text-sm font-medium text-violet-100 ring-1 ring-violet-500/35"
            >
              {ytIniLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
              {t("保存 youtube.ini", "Save youtube.ini")}
            </button>
            <button
              type="button"
              disabled={lock}
              onClick={() => void applyYoutubeBalancedPreset()}
              className="rounded-xl border border-cyan-500/20 bg-cyan-500/10 px-4 py-2 text-sm text-cyan-100"
            >
              {t("平衡预设", "Balanced preset")}
            </button>
            <button
              type="button"
              disabled={lock}
              onClick={syncFieldsFromText}
              className="rounded-xl border border-white/10 px-4 py-2 text-sm text-zinc-400"
            >
              {t("从原文同步表单", "Parse raw")}
            </button>
          </div>

          <details className="mt-4 rounded-xl border border-white/5 bg-black/20 p-3">
            <summary className="cursor-pointer text-xs text-zinc-500">{t("高级：youtube.ini 原文", "Raw youtube.ini")}</summary>
            <textarea
              className="mt-2 h-36 w-full resize-y rounded-lg border border-white/10 bg-black/50 p-3 font-mono text-[11px] text-zinc-400"
              value={youtubeIniText}
              onChange={(e) => setYoutubeIniText(e.target.value)}
              spellCheck={false}
            />
            <button
              type="button"
              disabled={lock}
              className="mt-2 rounded-lg bg-cyan-500/15 px-3 py-1.5 text-xs text-cyan-200"
              onClick={() => void saveYoutubeIniToServer(youtubeIniText)}
            >
              {t("保存原文", "Save raw")}
            </button>
          </details>

          {ytIniStatus ? <p className="mt-2 font-mono text-xs text-zinc-500">{ytIniStatus}</p> : null}
        </div>
      ) : (
        <div className="space-y-6">
          <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
            <div className="flex flex-wrap items-center justify-between gap-2">
              <div>
                <h3 className="text-sm font-semibold text-white">
                  {t("当前线路", "Current lane")}
                  {active ? (
                    <span className="ml-2 font-normal text-zinc-400">· {active.name}</span>
                  ) : null}
                  {activeLaneDirty ? (
                    <span className="ml-2 rounded-md bg-amber-500/15 px-2 py-0.5 text-[10px] font-medium text-amber-100 ring-1 ring-amber-500/25">
                      {t("有未保存修改", "Unsaved")}
                    </span>
                  ) : null}
                </h3>
                {active ? (
                  <p className="mt-0.5 font-mono text-[11px] text-zinc-500">
                    PM2: {activeSavedPm2}
                    {activePm2PreviewChanged ? ` → ${activeDraftPm2}` : ""}
                    {" · "}
                    key {keySuffixUi || "—"}
                  </p>
                ) : null}
              </div>
              <button
                type="button"
                disabled={lock}
                className="inline-flex items-center gap-1 rounded-lg bg-violet-500/20 px-3 py-1.5 text-xs text-violet-100 ring-1 ring-violet-500/30"
                onClick={() => {
                  const id = newChannelId();
                  const nc: YoutubeProChannel = {
                    id,
                    name: `${t("线路", "Ch")} ${channels.length + 1}`,
                    streamKey: "",
                    urlLines: "",
                    pm2Name: defaultPm2NameForChannel(id),
                    notes: "",
                  };
                  persistChannels([...channels, nc]);
                  setActiveCh(id);
                  setCurrentProc(resolveChannelPm2(nc));
                  applyProUrlDraft("");
                }}
              >
                <Plus className="h-3.5 w-3.5" />
                {t("新增线路", "Add lane")}
              </button>
            </div>

            {active ? (
              <div className="mt-4 space-y-3 border-t border-white/5 pt-4">
                <input
                  className="w-full rounded-xl border border-white/10 bg-black/40 px-3 py-2 text-sm"
                  value={activeLaneDraft?.name ?? ""}
                  onChange={(e) => setLaneDraftField("name", e.target.value)}
                  placeholder={t("线路名称", "Lane name")}
                />
                <label className="block text-xs text-zinc-500">{t("串流密钥", "Stream key")}</label>
                <input
                  className="w-full rounded-xl border border-white/10 bg-black/40 px-3 py-2 font-mono text-sm"
                  spellCheck={false}
                  value={activeLaneDraft?.streamKey ?? ""}
                  onChange={(e) => setLaneDraftField("streamKey", e.target.value)}
                  placeholder="xxxx-xxxx… / rtmp(s)://…"
                />
                <div className="flex flex-wrap gap-2">
                  <button
                    type="button"
                    disabled={lock}
                    onClick={() => void saveProLaneSettings()}
                    className="rounded-xl border border-white/10 bg-white/[0.05] px-4 py-2 text-sm text-zinc-100"
                  >
                    {laneSaving ? t("保存中...", "Saving...") : t("保存线路设置", "Save lane")}
                  </button>
                  <button
                    type="button"
                    disabled={lock}
                    onClick={() => void pushProStreamKeyToServer()}
                    className="rounded-xl bg-violet-500/25 px-4 py-2 text-sm font-medium text-violet-100 ring-1 ring-violet-500/35"
                    >
                      {t("写入 youtube.ini", "Write youtube.ini")}
                    </button>
                </div>
                {!activeLaneValidation.ok ? (
                  <p className="text-xs text-rose-300">{activeLaneValidation.message}</p>
                ) : null}
                <details className="rounded-xl border border-white/5 bg-black/20 p-3">
                  <summary className="cursor-pointer text-xs text-zinc-500">{t("高级：进程名", "Advanced: process name")}</summary>
                  <input
                    className="mt-2 w-full rounded-lg border border-white/10 bg-black/40 px-3 py-2 font-mono text-xs"
                    value={activeLaneDraft?.pm2Name ?? defaultPm2NameForChannel(active.id)}
                    onChange={(e) => setLaneDraftField("pm2Name", e.target.value)}
                    spellCheck={false}
                    placeholder="youtube2__…"
                  />
                  <p className="mt-2 font-mono text-[11px] text-zinc-500">
                    {t("实际生效", "Effective")}: {activeDraftPm2 || "—"}
                  </p>
                  {activeLaneValidation.message && !activeLaneValidation.ok ? (
                    <p className="mt-2 text-[11px] text-rose-300">{activeLaneValidation.message}</p>
                  ) : null}
                </details>
                <textarea
                  className="h-16 w-full resize-y rounded-xl border border-white/10 bg-black/40 p-3 text-xs text-zinc-300"
                  value={activeLaneDraft?.notes ?? ""}
                  onChange={(e) => setLaneDraftField("notes", e.target.value)}
                  placeholder={t("备注（可选）", "Notes (optional)")}
                />
                {laneStatus ? <p className="text-xs text-zinc-400">{laneStatus}</p> : null}
                <button
                  type="button"
                  disabled={lock || channels.length < 2}
                  className="inline-flex items-center gap-1 text-xs text-rose-300 hover:text-rose-200 disabled:opacity-30"
                  onClick={() => {
                    const next = channels.filter((c) => c.id !== active.id);
                    setLaneDrafts((prev) => {
                      const clone = { ...prev };
                      delete clone[active.id];
                      return clone;
                    });
                    persistChannels(next);
                    setActiveCh(next[0]?.id || "");
                  }}
                >
                  <Trash2 className="h-3.5 w-3.5" />
                  {t("删除此线路", "Delete lane")}
                </button>
              </div>
            ) : null}
          </div>

          <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
            <h3 className="text-sm font-semibold text-white">{t("直播地址列表", "URL list")}</h3>
            <p className="mt-1 text-xs text-zinc-500">
              {t(
                "每条线路独立写入自己的 URL_config.<pm2>.ini，不再复用或回填所谓“全局地址”。",
                "Each lane writes to its own URL_config.<pm2>.ini with no shared global URL draft.",
              )}
            </p>
            <p className="mt-1 font-mono text-[10px] text-zinc-600">
              {currentProc
                ? managedUrlConfigRelPathForPm2(currentProc)
                : "URL_config.ini"}
            </p>
            <div className="mt-3 flex flex-wrap gap-2 text-[11px]">
              <span className="rounded-full border border-cyan-500/20 bg-cyan-500/10 px-2.5 py-1 text-cyan-100">
                {t("有效地址", "Valid URLs")}: {currentUrlCount}
              </span>
              <span className="rounded-full border border-violet-500/20 bg-violet-500/10 px-2.5 py-1 text-violet-100">
                Douyin: {currentDouyinCount}
              </span>
            </div>
            {urlToolbar}
            {urlFetchNote ? <p className="mt-2 text-xs text-zinc-500">{urlFetchNote}</p> : null}
            <textarea
              className="mt-4 min-h-[160px] w-full resize-y rounded-xl border border-white/10 bg-black/50 p-4 font-mono text-xs text-zinc-300"
              value={proUrlDraft}
              onChange={(e) => {
                urlListDirtyRef.current = true;
                applyProUrlDraft(e.target.value);
              }}
              spellCheck={false}
              placeholder="https://live.douyin.com/..."
            />
            <div className="mt-3 flex flex-wrap gap-2">
              <button
                type="button"
                disabled={lock}
                onClick={() => void saveProUrlAndServer()}
                className="rounded-xl bg-cyan-500/20 px-4 py-2 text-sm text-cyan-100 ring-1 ring-cyan-500/30"
              >
                {t("保存地址", "Save URLs")}
              </button>
            </div>
          </div>
        </div>
      )}

      {mode === "single" ? (
        <div className="rounded-2xl border border-white/[0.08] bg-zinc-950/50 p-6">
          <h3 className="text-sm font-semibold text-white">{t("直播地址列表 · URL_config.ini", "URL_config.ini")}</h3>
          <p className="mt-1 text-xs text-zinc-500">
            {t("一行一个地址；可自动刷新或手动重新读取。", "One URL per line; auto-refresh or reload.")}
          </p>
          <div className="mt-3 flex flex-wrap gap-2 text-[11px]">
            <span className="rounded-full border border-cyan-500/20 bg-cyan-500/10 px-2.5 py-1 text-cyan-100">
              {t("有效地址", "Valid URLs")}: {currentUrlCount}
            </span>
            <span className="rounded-full border border-violet-500/20 bg-violet-500/10 px-2.5 py-1 text-violet-100">
              Douyin: {currentDouyinCount}
            </span>
          </div>
          {urlToolbar}
          {urlFetchNote ? <p className="mt-2 text-xs text-zinc-500">{urlFetchNote}</p> : null}
          <textarea
            className="mt-4 min-h-[200px] w-full resize-y rounded-xl border border-white/10 bg-black/50 p-4 font-mono text-xs text-zinc-300"
            value={urlConfig}
            onChange={(e) => {
              urlListDirtyRef.current = true;
              applySingleUrlDraft(e.target.value);
            }}
            spellCheck={false}
          />
          <button
            type="button"
            disabled={lock}
            onClick={() => void saveSingleUrlAndClearDirty()}
            className="mt-3 rounded-xl bg-cyan-500/20 px-4 py-2 text-sm text-cyan-100 ring-1 ring-cyan-500/30"
          >
            {t("保存 URL 配置", "Save URL config")}
          </button>
        </div>
      ) : null}

      <LiveProcessLiveLogCard
        t={t}
        currentProc={currentProc}
        fetchJson={fetchJson}
        urlListText={mode === "single" ? urlConfig : proUrlDraft}
        keySuffixUi={keySuffixUi}
        showKeyRow
        pollMs={800}
        refreshToken={activeViewRefreshToken}
      />
    </div>
  );
}
