import { Injectable } from '@nestjs/common';
import {
  PersonaFeatureKey,
  FactType,
  FactKind,
  lookupDxTreatment,
  treatmentCategoryNameZh,
} from '@pac/types';
import type {
  FeatureExtractor,
  FeatureExtractorContext,
  PersonaFeatureDraft,
  ActiveFact,
} from './feature.interface';

/**
 * treatment_chain_status 治疗链状态(v2.1 — 读独立 diagnosis_record / treatment_record)
 *
 * v2.1 重塑(对齐 canonical-fact-layer.md §四):
 *   不再读 encounter_record.content.treatments[] 嵌套数组,改读独立 fact:
 *     - diagnosis_record(kind=actual)— 病人有哪些诊断
 *     - treatment_record(kind=actual)— 病人做过哪些治疗
 *     - treatment_record(kind=planned)— 计划但未执行的治疗
 *
 * 规则(v1):
 *   - 无 diagnosis 且无 treatment              → 'no_chain'(从未进入)
 *   - 有 treatment.kind=actual status=active   → 'in_progress'(在管)
 *   - 有 diagnosis 但相应类别无 actual treatment → 'gap'(链有缺口,召回核心信号)
 *   - 所有 diagnosis 都有对应已完成 treatment   → 'closed'
 *
 * "对应"通过 PACDiagnosisCode → PACTreatmentCategory 映射(集中此处):
 *   K02(龋病)→ restorative(充填)
 *   K04(牙髓/根尖周)→ endodontic(根管)
 *   K05(牙周炎)→ periodontic(牙周治疗)
 *   K08(缺牙)→ implant / prosthodontic(种植/修复)
 *   IMPLANT_RECOMMENDED → implant
 */
@Injectable()
export class TreatmentChainStatusFeatureExtractor implements FeatureExtractor {
  readonly key = PersonaFeatureKey.TREATMENT_CHAIN_STATUS;

  // 诊断码 → 期望治疗 category 用单一真理源 canonical-codes.DiagnosisTreatmentMap

  extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null {
    const diagnoses = ctx.factsByType.get(FactType.DIAGNOSIS_RECORD) ?? [];
    const treatments = ctx.factsByType.get(FactType.TREATMENT_RECORD) ?? [];
    const recommendations =
      ctx.factsByType.get(FactType.RECOMMENDATION_RECORD) ?? [];

    if (
      diagnoses.length === 0 &&
      treatments.length === 0 &&
      recommendations.length === 0
    ) {
      return {
        key: this.key,
        description: '从未进入治疗链(无诊断 / 治疗 / 建议记录)',
        score: 0,
        evidence: { factIds: [] },
      };
    }

    // 已做的治疗按 category 索引
    const actualByCat = new Map<string, ActiveFact[]>();
    let inProgressCount = 0;
    for (const tx of treatments) {
      if (tx.kind !== FactKind.ACTUAL) continue;
      const c = tx.content as Record<string, unknown>;
      const cat = String(c.category ?? '');
      const status = String(c.status ?? '');
      if (status === 'in_progress' || tx.status === 'active') {
        // active 但 content.status 未明 in_progress 也算进行中(DW 数据兜底)
        if (status === 'in_progress' || status === 'active' || !status) {
          inProgressCount++;
        }
      }
      if (!cat) continue;
      const arr = actualByCat.get(cat) ?? [];
      arr.push(tx);
      actualByCat.set(cat, arr);
    }

    // 找出"诊断 + 推荐但无对应治疗"的缺口
    // gap 字符串短化(旧版含全口 26 颗牙位 + raw enum,UI 严重溢出):
    //   "K05/11;12;13;...37 → 未做 periodontic"(60+ 字符)
    //   → "牙周治疗 全口 26 牙"(短 + 用 chainLabel 中文,牙位 formatTooth 截断)
    const gaps: string[] = [];
    const factIds: string[] = [];
    const signalSources = [...diagnoses, ...recommendations];
    for (const sig of signalSources) {
      factIds.push(sig.id);
      const c = sig.content as Record<string, unknown>;
      const code = String(c.code ?? '');
      const tooth = String(c.tooth_position ?? '');
      const rule = lookupDxTreatment(code);
      const expectedCats = rule?.categories;
      if (!expectedCats) continue;
      const hasFollowup = expectedCats.some((cat) => actualByCat.has(cat));
      if (!hasFollowup) {
        const chainLabel = rule?.chainLabel ?? treatmentCategoryNameZh(expectedCats[0]!) ?? code;
        // normalize 后再生成 gap 文本 — 跟 chain-composer 桶分一致:
        // 同 N 颗牙不同顺序("17;47;37" / "47;17;37")算 1 个 gap,不重复
        const normalizedTooth = rule?.wholeMouth
          ? '全口'
          : formatToothShort(normalizeTooth(tooth));
        gaps.push(`${chainLabel} ${normalizedTooth}`);
      }
    }
    for (const tx of treatments) factIds.push(tx.id);

    let status: string;
    let description: string;
    let score: number;
    if (gaps.length > 0) {
      status = 'gap';
      // 去重(同 K 码 + 牙位的多次诊断 / 推荐 → 同一 gap;UI 只显示 distinct)
      const dedupGaps = [...new Set(gaps)];
      description = `${dedupGaps.length} 处缺口 — ${dedupGaps.slice(0, 3).join('、')}`;
      score = 3;
    } else if (inProgressCount > 0) {
      status = 'in_progress';
      description = `治疗链进行中(${inProgressCount} 项治疗在进行)`;
      score = 1;
    } else if (treatments.length > 0) {
      status = 'closed';
      description = `治疗链已闭环(${treatments.length} 项治疗已完成)`;
      score = 2;
    } else {
      // 只有诊断/推荐,无任何 treatment — 视为新链待启动
      status = 'gap';
      description = `仅有诊断 / 推荐,未启动任何治疗(${signalSources.length} 个信号)`;
      score = 3;
    }

    return {
      key: this.key,
      description: `[${status}] ${description}`,
      score,
      evidence: { factIds },
    };
  }
}

/// 牙位规整化(去 trim + 去重 + 字典序)— 跟 chain-composer.normalizeTooth 同语义,
/// 让"17;47;37" / "47;17;37" 算同一个 gap,不因 host 录入顺序差异重复
function normalizeTooth(s: string): string {
  if (!s.trim()) return '';
  return Array.from(new Set(s.split(';').map((t) => t.trim()).filter(Boolean)))
    .sort()
    .join(';');
}

/// 牙位短化:>20 颗 → 全口 N 牙;≤4 颗 → 完整列;否则截前 4 + 等 N 颗
function formatToothShort(tooth: string, wholeMouthDefault?: boolean): string {
  if (!tooth.trim()) return wholeMouthDefault ? '全口' : '未标注牙位';
  const list = tooth.split(';').map((s) => s.trim()).filter(Boolean);
  if (list.length === 0) return '未标注牙位';
  if (list.length >= 20) return `全口 ${list.length} 牙`;
  if (list.length <= 4) return list.join(';');
  return `${list.slice(0, 4).join(';')} 等 ${list.length} 颗`;
}
