import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import CustomerServiceWorkspaceFrame from '../components/CustomerServiceWorkspaceFrame';
import ConversationList, {
  CONVERSATION_LIST_SAMPLE_SECTIONS,
  resolveConversationStatusTag,
  normalizeConversationItemTags,
} from '../components/ConversationList';
import InfoDisplayPanel from '../components/InfoDisplayPanel';
import AgentNodeCard from '../components/AgentNodeCard';
import AgentStatusBar from '../components/AgentStatusBar';
import { getAgentFamilyAvatar } from '../components/AgentFamilyAvatar';
import ChatMessage, { useStreamingTaskGroups } from '../components/ChatMessage';
import Button from '../components/Button';
import Icon from '../components/Icon';
import plannerAgentAvatar from '../components/avatar-assets/planner-agent.png';
import analysisAgentAvatar from '../components/avatar-assets/analysis-agent.png';
import outboundAgentAvatar from '../components/avatar-assets/outbound-agent.png';
import executionAgentAvatar from '../components/avatar-assets/execution-agent.png';
import alertAgentAvatar from '../components/avatar-assets/alert-agent.png';
import { getTeamMemberByName } from '../teamMembers';
import { tfdsResizeHandleLineClass } from '../shared/resize-handle';
import { useListInitialLoading } from './pageListShared';

const PANEL_OVERLAP = 32;
const DEFAULT_SIDE_WIDTH = 432;
const AVATAR_ONLY_WIDTH = 88;
const CARD_MIN_WIDTH = 333;
/**
 * `mainMinWidth` 必须根据右侧主白卡内是否同时存在 InfoDisplayPanel 动态计算：
 *   - 双栏（Agent Flow + InfoDisplayPanel）：≥ AGENT_PANEL_MIN_WIDTH + MAIN_CONTENT_GAP + INFO_PANEL_MIN_WIDTH
 *   - 单栏（仅 Agent Flow，infoPanelVisible=false）：≥ AGENT_PANEL_MIN_WIDTH 即可
 * 写死 720 会导致即使用户主动关闭信息栏，红框也无法继续向下拖窄到 Agent Flow 真正的最小宽度。
 */
const MAIN_PANEL_DUAL_MIN_WIDTH = 720;
const MAIN_CONTENT_GAP = 16;
const AGENT_PANEL_MIN_WIDTH = 360;
const INFO_PANEL_MIN_WIDTH = 320;
const INFO_PANEL_DEFAULT_WIDTH = 380;
const MAIN_PANEL_CONTENT = [
  'relative flex size-full min-h-0 min-w-0 items-stretch justify-center gap-4 p-4',
].join(' ');
/**
 * Agent Flow（agent 执行区 / agent 对话区 / agent 沟通区）容器：
 *   顶部为 5 个 AgentNodeCard 组成的"节点流"（垂直可滚动，最新节点默认贴底显示）；
 *   底部为 AgentStatusBar，默认吸底向上展开。
 * 宽度规则：
 *   - 外层白色容器（AGENT_FLOW）支持宽度自由调整，跟随主白卡 ↔ 右侧信息栏的拖拽
 *     热区实时调整，没有宽度上限，会随容器一起继续撑大或收窄。
 *   - 内层组件区（AGENT_FLOW_INNER）才是 900px 限宽的主体：当外层白卡宽度 ≤ 900px
 *     时，节点流与状态栏跟随容器自适应铺满；当外层白卡宽度 > 900px 时，节点流与状态栏
 *     停留在最大 900px 的尺寸并通过 `mx-auto` 在白卡内水平居中，多余空间留白。
 *   - 即便外层宽度再大，内部组件也不会超过 900px，避免出现节点卡片被无限拉宽导致
 *     行长过长、阅读体验下降。
 * 间距规则：
 *   - 节点滚动区使用 `pt-6`：顶部留 24px。
 *   - 节点滚动区与 AgentStatusBar 之间的固定 16px 留白由 AgentStatusBar 的外层
 *     STICKY 容器 `pt-4` 提供，让 AgentStatusBar 整个板块（含上方阴影/彩光）的顶部
 *     与最后一个节点保持 16px 间距。
 *   - AgentStatusBar 板块下方的留白由 AgentStatusBar 自身 ROOT 内蕴 padding/彩光条
 *     负责，外层不再叠加 `pb-*`。
 */
const AGENT_FLOW_MAX_WIDTH = 900;
const AGENT_FLOW = 'flex h-full min-w-0 flex-1 flex-col overflow-hidden rounded-xl bg-surface';
const AGENT_FLOW_INNER = 'mx-auto flex h-full w-full min-w-0 flex-col';
const AGENT_FLOW_SCROLL_AREA = 'flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto pt-6';
const AGENT_FLOW_STICKY_AREA = 'shrink-0 pt-4';
const INFO_PANEL_FRAME = 'relative flex h-full min-w-0 shrink-0 items-stretch overflow-visible';
const INFO_PANEL_RESIZE_HANDLE = [
  'group/workspace-info-resize absolute inset-y-0 left-0 z-20 flex w-2 -translate-x-1/2 cursor-col-resize items-stretch justify-center border-0 bg-transparent p-0',
  'touch-none select-none',
  'focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-blueGrey-400',
].join(' ');
const INFO_PANEL_RESIZE_HANDLE_LINE = tfdsResizeHandleLineClass('workspace-info-resize');
const WORKSPACE_CONVERSATION_SECTIONS = CONVERSATION_LIST_SAMPLE_SECTIONS;
const DEFAULT_CONVERSATION_ID = 'pending-1';
const AGENT_AVATAR = getTeamMemberByName('段然');

function clamp(value, min, max) {
  return Math.min(max, Math.max(min, value));
}

function getConversationItems(sections) {
  return (sections || []).flatMap((section) => section.items || []);
}

function findConversationItem(itemId, sections = WORKSPACE_CONVERSATION_SECTIONS) {
  return getConversationItems(sections).find((item) => item.id === itemId);
}

/**
 * 为每个左侧会话/工单生成一组 5 段 Agent 节点剧本，
 * 覆盖抖音电商客服工单托管的：进线归集 → 问题诊断 → 外呼商家 → 退款执行 → 异常提醒待确认。
 *
 * ⚠️ 节点 `title` 必须从 AgentFamilyAvatar 家族数据派生（`getAgentFamilyAvatar(family).name`），
 * 不要再手写"规划Agent"/"异常提醒"/"异常提醒Agent"这种重复文案。这样头像与标题文案的
 * 来源是同一份家族数据，永远保持像素级一致；节点 title 也会与底部 AgentStatusBar 顶部
 * 头像默认名称（最新节点的家族名）保持一致语义。
 */
function buildAgentNodesForConversation(conversation) {
  const userName = conversation?.userName || '海阔**天空';
  const orderId = conversation?.orderId || '2918575148379876';
  const channel = conversation?.tags?.[0]?.label === '生活服务' ? '抖音生活服务' : '抖音电商';
  const titleOf = (family) => getAgentFamilyAvatar(family).name;

  return [
    {
      id: 'intake',
      title: titleOf('planner'),
      timestamp: '2026-04-16 14:01:08',
      agentFamilyAvatar: 'planner',
      avatarSrc: plannerAgentAvatar,
      avatarAlt: '规划Agent头像',
      summary: '已完成工单进线归集，已识别核心诉求',
      mentionLabel: '@ 工单调度 | 调度 Anna',
      statusText: '已完成进线归集',
      statusTone: 'success',
      cardType: 'plain-text',
      collapsible: true,
      defaultCollapsed: true,
      textContent:
        `已自动归集本次工单进线信息：渠道 ${channel}，用户 ${userName}，工单单号 ${orderId}，主要诉求集中在订单履约异常与退款补偿，建议优先按"先安抚 → 同步处理 → 给确定方案"的口径进行后续编排。`,
      secondaryActionText: null,
      primaryActionText: null,
    },
    {
      id: 'diagnosis',
      title: titleOf('analysis'),
      timestamp: '2026-04-16 14:03:42',
      agentFamilyAvatar: 'analysis',
      avatarSrc: analysisAgentAvatar,
      avatarAlt: '研判Agent头像',
      summary: '已完成工单研判，给出处理结论',
      mentionLabel: '@ 客服 | 段然',
      statusText: '已完成工单研判',
      statusTone: 'success',
      cardType: 'field-description',
      collapsible: true,
      defaultCollapsed: true,
      detailFields: [
        { key: '诉求类别', value: '订单履约异常 · 退款补偿' },
        { key: '风险等级', value: '中（重复进线 3 次）' },
        { key: '研判结论', value: '走商家核实 + 规则外退款补偿' },
        { key: '处理时效', value: '建议 30 分钟内完成' },
      ],
      secondaryActionText: null,
      primaryActionText: null,
    },
    {
      id: 'merchant-call',
      title: titleOf('outbound'),
      timestamp: '2026-04-16 14:08:21',
      agentFamilyAvatar: 'outbound',
      avatarSrc: outboundAgentAvatar,
      avatarAlt: '外呼Agent头像',
      summary: '已外呼商家完成订单核实',
      mentionLabel: '@ 商家协商 | 执行 Jack',
      statusText: '已完成商家外呼核实',
      statusTone: 'success',
      cardType: 'field-description',
      collapsible: true,
      defaultCollapsed: true,
      detailFields: [
        { key: '商家联系人', value: '门店运营 · 王店长' },
        { key: '商家反馈', value: '同意按规则外退款 + 同等权益补偿券' },
        { key: '通话时长', value: '02:47' },
        { key: '商家工单', value: 'M_2026041614082' },
      ],
      secondaryActionText: null,
      primaryActionText: null,
    },
    {
      id: 'refund',
      title: titleOf('execution'),
      timestamp: '2026-04-16 14:12:55',
      agentFamilyAvatar: 'execution',
      avatarSrc: executionAgentAvatar,
      avatarAlt: '执行Agent头像',
      summary: '已根据外呼结果完成退款操作',
      mentionLabel: '@ 退款 | 执行 Jack',
      statusText: '退款执行完成',
      statusTone: 'success',
      cardType: 'field-description',
      collapsible: true,
      defaultCollapsed: false,
      detailFields: [
        { key: '退款状态', value: '退款成功' },
        { key: '订单是否已退款', value: '已退款' },
        { key: '订单展示状态', value: '交易完成' },
        { key: '用户退款类型', value: '规则外退款' },
        { key: '预约状态', value: '已预约' },
      ],
      showDetailAction: true,
      detailActionLabel: '查看退款流水',
      secondaryActionText: null,
      primaryActionText: null,
    },
    {
      id: 'alert',
      title: titleOf('alert'),
      timestamp: '2026-04-16 14:14:08',
      agentFamilyAvatar: 'alert',
      avatarSrc: alertAgentAvatar,
      avatarAlt: '异常提醒Agent头像',
      summary: '检测到规则外退款风险，请人工确认补偿口径',
      mentionLabel: '@ 客服 | 段然',
      statusText: '存在异常风险待确认',
      // 节点已被用户/系统判定属于"待人工确认"类型，统一使用 AgentNodeCard 默认的
      // IconLoading 彩环（即 pending tone），而不是 warning 橙色三角警示，
      // 让"待确认"在节点流里始终呈现统一的 AI loading 视觉。
      statusTone: 'pending',
      cardType: 'plain-text',
      collapsible: true,
      defaultCollapsed: false,
      textContent:
        '本次按规则外退款执行，已成功原路退回实付金额，并叠加同等权益补偿券。但用户在过去 7 天内已重复进线 3 次，建议人工客服在外呼回访前再次核对补偿口径与历史承诺，避免出现重复退款、补偿降级或承诺不一致的情况。',
      showDetailAction: true,
      detailActionLabel: '查看详情',
      secondaryActionText: '稍后处理',
      primaryActionText: '立即处理',
    },
  ];
}

/**
 * 托管助手 mock 任务流（与客服工作台保持一致的语义）：理解 → 检索 → 生成。
 */
function buildAssistantTaskGroups(latestUserText) {
  const summary = (latestUserText || '').trim();
  const truncated = summary.length > 36 ? `${summary.slice(0, 36)}…` : summary;
  return [
    {
      id: 'ticket-understand',
      title: '理解工单当前节点与未完成项',
      status: 'completed',
      defaultExpanded: true,
      steps: [
        {
          id: 'ticket-understand-parse',
          title: '解析当前 Agent 节点剧本与待人工确认项',
          actionLabel: '正在调用工单意图 Agent',
          actionDetail: truncated ? `Ticket_Intent_Agent /msg "${truncated}"` : 'Ticket_Intent_Agent /node/latest',
          actionIconName: 'message-question-circle-stroked',
        },
      ],
    },
    {
      id: 'ticket-retrieve',
      title: '检索退款规则、商家反馈与历史进线',
      status: 'completed',
      defaultExpanded: true,
      steps: [
        {
          id: 'ticket-retrieve-rules',
          title: '匹配抖音电商规则外退款判定与补偿口径',
          actionLabel: '正在调用规则 Agent',
          actionDetail: 'Knowledge_Agent /policy/refund',
          actionIconName: 'line-chart-up-04-stroked',
        },
        {
          id: 'ticket-retrieve-history',
          title: '汇总用户近 7 天 3 次进线诉求',
          actionLabel: '正在调用上下文 Agent',
          actionDetail: 'Context_Agent /history/7d',
          actionIconName: 'line-chart-up-04-stroked',
        },
      ],
    },
    {
      id: 'ticket-compose',
      title: '生成人工确认补偿口径建议',
      status: 'processing',
      defaultExpanded: true,
      steps: [
        {
          id: 'ticket-compose-tone',
          title: '校准回复语气：先确认结果 → 再说明补偿 → 给后续承诺',
          actionLabel: '正在调用话术 Agent',
          actionDetail: 'Tone_Agent /reply/calibrate',
          actionIconName: 'sticker-square-stroked',
        },
        {
          id: 'ticket-compose-draft',
          title: '生成可一键发送给用户的回访话术',
          actionLabel: '正在调用生成 Agent',
          actionDetail: 'Reply_Agent /reply/draft',
          actionIconName: 'code-square-01-stroked',
        },
      ],
    },
  ];
}

const ASSISTANT_FALLBACK_REPLY = '您好，本次工单已按规则外方案完成原路退款，并为您额外补发同等面额优惠券；针对您之前 3 次进线的体验问题，平台已同步登记。稍后将通过电话回访与您确认是否需要进一步处理。';

function isStreamingFinished(streamed, fullGroups) {
  if (!Array.isArray(streamed) || !Array.isArray(fullGroups)) return false;
  if (streamed.length !== fullGroups.length) return false;
  const lastStreamed = streamed[streamed.length - 1];
  const lastFull = fullGroups[fullGroups.length - 1];
  if (!lastStreamed || !lastFull) return false;
  return (
    lastStreamed.status === 'completed'
    && (lastStreamed.steps?.length ?? 0) >= (lastFull.steps?.length ?? 0)
  );
}

function TicketAssistantMessage({ threadId, latestUserText, recommendedReply, onCopyToInput, onSendToUser }) {
  const fullTaskGroups = useMemo(
    () => buildAssistantTaskGroups(latestUserText),
    [latestUserText],
  );
  const streamed = useStreamingTaskGroups(fullTaskGroups, { intervalMs: 600 });
  const finished = isStreamingFinished(streamed, fullTaskGroups);

  return (
    <div className="flex flex-col gap-3" key={threadId}>
      {latestUserText ? (
        <ChatMessage
          role="user"
          userBubbleTone="fill"
          userContent={[{ type: 'text', value: latestUserText }]}
        />
      ) : null}
      <ChatMessage
        role="ai"
        header={{ name: 'HiAI' }}
        title=""
        steps={null}
        taskGroups={streamed}
      />
      {finished ? (
        <ChatMessage
          role="ai"
          title=""
          steps={null}
          resultText={recommendedReply}
        />
      ) : null}
      {finished ? (
        <div className="flex items-center gap-1">
          <Button
            type="button"
            variant="ghost-black"
            size="sm"
            iconOnly
            tooltip="复制到 Ticko 指令输入框"
            icon={<Icon name="copy-06-stroked" size="sm" color="currentColor" aria-hidden="true" />}
            onClick={() => onCopyToInput?.(recommendedReply)}
          />
          <Button
            type="button"
            variant="ghost-black"
            size="sm"
            iconOnly
            tooltip="作为外呼话术发送"
            icon={<Icon name="send-03-stroked" size="sm" color="currentColor" aria-hidden="true" />}
            onClick={() => onSendToUser?.(recommendedReply)}
          />
        </div>
      ) : null}
    </div>
  );
}

/**
 * 在工单托管语义下，左侧 ConversationList 卡片消息流的"消息"由 Agent 节点摘要派生：
 * 把 5 个 AgentNodeCard 的 summary 转为 bot/agent 消息，让左侧卡片态保持完整可读。
 */
function buildConversationCardPreviewMessages(agentNodes = [], conversation) {
  const userName = conversation?.userName || '用户';
  const userMessage = {
    id: `${conversation?.id || 'ticket'}-user`,
    role: 'user',
    text: `${userName}：希望尽快确认订单履约和退款情况，避免再次反复沟通。`,
  };
  const agentMessages = agentNodes.map((node) => ({
    id: `${conversation?.id || 'ticket'}-${node.id}`,
    role: node.statusTone === 'warning' ? 'agent' : 'bot',
    text: node.summary,
  }));
  return [userMessage, ...agentMessages];
}

const TICKET_CARD_META_BY_SECTION = {
  pending: [
    { label: '24:20', variant: 'grey', iconName: 'alarm-clock-stroked' },
    { label: '催4', variant: 'grey' },
    { label: '高优', variant: 'grey' },
  ],
  managed: [
    { label: '24时30分', variant: 'grey', iconName: 'alarm-clock-stroked' },
    { label: '催4', variant: 'grey' },
    { label: '平台托管', variant: 'grey' },
  ],
  other: [
    { label: '已归档', variant: 'grey' },
    { label: '用户已确认', variant: 'grey' },
  ],
};

function mergeTicketMetaTags(itemTags = [], sectionId) {
  const baseTags = TICKET_CARD_META_BY_SECTION[sectionId] || TICKET_CARD_META_BY_SECTION.pending;
  const auxiliaryTags = (itemTags || []).filter((tag) => tag?.variant === 'grey');
  const mergedTags = [...baseTags, ...auxiliaryTags];
  const seenLabels = new Set();

  return mergedTags.filter((tag) => {
    if (!tag?.label || seenLabels.has(tag.label)) return false;
    seenLabels.add(tag.label);
    return true;
  });
}

function buildTicketProgressText(agentNodes = [], conversation) {
  const latestNode = agentNodes[agentNodes.length - 1];
  const executionNode = agentNodes.find((node) => node.id === 'refund');
  const title = conversation?.title || '当前工单';

  return [
    `${latestNode?.title || '异常提醒Agent'} 已完成 ${title} 的整体风险确认`,
    executionNode?.summary || '执行Agent 已完成关键处理动作',
    '客服 Agent 正准备复核补偿口径并同步用户下一步处理结果。',
  ].join('，');
}

function buildTicketProgressAgents(agentNodes = []) {
  return agentNodes
    .map((node) => node.agentFamilyAvatar)
    .filter(Boolean)
    .slice(0, 4);
}

const STATUS_BAR_NODE_ICON_BY_TONE = {
  success: { iconName: 'check-circle-stroked', iconClassName: 'text-green-500' },
  pending: { iconLoadingRing: true, iconClassName: '' },
  warning: { iconName: 'alert-circle-stroked', iconClassName: 'text-orange-500' },
  default: { iconName: 'message-check-circle-stroked', iconClassName: 'text-blueGrey-500' },
};

const STATUS_BAR_SUMMARY_KEY_BY_NODE_ID = {
  intake: '进线归集',
  diagnosis: '工单研判',
  'merchant-call': '商家核实',
  refund: '退款执行',
  alert: '风险确认',
};

function getStatusBarNodeIcon(node) {
  return STATUS_BAR_NODE_ICON_BY_TONE[node?.statusTone] || STATUS_BAR_NODE_ICON_BY_TONE.default;
}

function getNodeContentDigest(node) {
  if (!node) return '';
  if (Array.isArray(node.detailFields) && node.detailFields.length > 0) {
    return node.detailFields
      .map((field) => `${field.key}：${field.value}`)
      .join('；');
  }
  if (node.textContent) return node.textContent;
  if (node.statusText || node.summary) {
    return [node.statusText, node.summary].filter(Boolean).join('：');
  }
  return '该节点暂无补充内容';
}

function buildStatusBarPlanSteps(agentNodes = []) {
  return agentNodes.map((node) => {
    const icon = getStatusBarNodeIcon(node);
    return {
      id: node.id,
      text: node.summary || node.statusText,
      agentLabel: node.mentionLabel || `@ ${node.title}`,
      iconName: icon.iconName,
      iconLoadingRing: icon.iconLoadingRing,
      iconClassName: icon.iconClassName,
    };
  });
}

function buildStatusBarSummarySections(agentNodes = []) {
  return agentNodes.map((node) => ({
    key: STATUS_BAR_SUMMARY_KEY_BY_NODE_ID[node.id] || (node.title || '节点总结').slice(0, 4),
    value: getNodeContentDigest(node),
  }));
}

function buildStatusBarSummaryTags(agentNodes = []) {
  const completedCount = agentNodes.filter((node) => node.statusTone === 'success').length;
  const pendingCount = agentNodes.filter((node) => node.statusTone === 'pending').length;
  const latestNode = agentNodes[agentNodes.length - 1];
  return [
    `${agentNodes.length} 个节点`,
    `已完成 ${completedCount}`,
    pendingCount > 0 ? `待确认 ${pendingCount}` : '无待确认',
    latestNode?.statusText || latestNode?.summary || '最新节点已同步',
  ];
}

function buildStatusBarPrimaryMenuItems(agentNodes = []) {
  return agentNodes
    .filter((node) => node.secondaryActionText || node.primaryActionText || node.showDetailAction)
    .slice(-3)
    .map((node) => ({
      id: `${node.id}-action`,
      label: node.primaryActionText || node.secondaryActionText || node.detailActionLabel || node.statusText,
      iconName: node.statusTone === 'pending' ? 'alert-circle-stroked' : 'check-circle-stroked',
    }));
}

function buildWorkspaceConversationSections(sections = WORKSPACE_CONVERSATION_SECTIONS) {
  return sections.map((section) => ({
    ...section,
    items: (section.items || []).map((item) => {
      const nodes = buildAgentNodesForConversation(item);
      return {
        ...item,
        messages: buildConversationCardPreviewMessages(nodes, item),
        status: 'replied',
        draftText: '',
        ticketProgressText: buildTicketProgressText(nodes, item),
        ticketProgressAgents: buildTicketProgressAgents(nodes),
        ticketMetaTags: mergeTicketMetaTags(item.tags, section.id),
      };
    }),
  }));
}

function buildThreadContext(item) {
  const resolvedItem = item || findConversationItem(DEFAULT_CONVERSATION_ID);
  return {
    id: resolvedItem?.id || DEFAULT_CONVERSATION_ID,
    title: resolvedItem?.title || '当前工单',
    userName: resolvedItem?.userName || '用户',
    userId: resolvedItem?.orderId || '',
    agentName: AGENT_AVATAR?.name || '客服',
  };
}

/**
 * AgentFlowPanel — Agent Flow（agent 执行区 / agent 对话区 / agent 沟通区）
 *
 * 在工单 Agent 工作台模板中，主白卡内左侧主面板就是这个 Agent Flow：
 *   - 顶部：5 个 AgentNodeCard 组成的"节点流"，垂直可滚动；
 *   - 底部：AgentStatusBar，默认吸底向上展开；
 * 宽度规则：
 *   - 外层白色容器自身没有宽度上限，跟随主白卡 ↔ 右侧信息栏的拖拽热区自由伸缩；
 *   - 内部节点流与 AgentStatusBar 限制最大宽度 900px：白卡 ≤ 900px 时跟随自适应铺满，
 *     白卡 > 900px 时停留在 900px 并通过 `mx-auto` 在白卡内居中，多余空间留白。
 * 默认展示最新节点在 flow 底部（与 IM 对话区"最新消息"语义一致），切换工单时也复位到底部。
 */
function AgentFlowPanel({
  conversationKey,
  agentNodes,
  activeConversationItem,
  activeConversationTags,
  statusBarPrefill,
  statusBarSendSignal,
  infoPanelVisible,
  onToggleInfoPanel,
}) {
  const scrollRef = useRef(null);

  /* Agent Flow 默认展示最新节点 = 滚动到底部；切换工单或节点变化时也复位到底部 */
  useLayoutEffect(() => {
    const node = scrollRef.current;
    if (!node) return;
    node.scrollTop = node.scrollHeight;
  }, [conversationKey, agentNodes]);

  /**
   * 「最新节点」= 节点数组末尾（节点按时间正序排列，flow 底部即视口最底部）。
   * 底部 AgentStatusBar 顶部头像 + 标题文案必须完全等价于这条节点：
   *   ① 头像（headerAgentFamilyAvatar）= latestNode.agentFamilyAvatar
   *   ② 标题文案（title）= latestNode.summary
   * 这样底部状态栏与上方最新一条节点白卡的"头像 + 文案 + 标签"形成
   * 像素级一致的"摘要镜像"，避免出现"上面是异常提醒 Agent，下面写
   * 已完成任务规划"的语义割裂。
   */
  const latestNode = agentNodes[agentNodes.length - 1];
  const latestAgentFamily = latestNode?.agentFamilyAvatar || 'planner';
  const latestSummary = latestNode?.summary;
  const statusBarPlanSteps = useMemo(() => buildStatusBarPlanSteps(agentNodes), [agentNodes]);
  const statusBarSummarySections = useMemo(() => buildStatusBarSummarySections(agentNodes), [agentNodes]);
  const statusBarSummaryTags = useMemo(() => buildStatusBarSummaryTags(agentNodes), [agentNodes]);
  const statusBarPrimaryMenuItems = useMemo(() => buildStatusBarPrimaryMenuItems(agentNodes), [agentNodes]);
  const statusBarPrimaryActionLabel = latestNode?.primaryActionText || latestNode?.secondaryActionText || '完结';
  const statusBarInputPlaceholder = statusBarPrefill.text
    || `可以输入指令，调整${latestNode?.title || '最新 Agent 节点'}处理流程`;

  return (
    <div
      className={AGENT_FLOW}
      data-tfds-region="agent-flow"
      data-region-alias="agent-flow agent-execute-area agent-chat-area agent-comm-area"
    >
      {/* 内层 900px 限宽 + 居中容器：节点流与 AgentStatusBar 在白卡 > 900px 时停留在 900px 居中显示 */}
      <div className={AGENT_FLOW_INNER} style={{ maxWidth: `${AGENT_FLOW_MAX_WIDTH}px` }}>
        <div ref={scrollRef} className={AGENT_FLOW_SCROLL_AREA}>
          {agentNodes.map((node) => (
            <AgentNodeCard
              key={`${conversationKey}-${node.id}`}
              {...node}
            />
          ))}
        </div>
        <div className={AGENT_FLOW_STICKY_AREA}>
          <AgentStatusBar
            key={`status-${conversationKey}-${statusBarPrefill.seed}-${statusBarSendSignal.seed}`}
            statusVariant="intervention"
            sticky={false}
            defaultCollapsed
            /* 顶部头像 + 标题文案 = Agent Flow 最新节点的家族 + summary，确保上下"摘要镜像" */
            headerAgentFamilyAvatar={latestAgentFamily}
            title={latestSummary}
            /* 状态栏展开区所有内容都从上方 5 个 AgentNodeCard 节点派生：
               planSteps = 每个节点 summary + mentionLabel；
               summarySections = 每个节点 statusText/title + detailFields/textContent；
               summaryTags = 节点数量、完成数、待确认数与最新节点状态。 */
            planSteps={statusBarPlanSteps}
            summaryTitle="Agent 节点总结"
            summaryTags={statusBarSummaryTags}
            summarySections={statusBarSummarySections}
            primaryActionLabel={statusBarPrimaryActionLabel}
            primaryMenuItems={statusBarPrimaryMenuItems}
            /* 工单上下文同步：用户头像 / 用户昵称 / 工单 ID / 状态标签必须与左侧 ConversationList
               当前选中工单条目完全一致，禁止再用静态默认值。这里直接读 activeConversationItem。 */
            customerName={activeConversationItem?.userName || '海阔**天空'}
            customerId={activeConversationItem?.orderId || '2918575148379876'}
            customerAvatarSrc={activeConversationItem?.avatarSrc}
            metaTags={Array.isArray(activeConversationTags) && activeConversationTags.length > 0
              ? activeConversationTags
              : (Array.isArray(activeConversationItem?.tags) ? activeConversationItem.tags : undefined)}
            inputPlaceholder={statusBarInputPlaceholder}
            detailActionLabel={infoPanelVisible ? '收起详情' : '查看详情'}
            onDetailClick={onToggleInfoPanel}
          />
        </div>
      </div>
    </div>
  );
}

/**
 * TicketAgentWorkspaceFramePattern — 工单 Agent 工作台框架
 *
 * 整体复用客服工作台框架；区别是中间主白卡的 IM 对话区被替换为
 * 5 个 AgentNodeCard 业务组件 + AgentStatusBar 业务组件的上下拼接：
 *   - 顶部为可滚动的 Agent 节点剧本（5 个节点）
 *   - 底部为 AgentStatusBar 默认 sticky 吸底，向上展开
 * 整体内容模拟抖音电商客服工单托管场景。
 */
export default function TicketAgentWorkspaceFramePattern() {
  const listLoading = useListInitialLoading();
  const mainPanelRef = useRef(null);
  const [sideWidth, setSideWidth] = useState(DEFAULT_SIDE_WIDTH);
  const [leftContentMinWidth, setLeftContentMinWidth] = useState(AVATAR_ONLY_WIDTH);
  const [mainPanelWidth, setMainPanelWidth] = useState(0);
  const [infoPanelWidth, setInfoPanelWidth] = useState(INFO_PANEL_DEFAULT_WIDTH);
  const [activeConversationId, setActiveConversationId] = useState(DEFAULT_CONVERSATION_ID);
  /**
   * 默认不显示右侧信息 tab 栏组件；只有点击底部 AgentStatusBar 的"查看详情"按钮后才显示。
   */
  const [infoPanelVisible, setInfoPanelVisible] = useState(false);

  const workspaceSections = useMemo(
    () => buildWorkspaceConversationSections(WORKSPACE_CONVERSATION_SECTIONS),
    [],
  );
  const activeConversationItem = useMemo(
    () => findConversationItem(activeConversationId, workspaceSections) || findConversationItem(DEFAULT_CONVERSATION_ID, workspaceSections),
    [activeConversationId, workspaceSections],
  );
  /**
   * 应用与 ConversationList 内部完全一致的 tags 归一化：
   * 列表渲染时 ConversationList 会用 `normalizeItemTags(item.tags, resolveStatusTag(activeTab, section.id))`
   * 把每个 item 的最后一个 tag 替换为它所在 section 的状态色 tag（"托管中"绿、"待干预"红、
   * "已处理"蓝等）。模板里的 `activeConversationItem` 来自原始 sections 数据（未经归一化），
   * 这里必须**复刻同一份归一化结果**，让底部 AgentStatusBar 拿到的 tags 与左侧选中条目所
   * 渲染出来的 tags 完全一致（颜色 + 文案 + 顺序），实现"右侧 = 左侧选中详情"的镜像。
   * 模板传给 ConversationList 的 `defaultActiveTab="all"`，因此这里 activeTab 也用 "all"，
   * 由 section.id 决定每个工单的状态 tag。
   */
  const activeConversationSectionId = useMemo(() => {
    for (const section of workspaceSections) {
      if ((section.items || []).some((item) => item.id === activeConversationItem?.id)) {
        return section.id;
      }
    }
    return null;
  }, [activeConversationItem, workspaceSections]);
  const activeConversationTags = useMemo(() => {
    const statusTag = resolveConversationStatusTag('all', activeConversationSectionId);
    return normalizeConversationItemTags(activeConversationItem?.tags, statusTag);
  }, [activeConversationItem, activeConversationSectionId]);
  const activeThread = useMemo(
    () => buildThreadContext(activeConversationItem),
    [activeConversationItem],
  );
  const activeAgentNodes = useMemo(
    () => buildAgentNodesForConversation(activeConversationItem),
    [activeConversationItem],
  );

  /* 切换工单时把当前工单最后一个 Agent 节点摘要作为托管助手的"用户最新诉求"输入 */
  const latestUserText = useMemo(() => {
    const lastNode = activeAgentNodes[activeAgentNodes.length - 1];
    return lastNode?.summary || '';
  }, [activeAgentNodes]);
  const recommendedReply = ASSISTANT_FALLBACK_REPLY;

  const [statusBarPrefill, setStatusBarPrefill] = useState({ text: '', seed: 0 });
  const [statusBarSendSignal, setStatusBarSendSignal] = useState({ text: '', seed: 0 });

  useEffect(() => {
    setStatusBarPrefill({ text: '', seed: 0 });
    setStatusBarSendSignal({ text: '', seed: 0 });
  }, [activeThread.id]);

  const handleAssistantCopy = useCallback((text) => {
    setStatusBarPrefill((prev) => ({ text: text || '', seed: prev.seed + 1 }));
  }, []);
  const handleAssistantSend = useCallback((text) => {
    setStatusBarSendSignal((prev) => ({ text: text || '', seed: prev.seed + 1 }));
  }, []);

  const infoPanels = useMemo(
    () => [
      {
        id: 'assistant',
        tabs: [{ id: 'assistant', label: '托管助手' }],
        content: (
          <TicketAssistantMessage
            threadId={activeThread.id}
            latestUserText={latestUserText}
            recommendedReply={recommendedReply}
            onCopyToInput={handleAssistantCopy}
            onSendToUser={handleAssistantSend}
          />
        ),
      },
      {
        id: 'tickets',
        tabs: [
          { id: 'history', label: '历史工单' },
          { id: 'logs', label: '工单日志' },
          { id: 'tools', label: '信息工具' },
        ],
      },
      {
        id: 'info',
        tabs: [
          { id: 'order', label: '订单信息' },
          { id: 'user', label: '用户画像' },
          { id: 'records', label: '沟通记录' },
        ],
      },
    ],
    [activeThread.id, latestUserText, recommendedReply, handleAssistantCopy, handleAssistantSend],
  );

  const handleConversationLayoutWidthRequest = useCallback((contentWidth) => {
    setSideWidth(contentWidth + PANEL_OVERLAP);
  }, []);

  const handleConversationVariantChange = useCallback((nextVariant) => {
    const nextMinWidth = nextVariant === 'card' ? CARD_MIN_WIDTH : AVATAR_ONLY_WIDTH;
    setLeftContentMinWidth(nextMinWidth);
    setSideWidth((currentWidth) => Math.max(currentWidth, nextMinWidth + PANEL_OVERLAP));
  }, []);

  useEffect(() => {
    const node = mainPanelRef.current;
    if (!node) return undefined;

    function updateWidth() {
      setMainPanelWidth(node.getBoundingClientRect().width);
    }

    updateWidth();
    const observer = typeof ResizeObserver !== 'undefined' ? new ResizeObserver(updateWidth) : null;
    observer?.observe(node);
    window.addEventListener('resize', updateWidth);

    return () => {
      observer?.disconnect();
      window.removeEventListener('resize', updateWidth);
    };
  }, []);

  const maxInfoPanelWidth = Number.isFinite(mainPanelWidth) && mainPanelWidth > 0
    ? Math.max(INFO_PANEL_MIN_WIDTH, mainPanelWidth - MAIN_CONTENT_GAP - AGENT_PANEL_MIN_WIDTH)
    : INFO_PANEL_DEFAULT_WIDTH;
  const currentInfoPanelWidth = clamp(infoPanelWidth, INFO_PANEL_MIN_WIDTH, maxInfoPanelWidth);

  useEffect(() => {
    setInfoPanelWidth((currentWidth) => clamp(currentWidth, INFO_PANEL_MIN_WIDTH, maxInfoPanelWidth));
  }, [maxInfoPanelWidth]);

  const updateInfoPanelWidth = useCallback((nextWidth) => {
    setInfoPanelWidth(clamp(nextWidth, INFO_PANEL_MIN_WIDTH, maxInfoPanelWidth));
  }, [maxInfoPanelWidth]);

  const handleInfoPanelResizePointerDown = useCallback((event) => {
    event.preventDefault();
    const startX = event.clientX;
    const startWidth = currentInfoPanelWidth;
    const previousCursor = document.body.style.cursor;
    const previousUserSelect = document.body.style.userSelect;
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';

    function handlePointerMove(moveEvent) {
      const delta = moveEvent.clientX - startX;
      updateInfoPanelWidth(startWidth - delta);
    }

    function handlePointerUp() {
      document.body.style.cursor = previousCursor;
      document.body.style.userSelect = previousUserSelect;
      window.removeEventListener('pointermove', handlePointerMove);
      window.removeEventListener('pointerup', handlePointerUp);
    }

    window.addEventListener('pointermove', handlePointerMove);
    window.addEventListener('pointerup', handlePointerUp);
  }, [currentInfoPanelWidth, updateInfoPanelWidth]);

  const handleInfoPanelResizeKeyDown = useCallback((event) => {
    if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight' && event.key !== 'Home' && event.key !== 'End') return;
    event.preventDefault();

    if (event.key === 'Home') {
      updateInfoPanelWidth(maxInfoPanelWidth);
      return;
    }

    if (event.key === 'End') {
      updateInfoPanelWidth(INFO_PANEL_MIN_WIDTH);
      return;
    }

    const delta = event.shiftKey ? 32 : 16;
    updateInfoPanelWidth(currentInfoPanelWidth + (event.key === 'ArrowLeft' ? delta : -delta));
  }, [currentInfoPanelWidth, maxInfoPanelWidth, updateInfoPanelWidth]);

  const leftPanel = (
    <ConversationList
      title="工单队列"
      sections={workspaceSections}
      cardScenario="ticket"
      loading={listLoading}
      defaultActiveTab="all"
      activeItemId={activeThread.id}
      onItemClick={(item) => setActiveConversationId(item.id)}
      resizable={false}
      collapsible
      autoCollapseOnNarrow
      onLayoutWidthRequest={handleConversationLayoutWidthRequest}
      onVariantChange={handleConversationVariantChange}
      className="h-full"
      style={{ width: '100%', height: '100%' }}
    />
  );

  const mainPanel = (
    <div ref={mainPanelRef} className={MAIN_PANEL_CONTENT}>
      <AgentFlowPanel
        conversationKey={activeThread.id}
        agentNodes={activeAgentNodes}
        activeConversationItem={activeConversationItem}
        activeConversationTags={activeConversationTags}
        statusBarPrefill={statusBarPrefill}
        statusBarSendSignal={statusBarSendSignal}
        infoPanelVisible={infoPanelVisible}
        onToggleInfoPanel={() => setInfoPanelVisible((prev) => !prev)}
      />
      {infoPanelVisible ? (
        <div className={INFO_PANEL_FRAME} style={{ width: `${currentInfoPanelWidth}px` }}>
          <button
            type="button"
            className={INFO_PANEL_RESIZE_HANDLE}
            aria-label="调整右侧信息面板宽度"
            aria-orientation="vertical"
            aria-valuemin={INFO_PANEL_MIN_WIDTH}
            aria-valuemax={Math.round(maxInfoPanelWidth)}
            aria-valuenow={Math.round(currentInfoPanelWidth)}
            onPointerDown={handleInfoPanelResizePointerDown}
            onKeyDown={handleInfoPanelResizeKeyDown}
          >
            <span className={INFO_PANEL_RESIZE_HANDLE_LINE} aria-hidden="true" />
          </button>
          <InfoDisplayPanel
            panels={infoPanels}
            className="!h-full w-full"
            style={{ height: '100%' }}
            onClose={() => setInfoPanelVisible(false)}
            closeActionLabel="关闭信息栏"
          />
        </div>
      ) : null}
    </div>
  );

  /**
   * 动态 mainMinWidth：信息栏可见时按双栏并排算（720），不可见时仅需 Agent Flow 最小宽度（360）。
   * 这样关闭 InfoDisplayPanel 后，外层框架会立刻把"右侧主白卡（红框）"的最小宽度释放下来，
   * 用户可以继续把红框向右拖窄、把左侧 ConversationList 拖宽，而不是被 720 永久卡住。
   */
  const dynamicMainMinWidth = infoPanelVisible
    ? MAIN_PANEL_DUAL_MIN_WIDTH
    : AGENT_PANEL_MIN_WIDTH;

  return (
    <div
      className="tfds-page-ticket-agent-workspace flex h-full min-h-0 min-w-0 w-full items-stretch overflow-hidden"
      style={{
        background: 'var(--color-blueGrey-200, #F2F4F7)',
        borderRadius: 0,
      }}
    >
      <CustomerServiceWorkspaceFrame
        sideWidth={sideWidth}
        minSideWidth={100}
        leftContentMinWidth={leftContentMinWidth}
        mainMinWidth={dynamicMainMinWidth}
        onSideWidthChange={setSideWidth}
        leftPanel={leftPanel}
        mainPanel={mainPanel}
      />
    </div>
  );
}
