import { Injectable, Logger } from '@nestjs/common';
import {
  Action,
  FactKind,
  FactStatus,
  FactType,
  PACDiagnosisCodeSchema,
} from '@pac/types';
import type { Parser, ParserContext, FactDraft } from './parser.interface';
import { normalizeToothPosition } from './tooth-position.util';

/**
 * DiagnosisParser — `diagnosis_recorded` 解析器(v2.1 新)
 *
 * 1 个 canonical diagnosis row → 1 个 `diagnosis_record` fact(独立 fact_type)。
 *
 * 算法语义(canonical-fact-layer.md §四.7):
 *   "找所有有 K08 诊断且没启动 implant 治疗的患者"
 *   → SELECT FROM patient_facts WHERE type='diagnosis_record' AND content->>'code'='K08'
 *
 * **code 可空(W3 末改)**:host ~98% 诊断没填 ICD 码,yaml 中文白名单也翻不全。
 *   - code ∈ PACDiagnosisCodes  → 正常落码(code_source: std_code / name_map)
 *   - code 翻不出(enum_mapping _default "")→ **仍落 fact**,code=null,保 name_zh 原文,
 *     留给 Layer C LLM 后续补码(不再 skip 丢数据)。
 *   - code_source 溯源:有 stdCodeRaw → 'std_code';否则 code 有值 → 'name_map';都没有 → null。
 */
@Injectable()
export class DiagnosisParser implements Parser {
  readonly name = Action.DIAGNOSIS_RECORDED;
  private readonly logger = new Logger(DiagnosisParser.name);

  parse(ctx: ParserContext): FactDraft[] {
    const c = ctx.canonicalRow;
    const externalId = String(c.externalId ?? ctx.transaction.subjectId).trim();
    if (!externalId) {
      this.logger.warn(
        `diagnosis parser: missing externalId for transaction=${ctx.transaction.id};skip`,
      );
      return [];
    }

    // code 校验:命中 PAC 闭集 → 用;否则置 null(不丢 fact,留 message 给 LLM 补码)
    const rawCode = String(c.code ?? '').trim();
    const codeParsed = PACDiagnosisCodeSchema.safeParse(rawCode);
    const code = codeParsed.success ? codeParsed.data : null;

    // code_source 溯源:① 上游显式声明(如影像 AI codeSource='image_ai')优先;
    //   ② 否则按 stdCode 有无推断(std_code / name_map);③ 无码 → null。通用,非宿主特化。
    const explicitSource = String(c.codeSource ?? '').trim();
    const hasStdCode = !!String(c.stdCodeRaw ?? '').trim();
    const codeSource: string | null = explicitSource
      ? explicitSource
      : code
        ? hasStdCode
          ? 'std_code'
          : 'name_map'
        : null;

    const toothPosition = normalizeToothPosition(c.toothPosition as string | undefined);
    const sourceEncounter = (c.sourceEncounterExternalId as string | undefined) ?? null;
    // host 录入习惯尾部带标点("慢性牙龈炎;" / "牙周炎。" / "牙龈炎,"  / "龋齿？" / "缺失？"...)
    // 在 parser 层一次性清洗:首尾空白 + 尾部中英文标点 → 下游 chain-composer / WhyCard / persona 全部受益
    const nameZh = cleanName((c.name as string | undefined) ?? null);

    return [
      {
        subjectId: `${FactType.DIAGNOSIS_RECORD}:${externalId}`,
        kind: FactKind.ACTUAL,
        type: FactType.DIAGNOSIS_RECORD,
        status: FactStatus.ACTIVE,
        clinicId: ctx.transaction.clinicId,
        occurredAt: ctx.transaction.occurredAt,
        title: `诊断 ${code ?? nameZh ?? '未定码'}${toothPosition ? ` · 牙位 ${toothPosition}` : ''}`,
        summary: nameZh,
        content: {
          code,
          code_source: codeSource,
          name_zh: nameZh,
          tooth_position: toothPosition,
          severity: (c.severity as string | undefined) ?? null,
          onset_date: (c.onsetDate as string | undefined) ?? null,
          doctor_id: c.doctorId ? String(c.doctorId) : null, // host user_id Int64 → string
          doctor_name: (c.doctorName as string | undefined) ?? null,
          source_encounter_external_id: sourceEncounter ?? externalId,
        },
      },
    ];
  }
}

/// 诊断名清洗:首尾空白 + 尾部标点(中英文,含 host 常见的 ;。,；,？?!! 等)
/// 例:" 慢性牙龈炎；" → "慢性牙龈炎"
function cleanName(s: string | null): string | null {
  if (!s) return null;
  // 1. 首尾空白(含全角空格)
  // 2. 尾部标点 — 中英文混合:;。,;.,？?!!:：、
  const cleaned = s
    .trim()
    .replace(/[\s　;。,;.,？?!!:：、]+$/g, '')
    .trim();
  return cleaned || null;
}
