import type { DraftPlanScriptInput } from '../../shared/input.types';
import { smartDateDisplay, toothFriendly } from '../../shared/script-facts';
import { resolveDisease } from './phrasing';
import { deidentifyDoctor } from '../../shared/pii';
// ⭐ benefitBlock 与标准/深度档**共用同一份**护栏文案 —— 两处各写一份必然漂,
//   而「哪一档漏了哪条禁令」要等到客服照着念了才会发现。
import { renderTreatmentPlan, buildPersonaGuide, benefitBlock } from '../../shared/fact-block';
import { AGENT_IDENTITY_PLACEHOLDER } from '../../shared/agent-identity';
import { DENTURE_FIRST_AGE, IMPLANT_LAST_AGE, EARLY_ORTHO_MAX_AGE } from '@pac/types';

/**
 * 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;
  // 高龄介绍顺序(同 fact-block,口径共用 DENTURE_FIRST_AGE):义齿在前、种植并行,不下禁忌结论。
  const dentureFirst = patient.age != null && patient.age > DENTURE_FIRST_AGE;
  // 低龄种植闸 / 早矫措辞(同 fact-block,口径共用 IMPLANT_LAST_AGE / EARLY_ORTHO_MAX_AGE)
  const implantLast = patient.age != null && patient.age <= IMPLANT_LAST_AGE;
  const earlyOrtho = patient.age != null && patient.age <= EARLY_ORTHO_MAX_AGE;

  // 自报家门:身份是「诊断医生的助理」不是客服(业务口径 2026-07-29,见 agent-identity.ts);
  //   姓名**不进 prompt** —— 给 【回访客服】 占位符,LLM 原样输出,读详情时按当前登录人回填
  //   (话术缓存 per-plan、召回池共享,烤进人名会让后开的人读到别人的名字)。
  const selfIntro = `我是${doctorSurname}医生的${AGENT_IDENTITY_PLACEHOLDER}`;

  // 画像精简版(⚠安全 + 定语气):稳健档只灌"护栏"类,不灌切入点(避免诱导加推销),保持填空纪律。
  const persona = buildPersonaGuide(input.personaHighlights ?? [], 'essential');

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

## 开场用
- {智能称呼}:${salutation}
- {自报家门}:${selfIntro}(${AGENT_IDENTITY_PLACEHOLDER} 原样保留,别替换成具体姓名,也别自己编)
- 诊所名(**只在需要提到诊所时用这个**,别自己编"XX口腔";{自报家门} 里不用带):${clinicName}
- {智能时间显示}(最近一次就诊,用于开场"自从…来过"):${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光/牙片"等任何拍片表述**(删除模板里的拍片句)' : ''}${dentureFirst ? `\n\n## 高龄沟通(约束)\n- 本患者 ${patient.age} 岁:缺牙修复**先讲活动义齿**,种植可并行提及,\n  但**不要主推手术、不要承诺能不能种** —— 落到"来院让医生按身体条件评估";措辞更耐心,可提示家属陪同。` : ''}${implantLast ? `\n\n## 低龄沟通(硬约束)\n- 本患者 ${patient.age} 岁,颌骨尚未发育完成:**不要主推种植**(未成年相对禁忌)。缺牙先讲间隙管理 / 正畸方向,\n  修复方式落到"来院让医生按发育情况评估",不承诺能不能种。` : ''}${earlyOrtho ? `\n\n## 矫治措辞(约束)\n- 本患者 ${patient.age} 岁处替牙期:涉及矫正一律说「**早期矫治**」,不要说成给恒牙列排齐的"正畸/戴牙套"。` : ''}${benefitBlock(input.benefit)}${persona ? `\n\n${persona}` : ''}`;
}
