'use client';

import { lookupDxTreatment, NO_RESTORATION_GAP_EXAM_PATTERNS } from '@pac/types';
import { cn } from '@/lib/utils';
import type { AdaptedFact } from './adapt-data';
import { factLabel } from './fact-label';

/**
 * ToothTimeline — 每颗牙(+ 全口治疗线)的事实时间轴
 *
 * 设计:零推断、忠实展示。把临床 fact 按"治疗问题"分泳道:
 *   - 按牙的病 → 泳道键 = 牙位(多牙位 fact 拆进每颗牙)
 *   - 全口的病 → 泳道键 = 治疗线类别(牙周 / 正畸 / 全口·其他),按 category/诊断码分桶
 * 每条泳道内 = 时间倒序的事实流。不做 lifecycle / 合并 / 替代闭环(那些归召回 SQL)。
 */
const CLINICAL_TYPES = new Set([
  'diagnosis_record',
  'treatment_record',
  'recommendation_record',
  'image_record',
]);

const WHOLE_PERIO = '全口 · 牙周';
const WHOLE_ORTHO = '全口 · 正畸';
const WHOLE_OTHER = '全口 · 其他';

export function ToothTimeline({ facts }: { facts: AdaptedFact[] }) {
  const clinical = facts.filter((f) => CLINICAL_TYPES.has(f.type));
  if (clinical.length === 0) {
    return <div className="text-center py-12 text-sm text-slate-400">无牙位事实</div>;
  }

  // 固定桥"桥体覆盖":修复(prosthodontic)治疗记在【基牙】上(如 25;27),桥体牙(26)自己泳道
  //   看不到这座桥 → 像没修。这里按几何补:同象限基牙之间的缺失位 = 桥体,在那颗牙补一行
  //   "已由 X-Y 固定桥覆盖"(同后端 btx 桥区间口径:基牙间缺失位由桥覆盖)。
  const bridgeNote = new Map<string, string>(); // 桥体牙位 → 跨度"25-27"
  for (const f of facts) {
    if (f.type !== 'treatment_record') continue;
    if (String((f.content ?? {}).category ?? '') !== 'prosthodontic') continue;
    const nums = factTeeth(f)
      .map((t) => parseInt(t, 10))
      .filter((n) => !Number.isNaN(n));
    if (nums.length < 2) continue;
    // 按象限(十位)分组;组内 min..max 间的缺失位即桥体
    const byQuad = new Map<number, number[]>();
    for (const n of nums) {
      const q = Math.floor(n / 10);
      (byQuad.get(q) ?? byQuad.set(q, []).get(q)!).push(n);
    }
    for (const ts of byQuad.values()) {
      if (ts.length < 2) continue;
      const sorted = [...ts].sort((a, b) => a - b);
      const lo = sorted[0]!;
      const hi = sorted[sorted.length - 1]!;
      const present = new Set(sorted);
      for (let n = lo + 1; n < hi; n++) {
        if (!present.has(n) && !bridgeNote.has(String(n))) {
          bridgeNote.set(String(n), `${lo}-${hi}`);
        }
      }
    }
  }

  // 检查所见"缺牙间隙关闭/无修复间隙"→ 每颗牙的无修复注记(同后端 gap a''' 口径)。
  //   信息在 emr_record.exam_findings 文本里(本视图诊断/治疗都看不到)→ 补一行让客服懂为何不跟进。
  const noRestorNote = new Map<string, string>();
  const noRestorRe = new RegExp(NO_RESTORATION_GAP_EXAM_PATTERNS.join('|'));
  for (const f of facts) {
    if (f.type !== 'emr_record') continue;
    for (const seg of parseJsonArrayLoose((f.content ?? {}).exam_findings)) {
      const msg = String(seg.message ?? '');
      if (!/缺[牙失]/.test(msg) || !noRestorRe.test(msg)) continue;
      for (const t of String(seg.toothPosition ?? '')
        .split(/[;,]+/)
        .map((x) => toothBase(x.trim()))
        .filter(Boolean)) {
        if (!noRestorNote.has(t)) noRestorNote.set(t, msg.trim());
      }
    }
  }

  // 分桶
  const lanes = new Map<string, AdaptedFact[]>();
  const push = (k: string, f: AdaptedFact) => {
    const arr = lanes.get(k) ?? [];
    arr.push(f);
    lanes.set(k, arr);
  };
  for (const f of clinical) {
    // ⭐ 全口病(牙周 K05 / 正畸 K07 诊断、periodontic/orthodontic 治疗)无视 dx 自带牙位,
    //   直接进全口泳道 —— 跟召回 wholeMouth 口径一致。否则慢性牙龈炎(全口诊断列了 28 颗牙)
    //   会被拆进每颗牙,让 11/12/13 等只有全口病的牙位凭空出现单独泳道(显示假象)。
    if (isWholeMouthFact(f)) {
      push(wholeMouthLane(f), f);
      continue;
    }
    const teeth = factTeeth(f);
    if (teeth.length > 0) {
      for (const t of teeth) push(t, f);
    } else {
      push(wholeMouthLane(f), f);
    }
  }

  // 排序:牙位泳道(数值序)→ 牙周 → 正畸 → 全口其他
  const toothKeys = [...lanes.keys()]
    .filter((k) => ![WHOLE_PERIO, WHOLE_ORTHO, WHOLE_OTHER].includes(k))
    .sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
  const wholeKeys = [WHOLE_PERIO, WHOLE_ORTHO, WHOLE_OTHER].filter((k) => lanes.has(k));
  const orderedKeys = [...toothKeys, ...wholeKeys];

  const tkey = (f: AdaptedFact) => {
    const t = f.occurredAt ?? f.plannedFor;
    return t ? new Date(t).getTime() : -Infinity;
  };
  // 临床顺序 rank:诊断 1 → 影像 2 → 建议 3 → 计划 4 → 治疗 5。
  // 整体时间倒序(新→旧),同一时刻也跟着倒:治疗 → 计划 → 建议 → 影像 → 诊断。
  const typeRank = (f: AdaptedFact) => {
    if (f.type === 'diagnosis_record') return 1;
    if (f.type === 'image_record') return 2;
    if (f.type === 'recommendation_record') return 3;
    if (f.type === 'treatment_record') return f.kind === 'planned' ? 4 : 5;
    return 6;
  };

  return (
    <div className="space-y-2.5">
      {orderedKeys.map((k) => {
        const rows = [...lanes.get(k)!].sort(
          (a, b) => tkey(b) - tkey(a) || typeRank(b) - typeRank(a),
        );
        const isWhole = k.startsWith('全口');
        return (
          <div key={k} className="rounded-lg border overflow-hidden">
            <div
              className={cn(
                'flex items-center gap-2 px-2.5 py-1.5 text-[12px] font-semibold border-b',
                isWhole ? 'bg-amber-50 text-amber-800' : 'bg-slate-50 text-slate-700',
              )}
            >
              <span className="tabular-nums">{isWhole ? k : `牙位 ${k}`}</span>
              <span className="ml-auto text-[10px] font-normal text-slate-400">{rows.length} 项</span>
            </div>
            <div className="divide-y">
              {rows.map((f) => (
                <ToothFactRow key={`${k}-${f.id}`} fact={f} />
              ))}
              {!isWhole && bridgeNote.has(k) && (
                <div className="flex items-baseline gap-2 px-2.5 py-1.5 text-[12px] bg-brand-50/40">
                  <span className="w-[74px] flex-none" />
                  <span className="flex-none px-1.5 py-px rounded text-[10px] font-medium bg-brand-50 text-brand-700">
                    桥体
                  </span>
                  <span className="flex-1 min-w-0 text-slate-600 leading-snug">
                    已由 {bridgeNote.get(k)} 固定桥覆盖(桥体)
                  </span>
                </div>
              )}
              {!isWhole && noRestorNote.has(k) && (
                <div className="flex items-baseline gap-2 px-2.5 py-1.5 text-[12px] bg-slate-50/60">
                  <span className="w-[74px] flex-none" />
                  <span className="flex-none px-1.5 py-px rounded text-[10px] font-medium bg-slate-100 text-slate-500">
                    非修复
                  </span>
                  <span className="flex-1 min-w-0 text-slate-500 leading-snug" title={noRestorNote.get(k)}>
                    检查所见:{noRestorNote.get(k)}(无修复指征)
                  </span>
                </div>
              )}
            </div>
          </div>
        );
      })}
    </div>
  );
}

function ToothFactRow({ fact }: { fact: AdaptedFact }) {
  const tIso = fact.occurredAt ?? fact.plannedFor;
  const planned = !fact.occurredAt && !!fact.plannedFor;
  const dateStr = tIso
    ? (planned ? '约 ' : '') + new Date(tIso).toLocaleDateString('zh-CN').replace(/\//g, '.')
    : '—';
  const { badge, badgeTone, text } = factLabel(fact);
  return (
    <div className="flex items-baseline gap-2 px-2.5 py-1.5 text-[12px]">
      <span className="w-[74px] flex-none tabular-nums text-[10.5px] text-slate-400">{dateStr}</span>
      <span
        className={cn(
          'flex-none px-1.5 py-px rounded text-[10px] font-medium',
          badgeTone,
        )}
      >
        {badge}
      </span>
      <span className="flex-1 min-w-0 text-slate-800 leading-snug" title={text}>
        {text}
      </span>
    </div>
  );
}


/// 牙位归一(剥面/方位后缀,保留牙位 base);跟 facts-timeline 同义
function parseJsonArrayLoose(raw: unknown): Array<{ toothPosition?: string; message?: string }> {
  if (Array.isArray(raw)) return raw as Array<{ toothPosition?: string; message?: string }>;
  if (typeof raw !== 'string' || !raw.trim() || raw === 'null') return [];
  try {
    const p = JSON.parse(raw);
    return Array.isArray(p) ? p : [];
  } catch {
    return [];
  }
}

function toothBase(t: string): string {
  const m = /^(\d{1,2}[A-E]?)/i.exec(t.trim());
  return m ? m[1]!.toUpperCase() : t.trim().toUpperCase();
}

function factTeeth(f: AdaptedFact): string[] {
  const c = (f.content ?? {}) as Record<string, unknown>;
  const tp = c.tooth_positions;
  const raw = Array.isArray(tp) ? tp.join(';') : String(c.tooth_position ?? tp ?? '');
  return raw
    .split(/[;,]+/)
    .map((s) => s.trim())
    .filter(Boolean)
    .map(toothBase);
}

/// 全口事实分桶:牙周(periodontic/K05)/ 正畸(orthodontic/K07)/ 其他(预防/检查…)
function wholeMouthLane(f: AdaptedFact): string {
  const c = (f.content ?? {}) as Record<string, unknown>;
  const cat = String(c.category ?? '');
  const code = String(c.code ?? '');
  if (cat === 'periodontic' || code === 'K05') return WHOLE_PERIO;
  if (cat === 'orthodontic' || code === 'K07') return WHOLE_ORTHO;
  return WHOLE_OTHER;
}

/// 全口病:无视 fact 自带牙位,归全口泳道(跟召回 wholeMouth 口径一致)。
///   - 诊断/建议:code 的 wholeMouth 标志(K05 牙周 / K07 正畸 / SRP_/ORTHO_CONSULT_RECOMMENDED)
///   - 治疗:category = periodontic / orthodontic(洁牙/刮治/正畸 全口性)
/// 方案 A(正畸=全牙弓):正畸/牙周治疗一律全口,即便标了局部牙位也归全口泳道 —— 与"诊断按
///   code 判全口"对齐(K07/K05 诊断 + 同科治疗都落全口,不分叉)。前提:正畸/牙周诊断已正确编码
///   (diagnosis.yaml keyword 兜底把"牙列间隙/错牙合/开颌…"补成 K07),否则空 code 诊断仍会按牙分叉。
/// K06(牙龈/牙槽嵴)非 wholeMouth → 仍按牙;K02 龋等结构病按牙。
function isWholeMouthFact(f: AdaptedFact): boolean {
  const c = (f.content ?? {}) as Record<string, unknown>;
  if (f.type === 'treatment_record') {
    const cat = String(c.category ?? '');
    return cat === 'periodontic' || cat === 'orthodontic';
  }
  if (f.type === 'diagnosis_record' || f.type === 'recommendation_record') {
    const code = String(c.code ?? '');
    return !!lookupDxTreatment(code)?.wholeMouth;
  }
  return false;
}
