'use client';

import { useState, useMemo } from 'react';
import {
  Stethoscope,
  Pill,
  ClipboardList,
  UserRound,
  FileText,
  Image as ImageIcon,
  Calendar,
  CircleDollarSign,
  Undo2,
  MessageCircle,
  Receipt,
  Wallet,
  AlertTriangle,
  Users,
  AlertOctagon,
} from 'lucide-react';
import { diagnosisCodeNameZh, treatmentCategoryNameZh } from '@pac/types';
import { cn, formatToothPosition } from '@/lib/utils';
import { Chip } from './shared';
import type { AdaptedFact } from './adapt-data';

/**
 * FactsTimeline — 患者事实时间轴(drawer 用)
 *
 * 新版改进(W3 末):
 *   ① 顶部加 fact_type 多选筛选(chip 切换,显示该 type 计数)
 *   ② EMR / 影像节点 value 重写(原"病历 ad575..."无意义,改"主诉" / "modality + 牙位")
 *   ③ 图标走 lucide-react(替代 inline SVG path,更易维护)
 *   ④ 按 occurredAt ?? plannedFor 倒序;planned 加"约"前缀
 */
export function FactsTimeline({ facts }: { facts: AdaptedFact[] }) {
  if (facts.length === 0) {
    return <div className="text-center py-12 text-sm text-slate-400">无 fact</div>;
  }

  // ─ 按 type 统计 + 默认全选 ─
  const typeCounts = useMemo(() => {
    const c = new Map<string, number>();
    for (const f of facts) c.set(f.type, (c.get(f.type) ?? 0) + 1);
    return c;
  }, [facts]);
  const allTypes = useMemo(() => [...typeCounts.keys()].sort(), [typeCounts]);

  const [selected, setSelected] = useState<Set<string>>(() => new Set(allTypes));
  const allOn = selected.size === allTypes.length;
  const toggleAll = () => setSelected(allOn ? new Set() : new Set(allTypes));
  const toggleType = (t: string) =>
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(t)) next.delete(t);
      else next.add(t);
      return next;
    });

  // 时间轴排序键:actual 用 occurredAt;planned 用 plannedFor 兜底
  const tkey = (f: AdaptedFact) => {
    const t = f.occurredAt ?? f.plannedFor;
    return t ? new Date(t).getTime() : -Infinity;
  };
  const sorted = useMemo(
    () => [...facts].filter((f) => selected.has(f.type)).sort((a, b) => tkey(b) - tkey(a)),
    [facts, selected],
  );

  return (
    <div className="space-y-3">
      {/* 结构化统计摘要 — 资金 / 临床 / 行为 / 时间窗 四块,一眼看清患者画像 */}
      <StatsSummary facts={facts} />

      {/* 类型多选筛选 chip 行 */}
      <div className="flex flex-wrap items-center gap-1.5 pb-2 border-b border-slate-100">
        <FilterChip label="全部" count={facts.length} active={allOn} onClick={toggleAll} />
        <span className="text-slate-200 mx-0.5">|</span>
        {allTypes.map((t) => {
          const meta = FACT_META[t] ?? FACT_META_FALLBACK;
          return (
            <FilterChip
              key={t}
              label={meta.label}
              count={typeCounts.get(t) ?? 0}
              active={selected.has(t)}
              tone={meta.tone}
              onClick={() => toggleType(t)}
            />
          );
        })}
      </div>

      {/* 选中类型为空的提示 */}
      {sorted.length === 0 && (
        <div className="text-center py-12 text-sm text-slate-400">已勾选类型无事实</div>
      )}

      {/* 时间轴 */}
      {sorted.length > 0 && (
        <div className="relative pl-6">
          <div className="absolute left-[10px] top-1 bottom-1 w-px bg-slate-200" />
          {sorted.map((f) => (
            <TimelineRow key={f.id} fact={f} />
          ))}
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────
// StatsSummary — 4 块结构化聚合(资金 / 临床 / 行为 / 时间窗)
// ─────────────────────────────────────────────

function StatsSummary({ facts }: { facts: AdaptedFact[] }) {
  // ─ 资金 ─
  let paid = 0;
  let refunded = 0;
  for (const f of facts) {
    const c = (f.content ?? {}) as Record<string, unknown>;
    const cents = Number(c.amount_cents ?? 0);
    if (!Number.isFinite(cents)) continue;
    if (f.type === 'payment_record') paid += cents;
    else if (f.type === 'refund_record') refunded += Math.abs(cents);
  }
  const netCents = paid - refunded;

  // ─ 临床 ─ 诊断 top codes / 治疗 top categories
  const dxCodes = new Map<string, number>();
  const txCats = new Map<string, number>();
  for (const f of facts) {
    const c = (f.content ?? {}) as Record<string, unknown>;
    if (f.type === 'diagnosis_record') {
      const code = String(c.code ?? '');
      if (code) dxCodes.set(code, (dxCodes.get(code) ?? 0) + 1);
    } else if (f.type === 'treatment_record') {
      const cat = String(c.category ?? '');
      if (cat && cat !== 'review') txCats.set(cat, (txCats.get(cat) ?? 0) + 1);
    }
  }
  const dxTop = [...dxCodes.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
  const txTop = [...txCats.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);

  // ─ 行为 ─ 接诊 / 预约履约率
  const encounterCount = facts.filter((f) => f.type === 'encounter_record').length;
  const apptAll = facts.filter((f) => f.type === 'appointment_record');
  const apptArrived = apptAll.filter((f) => {
    const c = (f.content ?? {}) as Record<string, unknown>;
    const st = String(c.status ?? '');
    return st === 'arrived' || st === 'completed' || st === 'in_treatment';
  }).length;
  const apptShowRate = apptAll.length > 0 ? Math.round((apptArrived / apptAll.length) * 100) : null;

  // ─ 时间窗 ─ 首次 / 最近 / 跨度天数
  const allTimes = facts
    .map((f) => f.occurredAt ?? f.plannedFor)
    .filter((t): t is string => !!t)
    .map((t) => new Date(t).getTime())
    .filter((t) => !Number.isNaN(t));
  const firstT = allTimes.length ? Math.min(...allTimes) : null;
  const lastT = allTimes.length ? Math.max(...allTimes) : null;
  const spanDays = firstT && lastT ? Math.round((lastT - firstT) / 86400_000) : null;
  const fmtDate = (t: number | null) =>
    t ? new Date(t).toLocaleDateString('zh-CN').replace(/\//g, '.') : '—';

  return (
    <div className="rounded-lg border border-slate-200 bg-slate-50/60 p-2.5">
      <div className="grid grid-cols-4 gap-3">
        <StatBlock label="累计净消费">
          <div className="text-[13px] font-semibold text-slate-900 tabular-nums">
            ¥{(netCents / 100).toLocaleString()}
          </div>
          <div className="text-[10px] text-slate-500 tabular-nums">
            收款 ¥{(paid / 100).toLocaleString()}
            {refunded > 0 && (
              <span className="text-rose-600 ml-1">- ¥{(refunded / 100).toLocaleString()}</span>
            )}
          </div>
        </StatBlock>
        <StatBlock label={`诊断 ${dxCodes.size} 种`}>
          <div className="text-[11px] text-slate-700 leading-tight">
            {dxTop.length > 0 ? (
              dxTop.map(([code, n]) => (
                <span key={code} className="inline-block mr-1.5" title={code}>
                  <span className="font-medium">{diagnosisCodeNameZh(code)}</span>
                  <span className="text-slate-400 ml-0.5">×{n}</span>
                </span>
              ))
            ) : (
              <span className="text-slate-400">—</span>
            )}
          </div>
        </StatBlock>
        <StatBlock label={`治疗 ${txCats.size} 类`}>
          <div className="text-[11px] text-slate-700 leading-tight">
            {txTop.length > 0 ? (
              txTop.map(([cat, n]) => (
                <span key={cat} className="inline-block mr-1.5" title={cat}>
                  <span className="font-medium">{treatmentCategoryNameZh(cat)}</span>
                  <span className="text-slate-400 ml-0.5">×{n}</span>
                </span>
              ))
            ) : (
              <span className="text-slate-400">—</span>
            )}
          </div>
        </StatBlock>
        <StatBlock label={`接诊 ${encounterCount}`}>
          <div className="text-[11px] text-slate-700 leading-tight">
            预约 {apptAll.length}{' '}
            {apptShowRate != null && (
              <span className={cn('tabular-nums', apptShowRate >= 80 ? 'text-emerald-700' : 'text-amber-700')}>
                履约 {apptShowRate}%
              </span>
            )}
          </div>
          <div className="text-[10px] text-slate-500 tabular-nums">
            {fmtDate(firstT)} → {fmtDate(lastT)}
            {spanDays != null && spanDays > 0 && <span className="ml-1">· 跨 {spanDays} 天</span>}
          </div>
        </StatBlock>
      </div>
    </div>
  );
}

function StatBlock({ label, children }: { label: string; children: React.ReactNode }) {
  return (
    <div className="flex flex-col gap-0.5 min-w-0">
      <div className="text-[10px] text-slate-500 truncate">{label}</div>
      {children}
    </div>
  );
}

// ─────────────────────────────────────────────
// 单个 fact 时间轴行
// ─────────────────────────────────────────────

function TimelineRow({ fact }: { fact: AdaptedFact }) {
  const meta = FACT_META[fact.type] ?? FACT_META_FALLBACK;
  const Icon = meta.Icon;
  const T = toneOf(meta.tone);
  const { title, note } = factSummary(fact);

  // planned 事实(预约/计划治疗)显示 plannedFor;加"约"前缀以区分已发生
  const tIso = fact.occurredAt ?? fact.plannedFor;
  const dateStr = tIso
    ? (fact.occurredAt ? '' : '约 ') + new Date(tIso).toLocaleDateString('zh-CN').replace(/\//g, '.')
    : '—';

  return (
    <div className="relative pb-3">
      <span
        className={cn(
          'absolute -left-[22px] top-0.5 w-5 h-5 rounded-full flex items-center justify-center ring-2 ring-white',
          T.bg,
          T.text,
        )}
      >
        <Icon className="w-3 h-3" />
      </span>
      <div className="flex items-center gap-2 flex-wrap">
        <span className="text-[11px] text-slate-500 tabular-nums font-mono">{dateStr}</span>
        <Chip tone={meta.tone} size="xs">
          {meta.label}
        </Chip>
        {fact.status !== 'active' && (
          <Chip tone="slate" size="xs">
            {FACT_STATUS_ZH[fact.status] ?? fact.status}
          </Chip>
        )}
      </div>
      <div className="mt-0.5 text-[12.5px] font-medium text-slate-900">{title}</div>
      {note && <div className="text-[11px] text-slate-500 mt-0.5">{note}</div>}
    </div>
  );
}

// ─────────────────────────────────────────────
// 顶部筛选 chip
// ─────────────────────────────────────────────

function FilterChip({
  label,
  count,
  active,
  tone = 'slate',
  onClick,
}: {
  label: string;
  count: number;
  active: boolean;
  tone?: string;
  onClick: () => void;
}) {
  const T = toneOf(tone);
  return (
    <button
      type="button"
      onClick={onClick}
      className={cn(
        'inline-flex items-center gap-1 px-2 py-0.5 rounded text-[11px] border transition-colors',
        active
          ? cn(T.bg, T.text, T.border, 'font-medium')
          : 'bg-slate-50 text-slate-500 border-slate-200 hover:bg-slate-100',
      )}
    >
      <span>{label}</span>
      <span
        className={cn(
          'tabular-nums text-[10px] px-1 rounded',
          active ? 'bg-white/60' : 'bg-slate-200/60 text-slate-600',
        )}
      >
        {count}
      </span>
    </button>
  );
}

// ─────────────────────────────────────────────
// fact.type → 元信息(图标 + 中文 + 色)
// ─────────────────────────────────────────────

interface FactMeta {
  label: string;
  tone: string;
  Icon: React.ComponentType<{ className?: string }>;
}

const FACT_META: Record<string, FactMeta> = {
  diagnosis_record: { label: '诊断', tone: 'rose', Icon: AlertTriangle },
  treatment_record: { label: '治疗', tone: 'teal', Icon: Stethoscope },
  recommendation_record: { label: '医嘱', tone: 'indigo', Icon: Pill },
  encounter_record: { label: '接诊', tone: 'slate', Icon: UserRound },
  emr_record: { label: '病历', tone: 'sky', Icon: FileText },
  image_record: { label: '影像', tone: 'violet', Icon: ImageIcon },
  appointment_record: { label: '预约', tone: 'amber', Icon: Calendar },
  payment_record: { label: '收款', tone: 'emerald', Icon: CircleDollarSign },
  refund_record: { label: '退费', tone: 'rose', Icon: Undo2 },
  consultation_record: { label: '咨询', tone: 'sky', Icon: MessageCircle },
  visit_registration_record: { label: '挂号', tone: 'slate', Icon: ClipboardList },
  order_record: { label: '医嘱单', tone: 'emerald', Icon: Receipt },
  recharge_record: { label: '充值', tone: 'emerald', Icon: Wallet },
  complaint_record: { label: '投诉', tone: 'rose', Icon: AlertOctagon },
  referral_record: { label: '转介', tone: 'indigo', Icon: Users },
};

const FACT_META_FALLBACK: FactMeta = { label: '事实', tone: 'slate', Icon: FileText };

interface ToneCls {
  bg: string;
  text: string;
  border: string;
}

const TONE_FALLBACK: ToneCls = { bg: 'bg-slate-100', text: 'text-slate-700', border: 'border-slate-300' };

const TONE = {
  rose: { bg: 'bg-rose-100', text: 'text-rose-700', border: 'border-rose-200' },
  teal: { bg: 'bg-teal-100', text: 'text-teal-700', border: 'border-teal-200' },
  indigo: { bg: 'bg-indigo-100', text: 'text-indigo-700', border: 'border-indigo-200' },
  slate: TONE_FALLBACK,
  sky: { bg: 'bg-sky-100', text: 'text-sky-700', border: 'border-sky-200' },
  violet: { bg: 'bg-violet-100', text: 'text-violet-700', border: 'border-violet-200' },
  amber: { bg: 'bg-amber-100', text: 'text-amber-800', border: 'border-amber-200' },
  emerald: { bg: 'bg-emerald-100', text: 'text-emerald-700', border: 'border-emerald-200' },
} as const satisfies Record<string, ToneCls>;

const toneOf = (tone: string): ToneCls => (TONE as Record<string, ToneCls>)[tone] ?? TONE_FALLBACK;

const FACT_STATUS_ZH: Record<string, string> = {
  active: '当前',
  superseded: '已替代',
  cancelled: '已取消',
  fulfilled: '已完成',
  expired: '已过期',
  invalidated: '已失效',
};

const APPT_STATUS_ZH: Record<string, string> = {
  scheduled: '待就诊',
  rescheduled: '已改约',
  cancelled: '已取消',
  arrived: '已到诊',
  in_treatment: '就诊中',
  completed: '已完成',
  no_show: '爽约',
  walk_in: '现场加号',
};

const TX_STATUS_ZH: Record<string, string> = {
  planned: '计划中',
  in_progress: '进行中',
  completed: '已完成',
  cancelled: '已取消',
  failed: '失败',
};

const MODALITY_ZH: Record<string, string> = {
  pa: '根尖片',
  bw: '咬翼片',
  pano: '曲面断层',
  cbct: 'CBCT',
  intraoral_photo: '口内照片',
};

const CHANNEL_ZH: Record<string, string> = {
  cash: '现金',
  wechat: '微信',
  alipay: '支付宝',
  card: '银行卡',
  pos: 'POS',
  store: '储值',
  insurance: '保险',
  advance: '预付',
  membership_card: '会员卡',
  medical_insurance: '医保',
  apple_pay: 'Apple Pay',
  third_party: '第三方',
  check: '支票',
  debt: '挂账',
  mini_program: '小程序',
  other: '其他',
};

const APPT_TYPE_ZH: Record<string, string> = {
  first_visit: '初诊',
  follow_up: '复诊',
  emergency: '急诊',
  consultation: '咨询',
};

const ENCOUNTER_TYPE_ZH: Record<string, string> = {
  first_visit: '初诊',
  follow_up: '复诊',
  emergency: '急诊',
};

function hhmm(iso: string): string {
  const d = new Date(iso);
  return Number.isNaN(d.getTime())
    ? ''
    : d.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false });
}

function truncate(s: string, n: number): string {
  if (!s) return '';
  return s.length > n ? s.slice(0, n) + '…' : s;
}

// ─────────────────────────────────────────────
// fact 摘要文本(每个 type 一个 case)
// ─────────────────────────────────────────────

/**
 * fact 摘要文本(每个 type 自定义有意义字段;严格去重)
 *
 * 去重纪律(W3 末调):
 *   - **title 不重复 type 词**(左侧已有 type chip "诊断"/"治疗"/"预约"/"接诊"…)
 *     → title 直接显示具体内容(如 "慢性牙周炎"/"全口洁治"/"初诊·张三主诉"),省"诊断 / 治疗" 前缀
 *   - **note 不重复 fact.status**(右侧已有 "已替代/已取消/已完成/已过期/已失效" status chip 兜底显示)
 *     → 治疗 status 不再显示;预约 status 仅在非默认态(no_show/cancelled 等)时显示
 *
 * 各 type 显示选型:
 *   diagnosis : 中文病名 / 牙位 + 医生
 *   treatment : subtype / 牙位 + quantity unit + 医生
 *   encounter : encounter_type (初/复诊) + chief_complaint + 医生
 *   emr       : illness_desc 主诉 / 治疗计划 + 医生
 *   image     : modality 中文 / 牙位 + finding + 医生
 *   appointment: type 翻译 + 时间 + 主诉(+ 非默认 status / cancellation_reason if any)
 *   payment   : 金额 + channel 中文
 *   refund    : 金额 + reason
 *
 * 移除:doctor_id / *_external_id / source_encounter_id / code_source 等 raw / 技术字段
 */
function factSummary(f: AdaptedFact): { title: string; note: string } {
  const c = (f.content ?? {}) as Record<string, unknown>;
  const doctor = (c.doctor_name as string | undefined) ?? '';
  switch (f.type) {
    case 'diagnosis_record': {
      const code = (c.code as string) ?? '?';
      const name = (c.name_zh as string) ?? (c.name as string) ?? '';
      const tooth = (c.tooth_position as string) ?? '';
      return {
        title: name || code,
        note: [tooth && `牙位 ${formatToothPosition(tooth, 4)}`, doctor && `${doctor}医生`].filter(Boolean).join(' · '),
      };
    }
    case 'treatment_record': {
      const cat = (c.category as string) ?? '';
      const sub = (c.subtype as string) ?? '';
      const tooth = (c.tooth_position as string) ?? '';
      const qty = c.quantity != null ? Number(c.quantity) : null;
      const unit = (c.unit_name as string) ?? '';
      const qtyStr = qty != null && qty > 0 ? `${qty}${unit ? ' ' + unit : ''}` : '';
      // status 不在 note 显示:fact.status chip 已表达"已完成/已取消"
      return {
        title: sub || treatmentCategoryNameZh(cat) || '治疗',
        note: [qtyStr, tooth && `牙位 ${formatToothPosition(tooth, 4)}`, doctor && `${doctor}医生`].filter(Boolean).join(' · '),
      };
    }
    case 'recommendation_record': {
      const code = (c.code as string) ?? '?';
      const tooth = (c.tooth_position as string) ?? '';
      return {
        title: `建议 ${code}`,
        note: [tooth && `牙位 ${formatToothPosition(tooth, 4)}`, doctor && `${doctor}医生`].filter(Boolean).join(' · '),
      };
    }
    case 'encounter_record': {
      const etype = (c.encounter_type as string) ?? '';
      const chief = (c.chief_complaint as string) ?? '';
      const etypeZh = etype ? ENCOUNTER_TYPE_ZH[etype] ?? etype : '';
      // title 优先用 chief_complaint 主诉(最有信息),fallback 用初/复诊;不写"接诊"(chip 重复)
      const title = chief
        ? truncate(chief, 24)
        : etypeZh
          ? etypeZh
          : '到诊';
      return {
        title,
        note: [etypeZh && chief && etypeZh, doctor && `${doctor}医生`].filter(Boolean).join(' · '),
      };
    }
    case 'emr_record': {
      // 主诉(L1)+ 治疗计划/处置(L2)+ 医生 — raw id / pre_illness(跟 illness_desc 重复)移除
      // title 不再加"病历"前缀(chip 已表达)
      const ill = (c.illness_desc as string) ?? '';
      const plan = (c.treatment_plan as string) ?? '';
      const disp = parseFirstMessage(c.disposal);
      const planText = plan || disp;
      return {
        title: ill ? truncate(ill, 30) : '病历记录',
        note: [planText && `计划:${truncate(planText, 30)}`, doctor && `${doctor}医生`].filter(Boolean).join(' · '),
      };
    }
    case 'image_record': {
      const mod = (c.modality as string) ?? '';
      const finding = (c.finding as string) ?? '';
      const tooths = (c.tooth_positions as string | string[] | null) ?? null;
      const toothStr = Array.isArray(tooths) ? tooths.join(';') : tooths || '';
      const modZh = MODALITY_ZH[mod] ?? mod ?? '影像';
      return {
        title: modZh,
        note: [
          toothStr && `牙位 ${formatToothPosition(toothStr, 4)}`,
          finding && truncate(finding, 30),
          doctor && `${doctor}医生`,
        ]
          .filter(Boolean)
          .join(' · '),
      };
    }
    case 'appointment_record': {
      const at = (c.scheduled_at as string) ?? '';
      const type = (c.appointment_type as string) ?? '';
      const st = (c.status as string) ?? '';
      const cancel = (c.cancellation_reason as string) ?? '';
      const complaint = (c.complaint_text as string) ?? (c.complaint_category as string) ?? '';
      const time = at ? hhmm(at) : '';
      const typeZh = type ? APPT_TYPE_ZH[type] ?? type : '';
      // title 不加"预约"前缀(chip 已表达);用 type 翻译 + 时段
      // status 仅在非默认态(no_show / cancelled / rescheduled / arrived)时显示,避免普通"scheduled" 噪音
      const showStatus = st && st !== 'scheduled' && st !== 'completed';
      return {
        title: [typeZh, time].filter(Boolean).join(' · ') || '预约',
        note: [
          complaint && truncate(complaint, 20),
          showStatus && (APPT_STATUS_ZH[st] ?? st),
          cancel && `取消原因:${truncate(cancel, 16)}`,
        ]
          .filter(Boolean)
          .join(' · '),
      };
    }
    case 'payment_record': {
      // title 不加"收款"前缀(chip 已表达),直接金额;note 显示渠道翻译
      const cents = Number(c.amount_cents ?? 0);
      const ch = (c.channel as string) ?? '';
      const chZh = CHANNEL_ZH[ch] ?? ch;
      return { title: `¥${(cents / 100).toFixed(2)}`, note: chZh };
    }
    case 'refund_record': {
      const cents = Number(c.amount_cents ?? 0);
      const reason = (c.reason as string) ?? '';
      return { title: `¥${(cents / 100).toFixed(2)}`, note: reason };
    }
    default: {
      return { title: f.title ?? f.type, note: f.summary ?? '' };
    }
  }
}

/// 从 JSON 数组字段(EMR.disposal / treatment_plan 等)取第一条 message
function parseFirstMessage(raw: unknown): string {
  if (typeof raw !== 'string' || !raw || raw === 'null') return '';
  try {
    const arr = JSON.parse(raw);
    if (Array.isArray(arr) && arr[0] && typeof arr[0] === 'object') {
      return String((arr[0] as Record<string, unknown>).message ?? '');
    }
  } catch {
    /* swallow */
  }
  return '';
}
