/**
 * 治疗术式关键词分类器 — CI 防漂移测试。
 *
 * 验证:精确 enum_mapping 未命中时,keyword_mapping(按序含词 + 条件从句剥离)能把
 *       host 长尾 free-text 术式正确归到 PAC category;噪音/无操作正确落空(→ _default 丢弃)。
 *
 * fixture 术式取自服务器真实漏配样本(/tmp/sync-full2.log missing-category WARN)。
 * 直接加载线上 jvs-dw yaml 的 keyword_mapping(顺带校验新 schema 字段合法)。
 */
import * as fs from 'fs';
import * as path from 'path';
import * as yaml from 'js-yaml';
import { classifyByKeyword } from '../src/modules/sync/assembler/assembler-engine';
import { AssemblerConfigSchema } from '../src/modules/sync/assembler/assembler.schema';
import { resolveDictIncludes } from '../src/modules/sync/assembler/dict-include';

function loadCfg(file: string) {
  const p = path.join(__dirname, '..', 'data', 'jvs-dw', 'assemblers', file);
  // 走生产同款加载:dict_includes(共享字典分层)在 zod 校验前解析合并
  const parsed = resolveDictIncludes(yaml.load(fs.readFileSync(p, 'utf-8')), p);
  const r = AssemblerConfigSchema.safeParse(parsed);
  if (!r.success) {
    throw new Error(`${file} schema invalid:\n${JSON.stringify(r.error.issues, null, 2)}`);
  }
  return r.data;
}

describe('treatment_actual keyword classifier (real jvs-dw yaml)', () => {
  const cfg = loadCfg('treatment_actual.yaml');
  const rules = cfg.keyword_mapping!.category!;
  const strip = cfg.keyword_strip_clauses;

  // [术式(已 normalize:ASCII 标点), 期望 category 或 undefined(落 _default), 解决的问题类]
  const cases: Array<[string, string | undefined, string]> = [
    ['洁牙+喷砂', 'periodontic', 'A 加号复合'],
    ['龈上洁治+局部刮治', 'periodontic', 'A'],
    ['龈上洁治术+喷砂', 'periodontic', 'A 后缀变体(术)'],
    ['根管治疗,冠修复', 'endodontic', 'B 复合优先级 根管>冠'],
    ['种植冠/桥修复(多颗)', 'implant', 'B 种植>冠'],
    ['根管治疗+桩冠修复', 'endodontic', 'B'],
    ['洗牙', 'periodontic', 'C 同义口语'],
    ['细洁', 'periodontic', 'C'],
    ['定期涂氟', 'preventive', 'C'],
    ['拟涂氟,已告知家长及患者病情', 'preventive', 'D 剥离备注噪音(已告知)'],
    ['窝沟封闭。已告知家长', 'preventive', 'D'],
    ['充填,必要时根管治疗', 'restorative', 'E 负向词:剥离"必要时根管"→充填'],
    ['戴贴面', 'cosmetic', '贴面先于冠'],
    ['乳牙拔除术', 'surgical', '操作词(拔除)>人群词(乳牙)'],
    ['儿童早期矫治', 'orthodontic', '操作词(矫治)>人群词(儿童)'],
    ['活动义齿修复', 'prosthodontic', ''],
    ['全瓷冠修复', 'prosthodontic', ''],
    // —— 噪音 / 无操作 → undefined(落 _default 丢弃,不误判成治疗)——
    ['定期观察,必要时拔除', undefined, 'E:剥离"必要时拔除"→只剩观察→不误判 surgical'],
    ['无需处理', undefined, '无操作'],
    ['不予处理(无间隙)', undefined, '无操作'],
    ['未指定治疗项', undefined, '无操作'],
    ['拍照口扫,反馈给加工单位,调整治疗计划', undefined, '流程'],
  ];

  test.each(cases)('「%s」→ %s', (input, expected) => {
    expect(classifyByKeyword(input, rules, strip)).toBe(expected);
  });

  it('keyword_mapping/strip 字段被 schema 接受且非空', () => {
    expect(rules.length).toBeGreaterThan(5);
    expect(strip).toContain('必要时');
  });
});

describe('treatment_planned keyword classifier (计划:不剥建议/拟)', () => {
  const cfg = loadCfg('treatment_planned.yaml');
  const rules = cfg.keyword_mapping!.category!;
  const strip = cfg.keyword_strip_clauses;

  it('「建议拔除智齿」→ surgical(planned 保留"建议")', () => {
    expect(classifyByKeyword('建议拔除智齿', rules, strip)).toBe('surgical');
  });

  it('「建议全口洁治后,必要时正畸治疗」→ periodontic(剥"必要时正畸"留洁治)', () => {
    expect(classifyByKeyword('建议全口洁治后,必要时正畸治疗', rules, strip)).toBe('periodontic');
  });

  it('planned strip 不含"建议"', () => {
    expect(strip).not.toContain('建议');
    expect(strip).toContain('必要时');
  });
});
