import { diagnosisCodeNameZh, personaTagLabel } from '@pac/types';

/**
 * 把优先级三维的**数字**翻成**这个患者的情况**。
 *
 * 原先 hover 每行右边写的是口径定义(「病情多急(末诊/超期)」「治疗类型 + 牙数预估收入」),
 * 换谁看都一样 —— 看完仍不知道这位患者为什么是 10 分。这里改成讲他自己的事:
 * 「紧急 · 90 天以上没来」「种植 · 缺 6 颗以上」「重要价值 · 主动咨询过 · 成熟客」。
 *
 * 取值来源尽量**从已有数据推**,不额外存:
 *   急迫档位 ← urgency 分值(10/7/4/1 一一对应,无歧义)
 *   治疗类型 ← reason.signals.triggers[].code;牙数 ← signals.toothPosition
 *   主诉命中 ← intentBehavior(8=咨询过 / 2=没有,二值)
 * 只有 RFM 分群和生命周期反推不出来(8 分既可能是重要保持也可能是重要发展),
 * 由 scorer 在 breakdown 里带上。
 */

export interface PriorityBreakdown {
  urgency?: number; // 急迫性 0-10
  value?: number; // 价值性 0-10
  willingness?: number; // 意愿度 0-10
  rfmAdherence?: number; // 意愿·RFM依从
  intentBehavior?: number; // 意愿·主诉行为
  trustBase?: number; // 意愿·信任基础
  freshness?: number; // 新鲜度因子 0.4-1
  confidenceFactor?: number; // 置信度因子(诊断1.0/建议0.8/影像0.5)
  base?: number; // 三维加权(未乘因子)
  raw?: number; // 综合 ×因子后
  /// 讲人话用(不参与计算):数字反推不出来的两项,由 scorer 带出。老 plan 可能没有。
  rfmSegment?: string | null;
  lifecycleStage?: string | null;
}

/** reason.signals 里本模块用到的部分(其余字段不关心) */
export interface PriorityExplainSignals {
  triggers?: Array<{ code?: string | null }> | null;
  toothPosition?: string | null;
}

const URGENCY_ZH: Record<number, string> = {
  10: '紧急 · 90 天以上没来',
  7: '高 · 30-90 天没来',
  4: '中 · 近期来过或刚发现',
  1: '低',
  0: '无待做项目',
};

/** 牙位串 `16;17;27` → 颗数;全口病种为空 → 0 */
function toothCount(toothPosition?: string | null): number {
  if (!toothPosition) return 0;
  return toothPosition.split(/[;,]+/).filter((s) => s.trim()).length;
}

export function explainUrgency(breakdown: PriorityBreakdown): string {
  return URGENCY_ZH[Math.round(breakdown.urgency ?? 0)] ?? '';
}

/** 价值性 = 治疗类型(+ 种植/修复看牙数分档) */
export function explainValue(
  breakdown: PriorityBreakdown,
  signals?: PriorityExplainSignals | null,
): string {
  const code = signals?.triggers?.find((t) => t?.code)?.code ?? '';
  const name = code ? diagnosisCodeNameZh(code) : '';
  const n = toothCount(signals?.toothPosition);
  // 种植/修复的分值就是按牙数抬的,说清楚哪一档比只报名字有用
  if (code === 'K08') {
    const tier = n >= 6 ? '缺 6 颗以上' : n >= 2 ? `缺 ${n} 颗` : '缺 1 颗';
    return `种植 · ${tier}`;
  }
  if (code === 'K03' && n >= 3) return `${name || '牙体修复'} · ${n} 颗`;
  if (name) return n > 0 ? `${name} · ${n} 颗` : name;
  return `预估收入档 ${(breakdown.value ?? 0).toFixed(0)}/10`;
}

/** 意愿度 = RFM 分群 · 有没有主动咨询过 · 生命周期 */
export function explainWillingness(breakdown: PriorityBreakdown): string {
  const parts: string[] = [];
  // 中文名走圈人字典(单一真理源),不在这儿另抄一张表
  const rfm = personaTagLabel('rfm', breakdown.rfmSegment);
  if (rfm) parts.push(rfm);
  // intentBehavior 是二值(8 咨询过 / 2 没有)—— 「主动问过」是最强的意愿信号,一定要说
  if (breakdown.intentBehavior !== undefined) {
    parts.push(breakdown.intentBehavior >= 8 ? '主动咨询过' : '没主动问过');
  }
  const stage = personaTagLabel('lifecycle_stage', breakdown.lifecycleStage);
  if (stage) parts.push(stage);
  return parts.length ? parts.join(' · ') : `RFM ${breakdown.rfmAdherence ?? '-'}`;
}

/** 新鲜度 —— 只在打了折时才值得占一行 */
export function explainFreshness(breakdown: PriorityBreakdown): string {
  const f = breakdown.freshness ?? 1;
  return f >= 1 ? '诊断还在黄金窗内' : '诊断偏老,已按时间降权';
}

/** 置信度 —— 因子直接对应信号来源 */
export function explainConfidence(breakdown: PriorityBreakdown): string {
  const c = breakdown.confidenceFactor ?? 1;
  if (c >= 1) return '来自医生诊断';
  if (c >= 0.8) return '来自医生建议';
  return '来自影像 AI,已降权';
}

/**
 * 池内位置的一句话。
 *
 * 「9.78 / 10」看着像"还行",实际是整池前 0.05%(生产池 p50 只有 3.1)——
 * 不给参照客服判断不了要不要先打他,这正是加这行的原因。
 */
export function explainPercentile(percentile?: number | null): string | null {
  if (percentile === null || percentile === undefined) return null;
  if (percentile >= 99) return '召回池里最该先打的一批(前 1%)';
  if (percentile >= 90) return `高于池内 ${percentile}% 的患者 · 优先打`;
  if (percentile >= 60) return `高于池内 ${percentile}% 的患者`;
  if (percentile >= 30) return `高于池内 ${percentile}% 的患者 · 中等`;
  return `高于池内 ${percentile}% 的患者 · 排在后面`;
}
