import type { DraftPlanScriptInput } from '../../shared/input.types';
import { smartDateDisplay, toothFriendly } from '../../shared/script-facts';
import { resolveDisease } from './phrasing';
import { deidentifyDoctor } from '../../shared/pii';
import { renderTreatmentPlan } from '../../shared/fact-block';

/**
 * Prompt 版本管理约定:
 *   - 格式 "{callKey}@{date}-{letter}"
 *   - 改 prompt 文本 / system 指令 → 必须 bump letter(a→b→c...)
 *   - 改 schema 字段 → 必须 bump date(影响输入输出形态)
 *   - bump 后,AgentInvocation.promptVersion 字段会落新版本,
 *     SQL: `SELECT prompt_version, avg(judge_score), avg(cost_yuan) FROM agent_invocations
 *          WHERE call_key='draft_plan_script' GROUP BY prompt_version ORDER BY started_at DESC`
 *     即可对比版本效果。
 *
 * ⭐ 现行 promptVersion 常量在 stable.call.ts。system prompt 由 skill-composer 装配
 *   (shared/_base/common.md + tiers/stable/_base/format.md + 命中的人群 SKILL.md),
 *   本文件只负责 user prompt(按患者拼事实槽)。
 *
 * 历史:
 *   - 2026-05-17-a — 初版,5 字段
 *   - 2026-05-24-b/c/d — B 方案 4 段 markdown + 事实漂移修
 *   - 2026-05-27-skills-base-v1 — 引入 base + skills harness,删除单块 legacy system
 *   - 2026-06-02-4module-v3 (现行) — 4 模块重构 + 确定性事实注入 + 单一聚焦
 */

/**
 * 业务 prompt — 装配 input 成具体上下文。
 *
 * 设计:
 *   - 把患者信息以"病历摘要"风格组织,LLM 对自然语言上下文比对 JSON 上下文更稳
 *   - skills 正文已直接注入 system(composeSystem),user prompt 不再重列名字清单(冗余,已删)
 *   - 能程序算的事实(称呼/日期/主漏诊项/复查时长/风险优势)全在 script-facts.ts 算好,
 *     LLM 只润色,不做年龄分支/日期格式/优先级/查表等确定性判断
 */
export function buildDraftPlanScriptPrompt(input: DraftPlanScriptInput): string {
  const { patient, clinicName, plan, clinicalContext } = input;

  // 单一聚焦:reasons[0] 即聚焦项(orchestrator 已按 priorityScore 降序,单一源);
  //   其他 reason 的内容**完全不进 prompt**(不泄漏其他项)。
  const top = plan.reasons[0];

  // ⭐ 去名留称呼:user prompt 给"姓+敬称"的**可用称呼**(徐女士/韩医生),去掉名(维/雅静全名不进)。
  //   不是 token,LLM 直接用;客服也直接看这个称呼。真名(全名)在内部 ScriptContext,不进 prompt。
  const now = new Date();
  const salutation = patient.salutation; // 姓+性别敬称 / 家长(orchestrator 算好,姓级)
  // 诊断医生去名:韩维 → "韩"(模板会拼成"韩医生");无名 → "您的主治"(→"您的主治医生")
  const doctorSurname = deidentifyDoctor(top?.triggerDoctor ?? clinicalContext.primaryDoctorName ?? null);
  // ⭐ 开场"自从…来过之后近况如何"锚【最近一次就诊】(可能晚于诊断日:缺牙早诊断、后来又来洁过牙);
  //   诊断日(triggerDate)更早、归"告知段"用,不放开场,否则"自从2021诊断后"会忽略患者2023又来过。
  const lastVisitDate =
    clinicalContext.daysSinceLastVisit != null
      ? new Date(now.getTime() - clinicalContext.daysSinceLastVisit * 86400_000)
      : null;
  const lastVisitDisplay =
    smartDateDisplay(lastVisitDate, now) ??
    (top?.triggerDate ? smartDateDisplay(new Date(top.triggerDate), now) : null) ??
    '上次';
  // 主诉取"该应治未治项那次诊断"的(项目相关),非最近一次
  const chiefComplaint = top?.medicalRecord?.chiefComplaint ?? clinicalContext.lastChiefComplaint ?? null;
  // 病种知识(单一访问源:subKey 优先 + 文本兜底);稳健档把 risks/advantages 注入模板槽
  const disease = resolveDisease(top ?? null, plan.primaryScenarioLabel);
  const reviewDuration = disease.reviewDuration;
  // 牙位:聚焦项的 FDI → 患者俗称(如 "上门牙");全口/无具体牙位 → null(不放牙位槽)
  const toothText = top?.toothPositions?.length ? toothFriendly(top.toothPositions) : null;
  // 本次召回目标(plan.goal 原文,内部参考):指导复查/邀约方向,LLM 不要逐字念
  const goal = plan.goal?.trim() || null;
  // 医生那次的交代(医嘱/建议/治疗计划)—— 来自聚焦病历,可引用医生原话体现关怀;
  //   处置/检查所见暂不进稳健档(噪声 + 编造风险),留全集给深度档。
  const mr = top?.medicalRecord;
  const doctorNoteLines: string[] = [];
  if (mr?.doctorAdvice) doctorNoteLines.push(`- 医嘱(原话):${mr.doctorAdvice}`);
  if (mr?.recommendations?.length) {
    doctorNoteLines.push(
      `- 医生建议:${mr.recommendations
        .map((r) => (r.toothPosition ? `${r.text}(${toothFriendly(r.toothPosition)})` : r.text))
        .join(';')}`,
    );
  }
  // 治疗计划:结构化 plannedTreatments(主)+ host 自由文本(常空,补)—— 不再只靠常空的 treatmentPlanText
  const treatPlan = mr ? renderTreatmentPlan(mr) : null;
  if (treatPlan) doctorNoteLines.push(`- 治疗计划:${treatPlan}`);

  const riskLines = disease.risks.length
    ? disease.risks.map((r) => `  - ${r}`).join('\n')
    : '  - (按常识温和提醒,不吓唬人)';
  const advLines = disease.advantages.length
    ? disease.advantages.map((a) => `  - ${a}`).join('\n')
    : '  - 趁现在早点处理会更省心';

  // 患者基本信息(只给话术用得到的:性别 + 年龄)。⚠️ 不含称呼 —— 称呼是"通话对象"(可能是监护人),
  //   跟患者性别/年龄是两回事;混在一起会出现"徐女士,男,9岁"这类矛盾。
  const g = (patient.gender ?? '').trim().toUpperCase();
  const genderText = g === '男' || g === 'M' ? '男' : g === '女' || g === 'F' ? '女' : '';
  const basics = [genderText, patient.age != null ? `${patient.age}岁` : ''].filter(Boolean).join('，');
  // 监护人提示(未成年:打给家长,患者是孩子)。⚠️ 不放监护人真名(脱敏;名字对生成无价值,称呼走 {智能称呼})
  const guardianHint = patient.guardian
    ? `本次电话打给${patient.guardian.relationshipLabel}(称呼见{智能称呼}),沟通对象是家长,患者是孩子,话术里称孩子为"宝宝"`
    : null;
  // 熟络度信号(只给原始事实;语气怎么拿捏的规则在人群共性 SKILL,这里不复述 → 单一源)
  const toneHint = `已完成 ${clinicalContext.completedTreatmentCount} 次治疗、距上次就诊 ${
    clinicalContext.daysSinceLastVisit ?? '未知'
  } 天(语气怎么拿捏见沟通知识,你按这信号判断)`;
  // ≤18 / 年龄未知 → 禁拍片 belt(青少年走成人模板时,成人模板含"拍片"句,这里硬提醒删)
  const noXray = patient.age == null || patient.age <= 18;

  // 自报家门:有登录客服名 → "我是X诊所的{岗位}{姓名}"(小王=简称,非全名,直接用);无名 → 通用"客服"
  const selfIntro = input.agent?.name
    ? `我是${clinicName}的${input.agent.roleTitle}${input.agent.name}`
    : `我是${clinicName}的客服`;

  // ⚠️ user prompt 只给"模板占位需要的值";用法说明全在 system 提示词,这里不重复、不放内部元数据。
  return `# 本次回访患者信息(只能用以下事实,不要编造或推断额外信息)

## 开场用
- {智能称呼}:${salutation}
- {自报家门}:${selfIntro}
- {智能时间显示}(最近一次就诊,用于开场"自从…来过"):${lastVisitDisplay}
- 那次主诉(该问题诊断那次,可能更早):${chiefComplaint ?? '无记录'}
- {诊断医生}:${doctorSurname}医生${guardianHint ? `\n- 触达说明:${guardianHint}` : ''}

## 本次应治未治
- {应治未治项}:${disease.label}${toothText ? `\n- {牙位}:${toothText}` : ''}
- {风险要点}:
${riskLines}
- {治疗优势}:
${advLines}
- {复查时长}:${reviewDuration}${
    doctorNoteLines.length
      ? `\n\n## 医生那次的交代(可引用医生原话体现"医生一直惦记着你";⚠️ 只引用、不演绎、不报价、不承诺,复查框架不变;与本次{应治未治项}无关的别提)\n${doctorNoteLines.join('\n')}`
      : ''
  }

## 本次目标(内部参考 — 指导复查/邀约方向,不要逐字念给患者)
- ${goal ?? '邀约来院复查,让医生评估本次问题'}

## 患者
- ${basics}

## 语气
- ${toneHint}${noXray ? '\n\n## 安全(硬约束)\n- 本患者未满 18 岁或年龄未知:**整篇严禁出现"拍片/拍个片/X光/牙片"等任何拍片表述**(删除模板里的拍片句)' : ''}`;
}
