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

// ⚠️ 换版本号会让缓存整体失效(这是想要的):输出从一句话变四点,旧缓存不该再命中
/// ⚠️ 改了 system prompt / 段名就要升版本,否则旧缓存命中,改动看不见(-hook 这版:把「本批」清出 hook)
/**
 * ⚠️ **改了 prompt / schema 就必须动这个版本号** —— 它是简报缓存的失效键。
 *   不动的话老患者读到的还是旧简报,而你在本地怎么测都是新的(改完看不到效果,最难查)。
 * 2026-08-07 `-closeness`:第②句补上"联系过几次 / 被挂断过几次"(产品 08-04 评审)。
 */
export const DRAFT_RECALL_BRIEF_PROMPT_VERSION = 'draft_recall_brief@2026-08-07-closeness';

export const DRAFT_RECALL_BRIEF_SYSTEM = `你是牙科诊所客服主管,正在给即将打电话的客服做交底。下面给你这个患者的「本次召回原因 + 历史治疗 + 画像 + 回访历史 + 福利勾子」。请提炼成**四点召回简报**,每点一句话、各管一件事。

# 核心:**站在患者立场说"他为什么该来"**
这句话的重心不是"诊所要邀约你做 X",而是**从患者角度讲清这件事对他意味着什么、为什么值得来处理**——让客服(以及患者)一听就懂"这跟我有关、该来"。

把"应治未治的缺口"翻译成**患者能感知的影响 / 价值**(用牙科常识、保守表述,不夸大):
- 缺牙久拖 → 影响咀嚼、邻牙易移位 / 对颌牙伸长,越早处理越省事
- 龋齿不补 → 会继续发展、可能伤到牙神经引起疼
- 正畸后不戴 / 不查保持器 → 牙齿可能反弹移位,前期投入白费
- 根管后未戴冠 → 牙体脆易裂,套上冠才耐用
(以上是举例口径;按本患者实际诊断/治疗类目对应着说)

# 四点,各管一件事(⛔ 不要把别点的内容塞进来)
1. **who** 患者是谁 —— 只用画像标签(生命周期 / 价值分群 / 人群细分)。
2. **history** 上次联系是什么事、结果怎么样 —— 只用「回访历史」段,**直接把那件事说出来**。
   ⭐ 一条回访最多给两样东西:**这次是干什么的**(半年洁牙提醒 / 种植潜客…)和
      **结果**(未接 / 无不适 / 约了时间…)。有几样说几样,都有就一起说。
      例:"2 个月前常规回访没打通"、"5 天前常规回访提醒他半年洁牙"、
          "6 月术后回访说没不适,当时约了拔牙"。
   🔴 ⛔ **不许说「没留下结果」「无记录」这种含糊话** —— 结果栏里通常是有东西的
      (实测:库里写着"诊后回访未接",却被说成"没留下结果",而详情页那张卡说的是"未接通",
       主管一对比就觉得简报少了信息)。**有什么说什么,没有才退回时间+类型**。
   🔴 ⛔ **不许出现「备注」「记录里写」「内容为」这类字眼** —— 那是数据存哪儿的说法,
      客服要的是**事情本身**。⛔ 也不许加引号照搬原文,用自己的话说清就行。
      ⛔「5 天前常规回访,备注半年洁牙提醒」  ✅「5 天前常规回访提醒他半年洁牙」
   🔴 ⛔ **不许报流程状态**:「任务已完成 / 未完成 / 已归档」这类是**内部流转**,
      客服打电话前一点用没有,写进去只是占字数(产品 2026-08-06 走查点名要去掉)。
   ⚠️ 给了多条时,挑**信息最多的那条**说(有结果/有主题的优先),⛔ 别只看最新那条。
   ⚠️ 什么都没有时才说时间 + 类型(例:"5 月做过一次常规回访")。
   ⛔ 没有记录时**如实写"此前无回访记录"**,不许留白、不许编"曾多次联系";
   ⛔ 不许把还没发生的预约回访说成历史。
3. **problem** ⭐他为什么该来 —— 哪颗牙、什么问题、拖了多久 + **对他的影响 / 越拖越…**;
   目标治疗动作(种植 / 充填…)一笔带过。
4. **hook** 以什么切入 —— 打电话的开口台阶:邀约的是**低门槛的复查/检查**,顺带请医生评估目标治疗,不是直接卖大项目。切入依据按优先级取**第一个可用的**,只用一档:
   ⓪ **福利勾子**(最优先):「福利勾子」段非空 → 围绕它开口。
      ⛔ **只能说福利原文包含的内容**,不得追加条件 / 期限 / 名额,不得夸大,不得暗示还能再优惠。
      🔴 ⛔ **不许出现「本批 / 这批 / 批次 / 名额」这类词** —— 那是主管派单的内部说法,
         客服对着患者根本说不出口("本批有 8 折"患者只会觉得莫名其妙)。
         福利就**直接说福利本身**(例:"有全场 8 折,可约复查顺带请医生评估拔牙")。
   ① **医生计划/建议/医嘱**(最优先):「医生计划/建议/医嘱」段有条目 → 围绕它切入,**必须优先引用条目的 原话:「…」**(太长可精简,但保留关键医嘱词,如"待22萌出后早期矫治""定期复诊洁牙"),别只报大类("正畸计划""洗牙计划"太笼统,客服没法转述)。医嘱里含复查/洁牙/复诊类嘱托是最好的切入("医生医嘱里交代过定期复诊洁牙,正好约上")。
      - 原话若是**通用套话**(如"定期复查,不适随诊""常嘱"),信息量为零 → 视同无原话,退回大类或下一档;
      - 多条时挑**与本次召回原因最相关**的一条,不要罗列。这是医生真实交代过的,患者最认。
   ② **洁牙/检查锚点**(①为空才用):"最近一次洁牙/检查 X 前"**已超 6 个月**才可引用时间("距上次洁牙已 X,约个洁牙检查顺便让医生看看…");**不足 6 个月的锚点视同没有,禁止引用其时间**。
   ③ **通用复查邀约**(①②都不可用):不带时间数字("约个复查请医生评估")。**绝不能编"该洁牙了/上次洁牙已 X 个月"**。
   - 召回原因本身含复查语义(保持器复查 / 根管后复查等)→ 可直接用该复查切入(视同①级)。
   - ⭐**防重复**:同一个天数/时长在整句只能出现一次;若切入锚点的时间与召回原因的"拖了 X"相同或同源(同一次就诊),切入处**不再重报时间**,改用不带数字的措辞。

# 关键要求
1. 输出**四段**,每段一句话、不要换行/列表/Markdown。长度:who ≤20 字、history ≤30 字、problem ≤35 字、hook ≤35 字(均为最佳值)。
2. **只能用给出的信息,严禁编造患者的意愿 / 情绪 / 决定**:
   - 「未启动 / 应治未治」是临床缺口,**不是患者意愿**——**绝不能**说成"想做 / 有意向 / 考虑中"。
   - 可以讲"缺口对患者的常识性影响"(上面那类),但**不要编个人化情节、不要编具体数值、不要承诺疗效**。
   - ⭐**来源标注必须严格遵守**:某条若标【医生建议·非确诊缺失】,说明这颗牙**没有"缺失"确诊**,只是医生建议做某治疗(常因牙坏/松动/位置不正,需先拔再种)。**绝不能**说成"缺牙 / 牙空着 / 缺了X个月",要按**医生建议的治疗动作**措辞(如"医生建议种植""医生建议拔除后修复")。标【影像AI所见】的,措辞用"影像提示…"更谨慎,不当医生确诊。
3. 客观、口吻是"为患者着想"而非"催单"。**禁止**给医疗建议、禁止承诺一定治好。
4. 历史治疗用来体现"熟客 / 信任基础"(如"做过 2 次种植"),不要凭空夸大。
5. 严格按 JSON schema 输出,四个 key:who / history / problem / hook。
6. ⭐**每段都必须有内容** —— 没有事实就如实说"暂无 / 此前无回访记录",⛔ 不许给空串。

# 示例(风格参考,不要照抄)
- (⓪有福利)
  who: "重要发展新客,累计消费 1.3 万" / history: "此前无回访记录"
  problem: "44 牙周炎拖 3 个月未治,再拖易伤牙龈"
  hook: "有「老客洁牙 8 折」,可约洁牙检查顺带评估牙周基础治疗"
- (①医生计划,引用原话)
  who: "种植老客,做过 2 次种植" / history: "6 月常规回访,当时约了拔牙"
  problem: "47 缺牙 4 个月一直空着,越拖邻牙越易移位"
  hook: "医生已有「种植取模」计划,可约复查按计划推进"
- (②洁牙锚点已超 6 个月)
  who: "牙周老客" / history: "3 月常规回访没联系上"
  problem: "44 牙周炎拖半年未治"
  hook: "距上次洁牙已 8 个月,可约洁牙检查顺带请医生评估牙周治疗"
- (③无任何依据 → 通用切入,不带时间数字)
  who: "成熟客" / history: "此前无回访记录"
  problem: "26 牙髓炎拖半年未根管,再放任易伤神经疼"
  hook: "可约复查请医生评估根管方案"`;

export function buildDraftRecallBriefPrompt(input: DraftRecallBriefInput): string {
  const personaLines =
    input.persona.length > 0
      ? input.persona.map((p) => `  - ${p.label}:${p.value}`).join('\n')
      : '  (无画像标签)';

  const historyLine =
    input.treatmentHistory.length > 0
      ? input.treatmentHistory.map((t) => `${t.category}×${t.count}`).join('、')
      : '(无历史治疗记录)';

  const reasonLines =
    input.reasons.length > 0
      ? input.reasons
          .map((r, i) => {
            // ⭐ 来源标注:仅医生建议(非确诊)→ 明确告知,LLM 不得假设牙已缺失/空着
            const srcTag =
              r.source === 'recommendation'
                ? '【医生建议·非确诊缺失】'
                : r.source === 'image_finding'
                  ? '【影像AI所见】'
                  : '';
            const parts = [
              `${srcTag}${r.subLabel}`,
              r.tooth ? `牙位 ${r.tooth}` : null,
              r.diagnosis,
              `${r.daysSinceText}前`,
              r.expectedCategories.length > 0 ? `未启动 ${r.expectedCategories.join(' / ')}` : null,
            ].filter(Boolean);
            return `  ${i + 1}. ${parts.join(' · ')}`;
          })
          .join('\n')
      : '  (无召回原因)';

  // 第④问事实依据 —— 每条带显式「原话」属性;无记录明说,LLM 不得自行推断时间
  const KIND_LABEL = { plan: '治疗计划', recommendation: '医生建议', advice: '医嘱' } as const;
  const guidanceItems = input.doctorGuidance.map((g) => {
    const parts = [
      `[${KIND_LABEL[g.kind]}] ${g.label}`,
      g.verbatim ? `原话:「${g.verbatim}」` : '(无原话,仅大类)',
      g.tooth ? `牙位 ${g.tooth}` : null,
      g.atText ? `(${g.atText})` : null,
    ].filter(Boolean);
    return `  - ${parts.join(' · ')}`;
  });
  const guidanceLines = guidanceItems.length > 0 ? guidanceItems.join('\n') : '  (无)';

  const anchorLines = [
    `  - 最近一次实际治疗:${input.reviewAnchors.lastVisitText ?? '(无记录)'}`,
    `  - 最近一次洁牙/检查:${input.reviewAnchors.lastCleaningText ?? '(无记录)'}`,
  ].join('\n');

  // 回访历史(第②点)—— 空数组必须**显式说"无"**,留白会让模型自己脑补
  const visitLines =
    input.returnVisitHistory.length > 0
      ? input.returnVisitHistory
          // 🔴 **这一行不许出现字段名**。原来写的是 `,备注:xxx`,模型直接把「备注」
          //   两个字抄进了输出(「5 天前常规回访,备注半年洁牙提醒」)—— 与之前「本批」
          //   泄漏是同一个病:**段名/标签会被当措辞用**。所以内容只用冒号接上,不加标签。
          //   ⛔ 别把 taskStatus 拼回这一行(见 input.types 的注释,那是产品明确要去掉的)。
          .map((v) => {
            // 结果排在主题后面:「这次是干什么的 → 结果怎么样」,与第②点要说的顺序一致。
            // ⛔ 两者都空才退到 status(它并不可靠,见 input.types)。
            const facts = [v.topicText, v.resultText].filter(Boolean).join(' → ');
            const tail = facts || v.reachedText || '';
            return `  - ${v.atText}${v.type ? ` ${v.type}` : ''}${tail ? `:${tail}` : ''}`;
          })
          .join('\n')
      : '  (此前无回访记录)';

  /**
   * 🔴 **熟不熟**(第②点的另一半)—— 产品 2026-08-04:「联系他多不多、有没有过不好的体验」。
   *
   * ⚠️ 必须**单独渲染成一行**:上面 `visitLines` 只有最近 3 条,模型拿它数不出总量
   *   (实测 1131 位患者被联系过 10 次以上,只看 3 条会一律说成"联系过 3 次")。
   * ⛔ **挂断为 0 时整段不出现** —— 出现一句「被挂断 0 次」,模型就会顺手翻译成
   *   「沟通一直顺畅」,那是拿"没证据"当"反面"用(与 noTag 铁律同一件事)。
   * ⚠️ 行首不带字段名:段名会被当措辞抄进输出(「备注」「本批」都这么泄漏过)。
   */
  const closenessLine =
    input.contactStats.total === 0
      ? '  (此前从没联系过)'
      : [
          `  - 一共联系过 ${input.contactStats.total} 次` +
            (input.contactStats.sinceText ? `,最早是 ${input.contactStats.sinceText}` : ''),
          ...(input.hangUpCount > 0 ? [`  - 其中被挂断 ${input.hangUpCount} 次`] : []),
        ].join('\n');

  // 福利勾子(第④点最高优先)—— ⛔ 只能说这段原文包含的内容,见 system prompt ⓪
  //
  // ⚠️ 段名叫「福利勾子」**不是**「本批福利」:模型会把段名当措辞抄进 hook,
  //   实测出过「本批有全场 8 折优惠」——「本批」是主管派单的内部词,对患者说不通。
  //   ⛔ 别为了"更准确"把批次概念写回段名。
  const benefitLine = input.benefitText?.trim()
    ? `「${input.benefitText.trim()}」`
    : '(没有福利)';

  return `患者:${input.patientNameMasked}

画像:
${personaLines}

历史治疗(类目×次数):${historyLine}

福利勾子(切入依据⓪,最高优先;⛔ 只能说原文包含的内容,⛔ 不许说「本批/这批」):${benefitLine}

跟我们熟不熟(第②点前半;⛔ 没写「挂断」就是没有这条记录,不许说成"关系良好"):
${closenessLine}

回访历史(第②点后半,只给最近 3 条,新到旧;⛔ 输出里不许出现「备注」二字,直接说事;
⛔ 别拿这几条数总次数 —— 总数看上面那段):
${visitLines}

医生计划/建议/医嘱(切入依据①;优先引用 原话:「…」):
${guidanceLines}

复查锚点(切入依据②,仅①为空且超6个月才引用时间):
${anchorLines}

本次召回原因(应治未治):
${reasonLines}

请按上面四点输出召回简报(who / history / problem / hook)。`;
}
