"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useEffectEvent, useMemo, useState } from "react";
import { readBackendUnavailableMessage, useResolvedApiBase } from "../lib/api-base";
import { DetailKeyGrid, DetailSection } from "./admin-detail";
import { ConfirmDialog, EmptyPanel, ErrorBanner, NoticeBanner, PageStatus } from "./admin-feedback";
import { AdminFrame } from "./admin-frame";
import { ResourceActions, ResourceCard, ResourceCardHeader, ResourceToolbar } from "./admin-resource";
import { adminNavItems, adminSectionMeta, getAdminSection } from "./admin-config";
import styles from "./platform-shell.module.css";

type ExecutorName = "browser" | "agent-s";
type TaskStatus = "enabled" | "disabled";
type RunStatus = "queued" | "running" | "success" | "failed" | "partial";
type Summary = {
  todayTaskCount: number;
  todayRunCount: number;
  todayExceptionCount: number;
  recentRuns?: Array<{
    id: string;
    taskId: string;
    traceId: string;
    status: RunStatus;
    errorTag: string | null;
    failureReason: string | null;
    executor?: string | null;
    routePolicy?: string | null;
    startedAt: string;
    endedAt: string | null;
  }>;
};
type TaskItem = { id: string; name: string; siteUrl: string; cronExpr: string; stepsJson: string; serviceKey: string; preferredExecutor: ExecutorName; allowAgentFallback: boolean; status: TaskStatus; lastRunAt: string | null; createdAt: string };
type RunItem = { id: string; taskId: string; traceId: string; status: RunStatus; errorTag: string | null; failureReason: string | null; startedAt: string; endedAt: string | null; executor?: string | null; task?: TaskItem };
type StepLog = { index: number; action: string; status: "success" | "failed"; message: string; timestamp: string };
type RunDetail = { run: RunItem; evidence: { screenshots: string[]; stepLogs: StepLog[]; pageUrl: string | null; domSummary: string | null; resultJsonUrl: string }; diagnosis: { executor: string; routePolicy: string } | null; context: unknown };
type ReportItem = { task: { id: string; name: string; siteUrl: string; cronExpr: string; serviceKey: string; preferredExecutor: ExecutorName; allowAgentFallback: boolean; status: TaskStatus; lastRunAt: string | null }; latestRun: { id: string; traceId: string; status: RunStatus; errorTag: string | null; failureReason: string | null; startedAt: string; endedAt: string | null; pageUrl: string | null; domSummary: string | null; executor: string; routePolicy: string | null; screenshots: string[]; resultJsonUrl: string } | null };
type InspectionReport = { generatedAt: string; reportDate: string; totalTasks: number; coveredTasks: number; successTasks: number; exceptionTasks: number; items: ReportItem[] };
type UserInfo = { username: string; displayName: string };
type TaskFormState = { name: string; siteUrl: string; cronExpr: string; serviceKey: string; preferredExecutor: ExecutorName; allowAgentFallback: boolean; stepsJson: string };
type ViewKey = "overview" | "tasks" | "reports" | "runs" | "monitors" | "agents";
type ConfirmState = { title: string; message: string; confirmLabel?: string; tone?: "default" | "danger"; action: () => void | Promise<void> };
type RunListPayload = { list: RunItem[]; total: number; page: number; pageSize: number };

const defaultSteps = JSON.stringify([{ action: "goto", url: "http://www.huhhot.gov.cn/", timeoutMs: 20000 }, { action: "waitForSelector", selector: "body", timeoutMs: 12000 }, { action: "assertVisible", selector: "body", timeoutMs: 12000 }, { action: "screenshot", value: "landing-page" }], null, 2);
const defaultForm: TaskFormState = { name: "呼和浩特市政府首页巡检", siteUrl: "http://www.huhhot.gov.cn/", cronExpr: "*/15 * * * *", serviceKey: "gov-portal-main", preferredExecutor: "browser", allowAgentFallback: false, stepsJson: defaultSteps };
function formatTime(value?: string | null) { return value ? new Date(value).toLocaleString("zh-CN", { hour12: false }) : "暂无"; }
function statusLabel(status: RunStatus | TaskStatus) { return ({ enabled: "启用", disabled: "停用", queued: "排队中", running: "执行中", success: "成功", failed: "失败", partial: "部分成功" } as const)[status]; }
function executorLabel(value: string) { return value === "agent-s" ? "Agent-S" : "Browser"; }
function routePolicyLabel(value?: string | null) { return ({ "browser-first": "先 Browser 后 Agent-S", "browser-only": "仅 Browser", "agent-only": "仅 Agent-S" } as Record<string, string>)[value ?? ""] ?? "暂无"; }
function reportConclusion(item: ReportItem) { if (!item.latestRun) return "当日没有执行记录。"; if (item.latestRun.status === "success") return "本次巡检成功，页面可访问，且已留存截图。"; if (item.latestRun.status === "partial") return `本次巡检存在异常，但已保留现场证据。${item.latestRun.failureReason ?? ""}`; if (item.latestRun.status === "failed") return `本次巡检失败。${item.latestRun.failureReason ?? "请结合步骤日志继续排查。"}`; return "任务仍在执行中。"; }
function compactTraceId(value: string) { return value.length > 12 ? `${value.slice(0, 8)}...${value.slice(-4)}` : value; }
function readErrorMessage(error: unknown, apiBase?: string) { if (error instanceof TypeError) return readBackendUnavailableMessage(apiBase); if (error instanceof Error) return error.message; if (error && typeof error === "object" && "message" in error) { const message = (error as { message?: string | string[] }).message; return Array.isArray(message) ? message.join("；") : (message ?? "请求失败，请稍后重试。"); } return "请求失败，请稍后重试。"; }
function Field(props: { label: string; hint?: string; children: React.ReactNode }) { return <label className={styles.field}><span className={styles.fieldLabel}>{props.label}</span>{props.hint ? <span className={styles.fieldHint}>{props.hint}</span> : null}{props.children}</label>; }
function StatCard(props: { label: string; value: number; tone?: string }) { return <article className={styles.statCard} data-tone={props.tone ?? "default"}><span>{props.label}</span><strong>{props.value}</strong></article>; }
function downloadJson(fileName: string, payload: unknown) { const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json;charset=utf-8" }); const url = window.URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = fileName; document.body.appendChild(anchor); anchor.click(); anchor.remove(); window.URL.revokeObjectURL(url); }

export function PlatformShell() {
  const pathname = usePathname();
  const apiBase = useResolvedApiBase();
  const activeView = getAdminSection(pathname) as ViewKey;
  const [username, setUsername] = useState("admin");
  const [password, setPassword] = useState("");
  const [token, setToken] = useState("");
  const [currentUser, setCurrentUser] = useState<UserInfo | null>(null);
  const [summary, setSummary] = useState<Summary | null>(null);
  const [report, setReport] = useState<InspectionReport | null>(null);
  const [reportDate, setReportDate] = useState(new Date().toISOString().slice(0, 10));
  const [tasks, setTasks] = useState<TaskItem[]>([]);
  const [runs, setRuns] = useState<RunItem[]>([]);
  const [selectedRunId, setSelectedRunId] = useState<string | null>(null);
  const [selectedRunDetail, setSelectedRunDetail] = useState<RunDetail | null>(null);
  const [taskForm, setTaskForm] = useState<TaskFormState>(defaultForm);
  const [editingTaskId, setEditingTaskId] = useState<string | null>(null);
  const [taskKeyword, setTaskKeyword] = useState("");
  const [taskStatusFilter, setTaskStatusFilter] = useState<"all" | TaskStatus>("all");
  const [taskExecutorFilter, setTaskExecutorFilter] = useState<"all" | ExecutorName>("all");
  const [selectedTaskIds, setSelectedTaskIds] = useState<string[]>([]);
  const [runKeyword, setRunKeyword] = useState("");
  const [runStatusFilter, setRunStatusFilter] = useState<"all" | RunStatus>("all");
  const [runExecutorFilter, setRunExecutorFilter] = useState<"all" | ExecutorName>("all");
  const [runErrorTagFilter, setRunErrorTagFilter] = useState("");
  const [runPage, setRunPage] = useState(1);
  const [runPageSize, setRunPageSize] = useState("20");
  const [runTotal, setRunTotal] = useState(0);
  const [selectedRunIds, setSelectedRunIds] = useState<string[]>([]);
  const [reportKeyword, setReportKeyword] = useState("");
  const [reportStatusFilter, setReportStatusFilter] = useState<"all" | RunStatus | "not-run">("all");
  const [errorMessage, setErrorMessage] = useState("");
  const [infoMessage, setInfoMessage] = useState("");
  const [isSigningIn, setIsSigningIn] = useState(false);
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [showRefreshOverlay, setShowRefreshOverlay] = useState(false);
  const [submittingTask, setSubmittingTask] = useState(false);
  const [executingTaskId, setExecutingTaskId] = useState<string | null>(null);
  const [confirmState, setConfirmState] = useState<ConfirmState | null>(null);
  const [confirmBusy, setConfirmBusy] = useState(false);

  const request = useEffectEvent(async <T,>(path: string, init?: RequestInit & { allowAnonymous?: boolean }): Promise<T> => {
    const headers = new Headers(init?.headers);
    headers.set("Content-Type", "application/json");
    if (token && !init?.allowAnonymous) headers.set("Authorization", `Bearer ${token}`);
    try {
      const response = await fetch(`${apiBase}${path}`, { ...init, headers, cache: "no-store" });
      if (!response.ok) {
        let payload: unknown = null;
        try { payload = await response.json(); } catch { payload = null; }
        throw new Error(readErrorMessage(payload, apiBase));
      }
      return await response.json() as T;
    } catch (error) {
      throw new Error(readErrorMessage(error, apiBase));
    }
  });
  useEffect(() => {
    const savedToken = window.localStorage.getItem("sentai.token");
    const savedUser = window.localStorage.getItem("sentai.user");
    if (savedToken) setToken(savedToken);
    if (savedUser) {
      try { setCurrentUser(JSON.parse(savedUser) as UserInfo); } catch { window.localStorage.removeItem("sentai.user"); }
    }
  }, []);

  useEffect(() => {
    if (token) window.localStorage.setItem("sentai.token", token);
    else window.localStorage.removeItem("sentai.token");
  }, [token]);

  useEffect(() => {
    if (currentUser) window.localStorage.setItem("sentai.user", JSON.stringify(currentUser));
    else window.localStorage.removeItem("sentai.user");
  }, [currentUser]);

  useEffect(() => {
    if (!infoMessage) return;
    const timer = window.setTimeout(() => setInfoMessage(""), 4000);
    return () => window.clearTimeout(timer);
  }, [infoMessage]);

  const loadRunDetail = useEffectEvent(async (runId: string) => {
    const detail = await request<RunDetail>(`/api/runs/${runId}`);
    setSelectedRunId(runId);
    setSelectedRunDetail(detail);
  });

  const refreshData = useEffectEvent(async (preferredRunId?: string, nextReportDate?: string) => {
    if (!token) return;
    setIsRefreshing(true);
    setErrorMessage("");
    try {
      const activeReportDate = nextReportDate ?? reportDate;
      const runParams = new URLSearchParams();
      if (activeView === "runs") {
        if (runKeyword.trim()) runParams.set("keyword", runKeyword.trim());
        if (runStatusFilter !== "all") runParams.set("status", runStatusFilter);
        if (runExecutorFilter !== "all") runParams.set("executor", runExecutorFilter);
        if (runErrorTagFilter.trim()) runParams.set("errorTag", runErrorTagFilter.trim());
        runParams.set("page", String(runPage));
        runParams.set("pageSize", runPageSize);
      } else {
        runParams.set("page", "1");
        runParams.set("pageSize", "20");
      }
      const [nextSummary, nextReport, taskPayload, runPayload] = await Promise.all([
        request<Summary>("/api/dashboard/summary"),
        request<InspectionReport>(`/api/runs/report?date=${activeReportDate}`),
        request<{ list: TaskItem[] }>("/api/tasks"),
        request<RunListPayload>(`/api/runs?${runParams.toString()}`),
      ]);
      setSummary(nextSummary);
      setReport(nextReport);
      setTasks(taskPayload.list);
      setRuns(runPayload.list);
      setRunTotal(runPayload.total);
      const targetRunId = preferredRunId ?? (selectedRunId && runPayload.list.some((item) => item.id === selectedRunId) ? selectedRunId : runPayload.list[0]?.id ?? null);
      if (targetRunId) await loadRunDetail(targetRunId);
      else { setSelectedRunId(null); setSelectedRunDetail(null); }
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    } finally {
      setIsRefreshing(false);
    }
  });

  useEffect(() => {
    if (token) void refreshData(undefined, reportDate);
  }, [token, reportDate, activeView, runKeyword, runStatusFilter, runExecutorFilter, runErrorTagFilter, runPage, runPageSize]);

  const filteredTasks = useMemo(() => {
    const keyword = taskKeyword.trim();
    return tasks.filter((task) => {
      const matchesKeyword = !keyword || task.name.includes(keyword) || task.siteUrl.includes(keyword) || task.serviceKey.includes(keyword);
      const matchesStatus = taskStatusFilter === "all" || task.status === taskStatusFilter;
      const matchesExecutor = taskExecutorFilter === "all" || task.preferredExecutor === taskExecutorFilter;
      return matchesKeyword && matchesStatus && matchesExecutor;
    });
  }, [taskExecutorFilter, taskKeyword, taskStatusFilter, tasks]);

  const runSummary = useMemo(() => ({
    total: runTotal,
    success: runs.filter((run) => run.status === "success").length,
    failed: runs.filter((run) => run.status === "failed").length,
    running: runs.filter((run) => run.status === "running").length,
  }), [runTotal, runs]);

  const allVisibleRunIds = useMemo(() => runs.map((run) => run.id), [runs]);
  const allVisibleTaskIds = useMemo(() => filteredTasks.map((task) => task.id), [filteredTasks]);
  const allVisibleRunsSelected = allVisibleRunIds.length > 0 && allVisibleRunIds.every((id) => selectedRunIds.includes(id));
  const allVisibleTasksSelected = allVisibleTaskIds.length > 0 && allVisibleTaskIds.every((id) => selectedTaskIds.includes(id));
  const totalRunPages = useMemo(() => {
    const size = Math.max(Number(runPageSize) || 20, 1);
    return Math.max(Math.ceil(runTotal / size), 1);
  }, [runPageSize, runTotal]);

  const overviewRecentRuns = useMemo(
    () => (summary?.recentRuns ?? []).slice(0, 4),
    [summary],
  );

  const filteredReportItems = useMemo(() => {
    const keyword = reportKeyword.trim();
    if (!report) return [];
    return report.items.filter((item) => {
      const latestStatus = item.latestRun?.status ?? "not-run";
      const matchesKeyword =
        !keyword ||
        item.task.name.includes(keyword) ||
        item.task.siteUrl.includes(keyword) ||
        item.task.serviceKey.includes(keyword);
      const matchesStatus = reportStatusFilter === "all" || latestStatus === reportStatusFilter;
      return matchesKeyword && matchesStatus;
    });
  }, [report, reportKeyword, reportStatusFilter]);

  useEffect(() => {
    setSelectedRunIds((current) => current.filter((id) => runs.some((item) => item.id === id)));
  }, [runs]);

  useEffect(() => {
    setSelectedTaskIds((current) => current.filter((id) => filteredTasks.some((item) => item.id === id)));
  }, [filteredTasks]);

  useEffect(() => {
    if (runPage > totalRunPages) {
      setRunPage(totalRunPages);
    }
  }, [runPage, totalRunPages]);

  async function handleLogin(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setIsSigningIn(true);
    setErrorMessage("");
    try {
      const payload = await request<{ token: string; user: UserInfo }>("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password }), allowAnonymous: true });
      setToken(payload.token);
      setCurrentUser(payload.user);
      setInfoMessage("登录成功，正在加载平台数据。");
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    } finally {
      setIsSigningIn(false);
    }
  }

  function resetTaskForm() {
    setEditingTaskId(null);
    setTaskForm(defaultForm);
  }

  function startEditTask(task: TaskItem) {
    setEditingTaskId(task.id);
    setTaskForm({ name: task.name, siteUrl: task.siteUrl, cronExpr: task.cronExpr, serviceKey: task.serviceKey, preferredExecutor: task.preferredExecutor, allowAgentFallback: task.allowAgentFallback, stepsJson: task.stepsJson });
    setInfoMessage("已载入任务配置，可直接修改并保存。");
  }

  async function handleTaskSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    setSubmittingTask(true);
    setErrorMessage("");
    try {
      JSON.parse(taskForm.stepsJson);
      await request(editingTaskId ? `/api/tasks/${editingTaskId}` : "/api/tasks", { method: editingTaskId ? "PUT" : "POST", body: JSON.stringify(taskForm) });
      resetTaskForm();
      setInfoMessage(editingTaskId ? "任务已更新。" : "任务已创建。");
      await refreshData(undefined, reportDate);
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    } finally {
      setSubmittingTask(false);
    }
  }

  async function handleToggleTask(task: TaskItem) {
    try {
      await request(`/api/tasks/${task.id}/status`, { method: "PATCH", body: JSON.stringify({ status: task.status === "enabled" ? "disabled" : "enabled" }) });
      setInfoMessage(task.status === "enabled" ? "任务已停用。" : "任务已启用。");
      await refreshData(undefined, reportDate);
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    }
  }

  async function handleExecuteTask(task: TaskItem) {
    if (task.status !== "enabled") {
      setInfoMessage("任务已停用，请先启用后再执行。");
      return;
    }
    setExecutingTaskId(task.id);
    setErrorMessage("");
    try {
      const result = await request<{ runId: string }>(`/api/runs/execute/${task.id}`, { method: "POST" });
      setInfoMessage("任务已执行完成，最新记录已同步。");
      await refreshData(result.runId, reportDate);
    } catch (error) {
      setErrorMessage(readErrorMessage(error));
    } finally {
      setExecutingTaskId(null);
    }
  }

  function openConfirm(config: ConfirmState) { setConfirmState(config); }
  function closeConfirm() { if (!confirmBusy) setConfirmState(null); }
  async function executeConfirm() {
    if (!confirmState) return;
    setConfirmBusy(true);
    try {
      await confirmState.action();
      setConfirmState(null);
    } finally {
      setConfirmBusy(false);
    }
  }

  async function handleDeleteTask(task: TaskItem) {
    openConfirm({
      title: "删除巡检任务",
      message: `确认删除任务“${task.name}”吗？关联执行记录也会被移除。`,
      confirmLabel: "确认删除",
      tone: "danger",
      action: async () => {
        await request(`/api/tasks/${task.id}`, { method: "DELETE" });
        if (editingTaskId === task.id) {
          resetTaskForm();
        }
        setInfoMessage("巡检任务已删除。");
        await refreshData(undefined, reportDate);
      },
    });
  }

  async function handleDeleteRun(run: RunItem) {
    openConfirm({
      title: "删除执行记录",
      message: `确认删除这条执行记录吗？Trace ID: ${run.traceId}`,
      confirmLabel: "确认删除",
      tone: "danger",
      action: async () => {
        await request(`/api/runs/${run.id}`, { method: "DELETE" });
        setSelectedRunIds((current) => current.filter((id) => id !== run.id));
        const nextPreferredRunId =
          selectedRunId === run.id
            ? runs.find((item) => item.id !== run.id)?.id
            : selectedRunId ?? undefined;
        setInfoMessage("执行记录已删除。");
        await refreshData(nextPreferredRunId, reportDate);
      },
    });
  }

  function toggleRunSelected(runId: string, checked: boolean) {
    setSelectedRunIds((current) => {
      if (checked) return current.includes(runId) ? current : [...current, runId];
      return current.filter((id) => id !== runId);
    });
  }

  function toggleVisibleRunsSelected(checked: boolean) {
    setSelectedRunIds((current) => {
      if (checked) return Array.from(new Set([...current, ...allVisibleRunIds]));
      return current.filter((id) => !allVisibleRunIds.includes(id));
    });
  }

  function toggleTaskSelected(taskId: string, checked: boolean) {
    setSelectedTaskIds((current) => {
      if (checked) return current.includes(taskId) ? current : [...current, taskId];
      return current.filter((id) => id !== taskId);
    });
  }

  function toggleVisibleTasksSelected(checked: boolean) {
    setSelectedTaskIds((current) => {
      if (checked) return Array.from(new Set([...current, ...allVisibleTaskIds]));
      return current.filter((id) => !allVisibleTaskIds.includes(id));
    });
  }

  async function handleDeleteSelectedRuns() {
    if (!selectedRunIds.length) return;
    openConfirm({
      title: "批量删除执行记录",
      message: `确认删除已选中的 ${selectedRunIds.length} 条执行记录吗？删除后无法恢复。`,
      confirmLabel: "确认删除",
      tone: "danger",
      action: async () => {
        const result = await request<{ deleted: number; runIds?: string[] }>("/api/runs/actions/delete", {
          method: "POST",
          body: JSON.stringify({ runIds: selectedRunIds }),
        });
        const deletedIds = result.runIds ?? selectedRunIds;
        setSelectedRunIds((current) => current.filter((id) => !deletedIds.includes(id)));
        if (selectedRunId && deletedIds.includes(selectedRunId)) {
          setSelectedRunId(null);
          setSelectedRunDetail(null);
        }
        setInfoMessage(`已删除 ${result.deleted} 条执行记录。`);
        await refreshData(undefined, reportDate);
      },
    });
  }

  async function handleDeleteFilteredRuns() {
    openConfirm({
      title: "清理当前筛选执行记录",
      message: "确认删除当前筛选条件下的全部执行记录吗？这个操作会跨分页生效。",
      confirmLabel: "确认清理",
      tone: "danger",
      action: async () => {
        const result = await request<{ deleted: number; runIds?: string[] }>("/api/runs/actions/delete", {
          method: "POST",
          body: JSON.stringify({
            filters: {
              keyword: runKeyword.trim() || undefined,
              status: runStatusFilter === "all" ? undefined : runStatusFilter,
              executor: runExecutorFilter === "all" ? undefined : runExecutorFilter,
              errorTag: runErrorTagFilter.trim() || undefined,
            },
          }),
        });
        const deletedIds = result.runIds ?? [];
        if (selectedRunId && deletedIds.includes(selectedRunId)) {
          setSelectedRunId(null);
          setSelectedRunDetail(null);
        }
        setSelectedRunIds((current) => current.filter((id) => !deletedIds.includes(id)));
        setInfoMessage(`已清理 ${result.deleted} 条执行记录。`);
        await refreshData(undefined, reportDate);
      },
    });
  }

  async function handleDeleteSelectedTasks() {
    if (!selectedTaskIds.length) return;
    openConfirm({
      title: "批量删除巡检任务",
      message: `确认删除已选中的 ${selectedTaskIds.length} 个任务吗？关联执行记录也会被移除。`,
      confirmLabel: "确认删除",
      tone: "danger",
      action: async () => {
        const result = await request<{ deleted: number; taskIds?: string[] }>("/api/tasks/actions/delete", {
          method: "POST",
          body: JSON.stringify({ taskIds: selectedTaskIds }),
        });
        const deletedIds = result.taskIds ?? selectedTaskIds;
        setSelectedTaskIds((current) => current.filter((id) => !deletedIds.includes(id)));
        if (editingTaskId && deletedIds.includes(editingTaskId)) {
          resetTaskForm();
        }
        setInfoMessage(`已删除 ${result.deleted} 个巡检任务。`);
        await refreshData(undefined, reportDate);
      },
    });
  }

  async function handleDeleteFilteredTasks() {
    openConfirm({
      title: "清理当前筛选任务",
      message: "确认删除当前筛选条件下的全部任务吗？关联执行记录也会被移除。",
      confirmLabel: "确认清理",
      tone: "danger",
      action: async () => {
        const result = await request<{ deleted: number; taskIds?: string[] }>("/api/tasks/actions/delete", {
          method: "POST",
          body: JSON.stringify({
            filters: {
              keyword: taskKeyword.trim() || undefined,
              status: taskStatusFilter === "all" ? undefined : taskStatusFilter,
              preferredExecutor: taskExecutorFilter === "all" ? undefined : taskExecutorFilter,
            },
          }),
        });
        const deletedIds = result.taskIds ?? [];
        setSelectedTaskIds((current) => current.filter((id) => !deletedIds.includes(id)));
        if (editingTaskId && deletedIds.includes(editingTaskId)) {
          resetTaskForm();
        }
        setInfoMessage(`已清理 ${result.deleted} 个巡检任务。`);
        await refreshData(undefined, reportDate);
      },
    });
  }

  function handleLogout() {
    setToken("");
    setCurrentUser(null);
    setSummary(null);
    setReport(null);
    setTasks([]);
    setRuns([]);
    setRunTotal(0);
    setSelectedRunIds([]);
    setSelectedTaskIds([]);
    setSelectedRunId(null);
    setSelectedRunDetail(null);
    setErrorMessage("");
    setInfoMessage("");
  }

  function exportTasks() {
    downloadJson("inspection-tasks.json", filteredTasks);
    setInfoMessage(`已导出 ${filteredTasks.length} 条巡检任务。`);
  }

  function exportRuns() {
    downloadJson("inspection-runs.json", runs);
    setInfoMessage(`已导出当前页 ${runs.length} 条执行记录。`);
  }

  function exportReports() {
    downloadJson("inspection-report-items.json", filteredReportItems);
    setInfoMessage(`已导出 ${filteredReportItems.length} 条报告项。`);
  }

  function handlePrintReport() {
    const params = new URLSearchParams();
    params.set("date", reportDate);
    if (reportKeyword.trim()) params.set("keyword", reportKeyword.trim());
    if (reportStatusFilter !== "all") params.set("status", reportStatusFilter);
    window.open(`/reports/print?${params.toString()}`, "_blank", "noopener,noreferrer");
  }

  async function handleManualRefresh() {
    if (!token || isRefreshing) return;
    setShowRefreshOverlay(true);
    try {
      await refreshData(undefined, reportDate);
    } finally {
      setShowRefreshOverlay(false);
    }
  }

  if (!token) {
    return (
      <main className={`${styles.page} ${styles.loginPage}`}>
        <section className={styles.loginStack}>
          <NoticeBanner message={infoMessage} />
          <ErrorBanner title="请求未完成" message={errorMessage} />
          <form className={`${styles.authCard} ${styles.loginCard}`} onSubmit={handleLogin}>
            <Field label="用户名" hint="">
              <input aria-label="用户名" value={username} onChange={(event) => setUsername(event.target.value)} />
            </Field>
            <Field label="密码" hint="">
              <input aria-label="密码" type="password" value={password} onChange={(event) => setPassword(event.target.value)} />
            </Field>
            <div className={styles.buttonRow}>
              <button type="submit" disabled={isSigningIn}>
                {isSigningIn ? "登录中..." : "登录"}
              </button>
            </div>
          </form>
        </section>
      </main>
    );
  }

  return (
    <main className={styles.page}>
      <section className={styles.topbar}>
        <div className={styles.brandCard}>
          <p className={styles.eyebrow}>SentAI Platform</p>
          <h1>AI 智能巡检与故障归因平台</h1>
          <p className={styles.description}>
            当前版本聚焦外网网站巡检主链路，支持 Browser 与 Agent-S 两种执行路线，
            统一沉淀截图、步骤日志、DOM 摘要和结果清单。
          </p>
          <div className={styles.heroTags}>
            <span>外网网站巡检</span>
            <span>执行证据留痕</span>
            <span>双路线执行</span>
          </div>
        </div>
        <section className={styles.authCard}>
          <div className={styles.cardHeader}><div><h2>登录用户</h2><p>{currentUser?.username ?? currentUser?.displayName}</p></div><span className={styles.statusChip} data-state={isRefreshing ? "busy" : "idle"}>{isRefreshing ? "同步中" : "已就绪"}</span></div>
          <div className={`${styles.buttonRow} ${styles.sessionActions}`}>
            <button type="button" onClick={() => void handleManualRefresh()} disabled={isRefreshing}>手动刷新</button>
            <button type="button" className={styles.secondaryButton} onClick={handlePrintReport}>打印报告</button>
            <button type="button" className={styles.secondaryButton} onClick={handleLogout}>退出登录</button>
          </div>
        </section>
      </section>

      <NoticeBanner message={infoMessage} />
      <ErrorBanner title="请求未完成" message={errorMessage} />
      {showRefreshOverlay ? (
        <div className={styles.dialogBackdrop} role="presentation">
          <section className={styles.loadingDialog} aria-live="polite" aria-busy="true">
            <div className={styles.loadingSpinner} />
            <strong>正在刷新概览数据</strong>
            <p>请稍候，系统正在同步巡检任务、执行记录与报告摘要。</p>
          </section>
        </div>
      ) : null}

      {token && summary ? (
        <AdminFrame
          section={activeView}
          title={adminSectionMeta[activeView].title}
          description={adminSectionMeta[activeView].desc}
          embedded
          actions={
            <div className={styles.buttonRow}>
              <PageStatus busy={isRefreshing} busyLabel="同步中" idleLabel="已就绪" />
            </div>
          }
        >
          <div className={styles.stack}>
            {activeView === "overview" ? (
              <div className={styles.stack}>
                <section className={styles.panel}>
                  <ResourceToolbar title="平台概览" description="先用一句话看清当前平台状态，再决定进入哪个模块继续处理。" />
                  <div className={styles.reportGrid}>
                    <ResourceCard>
                      <ResourceCardHeader
                        title="平台态势"
                        subtitle="控制台、任务、报告和执行链当前的综合状态"
                        badges={
                          <span
                            className={styles.badge}
                            data-status={summary.todayExceptionCount > 0 ? "failed" : "success"}
                          >
                            {summary.todayExceptionCount > 0 ? "存在异常" : "运行平稳"}
                          </span>
                        }
                      >
                        <div className={styles.metaList}>
                          <span>登录账号：{currentUser?.username ?? currentUser?.displayName ?? "未知"}</span>
                          <span>后端服务：{apiBase}</span>
                          <span>今日执行：{summary.todayRunCount} 次</span>
                          <span>异常告警：{summary.todayExceptionCount} 次</span>
                        </div>
                      </ResourceCardHeader>
                    </ResourceCard>
                    <ResourceCard>
                      <ResourceCardHeader
                        title="核心能力"
                        subtitle="当前平台已经接通的主链路能力"
                        badges={<span className={styles.badge} data-status="success">已接通</span>}
                      >
                        <div className={styles.metaList}>
                          <span>巡检任务：{tasks.length} 个</span>
                          <span>报告覆盖：{report?.coveredTasks ?? 0} / {report?.totalTasks ?? 0}</span>
                          <span>执行记录：{runs.length} 条</span>
                          <span>Browser / Agent-S 双路线执行已启用</span>
                        </div>
                      </ResourceCardHeader>
                    </ResourceCard>
                    <ResourceCard>
                      <ResourceCardHeader
                        title="处理建议"
                        subtitle="按当前态势建议你优先进入的模块"
                        badges={<span className={styles.badge} data-status={summary.todayExceptionCount > 0 ? "partial" : "success"}>{summary.todayExceptionCount > 0 ? "建议排查" : "建议巡检"}</span>}
                        actions={
                          <ResourceActions>
                            <Link href={summary.todayExceptionCount > 0 ? "/runs" : "/tasks"} className={styles.linkButton}>
                              {summary.todayExceptionCount > 0 ? "查看执行记录" : "查看巡检任务"}
                            </Link>
                          </ResourceActions>
                        }
                      >
                        <div className={styles.metaList}>
                          <span>{summary.todayExceptionCount > 0 ? "今天已经出现异常记录，建议先进入执行记录页查看失败原因和证据。" : "今天整体状态平稳，建议继续从任务页维护调度和巡检策略。"}</span>
                          <span>{report && report.exceptionTasks > 0 ? `报告中有 ${report.exceptionTasks} 个异常任务，可进一步进入报告页查看结论。` : "当前报告摘要没有异常任务，可继续关注覆盖率和最近执行情况。"}</span>
                        </div>
                      </ResourceCardHeader>
                    </ResourceCard>
                  </div>
                </section>
                <section className={styles.panel}>
                  <div className={styles.cardHeader}><div><h3>今日概览</h3><p>先看总览，再决定去任务、报告还是执行记录页深挖。</p></div></div>
                  <div className={styles.statsGrid}>
                    <StatCard label="巡检任务" value={summary.todayTaskCount} />
                    <StatCard label="执行次数" value={summary.todayRunCount} />
                    <StatCard label="异常次数" value={summary.todayExceptionCount} tone="danger" />
                  </div>
                </section>
                {report ? <section className={styles.panel}>
                  <div className={styles.cardHeader}><div><h3>今日报告摘要</h3><p>展示当日覆盖情况、成功数和异常数。</p></div><Link href="/reports" className={styles.linkButton}>查看完整报告</Link></div>
                  <div className={styles.statsGrid}>
                    <StatCard label="任务总数" value={report.totalTasks} />
                    <StatCard label="已覆盖任务" value={report.coveredTasks} />
                    <StatCard label="成功任务" value={report.successTasks} tone="success" />
                    <StatCard label="异常任务" value={report.exceptionTasks} tone="warning" />
                  </div>
                </section> : null}
                <section className={styles.panel}>
                  <div className={styles.cardHeader}>
                    <div>
                      <h3>最近执行动态</h3>
                      <p>展示最近几次执行的结果，方便快速判断平台当前是否稳定。</p>
                    </div>
                    <Link href="/runs" className={styles.linkButton}>查看全部记录</Link>
                  </div>
                  <div className={styles.taskList}>
                    {overviewRecentRuns.length ? overviewRecentRuns.map((run) => (
                      <ResourceCard key={run.id}>
                        <ResourceCardHeader
                          title={compactTraceId(run.traceId)}
                          subtitle={`开始时间：${formatTime(run.startedAt)}`}
                          badges={<span className={styles.badge} data-status={run.status}>{statusLabel(run.status)}</span>}
                          actions={
                            <ResourceActions>
                              <Link href={`/runs?runId=${run.id}`} className={styles.linkButton}>查看详情</Link>
                            </ResourceActions>
                          }
                        >
                          <div className={styles.metaList}>
                            <span>Trace ID：{run.traceId}</span>
                            <span>执行器：{executorLabel(run.executor ?? "browser")}</span>
                            <span>路由策略：{routePolicyLabel(run.routePolicy)}</span>
                            <span>错误标签：{run.errorTag ?? "无"}</span>
                            <span>结束时间：{formatTime(run.endedAt)}</span>
                            <span>失败原因：{run.failureReason ?? "无"}</span>
                          </div>
                        </ResourceCardHeader>
                      </ResourceCard>
                    )) : <EmptyPanel message="当前还没有最近执行动态，可先从任务页触发一次巡检。" />}
                  </div>
                </section>
              </div>
            ) : null}

            {activeView === "tasks" ? (
              <div className={styles.stack}>
                <section className={styles.panel}>
                  <div className={styles.cardHeader}><div><h3>{editingTaskId ? "编辑巡检任务" : "创建巡检任务"}</h3><p>任务定义决定了目标站点、调度频率、执行器和步骤脚本。</p></div>{editingTaskId ? <button type="button" className={styles.secondaryButton} onClick={resetTaskForm}>取消编辑</button> : null}</div>
                  <div className={styles.taskComposer}>
                    <form className={styles.form} onSubmit={handleTaskSubmit}>
                      <div className={styles.formGrid}>
                        <Field label="任务名称" hint="建议带上站点与巡检目标。"><input aria-label="任务名称" value={taskForm.name} onChange={(event) => setTaskForm((current) => ({ ...current, name: event.target.value }))} /></Field>
                        <Field label="站点地址" hint="请输入完整的 http 或 https 地址。"><input aria-label="站点地址" value={taskForm.siteUrl} onChange={(event) => setTaskForm((current) => ({ ...current, siteUrl: event.target.value }))} /></Field>
                        <Field label="Cron 表达式" hint="例如 */15 * * * *。"><input aria-label="Cron 表达式" value={taskForm.cronExpr} onChange={(event) => setTaskForm((current) => ({ ...current, cronExpr: event.target.value }))} /></Field>
                        <Field label="服务标识" hint="建议简洁且稳定。"><input aria-label="服务标识" value={taskForm.serviceKey} onChange={(event) => setTaskForm((current) => ({ ...current, serviceKey: event.target.value }))} /></Field>
                      </div>
                      <section className={styles.strategyCard}>
                        <div className={styles.cardHeader}><div><h4>执行策略</h4><p>{taskForm.preferredExecutor === "agent-s" ? "当前会走 Agent-S 路线执行浏览器操作，适合需要更灵活编排的场景。" : "当前会走标准 Browser 路线，适合稳定的首页探活与业务路径校验。"}</p></div><span className={styles.strategyTag}>{executorLabel(taskForm.preferredExecutor)}</span></div>
                        <Field label="首选执行器" hint="决定任务默认走哪条执行链路。"><select aria-label="首选执行器" value={taskForm.preferredExecutor} onChange={(event) => setTaskForm((current) => ({ ...current, preferredExecutor: event.target.value as ExecutorName }))}><option value="browser">browser</option><option value="agent-s">agent-s</option></select></Field>
                        <label className={styles.switchField}><div><span className={styles.fieldLabel}>失败后允许回退</span><span className={styles.fieldHint}>勾选后会先尝试 Browser，失败时再自动切到 Agent-S。</span></div><input type="checkbox" checked={taskForm.allowAgentFallback} onChange={(event) => setTaskForm((current) => ({ ...current, allowAgentFallback: event.target.checked }))} /></label>
                      </section>
                      <Field label="步骤脚本 JSON" hint="声明浏览器动作，如 goto、click、input 和 screenshot。"><textarea aria-label="步骤脚本" rows={14} value={taskForm.stepsJson} onChange={(event) => setTaskForm((current) => ({ ...current, stepsJson: event.target.value }))} /></Field>
                      <div className={styles.buttonRow}><button type="submit" disabled={submittingTask}>{submittingTask ? "保存中..." : editingTaskId ? "保存任务" : "创建任务"}</button><button type="button" className={styles.secondaryButton} onClick={resetTaskForm}>重置表单</button></div>
                    </form>
                    <aside className={styles.guideCard}>
                      <h4>填写建议</h4>
                      <ul className={styles.guideList}>
                        <li>首页探活任务通常只需要 goto、waitForSelector 和 screenshot。</li>
                        <li>业务路径巡检可以加入 click、input、assertText 和详情页校验。</li>
                        <li>如果需要复杂浏览器操作，可以优先尝试 Agent-S 路线。</li>
                        <li>报告会直接展示任务名称和服务标识，建议统一命名规范。</li>
                      </ul>
                    </aside>
                  </div>
                </section>
                <section className={styles.panel}>
                  <ResourceToolbar title="任务列表" description="统一用工具栏完成搜索、筛选、导出和新建，避免在长列表里反复来回滚动。">
                    <label className={styles.inlineField}><span>关键词</span><input aria-label="任务关键词" placeholder="按名称、地址或服务标识筛选" value={taskKeyword} onChange={(event) => setTaskKeyword(event.target.value)} /></label>
                    <label className={styles.inlineField}>
                      <span>状态</span>
                      <select value={taskStatusFilter} onChange={(event) => setTaskStatusFilter(event.target.value as "all" | TaskStatus)}>
                        <option value="all">全部状态</option>
                        <option value="enabled">启用</option>
                        <option value="disabled">停用</option>
                      </select>
                    </label>
                    <label className={styles.inlineField}>
                      <span>执行器</span>
                      <select value={taskExecutorFilter} onChange={(event) => setTaskExecutorFilter(event.target.value as "all" | ExecutorName)}>
                        <option value="all">全部执行器</option>
                        <option value="browser">Browser</option>
                        <option value="agent-s">Agent-S</option>
                      </select>
                    </label>
                    <ResourceActions>
                      <button type="button" className={styles.secondaryButton} onClick={exportTasks}>导出</button>
                      <label className={styles.checkField}><input type="checkbox" checked={allVisibleTasksSelected} onChange={(event) => toggleVisibleTasksSelected(event.target.checked)} /><span>全选当前筛选</span></label>
                      <button type="button" className={styles.ghostDangerButton} onClick={() => void handleDeleteFilteredTasks()}>清理当前筛选</button>
                      <button type="button" className={styles.ghostDangerButton} disabled={!selectedTaskIds.length} onClick={() => void handleDeleteSelectedTasks()}>{`批量删除已选 (${selectedTaskIds.length})`}</button>
                      <button type="button" className={styles.secondaryButton} onClick={resetTaskForm}>新建任务</button>
                    </ResourceActions>
                  </ResourceToolbar>
                  <div className={styles.taskList}>
                    {filteredTasks.length > 0 ? filteredTasks.map((task) => <ResourceCard key={task.id}><ResourceCardHeader title={task.name} subtitle={task.siteUrl} badges={<span className={styles.badge} data-status={task.status}>{statusLabel(task.status)}</span>} actions={<><label className={styles.checkField}><input type="checkbox" checked={selectedTaskIds.includes(task.id)} onChange={(event) => toggleTaskSelected(task.id, event.target.checked)} /><span>选择</span></label><button type="button" onClick={() => startEditTask(task)}>编辑</button><button type="button" className={styles.secondaryButton} onClick={() => void handleToggleTask(task)}>{task.status === "enabled" ? "停用" : "启用"}</button><button type="button" className={styles.secondaryButton} onClick={() => void handleDeleteTask(task)}>删除</button><button type="button" disabled={executingTaskId === task.id || task.status !== "enabled"} onClick={() => void handleExecuteTask(task)}>{executingTaskId === task.id ? "执行中..." : "立即执行"}</button></>}>
                      <div className={styles.metaList}><span>服务标识：{task.serviceKey}</span><span>首选执行器：{executorLabel(task.preferredExecutor)}</span><span>允许回退：{task.allowAgentFallback ? "是" : "否"}</span><span>Cron：{task.cronExpr}</span><span>最近执行：{formatTime(task.lastRunAt)}</span></div>
                    </ResourceCardHeader></ResourceCard>) : <EmptyPanel message="当前筛选条件下没有巡检任务。" />}
                  </div>
                </section>
              </div>
            ) : null}

            {activeView === "reports" && report ? (
              <div className={styles.stack}>
                <section className={styles.panel}>
                  <div className={styles.cardHeader}><div><h3>报告工具栏</h3><p>当前生成时间：{formatTime(report.generatedAt)}。先筛选和定位，再看单条报告项，不再铺满一整页超长大卡片。</p></div></div>
                  <div className={styles.toolbar}>
                    <label className={styles.inlineField}><span>报告日期</span><input aria-label="报告日期" type="date" value={reportDate} onChange={(event) => setReportDate(event.target.value)} /></label>
                    <label className={styles.inlineField}><span>关键词</span><input aria-label="报告关键词" placeholder="按任务名、站点地址或服务标识筛选" value={reportKeyword} onChange={(event) => setReportKeyword(event.target.value)} /></label>
                    <label className={styles.inlineField}>
                      <span>结果状态</span>
                      <select value={reportStatusFilter} onChange={(event) => setReportStatusFilter(event.target.value as "all" | RunStatus | "not-run")}>
                        <option value="all">全部状态</option>
                        <option value="success">成功</option>
                        <option value="failed">失败</option>
                        <option value="partial">部分成功</option>
                        <option value="running">执行中</option>
                        <option value="not-run">未执行</option>
                      </select>
                    </label>
                    <div className={styles.buttonRow}><button type="button" onClick={() => void refreshData(undefined, reportDate)}>查询报告</button><button type="button" className={styles.secondaryButton} onClick={() => setReportDate(new Date().toISOString().slice(0, 10))}>回到今天</button><button type="button" className={styles.secondaryButton} onClick={exportReports}>导出</button><button type="button" className={styles.secondaryButton} onClick={handlePrintReport}>打印报告</button></div>
                  </div>
                  <div className={styles.statsGrid}><StatCard label="任务总数" value={report.totalTasks} /><StatCard label="当日已执行" value={report.coveredTasks} /><StatCard label="成功任务" value={report.successTasks} tone="success" /><StatCard label="异常任务" value={report.exceptionTasks} tone="warning" /></div>
                </section>
                <section className={styles.panel}>
                  <ResourceToolbar title="报告列表" description="一条报告项只保留高价值信息，避免整页都是超大块卡片。" />
                  <div className={styles.taskList}>
                    {filteredReportItems.length > 0 ? filteredReportItems.map((item) => <ResourceCard key={item.task.id}><ResourceCardHeader title={item.task.name} subtitle={item.task.siteUrl} badges={<span className={styles.badge} data-status={item.latestRun?.status ?? "queued"}>{item.latestRun ? statusLabel(item.latestRun.status) : "未执行"}</span>} actions={<ResourceActions>{item.latestRun?.resultJsonUrl ? <a className={styles.linkButton} href={`${apiBase}${item.latestRun.resultJsonUrl}`} target="_blank" rel="noreferrer">查看结果</a> : null}{item.latestRun?.screenshots.length ? <a className={styles.secondaryButton} href={`${apiBase}${item.latestRun.screenshots[0]}`} target="_blank" rel="noreferrer">查看截图</a> : <span className={styles.metaChip}>暂无截图</span>}</ResourceActions>}>
                      <p className={styles.reportConclusion}>{reportConclusion(item)}</p>
                      <div className={styles.metaChipList}><span className={styles.metaChip}><strong>服务标识</strong>{item.task.serviceKey}</span><span className={styles.metaChip}><strong>首选执行器</strong>{executorLabel(item.task.preferredExecutor)}</span><span className={styles.metaChip}><strong>实际执行器</strong>{item.latestRun?.executor ?? "暂无"}</span><span className={styles.metaChip}><strong>路由策略</strong>{routePolicyLabel(item.latestRun?.routePolicy)}</span><span className={styles.metaChip}><strong>截图数量</strong>{item.latestRun?.screenshots.length ?? 0}</span></div>
                    </ResourceCardHeader></ResourceCard>) : <EmptyPanel message="当前筛选条件下没有报告项。" />}
                  </div>
                </section>
              </div>
            ) : null}
            {activeView === "runs" ? (
              <div className={styles.stack}>
                <section className={styles.panel}>
                  <div className={styles.cardHeader}><div><h3>执行记录总览</h3><p>先筛选和定位，再在右侧查看完整证据，避免一页堆满所有细节。</p></div></div>
                  <div className={styles.statsGrid}><StatCard label="最新记录" value={runSummary.total} /><StatCard label="成功" value={runSummary.success} tone="success" /><StatCard label="失败" value={runSummary.failed} tone="danger" /><StatCard label="执行中" value={runSummary.running} tone="warning" /></div>
                  <div className={styles.toolbar}><label className={styles.inlineField}><span>搜索记录</span><input aria-label="搜索执行记录" placeholder="按任务名、Trace ID、错误标签筛选" value={runKeyword} onChange={(event) => { setRunKeyword(event.target.value); setRunPage(1); }} /></label><label className={styles.inlineField}><span>执行状态</span><select aria-label="执行状态" value={runStatusFilter} onChange={(event) => { setRunStatusFilter(event.target.value as "all" | RunStatus); setRunPage(1); }}><option value="all">全部状态</option><option value="running">执行中</option><option value="success">成功</option><option value="failed">失败</option><option value="partial">部分成功</option></select></label><label className={styles.inlineField}><span>执行器</span><select value={runExecutorFilter} onChange={(event) => { setRunExecutorFilter(event.target.value as "all" | ExecutorName); setRunPage(1); }}><option value="all">全部执行器</option><option value="browser">Browser</option><option value="agent-s">Agent-S</option></select></label><label className={styles.inlineField}><span>错误标签</span><input value={runErrorTagFilter} placeholder="如 EXECUTION_ERROR" onChange={(event) => { setRunErrorTagFilter(event.target.value); setRunPage(1); }} /></label><label className={styles.inlineField}><span>每页条数</span><select value={runPageSize} onChange={(event) => { setRunPageSize(event.target.value); setRunPage(1); }}><option value="20">20</option><option value="50">50</option><option value="100">100</option></select></label><div className={styles.buttonRow}><button type="button" className={styles.secondaryButton} onClick={exportRuns}>导出</button><button type="button" className={styles.ghostDangerButton} onClick={() => void handleDeleteFilteredRuns()}>清理当前筛选</button><button type="button" className={styles.ghostDangerButton} disabled={!selectedRunIds.length} onClick={() => void handleDeleteSelectedRuns()}>{`批量删除已选 (${selectedRunIds.length})`}</button></div></div>
                </section>
                <section className={styles.runWorkspace}>
                  <div className={styles.runListPanel}>
                    <div className={styles.cardHeader}><div><h3>执行列表</h3><p>默认展示最近 20 条，点击任一记录查看右侧详情。</p></div></div>
                    <div className={styles.runSelectionBar}><label className={styles.selectionToggle}><input type="checkbox" checked={allVisibleRunsSelected} onChange={(event) => toggleVisibleRunsSelected(event.target.checked)} /><span>全选当前页</span></label><span className={styles.selectionMeta}>{`当前页 ${runs.length} 条 / 共 ${runTotal} 条`}</span></div>
                    <div className={styles.runList}>{runs.length > 0 ? runs.map((run) => <article key={run.id} className={styles.runRow} data-active={selectedRunId === run.id}><label className={styles.runSelectCell}><input type="checkbox" checked={selectedRunIds.includes(run.id)} onChange={(event) => toggleRunSelected(run.id, event.target.checked)} /></label><button type="button" className={styles.runRowButton} onClick={() => void loadRunDetail(run.id)}><div className={styles.runRowMain}><div className={styles.runRowHeader}><h4>{run.task?.name ?? "巡检任务"}</h4><span className={styles.badge} data-status={run.status}>{statusLabel(run.status)}</span></div><div className={styles.metaList}><span>开始时间：{formatTime(run.startedAt)}</span><span>Trace ID：{run.traceId}</span><span>错误标签：{run.errorTag ?? "无"}</span></div></div><div className={styles.runRowSide}><span>{executorLabel(run.executor ?? run.task?.preferredExecutor ?? "browser")}</span></div></button></article>) : <EmptyPanel message="没有匹配的执行记录，请调整筛选条件。" />}</div>
                    <div className={styles.runPaginationBar}><span className={styles.selectionMeta}>{`第 ${runPage} / ${totalRunPages} 页`}</span><div className={styles.runPager}><button type="button" className={styles.secondaryButton} disabled={runPage <= 1} onClick={() => setRunPage((current) => Math.max(current - 1, 1))}>上一页</button><button type="button" className={styles.secondaryButton} disabled={runPage >= totalRunPages} onClick={() => setRunPage((current) => Math.min(current + 1, totalRunPages))}>下一页</button></div></div>
                  </div>
                  <aside className={styles.runDetailPanel}>
                    <div className={styles.cardHeader}><div><h3>执行详情</h3><p>把关键结论、截图、步骤日志和页面摘要拆开看，避免信息打架。</p></div></div>
                    {selectedRunDetail ? <div className={styles.detailStack}>
                      <DetailSection title={selectedRunDetail.run.task?.name ?? "巡检任务"} description={`开始时间：${formatTime(selectedRunDetail.run.startedAt)}`}>
                        <div className={styles.detailHeader}><div><p>{selectedRunDetail.evidence.pageUrl ?? "暂无页面地址"}</p></div><span className={styles.badge} data-status={selectedRunDetail.run.status}>{statusLabel(selectedRunDetail.run.status)}</span></div>
                        <DetailKeyGrid items={[{ label: "Trace ID", value: selectedRunDetail.run.traceId }, { label: "执行器", value: executorLabel(selectedRunDetail.diagnosis?.executor ?? "browser") }, { label: "路由策略", value: routePolicyLabel(selectedRunDetail.diagnosis?.routePolicy) }, { label: "错误标签", value: selectedRunDetail.run.errorTag ?? "无" }]} />
                        <div className={styles.metaList}><span>页面地址：{selectedRunDetail.evidence.pageUrl ?? "暂无"}</span><span>失败原因：{selectedRunDetail.run.failureReason ?? "无"}</span></div>
                        <div className={styles.buttonRow}><a href={`${apiBase}${selectedRunDetail.evidence.resultJsonUrl}`} target="_blank" rel="noreferrer" className={styles.linkButton}>查看结果清单</a><button type="button" className={styles.ghostDangerButton} onClick={() => void handleDeleteRun(selectedRunDetail.run)}>删除记录</button></div>
                      </DetailSection>
                      <DetailSection title="截图证据" description="点击缩略图可查看原图。"><div className={styles.screenshotGrid}>{selectedRunDetail.evidence.screenshots.length > 0 ? selectedRunDetail.evidence.screenshots.map((src) => <a key={src} href={`${apiBase}${src}`} target="_blank" rel="noreferrer" className={styles.shotItem}><img src={`${apiBase}${src}`} alt="巡检截图" /></a>) : <EmptyPanel message="本次执行没有截图证据。" />}</div></DetailSection>
                      <DetailSection title="步骤日志" description="按执行顺序展示关键动作和结果。"><div className={styles.timeline}>{selectedRunDetail.evidence.stepLogs.length > 0 ? selectedRunDetail.evidence.stepLogs.map((log) => <article key={`${log.index}-${log.timestamp}`} className={styles.timelineItem}><div className={styles.timelineIndex}>{log.index}</div><div className={styles.timelineBody}><div className={styles.timelineHeader}><strong>{log.action}</strong><span className={styles.badge} data-status={log.status === "success" ? "success" : "failed"}>{log.status === "success" ? "成功" : "失败"}</span></div><p>{log.message}</p><span>{formatTime(log.timestamp)}</span></div></article>) : <EmptyPanel message="本次执行没有步骤日志。" />}</div></DetailSection>
                      <DetailSection title="DOM 摘要" description="用于快速判断页面是否渲染到了预期内容。"><pre className={styles.codeBlock}>{selectedRunDetail.evidence.domSummary ?? "暂无 DOM 摘要"}</pre></DetailSection>
                    </div> : <EmptyPanel message="请先从左侧选择一条执行记录，再查看详情。" />}
                  </aside>
                </section>
              </div>
            ) : null}
          </div>
        </AdminFrame>
      ) : null}
      <ConfirmDialog
        open={Boolean(confirmState)}
        title={confirmState?.title ?? ""}
        message={confirmState?.message ?? ""}
        confirmLabel={confirmState?.confirmLabel}
        tone={confirmState?.tone ?? "default"}
        busy={confirmBusy}
        onCancel={closeConfirm}
        onConfirm={() => void executeConfirm()}
      />
    </main>
  );
}





