import React, { useState, useEffect, useRef } from 'react';
import { useSearchParams, useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { ArrowLeft, Check, CheckCircle2, Clock, FileDown, FileText, Flag, Lock, Loader2, Printer, Settings, Shield, Unlock, Users } from 'lucide-react';
import ReactFlow, { useNodesState, useEdgesState, Background, BackgroundVariant, Handle, Position, ReactFlowProvider } from 'reactflow';
import type { Node as FlowNode, Edge, NodeTypes } from 'reactflow';
import 'reactflow/dist/style.css';
import { workJobApi } from '../api/WorkJob';
import { workHandleApi } from '../api/WorkHandle';
import { workflowDesignApi } from '../api/WorkflowDesign';
import { formatDateWithFormat } from '../utils/formatTime';
import { getUser, getTenantName } from '../utils/auth';
import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar';
import { useTranslation } from 'react-i18next';
import './WorkJobArchiveReport.css';

/** 生成水印背景样式：显示 用户名@租户名称 */
function useWatermarkStyle(): React.CSSProperties {
  return React.useMemo(() => {
    const user = getUser();
    const tenantName = getTenantName();
    const username = user?.nickname ?? user?.name ?? user?.username ?? '';
    const tenant = tenantName ?? user?.tenantName ?? '';
    const text = [username, tenant].filter(Boolean).length > 0
      ? `${username}@${tenant}`
      : '内部归档 INTERNAL ONLY';
    const svg = `<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg"><text x="50%" y="50%" font-size="20" fill="#f3f4f6" transform="rotate(-45 200 200)" text-anchor="middle">${text}</text></svg>`;
    const dataUrl = `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
    return { backgroundImage: dataUrl };
  }, []);
}

const iconModulesForNode = (import.meta as any).glob('../assets/节点图标/**/*.png', { eager: true, as: 'url' });
const getIconPathByFileName = (fileName: string | undefined): string | null => {
  if (!fileName) return null;
  let actualFileName = fileName;
  if (fileName.includes('/') || fileName.includes('\\')) {
    const pathParts = fileName.replace(/\\/g, '/').split('/');
    actualFileName = pathParts[pathParts.length - 1];
  }
  const match = actualFileName.match(/^(\d+)\.png$/);
  if (!match) return null;
  const iconNumber = parseInt(match[1], 10);
  let category = '';
  if (iconNumber >= 1000 && iconNumber <= 1011) category = '审核';
  else if (iconNumber >= 2000 && iconNumber <= 2016) category = '开始';
  else if (iconNumber >= 3000 && iconNumber <= 3028) category = '录入';
  else if (iconNumber >= 4000 && iconNumber <= 4024) category = '确认';
  else if (iconNumber >= 5000 && iconNumber <= 5018) category = '结束';
  else if (iconNumber >= 6000 && iconNumber <= 6027) category = '能量隔离';
  else if (iconNumber >= 7000 && iconNumber <= 7021) category = '解除隔离';
  if (!category) return null;
  const allKeys = Object.keys(iconModulesForNode);
  const matchingKey = allKeys.find(k => {
    const normalizedKey = k.replace(/\\/g, '/').toLowerCase();
    return normalizedKey.includes(category.toLowerCase()) && normalizedKey.endsWith(actualFileName.toLowerCase());
  });
  return matchingKey ? (iconModulesForNode[matchingKey] as string) : null;
};

const getNodeIcon = (type: string, status: 'completed' | 'in_progress' | 'pending') => {
  const iconClass = status === 'completed' ? 'text-green-600' : status === 'in_progress' ? 'text-blue-600' : 'text-gray-400';
  switch (type) {
    case 'createJob': return <CheckCircle2 className={`w-5 h-5 ${iconClass}`} />;
    case 'review':
    case 'confirm': return <Settings className={`w-5 h-5 ${iconClass}`} />;
    case 'inputInfo': return <FileText className={`w-5 h-5 ${iconClass}`} />;
    case 'lock':
    case 'isolation': return <Lock className={`w-5 h-5 ${iconClass}`} />;
    case 'unlock':
    case 'releaseIsolation': return <Unlock className={`w-5 h-5 ${iconClass}`} />;
    case 'coLock': return <Users className={`w-5 h-5 ${iconClass}`} />;
    case 'unlockCoLock': return <Unlock className={`w-5 h-5 ${iconClass}`} />;
    case 'returnLock': return <Lock className={`w-5 h-5 ${iconClass}`} />;
    case 'complete': return <Flag className={`w-5 h-5 ${iconClass}`} />;
    default: return <FileText className={`w-5 h-5 ${iconClass}`} />;
  }
};

/** 与作业详情页一致的节点组件（SimpleCustomNode） */
function SimpleCustomNode({ data, selected, id }: any) {
  const nodeId = data.nodeId || (id ? String(parseInt(id.split('-').pop() || '0') % 1000).padStart(3, '0') : '001');
  const status: 'completed' | 'in_progress' | 'pending' = data.status || 'pending';
  let borderClass = 'border-gray-400';
  let borderStyle: React.CSSProperties = { borderWidth: '3px' };
  let shadowStyle: React.CSSProperties = {};
  if (selected) {
    borderClass = 'border-blue-500 shadow-md ring-2 ring-blue-300';
    borderStyle = { borderWidth: '4px' };
    shadowStyle = { boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.1)' };
  } else {
    if (status === 'completed') {
      borderClass = 'border-green-500';
      shadowStyle = { boxShadow: '0 0 0 1px rgba(34, 197, 94, 0.1)' };
    } else if (status === 'in_progress') {
      borderClass = 'border-blue-500';
      shadowStyle = { boxShadow: '0 0 0 1px rgba(59, 130, 246, 0.1)' };
    } else {
      borderClass = 'border-gray-400';
    }
  }
  const getStatusText = (s: 'completed' | 'in_progress' | 'pending') =>
    s === 'completed' ? '已完成' : s === 'in_progress' ? '进行中' : '待执行';
  const getStatusTextColor = (s: 'completed' | 'in_progress' | 'pending') =>
    s === 'completed' ? 'text-green-600' : s === 'in_progress' ? 'text-blue-600' : 'text-gray-500';
  let iconImagePath: string | null = null;
  if (data.icon && /^\d+\.png$/.test(data.icon)) iconImagePath = getIconPathByFileName(data.icon);
  return (
    <div className={`relative px-4 py-4 rounded-lg shadow-sm w-[180px] h-auto min-h-[140px] bg-white ${borderClass} transition-all`} style={{ ...borderStyle, ...shadowStyle }}>
      <Handle id="top-source" type="source" position={Position.Top} className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm" style={{ top: -6, left: '50%', transform: 'translateX(-50%)' }} isConnectable={false} />
      <Handle id="top-target" type="target" position={Position.Top} className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm" style={{ top: -6, left: '50%', transform: 'translateX(-50%)' }} isConnectable={false} />
      <Handle id="bottom-source" type="source" position={Position.Bottom} className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm" style={{ bottom: -6, left: '50%', transform: 'translateX(-50%)' }} isConnectable={false} />
      <Handle id="bottom-target" type="target" position={Position.Bottom} className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm" style={{ bottom: -6, left: '50%', transform: 'translateX(-50%)' }} isConnectable={false} />
      <Handle id="left-source" type="source" position={Position.Left} className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm" style={{ left: -6, top: '50%', transform: 'translateY(-50%)' }} isConnectable={false} />
      <Handle id="left-target" type="target" position={Position.Left} className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm" style={{ left: -6, top: '50%', transform: 'translateY(-50%)' }} isConnectable={false} />
      <Handle id="right-source" type="source" position={Position.Right} className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm" style={{ right: -6, top: '50%', transform: 'translateY(-50%)' }} isConnectable={false} />
      <Handle id="right-target" type="target" position={Position.Right} className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm" style={{ right: -6, top: '50%', transform: 'translateY(-50%)' }} isConnectable={false} />
      <div className="flex flex-col items-center justify-between gap-3 h-full">
        <div className="bg-gray-50 border border-gray-100 p-3 rounded-xl shadow-sm flex items-center justify-center flex-shrink-0" style={{ borderRadius: '12px' }}>
          {iconImagePath ? (
            <img src={iconImagePath} alt={data.icon || '节点图标'} className="w-6 h-6 object-contain" onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} />
          ) : (
            getNodeIcon(data.type || 'createJob', status)
          )}
        </div>
        <div className="font-semibold text-sm text-gray-900 leading-tight text-center break-words w-full flex-1 flex items-center justify-center px-1">
          {data.label || data.nodeName || '节点'}
        </div>
        <div className="text-xs text-gray-500 text-center flex-shrink-0">ID: {nodeId}</div>
        <div className={`text-xs font-medium text-center flex-shrink-0 ${getStatusTextColor(status)}`}>{getStatusText(status)}</div>
      </div>
    </div>
  );
}

const archiveNodeTypes: NodeTypes = {
  createJob: SimpleCustomNode,
  confirm: SimpleCustomNode,
  review: SimpleCustomNode,
  inputInfo: SimpleCustomNode,
  lock: SimpleCustomNode,
  isolation: SimpleCustomNode,
  unlock: SimpleCustomNode,
  releaseIsolation: SimpleCustomNode,
  coLock: SimpleCustomNode,
  unlockCoLock: SimpleCustomNode,
  returnLock: SimpleCustomNode,
  complete: SimpleCustomNode,
  default: SimpleCustomNode,
};

/** 作业详情（与接口返回一致） */
type JobDetail = {
  id?: number;
  orderNo?: string;
  name?: string;
  initiatorName?: string;
  initiationTime?: number;
  description?: string;
  status?: string;
  completionTime?: number | null;
  designId?: number;
  designName?: string;
  designContent?: string;
  workflowWorkNodeDOList?: Array<{
    id?: number;
    uuid?: string;
    nodeName?: string;
    approvalStatus?: string | number;
    createTime?: number;
    workerUserId?: number;
    initiatorName?: string;
    [key: string]: any;
  }>;
  [key: string]: any;
};

/** 归档流水项 */
type ArchiveLogItem = {
  nodeName?: string;
  taskNode?: string;
  nickName?: string;
  executorName?: string;
  createTime?: number;
  taskStartTime?: number;
  handleTime?: number;
  approvalStatus?: string | number;
  taskStatus?: string;
  taskContent?: string;
  approvalOpinion?: string;
  remark?: string;
  [key: string]: any;
};

const statusToConclusion = (status?: string) => {
  const s = String(status || '').toLowerCase();
  if (s === 'completed' || s === 'done') return { text: '已完成', badge: '正常结束', desc: '作业已按照既定流程流转完毕，所有隔离点已解除，现场确认无误。' };
  if (s === 'running') return { text: '执行中', badge: '进行中', desc: '作业正在流转中，请关注当前节点进度。' };
  if (s === 'rejected') return { text: '已退回', badge: '已退回', desc: '作业已被退回，请根据意见修改后重新提交。' };
  return { text: '待执行', badge: '待执行', desc: '作业已创建，等待流程启动。' };
};

/** 根据 approvalStatus 判断节点展示状态 */
const getNodeStepState = (approvalStatus?: string | number): 'done' | 'active' | 'pending' => {
  if (approvalStatus == null || approvalStatus === '') return 'pending';
  const s = String(approvalStatus).toLowerCase();
  if (['approved', 'complete', 'completed', 'done', '3'].includes(s)) return 'done';
  if (['running', 'processing', '2'].includes(s)) return 'active';
  return 'pending';
};

/** 流程节点（含 parentUuid，用于主路径计算） */
type WorkflowNodeWithParent = {
  id?: number;
  uuid?: string;
  nodeName?: string;
  parentUuid?: string;
  createTime?: number;
  type?: string;
  [key: string]: any;
};

/**
 * 从完整节点列表中提取「主线」节点（仅一条从开始到结束的路径，并行/分支节点不包含）。
 * 与 WorkJobDetail 主路径逻辑一致：找头(createJob/无父)、尾(complete/结束)，沿路径遇多子节点时选分支数最少的。
 */
function getMainPathNodes(
  nodeList: WorkflowNodeWithParent[]
): WorkflowNodeWithParent[] {
  if (!nodeList?.length) return [];
  const nodeMap = new Map<string, WorkflowNodeWithParent>();
  nodeList.forEach((n) => { if (n.uuid) nodeMap.set(String(n.uuid), n); });

  const childrenMap = new Map<string, WorkflowNodeWithParent[]>();
  nodeList.forEach((node) => {
    if (node.parentUuid) {
      const parentUuids = String(node.parentUuid).split(',').map((u) => u.trim()).filter(Boolean);
      parentUuids.forEach((parentUuid) => {
        if (!childrenMap.has(parentUuid)) childrenMap.set(parentUuid, []);
        childrenMap.get(parentUuid)!.push(node);
      });
    }
  });
  childrenMap.forEach((children) => {
    children.sort((a, b) => (a.createTime ?? 0) - (b.createTime ?? 0));
  });

  const countBranchNodes = (node: WorkflowNodeWithParent, visited: Set<string> = new Set()): number => {
    const uid = node.uuid || '';
    if (visited.has(uid)) return 0;
    visited.add(uid);
    const children = childrenMap.get(uid) || [];
    let count = children.length;
    children.forEach((child) => { count += countBranchNodes(child, visited); });
    return count;
  };

  const findHead = (): WorkflowNodeWithParent | null => {
    const byCreateJob = nodeList.find((n) => n.type === 'createJob' || (n.nodeName && (n.nodeName.includes('创建') || n.nodeName.includes('开始'))));
    if (byCreateJob) return byCreateJob;
    const roots = nodeList.filter((n) => !n.parentUuid || String(n.parentUuid).trim() === '');
    if (roots.length > 0) {
      roots.sort((a, b) => (a.createTime ?? 0) - (b.createTime ?? 0));
      return roots[0];
    }
    return null;
  };

  const findTail = (): WorkflowNodeWithParent | null => {
    return nodeList.find((n) => n.type === 'complete' || (n.nodeName && (n.nodeName === '完成/结束' || n.nodeName.includes('结束作业')))) ?? null;
  };

  const buildPathFromHead = (head: WorkflowNodeWithParent, tail: WorkflowNodeWithParent): WorkflowNodeWithParent[] => {
    const path: WorkflowNodeWithParent[] = [head];
    const visited = new Set<string>([head.uuid || '']);
    let current: WorkflowNodeWithParent = head;

    while (current && current.uuid !== tail.uuid) {
      const children = childrenMap.get(current.uuid || '') || [];
      if (children.length === 0) break;
      const available = children.filter((c) => !visited.has(c.uuid || ''));
      if (available.length === 0) break;

      let next: WorkflowNodeWithParent;
      if (available.length === 1) {
        next = available[0];
      } else {
        const withCount = available.map((child) => ({
          node: child,
          count: countBranchNodes(child, new Set()),
        }));
        withCount.sort((a, b) => a.count - b.count);
        next = withCount[0].node;
      }
      visited.add(next.uuid || '');
      path.push(next);
      current = next;
    }
    return path;
  };

  const head = findHead();
  const tail = findTail();
  if (!head || !tail) return [];

  const mainPath = buildPathFromHead(head, tail);
  if (mainPath.length > 0 && mainPath[mainPath.length - 1].uuid !== tail.uuid) {
    mainPath.push(tail);
  }
  return mainPath;
}

/** 根据 approvalStatus 得到 React Flow 节点状态 */
const getFlowNodeStatus = (approvalStatus?: string | number): 'completed' | 'in_progress' | 'pending' => {
  if (approvalStatus == null || approvalStatus === '') return 'pending';
  const s = String(approvalStatus).toLowerCase();
  if (['approved', 'complete', 'completed', 'done', '3'].includes(s)) return 'completed';
  if (['running', 'processing', '2'].includes(s)) return 'in_progress';
  return 'pending';
};

/** 从 designContent 得到 nodes/edges，兼容 nodes/edges、nodeList/edgeList、elements 等格式 */
function parseDesignContentLikeJobDetail(
  designContent: string | object,
  workflowWorkNodeDOList: Array<{ uuid?: string; nodeName?: string; approvalStatus?: string | number; [key: string]: any }>
): { nodes: FlowNode[]; edges: Edge[] } | null {
  try {
    const jsonData = typeof designContent === 'string' ? JSON.parse(designContent) : designContent;
    if (!jsonData || typeof jsonData !== 'object') return null;
    let rawNodes: any[] = jsonData.nodes ?? jsonData.nodeList ?? [];
    let rawEdges: any[] = jsonData.edges ?? jsonData.edgeList ?? [];
    if (!Array.isArray(rawNodes) || !Array.isArray(rawEdges)) {
      const elements = jsonData.elements;
      if (Array.isArray(elements)) {
        rawNodes = elements.filter((el: any) => el.id != null && el.source == null && el.target == null);
        rawEdges = elements.filter((el: any) => el.source != null && el.target != null);
      }
    }
    if (!Array.isArray(rawNodes) || !Array.isArray(rawEdges) || rawNodes.length === 0) return null;

    const nodeMap = new Map<string, { nodeName?: string; approvalStatus?: string | number; type?: string; nodeIcon?: string; id?: number; [key: string]: any }>();
    workflowWorkNodeDOList.forEach((n) => { if (n.uuid) nodeMap.set(String(n.uuid), n); });
    const validTypes = new Set([
      'createJob',
      'confirm',
      'review',
      'inputInfo',
      'lock',
      'isolation',
      'unlock',
      'releaseIsolation',
      'coLock',
      'unlockCoLock',
      'returnLock',
      'complete',
    ]);
    const convertedNodes: FlowNode[] = rawNodes
      .map((node: any) => {
        const nodeId = node.uuid ?? node.id;
        if (nodeId == null || nodeId === '') return null;
        const id = String(nodeId);
        const workNode = nodeMap.get(id);
        const nodeData = node.data || {};
        const nodeName = workNode?.nodeName || nodeData.label || node.nodeName || node.label || '节点';
        const status = getFlowNodeStatus(workNode?.approvalStatus);
        const rawType = workNode?.type || node.type || nodeData.type || 'createJob';
        const type = validTypes.has(rawType) ? rawType : 'default';
        const icon = workNode?.nodeIcon ?? nodeData.icon ?? node.nodeIcon ?? '';
        const displayNodeId = workNode?.id != null ? String(workNode.id) : (nodeData.nodeId || '');
        const pos = node.position;
        const position = pos && typeof pos === 'object' && typeof pos.x === 'number' && typeof pos.y === 'number'
          ? { x: pos.x, y: pos.y }
          : { x: 0, y: 0 };
        return {
          id,
          type,
          position,
          data: {
            label: nodeName,
            nodeName,
            status,
            type: rawType,
            icon,
            nodeId: displayNodeId,
          },
        };
      })
      .filter((n): n is FlowNode => n != null);
    if (convertedNodes.length === 0) return null;

    const nodeIdSet = new Set(convertedNodes.map((n) => n.id));
    const convertedEdges: Edge[] = rawEdges
      .map((edge: any, i: number) => ({
        id: edge.id ?? `e-${i}`,
        source: String(edge.source),
        target: String(edge.target),
        sourceHandle: edge.sourceHandle,
        targetHandle: edge.targetHandle,
        type: (edge.type || 'straight') as any,
        style: { strokeWidth: 2, stroke: '#000000' },
        markerStart: { type: 'arrowclosed' as any, color: '#000000' },
      }))
      .filter((e) => nodeIdSet.has(e.source) && nodeIdSet.has(e.target));
    return { nodes: convertedNodes, edges: convertedEdges };
  } catch {
    return null;
  }
}

/** 仅根据 workflowWorkNodeDOList 构建简易拓扑（无 designContent 时的兜底） */
function buildFlowFromNodeList(
  workflowWorkNodeDOList?: Array<{ uuid?: string; nodeName?: string; approvalStatus?: string | number; parentUuid?: string; childrenUuid?: string; position?: string; createTime?: number; [key: string]: any }>
): { nodes: FlowNode[]; edges: Edge[] } {
  const list = workflowWorkNodeDOList ?? [];
  if (list.length === 0) return { nodes: [], edges: [] };
  const sorted = [...list].sort((a, b) => (a.createTime ?? 0) - (b.createTime ?? 0));
  const gap = 180;
  const validTypes = new Set([
    'createJob',
    'confirm',
    'review',
    'inputInfo',
    'lock',
    'isolation',
    'unlock',
    'releaseIsolation',
    'coLock',
    'unlockCoLock',
    'returnLock',
    'complete',
  ]);
  const nodes: FlowNode[] = sorted
    .filter((n) => n.uuid)
    .map((n, i) => {
      const id = String(n.uuid);
      let position = { x: 240, y: 60 + i * gap };
      if (n.position) {
        try {
          const p = typeof n.position === 'string' ? JSON.parse(n.position) : n.position;
          if (p && typeof p.x === 'number' && typeof p.y === 'number') position = p;
        } catch {}
      }
      const rawType = (n as any).type || 'createJob';
      const type = validTypes.has(rawType) ? rawType : 'default';
      const status = getFlowNodeStatus(n.approvalStatus);
      const label = n.nodeName ?? '节点';
      return {
        id,
        type,
        position,
        data: {
          label,
          nodeName: label,
          status,
          type: rawType,
          icon: (n as any).nodeIcon ?? '',
          nodeId: n.id != null ? String(n.id) : '',
        },
      };
    });
  const edgeIds = new Set(nodes.map((n) => n.id));
  const edges: Edge[] = [];
  list.forEach((n) => {
    if (!n.uuid || !n.parentUuid) return;
    const parent = String(n.parentUuid).trim();
    if (parent && edgeIds.has(parent) && edgeIds.has(n.uuid)) {
      edges.push({
        id: `e-${parent}-${n.uuid}`,
        source: parent,
        target: n.uuid,
        style: { strokeWidth: 2, stroke: '#000' },
        markerStart: { type: 'arrowclosed' as any, color: '#000000' },
      });
    }
  });
  if (edges.length === 0 && nodes.length > 1) {
    for (let i = 0; i < nodes.length - 1; i++) {
      edges.push({
        id: `e-fallback-${i}`,
        source: nodes[i].id,
        target: nodes[i + 1].id,
        style: { strokeWidth: 2, stroke: '#000' },
        markerStart: { type: 'arrowclosed' as any, color: '#000000' },
      });
    }
  }
  return { nodes, edges };
}

export default function WorkJobArchiveReport() {
  const { t } = useTranslation();
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const jobId = searchParams.get('id');

  const [jobDetail, setJobDetail] = useState<JobDetail | null>(null);
  const [archiveList, setArchiveList] = useState<ArchiveLogItem[]>([]);
  const [flowNodes, setFlowNodes, onFlowNodesChange] = useNodesState([]);
  const [flowEdges, setFlowEdges, onFlowEdgesChange] = useEdgesState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [exportingPdf, setExportingPdf] = useState(false);
  const page1Ref = useRef<HTMLDivElement>(null);
  const page2Ref = useRef<HTMLDivElement>(null);

  const watermarkStyle = useWatermarkStyle();

  useEffect(() => {
    if (!jobId) {
      setLoading(false);
      return;
    }
    const id = Number(jobId);
    if (Number.isNaN(id)) {
      setLoading(false);
      return;
    }
    setLoading(true);
    setError(null);
    setFlowNodes([]);
    setFlowEdges([]);

    const load = async () => {
      try {
        const response = await workJobApi.selectWorkflowWorkById(id);
        const raw = (response as any)?.data ?? response;
        const data = (raw && typeof raw === 'object' && raw.data !== undefined) ? raw.data : raw;
        setJobDetail(data || null);

        const [logRes] = await Promise.all([
          workHandleApi.getWorkflowWorkLogPage({ workId: id }),
        ]);
        const logData = (logRes as any)?.data ?? logRes;
        const list = Array.isArray(logData) ? logData : logData?.list ?? logData?.records ?? [];
        setArchiveList(Array.isArray(list) ? list : []);

        const nodeList = Array.isArray(data?.workflowWorkNodeDOList) ? data.workflowWorkNodeDOList : [];
        let designContent: string | object | null = data?.designContent ?? data?.content ?? null;
        if (!designContent && data?.designId) {
          try {
            const designResponse = await workflowDesignApi.selectWorkflowDesignById(data.designId);
            const designRaw = (designResponse as any)?.data ?? designResponse;
            const designData = (designRaw && designRaw.data !== undefined) ? designRaw.data : designRaw;
            const content = designData?.content ?? designData?.designContent;
            if (content != null) designContent = content;
          } catch (_) {}
        }

        if (designContent != null) {
          const parsed = parseDesignContentLikeJobDetail(designContent, nodeList);
          if (parsed && parsed.nodes.length > 0) {
            setFlowNodes(parsed.nodes);
            setFlowEdges(parsed.edges);
            setLoading(false);
            return;
          }
        }
        const fallback = buildFlowFromNodeList(nodeList);
        if (fallback.nodes.length > 0) {
          setFlowNodes(fallback.nodes);
          setFlowEdges(fallback.edges);
        }
      } catch (err: any) {
        setError(err?.message || '加载失败');
        setJobDetail(null);
        setArchiveList([]);
      } finally {
        setLoading(false);
      }
    };
    load();
  }, [jobId, setFlowNodes, setFlowEdges]);

  // 当 jobDetail 已有节点列表但流程仍为空时，用节点列表构建拓扑（兜底）
  useEffect(() => {
    if (!jobDetail || flowNodes.length > 0) return;
    const list = jobDetail.workflowWorkNodeDOList;
    if (!Array.isArray(list) || list.length === 0) return;
    const fallback = buildFlowFromNodeList(list);
    if (fallback.nodes.length > 0) {
      setFlowNodes(fallback.nodes);
      setFlowEdges(fallback.edges);
    }
  }, [jobDetail, flowNodes.length, setFlowNodes, setFlowEdges]);

  const handlePrint = () => window.print();

  const handleExportPdf = async () => {
    if (exportingPdf) return;
    const el1 = page1Ref.current ?? document.querySelector<HTMLDivElement>('.work-job-archive-report .page-sheet.page-1');
    const el2 = page2Ref.current ?? document.querySelector<HTMLDivElement>('.work-job-archive-report .page-sheet.page-2');
    if (!el1 || !el2) {
      toast.error('无法获取页面内容，请稍后重试');
      return;
    }
    setExportingPdf(true);
    const loadingId = toast.loading('正在生成 PDF...');
    try {
      const [html2canvas, { jsPDF }, htmlToImage] = await Promise.all([
        import('html2canvas'),
        import('jspdf'),
        import('html-to-image').then((m) => m).catch(() => null),
      ]);
      const A4_W = 210;
      const A4_H = 297;
      const scale = 2.5;
      /** 将 data URL 转为 Canvas，便于统一用 fitToA4 与 addImage */
      const dataUrlToCanvas = (dataUrl: string): Promise<HTMLCanvasElement> =>
        new Promise((resolve, reject) => {
          const img = new Image();
          img.crossOrigin = 'anonymous';
          img.onload = () => {
            const c = document.createElement('canvas');
            c.width = img.naturalWidth;
            c.height = img.naturalHeight;
            const ctx = c.getContext('2d');
            if (!ctx) { reject(new Error('getContext failed')); return; }
            ctx.drawImage(img, 0, 0);
            resolve(c);
          };
          img.onerror = () => reject(new Error('Image load failed'));
          img.src = dataUrl;
        });
      const replaceUnsupportedColors = (cssText: string): string => {
        let s = cssText;
        const replaceBalanced = (open: string, replacement: string): boolean => {
          const start = s.indexOf(open);
          if (start === -1) return false;
          const parenStart = start + open.length - 1;
          let depth = 1;
          let i = parenStart + 1;
          while (i < s.length && depth > 0) {
            if (s[i] === '(') depth++;
            else if (s[i] === ')') depth--;
            i++;
          }
          if (depth === 0) {
            s = s.slice(0, start) + replacement + s.slice(i);
            return true;
          }
          return false;
        };
        while (replaceBalanced('color-mix(', 'rgb(200,200,200)') || replaceBalanced('oklch(', 'rgb(80,80,80)')) {
          /* repeat until no more */
        }
        return s;
      };
      /** 通过 fetch 将图片转为 data URL；同源带 cookie，跨域用 CORS */
      const fetchImageAsDataUrl = (url: string): Promise<string | null> => {
        try {
          const isSameOrigin = typeof window !== 'undefined' && new URL(url, window.location.href).origin === window.location.origin;
          return fetch(url, { mode: isSameOrigin ? 'same-origin' : 'cors', credentials: isSameOrigin ? 'include' : 'omit' })
            .then((r) => (r.ok ? r.blob() : Promise.reject(new Error('not ok'))))
            .then(
              (blob) =>
                new Promise<string | null>((resolve, reject) => {
                  const r = new FileReader();
                  r.onload = () => resolve(typeof r.result === 'string' ? r.result : null);
                  r.onerror = () => reject(r.error);
                  r.readAsDataURL(blob);
                })
            )
            .catch(() => null);
        } catch {
          return Promise.resolve(null);
        }
      };

      /** 将容器内图片转为 data URL（导出 PDF 时用）；归档流水中的头像不转换，PDF 中只保留文字 */
      const preloadFlowImagesToDataUrls = async (container: HTMLElement): Promise<() => void> => {
        const imgs = container.querySelectorAll<HTMLImageElement>('img[src]');
        const restores: Array<{ el: HTMLImageElement; original: string }> = [];
        const setDataUrl = (img: HTMLImageElement, dataUrl: string, original: string) => {
          restores.push({ el: img, original });
          img.src = dataUrl;
        };
        const waitForImgLoad = (img: HTMLImageElement, timeoutMs = 3000): Promise<void> =>
          new Promise((resolve) => {
            if (img.complete && img.naturalWidth) {
              resolve();
              return;
            }
            const t = setTimeout(resolve, timeoutMs);
            img.onload = () => {
              clearTimeout(t);
              resolve();
            };
            img.onerror = () => {
              clearTimeout(t);
              resolve();
            };
          });
        const toDataUrlFromCanvas = (img: HTMLImageElement): boolean => {
          try {
            if (!img.naturalWidth || !img.naturalHeight) return false;
            const c = document.createElement('canvas');
            c.width = img.naturalWidth;
            c.height = img.naturalHeight;
            const ctx = c.getContext('2d');
            if (!ctx) return false;
            ctx.drawImage(img, 0, 0);
            const dataUrl = c.toDataURL('image/png');
            setDataUrl(img, dataUrl, img.src);
            return true;
          } catch {
            return false;
          }
        };
        await Promise.all(
          Array.from(imgs).map((img) => {
            if (img.closest('.archive-log-avatar')) return Promise.resolve();
            const src = (img.getAttribute('src') || img.src || '').trim();
            if (!src || src.startsWith('data:')) return Promise.resolve();
            const resolvedUrl = img.src || src;
            return new Promise<void>((resolve) => {
              const tryConvert = () => {
                if (!img.complete || !img.naturalWidth) {
                  img.onload = tryConvert;
                  img.onerror = () => resolve();
                  return;
                }
                if (toDataUrlFromCanvas(img)) {
                  waitForImgLoad(img).then(resolve);
                  return;
                }
                fetchImageAsDataUrl(resolvedUrl).then((dataUrl) => {
                  if (dataUrl) {
                    setDataUrl(img, dataUrl, img.src);
                    waitForImgLoad(img).then(resolve);
                  } else resolve();
                }).catch(() => resolve());
              };
              tryConvert();
            });
          })
        );
        return () => restores.forEach(({ el, original }) => { el.src = original; });
      };

      const stripOklch = (doc: Document) => {
        doc.querySelectorAll('style').forEach((style) => {
          if (style.textContent && (style.textContent.includes('oklch') || style.textContent.includes('color-mix'))) {
            style.textContent = replaceUnsupportedColors(style.textContent);
          }
        });
        try {
          Array.from(doc.styleSheets).forEach((sheet) => {
            try {
              const rules = sheet.cssRules ?? (sheet as CSSStyleSheet).rules;
              if (!rules) return;
              for (let i = 0; i < rules.length; i++) {
                const rule = rules[i] as CSSStyleRule;
                if (rule.style?.cssText && (rule.style.cssText.includes('oklch') || rule.style.cssText.includes('color-mix'))) {
                  rule.style.cssText = replaceUnsupportedColors(rule.style.cssText);
                }
              }
            } catch {
              /* 跨域或无法访问的样式表忽略 */
            }
          });
        } catch {
          /* styleSheets 不可用则忽略 */
        }
        doc.querySelectorAll('[style]').forEach((el) => {
          const elWithStyle = el as HTMLElement;
          if (elWithStyle.style?.cssText && (elWithStyle.style.cssText.includes('oklch') || elWithStyle.style.cssText.includes('color-mix'))) {
            elWithStyle.style.cssText = replaceUnsupportedColors(elWithStyle.style.cssText);
          }
          const attr = el.getAttribute('style');
          if (attr && (attr.includes('oklch') || attr.includes('color-mix'))) {
            el.setAttribute('style', replaceUnsupportedColors(attr));
          }
        });
      };
      const opts = {
        scale,
        useCORS: true,
        allowTaint: true,
        backgroundColor: '#ffffff',
        logging: false,
        onclone: (clonedDoc: Document) => {
          stripOklch(clonedDoc);
          // 第一页只固定宽度 210mm，高度随内容（含完整流水日志），导出时再按 A4 高度切片为多页，避免截断
          const page1 = clonedDoc.querySelector<HTMLElement>('.work-job-archive-report .page-sheet.page-1');
          if (page1) {
            page1.style.width = '210mm';
            page1.style.minWidth = '210mm';
            page1.style.minHeight = '0';
            page1.style.overflow = 'visible';
            page1.style.boxSizing = 'border-box';
          }
          // 导出时隐藏连接点与控制按钮，避免拓扑图在 PDF 中渲染混乱
          const exportStyle = clonedDoc.createElement('style');
          exportStyle.textContent = [
            '.react-flow__handle { display: none !important; }',
            '.react-flow__controls { display: none !important; }',
            '.react-flow__viewport { will-change: auto !important; }',
            /* 避免连接线被裁切，确保 SVG 边线完整导出 */
            '.archive-flow-print-wrapper, .archive-flow-inner, .react-flow__viewport, .react-flow__edges, .react-flow__renderer { overflow: visible !important; }',
            '.react-flow__edges svg { overflow: visible !important; }',
            /* PDF 导出：归档流水操作人列不显示头像，只显示名字文本 */
            '.archive-log-avatar { display: none !important; }',
          ].join('\n');
          clonedDoc.head.appendChild(exportStyle);
          /* 有头像时 Radix 可能不渲染 Fallback 内容，克隆里补上首字，避免 PDF 中为空白 */
          clonedDoc.querySelectorAll('.archive-log-avatar').forEach((avatar) => {
            const img = avatar.querySelector('[data-slot="avatar-image"]');
            if (!img?.getAttribute('src')) return;
            const fallback = avatar.querySelector('[data-slot="avatar-fallback"]');
            if (!fallback) return;
            const nameNode = avatar.nextSibling;
            const nameText = (nameNode?.nodeType === Node.TEXT_NODE ? nameNode.textContent : '')?.trim() ?? '';
            const initial = (nameText && nameText.charAt(0)) || (fallback.textContent?.trim()?.charAt(0)) || '?';
            fallback.textContent = initial;
          });
          /* 为边线 SVG 设置宽高，改善 html2canvas 对 SVG 的渲染 */
          clonedDoc.querySelectorAll('.react-flow__edges svg').forEach((svg) => {
            const el = svg as SVGSVGElement;
            const b = el.getBoundingClientRect();
            if (b.width > 0 && b.height > 0) {
              el.setAttribute('width', String(Math.ceil(b.width)));
              el.setAttribute('height', String(Math.ceil(b.height)));
            }
          });
        },
      };
      // 第一页内头像等图片转为 data URL，避免 PDF 中只渲染出 Fallback 文字（如只显示「李」）
      const restorePage1Images = await preloadFlowImagesToDataUrls(el1);
      await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));
      let canvas1: HTMLCanvasElement;
      try {
        canvas1 = await html2canvas.default(el1, opts);
      } finally {
        restorePage1Images();
      }

      // 截取第二页前：统一把拓扑图内图片转为 data URL（两种导出方式都生效），并隐藏手柄/控制栏（不改 overflow，避免 PDF 中位置偏移）
      const restoreFlowImages = await preloadFlowImagesToDataUrls(el2);
      const exportFlowStyle = document.createElement('style');
      exportFlowStyle.id = 'pdf-export-flow-style';
      exportFlowStyle.textContent = '.react-flow__handle, .react-flow__controls { display: none !important; }';
      document.head.appendChild(exportFlowStyle);
      await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r)));

      let canvas2: HTMLCanvasElement;
      try {
        if (htmlToImage?.toPng) {
          const dataUrl2 = await (htmlToImage as { toPng: (el: HTMLElement, o?: { pixelRatio?: number; backgroundColor?: string }) => Promise<string> }).toPng(el2, { pixelRatio: 2, backgroundColor: '#ffffff' });
          canvas2 = await dataUrlToCanvas(dataUrl2);
        } else {
          canvas2 = await html2canvas.default(el2, opts);
        }
      } finally {
        restoreFlowImages();
        document.getElementById('pdf-export-flow-style')?.remove();
      }
      const pdf = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
      // 第一页内容可能超过一屏：按 A4 高度切片为多页，保证流水日志等全部展示且每页 1:1 A4
      const sliceHeightPx = canvas1.width * (A4_H / A4_W);
      const needSlice = canvas1.height > sliceHeightPx;
      if (!needSlice) {
        const r1 = canvas1.width / canvas1.height;
        const isA4Ratio = Math.abs(r1 - A4_W / A4_H) < 0.05;
        if (isA4Ratio) {
          pdf.addImage(canvas1.toDataURL('image/jpeg', 0.92), 'JPEG', 0, 0, A4_W, A4_H);
        } else {
          const fitToA4 = (canvas: HTMLCanvasElement) => {
            const w = canvas.width;
            const h = canvas.height;
            const r = w / h;
            if (A4_W / A4_H >= r) return { w: A4_H * r, h: A4_H };
            return { w: A4_W, h: A4_W / r };
          };
          const size1 = fitToA4(canvas1);
          const x1 = (A4_W - size1.w) / 2;
          const y1 = (A4_H - size1.h) / 2;
          pdf.addImage(canvas1.toDataURL('image/jpeg', 0.92), 'JPEG', x1, y1, size1.w, size1.h);
        }
      } else {
        for (let y = 0; y < canvas1.height; y += sliceHeightPx) {
          const sh = Math.min(sliceHeightPx, canvas1.height - y);
          const temp = document.createElement('canvas');
          temp.width = canvas1.width;
          temp.height = sliceHeightPx;
          const ctx = temp.getContext('2d');
          if (ctx) {
            ctx.fillStyle = '#ffffff';
            ctx.fillRect(0, 0, temp.width, temp.height);
            ctx.drawImage(canvas1, 0, y, canvas1.width, sh, 0, 0, canvas1.width, sh);
          }
          pdf.addImage(temp.toDataURL('image/jpeg', 0.92), 'JPEG', 0, 0, A4_W, A4_H);
          if (y + sliceHeightPx < canvas1.height) pdf.addPage();
        }
      }
      const fitToA4 = (canvas: HTMLCanvasElement) => {
        const w = canvas.width;
        const h = canvas.height;
        const r = w / h;
        if (A4_W / A4_H >= r) return { w: A4_H * r, h: A4_H };
        return { w: A4_W, h: A4_W / r };
      };
      const size2 = fitToA4(canvas2);
      const x2 = (A4_W - size2.w) / 2;
      const y2 = (A4_H - size2.h) / 2;
      pdf.addPage();
      pdf.addImage(canvas2.toDataURL('image/jpeg', 0.92), 'JPEG', x2, y2, size2.w, size2.h);
      const fileName = `作业归档报告_${jobDetail?.orderNo ?? jobDetail?.code ?? jobId ?? 'export'}.pdf`;
      pdf.save(fileName);
      toast.success('PDF 导出成功', { id: loadingId });
    } catch (e: any) {
      const raw = e?.message || e?.toString?.() || '';
      const msg = /fetch|import|dynamic/i.test(raw)
        ? 'PDF 导出失败：请先在项目目录执行 npm install html2canvas jspdf 安装依赖'
        : raw || 'PDF 导出失败';
      toast.error(msg, { id: loadingId });
      console.error('[导出PDF]', e);
    } finally {
      setExportingPdf(false);
    }
  };

  const handleBack = () => navigate(`/work-job/detail${jobId ? `?id=${jobId}` : ''}`);

  const conclusion = jobDetail ? statusToConclusion(jobDetail.status) : statusToConclusion('');
  const nodeList = jobDetail?.workflowWorkNodeDOList ?? [];
  // 流程执行摘要只渲染主线节点，并行/分支节点不展示（与 WorkJobDetail 主路径逻辑一致）
  const mainPathNodes = getMainPathNodes(nodeList as WorkflowNodeWithParent[]);
  const sortedNodes = mainPathNodes.length > 0 ? mainPathNodes : [...nodeList].sort((a, b) => (a.createTime ?? 0) - (b.createTime ?? 0));
  const currentNodeId = jobDetail?.currentNodeId ?? null;

  if (loading) {
    return (
      <div className="work-job-archive-report" style={{ padding: '2rem', textAlign: 'center', color: '#64748b' }}>
        加载中...
      </div>
    );
  }
  if (error) {
    return (
      <div className="work-job-archive-report" style={{ padding: '2rem', textAlign: 'center', color: '#94a3b8' }}>
        {error}
        <div style={{ marginTop: '1rem' }}>
          <button type="button" onClick={handleBack} className="back-btn" style={{ color: '#334155', borderColor: '#cbd5e1' }}>
            返回
          </button>
        </div>
      </div>
    );
  }
  if (!jobId) {
    return (
      <div className="work-job-archive-report" style={{ padding: '2rem', textAlign: 'center', color: '#94a3b8' }}>
        请从作业详情页进入归档
        <div style={{ marginTop: '1rem' }}>
          <button type="button" onClick={() => navigate('/dashboard')} className="back-btn" style={{ color: '#334155', borderColor: '#cbd5e1' }}>
            返回
          </button>
        </div>
      </div>
    );
  }

  return (
    <div className="work-job-archive-report">

      {/* 顶部操作栏 */}
      <div className="archive-toolbar no-print">
        <h2>作业归档详情页</h2>
        <div className="archive-toolbar-actions">
          <button type="button" onClick={handleExportPdf} className="export-pdf-btn" disabled={exportingPdf}>
            <FileDown style={{ width: 16, height: 16 }} />
            {exportingPdf ? '导出中...' : '导出PDF'}
          </button>
          <button type="button" onClick={handlePrint} className="print-btn">
            <Printer style={{ width: 16, height: 16 }} />
            打印归档
          </button>
          <button type="button" onClick={handleBack} className="back-btn">
            <ArrowLeft style={{ width: 16, height: 16 }} />
            返回
          </button>
        </div>
      </div>

      {/* 纸张容器：大屏下按视口缩放，1:1 还原 A4 观感、减小左右留白 */}
      <div className="archive-pages-wrap">
      {/* ================= 第 1 页：基本信息与结果（与 test1 一致） ================= */}
      <div ref={page1Ref} className="page-sheet watermark-bg page-1" style={watermarkStyle}>
        <div className="top-bar" />
        <header className="page-header">
          <div>
            <h1>作业归档报告</h1>
            <p className="subtitle">Archiving Report for Complex Workflow</p>
          </div>
          <div className="doc-no-wrap">
            <div className="doc-no-label">作业单据编号</div>
            <div className="doc-no">{jobDetail?.orderNo ?? jobDetail?.code ?? '-'}</div>
          </div>
        </header>

        <div className="info-grid">
          <div className="info-box">
            <h3>基本信息</h3>
            <div className="info-row"><span className="info-label">作业名称</span><span className="info-value">{jobDetail?.name ?? '-'}</span></div>
            <div className="info-row"><span className="info-label">发起人</span><span className="info-value">{jobDetail?.initiatorName ?? jobDetail?.initiator ?? '-'}</span></div>
            <div className="info-row"><span className="info-label">发起时间</span><span className="info-value">{jobDetail?.initiationTime ? formatDateWithFormat(jobDetail.initiationTime) : '-'}</span></div>
          </div>
          <div className="info-box">
            <h3>执行结论</h3>
            <div className="conclusion-row">
              <span className="conclusion-status">{conclusion.text}</span>
              <span className="conclusion-badge">{conclusion.badge}</span>
            </div>
            <p className="conclusion-desc">{conclusion.desc}</p>
          </div>
        </div>

        <section className="section">
          <div className="section-header">
            <h3 className="section-title">流程执行摘要</h3>
          </div>
          <div className="summary-bar summary-steps">
            {(() => {
              const nodes = sortedNodes;
              const stepCount = nodes.length;
              if (stepCount === 0) return null;
              const gridCols = Array.from({ length: stepCount * 2 - 1 }, (_, j) => (j % 2 === 0 ? 'minmax(36px, 1fr)' : '1fr')).join(' ');
              const getState = (i: number) => (nodes[i] ? getNodeStepState(nodes[i].approvalStatus) : 'pending' as const);
              const connDone = (i: number) => getState(i) !== 'pending' || getState(i + 1) !== 'pending';
              const el: React.ReactNode[] = [];
              for (let i = 0; i < stepCount; i++) {
                const state = getState(i);
                const iconClass = state === 'done' ? 'summary-step-done' : state === 'active' ? 'summary-step-active summary-step-icon-active' : 'summary-step-pending';
                el.push(
                  <div key={`icon-${i}`} className={`summary-step-icon ${iconClass}`}>
                    {state === 'done' && <Check size={18} strokeWidth={2.5} />}
                    {state === 'active' && <Clock size={18} strokeWidth={2.5} />}
                    {state === 'pending' && <span className="summary-step-dot" />}
                  </div>
                );
                if (i < stepCount - 1) {
                  el.push(<div key={`conn-${i}`} className={`summary-connector ${connDone(i) ? 'summary-connector-done' : 'summary-connector-pending'}`} />);
                }
              }
              for (let i = 0; i < stepCount; i++) {
                const col = 1 + i * 2;
                const label = nodes[i]?.nodeName ?? '';
                el.push(<div key={`label-${i}`} className="summary-label-cell" style={{ gridColumn: col }}><span className="summary-step-label">{label || '\u00A0'}</span></div>);
              }
              return (
                <div className="summary-grid" style={{ gridTemplateColumns: gridCols }}>
                  {el}
                </div>
              );
            })()}
          </div>
          <p className="summary-footnote">*流程分支详情请翻阅附录页完整拓扑图</p>
        </section>

        <section className="record-section">
          <div className="record-log-title">
            <span className="record-log-title-bar" />
            <h3 className="record-log-title-text">归档流水日志 <span className="record-log-title-en">/ ARCHIVE LOG</span></h3>
          </div>
          <div className="record-table-wrap">
            <table className="record-table record-table-log">
              <thead>
                <tr>
                  <th className="col-no">#</th>
                  <th>任务名称</th>
                  <th>操作人</th>
                  <th>操作时间</th>
                  <th>状态</th>
                  <th>操作记录</th>
                </tr>
              </thead>
              <tbody>
                {archiveList.length === 0 ? (
                  <tr><td colSpan={6} style={{ textAlign: 'center', color: '#94a3b8', padding: '1rem' }}>暂无流水记录</td></tr>
                ) : (
                  archiveList.map((log, idx) => {
                    const nodeName = log.nodeName ?? log.taskNode ?? '-';
                    const operator = log.nickName ?? log.executorName ?? '-';
                    const initial = (operator && operator !== '-') ? operator.trim().charAt(0).toUpperCase() : '?';
                    const time = log.createTime ?? log.taskStartTime ?? log.handleTime;
                    const timeStr = time != null ? formatDateWithFormat(time) : '-';
                    const st = String(log.approvalStatus ?? log.taskStatus ?? '').toLowerCase();
                    const isProcessing = ['running', 'processing', '2'].includes(st);
                    const statusText = isProcessing ? '处理中' : (st.includes('approve') || st === '3' ? '通过' : st ? '启动' : '-');
                    const remark = log.taskContent ?? log.approvalOpinion ?? log.remark ?? '';
                    return (
                      <tr key={log.id ?? idx}>
                        <td className="col-no">{idx + 1}</td>
                        <td>{nodeName}</td>
                        <td>
                          <span className="cell-operator">
                            <Avatar className="archive-log-avatar">
                              {(log as any).avatar ? (
                                <AvatarImage
                                  src={(log as any).avatar}
                                  alt={operator}
                                />
                              ) : null}
                              <AvatarFallback className={isProcessing ? 'avatar-active' : ''}>
                                {initial}
                              </AvatarFallback>
                            </Avatar>
                            {operator}
                          </span>
                        </td>
                        <td className="cell-time">{timeStr}</td>
                        <td><span className={`cell-status ${isProcessing ? 'status-processing' : 'status-done'}`}><span className="status-dot" /> {statusText}</span></td>
                        <td>{remark}</td>
                      </tr>
                    );
                  })
                )}
              </tbody>
            </table>
          </div>
        </section>

        <div className="page-num">{t('common.pageOfTotal', { current: 1, total: 2 })}</div>
      </div>

      {/* ================= 第 2 页：完整作业流程拓扑图（React Flow，可拖拽；打印时整体缩小） ================= */}
      <div ref={page2Ref} className="page-sheet watermark-bg page-2" style={watermarkStyle}>
        <div className="page-2-header">
          <h2>附录：完整作业流程拓扑图</h2>
          <span className="doc-ref">单据：{jobDetail?.orderNo ?? jobDetail?.code ?? '-'}</span>
        </div>

        <div className="flow-wrap flow-container archive-flow-print-wrapper">
          {flowNodes.length > 0 ? (
            <ReactFlowProvider>
              <style>{`
                .react-flow__attribution { display: none !important; }
                .react-flow__arrowhead { fill: #000000 !important; }
                .react-flow__edge-path { stroke: #000000 !important; }
                .react-flow__edge path { stroke: #000000 !important; }
              `}</style>
              <div className="archive-flow-inner">
              <ReactFlow
                key={`archive-flow-${flowNodes.length}`}
                nodes={flowNodes}
                edges={flowEdges}
                onNodesChange={onFlowNodesChange}
                onEdgesChange={onFlowEdgesChange}
                nodeTypes={archiveNodeTypes}
                fitView
                fitViewOptions={{ padding: 0.35 }}
                nodesDraggable={true}
                nodesConnectable={false}
                elementsSelectable={true}
                defaultEdgeOptions={{
                  style: { strokeWidth: 2, stroke: '#000000' },
                  markerStart: { type: 'arrowclosed' as any, color: '#000000' },
                }}
              >
                <Background variant={BackgroundVariant.Dots} gap={16} size={1} color="#000000" />
              </ReactFlow>
              </div>
            </ReactFlowProvider>
          ) : (
            <div className="flex items-center justify-center h-full text-gray-400 text-sm">暂无流程拓扑数据（节点数：{jobDetail?.workflowWorkNodeDOList?.length ?? 0}）</div>
          )}
        </div>

        <div className="flow-legend">
          <div className="flow-legend-item"><div className="icon icon-done" /> 已完成</div>
          <div className="flow-legend-item"><div className="icon icon-active" /> 进行中/当前</div>
          <div className="flow-legend-item"><div className="icon icon-pending" /> 未执行</div>
        </div>

        <div className="page-num">{t('common.pageOfTotal', { current: 2, total: 2 })}</div>
      </div>
      </div>
    </div>
  );
}
