/**
 * 牙位字符串规范化(W4 末)
 *
 * Host(瑞泰/瑞尔 DW)的 toothPosition 字段经常带牙面后缀:
 *   FDI 恒牙形态(数字 2 位):
 *     "17 D"       → 17 号牙远中面(distal)
 *     "37 O"       → 37 号牙咬合面(occlusal)
 *     "46 B;36 B"  → 多牙位带颊侧面(buccal)
 *     "11 M;21 M"  → 多牙位带近中面(mesial)
 *   Palmer 乳牙形态(象限数字 + 字母 A-E,儿童 host 大量使用):
 *     "1D OD"      → 上颌右乳第一磨牙 远中-咬合面
 *     "1D M"       → 上颌右乳第一磨牙 近中面
 *     "2E MO"      → 上颌左乳第二磨牙 近中-咬合面
 *
 * 牙面修饰:
 *   D = Distal(远中)  M = Mesial(近中)
 *   O = Occlusal(咬合) B = Buccal(颊侧)
 *   L = Lingual(舌侧) P = Palatal(腭侧)
 *   I = Incisal(切端)
 *
 * 问题:同一颗牙(17 / 1D 等)在不同接诊带不同牙面后缀 → chain-composer 拆链
 *
 * 处置(本工具职责):**只剥牙面后缀,保留牙位编号(FDI 或 Palmer)本身**。
 *   FDI:
 *     "17 D"       → "17"
 *     "46 B;36 B"  → "46;36"
 *     "11 M;21 M"  → "11;21"
 *     "32"         → "32"        (无变化)
 *   Palmer 乳牙(W4 末加):
 *     "1D OD"      → "1D"        (剥 OD 面后缀,保留 1D 牙位)
 *     "1D M"       → "1D"
 *     "2E MO"      → "2E"
 *     "1A;1B;1C"   → "1A;1B;1C"  (无空格 = 无面后缀,不动)
 *
 * 边界:
 *   - 只匹配 "牙位 + **空格** + 牙面字母" 形态(空格是关键分隔)
 *   - 不带空格的 Palmer 单字母(1A/1B/1C/1D/1E)= 完整牙位,保留
 *   - 输入 null/empty 原样返回
 */

// 牙位 base = FDI 1-2 位数字(11/22/47 等) 或 Palmer 数字+字母(1A/1D/3E 等)
// 后跟空格 + 一组牙面字母(DMOBLPI 大小写) → 把面字母剥掉,保留 base
const SURFACE_SUFFIX_RE = /(\d+[A-Ea-e]?)\s+[DMOBLPIdmoblpi]+/g;

export function normalizeToothPosition(input: string | null | undefined): string | null {
  if (input === null || input === undefined) return null;
  const trimmed = String(input).trim();
  if (!trimmed) return null;
  // 替换:把 "牙位 base + 空格 + 牙面字母+" 中的面字母部分去掉,保留 base
  const out = trimmed.replace(SURFACE_SUFFIX_RE, '$1');
  return out || null;
}

/**
 * 牙位串拆分 + 规整化后返回单牙位 set
 * 用于 chain target match / scenario tooth-level 排除等场景
 *
 * 例:
 *   "1D OD;3E O;4E O" → Set { "1D", "3E", "4E" }
 *   "11 M;21 M"       → Set { "11", "21" }
 *   "1A;1B;1C"        → Set { "1A", "1B", "1C" }
 */
/**
 * 合法牙位 token 判定:必须【数字开头 + 至少 2 字符】。
 *   合法:FDI 恒牙 11-48 / FDI 乳牙 51-85(两位数字) · Palmer 乳牙 1A-8E(数字+字母)。
 *   非法:裸单数字 1-8(象限号,医生笔误漏打,如 "16"→"1")—— 任何记号体系都不是合法牙,
 *         留着会造永不可解的幽灵召回(caries@1)。丢弃 ≠ 解读(不猜它本该是哪颗),只是不对垃圾下手。
 */
export function isValidToothToken(t: string): boolean {
  return t.length >= 2 && /^\d/.test(t);
}

export function toothSet(input: string | null | undefined): Set<string> {
  const norm = normalizeToothPosition(input);
  if (!norm) return new Set();
  return new Set(
    norm.split(';').map((t) => t.trim()).filter(Boolean).filter(isValidToothToken),
  );
}
