'use client';

import { useState } from 'react';
import { cn, formatDaysReadable } from '@/lib/utils';
import { Chip } from './shared';
import type { Chain } from './mock-data';

const STAGE_LABELS = ['发现机会', '进入治疗链', '治疗执行', '术后管理', '闭环'];
const STAGE_NUM = ['①', '②', '③', '④', '⑤'];

// 按 category 渲染潜在新链的"诊断依据"和"医嘱建议时窗"
// 临床惯例 ref:
//   - implant 缺牙:6 个月内防邻牙倾斜 / 对颌伸长
//   - periodontic 牙周炎:3 个月内防进展(种植患者更紧 1-2 月)
//   - restorative 龋齿:1 个月内防牙髓炎
//   - prosthodontic 冠桥:1-2 个月内防牙体进一步缺损
function chainTargetMeta(category: string | undefined): { evidence: string; window: string } {
  switch (category) {
    case 'implant':
      return { evidence: 'CBCT 已诊断牙缺失', window: '医嘱建议 6 个月内' };
    case 'periodontic':
      return { evidence: '已诊断牙周炎', window: '医嘱建议 3 个月内' };
    case 'restorative':
      return { evidence: '已诊断龋齿', window: '医嘱建议 1 个月内' };
    case 'prosthodontic':
      return { evidence: '已诊断冠修复需求', window: '医嘱建议 1-2 个月内' };
    default:
      return { evidence: '已有诊断', window: '建议尽早启动' };
  }
}

// ─────────────────────────────────────────────
// 5 阶段状态词表 — 单一真理源(badge / sidebar / list 都引用,避免随手起名)
//   status × currentStage 决定文案 & 配色,不在调用方再编。
//
//   discovered          ★ 潜在新链          rose
//   entered             ⏵ 已进入            amber
//   ongoing  stage=3    ↻ 在管 · 治疗中     sky
//   ongoing  stage=4    ↻ 在管 · 复查中     sky
//   closed              ✓ 已闭环            emerald
//
//   "二级信息"(时间/断口)由调用点拼,不进词表
// ─────────────────────────────────────────────
type ChainStatusVisual = {
  short: string;        // 短标签(badge / sidebar 用),如 "已进入"
  long: string;         // 长标签(全景卡 badge 用),如 "↻ 在管 · 治疗中"
  icon: string;         // ★ / ⏵ / ↻ / ✓
  tone: 'rose' | 'amber' | 'sky' | 'emerald' | 'slate';
};
function chainStatusVisual(chain: Pick<Chain, 'status' | 'currentStage' | 'target'>): ChainStatusVisual {
  // closed 永不 ★(临床已结束)
  if (chain.status === 'closed') return { short: '已闭环', long: '✓ 已闭环', icon: '✓', tone: 'emerald' };
  // ⭐ target + entered/ongoing = 老链已在管,又来新诊断 → SQL 重新召回 →"再启新链"
  //   语义:不是凭空新建,是同 chain 上"上次治疗后又有新事件,该再启一轮"
  //   典型:张吴双 牙周 2024.04.27 洁牙后 + 2026.03.27 新 K05 诊断 → 该再做基础治疗
  if (chain.target && (chain.status === 'entered' || chain.status === 'ongoing')) {
    return { short: '再启新链', long: '★ 再启新链', icon: '★', tone: 'rose' };
  }
  if (chain.status === 'entered') return { short: '已进入', long: '⏵ 已进入', icon: '⏵', tone: 'amber' };
  if (chain.target && chain.status === 'discovered') {
    return { short: '潜在新链', long: '★ 潜在新链', icon: '★', tone: 'rose' };
  }
  // discovered + target=false(SQL 未召回 — 同牙位拔除 ⑤c 排除 / cooldown 未过等)
  //   不再 ★ 误导客服;改"已发现 · 暂不召回",中性灰色表示"已识别但 SQL 评估暂不进入主流程"
  //   典型:季根财 K05 全口 — 所有牙位都被同期 surgical 拔除,牙周治疗已无意义
  if (chain.status === 'discovered') return { short: '已发现', long: '已发现 · 暂不召回', icon: '⊙', tone: 'slate' };
  // ongoing(无 target)— 按 stage 区分"治疗中" / "复查中"
  const sub = chain.currentStage >= 4 ? '复查中' : '治疗中';
  return { short: `在管 · ${sub}`, long: `↻ 在管 · ${sub}`, icon: '↻', tone: 'sky' };
}

/// corner badge 文案 — 跟 chainStatusVisual 同口径,但只输出 ★ 那两档(target=true 才用)
function chainTargetCornerLabel(chain: Pick<Chain, 'status' | 'target'>): string {
  if (!chain.target) return '';
  return chain.status === 'discovered' ? '★ 潜在新链' : '★ 再启新链';
}

// 5 状态徽章(W3 末从 3 态升级)— 后端 chain-composer 5 阶段引擎产物
function ChainStatusBadge({ status, currentStage, target }: { status: Chain['status']; currentStage: Chain['currentStage']; target?: boolean }) {
  const v = chainStatusVisual({ status, currentStage, target });
  return (
    <Chip tone={v.tone} size="xs" icon={v.tone !== 'emerald'}>
      {v.short}
    </Chip>
  );
}

/**
 * TimelineStageNode — 5 阶段 timeline 单节点(共享渲染)
 *
 * 4 信息位:
 *   L0  圆点 + ① 发现机会(静态阶段 header)
 *   L1  title   具体动作(如 慢性牙周炎 / *全口洁治)
 *   L2  detail  关键事实(如 全口 26 牙 / ¥58 / 1/3 步骤)
 *   L3  doctor  医生名(可空)
 *   L4  at      日期 YYYY.MM.DD
 *   L5  hint    discovered/未闭环 等态的"建议下一步"提示(rose tone)
 *
 * 视觉:
 *   - done    实心圆 ✓(theme 色:闭环 emerald / 在管 sky / 召回 rose)
 *   - current 实心圆高亮(amber ring)
 *   - missing 虚线灰圆
 */
function TimelineStageNode({
  node,
  idx,
  theme,
}: {
  node: Chain['nodes'][number];
  idx: number;
  theme: 'rose' | 'emerald' | 'sky' | 'amber';
}) {
  const isDone = !!node.done;
  const isCurrent = !!node.current;
  const dotCls = isDone
    ? cn(THEME_DOT[theme])
    : isCurrent
      ? 'bg-amber-400 border-amber-400 ring-2 ring-amber-100'
      : 'bg-white border-dashed border-slate-300';
  const titleCls = isDone || isCurrent ? 'text-slate-900' : 'text-slate-400';
  const detailCls = isDone || isCurrent ? 'text-slate-600' : 'text-slate-400';
  const atCls = isDone || isCurrent ? 'text-slate-500' : 'text-slate-300';
  const dash = '—';
  const title = node.title || node.label || dash;
  const detail = node.detail || dash;
  const doctor = node.doctor || dash;
  const at = node.at || dash;
  return (
    // min-w-0 + overflow-hidden 让 truncate 在 grid col 里生效
    <div className="flex flex-col items-center text-center min-w-0 overflow-hidden px-0.5">
      {/* L0 dot */}
      <span
        className={cn(
          'relative z-10 flex items-center justify-center w-[14px] h-[14px] rounded-full border-2',
          dotCls,
        )}
      >
        {isDone && (
          <svg viewBox="0 0 12 12" className="w-2 h-2 text-white">
            <path d="M2 6.5l2.5 2.5L10 3" stroke="currentColor" strokeWidth="2.5" fill="none" strokeLinecap="round" />
          </svg>
        )}
      </span>

      {/* stage header(静态)*/}
      <div
        className={cn(
          'mt-1 text-[9.5px] font-medium tracking-wider whitespace-nowrap',
          isDone || isCurrent ? 'text-slate-400' : 'text-slate-300',
        )}
      >
        {STAGE_NUM[idx]} {STAGE_LABELS[idx]}
      </div>

      {/* 4 行强制占位(单行省略号,字段空用 — 占位 → 行对齐)
          标题 title="..." 给 hover 看全文 */}
      <div
        className={cn('mt-1 text-[11px] font-semibold leading-tight truncate w-full', titleCls)}
        title={title}
      >
        {title}
      </div>
      <div className={cn('text-[10px] mt-0.5 leading-tight truncate w-full', detailCls)} title={detail}>
        {detail}
      </div>
      <div className="text-[10px] mt-0.5 leading-tight truncate w-full text-slate-500" title={doctor}>
        {doctor}
      </div>
      <div className={cn('text-[10px] mt-0.5 tabular-nums leading-tight truncate w-full', atCls)} title={at}>
        {at}
      </div>
    </div>
  );
}

/// 从 nodes 收集所有 hint,按阶段排序 — 给底部"建议下一步" 汇总区用
function collectHints(chain: Chain): Array<{ stage: number; text: string }> {
  return chain.nodes
    .filter((n) => !!n.hint)
    .map((n) => ({ stage: n.stage, text: n.hint! }));
}

const THEME_DOT: Record<'rose' | 'emerald' | 'sky' | 'amber', string> = {
  rose: 'bg-rose-500 border-rose-500',
  emerald: 'bg-emerald-500 border-emerald-500',
  sky: 'bg-sky-500 border-sky-500',
  amber: 'bg-amber-500 border-amber-500',
};

/// 状态 → 主题色映射
function themeOfStatus(status: Chain['status']): 'rose' | 'emerald' | 'sky' | 'amber' {
  if (status === 'closed') return 'emerald';
  if (status === 'ongoing') return 'sky';
  if (status === 'entered') return 'amber';
  return 'rose';
}

// ──────────────────────────────────────────
// ChainTimeline — 默认形态(详情抽屉用)
// ──────────────────────────────────────────
export function ChainTimeline({ chains }: { chains: Chain[] }) {
  // focused chain:必须 target=true 且未闭环(closed chain 渲染成 ★潜在新链会自相矛盾)
  const target = chains.find((c) => c.target && c.status !== 'closed');
  const history = chains.filter((c) => c !== target);
  return (
    <div className="space-y-2">
      {target && <TargetTimelineRow chain={target} />}
      <HistoryStrip chains={history} />
    </div>
  );
}

function TargetTimelineRow({ chain }: { chain: Chain }) {
  const theme = themeOfStatus(chain.status);
  return (
    <div className="relative rounded-lg border border-rose-200 bg-gradient-to-br from-rose-50/50 to-white px-3 pt-3 pb-2 ring-1 ring-rose-100">
      {/* 左上角 corner badge 表达 ★ 状态(★ 潜在新链 / ★ 再启新链),标题旁不再重复 ChainStatusBadge */}
      <span className="absolute -top-2 left-3 inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full bg-rose-600 text-white text-[9.5px] font-medium shadow-sm">
        {chainTargetCornerLabel(chain)}
      </span>
      <div className="flex items-center gap-2 mb-2 min-w-0">
        <span className="text-[13.5px] font-semibold text-slate-900 truncate" title={chain.name}>
          {chain.name}
        </span>
        {chain.lifecycleNoteZh && (
          <span className="text-[10.5px] text-slate-500 whitespace-nowrap">· {chain.lifecycleNoteZh}</span>
        )}
      </div>

      <div className="relative pt-0.5 pb-1">
        <div className="absolute left-3 right-3 top-[7px] h-px bg-slate-200" />
        <div className="absolute left-3 top-[7px] h-px bg-rose-400" style={{ width: `${(chain.currentStage / 5) * 100}%` }} />
        <div className="grid grid-cols-5 gap-2">
          {chain.nodes.map((n, idx) => (
            <TimelineStageNode key={idx} node={n} idx={idx} theme={theme} />
          ))}
        </div>
      </div>

      {/* 断口 banner — 召回核心信号(诊断 + gap + 医嘱时窗)。
          新链场景里这条已完整表达"该做什么 + 多久没做",不再加底部"建议下一步" 行 */}
      <div className="mt-1.5 flex items-center gap-2 px-2.5 py-1.5 rounded bg-rose-50 border border-rose-100 text-[11.5px]">
        <svg viewBox="0 0 24 24" className="w-3.5 h-3.5 text-rose-600 flex-none" fill="none" stroke="currentColor" strokeWidth="2">
          <path
            d="M12 9v4M12 17h.01M10.3 3.86 1.82 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.86a2 2 0 0 0-3.4 0z"
            strokeLinecap="round"
          />
        </svg>
        <div className="text-rose-800 leading-tight min-w-0 truncate">
          {chain.status === 'discovered' ? (
            <>
              <strong className="font-semibold">链断口</strong> · 已诊断 {chain.diagnosedAt}
              <strong className="font-semibold tabular-nums"> · {formatDaysReadable(chain.gapDays)}未进入治疗链</strong>
            </>
          ) : (
            // 再启新链:链已 ongoing/entered,gapDays 用最早诊断算误导(实际有 actual)
            // 用相对说法,避免误用 diagnosedAt;后续后端给 latestDxAt + lastActualAt 再精化
            <>
              <strong className="font-semibold">再启信号</strong> · 在管治疗链上又被诊断 <strong className="font-semibold">建议再启一轮基础治疗</strong>
            </>
          )}
          <span className="text-rose-600/80 ml-1">{chainTargetMeta(chain.category).window}</span>
        </div>
      </div>
    </div>
  );
}

function HistoryStrip({ chains }: { chains: Chain[] }) {
  return (
    <div className="rounded-lg border border-slate-200 bg-slate-50/60 px-3 py-2">
      <div className="flex items-center justify-between mb-1.5">
        <div className="flex items-center gap-1.5 text-[11px] text-slate-500">
          <svg viewBox="0 0 24 24" className="w-3 h-3" fill="none" stroke="currentColor" strokeWidth="2">
            <path d="M12 8v4l3 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" strokeLinecap="round" />
          </svg>
          <span>历史治疗链 · {chains.length} 条 · 信任锚</span>
        </div>
      </div>
      <div className="grid grid-cols-2 gap-2">
        {chains.map((c) => (
          <HistoryChainCard key={c.id} chain={c} />
        ))}
      </div>
    </div>
  );
}

function HistoryChainCard({ chain }: { chain: Chain }) {
  const reach = chain.status === 'closed' ? 5 : chain.currentStage;
  const lastNode = chain.nodes[4];
  const currNode = chain.nodes[chain.currentStage - 1];
  return (
    <div className="bg-white rounded border border-slate-200 px-2.5 py-1.5 flex items-center gap-2">
      <span
        className={cn(
          'flex-none w-5 h-5 rounded-full flex items-center justify-center text-[10px] font-bold',
          chain.status === 'closed' ? 'bg-emerald-100 text-emerald-700' : 'bg-sky-100 text-sky-700',
        )}
      >
        {chain.status === 'closed' ? '✓' : '🔄'}
      </span>
      <div className="min-w-0 flex-1">
        <div className="flex items-center justify-between gap-2">
          <span className="text-[11.5px] font-medium text-slate-900 truncate">{chain.name}</span>
          <span className="text-[10.5px] text-slate-500 tabular-nums whitespace-nowrap">
            ¥{(chain.value ?? 0).toLocaleString()}
          </span>
        </div>
        <div className="flex items-center gap-1.5 mt-0.5">
          <MiniDots reach={reach} closed={chain.status === 'closed'} />
          <span className="text-[9.5px] text-slate-500 truncate">
            {chain.status === 'closed'
              ? `闭环 · ${lastNode?.at ?? ''}`
              : `在管 · ${currNode?.note || '进行中'}`}
          </span>
        </div>
      </div>
    </div>
  );
}

function MiniDots({
  reach,
  closed,
  target,
}: {
  reach: number;
  closed?: boolean;
  target?: boolean;
}) {
  return (
    <span className="inline-flex items-center gap-[2px]">
      {[1, 2, 3, 4, 5].map((s) => (
        <span
          key={s}
          className={cn(
            'w-1.5 h-1.5 rounded-full',
            s > reach
              ? target
                ? 'border border-dashed border-rose-300 bg-transparent'
                : 'bg-slate-200'
              : target
                ? 'bg-rose-500'
                : closed
                  ? 'bg-emerald-500'
                  : s === reach
                    ? 'bg-amber-400 ring-2 ring-amber-100'
                    : 'bg-sky-500',
          )}
        />
      ))}
    </span>
  );
}

// ──────────────────────────────────────────
// ChainSidebar — 侧边栏紧凑视图(主页面用)
// ──────────────────────────────────────────
export function ChainSidebar({ chains }: { chains: Chain[] }) {
  const sorted = [...chains].sort((a, b) => (b.target ? 1 : 0) - (a.target ? 1 : 0));
  const visible = sorted.slice(0, 3);
  return (
    <div className="space-y-1.5">
      {visible.map((c) => (
        <ChainSidebarRow key={c.id} chain={c} />
      ))}
    </div>
  );
}

function ChainSidebarRow({ chain }: { chain: Chain }) {
  const reach = chain.status === 'closed' ? 5 : chain.currentStage;
  // 最近活跃步骤(已 done 或 current 的最大 stage 节点)→ 显示该步骤名 + 时间
  const recentNode = chain.nodes
    .filter((n) => n.done || n.current)
    .sort((a, b) => b.stage - a.stage)[0];
  if (chain.target) {
    // 新链:用 S1 节点真实数据(诊断名 + 牙位 + 医生)替代旧 hard-coded chainTargetMeta.evidence
    const s1 = chain.nodes[0];
    const evidence = [s1?.title, s1?.detail].filter(Boolean).join(' · ');
    return (
      <div className="relative rounded-md border border-rose-300 bg-gradient-to-br from-rose-50 to-white px-2.5 pt-2 pb-2 ring-1 ring-rose-100">
        <span className="absolute -top-1.5 left-2 inline-flex items-center px-1.5 py-px rounded-full bg-rose-600 text-white text-[9px] font-bold shadow-sm">
          {chainTargetCornerLabel(chain)}
        </span>
        <div className="flex items-center justify-between gap-2 mt-0.5">
          <span className="text-[12px] font-semibold text-slate-900 truncate" title={chain.name}>
            {chain.name}
          </span>
        </div>
        <div className="flex items-center gap-1.5 mt-1">
          {/* discovered=只到 S1;ongoing/entered 用真实 currentStage 反映已走到第几步 */}
          <MiniDots reach={chain.status === 'discovered' ? 1 : chain.currentStage} closed={false} target />
          <span className="text-[10px] text-rose-600 font-semibold tabular-nums">
            {chain.status === 'discovered'
              ? <>⛔ {formatDaysReadable(chain.gapDays)}断口</>
              : <>↻ 在管再诊</>}
          </span>
        </div>
        <div className="text-[10.5px] text-slate-600 mt-1 leading-tight truncate" title={`${evidence || '已诊断'} · ${chain.diagnosedAt}`}>
          {evidence || '已诊断'} · {chain.diagnosedAt}
        </div>
      </div>
    );
  }
  // 二级信息(时间):闭环→闭环 at;entered→启动 at;ongoing→最近 done 节点 at
  // (discovered+!target 已被后端 filter,不会到这里)
  const closedAt = recentNode?.at || chain.nodes[0]?.at || chain.diagnosedAt || '—';
  const sub =
    chain.status === 'closed'
      ? `闭环 ${closedAt}`
      : chain.status === 'entered'
        ? `启动 ${recentNode?.at || chain.diagnosedAt || '—'}`
        : recentNode?.at
          ? `最近 ${recentNode.at}`
          : '';
  const v = chainStatusVisual(chain);
  const wrapCls = TONE_WRAP[v.tone];
  const dotCls = TONE_DOT[v.tone];
  return (
    <div className={cn('rounded-md border px-2.5 py-2', wrapCls)}>
      <div className="flex items-center justify-between gap-2">
        <div className="flex items-center gap-1.5 min-w-0">
          <span className={cn('flex-none w-4 h-4 rounded-full flex items-center justify-center text-[10px] font-bold', dotCls)}>
            {v.icon}
          </span>
          <span className="text-[12px] font-medium text-slate-900 truncate" title={chain.name}>
            {chain.name}
          </span>
        </div>
      </div>
      <div className="flex items-center gap-1.5 mt-1 min-w-0">
        <MiniDots reach={reach} closed={chain.status === 'closed'} />
        <span className={cn('text-[10px] font-medium flex-none', TONE_TEXT[v.tone])}>{v.short}</span>
        {sub && (
          <>
            <span className="text-slate-300 flex-none">·</span>
            <span className="text-[10px] text-slate-500 truncate" title={sub}>
              {sub}
            </span>
          </>
        )}
      </div>
    </div>
  );
}

// status 主色 → 卡片背景 / dot / 文字 三套类名(单一来源,免散写)
type ChainTone = 'rose' | 'amber' | 'sky' | 'emerald' | 'slate';
const TONE_WRAP: Record<ChainTone, string> = {
  rose: 'bg-rose-50/40 border-rose-200/70',
  amber: 'bg-amber-50/40 border-amber-200/70',
  sky: 'bg-sky-50/40 border-sky-200/70',
  emerald: 'bg-emerald-50/40 border-emerald-200/70',
  slate: 'bg-slate-50/40 border-slate-200/70',
};
const TONE_DOT: Record<ChainTone, string> = {
  rose: 'bg-rose-500 text-white',
  amber: 'bg-amber-500 text-white',
  sky: 'bg-sky-500 text-white',
  emerald: 'bg-emerald-500 text-white',
  slate: 'bg-slate-400 text-white',
};
const TONE_TEXT: Record<ChainTone, string> = {
  rose: 'text-rose-700',
  amber: 'text-amber-700',
  sky: 'text-sky-700',
  emerald: 'text-emerald-700',
  slate: 'text-slate-700',
};

// ──────────────────────────────────────────
// ChainDetailView — drawer 用:上明细 + 下全部列表(点击切换)
// ──────────────────────────────────────────
export function ChainDetailView({ chains }: { chains: Chain[] }) {
  const sorted = [...chains].sort((a, b) => (b.target ? 1 : 0) - (a.target ? 1 : 0));
  const [activeId, setActiveId] = useState<string | undefined>(sorted[0]?.id);
  const active = sorted.find((c) => c.id === activeId) ?? sorted[0];
  if (!active) return <div className="text-[12px] text-slate-400">无治疗链数据</div>;
  return (
    <div className="space-y-4">
      {/* 上半:激活的明细(target → TimelineRow;closed → ClosedFullCard)*/}
      {active.target ? (
        <TargetTimelineRow chain={active} />
      ) : (
        <ClosedChainFullCard chain={active} />
      )}

      {/* 下半:全部列表(点击切换上面的明细)*/}
      <div className="pt-3 border-t border-slate-200">
        <div className="text-[11px] text-slate-500 mb-2">
          全部 {chains.length} 条治疗链
        </div>
        <div className="grid grid-cols-2 gap-2">
          {sorted.map((c) => (
            <ChainListItem
              key={c.id}
              chain={c}
              active={c.id === active.id}
              onClick={() => setActiveId(c.id)}
            />
          ))}
        </div>
      </div>
    </div>
  );
}

function ChainListItem({
  chain,
  active,
  onClick,
}: {
  chain: Chain;
  active: boolean;
  onClick: () => void;
}) {
  const isTarget = !!chain.target;
  // 用 chainStatusVisual 统一图标 + tone — 5 态精确(包括 discovered 未召回降级 slate)
  const v = chainStatusVisual(chain);
  const iconBgClass: Record<string, string> = {
    rose: 'bg-rose-100 text-rose-700',
    amber: 'bg-amber-100 text-amber-700',
    sky: 'bg-sky-100 text-sky-700',
    emerald: 'bg-emerald-100 text-emerald-700',
    slate: 'bg-slate-100 text-slate-500',
  };
  const activeRing: Record<string, string> = {
    rose: 'border-rose-400 bg-rose-50 ring-1 ring-rose-200',
    amber: 'border-amber-400 bg-amber-50 ring-1 ring-amber-200',
    sky: 'border-teal-400 bg-teal-50 ring-1 ring-teal-200',
    emerald: 'border-emerald-400 bg-emerald-50 ring-1 ring-emerald-200',
    slate: 'border-slate-300 bg-slate-50 ring-1 ring-slate-200',
  };
  return (
    <button
      type="button"
      onClick={onClick}
      className={cn(
        'text-left rounded-md border px-2.5 py-2 transition',
        active
          ? activeRing[v.tone]
          : 'border-slate-200 bg-white hover:bg-slate-50',
      )}
    >
      <div className="flex items-center gap-1.5">
        <span className={cn('inline-flex items-center justify-center w-4 h-4 rounded-full text-[9px] font-bold flex-none', iconBgClass[v.tone])}>
          {v.icon}
        </span>
        <span className="text-[12px] font-medium text-slate-900 truncate">{chain.name}</span>
      </div>
      {(() => {
        if (isTarget) {
          // discovered + target=true(真潜在新链):"断口" 补召回紧迫度
          return (
            <div className="mt-1 text-[10.5px] text-slate-500 truncate">
              <span className={TONE_TEXT[v.tone]}>{v.short}</span>
              <span className="text-slate-300 mx-1">·</span>断口 {formatDaysReadable(chain.gapDays)} · {chain.diagnosedAt}
            </div>
          );
        }
        // 后端 filterDiscoveredByPatientLevel 已删 discovered+!target,这里只剩 entered/ongoing/closed
        const sub =
          chain.status === 'closed'
            ? `闭环 ${chain.nodes[4]?.at || [...chain.nodes].reverse().find((n) => n.done)?.at || chain.diagnosedAt || '—'}`
            : chain.status === 'entered'
              ? `启动 ${chain.diagnosedAt}`
              : `最近 ${[...chain.nodes].reverse().find((n) => n.done || n.current)?.at || '—'}`;
        return (
          <div className="mt-1 text-[10.5px] text-slate-500 truncate">
            <span className={TONE_TEXT[v.tone]}>{v.short}</span>
            <span className="text-slate-300 mx-1">·</span>{sub}
          </div>
        );
      })()}
    </button>
  );
}

function ClosedChainFullCard({ chain }: { chain: Chain }) {
  const theme = themeOfStatus(chain.status);
  // 统一走 chainStatusVisual — corner badge 用 long 标签("↻ 在管 · 治疗中" / "⏵ 已进入" / "✓ 已闭环")
  // 跟侧边/列表的"短标签"含义对齐,只是位置不同写法略丰富
  const v = chainStatusVisual(chain);
  const cardCls =
    v.tone === 'emerald'
      ? 'border-emerald-200 bg-gradient-to-br from-emerald-50/50 to-white ring-emerald-100'
      : v.tone === 'sky'
        ? 'border-sky-200 bg-gradient-to-br from-sky-50/50 to-white ring-sky-100'
        : 'border-amber-200 bg-gradient-to-br from-amber-50/50 to-white ring-amber-100';
  const badgeCls =
    v.tone === 'emerald' ? 'bg-emerald-600' : v.tone === 'sky' ? 'bg-sky-600' : 'bg-amber-600';
  const lineCls =
    v.tone === 'emerald' ? 'bg-emerald-400' : v.tone === 'sky' ? 'bg-sky-400' : 'bg-amber-400';
  const badgeLabel = v.long;
  // 闭环链不再渲染"建议下一步"(已闭环就没"下一步"可建议;
  // 替代闭环时 chain header amber "已被替代:X" chip 已表达后续方案,不需要重复)
  const hints = chain.status === 'closed' ? [] : collectHints(chain);
  return (
    <div className={cn('relative rounded-lg border ring-1 px-3 pt-3 pb-2', cardCls)}>
      <span className={cn('absolute -top-2 left-3 inline-flex items-center gap-1 px-1.5 py-0.5 rounded-full text-white text-[9.5px] font-medium shadow-sm', badgeCls)}>
        {badgeLabel}
      </span>
      {/* 左上角 corner badge 已表达 status,标题旁不再重复 ChainStatusBadge */}
      <div className="flex items-center gap-2 mb-2 min-w-0">
        <span className="text-[13.5px] font-semibold text-slate-900 truncate" title={chain.name}>
          {chain.name}
        </span>
        {chain.lifecycleNoteZh && (
          <span className="text-[10.5px] text-slate-500 whitespace-nowrap">· {chain.lifecycleNoteZh}</span>
        )}
        {chain.alternativeClosedBy && (
          <span
            className="text-[10.5px] px-1.5 py-px rounded bg-amber-100 text-amber-800 whitespace-nowrap"
            title={`原诊断方案已被 "${chain.alternativeClosedBy}" 替代(如 K04 → K08 拔除做种植);客服不需要召回此链`}
          >
            ⟳ 已被替代:{chain.alternativeClosedBy}
          </span>
        )}
      </div>
      <div className="relative pt-0.5 pb-1">
        <div className="absolute left-3 right-3 top-[7px] h-px bg-slate-200" />
        <div
          className={cn('absolute left-3 top-[7px] h-px', lineCls)}
          style={{ width: chain.status === 'closed' ? '100%' : `${(chain.currentStage / 5) * 100}%` }}
        />
        <div className="grid grid-cols-5 gap-2">
          {chain.nodes.map((n, idx) => (
            <TimelineStageNode key={idx} node={n} idx={idx} theme={theme} />
          ))}
        </div>
      </div>
      <ChainHintFooter hints={hints} tone={theme} />
    </div>
  );
}

/// 底部汇总"建议下一步" — 只取最有意义的一条(优先 stage 最小的 = 离当前最近的下一步)
/// 全部 hint 通过 title 可 hover 查看(完整信息保留,UI 不冗长)
function ChainHintFooter({
  hints,
  tone,
}: {
  hints: Array<{ stage: number; text: string }>;
  tone: 'rose' | 'emerald' | 'sky' | 'amber';
}) {
  if (hints.length === 0) return null;
  const cls =
    tone === 'rose'
      ? 'bg-rose-50 border-rose-100 text-rose-700'
      : tone === 'sky'
        ? 'bg-sky-50 border-sky-100 text-sky-700'
        : tone === 'amber'
          ? 'bg-amber-50 border-amber-100 text-amber-700'
          : 'bg-emerald-50 border-emerald-100 text-emerald-700';
  const primary = [...hints].sort((a, b) => a.stage - b.stage)[0]!;
  const allText = hints.map((h) => h.text).join(' · ');
  return (
    <div
      className={cn(
        'mt-2 flex items-center gap-1.5 px-2.5 py-1.5 rounded border text-[11.5px] leading-tight',
        cls,
      )}
      title={hints.length > 1 ? `全部建议:${allText}` : undefined}
    >
      <span className="font-semibold flex-none">建议下一步</span>
      <span className="text-slate-400 flex-none">·</span>
      <span className="min-w-0 truncate">{primary.text}</span>
    </div>
  );
}
