/**
 * 召回理由的**聚焦顺序** —— 「这一单，客服该先谈哪件事」。
 *
 * ═══ 为什么需要它 ══════════════════════════════════════════════════
 * 一条 plan 可以有多条 reason(实测本地库:**40% 的在跑单跨多个业务标签**,
 * 平均主次分差 15.3 分)。原来的规则只有一条:**分最高的那条排第一**,
 * 而排第一的那条决定了三件事 ——
 *   · 详情页聚焦哪个诊断(plan-detail-app 的 focusedReason)
 *   · 话术整篇讲什么(fact-block 的 `plan.reasons[0]`:主诉/病种/牙位/病历/医生姓/日期锚)
 *   · 其余 reason 退为"顺带提一句"
 *
 * 🔴 问题:主管在矩阵上点「补牙 · 1年内」分下去一批人,**其中相当一部分**
 *   身上分最高的是别的病(实测:补牙这一格 49%、根管 41%、阻生牙 38% 不是主因)。
 *   客服打开看到的聚焦项和话术是缺牙/正畸,而主管以为自己在做补牙专项。
 *   **主管的分配意图在最后一米丢了**,且界面上没有任何迹象。
 *
 * ✅ 规则:**这单是通过某个批次分下来的 → 聚焦那个批次选的标签**;
 *   没有批次(自助认领)或批次已撤销 → 完全等价于原来的"分最高"。
 *
 * ═══ 三处排序必须一起走这个函数 ═════════════════════════════════════
 * ⚠️ 仓库里按 `priorityScore desc` 排 reasons 的地方**不止一处**,
 *   只改其中一个,另外两个会静默把顺序洗回去(改完"看着没效果",且不报错):
 *     · plan-aggregate.service  详情页数据源(Prisma orderBy)
 *     · plan-detail-app.tsx     前端拿到后**又排了一次**
 *     · plan-script.orchestrator 话术生成前**再排一次**,取 [0] 当 top
 *   本函数是这三处的唯一实现,⛔ 别在任何一处重写等价逻辑。
 */

/**
 * 召回子规则 → 它能对应的**业务标签**(初选矩阵 X 轴的 8 类)。
 *
 * ⚠️ 一对多是真实的,不是偷懒:
 *   · `ortho_no_consult` 由年龄决定落 早矫(3-12) 还是 正畸(13-40) —— 同一患者只可能是其中一个
 *   · `hard_tissue_damage`(K03)由诊断名是否含「残根/残冠」决定落 拔牙 还是 修复
 *   两者都要"看患者/看诊断名"才能定死,而**聚焦排序只需要判「这条 reason 有没有可能是
 *   主管选的那一格」**,取候选集合做包含判断就够,不必把它解到唯一值。
 *
 * ⛔ 不在这里的子规则(development_eruption / jaw_cyst / extraction_recommended)
 *   本来就不属于 8 类业务标签,矩阵里没有它们的行 —— 返回空集,永远不被选为聚焦项。
 */
export const SUBKEY_TO_POTENTIAL_LABELS: Readonly<Record<string, readonly string[]>> = {
  missing_tooth: ['implant'],
  caries_no_filling: ['filling'],
  endo_no_rct: ['endo'],
  perio_no_srp: ['perio'],
  gum_alveolar_lesion: ['perio'],
  impacted_tooth: ['extraction'],
  hard_tissue_damage: ['restoration', 'extraction'],
  ortho_no_consult: ['ortho', 'early_ortho'],
};

/**
 * 取 sub_key 的规则名 —— 落库形态是 `caries_no_filling@36` / `perio_no_srp@whole`
 * (`@` 之后是牙位或 `whole`)。⛔ 别用 split('@')[0] 之外的切法:牙位本身不含 `@`。
 */
export function subKeyRule(subKey: string | null | undefined): string {
  if (!subKey) return '';
  const i = subKey.indexOf('@');
  return i === -1 ? subKey : subKey.slice(0, i);
}

/** 这条 reason 是否**可能**就是主管选的那一格 */
export function reasonMatchesLabel(
  subKey: string | null | undefined,
  label: string | null | undefined,
): boolean {
  if (!label) return false;
  const rule = subKeyRule(subKey);
  if (!rule) return false;
  return (SUBKEY_TO_POTENTIAL_LABELS[rule] ?? []).includes(label);
}

/** 排序只需要这两个字段 —— 服务端 Prisma 行、前端 DTO、编排器入参都满足 */
export interface FocusOrderable {
  subKey?: string | null;
  priorityScore: number;
}

/**
 * 按「批次意图优先，其余按分」排序。**不修改入参**。
 *
 * @param focusLabel 批次 criteria 里的 potentialTreatment;
 *   ⚠️ 只在批次仍 **confirmed** 时传 —— 撤销后批次意图已作废,应回落"分最高"。
 *   判据与话术带不带福利完全一致(见 plan-script.orchestrator 的 benefit),⛔ 别另立标准。
 *
 * `focusLabel` 为空、或没有任何 reason 命中它 → 结果与原来的 `priorityScore desc` **逐位相同**。
 */
export function focusOrderReasons<T extends FocusOrderable>(
  reasons: readonly T[],
  focusLabel?: string | null,
): T[] {
  return [...reasons].sort((a, b) => {
    const am = reasonMatchesLabel(a.subKey, focusLabel) ? 1 : 0;
    const bm = reasonMatchesLabel(b.subKey, focusLabel) ? 1 : 0;
    if (am !== bm) return bm - am; // 命中批次标签的排前面
    return b.priorityScore - a.priorityScore; // 其余保持原口径
  });
}
