import React from "react";
import { fmtClock, fmtDate } from "./time";
import { LoadingTitle } from "./loading-indicator";
import { errorMessage, requestJson } from "./unidesk-error";
import { UniDeskErrorBanner } from "./unidesk-error-banner";

type AnyRecord = Record<string, any>;

const h = React.createElement;
const { useEffect } = React;
const useState: any = React.useState;


function fmtPercent(value: any): string {
  const number = Number(value);
  return Number.isFinite(number) ? `${Math.max(0, Math.min(100, number)).toFixed(1)}%` : "--";
}

function fmtDuration(seconds: any): string {
  if (seconds === null || seconds === undefined || seconds === "") return "--";
  const value = Number(seconds);
  if (!Number.isFinite(value)) return "--";
  if (value < 60) return `${Math.max(0, Math.round(value))}s`;
  if (value < 3600) return `${Math.floor(value / 60)}m ${Math.round(value % 60)}s`;
  return `${Math.floor(value / 3600)}h ${Math.floor((value % 3600) / 60)}m`;
}

function fmtNumber(value: any, digits = 2): string {
  const number = Number(value);
  if (!Number.isFinite(number)) return value === false ? "false" : value === true ? "true" : "--";
  const abs = Math.abs(number);
  if (Number.isInteger(number) || abs >= 1000) return number.toLocaleString("zh-CN", { maximumFractionDigits: 0 });
  if (abs >= 1) return number.toLocaleString("zh-CN", { maximumFractionDigits: digits });
  return number.toLocaleString("zh-CN", { maximumFractionDigits: Math.max(digits, 6) });
}

function fmtValue(value: any): string {
  if (value === null || value === undefined || value === "") return "--";
  if (typeof value === "boolean") return value ? "true" : "false";
  if (typeof value === "number") return fmtNumber(value, 4);
  if (Array.isArray(value)) return value.map((item) => fmtValue(item)).join(" x ");
  if (typeof value === "object") return "已上报";
  return String(value);
}

function fmtEpochSpeed(value: any): string {
  const number = Number(value);
  if (!Number.isFinite(number) || number <= 0) return "--";
  const digits = number >= 100 ? 0 : number >= 10 ? 1 : 2;
  return `${number.toLocaleString("zh-CN", { maximumFractionDigits: digits })} epoch/h`;
}

function safeDomId(value: string): string {
  return value.replace(/[^a-zA-Z0-9_-]/g, "-");
}

function objectValue(value: any): AnyRecord {
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
}

function StatusBadge({ status, children }: AnyRecord) {
  const normalized = String(status || "unknown").toLowerCase();
  return h("span", { className: `status-badge ${normalized}` }, children || status || "unknown");
}

function MetricCard({ label, value, hint, tone }: AnyRecord) {
  return h("article", { className: `metric-card ${tone || ""}` },
    h("div", { className: "metric-label" }, label),
    h("div", { className: "metric-value" }, value),
    h("div", { className: "metric-hint" }, hint),
  );
}

function Panel({ title, eyebrow, actions, children, className, loading }: AnyRecord) {
  return h("section", { className: `panel ${className || ""}` },
    h("div", { className: "panel-head" },
      h("div", null,
        eyebrow ? h("p", { className: "panel-eyebrow" }, eyebrow) : null,
        h(LoadingTitle, { title, loading }),
      ),
      actions ? h("div", { className: "panel-actions" }, actions) : null,
    ),
    h("div", { className: "panel-body" }, children),
  );
}

function RawButton({ title, data, onOpen, testId }: AnyRecord) {
  return h("button", {
    type: "button",
    className: "ghost-btn",
    "data-testid": testId,
    onClick: (event: any) => {
      event?.stopPropagation?.();
      onOpen(title, data);
    },
  }, "查看原始JSON");
}

function EmptyState({ title, text }: AnyRecord) {
  return h("div", { className: "empty-state" }, h("strong", null, title), h("span", null, text));
}

function microserviceRuntime(service: any): AnyRecord {
  return service?.runtime && typeof service.runtime === "object" && !Array.isArray(service.runtime) ? service.runtime : {};
}

function microserviceBackend(service: any): AnyRecord {
  return service?.backend && typeof service.backend === "object" && !Array.isArray(service.backend) ? service.backend : {};
}

function microserviceRepository(service: any): AnyRecord {
  return service?.repository && typeof service.repository === "object" && !Array.isArray(service.repository) ? service.repository : {};
}

function queueCounts(queue: any): AnyRecord {
  return queue?.counts && typeof queue.counts === "object" && !Array.isArray(queue.counts) ? queue.counts : {};
}

function jobRows(queue: any): any[] {
  return Array.isArray(queue?.jobs) ? queue.jobs.slice(0, 240) : [];
}

function projectRows(projects: any): any[] {
  return Array.isArray(projects?.projects) ? projects.projects.slice(0, 1000) : [];
}

function allProjectRows(projects: any): any[] {
  return Array.isArray(projects?.projects) ? projects.projects : [];
}

function gpuRows(health: any, queue: any): any[] {
  if (Array.isArray(queue?.gpu)) return queue.gpu;
  if (Array.isArray(health?.gpu)) return health.gpu;
  return [];
}

function metApi(apiBaseUrl: string, path: string): string {
  return `${apiBaseUrl}/microservices/met-nonlinear/proxy${path}`;
}

function jobDuration(job: any): string {
  return job.startedAt && job.finishedAt ? fmtDuration((Date.parse(job.finishedAt) - Date.parse(job.startedAt)) / 1000) : "--";
}

function jobEtaSeconds(job: any): number | null {
  const progress = job.progress || {};
  if (progress.etaSeconds !== null && progress.etaSeconds !== undefined && progress.etaSeconds !== "") {
    const reported = Number(progress.etaSeconds);
    if (Number.isFinite(reported)) return Math.max(0, reported);
  }
  const currentEpoch = Number(progress.currentEpoch);
  const epochTarget = Number(progress.epochTarget ?? job.epochTarget);
  const startedAt = Date.parse(job.startedAt || "");
  if (!Number.isFinite(currentEpoch) || currentEpoch <= 0 || !Number.isFinite(epochTarget) || epochTarget <= currentEpoch || !Number.isFinite(startedAt)) return null;
  const elapsedSeconds = Math.max(0, (Date.now() - startedAt) / 1000);
  if (elapsedSeconds <= 0) return null;
  return Math.max(0, (elapsedSeconds / currentEpoch) * (epochTarget - currentEpoch));
}

function jobEpochPerHour(job: any): number | null {
  const progress = job.progress || {};
  const reported = Number(progress.epochPerHour);
  if (Number.isFinite(reported) && reported > 0) return reported;
  const startedAt = Date.parse(job.startedAt || "");
  const finishedAt = ["succeeded", "failed", "canceled"].includes(job.status) ? Date.parse(job.finishedAt || "") : Date.now();
  if (!Number.isFinite(startedAt) || !Number.isFinite(finishedAt) || finishedAt <= startedAt) return null;
  const epochs = Number(progress.currentEpoch ?? job.epochTarget);
  if (!Number.isFinite(epochs) || epochs <= 0) return null;
  return epochs / ((finishedAt - startedAt) / 3600000);
}

function statusLabel(status: string): string {
  if (status === "staged") return "待启动";
  if (status === "queued") return "排队中";
  if (status === "running") return "训练中";
  if (status === "succeeded") return "已完成";
  if (status === "failed") return "失败";
  if (status === "canceled") return "已取消";
  return status || "unknown";
}

type ProjectTreeNode = {
  name: string;
  path: string;
  depth: number;
  count: number;
  children: ProjectTreeNode[];
  project: any | null;
};

function createProjectTreeNode(name: string, path: string, depth: number): ProjectTreeNode {
  return { name, path, depth, count: 0, children: [], project: null };
}

function buildProjectTree(projects: any[]): ProjectTreeNode {
  const root = createProjectTreeNode("", "", -1);
  for (const project of projects) {
    const projectPath = String(project?.projectPath || "").replace(/\\/g, "/");
    const parts = projectPath.split("/").filter(Boolean);
    if (parts.length === 0) continue;
    let node = root;
    const cursor: string[] = [];
    for (const [index, part] of parts.entries()) {
      cursor.push(part);
      const path = cursor.join("/");
      let child = node.children.find((item) => item.path === path);
      if (!child) {
        child = createProjectTreeNode(part, path, index);
        node.children.push(child);
      }
      if (index === parts.length - 1) child.project = project;
      node = child;
    }
  }
  const fillCount = (node: ProjectTreeNode): number => {
    const childCount = node.children.reduce((sum, child) => sum + fillCount(child), 0);
    node.count = (node.project ? 1 : 0) + childCount;
    node.children.sort((a, b) => {
      if (Boolean(a.project) !== Boolean(b.project)) return a.project ? 1 : -1;
      return a.name.localeCompare(b.name, "zh-CN", { numeric: true, sensitivity: "base" });
    });
    return node.count;
  };
  fillCount(root);
  return root;
}

function detailPayload(detail: AnyRecord): AnyRecord {
  const data = objectValue(detail.data);
  return objectValue(data.project).projectPath ? objectValue(data.project) : data;
}

function detailJob(detail: AnyRecord): AnyRecord {
  return objectValue(objectValue(detail.data).job);
}

export function MetNonlinearPage({ microservices, onRaw, apiBaseUrl = "/api" }: AnyRecord) {
  const service = microservices.find((item: any) => item.id === "met-nonlinear") || null;
  const [state, setState] = useState({ loading: false, actionBusy: false, error: "", health: null, summary: null, queue: null, projects: null, history: null, images: null, refreshedAt: null });
  const [detail, setDetail] = useState({ loading: false, error: "", kind: "", key: "", title: "", data: null });
  const [ui, setUi] = useState(() => ({
    activeTab: "projects",
    selectedProjects: {},
    expandedProjectDirs: {},
    sourceProject: "",
    forkCount: 1,
    forkEpochs: 200,
    forkPrefix: `ui_fork_${Date.now()}`,
    maxConcurrency: 3,
    targetGpuName: "2080 Ti",
    actionMessage: "",
  }));

  function patchUi(patch: AnyRecord): void {
    setUi((prev: any) => ({ ...prev, ...patch }));
  }

  async function load(tab = ui.activeTab): Promise<void> {
    if (!service) return;
    setState((prev: any) => ({ ...prev, loading: true, error: "" }));
    try {
      const requests: Array<[string, Promise<any>]> = [
        ["health", requestJson(`${apiBaseUrl}/microservices/met-nonlinear/health`)],
        ["summary", requestJson(metApi(apiBaseUrl, "/api/summary"))],
      ];
      if (tab === "projects") {
        requests.push(["projectsRoot", requestJson(metApi(apiBaseUrl, "/api/projects?root=projects&limit=500"))]);
        requests.push(["exProjectsRoot", requestJson(metApi(apiBaseUrl, "/api/projects?root=ex_projects&limit=500"))]);
      }
      if (tab === "current" || tab === "completed" || tab === "failed") {
        requests.push(["queue", requestJson(metApi(apiBaseUrl, "/api/queue"))]);
      }
      if (tab === "completed" || tab === "failed") {
        requests.push(["history", requestJson(metApi(apiBaseUrl, "/api/history"))]);
      }
      if (tab === "gpu") {
        requests.push(["images", requestJson(metApi(apiBaseUrl, "/api/images"))]);
      }
      const entries = Object.fromEntries(await Promise.all(requests.map(async ([key, promise]) => [key, await promise] as const)));
      const patch: AnyRecord = { loading: false, actionBusy: false, error: "", health: entries.health, summary: entries.summary, refreshedAt: new Date() };
      if (entries.projectsRoot || entries.exProjectsRoot) {
        const projectsRoot = entries.projectsRoot;
        const exProjectsRoot = entries.exProjectsRoot;
        patch.projects = {
          ok: projectsRoot?.ok !== false && exProjectsRoot?.ok !== false,
          roots: [
            { root: "projects", count: allProjectRows(projectsRoot).length },
            { root: "ex_projects", count: allProjectRows(exProjectsRoot).length },
          ],
          projects: [...allProjectRows(projectsRoot), ...allProjectRows(exProjectsRoot)],
        };
      }
      if (entries.queue) patch.queue = entries.queue;
      if (entries.history) patch.history = entries.history;
      if (entries.images) patch.images = entries.images;
      setState((prev: any) => ({ ...prev, ...patch }));
    } catch (err) {
      setState((prev: any) => ({ ...prev, loading: false, actionBusy: false, error: errorMessage(err, "MET Nonlinear 加载失败") }));
    }
  }

  async function runAction(label: string, fn: () => Promise<string | void>): Promise<void> {
    setState((prev: any) => ({ ...prev, actionBusy: true, error: "" }));
    patchUi({ actionMessage: `${label}...` });
    try {
      const message = await fn();
      patchUi({ actionMessage: message || `${label}完成` });
      await load();
    } catch (err) {
      setState((prev: any) => ({ ...prev, actionBusy: false, error: errorMessage(err, `${label}失败`) }));
    }
  }

  async function saveQueueSettings(): Promise<void> {
    await runAction("保存并发设置", async () => {
      await requestJson(metApi(apiBaseUrl, "/api/queue/settings"), {
        method: "PUT",
        body: JSON.stringify({ maxConcurrency: Number(ui.maxConcurrency), targetGpuName: ui.targetGpuName }),
      });
    });
  }

  function selectedProjectPaths(): string[] {
    return Object.entries(ui.selectedProjects).filter(([, selected]) => selected).map(([path]) => path);
  }

  async function stageSelectedProjects(): Promise<void> {
    const paths = selectedProjectPaths();
    if (paths.length === 0) throw new Error("请先选择至少一个 project");
    await runAction("加入待启动队列", async () => {
      await requestJson(metApi(apiBaseUrl, "/api/queue"), {
        method: "POST",
        body: JSON.stringify({ projectPaths: paths, maxConcurrency: Number(ui.maxConcurrency), targetGpuName: ui.targetGpuName, start: false }),
      });
      patchUi({ activeTab: "current", selectedProjects: {} });
    });
  }

  async function forkProjects(): Promise<void> {
    const sourceProject = ui.sourceProject || projects[0]?.projectPath;
    if (!sourceProject) throw new Error("请先选择源 project");
    await runAction("Fork Project", async () => {
      const forked = await requestJson(metApi(apiBaseUrl, "/api/projects/fork"), {
        method: "POST",
        body: JSON.stringify({ sourceProject, count: Number(ui.forkCount), epochs: Number(ui.forkEpochs), prefix: ui.forkPrefix }),
      });
      const projectPaths = Array.isArray(forked.projectPaths) ? forked.projectPaths : [];
      const selectedProjects = projectPaths.reduce((acc: AnyRecord, projectPath: string) => {
        acc[projectPath] = true;
        return acc;
      }, { ...ui.selectedProjects });
      patchUi({
        selectedProjects,
      });
      return `已 fork ${projectPaths.length} 个 project，并已自动勾选；请确认后点击加入待启动队列。`;
    });
  }

  async function startQueue(): Promise<void> {
    await runAction("启动队列", async () => {
      await requestJson(metApi(apiBaseUrl, "/api/queue/start"), {
        method: "POST",
        body: JSON.stringify({ maxConcurrency: Number(ui.maxConcurrency), targetGpuName: ui.targetGpuName }),
      });
      patchUi({ activeTab: "current" });
    });
  }

  async function cancelJob(job: any): Promise<void> {
    await runAction("取消任务", async () => {
      await requestJson(metApi(apiBaseUrl, `/api/jobs/${encodeURIComponent(job.id)}/cancel`), { method: "POST", body: JSON.stringify({}) });
    });
  }

  async function openProjectDetail(project: any): Promise<void> {
    const projectPath = String(project?.projectPath || "");
    if (!projectPath) return;
    setDetail({ loading: true, error: "", kind: "project", key: projectPath, title: projectPath, data: null });
    try {
      const data = await requestJson(metApi(apiBaseUrl, `/api/projects/config?path=${encodeURIComponent(projectPath)}`));
      setDetail({ loading: false, error: "", kind: "project", key: projectPath, title: projectPath, data });
    } catch (err) {
      setDetail({ loading: false, error: errorMessage(err, "Project 详情加载失败"), kind: "project", key: projectPath, title: projectPath, data: null });
    }
  }

  async function openJobDetail(job: any): Promise<void> {
    const jobId = String(job?.id || "");
    if (!jobId) return;
    setDetail({ loading: true, error: "", kind: "job", key: jobId, title: job.projectPath || jobId, data: null });
    try {
      const data = await requestJson(metApi(apiBaseUrl, `/api/jobs/${encodeURIComponent(jobId)}`));
      setDetail({ loading: false, error: "", kind: "job", key: jobId, title: data?.job?.projectPath || job.projectPath || jobId, data });
    } catch (err) {
      setDetail({ loading: false, error: errorMessage(err, "Job 详情加载失败"), kind: "job", key: jobId, title: job.projectPath || jobId, data: null });
    }
  }

  useEffect(() => {
    load(ui.activeTab);
  }, [service?.id, service?.runtime?.providerStatus, ui.activeTab]);

  if (!service) return h(EmptyState, { title: "MET Nonlinear 未登记", text: "请在 config.json 的 microservices 中登记用户服务 id=met-nonlinear" });

  const runtime = microserviceRuntime(service);
  const repository = microserviceRepository(service);
  const backend = microserviceBackend(service);
  const counts = queueCounts(state.queue?.queue || state.summary?.queue);
  const gpus = gpuRows(state.health, state.queue);
  const targetGpu = state.health?.targetGpu || state.summary?.targetGpu || gpus.find((gpu: any) => String(gpu.name || "").includes("2080"));
  const image = state.images?.mlImage || state.health?.image || {};
  const jobs = jobRows(state.queue);
  const projects = projectRows(state.projects);
  const projectTree = buildProjectTree(projects);
  const sourceProject = ui.sourceProject || projects[0]?.projectPath || "";
  const currentJobs = jobs.filter((job) => ["staged", "queued", "running"].includes(job.status));
  const completedJobs = jobs.filter((job) => job.status === "succeeded");
  const failedJobs = jobs.filter((job) => ["failed", "canceled"].includes(job.status));
  const terminalHistory = Array.isArray(state.history?.jobs) ? state.history.jobs.slice(0, 120) : [];
  const tabItems = [
    { id: "projects", label: "项目库", count: projects.length },
    { id: "current", label: "当前队列", count: currentJobs.length || Number(counts.staged || 0) + Number(counts.queued || 0) + Number(counts.running || 0) },
    { id: "completed", label: "已完成", count: completedJobs.length || Number(counts.succeeded || 0) },
    { id: "failed", label: "失败诊断", count: failedJobs.length || Number(counts.failed || 0) + Number(counts.canceled || 0) },
    { id: "gpu", label: "GPU/镜像", count: gpus.length },
  ];

  function queueTable(rows: any[], mode: string) {
    if (rows.length === 0) return h(EmptyState, { title: mode === "current" ? "当前队列为空" : "暂无记录", text: mode === "current" ? "从项目库选择或 fork project 后先加入待启动队列，再启动队列。" : "终态任务会显示耗时、exit code 和失败诊断。" });
    return h("div", { className: "table-wrap met-job-table" }, h("table", null,
      h("thead", null, h("tr", null, h("th", null, "状态"), h("th", null, "Project"), h("th", null, "Epoch"), h("th", null, "速度"), h("th", null, "ETA/耗时"), h("th", null, "GPU"), h("th", null, "Exit"), h("th", null, "更新时间"), h("th", null, "操作"))),
      h("tbody", null, rows.map((job: any) => {
        const progress = job.progress || {};
        const canCancel = ["staged", "queued", "running"].includes(job.status);
        const isActive = detail.kind === "job" && detail.key === job.id;
        return h("tr", { key: job.id, className: `met-click-row ${isActive ? "active" : ""}`, onClick: () => openJobDetail(job), "data-testid": `met-job-row-${safeDomId(job.id)}` },
          h("td", null, h(StatusBadge, { status: job.status }, statusLabel(job.status))),
          h("td", null, h("button", { type: "button", className: "met-inline-link", onClick: (event: any) => { event.stopPropagation(); openJobDetail(job); } }, job.projectPath), h("code", null, job.id)),
          h("td", null,
            h("span", null, `${progress.currentEpoch ?? "--"} / ${progress.epochTarget ?? job.epochTarget ?? "--"}`),
            h("div", { className: "met-progress" }, h("span", { style: { width: fmtPercent(progress.progressPercent) } })),
          ),
          h("td", null, h("strong", null, fmtEpochSpeed(jobEpochPerHour(job)))),
          h("td", null, job.status === "succeeded" || job.status === "failed" || job.status === "canceled" ? jobDuration(job) : (job.status === "running" ? `ETA ${fmtDuration(jobEtaSeconds(job))}` : "--")),
          h("td", null, job.gpuName || "--"),
          h("td", null, job.exitCode ?? "--"),
          h("td", null, fmtDate(job.updatedAt)),
          h("td", null,
            canCancel ? h("button", { type: "button", className: "ghost-btn mini", onClick: (event: any) => { event.stopPropagation(); cancelJob(job); }, disabled: state.actionBusy }, "取消") : null,
            h(RawButton, { title: `MET Job ${job.id}`, data: job, onOpen: onRaw, testId: `raw-met-job-${job.id}` }),
          ),
        );
      })),
    ));
  }

  function currentQueueSummary() {
    return h("div", { className: "met-queue-summary", "data-testid": "met-current-summary" },
      h(StatusBadge, { status: "staged" }, `待启动 ${counts.staged ?? 0}`),
      h(StatusBadge, { status: "queued" }, `排队中 ${counts.queued ?? 0}`),
      h(StatusBadge, { status: "running" }, `训练中 ${counts.running ?? 0}`),
      h("span", null, `最大并发 ${state.summary?.queue?.maxConcurrency ?? state.queue?.queue?.maxConcurrency ?? ui.maxConcurrency}`),
      h("span", null, `目标 GPU ${state.summary?.queue?.targetGpuName ?? state.queue?.queue?.targetGpuName ?? ui.targetGpuName}`),
    );
  }

  function projectFolderExpanded(path: string, depth: number): boolean {
    const explicit = ui.expandedProjectDirs[path];
    return explicit === undefined ? depth < 2 : Boolean(explicit);
  }

  function toggleProjectFolder(path: string, depth: number): void {
    const expanded = projectFolderExpanded(path, depth);
    patchUi({ expandedProjectDirs: { ...ui.expandedProjectDirs, [path]: !expanded } });
  }

  function renderProjectTreeNode(node: ProjectTreeNode): any {
    const paddingLeft = 8 + Math.max(0, node.depth) * 16;
    const isProject = Boolean(node.project);
    if (isProject) {
      const project = node.project;
      const selected = Boolean(ui.selectedProjects[project.projectPath]);
      const active = detail.kind === "project" && detail.key === project.projectPath;
      return h("div", {
        key: node.path,
        className: `met-tree-row project ${selected ? "selected" : ""} ${active ? "active" : ""}`,
        style: { paddingLeft },
        onClick: () => openProjectDetail(project),
        "data-testid": `met-project-node-${safeDomId(project.projectPath)}`,
      },
        h("div", { className: "met-tree-name" },
          h("input", {
            type: "checkbox",
            checked: selected,
            onClick: (event: any) => event.stopPropagation(),
            onChange: (event: any) => patchUi({ selectedProjects: { ...ui.selectedProjects, [project.projectPath]: event.target.checked } }),
            "data-testid": `met-project-checkbox-${safeDomId(project.projectPath)}`,
          }),
          h("button", { type: "button", className: "met-inline-link project-path", onClick: (event: any) => { event.stopPropagation(); openProjectDetail(project); } }, node.name),
        ),
        h("span", null, project.useModel || "--"),
        h("span", null, project.epochTrain ?? "--"),
        h("span", null, fmtPercent(project.progress?.progressPercent)),
        h("span", null, fmtEpochSpeed(project.progress?.epochPerHour)),
      );
    }
    const expanded = projectFolderExpanded(node.path, node.depth);
    return h(React.Fragment, { key: node.path },
      h("div", { className: "met-tree-row folder", style: { paddingLeft }, "data-testid": `met-project-folder-${safeDomId(node.path)}` },
        h("button", { type: "button", className: "met-tree-toggle", onClick: () => toggleProjectFolder(node.path, node.depth), "aria-label": expanded ? `折叠 ${node.path}` : `展开 ${node.path}` }, expanded ? "-" : "+"),
        h("strong", null, node.name),
        h("span", { className: "met-tree-count" }, `${node.count} projects`),
      ),
      expanded ? node.children.map((child) => renderProjectTreeNode(child)) : null,
    );
  }

  function renderKvGrid(items: Array<{ label: string; value: any; hint?: string }>) {
    return h("div", { className: "met-detail-kv" }, items.map((item) => h("div", { key: item.label, className: "met-detail-kv-item" },
      h("span", null, item.label),
      h("strong", null, fmtValue(item.value)),
      item.hint ? h("small", null, item.hint) : null,
    )));
  }

  function renderMetricList(title: string, items: Array<{ label: string; value: any; hint?: string }>) {
    return h("div", { className: "met-detail-section" },
      h("h3", null, title),
      renderKvGrid(items),
    );
  }

  function renderLayerTable(layers: any[]) {
    if (!Array.isArray(layers) || layers.length === 0) return h(EmptyState, { title: "模型层未上报", text: "等待 data/model_info.json 或 compute_analysis.json 生成。" });
    return h("div", { className: "table-wrap met-layer-table" }, h("table", null,
      h("thead", null, h("tr", null, h("th", null, "Layer"), h("th", null, "Type"), h("th", null, "Params"), h("th", null, "Trainable"), h("th", null, "Compute"))),
      h("tbody", null, layers.slice(0, 18).map((layer: any, index: number) => h("tr", { key: `${layer.name || "layer"}-${index}` },
        h("td", null, layer.name || `#${index + 1}`),
        h("td", null, layer.type || "--"),
        h("td", null, fmtNumber(layer.num_params)),
        h("td", null, layer.trainable === undefined ? "--" : String(Boolean(layer.trainable))),
        h("td", null, fmtNumber(layer.compute?.total ?? layer.estimated_cost?.weighted_units?.total)),
      ))),
    ));
  }

  function renderDataFiles(files: any[]) {
    const list = Array.isArray(files) ? files : [];
    if (list.length === 0) return h(EmptyState, { title: "data/ 暂无文件", text: "训练或评估完成后会生成 training_state、metrics、model_info 等文件。" });
    return h("div", { className: "met-file-chip-grid" }, list.slice(0, 48).map((file: string) => h("span", { key: file }, file)), list.length > 48 ? h("span", null, `+${list.length - 48}`) : null);
  }

  function renderLogTail(text: any) {
    const lines = String(text || "").replace(/\x1b\[[0-9;]*[A-Za-z]/g, "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(-12);
    if (lines.length === 0) return h(EmptyState, { title: "暂无日志尾部", text: "该任务未上报 logTail 或日志已轮转。" });
    return h("div", { className: "met-log-lines" }, lines.map((line, index) => h("div", { key: `${index}-${line.slice(0, 16)}` }, line)));
  }

  function renderDetailPanel() {
    if (detail.loading) return h("section", { className: "met-detail-panel", "data-testid": "met-detail-panel" },
      h("div", { className: "panel-head compact" },
        h("div", null,
          h("p", { className: "panel-eyebrow" }, "Detail Loading"),
          h(LoadingTitle, { title: "详情加载中", loading: true }),
        ),
      ),
      h(EmptyState, { title: "详情加载中", text: detail.title || "正在读取 D601 data/ 和 config.json" }),
    );
    if (detail.error) return h("section", { className: "met-detail-panel", "data-testid": "met-detail-panel" }, h(UniDeskErrorBanner, { error: detail.error, wide: true }));
    if (!detail.data) return h("section", { className: "met-detail-panel muted", "data-testid": "met-detail-panel" }, h(EmptyState, { title: "选择一个项目或任务查看详情", text: "项目库、当前队列、已完成和失败诊断中的行都可以点击；默认只展示结构化字段，原始 JSON 需显式点击按钮。" }));
    const payload = detailPayload(detail);
    const job = detailJob(detail);
    const config = objectValue(payload.config);
    const progress = objectValue(payload.progress || job.progress);
    const data = objectValue(payload.data);
    const metrics = objectValue(payload.metrics || data.metrics || progress.trainingInfo?.evaluation_metrics);
    const trainingInfo = objectValue(data.trainingInfo || progress.trainingInfo);
    const trainingState = objectValue(data.trainingState);
    const model = objectValue(payload.model || data.model);
    const layers = Array.isArray(model.modelSummary) && model.modelSummary.length > 0 ? model.modelSummary : model.computeLayers;
    const evalMetrics = objectValue(trainingInfo.evaluation_metrics);
    const title = detail.kind === "job" ? "训练任务详情" : "Project 详情";
    return h("section", { className: "met-detail-panel", "data-testid": "met-detail-panel" },
      h("div", { className: "panel-head compact" },
        h("div", null,
          h("p", { className: "panel-eyebrow" }, detail.kind === "job" ? "Job + Project Detail" : "Project Library Detail"),
          h(LoadingTitle, { title }),
          h("code", null, payload.projectPath || job.projectPath || detail.title),
        ),
        h("div", { className: "panel-actions" }, h(RawButton, { title: `MET ${title}`, data: detail.data, onOpen: onRaw, testId: "raw-met-detail" })),
      ),
      detail.kind === "job" ? renderMetricList("任务状态", [
        { label: "Job ID", value: job.id },
        { label: "状态", value: statusLabel(job.status) },
        { label: "GPU", value: job.gpuName },
        { label: "Exit Code", value: job.exitCode },
        { label: "耗时", value: jobDuration(job) },
        { label: "训练速度", value: fmtEpochSpeed(jobEpochPerHour({ ...job, progress })) },
      ]) : null,
      renderMetricList("config.json", [
        { label: "use_model", value: config.use_model },
        { label: "epoch_train", value: config.epoch_train },
        { label: "step_per_epoch", value: config.step_per_epoch },
        { label: "learning_rate", value: config.learning_rate },
        { label: "using_gpu", value: config.using_gpu },
        { label: "use_points", value: config.use_points },
        { label: "sample_rate", value: config.sample_rate },
        { label: "time_clipped_s", value: config.time_clipped_s },
        { label: "H_UNITS", value: config.H_UNITS },
        { label: "INNER_KAN_UNITS", value: config.INNER_KAN_UNITS },
        { label: "INNER_KAN_LAYERS", value: config.INNER_KAN_LAYERS },
        { label: "GRID_SIZE", value: config.GRID_SIZE },
        { label: "SPLINE_ORDER", value: config.SPLINE_ORDER },
        { label: "USE_FAST_MODEL", value: config.USE_FAST_MODEL },
        { label: "IIR_TRAINABLE", value: config.IIR_TRAINABLE },
      ]),
      renderMetricList("data/ 训练状态", [
        { label: "Epoch", value: `${progress.currentEpoch ?? trainingState.current_epoch ?? trainingState.completed_epoch ?? "--"} / ${progress.epochTarget ?? config.epoch_train ?? "--"}` },
        { label: "Progress", value: fmtPercent(progress.progressPercent) },
        { label: "Last Loss", value: progress.lastLoss ?? trainingState.loss },
        { label: "Last Val Loss", value: progress.lastValLoss ?? trainingState.val_loss },
        { label: "Min Loss", value: trainingInfo.min_loss ?? trainingState.min_loss },
        { label: "Min Val Loss", value: trainingInfo.min_val_loss ?? trainingState.min_val_loss },
        { label: "Log Lines", value: progress.logLineCount },
        { label: "ETA", value: fmtDuration(progress.etaSeconds ?? trainingState.remaining_time) },
        { label: "训练速度", value: fmtEpochSpeed(progress.epochPerHour ?? trainingState.smoothed_speed) },
        { label: "Training Alive", value: trainingState.training_alive },
      ]),
      renderMetricList("模型参数", [
        { label: "Model Type", value: model.modelType ?? config.use_model },
        { label: "Total Params", value: model.totalParams, hint: model.totalParams === null || model.totalParams === undefined ? "未上报" : "data/model_info.json" },
        { label: "Trainable", value: model.trainableParams },
        { label: "Non-trainable", value: model.nonTrainableParams },
        { label: "Compute Cost", value: model.computeCost },
        { label: "Estimate Status", value: model.estimateStatus },
        { label: "Unsupported Layers", value: model.unsupportedLayerCount },
      ]),
      renderMetricList("指标", [
        { label: "train_loss", value: metrics.train_loss ?? evalMetrics.train_loss },
        { label: "val_loss", value: metrics.val_loss ?? evalMetrics.val_loss },
        { label: "train_mae", value: metrics.train_mae ?? evalMetrics.train_mae },
        { label: "val_mae", value: metrics.val_mae ?? evalMetrics.val_mae },
        { label: "train_afmae", value: metrics.train_afmae ?? evalMetrics.train_afmae },
        { label: "val_afmae", value: metrics.val_afmae ?? evalMetrics.val_afmae },
        { label: "freq_drift_hz", value: metrics.freq_drift_hz },
        { label: "sens_drift_percent", value: metrics.sens_drift_percent },
        { label: "linearity_percent", value: metrics.linearity_percent },
        { label: "weights_source", value: metrics.weights_source ?? evalMetrics.weights_source },
        { label: "lr min/mean/max", value: `${fmtValue(trainingInfo.learning_rate_min)} / ${fmtValue(trainingInfo.learning_rate_mean)} / ${fmtValue(trainingInfo.learning_rate_max)}` },
      ]),
      h("div", { className: "met-detail-section" }, h("h3", null, "模型层"), renderLayerTable(layers)),
      h("div", { className: "met-detail-section" }, h("h3", null, "data/ 文件"), renderDataFiles(data.files)),
      detail.kind === "job" ? h("div", { className: "met-detail-section" }, h("h3", null, "日志尾部"), renderLogTail(objectValue(detail.data).logTail)) : null,
    );
  }

  return h("div", { className: "met-page", "data-testid": "met-nonlinear-page" },
    h(Panel, {
      title: "MET Nonlinear 训练编排",
      eyebrow: "D601 GPU 用户服务",
      loading: state.loading || state.actionBusy,
      actions: h("div", { className: "panel-actions" },
        h("button", { type: "button", className: "ghost-btn", onClick: load, disabled: state.loading, "data-testid": "met-refresh-button" }, state.loading ? "刷新中" : "刷新"),
        h(RawButton, { title: "MET Nonlinear 用户服务", data: service, onOpen: onRaw, testId: "raw-met-service" }),
      ),
    },
      h("div", { className: "findjob-hero" },
        h("div", null,
          h("div", { className: "node-version-line" },
            h(StatusBadge, { status: runtime.providerStatus === "online" ? "online" : "warn" }, runtime.providerStatus || "unknown"),
            h("span", null, service.providerId),
            h("span", null, backend.public ? "公网暴露" : "仅 UniDesk frontend 代理访问"),
          ),
          h("p", { className: "muted paragraph" }, service.description),
        ),
        h("div", { className: "microservice-ref-card" },
          h("span", null, "Repo"),
          h("strong", null, repository.url || "--"),
          h("code", null, repository.commitId || "--"),
        ),
        h("div", { className: "microservice-ref-card" },
          h("span", null, "D601 Docker"),
          h("strong", null, `${backend.nodeBindHost || "--"}:${backend.nodePort || "--"}`),
          h("code", null, `${repository.composeFile || "--"} / ${repository.containerName || "--"}`),
        ),
      ),
      h(UniDeskErrorBanner, { error: state.error, wide: true }),
      ui.actionMessage ? h("div", { className: "met-action-log", "data-testid": "met-action-message" }, ui.actionMessage) : null,
    ),
    h("div", { className: "met-grid" },
      h(Panel, { title: "核心状态", eyebrow: state.refreshedAt ? `Updated ${fmtClock(state.refreshedAt)}` : "Queue + GPU", loading: state.loading },
        h("div", { className: "metric-grid" },
          h(MetricCard, { label: "Staged", value: counts.staged ?? 0, hint: "加入队列未开始", tone: Number(counts.staged || 0) > 0 ? "warn" : "" }),
          h(MetricCard, { label: "Queued", value: counts.queued ?? 0, hint: "排队等待调度", tone: Number(counts.queued || 0) > 0 ? "warn" : "" }),
          h(MetricCard, { label: "Running", value: counts.running ?? 0, hint: `max ${state.summary?.queue?.maxConcurrency ?? state.queue?.queue?.maxConcurrency ?? "--"}`, tone: Number(counts.running || 0) > 0 ? "ok" : "" }),
          h(MetricCard, { label: "Succeeded", value: counts.succeeded ?? 0, hint: "已完成" }),
          h(MetricCard, { label: "Failed", value: counts.failed ?? 0, hint: "需要诊断", tone: Number(counts.failed || 0) > 0 ? "warn" : "" }),
          h(MetricCard, { label: "2080Ti Free", value: targetGpu ? fmtPercent(Number(targetGpu.freeRatio) * 100) : "--", hint: targetGpu ? `${targetGpu.memoryFreeMiB}/${targetGpu.memoryTotalMiB} MiB` : "等待 GPU 上报" }),
          h(MetricCard, { label: "ML Image", value: image.present ? "READY" : "MISSING", hint: image.image || "met-nonlinear-ml:tf26", tone: image.present ? "ok" : "warn" }),
          h(MetricCard, { label: "Health", value: state.health?.ok ? "OK" : "--", hint: "D601 /health" }),
        ),
      ),
      h(Panel, { title: "队列控制", eyebrow: "Downloader-like staging", loading: state.actionBusy },
        h("div", { className: "met-control-strip" },
          h("label", null, "最大并发", h("input", { type: "number", min: 1, max: 16, value: ui.maxConcurrency, "data-testid": "met-max-concurrency-input", onChange: (event: any) => patchUi({ maxConcurrency: event.target.value }) })),
          h("label", null, "目标 GPU", h("input", { value: ui.targetGpuName, "data-testid": "met-target-gpu-input", onChange: (event: any) => patchUi({ targetGpuName: event.target.value }) })),
          h("button", { type: "button", className: "ghost-btn", onClick: saveQueueSettings, disabled: state.actionBusy, "data-testid": "met-save-settings-button" }, "保存设置"),
          h("button", { type: "button", className: "primary-btn", onClick: startQueue, disabled: state.actionBusy || Number(counts.staged || 0) === 0, "data-testid": "met-start-queue-button" }, "启动队列"),
        ),
        h("p", { className: "muted paragraph" }, "Project 先进入待启动队列，不会立即训练；点击启动队列后才切换为排队中，并由 D601 scheduler 按最大并发和 2080Ti 显存策略调度。"),
      ),
      h("section", { className: "panel met-workspace" },
        h("div", { className: "met-tabs", role: "tablist" }, tabItems.map((tab) => h("button", {
          key: tab.id,
          type: "button",
          className: ui.activeTab === tab.id ? "active" : "",
          onClick: () => patchUi({ activeTab: tab.id }),
          "data-testid": `met-tab-${tab.id}`,
        }, `${tab.label} ${tab.count}`))),
        h("div", { className: "panel-body" },
          ui.activeTab === "projects" ? h("div", { className: "met-form-grid", "data-testid": "met-projects-pane" },
            h("div", { className: "met-fork-card" },
              h("h3", null, "Fork Project"),
              h("label", null, "源 Project", h("select", { value: sourceProject, "data-testid": "met-source-project-select", onChange: (event: any) => patchUi({ sourceProject: event.target.value }) }, projects.map((project: any) => h("option", { key: project.projectPath, value: project.projectPath }, `${project.projectPath} · ${project.useModel || "model?"}`)))),
              h("label", null, "Fork 数量", h("input", { type: "number", min: 1, max: 100, value: ui.forkCount, "data-testid": "met-fork-count-input", onChange: (event: any) => patchUi({ forkCount: event.target.value }) })),
              h("label", null, "训练轮数", h("input", { type: "number", min: 1, max: 100000, value: ui.forkEpochs, "data-testid": "met-fork-epochs-input", onChange: (event: any) => patchUi({ forkEpochs: event.target.value }) })),
              h("label", null, "目标前缀", h("input", { value: ui.forkPrefix, "data-testid": "met-fork-prefix-input", onChange: (event: any) => patchUi({ forkPrefix: event.target.value }) })),
              h("button", { type: "button", className: "primary-btn", onClick: forkProjects, disabled: state.actionBusy || !sourceProject, "data-testid": "met-fork-button" }, "Fork Project"),
              h("p", { className: "muted paragraph" }, "Fork 只创建新 Project 并自动勾选，不会直接训练；需要在右侧确认后加入待启动队列。"),
            ),
            h("div", { className: "met-project-list" },
              h("div", { className: "panel-head compact" }, h("div", null, h("p", { className: "panel-eyebrow" }, `Existing Projects · ${(state.projects?.roots || []).map((root: any) => `${root.root} ${root.count}`).join(" / ")}`), h(LoadingTitle, { title: "选择已有 Project", loading: state.loading || state.actionBusy })), h("button", { type: "button", className: "ghost-btn", onClick: stageSelectedProjects, disabled: state.actionBusy || selectedProjectPaths().length === 0, "data-testid": "met-stage-selected-button" }, `加入待启动队列 (${selectedProjectPaths().length})`)),
              projects.length === 0 ? h(EmptyState, { title: "暂无 project", text: "等待 D601 返回 /api/projects" }) :
                h("div", { className: "met-project-table", "data-testid": "met-project-tree" },
                  h("div", { className: "met-tree-header" }, h("span", null, "文件树 Project"), h("span", null, "Model"), h("span", null, "Epochs"), h("span", null, "Progress"), h("span", null, "速度")),
                  projectTree.children.map((node) => renderProjectTreeNode(node)),
                ),
            ),
            renderDetailPanel(),
          ) : null,
          ui.activeTab === "current" ? h("div", { "data-testid": "met-current-pane" }, currentQueueSummary(), queueTable(currentJobs, "current"), renderDetailPanel(), h("div", { className: "panel-actions inline-actions" }, h(RawButton, { title: "MET Queue", data: state.queue, onOpen: onRaw, testId: "raw-met-queue" }))) : null,
          ui.activeTab === "completed" ? h("div", { "data-testid": "met-completed-pane" }, queueTable(completedJobs.length > 0 ? completedJobs : terminalHistory.filter((job: any) => job.status === "succeeded"), "completed"), renderDetailPanel()) : null,
          ui.activeTab === "failed" ? h("div", { "data-testid": "met-failed-pane" }, queueTable(failedJobs.length > 0 ? failedJobs : terminalHistory.filter((job: any) => ["failed", "canceled"].includes(job.status)), "failed"), renderDetailPanel(), h("div", { className: "panel-actions inline-actions" }, h(RawButton, { title: "MET History", data: state.history, onOpen: onRaw, testId: "raw-met-history" }))) : null,
          ui.activeTab === "gpu" ? h("div", { className: "met-gpu-pane", "data-testid": "met-gpu-pane" },
            gpus.length === 0 ? h(EmptyState, { title: "暂无 GPU 上报", text: "等待 D601 met-nonlinear-ts 或 ML image 提供 nvidia-smi 数据" }) :
              h("div", { className: "table-wrap" }, h("table", null,
                h("thead", null, h("tr", null, h("th", null, "Index"), h("th", null, "Name"), h("th", null, "Free"), h("th", null, "Policy"))),
                h("tbody", null, gpus.map((gpu: any) => h("tr", { key: gpu.index },
                  h("td", null, gpu.index),
                  h("td", null, gpu.name),
                  h("td", null, `${gpu.memoryFreeMiB} / ${gpu.memoryTotalMiB} MiB`, h("div", { className: "met-progress" }, h("span", { style: { width: fmtPercent(Number(gpu.freeRatio) * 100) } }))),
                  h("td", null, String(gpu.name || "").includes("2080") ? "target 2080Ti, <20% 限制并发" : "non-target"),
                ))),
              )),
            h("div", { className: "panel-actions inline-actions" }, h(RawButton, { title: "MET Images", data: state.images, onOpen: onRaw, testId: "raw-met-images" })),
          ) : null,
        ),
      ),
    ),
  );
}
