import type { DraftPlanScriptInput } from './input.types';

/**
 * Prompt 版本管理约定:
 *   - 格式 "{callKey}@{date}-{letter}"
 *   - 改 prompt 文本 / system 指令 / few-shot examples → 必须 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`
 *     即可对比版本效果。
 *
 * 历史:
 *   - 2026-05-17-a — 初版,deepseek-v4-pro,5 字段(opening/keyMessage/followup/objectionHandling/callToAction)
 *   - 2026-05-24-b — B 方案重写,deepseek-v4-flash,4 段直出 markdown
 *                    + few-shot demo,对齐前端 mockScript 4 段格式
 *   - 2026-05-24-c — 事实漂移修:补医生名/牙位俗称/诊所名硬约束
 *                    few-shot 改用 {占位符} 防止抄具体名字
 *   - 2026-05-24-d — 称呼用通话名(姓+先生/女士);明禁念 scenario 内部 label;
 *                    要求 opening/followup 引用 ≥1 / ≥2 条具体临床事实
 */
export const DRAFT_PLAN_SCRIPT_PROMPT_VERSION = 'draft_plan_script@2026-05-24-d';

/**
 * System prompt(稳定指令,不随 input 变)。
 *
 * 设计原则(B 方案):
 *   - 让 LLM 直出 markdown,格式契约由 schema.ts .describe() 强约束 + system 反例提示
 *   - Flash 模型偏好"清晰指令 + 至少 1 个完整 few-shot",所以下面 user prompt 末尾会带 1 个完整 example
 *   - 强约束词("必须"/"禁止"/"不得") + 反例段("❌ 错示范") Flash 遵循度提升明显
 */
export const DRAFT_PLAN_SCRIPT_SYSTEM = `你是某连锁牙科诊所的资深客服顾问,有 10 年外呼经验,擅长在不显得推销的前提下,自然地把患者请回诊所复诊。

# 核心原则
1. 语气温和、专业,不要"销售感"。患者是来看牙的,不是被推销的。
2. 必须提到具体临床事实(治疗阶段、上次到店做的事),让患者感受到诊所记得 ta。
3. 禁止承诺疗效("一定能治好"/"百分百"/"绝对有效" 等绝对化用语,医疗合规红线)。
4. 禁止使用"亲"、"宝"、"小哥哥/小姐姐" 等口语化称呼;统一用"X 先生/女士"。
5. 必须给具体时间选项(如"周四晚上 7 点或周六上午"),不能只说"有空过来";患者要能立刻回"好,周六可以"。
6. 异议处理覆盖最常见的几种(没时间/再考虑/价格疑虑/已在外院),不要面面俱到。

# 事实约束(绝不可编 — 患者一听就穿帮)
- **称呼**:严格使用 user prompt「患者.称呼」给的字符串(已是"X 先生/女士" 通话名),不得改成"亲""帅哥""路总"等,也不能用"路*""路星"等带掩码/全名
- **诊所名**:严格使用 user prompt「诊所.名称」给的字符串,不得简称/改字/补"中心""旗舰店"等任何字
- **医生名**:如果 user prompt「主诊医生」有值,统一用此名称呼(如"王医生");没有则用"您的主诊医生"泛指,绝不要编"李医生""张主任"
- **牙位**:对患者只能说俗称(上门牙/下门牙/智齿/大牙/前牙/后牙/虎牙),禁止说 FDI 数字编号("21""36""47" 这种患者完全听不懂)
  - user prompt「待做治疗」已经把牙位翻译成俗称,直接照抄;若有补充牙位也只能用俗称
- **数字日期**:不要编未给出的具体日期(如"5/21"),给时间选项时用"本周X / 下周X 晚上 X 点" 这种相对说法
- **召回场景代号**(⭐ 重要):user prompt「召回场景.主场景」字段是**内部分类编码**(如"启治召回"、"治疗后复诊召回"),**绝对不能在话术中念出**!患者听不懂"启治召回"是什么意思,会立即觉得是机器外呼。
  - opening 段必须以"具体临床事实"开场,例如:"上次{主诊医生}给您看牙时提到{待做治疗或上次到店}"、"您 X 月在我们这里检查过 {上次到店}",而不是"今天给您打电话是因为「启治召回」"

# 让患者感受"诊所记得我"(必须做到)
opening + followup 两段加起来,**必须自然引用至少 3 条** user prompt 给的具体临床事实,可选项:
- 上次到店时间(如"上周/上个月/X 月")
- 上次到店做了什么(lastVisitSummary)
- 主诊医生姓名(若有)
- 待做治疗具体内容(pendingTreatments,含俗称牙位)
- 待做治疗的临床后果(如"再拖可能要正畸/补骨,流程更长")
- 触发原因里的临床信号(如"距诊断已 X 天")

不要只说"有段时间没见您了" / "想跟您约时间复查" 这种万能空话 — 这样的话客服自己都说不出口。

# 输出约束(B 方案 · 重要)
你只输出 1 个 JSON 对象,包含 5 个 key:tone / opening / followup / objection / close。
opening / followup / objection / close 这 4 个字段的值都是 **Markdown 字符串**,每段必须严格按 schema 中 .describe() 指定的子结构格式(目的/正文/注意/异议预判/回写要点)。

# ❌ 错示范(必须避免)
- opening 段缺 \`**目的**:\` 开头 → 客服无法快速 scan 目的
- followup 段把异议应对话术写完整 → 跟 objection 段重复
- objection 段用 bullet \`- ...\` 列异议 → 必须用 \`### A. "xxx"\` 子标题
- close 段缺 \`**回写要点**\` → 客服不知道结果该怎么提交
- blockquote 里写排比抒情("您的健康是我们最大的牵挂...") → 假销售感
- ❌ "牙位 21""牙位 36""左上 26 牙" → 患者听不懂 FDI 牙位号
- ❌ "李医生""王主任"(user prompt 未给医生名时) → 编造医生名穿帮
- ❌ "本周四(5/21)" → 编造未给出的具体日期
- ❌ "围绕「启治召回」开场" / "本次想跟您沟通的是:启治召回" → 把内部 scenario label 念给患者听
- ❌ "路*您好" / "段*先生您好" → 用了掩码字符,通话名必须从 user prompt「患者.称呼」整体照抄

# ✅ 好示范的特征
- 开场白 30 秒内讲清楚"我是谁 / 为什么打 / 现在方不方便"
- 切入话题用"上次李医生 X 月给您拍了 CBCT" 这类硬事实开门
- 结束确认时复述"周四(5/21)晚 7:00 李医生" 完整信息

所有文案使用简体中文。只输出 JSON,不要任何解释。`;

/**
 * 业务 prompt — 装配 input 成具体上下文。
 *
 * 设计:
 *   - 把患者信息以"病历摘要"风格组织,LLM 对自然语言上下文比对 JSON 上下文更稳
 *   - 末尾带 1 个完整 few-shot example(精简版,展示 4 段 markdown 格式) — Flash 对 example 学习快
 */
export function buildDraftPlanScriptPrompt(input: DraftPlanScriptInput): string {
  const { patient, clinicName, plan, personaHighlights, clinicalContext } = input;

  const personaLines = personaHighlights.length > 0
    ? personaHighlights.map((p) => `- ${p.label}:${p.description}`).join('\n')
    : '- (暂无关键画像特征)';

  const reasonsLines = plan.reasons.length > 0
    ? plan.reasons
        .map((r, i) => `${i + 1}. [${r.scenarioLabel}, 优先级 ${r.priorityScore}] ${r.reason}`)
        .join('\n')
    : '- (无具体触发原因)';

  const pendingLines = clinicalContext.pendingTreatments.length > 0
    ? clinicalContext.pendingTreatments.map((t) => `- ${t}`).join('\n')
    : '- (暂无明确待做治疗)';

  return `# 召回任务背景

## 诊所
- 名称:${clinicName}(自报家门时**必须**用此名,不要编造其他诊所名)

## 患者
- 称呼:${patient.nameMasked}(已脱敏)
- 性别:${patient.gender ?? '未知'}
- 年龄:${patient.age ?? '未知'}

## 召回场景(⚠️ 内部分类,**不要在话术中念出**)
- 主场景代号:${plan.primaryScenarioLabel}(给客服理解任务性质用,患者听不懂)
- 综合优先级:${plan.priorityScore}

## 触发原因(给客服理解为什么挑这个患者;话术里要用更通俗的说法引出)
${reasonsLines}

## 患者画像关键特征
${personaLines}

## 临床上下文
- 距上次到店:${clinicalContext.daysSinceLastVisit ?? '未知'} 天
- 上次到店:${clinicalContext.lastVisitSummary ?? '无记录'}
- 主诊医生:${clinicalContext.primaryDoctorName ?? '(未知 — 话术中用「您的主诊医生」泛指,不要编名)'}
- 治疗链状态:${clinicalContext.treatmentChainSummary ?? '无数据'}
- 待做治疗(牙位已转患者俗称,直接照抄):
${pendingLines}

# 任务
请为这通外呼电话准备话术。**必须**在 followup 段引用至少一条上述临床事实(治疗阶段 / 上次内容 / 上面给的待做治疗),不要只说"有段时间没见您了"这种空话。
**事实约束硬要求**:诊所名/医生名/牙位俗称严格按上面给的字段,**不要编**。若主诊医生为未知,用"您的主诊医生" / "医生" 泛指。

# 输出格式参考(精简 few-shot,只看格式骨架 — 文字内的 {占位} 必须用 user prompt 真实字段替换)

\`\`\`json
{
  "tone": "warm",
  "opening": "**目的**:亲切自然地建立通话,用「上次到店」做切入,避免推销感。\\n\\n> \\"您好,请问是{患者称呼}吗?我是{诊所名}的客服。{您上次/X 月在我们这边检查时},{您的主诊医生}提到{1 条具体临床事实/待做治疗简述},我今天给您打过来想跟您再聊一下,您现在方便几分钟吗?\\"\\n\\n**注意**\\n- 称呼用「{患者称呼}」,不用全名/掩码\\n- 工作日 19:00 后是患者偏好窗口",
  "followup": "**目的**:把诊所记录的{临床事实},自然引到「该来一趟了」。\\n\\n> \\"上次{您的主诊医生}给您检查时提到{临床事实+牙位俗称}。如果再拖,后面治疗的流程会更复杂、成本也更高。\\"\\n\\n> \\"我们想约您近期来做个面诊评估,**这次只是评估和确认方案,不做任何操作,大概 30 分钟**,您看本周或下周哪天方便?\\"\\n\\n**异议预判**\\n- \\"再等等\\" → 强调时间窗(再拖可能要做额外处理)\\n- \\"费用高\\" → 引导先面诊,方案出来后再算费用\\n- \\"在外院看过\\" → 提交「已在外院治疗」并关闭本次召回",
  "objection": "### A. \\"我再考虑考虑\\"\\n> \\"完全理解,这是个不小的决定。这样,我先帮您把{您的主诊医生}的面诊时间留出来,**本周六上午或下周一晚上 7 点**,您选一个?到现场看了方案再决定也不晚。\\"\\n\\n### B. \\"价格太贵\\"\\n> \\"具体价格要看实际情况,{您的主诊医生}面诊后会给您 2-3 个方案,有不同档位可选。您先来评估,价格不合适咱们再聊别的方式。\\"\\n\\n### C. \\"已经在别的医院看了\\"\\n> \\"好的{患者称呼},那我这边帮您把这条记录关一下,日常护理还是按原来的周期回来就行,**祝您一切顺利**。\\"\\n> → 提交结果选「已在外院治疗」",
  "close": "> \\"好的{患者称呼},那我帮您约 **本周六上午 10 点**,到时候提前 10 分钟到前台就行。我会给您发个短信提醒,您注意接收。还有别的需要么?\\"\\n\\n**回写要点**\\n- 成功约上面诊 → 提交结果选「成功转化为新预约」,填预约时间 + 医生\\n- 同意但未定日期 → 选「约定下次回访」,填预计时间\\n- 考虑中 → 选「考虑中近期再跟进」,7 天后系统提醒二次跟进"
}
\`\`\`

⚠️ few-shot 里的 \`{占位}\` 必须替换成 user prompt 给的真实字段:
- \`{诊所名}\` ← user prompt「诊所.名称」(严格照抄,不简称、不加字)
- \`{患者称呼}\` ← user prompt「患者.称呼」(脱敏后的姓 + 先生/女士)
- \`{您的主诊医生}\` ← user prompt「主诊医生」字段值,若为未知就用"您的主诊医生"泛指(绝不要编"李医生""王主任")
- \`{临床事实+牙位俗称}\` ← user prompt「上次到店」「待做治疗」字段,牙位**已经是俗称**(上门牙/智齿/大牙等),直接照抄,绝不要写成"21 牙""36 位"

按 JSON schema 输出,只输出 JSON,不要任何解释性文字。`;
}
