"use client";

import {
  Activity,
  ArrowRight,
  Camera,
  ChartLine,
  CheckCircle2,
  ChevronDown,
  ChevronUp,
  CircleDot,
  Eye,
  GitBranch,
  ImageIcon,
  Loader2,
  MessageSquareText,
  MonitorSmartphone,
  MoreHorizontal,
  Pause,
  Play,
  Server,
  Trash2,
  Wrench,
  XCircle,
  type LucideIcon,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";

import { SectionAnchorTabs } from "@/components/editor/section-anchor-tabs";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { DataList, type DataListColumn } from "@/components/ui/data-list";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog";
import { FilterPills } from "@/components/ui/filter-pills";
import { ListToolbar } from "@/components/ui/list-toolbar";
import {
  ListPageLayout,
  ListPageSection,
} from "@/components/layout/list-page-layout";
import { SearchInput } from "@/components/ui/search-input";
import {
  API_BASE,
  conversationsApi,
  type Conversation,
  type ConversationDetail,
  type ConversationMessage,
} from "@/lib/api";
import { cn } from "@/lib/utils";

const PAGE_SIZE = 5;
const channelFilters = ["全部", "WebRTC", "WebSocket"] as const;
type ChannelFilter = (typeof channelFilters)[number];
type SortOrder = "newest" | "oldest";
type DetailTabId = "session" | "analysis";

const detailTabs = [
  { id: "session", label: "会话信息" },
  { id: "analysis", label: "分析" },
] as const;

function formatDate(value?: string | null): string {
  if (!value) return "—";
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) return "—";
  return date.toLocaleString("zh-CN", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
}

function durationLabel(row: Conversation): string {
  if (!row.endedAt) return "进行中";
  const seconds = Math.max(
    0,
    Math.round(
      (new Date(row.endedAt).getTime() - new Date(row.startedAt).getTime()) /
        1000,
    ),
  );
  const minutes = Math.floor(seconds / 60);
  return minutes ? `${minutes} 分 ${seconds % 60} 秒` : `${seconds} 秒`;
}

function channelLabel(channel: string): string {
  if (channel === "webrtc") return "WebRTC";
  if (channel === "websocket") return "WebSocket";
  return channel;
}

function channelValue(filter: ChannelFilter): string | undefined {
  if (filter === "WebRTC") return "webrtc";
  if (filter === "WebSocket") return "websocket";
  return undefined;
}

function statusLabel(status: string): string {
  if (status === "active") return "进行中";
  if (status === "failed") return "失败";
  return "已结束";
}

type TraceEvent = Record<string, unknown>;
type WorkflowNode = { label: string; type: string };
type TraceTone = "default" | "success" | "error" | "user";

type TracePresentation = {
  title: string;
  description: string;
  icon: LucideIcon;
  tone: TraceTone;
};

function recordValue(value: unknown): Record<string, unknown> {
  return value && typeof value === "object" && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {};
}

function textValue(value: unknown): string {
  return typeof value === "string" ? value : "";
}

function numberValue(value: unknown): number | null {
  return typeof value === "number" && Number.isFinite(value) ? value : null;
}

function workflowNodes(detail: ConversationDetail): Map<string, WorkflowNode> {
  const snapshot = recordValue(detail.extra.workflow?.snapshot);
  const nodes = Array.isArray(snapshot.nodes) ? snapshot.nodes : [];
  return new Map(
    nodes.flatMap((rawNode) => {
      const node = recordValue(rawNode);
      const id = textValue(node.id);
      if (!id) return [];
      const data = recordValue(node.data);
      const type = textValue(node.type) || "node";
      return [
        [
          id,
          {
            label: textValue(data.name) || type,
            type,
          },
        ] as const,
      ];
    }),
  );
}

function nodeLabel(nodes: Map<string, WorkflowNode>, nodeId: unknown): string {
  const id = textValue(nodeId);
  const node = nodes.get(id);
  return node ? `「${node.label}」` : id ? `「${id}」` : "当前节点";
}

function toolLabel(toolType: unknown): string {
  if (toolType === "client") return "客户端工具";
  if (toolType === "http") return "HTTP 服务端工具";
  if (toolType === "mcp") return "MCP 服务端工具";
  if (toolType === "system") return "系统工具";
  return "工具";
}

function tracePresentation(
  event: TraceEvent,
  nodes: Map<string, WorkflowNode>,
  startedEvent?: TraceEvent,
): TracePresentation {
  const eventName = textValue(event.event);
  const node = nodeLabel(nodes, event.nodeId);
  const outcome = recordValue(event.outcome);
  const duration = numberValue(outcome.durationMs);
  const tool = toolLabel(event.toolType ?? startedEvent?.toolType);

  switch (eventName) {
    case "node_entered":
      return {
        title: `进入节点 ${node}`,
        description: `节点类型：${textValue(event.nodeType) || nodes.get(textValue(event.nodeId))?.type || "未知"}`,
        icon: CircleDot,
        tone: "default",
      };
    case "node_exited":
      return {
        title: `离开节点 ${node}`,
        description: "当前节点处理完成，准备选择下一步。",
        icon: ArrowRight,
        tone: "default",
      };
    case "edge_selected":
      return {
        title: `节点转移：${nodeLabel(nodes, event.sourceNodeId)} → ${nodeLabel(nodes, event.targetNodeId)}`,
        description: `路由方式：${textValue(event.edgeMode) || "always"}`,
        icon: GitBranch,
        tone: "default",
      };
    case "action_started":
      return {
        title: `开始调用${tool}`,
        description: `${node} · ${textValue(event.toolId) || "未命名工具"}`,
        icon: tool === "客户端工具" ? MonitorSmartphone : Server,
        tone: "default",
      };
    case "action_completed":
      return {
        title: `${tool}执行成功`,
        description: `${node}${duration === null ? "" : ` · ${duration} ms`}`,
        icon: CheckCircle2,
        tone: "success",
      };
    case "action_failed":
    case "action_cancelled":
      return {
        title: `${tool}${eventName === "action_failed" ? "执行失败" : "已取消"}`,
        description: textValue(recordValue(outcome.error).message) || node,
        icon: XCircle,
        tone: "error",
      };
    case "tool_started":
      return {
        title: `开始调用${tool}`,
        description: `${node} · ${textValue(event.toolName) || textValue(event.functionName) || textValue(event.toolId) || "未命名工具"}`,
        icon: tool === "客户端工具" ? MonitorSmartphone : Server,
        tone: "default",
      };
    case "tool_completed":
      return {
        title: `${tool}调用成功`,
        description: `${textValue(event.toolName) || textValue(event.functionName) || node}${numberValue(event.durationMs) === null ? "" : ` · ${numberValue(event.durationMs)} ms`}`,
        icon: CheckCircle2,
        tone: "success",
      };
    case "tool_failed":
      return {
        title: `${tool}调用失败`,
        description: textValue(event.error) || textValue(event.toolName) || node,
        icon: XCircle,
        tone: "error",
      };
    case "client_tool_started":
      return {
        title:
          event.functionName === "show_message"
            ? "向客户端显示交互消息"
            : "向客户端下发工具调用",
        description: textValue(event.functionName) || "客户端工具",
        icon: MonitorSmartphone,
        tone: "default",
      };
    case "client_tool_completed": {
      const userAction = textValue(event.userAction);
      return {
        title: userAction === "confirmed" ? "用户已确认" : "客户端交互已完成",
        description: userAction
          ? `用户操作：${userAction}`
          : textValue(event.functionName) || textValue(event.status) || "执行成功",
        icon: CheckCircle2,
        tone: userAction ? "user" : "success",
      };
    }
    case "client_tool_failed":
      return {
        title: "客户端交互失败",
        description: textValue(event.error) || textValue(event.functionName),
        icon: XCircle,
        tone: "error",
      };
    case "message_started":
      return event.requiresConfirmation
        ? {
            title: "等待用户确认",
            description: `${node}向客户端显示了确认消息。`,
            icon: MonitorSmartphone,
            tone: "user",
          }
        : {
            title: "开始播放固定消息",
            description: node,
            icon: Activity,
            tone: "default",
          };
    case "message_completed": {
      const action = textValue(event.action);
      return {
        title: action === "confirmed" ? "用户已确认" : "消息步骤已完成",
        description: `${node}${action ? ` · 操作：${action}` : ""}`,
        icon: CheckCircle2,
        tone: action ? "user" : "success",
      };
    }
    case "message_interrupted":
      return {
        title: "用户输入打断消息",
        description: node,
        icon: Activity,
        tone: "user",
      };
    case "message_failed":
      return {
        title: "消息步骤失败",
        description: textValue(event.error) || node,
        icon: XCircle,
        tone: "error",
      };
    case "variables_updated": {
      const names = Array.isArray(event.variableNames)
        ? event.variableNames.filter((name): name is string => typeof name === "string")
        : [];
      return {
        title: "会话变量已更新",
        description: names.length ? names.join("、") : node,
        icon: Wrench,
        tone: "default",
      };
    }
    case "variables_snapshot":
      return {
        title: "记录会话变量快照",
        description: node,
        icon: Wrench,
        tone: "default",
      };
    case "workflow_ended":
      return {
        title: "工作流已结束",
        description: `${node} · ${textValue(event.outcome) || "success"}`,
        icon: CheckCircle2,
        tone: "success",
      };
    default:
      return {
        title: eventName || "运行事件",
        description: node,
        icon: Activity,
        tone: "default",
      };
  }
}

function artifactUrl(path: string): string {
  if (/^(https?:|data:|blob:)/.test(path)) return path;
  return `${API_BASE}${path.startsWith("/") ? path : `/${path}`}`;
}

export function HistoryPage() {
  const [rows, setRows] = useState<Conversation[]>([]);
  const [total, setTotal] = useState(0);
  const [currentPage, setCurrentPage] = useState(1);
  const [search, setSearch] = useState("");
  const [filter, setFilter] = useState<ChannelFilter>("全部");
  const [sortOrder, setSortOrder] = useState<SortOrder>("newest");
  const [loading, setLoading] = useState(true);
  const [loadError, setLoadError] = useState("");

  const [dialogOpen, setDialogOpen] = useState(false);
  const [detail, setDetail] = useState<ConversationDetail | null>(null);
  const [detailLoading, setDetailLoading] = useState(false);
  const [detailError, setDetailError] = useState("");
  const [detailTab, setDetailTab] = useState<DetailTabId>("session");
  const [deletingId, setDeletingId] = useState<string | null>(null);
  const skipRowClickRef = useRef(false);

  const clearSkipRowClick = useCallback(() => {
    window.setTimeout(() => {
      skipRowClickRef.current = false;
    }, 200);
  }, []);

  const load = useCallback(async () => {
    setLoading(true);
    setLoadError("");
    try {
      const result = await conversationsApi.list({
        page: currentPage,
        pageSize: PAGE_SIZE,
        search: search.trim() || undefined,
        channel: channelValue(filter),
        sortOrder,
      });
      setRows(result.items);
      setTotal(result.total);
    } catch (error) {
      setLoadError(error instanceof Error ? error.message : "加载历史记录失败");
    } finally {
      setLoading(false);
    }
  }, [currentPage, filter, search, sortOrder]);

  useEffect(() => {
    // Initial and query-driven external API synchronization.
    // eslint-disable-next-line react-hooks/set-state-in-effect
    void load();
  }, [load]);

  const openDetail = useCallback(async (conversation: Conversation) => {
    setDialogOpen(true);
    setDetailTab("session");
    setDetail(null);
    setDetailError("");
    setDetailLoading(true);
    try {
      setDetail(await conversationsApi.get(conversation.id));
    } catch (error) {
      setDetailError(
        error instanceof Error ? error.message : "对话内容加载失败",
      );
    } finally {
      setDetailLoading(false);
    }
  }, []);

  useEffect(() => {
    if (
      !dialogOpen ||
      !detail ||
      !["pending", "processing"].includes(detail.analysis.status)
    ) {
      return;
    }
    let stopped = false;
    let loadingLatest = false;
    const timer = window.setInterval(() => {
      if (loadingLatest) return;
      loadingLatest = true;
      void conversationsApi
        .get(detail.id)
        .then((latest) => {
          if (!stopped) setDetail(latest);
        })
        .catch(() => undefined)
        .finally(() => {
          loadingLatest = false;
        });
    }, 2000);
    return () => {
      stopped = true;
      window.clearInterval(timer);
    };
  }, [detail, dialogOpen]);

  const remove = useCallback(
    async (conversation: Conversation) => {
      const label = conversation.assistantName || "调试会话";
      skipRowClickRef.current = true;
      const confirmed = window.confirm(`确认删除对话“${label}”？`);
      if (!confirmed) {
        clearSkipRowClick();
        return;
      }
      setDeletingId(conversation.id);
      try {
        await conversationsApi.remove(conversation.id);
        if (detail?.id === conversation.id) {
          setDialogOpen(false);
          setDetail(null);
        }
        if (rows.length === 1 && currentPage > 1) {
          setCurrentPage(currentPage - 1);
        } else {
          await load();
        }
      } catch (error) {
        setLoadError(error instanceof Error ? error.message : "删除失败");
      } finally {
        setDeletingId(null);
        clearSkipRowClick();
      }
    },
    [clearSkipRowClick, currentPage, detail, load, rows.length],
  );

  const handleRowClick = useCallback(
    (conversation: Conversation) => {
      if (skipRowClickRef.current) return;
      void openDetail(conversation);
    },
    [openDetail],
  );

  function changeSearch(value: string) {
    setSearch(value);
    setCurrentPage(1);
  }

  function changeFilter(value: ChannelFilter) {
    setFilter(value);
    setCurrentPage(1);
  }

  function toggleSortOrder() {
    setSortOrder((value) => (value === "newest" ? "oldest" : "newest"));
    setCurrentPage(1);
  }

  const columns = useMemo<DataListColumn<Conversation>[]>(
    () => [
      {
        key: "assistant",
        header: "助手名称",
        width: "md:w-[360px]",
        cell: (row) => (
          <>
            <div className="truncate font-medium text-foreground">
              {row.assistantName || "调试会话"}
            </div>
            <div className="mt-1 truncate font-mono text-xs text-muted-soft">
              {row.id}
            </div>
          </>
        ),
      },
      {
        key: "channel",
        header: "接入通道",
        width: "md:w-[150px]",
        cell: (row) => (
          <Badge
            variant="secondary"
            className="h-6 bg-surface-strong px-3 text-muted-foreground"
          >
            {channelLabel(row.channel)}
          </Badge>
        ),
      },
      {
        key: "status",
        header: "状态",
        width: "md:w-[120px]",
        cell: (row) => (
          <Badge
            variant={row.status === "failed" ? "destructive" : "outline"}
            className="h-6 px-3"
          >
            {statusLabel(row.status)}
          </Badge>
        ),
      },
      {
        key: "messages",
        header: "消息数",
        width: "md:w-[100px]",
        cellClassName: "tabular-nums text-muted-foreground",
        cell: (row) => `${row.messageCount} 条`,
      },
      {
        key: "duration",
        header: "对话时长",
        width: "md:w-[130px]",
        cellClassName: "tabular-nums text-muted-foreground",
        cell: durationLabel,
      },
      {
        key: "startedAt",
        width: "md:w-[190px]",
        header: (
          <button
            type="button"
            onClick={(event) => {
              event.stopPropagation();
              toggleSortOrder();
            }}
            className="caption-label -mx-2 inline-flex items-center gap-1 rounded-md px-2 py-1 text-muted-soft transition-colors hover:bg-surface-strong hover:text-foreground"
            aria-label={
              sortOrder === "newest"
                ? "当前按最近开始排序，点击切换为最早开始"
                : "当前按最早开始排序，点击切换为最近开始"
            }
          >
            开始时间
            {sortOrder === "newest" ? (
              <ChevronDown size={13} />
            ) : (
              <ChevronUp size={13} />
            )}
          </button>
        ),
        cellClassName:
          "whitespace-nowrap tabular-nums text-muted-foreground",
        cell: (row) => formatDate(row.startedAt),
      },
      {
        key: "actions",
        header: "操作",
        align: "right",
        cell: (row) => (
          <div
            className="flex justify-end gap-2"
            onClick={(event) => event.stopPropagation()}
            onPointerDown={(event) => event.stopPropagation()}
          >
            <Button
              variant="outline"
              size="sm"
              className="gap-1.5 border-hairline-strong text-xs text-muted-foreground hover:text-foreground"
              onClick={(event) => {
                event.stopPropagation();
                void openDetail(row);
              }}
            >
              <Eye size={14} />
              查看
            </Button>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button
                  variant="outline"
                  size="icon-sm"
                  className="border-hairline-strong text-muted-foreground hover:text-foreground"
                  aria-label={`${row.assistantName || row.id} 更多操作`}
                  onClick={(event) => event.stopPropagation()}
                >
                  <MoreHorizontal size={15} />
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent
                align="end"
                className="w-32 min-w-32 rounded-xl border border-hairline bg-popover p-1"
              >
                <DropdownMenuItem
                  variant="destructive"
                  className="rounded-lg"
                  disabled={deletingId === row.id}
                  onSelect={(event) => {
                    event.preventDefault();
                    window.setTimeout(() => void remove(row), 0);
                  }}
                >
                  {deletingId === row.id ? (
                    <Loader2 size={14} className="animate-spin" />
                  ) : (
                    <Trash2 size={14} />
                  )}
                  删除
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </div>
        ),
      },
    ],
    [deletingId, openDetail, remove, sortOrder],
  );

  const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));
  const safeCurrentPage = Math.min(currentPage, totalPages);
  const pageStart = (safeCurrentPage - 1) * PAGE_SIZE;
  const pageEnd = pageStart + rows.length;

  return (
    <>
      <ListPageLayout
        title="历史记录"
        description="按会话查看对话、照片、工具调用、用户操作与工作流运行轨迹。"
      >
        <ListPageSection>
        <ListToolbar
          filters={
            <FilterPills
              options={channelFilters}
              value={filter}
              onChange={changeFilter}
            />
          }
          search={
            <SearchInput
              value={search}
              onChange={changeSearch}
              placeholder="搜索助手名称或会话 ID"
              className="lg:w-[320px]"
            />
          }
        />

        <DataList
          columns={columns}
          rows={rows}
          rowKey={(row) => row.id}
          loading={loading}
          error={loadError || null}
          onRetry={() => void load()}
          onRowClick={handleRowClick}
          empty={{
            title:
              total === 0 && !search && filter === "全部"
                ? "暂无对话记录"
                : "未找到匹配的对话记录",
            description:
              total === 0 && !search && filter === "全部"
                ? "完成一次助手通话后，文本历史会显示在这里。"
                : "请调整关键词或筛选条件后再试。",
          }}
          pagination={{
            page: safeCurrentPage,
            totalPages,
            onPageChange: setCurrentPage,
            summary:
              total === 0
                ? "没有数据"
                : `显示 ${pageStart + 1}-${Math.min(pageEnd, total)} / 共 ${total} 次会话`,
          }}
        />
      </ListPageSection>
      </ListPageLayout>

      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
        <DialogContent className="flex h-[min(94vh,980px)] w-full max-w-[calc(100%-1.5rem)] flex-col gap-0 overflow-hidden p-0 sm:max-w-7xl">
          <DialogHeader className="shrink-0 gap-1.5 px-5 py-4 sm:px-6">
            <DialogTitle className="flex items-center gap-2">
              <MessageSquareText size={18} />
              {detail?.assistantName || "对话详情"}
            </DialogTitle>
            <DialogDescription>
              {detail
                ? `${formatDate(detail.startedAt)} · ${channelLabel(detail.channel)} · ${detail.messageCount} 条消息`
                : "查看本次会话的完整对话与运行记录。"}
            </DialogDescription>
          </DialogHeader>

          <div className="flex min-h-0 flex-1 flex-col overflow-hidden border-t border-hairline sm:flex-row">
            <aside className="flex min-h-0 w-full shrink-0 flex-col border-b border-hairline sm:w-1/2 sm:border-b-0 sm:border-r">
              <SectionAnchorTabs
                ariaLabel="会话详情分区"
                sections={detailTabs}
                activeSectionId={detailTab}
                onSelect={(sectionId) =>
                  setDetailTab(sectionId as DetailTabId)
                }
                className="bg-popover px-3 pt-2 sm:px-4"
              />
              <div className="scrollbar-subtle min-h-0 flex-1 overflow-y-auto p-4 sm:p-5">
                {detailLoading && (
                  <div className="flex min-h-48 items-center justify-center gap-2 text-muted-foreground">
                    <Loader2 size={16} className="animate-spin" />
                    正在加载
                  </div>
                )}
                {!detailLoading && detailError && (
                  <div className="flex min-h-48 items-center justify-center text-sm text-destructive">
                    {detailError}
                  </div>
                )}
                {detail && detailTab === "session" && (
                  <SessionInfoPanel detail={detail} />
                )}
                {detail && detailTab === "analysis" && (
                  <AnalysisPanel analysis={detail.analysis} />
                )}
              </div>
            </aside>

            <main className="flex min-h-0 min-w-0 w-full flex-1 flex-col sm:w-1/2">
              <div className="flex shrink-0 items-center justify-between gap-3 border-b border-hairline px-4 py-2.5 sm:px-5">
                <div className="text-sm font-medium text-foreground">
                  对话记录转写
                </div>
                {detail && (
                  <div className="flex flex-wrap gap-1.5">
                    <Badge
                      variant="secondary"
                      className="gap-1 bg-surface-strong text-muted-foreground"
                    >
                      <MessageSquareText size={11} />
                      {detail.messages.length}
                    </Badge>
                    {detail.messages.some((message) =>
                      (message.artifacts ?? []).some(
                        (item) => item.kind === "image",
                      ),
                    ) && (
                      <Badge
                        variant="secondary"
                        className="gap-1 bg-surface-strong text-muted-foreground"
                      >
                        <ImageIcon size={11} />
                        含图片
                      </Badge>
                    )}
                  </div>
                )}
              </div>
              <div className="scrollbar-subtle min-h-0 flex-1 overflow-y-auto p-4 sm:p-5">
                {detailLoading && (
                  <div className="flex min-h-48 items-center justify-center gap-2 text-muted-foreground">
                    <Loader2 size={16} className="animate-spin" />
                    正在加载对话内容
                  </div>
                )}
                {!detailLoading && detailError && (
                  <div className="flex min-h-48 items-center justify-center text-destructive">
                    {detailError}
                  </div>
                )}
                {detail && <TranscriptPanel detail={detail} />}
              </div>
            </main>
          </div>

          <HistoryPlaybackBar key={detail?.id ?? "empty"} detail={detail} />
        </DialogContent>
      </Dialog>
    </>
  );
}

function Metadata({ label, value }: { label: string; value: string }) {
  return (
    <div className="min-w-0">
      <div className="text-xs text-muted-soft">{label}</div>
      <div className="mt-1 truncate font-medium text-foreground">{value}</div>
    </div>
  );
}

function SessionInfoPanel({ detail }: { detail: ConversationDetail }) {
  const nodes = workflowNodes(detail);
  const trace = (detail.extra.workflowTrace ?? []).map(recordValue);
  const toolCount = trace.filter(
    (event) => event.event === "action_started" || event.event === "tool_started",
  ).length;
  const transitionCount = trace.filter(
    (event) => event.event === "edge_selected",
  ).length;
  const imageCount = detail.messages.reduce(
    (count, message) =>
      count +
      (message.artifacts ?? []).filter((item) => item.kind === "image").length,
    0,
  );

  return (
    <div className="space-y-4">
      <div className="grid gap-3 sm:grid-cols-2">
        <Metadata label="接入通道" value={channelLabel(detail.channel)} />
        <Metadata label="运行模式" value={detail.runtimeMode} />
        <Metadata label="对话时长" value={durationLabel(detail)} />
        <Metadata label="状态" value={statusLabel(detail.status)} />
        <Metadata label="开始时间" value={formatDate(detail.startedAt)} />
        <Metadata label="结束时间" value={formatDate(detail.endedAt)} />
        <Metadata label="会话 ID" value={detail.id} />
        <Metadata
          label="助手 ID"
          value={detail.assistantId || "调试会话"}
        />
      </div>

      <div className="border-t border-hairline pt-4">
        <div className="caption-label mb-2 text-muted-soft">统计</div>
        <div className="flex flex-wrap gap-2">
          <Badge
            variant="secondary"
            className="gap-1.5 bg-surface-strong text-muted-foreground"
          >
            <MessageSquareText size={12} />
            {detail.messages.length} 条对话
          </Badge>
          {imageCount > 0 && (
            <Badge
              variant="secondary"
              className="gap-1.5 bg-surface-strong text-muted-foreground"
            >
              <ImageIcon size={12} />
              {imageCount} 张照片
            </Badge>
          )}
          {toolCount > 0 && (
            <Badge
              variant="secondary"
              className="gap-1.5 bg-surface-strong text-muted-foreground"
            >
              <Wrench size={12} />
              {toolCount} 次工具
            </Badge>
          )}
          {transitionCount > 0 && (
            <Badge
              variant="secondary"
              className="gap-1.5 bg-surface-strong text-muted-foreground"
            >
              <GitBranch size={12} />
              {transitionCount} 次转移
            </Badge>
          )}
          {nodes.size > 0 && (
            <Badge
              variant="secondary"
              className="gap-1.5 bg-surface-strong text-muted-foreground"
            >
              工作流 {nodes.size} 节点
            </Badge>
          )}
        </div>
      </div>
    </div>
  );
}

function analysisValue(value: unknown): string {
  if (value === null || value === undefined || value === "") return "未提取到";
  if (typeof value === "boolean") return value ? "是" : "否";
  if (typeof value === "object") return JSON.stringify(value);
  return String(value);
}

function AnalysisPanel({
  analysis,
}: {
  analysis: ConversationDetail["analysis"];
}) {
  if (analysis.status === "pending" || analysis.status === "processing") {
    return (
      <div className="flex min-h-48 items-center justify-center gap-2 text-sm text-muted-foreground">
        <Loader2 size={16} className="animate-spin" />
        正在分析通话内容
      </div>
    );
  }

  if (analysis.status === "failed") {
    return (
      <div className="rounded-2xl border border-destructive/25 bg-destructive/5 px-4 py-4">
        <div className="text-sm font-medium text-destructive">分析失败</div>
        <p className="mt-1.5 break-words text-xs leading-5 text-muted-foreground">
          {analysis.error || "分析服务没有返回有效结果。"}
        </p>
      </div>
    );
  }

  if (analysis.status === "completed") {
    return (
      <div className="space-y-4">
        <div>
          <div className="caption-label text-muted-soft">关键信息</div>
          {analysis.completedAt && (
            <div className="mt-1 text-xs text-muted-foreground">
              完成于 {formatDate(analysis.completedAt)}
            </div>
          )}
        </div>
        <div className="divide-y divide-hairline overflow-hidden rounded-2xl border border-hairline bg-background">
          {analysis.fields.map((field) => (
            <div
              key={field.name}
              className="grid grid-cols-[minmax(0,0.9fr)_minmax(0,1.1fr)] gap-4 px-4 py-3"
            >
              <div className="min-w-0">
                <div className="break-all text-sm font-medium text-foreground">
                  {field.name}
                </div>
                <div className="mt-0.5 text-[11px] text-muted-soft">
                  {field.type}
                </div>
              </div>
              <div
                className={cn(
                  "break-words text-sm text-foreground",
                  (field.value === null || field.value === undefined) &&
                    "text-muted-soft",
                )}
              >
                {analysisValue(field.value)}
              </div>
            </div>
          ))}
        </div>
      </div>
    );
  }

  return (
    <div className="flex min-h-48 flex-col items-center justify-center rounded-2xl border border-dashed border-hairline-strong bg-canvas-soft px-5 py-10 text-center">
      <div className="flex h-10 w-10 items-center justify-center rounded-full bg-surface-strong text-foreground">
        <ChartLine size={18} />
      </div>
      <div className="mt-4 text-sm font-medium text-foreground">
        暂无分析结果
      </div>
      <p className="mt-1.5 max-w-xs text-xs leading-5 text-muted-foreground">
        该会话未开启通话后分析，或没有可分析的对话内容。
      </p>
    </div>
  );
}

function TranscriptPanel({ detail }: { detail: ConversationDetail }) {
  const nodes = workflowNodes(detail);
  const trace = (detail.extra.workflowTrace ?? []).map(recordValue);
  const startedByInvocation = new Map<string, TraceEvent>();
  trace.forEach((event) => {
    if (event.event === "action_started" || event.event === "tool_started") {
      const invocationId = textValue(event.invocationId);
      if (invocationId) startedByInvocation.set(invocationId, event);
    }
  });

  const entries: TimelineEntry[] = [
    ...detail.messages.map((message) => ({
      kind: "message" as const,
      timestamp: message.occurredAt,
      order: message.sequence,
      message,
    })),
    ...trace.map((event, index) => ({
      kind: "trace" as const,
      timestamp: textValue(event.timestamp),
      order: numberValue(event.sequence) ?? index + 1,
      event,
    })),
  ].sort(
    (a, b) =>
      timestampOrder(a.timestamp) - timestampOrder(b.timestamp) ||
      a.order - b.order,
  );

  if (entries.length === 0) {
    return (
      <div className="flex min-h-48 items-center justify-center text-sm text-muted-foreground">
        本次会话没有产生可展示的记录
      </div>
    );
  }

  return (
    <div className="space-y-4">
      {entries.map((entry, index) =>
        entry.kind === "message" ? (
          <TimelineMessage
            key={`message-${entry.message.id}`}
            message={entry.message}
          />
        ) : (
          <TimelineTrace
            key={`trace-${textValue(entry.event.eventId) || index}`}
            event={entry.event}
            nodes={nodes}
            startedEvent={startedByInvocation.get(
              textValue(recordValue(entry.event.outcome).invocationId) ||
                textValue(entry.event.invocationId),
            )}
          />
        ),
      )}
    </div>
  );
}

function formatPlaybackClock(seconds: number): string {
  const total = Math.max(0, Math.floor(seconds));
  const m = Math.floor(total / 60);
  const s = total % 60;
  return `${m}:${String(s).padStart(2, "0")}`;
}

/** 底部录音回放区（Mock：会话录音尚未接入后端时可演示进度）。 */
function HistoryPlaybackBar({
  detail,
}: {
  detail: ConversationDetail | null;
}) {
  const durationSec = useMemo(() => {
    if (!detail?.endedAt) return 0;
    const ms =
      new Date(detail.endedAt).getTime() - new Date(detail.startedAt).getTime();
    return Number.isFinite(ms) ? Math.max(0, Math.round(ms / 1000)) : 0;
  }, [detail]);

  const [playing, setPlaying] = useState(false);
  const [elapsed, setElapsed] = useState(0);
  const hasRecording = false;

  useEffect(() => {
    if (!playing || durationSec <= 0) return;
    const id = window.setInterval(() => {
      setElapsed((current) => {
        if (current + 0.2 >= durationSec) {
          window.clearInterval(id);
          queueMicrotask(() => setPlaying(false));
          return durationSec;
        }
        return current + 0.2;
      });
    }, 200);
    return () => window.clearInterval(id);
  }, [playing, durationSec]);

  const progress = durationSec > 0 ? Math.min(1, elapsed / durationSec) : 0;

  return (
    <div className="shrink-0 border-t border-hairline bg-canvas-soft/40">
      <div className="flex items-center gap-3 px-4 py-2 sm:px-5">
        <Button
          type="button"
          size="icon-sm"
          variant="outline"
          className="shrink-0 border-hairline-strong"
          disabled={!detail || durationSec <= 0}
          aria-label={playing ? "暂停" : "播放"}
          onClick={() => {
            setPlaying((value) => !value);
          }}
        >
          {playing ? <Pause size={14} /> : <Play size={14} />}
        </Button>

        <div className="min-w-0 flex-1">
          <div className="flex items-center justify-between gap-3 text-[11px] tabular-nums text-muted-soft">
            <span>{formatPlaybackClock(elapsed)}</span>
            <span className="truncate text-muted-foreground">
              {hasRecording ? "会话录音" : "录音回放（Mock，尚未接入录音文件）"}
            </span>
            <span>{formatPlaybackClock(durationSec)}</span>
          </div>
          <div className="mt-1 h-1 overflow-hidden rounded-full bg-surface-strong">
            <div
              className="h-full rounded-full bg-primary transition-[width] duration-150"
              style={{ width: `${progress * 100}%` }}
            />
          </div>
        </div>
      </div>
    </div>
  );
}

type TimelineEntry =
  | {
      kind: "message";
      timestamp: string;
      order: number;
      message: ConversationMessage;
    }
  | {
      kind: "trace";
      timestamp: string;
      order: number;
      event: TraceEvent;
    };

function timestampOrder(value: string): number {
  const parsed = Date.parse(value);
  return Number.isNaN(parsed) ? Number.MAX_SAFE_INTEGER : parsed;
}

function TimelineMessage({ message }: { message: ConversationMessage }) {
  const isUser = message.role === "user";
  const images = (message.artifacts ?? []).filter((item) => item.kind === "image");
  const isImageMessage = message.contentType === "image" || images.length > 0;

  return (
    <div className={cn("flex", isUser ? "justify-end" : "justify-start")}>
      <div className="max-w-[88%] sm:max-w-[78%]">
        <div
          className={cn(
            "mb-1.5 flex items-center gap-1.5 text-xs text-muted-soft",
            isUser && "justify-end",
          )}
        >
          {isImageMessage && <Camera size={12} />}
          {isUser ? "用户" : "助手"} · {formatDate(message.occurredAt)}
        </div>
        <div
          className={cn(
            "overflow-hidden rounded-2xl text-left leading-6",
            isUser
              ? "rounded-br-md bg-primary text-primary-foreground"
              : "rounded-bl-md border border-hairline bg-background text-foreground shadow-sm",
            isImageMessage ? "p-1" : "px-4 py-3",
          )}
        >
          {images.map((image) => {
            const url = artifactUrl(image.contentUrl);
            return (
              <a
                key={image.id}
                href={url}
                target="_blank"
                rel="noreferrer"
                className="block overflow-hidden rounded-[0.8rem] bg-black/10"
                title="在新窗口查看原图"
              >
                {/* eslint-disable-next-line @next/next/no-img-element */}
                <img
                  src={url}
                  alt="用户在通话中拍摄的照片"
                  className="max-h-[28rem] w-full object-contain"
                />
              </a>
            );
          })}
          {isImageMessage && images.length === 0 && (
            <div className="flex min-h-40 min-w-56 flex-col items-center justify-center gap-2 rounded-[0.8rem] bg-background/10 px-6 text-center text-sm opacity-75">
              <ImageIcon size={22} />
              图片附件不可用
            </div>
          )}
          {message.content && (
            <div className={isImageMessage ? "px-3 py-2" : ""}>{message.content}</div>
          )}
        </div>
        {(message.extra.source || message.extra.node_id) && (
          <div className={cn("mt-1.5 text-[11px] text-muted-soft", isUser && "text-right")}>
            {[message.extra.source, message.extra.node_id].filter(Boolean).join(" · ")}
          </div>
        )}
        {message.extra.interrupted && (
          <div className="mt-1.5 text-xs text-muted-foreground">
            回复在生成中被打断
          </div>
        )}
      </div>
    </div>
  );
}

function TimelineTrace({
  event,
  nodes,
  startedEvent,
}: {
  event: TraceEvent;
  nodes: Map<string, WorkflowNode>;
  startedEvent?: TraceEvent;
}) {
  const presentation = tracePresentation(event, nodes, startedEvent);
  const Icon = presentation.icon;
  const iconClass = {
    default: "bg-surface-strong text-muted-foreground",
    success: "bg-success/10 text-success",
    error: "bg-destructive/10 text-destructive",
    user: "bg-primary/10 text-primary",
  }[presentation.tone];

  return (
    <div className="flex gap-3 rounded-xl border border-hairline-soft bg-background/65 p-3">
      <div className={cn("flex size-8 shrink-0 items-center justify-center rounded-full", iconClass)}>
        <Icon size={15} />
      </div>
      <div className="min-w-0 flex-1">
        <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-1">
          <div>
            <div className="text-sm font-medium text-foreground">{presentation.title}</div>
            <div className="mt-0.5 text-xs leading-5 text-muted-foreground">
              {presentation.description}
            </div>
          </div>
          <time className="shrink-0 text-[11px] tabular-nums text-muted-soft">
            {formatDate(textValue(event.timestamp))}
          </time>
        </div>
        <details className="mt-2 text-xs text-muted-foreground">
          <summary className="w-fit cursor-pointer select-none hover:text-foreground">
            详细数据
          </summary>
          <pre className="mt-2 max-h-64 overflow-auto rounded-lg bg-surface-strong p-3 font-mono text-[11px] leading-5 text-foreground">
            {JSON.stringify(event, null, 2)}
          </pre>
        </details>
      </div>
    </div>
  );
}
