import { useCallback, useEffect, useRef, useState } from 'react';
import Button from '../components/Button';
import Icon from '../components/Icon';
import ChatInput from '../components/ChatInput';
import catcatSvg from '../components/file-type-assets/catcat.svg';
import ChatMessage, {
  DEFAULT_CHAT_FOLLOW_UPS,
  DEFAULT_CHAT_RESULT,
  DEFAULT_CHAT_RESULT_ARTIFACTS,
  DEFAULT_CHAT_TASK_GROUPS,
  DEFAULT_CHAT_PLAN,
  DEFAULT_CHAT_THINKING,
  DEFAULT_CHAT_FORM_CONFIRM,
  useStreamingTaskGroups,
} from '../components/ChatMessage';
import CopilotArtifactDetailPanel from './shared/CopilotArtifactDetailPanel';
import { resolveArtifactDetailDemoContent } from './shared/artifact-detail-content';
import ChatArtifactSplitLayout from './shared/ChatArtifactSplitLayout';
import {
  CHAT_DEMO_PLATFORM_NAME,
  CHAT_DEMO_PROMPT_SUGGESTIONS,
  isChatHomeHandoffDemo,
  resolveChatDemoStarterContext,
} from '../page-flows/chat-conversation-demo';

/** AI 对话页顶栏与主内容区四周页面边距（与 Copilot 内容区 px-4 / pb-4 对齐） */
export const CHAT_CONVERSATION_PAGE_PADDING = 16;
/** 自动滚底锚点占位：最新消息与滚动视口底（输入框上沿）之间的留白 */
export const CHAT_LATEST_ANCHOR_GAP = 24;
/** 底部输入区：抬高 z-index + 预留 ChatInput 氛围层（-bottom-2）与阴影外扩空间 */
const CHAT_INPUT_FOOTER = 'relative z-10 w-full shrink-0 pt-2 pb-2';

/**
 * ChatConversationPattern — B 端 AI 对话页 / 平台 AI 会话详情
 *
 * 布局：
 *   - 外框：灰底会话工作区（不带左侧 NavBar），业务页面根容器不额外加整页圆角或描边
 *   - 顶导栏 shrink-0 → 中部消息区 flex-1 内滚自动滚底 → 底部 ChatInput shrink-0 吸底
 *   - 800px 居中对话流
 *
 * 状态：
 *   - 空对话 / 新建会话态 → 当消息为空时，必须展示新建会话页面（Hero + 居中 ChatInput + 推荐 prompt）
 *   - phase = 'chat'      → 有消息时展示消息流
 *   - phase = 'welcome'   → 点「新会话」后进入空对话 / 新建会话态
 *
 * 关键词路由（输入框发送文本时）：
 *   - 含「整理 / 分析 / 生成 / 梳理」 → 走完整任务规划链路：
 *       1. AI 引导文本 + 任务规划卡（待用户处理）
 *       2. 用户点「开始执行任务」→ 卡片自动收起 + 追加用户消息「开始执行任务」
 *       3. AI 流式执行流（每 600ms 推一步，用 useStreamingTaskGroups）
 *       4. 全部跑完 → 追加 AI 短答 + 产物组 + 追问按钮组
 *   - 含「停止 / 取消」 → 短答「已停止当前任务」
 *   - 其他 → 短答从备选句池随机一句
 *
 * 操作栏 historyMode：
 *   - 仅最后一条消息 historyMode=false（操作栏常显）
 *   - 其余历史消息 historyMode=true（hover 父消息时才显示，占位高度始终保留）
 */

const STREAM_INTERVAL = 600;
const AUTO_FLOW_TIMINGS = {
  thinkingDone: 900,
  confirmShow: 1400,
  /** 入口页 handoff 演示：澄清卡展示后自动确认并进入执行流 */
  demoConfirmAuto: 1000,
  confirmDone: 2400,
  flowStart: 3000,
};

const DEFAULT_CONVERSATION_CONTEXT = {
  account: '抖音电商客服账号 · after_sales_prod',
  environment: '生产环境 / AI Workflow v13',
  mock: '近 7 天售后会话样本 240 条',
  conclusion: '上一轮已完成售后政策口径梳理，并沉淀可继续追问的结论摘要。',
  artifacts: DEFAULT_CHAT_RESULT_ARTIFACTS.map((item) => item.title),
};

const DEFAULT_PLATFORM_NAME = CHAT_DEMO_PLATFORM_NAME;

function normalizeDisplayName(value, fallback) {
  if (typeof value === 'string' && value.trim()) return value.trim();
  return fallback;
}

/* ── 关键词 → 回复路由 ── */
const TASK_KEYWORDS = ['整理', '分析', '生成', '梳理', '输出', '汇总'];
const STOP_KEYWORDS = ['停止', '取消'];
const GENERIC_REPLIES = [
  '收到，已记下来。',
  '明白了，我马上处理。',
  '没问题，需要我同步给团队吗？',
  '好的，我先按这个思路推进。',
];

const FOLLOW_UP_REPLIES = {
  批量测试当前策略: {
    resultText:
      '已基于当前策略版本完成一轮模拟批测：覆盖 240 条近 7 天售后会话，重点校验退货退款、换货、拒收、运费归属 4 类路径。通过 226 条，需人工复核 14 条，风险主要集中在「签收超时但存在物流异常」和「凭证缺失但用户强诉求」两个边界场景。建议先修正升级口径，再进入 10% 灰度。',
    resultArtifacts: [
      {
        id: 'policy-batch-report',
        type: 'batch-report',
        title: '当前策略批量测试报告',
        meta: '批测 · 240 条样本',
      },
      {
        id: 'policy-risk-table',
        type: 'table',
        title: '未通过与需复核样本明细',
        meta: '表格 · 14 条高风险',
      },
    ],
    followUps: ['查看未通过样本', '生成策略修复建议', '同步到飞书文档'],
  },
  批量测试当前节点: {
    resultText:
      '已围绕当前节点做节点级模拟测试：共回放 96 条命中样本，覆盖入参缺失、类目白名单、签收时效、凭证有效性、运费判责 5 个分支。当前节点通过率 92.7%，失败样本多出现在「凭证材料为空但节点仍继续下钻」的路径，建议补一个必填校验和兜底转人工分支。',
    resultArtifacts: [
      {
        id: 'node-batch-report',
        type: 'batch-report',
        title: '当前节点回放测试报告',
        meta: '批测 · 96 条样本',
      },
      {
        id: 'node-coverage-sheet',
        type: 'table',
        title: '节点分支覆盖矩阵',
        meta: '表格 · 5 个分支',
      },
    ],
    followUps: ['定位失败分支', '补充节点断言', '生成回归测试集'],
  },
  '开启 AB 实验': {
    resultText:
      '已生成 AB 实验草案：A 组保持当前售后策略，B 组启用优化后的凭证校验和升级口径，建议先以 10% 流量灰度 24 小时。主指标看人工解决率和一次答复命中率，护栏指标看客诉率、转人工率、平均处理时长；若护栏波动超过 3%，自动暂停 B 组放量。',
    resultArtifacts: [
      {
        id: 'ab-strategy',
        type: 'strategy',
        title: '售后策略 AB 实验方案',
        meta: '策略 · 10% 灰度',
      },
      {
        id: 'ab-metric-sheet',
        type: 'table',
        title: 'AB 实验指标看板配置',
        meta: '表格 · 7 个指标',
      },
    ],
    followUps: ['生成实验配置', '补充护栏指标', '查看放量计划'],
  },
};

function nowHHmm() {
  const d = new Date();
  return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
}

function routeReply(text) {
  if (FOLLOW_UP_REPLIES[text]) {
    return { type: 'follow-up-result', ...FOLLOW_UP_REPLIES[text] };
  }
  if (STOP_KEYWORDS.some((k) => text.includes(k))) {
    return { type: 'short', text: '已停止当前任务，需要时再叫我。' };
  }
  if (TASK_KEYWORDS.some((k) => text.includes(k))) {
    return { type: 'task' };
  }
  return { type: 'short', text: GENERIC_REPLIES[Math.floor(Math.random() * GENERIC_REPLIES.length)] };
}

function buildThinkingContent(text, context) {
  const artifacts = context.artifacts?.length ? context.artifacts.join('、') : '暂无历史产物';
  return [
    `用户本轮输入：${text}`,
    `系统自动带入上一轮账号：${context.account}`,
    `系统自动带入上一轮环境：${context.environment}`,
    `系统自动带入上一轮 Mock：${context.mock}`,
    `系统自动带入上一轮结论：${context.conclusion || '暂无历史结论'}`,
    `系统自动带入上一轮产物：${artifacts}`,
    '基于上述连续会话上下文，先确认关键执行口径，再进入自动执行流。',
  ].join('\n');
}

function buildClarificationConfirms(context) {
  return DEFAULT_CHAT_FORM_CONFIRM.map((confirm) => ({
    ...confirm,
    id: 'auto-clarify-confirm',
    title: '人工澄清确认',
    primaryActionLabel: '确认并继续',
    secondaryActionLabel: '',
    formItems: [
      {
        id: 'scene',
        label: '业务场景',
        type: 'select',
        placeholder: '请选择业务场景',
        defaultValue: 'after-sales-policy',
        options: [
          { value: 'after-sales-policy', label: '售后政策整理' },
          { value: 'conversation-analysis', label: '会话场景分析' },
          { value: 'ab-experiment', label: 'AB 实验方案' },
        ],
        fullWidth: true,
      },
      {
        id: 'channel',
        label: '处理渠道',
        type: 'select',
        placeholder: '请选择处理渠道',
        defaultValue: 'current-context',
        options: [
          {
            value: 'current-context',
            label: `使用当前账号、${context.environment} 与 ${context.mock}`,
          },
          { value: 'refresh-mock', label: '刷新 Mock 数据后再执行' },
          { value: 'manual-review', label: '先转人工复核上下文再执行' },
        ],
        fullWidth: true,
      },
      {
        id: 'remark',
        label: '补充说明',
        type: 'input',
        placeholder: '请输入补充说明（可选）',
        fullWidth: true,
      },
    ],
  }));
}

function buildAutoFlowPayload(text, reply, context) {
  if (reply.type === 'follow-up-result') {
    return {
      resultText: `${reply.resultText}\n\n本轮追问已自动继承上一轮账号、环境、Mock、结论和产物上下文，未新开会话。`,
      resultArtifacts: reply.resultArtifacts,
      followUps: reply.followUps,
    };
  }

  if (reply.type === 'task') {
    return {
      resultText: DEFAULT_CHAT_RESULT,
      resultArtifacts: DEFAULT_CHAT_RESULT_ARTIFACTS,
      followUps: DEFAULT_CHAT_FOLLOW_UPS,
    };
  }

  return {
    resultText:
      `${reply.text} 已结合当前会话上下文继续处理「${text}」：账号为 ${context.account}，环境为 ${context.environment}，Mock 使用 ${context.mock}。如需深入推进，可继续选择下方追问或直接在底部输入补充要求。`,
    resultArtifacts: DEFAULT_CHAT_RESULT_ARTIFACTS.slice(0, 2),
    followUps: ['继续细化结论', '补充一轮 Mock 验证', '生成可交付文档'],
  };
}

function getFlowTotalMs() {
  const totalSteps = DEFAULT_CHAT_TASK_GROUPS.reduce(
    (sum, group) => sum + (group.steps?.length ?? 0),
    0,
  );
  return (totalSteps + 1) * STREAM_INTERVAL;
}

function hasActionablePrimaryButton(label) {
  return typeof label === 'string' && label.trim().length > 0;
}

function confirmsRequireHumanGate(confirms) {
  return Array.isArray(confirms) && confirms.some((confirm) => hasActionablePrimaryButton(confirm?.primaryActionLabel));
}

/* ── 单条消息 → ChatMessage props 映射 ──
 *  isLatest：是否为列表中最后一条（决定 historyMode 是否启用）
 *  handlers：所有交互回调集合
 *    · onPlanConfirm(msgId) — 任务规划卡「开始执行任务」点击
 *    · onPlanCancel(msgId)  — 任务规划卡「取消」点击
 *    · onFollowUpSelect(text) — 追问 chip 点击（默认作为新一轮对话发出）
 */
function messageToChatProps(msg, isLatest, handlers = {}) {
  const {
    onPlanConfirm,
    onPlanCancel,
    onFollowUpSelect,
    onConfirmPrimary,
    onConfirmSecondary,
    onConfirmOptionChange,
    onConfirmFormChange,
    onArtifactSelect,
    selectedArtifactId,
    assistantName,
  } = handlers;
  const aiHeader = { name: normalizeDisplayName(assistantName, DEFAULT_PLATFORM_NAME) };
  const artifactPanelProps = {
    onArtifactSelect,
    selectedArtifactId,
  };
  const baseProps = {
    className: msg.className || '',
    ...artifactPanelProps,
  };
  const baseActions = {
    showCopy: true,
    showQuote: true,
    showLike: true,
    showDislike: true,
    historyMode: !isLatest,
  };

  /* 把字符串数组包装成 { items, onSelect } 形式，让点击 chip 直接发起会话 */
  const wrapFollowUps = (raw) => {
    if (!raw) return null;
    const items = Array.isArray(raw) ? raw : raw.items;
    if (!Array.isArray(items) || items.length === 0) return null;
    return {
      items,
      onSelect: (item) => {
        const label = typeof item === 'string' ? item : item?.label;
        if (label && onFollowUpSelect) onFollowUpSelect(label);
      },
    };
  };

  if (msg.kind === 'user') {
    return {
      ...baseProps,
      role: 'user',
      timestamp: msg.timestamp,
      userContent: msg.userContent,
      userAttachments: msg.userAttachments,
      actions: baseActions,
    };
  }

  if (msg.kind === 'ai-text') {
    return {
      ...baseProps,
      role: 'ai',
      header: aiHeader,
      title: '',
      steps: null,
      resultText: msg.resultText,
      timestamp: msg.timestamp,
      actions: baseActions,
    };
  }

  if (msg.kind === 'ai-thinking') {
    return {
      ...baseProps,
      role: 'ai',
      header: aiHeader,
      title: '',
      steps: null,
      thinking: msg.thinking,
      resultText: msg.resultText,
      timestamp: msg.timestamp,
      actions: msg.thinking?.state === 'thinking' ? null : baseActions,
    };
  }

  if (msg.kind === 'ai-confirm') {
    return {
      ...baseProps,
      role: 'ai',
      header: aiHeader,
      title: '',
      steps: null,
      leadText: msg.leadText,
      confirms: Array.isArray(msg.confirms)
        ? msg.confirms.map((confirm) => ({
            ...confirm,
            onOptionChange: (value, option) => onConfirmOptionChange && onConfirmOptionChange(
              msg.id,
              confirm.id,
              value,
              option,
            ),
            onFormChange: (formValues, meta) => onConfirmFormChange && onConfirmFormChange(
              msg.id,
              confirm.id,
              formValues,
              meta,
            ),
            onPrimaryAction: (payload) => onConfirmPrimary && onConfirmPrimary(msg.id, confirm.id, {
              ...payload,
              primaryActionLabel: confirm.primaryActionLabel,
            }),
            onSecondaryAction: () => onConfirmSecondary && onConfirmSecondary(msg.id, confirm.id),
          }))
        : msg.confirms,
      timestamp: msg.timestamp,
      actions: baseActions,
    };
  }

  if (msg.kind === 'ai-task-plan') {
    /* 任务规划卡：未操作时操作栏自动隐藏（ChatMessage 内部 showMessageActions 兜底），
     * 处理后变为历史消息按 historyMode 走；defaultConfirmed=true 让历史卡片直接收起
     * 可选叠加 thinking，让深度思考 + 引导文本 + 卡片合并到同一条 AI 消息中 */
    return {
      ...baseProps,
      role: 'ai',
      header: aiHeader,
      title: '',
      steps: null,
      thinking: msg.thinking || null,
      leadText: msg.leadText,
      plan: {
        ...DEFAULT_CHAT_PLAN,
        defaultConfirmed: msg.planConfirmed === true,
        onPrimaryAction: () => onPlanConfirm && onPlanConfirm(msg.id),
        onSecondaryAction: () => onPlanCancel && onPlanCancel(msg.id),
      },
      timestamp: msg.timestamp,
      actions: baseActions,
    };
  }

  if (msg.kind === 'ai-flow') {
    /* 完整执行流消息：执行流 + 总结文本 + 产物组 + 追问 都可以同时存在；
     * 流式播放期间（stream=true）整条消息不显示操作栏，播完后转为最新消息显示 */
    return {
      ...baseProps,
      role: 'ai',
      header: aiHeader,
      title: '',
      steps: null,
      taskGroups: msg.taskGroups,
      resultText: msg.resultText,
      resultArtifacts: msg.resultArtifacts,
      followUps: wrapFollowUps(msg.followUps),
      taskBadge: msg.taskBadge,
      timestamp: msg.timestamp,
      actions: msg.stream === true ? null : baseActions,
    };
  }

  /* ai-result：兼容旧消息形态（短答文本 + 产物组 + 追问，独立成一条） */
  return {
    ...baseProps,
    role: 'ai',
    header: aiHeader,
    title: '',
    steps: null,
    resultText: msg.resultText,
    resultArtifacts: msg.resultArtifacts,
    followUps: wrapFollowUps(msg.followUps),
    taskBadge: msg.taskBadge,
    timestamp: msg.timestamp,
    actions: baseActions,
  };
}

export default function ChatConversationPattern({
  title = '抖音电商客服售后政策梳理',
  platformName = DEFAULT_PLATFORM_NAME,
  assistantName,
  initialMessages,
  /** 从 ChatHomePagePattern handoff：挂载后自动发送首条用户消息 */
  bootstrapSendSignal = null,
  /** 从入口页进入时，顶栏展示返回一级页 */
  onBackToHome = null,
}) {
  const resolvedPlatformName = normalizeDisplayName(platformName, DEFAULT_PLATFORM_NAME);
  const resolvedAssistantName = normalizeDisplayName(assistantName, resolvedPlatformName);
  const resolvedInitialMessagesRef = useRef(
    Array.isArray(initialMessages) ? initialMessages : [],
  );
  const hasBootstrapSend = Boolean(
    bootstrapSendSignal?.seed && String(bootstrapSendSignal?.text || '').trim(),
  );
  const [messages, setMessages] = useState(() => resolvedInitialMessagesRef.current);
  const [phase, setPhase] = useState(() => {
    if (resolvedInitialMessagesRef.current.length > 0) return 'chat';
    if (hasBootstrapSend) return 'chat';
    return 'welcome';
  }); // 'chat' | 'welcome'
  const bootstrapHandledSeedRef = useRef(null);
  const [selectedArtifact, setSelectedArtifact] = useState(null);
  const artifactDetailOpen = Boolean(selectedArtifact);
  /* ── ChatInput 受控状态机 ──
   * inputView：'default' | 'replying' | 'busy'
   *   · default  → 静止 / 失焦 / AI 完成回复（含 replying & busy 完成）
   *   · replying → AI 短答中（短文本回复 600ms）
   *   · busy     → AI 任务执行中（流式跑 taskGroups）
   * prefillText / prefillSeed：欢迎页推荐 chip 点击时把文本回填到输入框，等用户主动点发送 */
  const [inputView, setInputView] = useState('default');
  const [prefillText, setPrefillText] = useState('');
  const [prefillSeed, setPrefillSeed] = useState(0);
  const replyTimerRef = useRef(null);
  const busyTimerRef = useRef(null);
  const flowTimersRef = useRef([]);
  const pendingFlowResumeRef = useRef(null);
  const scrollRef = useRef(null);
  const latestAnchorRef = useRef(null);
  const conversationContextRef = useRef({ ...DEFAULT_CONVERSATION_CONTEXT });
  const idSeedRef = useRef(100);
  const nextId = useCallback((prefix = 'm') => `${prefix}${++idSeedRef.current}`, []);

  const scrollToLatest = useCallback((behavior = 'smooth') => {
    if (latestAnchorRef.current?.scrollIntoView) {
      latestAnchorRef.current.scrollIntoView({ block: 'end', behavior });
      return;
    }
    const el = scrollRef.current;
    if (el) el.scrollTo({ top: el.scrollHeight, behavior });
  }, []);

  const scheduleScrollToLatest = useCallback((behavior = 'smooth') => {
    if (typeof window === 'undefined') return;
    window.requestAnimationFrame(() => scrollToLatest(behavior));
  }, [scrollToLatest]);

  const clearFlowTimers = useCallback(() => {
    flowTimersRef.current.forEach((timer) => clearTimeout(timer));
    flowTimersRef.current = [];
  }, []);

  const clearPendingFlowResume = useCallback(() => {
    pendingFlowResumeRef.current = null;
  }, []);

  const pauseForHumanGate = useCallback((resume) => {
    pendingFlowResumeRef.current = resume;
    setInputView('default');
    scheduleScrollToLatest();
  }, [scheduleScrollToLatest]);

  const scheduleFlowTimer = useCallback((callback, delay) => {
    const timer = setTimeout(() => {
      flowTimersRef.current = flowTimersRef.current.filter((item) => item !== timer);
      callback();
      scheduleScrollToLatest();
    }, delay);
    flowTimersRef.current.push(timer);
    return timer;
  }, [scheduleScrollToLatest]);

  const resumePendingFlow = useCallback((delay = 240) => {
    const resume = pendingFlowResumeRef.current;
    pendingFlowResumeRef.current = null;
    if (typeof resume !== 'function') return;
    scheduleFlowTimer(() => {
      resume();
    }, delay);
  }, [scheduleFlowTimer]);

  /* chip 回填到输入框（覆盖当前草稿 + 自动聚焦） */
  const prefillInput = useCallback((text) => {
    setPrefillText(text || '');
    setPrefillSeed((s) => s + 1);
  }, []);

  /* 卸载时清掉计时器，避免内存泄漏 / setState on unmounted */
  useEffect(
    () => () => {
      if (replyTimerRef.current) clearTimeout(replyTimerRef.current);
      if (busyTimerRef.current) clearTimeout(busyTimerRef.current);
      clearFlowTimers();
      clearPendingFlowResume();
    },
    [clearFlowTimers, clearPendingFlowResume],
  );

  /* 自动滚到底：用 ResizeObserver 监听消息容器内容高度变化
   * 覆盖所有场景：新消息追加、流式 step 逐步推出、ChatMessage 内部折叠展开 */
  useEffect(() => {
    if (phase !== 'chat' || messages.length === 0) return undefined;
    const el = scrollRef.current;
    if (!el) return undefined;
    const inner = el.firstElementChild;
    if (!inner) return undefined;
    const ro = new ResizeObserver(() => {
      scrollToLatest();
    });
    ro.observe(inner);
    return () => ro.disconnect();
  }, [phase, messages.length, scrollToLatest]);

  /* 重置回欢迎状态 */
  const handleNewSession = useCallback(() => {
    setMessages([]);
    setPhase('welcome');
    setPrefillText('');
    setPrefillSeed((s) => s + 1);
  }, []);

  /* ── 取消链路：任务规划卡次按钮 ──
   * 在自动编排有人为闸门等待时，取消会清空 pending resume 并暂停后续链路。 */
  const handlePlanCancel = useCallback(
    (planMsgId) => {
      clearPendingFlowResume();
      setMessages((prev) =>
        prev.map((m) => (m.id === planMsgId ? { ...m, planConfirmed: true } : m)),
      );
      setMessages((prev) => [
        ...prev,
        {
          id: nextId('u'),
          kind: 'user',
          timestamp: nowHHmm(),
          userContent: [{ type: 'text', value: '取消' }],
        },
      ]);
      setInputView('replying');
      if (replyTimerRef.current) clearTimeout(replyTimerRef.current);
      replyTimerRef.current = setTimeout(() => {
        setMessages((prev) => [
          ...prev,
          {
            id: nextId('a'),
            kind: 'ai-text',
            timestamp: nowHHmm(),
            resultText: '好的，已取消任务。还有其他问题随时可以找我～',
          },
        ]);
        setInputView('default');
      }, 600);
    },
    [nextId, clearPendingFlowResume],
  );

  const handleConfirmPrimary = useCallback(
    (messageId, confirmId, payload = {}) => {
      const isActionable = hasActionablePrimaryButton(payload.primaryActionLabel);
      const selectedLabel = payload.option?.label ?? '';
      setMessages((prev) =>
        prev.map((message) =>
          message.id === messageId
            ? {
                ...message,
                confirms: Array.isArray(message.confirms)
                  ? message.confirms.map((confirm) => (
                    confirm.id === confirmId
                      ? {
                          ...confirm,
                          defaultConfirmed: true,
                          selectedValue: payload.value ?? confirm.selectedValue,
                          defaultSelectedValue: payload.value ?? confirm.defaultSelectedValue,
                          selectedLabel: selectedLabel || confirm.selectedLabel,
                          formValues: payload.formValues ?? confirm.formValues,
                        }
                      : confirm
                  ))
                  : message.confirms,
                leadText: isActionable ? '已确认，继续执行后续流程。' : message.leadText,
                timestamp: nowHHmm(),
              }
            : message,
        ),
      );
      if (isActionable) resumePendingFlow();
    },
    [resumePendingFlow],
  );

  const handleConfirmOptionChange = useCallback((messageId, confirmId, value, option) => {
    setMessages((prev) =>
      prev.map((message) =>
        message.id === messageId
          ? {
              ...message,
              confirms: Array.isArray(message.confirms)
                ? message.confirms.map((confirm) => (
                  confirm.id === confirmId
                    ? {
                        ...confirm,
                        selectedValue: value,
                        defaultSelectedValue: value,
                        selectedLabel: option?.label ?? confirm.selectedLabel,
                      }
                    : confirm
                ))
                : message.confirms,
            }
          : message,
      ),
    );
  }, []);

  const handleConfirmFormChange = useCallback((messageId, confirmId, formValues, meta = {}) => {
    setMessages((prev) =>
      prev.map((message) =>
        message.id === messageId
          ? {
              ...message,
              confirms: Array.isArray(message.confirms)
                ? message.confirms.map((confirm) => (
                  confirm.id === confirmId
                    ? {
                        ...confirm,
                        formValues,
                        lastChangedFieldId: meta.fieldId,
                      }
                    : confirm
                ))
                : message.confirms,
            }
          : message,
      ),
    );
  }, []);

  const handleConfirmSecondary = useCallback(
    (messageId, confirmId) => {
      setMessages((prev) =>
        prev.map((message) =>
          message.id === messageId
            ? {
                ...message,
                confirms: Array.isArray(message.confirms)
                  ? message.confirms.map((confirm) => (
                    confirm.id === confirmId ? { ...confirm, defaultConfirmed: true } : confirm
                  ))
                  : message.confirms,
                leadText: '已取消当前确认，流程暂停。',
                timestamp: nowHHmm(),
              }
            : message,
        ),
      );
      clearPendingFlowResume();
      setInputView('default');
    },
    [clearPendingFlowResume],
  );

  /* ── 任务规划卡主按钮：有人为闸门时用于恢复自动链路；无闸门时保留旧兜底链路 ── */
  const handlePlanConfirm = useCallback(
    (planMsgId) => {
      const hasPendingResume = typeof pendingFlowResumeRef.current === 'function';
      /* 1) 标记该任务规划卡为「已确认」（持久化到消息数据，重渲染时保持收起态） */
      setMessages((prev) =>
        prev.map((m) => (m.id === planMsgId ? { ...m, planConfirmed: true } : m)),
      );

      if (hasPendingResume) {
        setMessages((prev) => [
          ...prev,
          {
            id: nextId('u'),
            kind: 'user',
            timestamp: nowHHmm(),
            userContent: [{ type: 'text', value: '开始执行任务' }],
          },
        ]);
        resumePendingFlow();
        return;
      }

      const flowMsgId = nextId('flow');

      /* 2) 立即追加：用户消息「开始执行任务」+ AI 流式执行流（带 stream 标记，由 StreamingChatMessage 接管） */
      setMessages((prev) => [
        ...prev,
        {
          id: nextId('u'),
          kind: 'user',
          timestamp: nowHHmm(),
          userContent: [{ type: 'text', value: '开始执行任务' }],
        },
        {
          id: flowMsgId,
          kind: 'ai-flow',
          timestamp: nowHHmm(),
          taskGroups: DEFAULT_CHAT_TASK_GROUPS,
          stream: true,
        },
      ]);

      /* 3) ChatInput 进 busy（任务执行态，编辑器锁定，仅显示状态文案 + 停止按钮） */
      setInputView('busy');

      /* 4) 估算流式总时长：(总 step 数 + 1 buffer) × 600ms，跑完后追加结果消息 + 输入框回 default */
      const totalSteps = DEFAULT_CHAT_TASK_GROUPS.reduce(
        (sum, g) => sum + (g.steps?.length ?? 0),
        0,
      );
      const totalMs = (totalSteps + 1) * STREAM_INTERVAL;
      if (busyTimerRef.current) clearTimeout(busyTimerRef.current);
      busyTimerRef.current = setTimeout(() => {
        /* 流式播完 → 更新同一条消息：取消 stream 标记 + 所有组转 completed +
         * 补上总结文本 + 产物组 + 追问。整条消息此时变成「完整 AI 回复」一条到底 */
        setMessages((prev) =>
          prev.map((m) =>
            m.id === flowMsgId
              ? {
                  ...m,
                  stream: false,
                  taskGroups: DEFAULT_CHAT_TASK_GROUPS.map((g) => ({
                    ...g,
                    status: 'completed',
                  })),
                  resultText: DEFAULT_CHAT_RESULT,
                  resultArtifacts: DEFAULT_CHAT_RESULT_ARTIFACTS,
                  followUps: DEFAULT_CHAT_FOLLOW_UPS,
                  timestamp: nowHHmm(),
                }
              : m,
          ),
        );
        setInputView('default');
      }, totalMs);
    },
    [nextId, resumePendingFlow],
  );

  const startAutoOrchestratedFlow = useCallback(
    (text, reply, ctx = {}) => {
      const autoPlayDemo = isChatHomeHandoffDemo(ctx);
      const contextSnapshot = { ...conversationContextRef.current };
      const thinkingMsgId = nextId('think');
      const confirmMsgId = nextId('confirm');
      const flowMsgId = nextId('flow');
      const payload = buildAutoFlowPayload(text, reply, contextSnapshot);
      const clarificationConfirms = buildClarificationConfirms(contextSnapshot);

      const finishExecutionFlow = () => {
        setMessages((prev) =>
          prev.map((message) =>
            message.id === flowMsgId
              ? {
                  ...message,
                  stream: false,
                  taskGroups: DEFAULT_CHAT_TASK_GROUPS.map((group) => ({
                    ...group,
                    status: 'completed',
                  })),
                  resultText: payload.resultText,
                  resultArtifacts: payload.resultArtifacts,
                  followUps: payload.followUps,
                  timestamp: nowHHmm(),
                }
              : message,
          ),
        );
        conversationContextRef.current = {
          ...contextSnapshot,
          conclusion: payload.resultText,
          artifacts: payload.resultArtifacts.map((artifact) => artifact.title),
          lastUserIntent: text,
        };
        setInputView('default');
      };

      const startExecutionFlow = () => {
        setInputView('busy');
        setMessages((prev) => [
          ...prev,
          {
            id: flowMsgId,
            kind: 'ai-flow',
            timestamp: nowHHmm(),
            taskGroups: DEFAULT_CHAT_TASK_GROUPS,
            stream: true,
          },
        ]);
        scheduleFlowTimer(finishExecutionFlow, getFlowTotalMs());
      };

      const continueAfterConfirm = () => {
        setMessages((prev) =>
          prev.map((message) =>
            message.id === confirmMsgId
              ? {
                  ...message,
                  confirms: Array.isArray(message.confirms)
                    ? message.confirms.map((confirm) => ({
                        ...confirm,
                        defaultConfirmed: true,
                      }))
                    : message.confirms,
                  leadText: '已确认澄清信息，开始自动执行。',
                  timestamp: nowHHmm(),
                }
              : message,
          ),
        );
        scheduleFlowTimer(startExecutionFlow, 320);
      };

      setMessages((prev) => [
        ...prev,
        {
          id: thinkingMsgId,
          kind: 'ai-thinking',
          timestamp: nowHHmm(),
          thinking: {
            ...DEFAULT_CHAT_THINKING,
            state: 'thinking',
            inProgressLabel: '深度思考中 ...',
            defaultExpanded: false,
          },
        },
      ]);

      scheduleFlowTimer(() => {
        setMessages((prev) =>
          prev.map((message) =>
            message.id === thinkingMsgId
              ? {
                  ...message,
                  thinking: {
                    ...DEFAULT_CHAT_THINKING,
                    state: 'completed',
                    durationLabel: '深度思考（用时 3.20 秒）',
                    content: buildThinkingContent(text, contextSnapshot),
                    defaultExpanded: false,
                  },
                  resultText: '已完成上下文理解，接下来需要先确认本轮执行口径。',
                  timestamp: nowHHmm(),
                }
              : message,
          ),
        );
      }, AUTO_FLOW_TIMINGS.thinkingDone);

      scheduleFlowTimer(() => {
        setMessages((prev) => [
          ...prev,
          {
            id: confirmMsgId,
            kind: 'ai-confirm',
            timestamp: nowHHmm(),
            leadText: '请确认以下人工澄清信息；若卡片右下角存在可操作主按钮，则点击后再继续执行。',
            confirms: clarificationConfirms,
          },
        ]);

        if (autoPlayDemo) {
          scheduleFlowTimer(continueAfterConfirm, AUTO_FLOW_TIMINGS.demoConfirmAuto);
          return;
        }

        if (confirmsRequireHumanGate(clarificationConfirms)) {
          pauseForHumanGate(continueAfterConfirm);
          return;
        }

        scheduleFlowTimer(continueAfterConfirm, 320);
      }, AUTO_FLOW_TIMINGS.confirmShow);
    },
    [nextId, pauseForHumanGate, scheduleFlowTimer],
  );

  /* ── 用户输入框发送 ──
   * 状态机：default → replying → AI 深度思考 → 人工澄清确认 → busy 执行流 → 结论/产物/追问 → default
   * 底部输入与 followUps 点击都进入同一条会话流，并自动继承上一轮上下文。 */
  const handleSend = useCallback(
    (text, ctx = {}) => {
      const trimmed = (text || '').trim();
      if (!trimmed) return;

      conversationContextRef.current = resolveChatDemoStarterContext(
        trimmed,
        DEFAULT_CONVERSATION_CONTEXT,
      );

      /* 欢迎屏发送第一条消息 → 切到 chat 阶段 */
      if (phase === 'welcome') setPhase('chat');

      const userTime = nowHHmm();

      /* 优先用 ctx.segments 还原 chip + 文本混排结构（保留 entity tag 渲染）
       * 若拿不到 segments（旧调用方）则回退到一段纯 text token */
      const userContent = Array.isArray(ctx.segments) && ctx.segments.length > 0
        ? ctx.segments
        : [{ type: 'text', value: trimmed }];

      /* 1) 立即追加用户气泡 + ChatInput 进 replying（短答态，编辑器锁定） */
      setMessages((prev) => [
        ...prev,
        {
          id: nextId('u'),
          kind: 'user',
          timestamp: userTime,
          userContent,
        },
      ]);
      setInputView('replying');
      scheduleScrollToLatest();

      const reply = routeReply(trimmed);
      if (replyTimerRef.current) clearTimeout(replyTimerRef.current);
      if (busyTimerRef.current) clearTimeout(busyTimerRef.current);
      clearFlowTimers();
      clearPendingFlowResume();

      if (reply.type === 'short' && STOP_KEYWORDS.some((keyword) => trimmed.includes(keyword))) {
        replyTimerRef.current = setTimeout(() => {
          setMessages((prev) => [
            ...prev,
            {
              id: nextId('a'),
              kind: 'ai-text',
              timestamp: nowHHmm(),
              resultText: reply.text,
            },
          ]);
          setInputView('default');
          scheduleScrollToLatest();
        }, 600);
        return;
      }

      startAutoOrchestratedFlow(trimmed, reply, ctx);
    },
    [phase, nextId, clearFlowTimers, clearPendingFlowResume, scheduleScrollToLatest, startAutoOrchestratedFlow],
  );

  const handleFollowUpSend = useCallback(
    (text) => {
      handleSend(text, { source: 'follow-up' });
    },
    [handleSend],
  );

  /* 入口页 handoff：首条消息与用户在对话页点发送走同一 handleSend 链路
   * 使用 useEffect + setTimeout(0)，避免 Strict Mode 首遍挂载卸载时清掉编排定时器后不再重触发 */
  useEffect(() => {
    if (!bootstrapSendSignal?.seed) return undefined;
    const text = String(bootstrapSendSignal.text || '').trim();
    if (!text) return undefined;

    const timer = window.setTimeout(() => {
      if (bootstrapHandledSeedRef.current === bootstrapSendSignal.seed) return;
      bootstrapHandledSeedRef.current = bootstrapSendSignal.seed;
      handleSend(text, {
        source: bootstrapSendSignal.source || 'chat-home-handoff',
        segments: bootstrapSendSignal.segments,
        attachments: bootstrapSendSignal.attachments,
      });
    }, 0);

    return () => window.clearTimeout(timer);
  }, [bootstrapSendSignal, handleSend]);

  /* ── 用户主动停止：replying / busy 期间点 ChatInput 的停止按钮 ──
   * 立即清掉计时器 + 输入框回 default + 追加 AI 短答提示 */
  const handleStop = useCallback(() => {
    if (replyTimerRef.current) {
      clearTimeout(replyTimerRef.current);
      replyTimerRef.current = null;
    }
    if (busyTimerRef.current) {
      clearTimeout(busyTimerRef.current);
      busyTimerRef.current = null;
    }
    clearFlowTimers();
    clearPendingFlowResume();
    setInputView('default');
    setMessages((prev) => [
      ...prev,
      {
        id: nextId('a'),
        kind: 'ai-text',
        timestamp: nowHHmm(),
        resultText: '已停止当前任务，需要时再叫我。',
      },
    ]);
  }, [nextId, clearFlowTimers, clearPendingFlowResume]);

  /* 新会话：清空消息 + 切欢迎屏 + 重置输入框状态 */
  const handleArtifactSelect = useCallback((artifact) => {
    if (!artifact?.id) return;
    setSelectedArtifact((prev) => (prev?.id === artifact.id ? null : artifact));
  }, []);

  const handleArtifactDetailClose = useCallback(() => {
    setSelectedArtifact(null);
  }, []);

  const handleNewSessionFull = useCallback(() => {
    if (replyTimerRef.current) clearTimeout(replyTimerRef.current);
    if (busyTimerRef.current) clearTimeout(busyTimerRef.current);
    clearFlowTimers();
    clearPendingFlowResume();
    conversationContextRef.current = { ...DEFAULT_CONVERSATION_CONTEXT };
    setInputView('default');
    setSelectedArtifact(null);
    handleNewSession();
  }, [handleNewSession, clearFlowTimers, clearPendingFlowResume]);

  const lastIdx = messages.length - 1;
  const isNewConversationState = phase === 'welcome' || messages.length === 0;
  const showSessionHeader = messages.length > 0;

  return (
    <div
      className="tfds-page-chat-conversation flex h-full w-full min-h-0 flex-col overflow-hidden"
      style={{
        flex: '1 0 auto',
        alignSelf: 'stretch',
        background: 'var(--color-blueGrey-200, #F2F4F7)',
        borderRadius: 'inherit',
        border: '1px solid var(--color-border-default, #E4E7EC)',
      }}
    >
      <TopBar
        title={title}
        platformName={resolvedPlatformName}
        onNewSession={handleNewSessionFull}
        disableNewSession={isNewConversationState}
        onBackToHome={onBackToHome}
        showSessionHeader={showSessionHeader}
      />

      <div
        className="flex min-h-0 flex-1 flex-col"
        style={{
          paddingLeft: CHAT_CONVERSATION_PAGE_PADDING,
          paddingRight: CHAT_CONVERSATION_PAGE_PADDING,
          paddingBottom: CHAT_CONVERSATION_PAGE_PADDING,
        }}
      >
      {isNewConversationState ? (
        <NewConversationPhase
          onSend={handleSend}
          onStop={handleStop}
          onPrefill={prefillInput}
          inputView={inputView}
          prefillText={prefillText}
          prefillSeed={prefillSeed}
          assistantName={resolvedAssistantName}
        />
      ) : artifactDetailOpen ? (
        <ChatArtifactSplitLayout
          conversation={(
            <ChatPhase
              scrollRef={scrollRef}
              latestAnchorRef={latestAnchorRef}
              messages={messages}
              lastIdx={lastIdx}
              splitLayout
              handlers={{
                onPlanConfirm: handlePlanConfirm,
                onPlanCancel: handlePlanCancel,
                onConfirmPrimary: handleConfirmPrimary,
                onConfirmSecondary: handleConfirmSecondary,
                onConfirmOptionChange: handleConfirmOptionChange,
                onConfirmFormChange: handleConfirmFormChange,
                onFollowUpSelect: handleFollowUpSend,
                onArtifactSelect: handleArtifactSelect,
                selectedArtifactId: selectedArtifact?.id ?? null,
                assistantName: resolvedAssistantName,
              }}
              onSend={handleSend}
              onStop={handleStop}
              inputView={inputView}
              prefillText={prefillText}
              prefillSeed={prefillSeed}
            />
          )}
          artifactPanel={(
            <CopilotArtifactDetailPanel
              artifact={selectedArtifact}
              onClose={handleArtifactDetailClose}
            >
              {resolveArtifactDetailDemoContent(selectedArtifact)}
            </CopilotArtifactDetailPanel>
          )}
        />
      ) : (
        <ChatPhase
          scrollRef={scrollRef}
          latestAnchorRef={latestAnchorRef}
          messages={messages}
          lastIdx={lastIdx}
          handlers={{
            onPlanConfirm: handlePlanConfirm,
            onPlanCancel: handlePlanCancel,
            onConfirmPrimary: handleConfirmPrimary,
            onConfirmSecondary: handleConfirmSecondary,
            onConfirmOptionChange: handleConfirmOptionChange,
            onConfirmFormChange: handleConfirmFormChange,
            onFollowUpSelect: handleFollowUpSend,
            onArtifactSelect: handleArtifactSelect,
            selectedArtifactId: selectedArtifact?.id ?? null,
            assistantName: resolvedAssistantName,
          }}
          onSend={handleSend}
          onStop={handleStop}
          inputView={inputView}
          prefillText={prefillText}
          prefillSeed={prefillSeed}
        />
      )}
      </div>
    </div>
  );
}

/* ============================================================
 * ChatPhase — 对话阶段：消息流 + 底部 ChatInput
 * ============================================================ */
function ChatPhase({
  scrollRef,
  latestAnchorRef,
  messages,
  lastIdx,
  handlers,
  onSend,
  onStop,
  inputView,
  prefillText,
  prefillSeed,
  splitLayout = false,
}) {
  const contentWidth = splitLayout ? '100%' : '800px';
  /* 分栏态与 Copilot 助手模版对齐：渐隐 32px、消息区底 72px、输入区仅 pt-2（底边距由页面内容区 16px 承担） */
  const fadeInset = splitLayout ? 32 : 40;
  const messageBottomPad = splitLayout ? '72px' : '80px';
  /* 上下渐隐遮罩：让消息进入 / 离开滚动视口时柔和过渡，不硬切
   * 顶部渐隐仅在「可向上滚」时启用，回到第一条时取消，避免首条被遮淡 */
  const [atTop, setAtTop] = useState(true);
  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return undefined;
    const update = () => setAtTop(el.scrollTop <= 4);
    update();
    el.addEventListener('scroll', update, { passive: true });
    return () => el.removeEventListener('scroll', update);
  }, [scrollRef, messages.length]);
  const FADE_MASK = atTop
    ? `linear-gradient(to bottom, #000 0, #000 calc(100% - ${fadeInset}px), transparent 100%)`
    : `linear-gradient(to bottom, transparent 0, #000 ${fadeInset}px, #000 calc(100% - ${fadeInset}px), transparent 100%)`;
  return (
    <div className="flex min-h-0 w-full min-w-0 flex-1 flex-col">
      {/* 消息区：自适应剩余高度 + 内滚 + 上下渐隐（z-0，低于底部输入区） */}
      <div
        ref={scrollRef}
        className="relative z-0 w-full min-h-0 flex-1 overflow-x-hidden overflow-y-auto"
        style={{
          padding: '8px 0',
          maskImage: FADE_MASK,
          WebkitMaskImage: FADE_MASK,
        }}
      >
        <div
          className="mx-auto flex w-full flex-col gap-2"
          /* 底部 80px padding：让最后一条会话卡片再上移 20px，避免被底部渐隐遮挡 */
          style={{
            width: contentWidth,
            maxWidth: '100%',
            padding: `0 0 ${messageBottomPad}`,
          }}
        >
          {messages.map((m, idx) => {
            const isLatest = idx === lastIdx;
            const props = messageToChatProps(m, isLatest, handlers);
            /* 流式执行流走子组件：挂载即按 600ms 节奏推 step；流式期间不传操作栏 */
            if (m.kind === 'ai-flow' && m.stream === true) {
              return <StreamingChatMessage key={m.id} {...props} taskGroups={m.taskGroups} />;
            }
            return <ChatMessage key={m.id} {...props} />;
          })}
          <div
            ref={latestAnchorRef}
            aria-hidden="true"
            style={{ height: CHAT_LATEST_ANCHOR_GAP, flexShrink: 0 }}
          />
        </div>
      </div>

      {/* 底部 ChatInput：抬高层级 + pb-2 承接氛围层外扩；页面内容区 pb-4 承担四周边距 */}
      <div className={CHAT_INPUT_FOOTER}>
        {splitLayout ? (
          <ChatInput
            variant={inputView}
            autoReplyOnSend={false}
            prefillText={prefillText}
            prefillSeed={prefillSeed}
            placeholder="继续对话，或输入「整理 / 分析 / 生成」触发任务流..."
            onSend={onSend}
            onStop={onStop}
          />
        ) : (
          <div
            className="mx-auto w-full"
            style={{ width: contentWidth, maxWidth: '100%' }}
          >
            <ChatInput
              variant={inputView}
              autoReplyOnSend={false}
              prefillText={prefillText}
              prefillSeed={prefillSeed}
              placeholder="继续对话，或输入「整理 / 分析 / 生成」触发任务流..."
              onSend={onSend}
              onStop={onStop}
            />
          </div>
        )}
      </div>
    </div>
  );
}

/* ============================================================
 * NewConversationPhase — AI 对话页的唯一空会话 / 新建会话页面
 * 当 messages 为空时，必须展示该页面；复用 Copilot welcome 视觉，ChatInput 仍底部吸底
 * ============================================================ */
function NewConversationPhase({
  onSend,
  onStop,
  onPrefill,
  inputView,
  prefillText,
  prefillSeed,
  assistantName,
}) {
  return (
    <>
      {/* 中部 hero：自适应剩余高度，居中展示头像/标题/欢迎语/chips */}
      <div className="flex flex-1 min-h-0 w-full items-center justify-center overflow-y-auto">
        <div
          className="flex w-full flex-col items-center justify-center gap-6"
          style={{ width: '440px', maxWidth: '100%', padding: '40px 0' }}
        >
          {/* AI 头像 + 标题 */}
          <div className="flex flex-col items-center gap-3">
            {/* 头像：渐变描边圆 + 蓝色投影 + CATCAT */}
            <div style={{ position: 'relative', width: '66px', height: '66px', flexShrink: 0 }}>
              <div
                style={{
                  position: 'absolute',
                  inset: '0',
                  borderRadius: '50%',
                  padding: '0.5px',
                  background:
                    'linear-gradient(135deg, rgba(63,226,213,0.6) 0%, rgba(64,147,224,0.6) 35%, rgba(122,97,250,0.6) 65%, rgba(214,130,235,0.6) 100%)',
                }}
              >
                <div
                  style={{
                    width: '100%',
                    height: '100%',
                    borderRadius: '50%',
                    background: [
                      'linear-gradient(42deg, #FFF 11.61%, rgba(255,255,255,0.00) 37.84%)',
                      'linear-gradient(74deg, rgba(63,226,213,0.15) 12.18%, rgba(64,147,224,0.15) 39.9%, rgba(122,97,250,0.15) 63.86%, rgba(214,130,235,0.15) 86.38%)',
                      '#FFF',
                    ].join(', '),
                    boxShadow: '0px 8px 15px rgba(180,218,244,0.50)',
                  }}
                />
              </div>
              <img
                src={catcatSvg}
                alt={assistantName}
                style={{
                  position: 'absolute',
                  width: '32px',
                  height: '32px',
                  top: '50%',
                  left: '50%',
                  transform: 'translate(-50%, -50%)',
                  zIndex: 1,
                }}
              />
            </div>
            <span
              className="whitespace-nowrap [font-weight:var(--font-semibold)]"
              style={{
                fontSize: '18px',
                lineHeight: '24px',
                letterSpacing: '-0.03em',
                color: 'var(--foreground, #0F1C35)',
              }}
            >
              {assistantName}
            </span>
          </div>

          {/* 欢迎语 */}
          <p
            className="text-sm text-center leading-5 m-0"
            style={{ color: 'var(--foreground-muted, rgba(15,28,53,0.6))', width: '100%' }}
          >
            您好，我可以帮您整理资料、分析数据并输出结论。
          </p>

          {/* 快捷建议 chips（垂直堆叠 + 白底内阴影 + 右箭头，复用 Copilot 风格）
           * 行为约定：点击只回填到输入框（覆盖草稿）+ 自动聚焦，等用户主动点发送 */}
          <div className="flex flex-col gap-2 w-full">
            {CHAT_DEMO_PROMPT_SUGGESTIONS.map((text) => (
              <button
                key={text}
                type="button"
                onClick={() => onPrefill(text)}
                className="flex items-center justify-between w-full cursor-pointer border-0 text-left"
                style={{
                  padding: '10px 12px',
                  borderRadius: '8px',
                  background: 'rgba(255,255,255,0.6)',
                  boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.9)',
                  fontSize: '12px',
                  lineHeight: '16px',
                  color: 'var(--foreground, #0F1C35)',
                  fontFamily: 'inherit',
                  transition: 'background var(--motion-ui-fast)',
                }}
                onMouseEnter={(e) => {
                  e.currentTarget.style.background = 'rgba(255,255,255,0.85)';
                }}
                onMouseLeave={(e) => {
                  e.currentTarget.style.background = 'rgba(255,255,255,0.6)';
                }}
              >
                <span>{text}</span>
                <Icon name="arrow-narrow-right-stroked" size={12} />
              </button>
            ))}
          </div>
        </div>
      </div>

      {/* 底部 ChatInput：与 chat 阶段保持一致 */}
      <div className={CHAT_INPUT_FOOTER}>
        <div
          className="mx-auto w-full"
          style={{ width: '800px', maxWidth: '100%' }}
        >
          <ChatInput
            variant={inputView}
            autoReplyOnSend={false}
            prefillText={prefillText}
            prefillSeed={prefillSeed}
            placeholder="需要我为你做什么…"
            onSend={onSend}
            onStop={onStop}
          />
        </div>
      </div>
    </>
  );
}

/* ============================================================
 * StreamingChatMessage — 把执行流的 taskGroups 接到 useStreamingTaskGroups
 * 挂载即开始按 600ms 节奏一条一条刷出；最新一条不显示操作栏
 * ============================================================ */
function StreamingChatMessage({ taskGroups, ...rest }) {
  const streamed = useStreamingTaskGroups(taskGroups, { intervalMs: STREAM_INTERVAL });
  return <ChatMessage {...rest} taskGroups={streamed} />;
}

/* ============================================================
 * TopBar — 顶导栏（参考 Figma 独立任务导航）
 * 左：品牌区 | 中：会话图标 + 标题（相对顶栏整体动态居中）| 右：新建会话 + 分享（outline 纯图标）
 * ============================================================ */
function TopBar({
  title,
  platformName,
  onNewSession,
  disableNewSession,
  onBackToHome,
  showSessionHeader = false,
}) {
  return (
    <div
      className="relative flex w-full shrink-0 items-center"
      style={{ padding: CHAT_CONVERSATION_PAGE_PADDING, minHeight: '64px' }}
    >
      {/* 左：可选返回 + 品牌 */}
      <div className="relative z-[1] flex min-w-0 shrink-0 items-center gap-2">
        {typeof onBackToHome === 'function' ? (
          <Button
            variant="ghost-black"
            iconOnly
            icon={<Icon name="arrow-left-stroked" />}
            onClick={onBackToHome}
            tooltip="返回 AI 入口页"
            aria-label="返回 AI 入口页"
          />
        ) : null}
        <img
          src={catcatSvg}
          alt={platformName}
          style={{ width: '28px', height: '28px', flexShrink: 0 }}
        />
        <span
          className="truncate"
          style={{
            fontFamily: '"Arial Black", "PingFang SC", sans-serif',
            fontSize: '14px',
            lineHeight: '20px',
            fontWeight: 900,
            color: 'var(--foreground, #222727)',
            maxWidth: '120px',
          }}
        >
          {platformName}
        </span>
      </div>

      {/* 中：会话图标 + 标题（有真实消息后显示） */}
      {showSessionHeader && title ? (
        <div
          className="pointer-events-none absolute left-1/2 top-1/2 flex max-w-[min(100%,560px)] -translate-x-1/2 -translate-y-1/2 items-center justify-center"
          style={{ gap: '12px', padding: '0 12px' }}
        >
          <div
            className="flex shrink-0 items-center justify-center"
            style={{
              width: '32px',
              height: '32px',
              borderRadius: '8px',
              border: '0.5px solid var(--color-border-default, rgba(34,39,39,0.09))',
              background:
                'linear-gradient(180deg, rgba(200,214,210,0.09) 0%, rgba(52,59,57,0.09) 100%)',
              color: 'var(--foreground, rgba(34,39,39,0.8))',
            }}
          >
            <Icon name="message-circle-02-stroked" />
          </div>
          <h1
            className="m-0 min-w-0 truncate [font-weight:var(--font-semibold)]"
            style={{
              fontSize: '16px',
              lineHeight: '22px',
              color: 'var(--foreground, rgba(34,39,39,0.8))',
            }}
            title={title}
          >
            {title}
          </h1>
        </div>
      ) : null}

      {/* 右：新建会话 + 分享（白底灰框 outline，纯图标） */}
      <div className="relative z-[1] ml-auto flex shrink-0 items-center gap-2">
        <Button
          variant="outline-black"
          iconOnly
          icon={<Icon name="message-plus-square-stroked" />}
          onClick={onNewSession}
          disabled={disableNewSession}
          tooltip="新建会话"
          aria-label="新建会话"
        />
        <Button
          variant="outline-black"
          iconOnly
          icon={<Icon name="share-07-stroked" />}
          tooltip="分享"
          aria-label="分享"
        />
      </div>
    </div>
  );
}
