'use client';

import { useCallback, useEffect, useState } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { ApiError } from '@/lib/api-client';
import { Maximize2, Minimize2 } from 'lucide-react';
import {
  recallDebugApi,
  type PatientCandidate,
  type RecallDebugReport,
  type SignalFunnel,
  type TraceReason,
} from './recall-debug-api';

type LoadState =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'ready'; data: RecallDebugReport }
  | { status: 'error'; message: string };

const GATE_ZH: Record<string, string> = {
  cooldown: '冷静期',
  already_treated: '已治疗',
  future_appointment: '未来预约',
  compliance: '合规闸',
};

function fmt(d: string | null): string {
  if (!d) return '—';
  return new Date(d).toLocaleString('zh-CN', {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  });
}

function fmtDay(d: string | null): string {
  if (!d) return '—';
  return new Date(d).toLocaleDateString('zh-CN');
}

const UUID_RE =
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

function errMsg(err: unknown): string {
  return err instanceof ApiError
    ? `${err.message}${err.code ? ` (${err.code})` : ''}`
    : err instanceof Error
      ? err.message
      : String(err);
}

export function RecallDebugApp() {
  const [input, setInput] = useState('');
  const [state, setState] = useState<LoadState>({ status: 'idle' });
  const [candidates, setCandidates] = useState<PatientCandidate[] | null>(null);
  const [searching, setSearching] = useState(false);

  const load = useCallback(async (pid: string) => {
    const id = pid.trim();
    if (!id) return;
    setCandidates(null);
    setState({ status: 'loading' });
    try {
      const data = await recallDebugApi.explain(id);
      setState({ status: 'ready', data });
    } catch (err) {
      setState({ status: 'error', message: errMsg(err) });
    }
  }, []);

  // uuid → 直查;否则按姓名/病历号/external_id 搜索,重名给列表挑选
  const submit = useCallback(
    async (term: string) => {
      const q = term.trim();
      if (!q) return;
      if (UUID_RE.test(q)) {
        void load(q);
        return;
      }
      setSearching(true);
      setCandidates(null);
      try {
        const list = await recallDebugApi.search(q);
        const only = list.length === 1 ? list[0] : undefined;
        if (only) void load(only.id);
        else setCandidates(list);
      } catch (err) {
        setState({ status: 'error', message: errMsg(err) });
      } finally {
        setSearching(false);
      }
    },
    [load],
  );

  // 深链:?patientId= 直接加载(研发可收藏 / 分享具体患者)
  useEffect(() => {
    const pid = new URLSearchParams(window.location.search).get('patientId');
    if (pid) {
      setInput(pid);
      void load(pid);
    }
  }, [load]);

  return (
    <main className="container mx-auto max-w-6xl space-y-4 p-6">
      <div>
        <h1 className="text-lg font-medium">召回归因调试</h1>
        <p className="text-sm text-muted-foreground">
          研发自查:一个患者「为什么召 / 为什么没召」。为什么召 = 倒读现有
          plan→reason→fact→transaction(T0 快照);为什么没召 = 引擎 ground truth +
          人话闸门漏斗(现在时定位)。
        </p>
      </div>

      <form
        className="flex gap-2"
        onSubmit={(e) => {
          e.preventDefault();
          void submit(input);
        }}
      >
        <Input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="姓名 / 病历号 / external_id / PAC id"
          className="max-w-md text-sm"
        />
        <Button type="submit" disabled={searching || state.status === 'loading'}>
          {searching ? '搜索中…' : state.status === 'loading' ? '查询中…' : '查询'}
        </Button>
      </form>

      {candidates && <CandidateList list={candidates} onPick={(id) => void load(id)} />}

      {state.status === 'error' && (
        <Card>
          <CardContent className="py-6 text-sm text-destructive">
            {state.message}
          </CardContent>
        </Card>
      )}

      {state.status === 'ready' && <Report data={state.data} />}
    </main>
  );
}

function CandidateList({
  list,
  onPick,
}: {
  list: PatientCandidate[];
  onPick: (id: string) => void;
}) {
  if (list.length === 0) {
    return (
      <Card>
        <CardContent className="py-6 text-sm text-muted-foreground">
          没找到匹配的患者。
        </CardContent>
      </Card>
    );
  }
  return (
    <Card>
      <CardContent className="py-3">
        <p className="mb-2 text-xs text-muted-foreground">
          {list.length} 个匹配,点选一个:
        </p>
        <div className="divide-y divide-border/60">
          {list.map((c) => (
            <button
              key={c.id}
              type="button"
              onClick={() => onPick(c.id)}
              className="flex w-full flex-wrap items-center gap-x-3 gap-y-1 rounded px-1 py-2 text-left text-sm hover:bg-muted/40"
            >
              <span className="font-medium">{c.name ?? '(无名)'}</span>
              {c.gender && <span className="text-muted-foreground">{c.gender}</span>}
              {c.age != null && <span className="text-muted-foreground">{c.age}岁</span>}
              <span className="font-mono text-xs text-muted-foreground">
                external {c.externalId}
              </span>
              {c.medicalRecordNumber && (
                <span className="font-mono text-xs text-muted-foreground">
                  病历 {c.medicalRecordNumber}
                </span>
              )}
              {!c.active && <Badge variant="destructive">已停用</Badge>}
            </button>
          ))}
        </div>
      </CardContent>
    </Card>
  );
}

function Report({ data }: { data: RecallDebugReport }) {
  const { patient, recalled, notRecalled, groundTruth, note } = data;
  return (
    <div className="space-y-4">
      <Card>
        <CardContent className="flex flex-wrap items-center gap-x-4 gap-y-2 py-4 text-sm">
          <span className="font-medium">{patient.name ?? '(无名)'}</span>
          <span className="font-mono text-muted-foreground">{patient.externalId}</span>
          {!patient.active && <Badge variant="destructive">已停用</Badge>}
          <Badge variant={groundTruth.engineHitCount > 0 ? 'default' : 'secondary'}>
            引擎 ground truth:{groundTruth.engineHitCount > 0 ? '可召' : '不召'}
            {groundTruth.hitSubKeys.length > 0 && ` · ${groundTruth.hitSubKeys.join(', ')}`}
          </Badge>
          <span className="text-muted-foreground">
            生成于 {fmt(data.computedAt)}
          </span>
          <span className="basis-full text-muted-foreground">{note}</span>
        </CardContent>
      </Card>

      <div className="grid gap-4 lg:grid-cols-2">
        <WhyRecalled data={data} />
        <WhyNot notRecalled={notRecalled} engineWouldRecall={recalled.engineWouldRecall} />
      </div>
    </div>
  );
}

function WhyRecalled({ data }: { data: RecallDebugReport }) {
  const { plans, hasActivePlan, engineWouldRecall } = data.recalled;
  const pick = (ps: typeof plans) => (ps.find((p) => p.isActive) ?? ps[0])?.planId ?? null;
  const [selectedId, setSelectedId] = useState<string | null>(pick(plans));
  // 患者切换 → plans 变了,重置到该患者的 active/最新版
  useEffect(() => {
    setSelectedId(pick(plans));
  }, [plans]);
  const selected = plans.find((p) => p.planId === selectedId) ?? plans[0] ?? null;

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">为什么召 · 证据链(T0)</CardTitle>
      </CardHeader>
      <CardContent className="space-y-4">
        {!hasActivePlan && (
          <p className="text-sm text-muted-foreground">
            无 active plan。
            {engineWouldRecall && ' 引擎判当下可召,等下次重算即生成 plan。'}
          </p>
        )}

        {plans.length > 1 && (
          <div className="flex flex-wrap items-center gap-1.5">
            <span className="text-xs text-muted-foreground">版本</span>
            {plans.map((p) => (
              <button
                key={p.planId}
                type="button"
                onClick={() => setSelectedId(p.planId)}
                className={`rounded px-2 py-0.5 text-xs ${
                  p.planId === selected?.planId
                    ? 'bg-primary text-primary-foreground'
                    : 'bg-muted text-muted-foreground hover:bg-muted/70'
                }`}
              >
                v{p.version}
                {p.isActive ? ' ·当前' : ''}
              </button>
            ))}
          </div>
        )}

        {selected && (
          <div className="space-y-3">
            <div className="flex items-center gap-2 text-sm">
              <Badge variant={selected.isActive ? 'default' : 'secondary'}>
                {selected.status}
                {!selected.isActive && ' · 历史'}
              </Badge>
              <span className="text-muted-foreground">v{selected.version}</span>
              <span className="font-medium">{selected.priorityScore.toFixed(0)} 分</span>
              <span className="text-muted-foreground">· {fmt(selected.generatedAt)}</span>
            </div>
            {selected.reasons.map((r, i) => (
              <ReasonTrace key={i} r={r} />
            ))}
          </div>
        )}
      </CardContent>
    </Card>
  );
}

function ReasonTrace({ r }: { r: TraceReason }) {
  return (
    <div className="rounded-lg border border-border/60 p-3 text-sm">
      <div className="flex items-center gap-2">
        <Badge variant="secondary">{r.subKey ?? r.scenario}</Badge>
        <span className="font-medium">{r.priorityScore.toFixed(0)} 分</span>
      </div>
      <p className="mt-1 text-muted-foreground">{r.reason}</p>
      <div className="mt-2 space-y-1.5">
        {r.evidence.map((f) => (
          <div key={f.factId} className="rounded-md bg-muted/40 px-2 py-1.5">
            <div className="flex flex-wrap items-center gap-x-2 text-xs">
              <span className="rounded bg-emerald-500/15 px-1.5 py-0.5 font-medium text-emerald-700 dark:text-emerald-300">
                {f.type ?? 'fact'}
              </span>
              {f.code && <span className="font-mono">{f.code}</span>}
              {f.version != null && (
                <span className="text-muted-foreground">v{f.version}</span>
              )}
              {f.status && f.status !== 'active' && (
                <span className="text-amber-600 dark:text-amber-400">{f.status}</span>
              )}
              {f.doctor && <span className="text-muted-foreground">{f.doctor}</span>}
              <span className="text-muted-foreground">{fmtDay(f.occurredAt)}</span>
            </div>
            {f.transactions.map((t) => (
              <TxRow key={t.transactionId} t={t} />
            ))}
          </div>
        ))}
        {r.evidence.length === 0 && (
          <p className="text-xs text-muted-foreground">该 reason 无 evidence.factIds。</p>
        )}
      </div>
    </div>
  );
}

function TxRow({
  t,
}: {
  t: {
    transactionId: string;
    sourceEventId: string | null;
    subjectType: string | null;
    occurredAt: string | null;
  };
}) {
  const [raw, setRaw] = useState<unknown | undefined>(undefined);
  const [loading, setLoading] = useState(false);
  const [err, setErr] = useState<string | null>(null);
  const [open, setOpen] = useState(false);

  const toggle = async () => {
    if (open) {
      setOpen(false);
      return;
    }
    setOpen(true);
    if (raw !== undefined || loading) return;
    setLoading(true);
    setErr(null);
    try {
      const r = await recallDebugApi.rawPayload(t.transactionId);
      setRaw(r.rawPayload);
    } catch (e) {
      setErr(
        e instanceof ApiError
          ? e.message
          : e instanceof Error
            ? e.message
            : String(e),
      );
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="mt-1 pl-3 text-xs text-muted-foreground">
      <button
        type="button"
        onClick={() => void toggle()}
        className="text-left hover:text-foreground"
      >
        ↳ transaction · sourceEventId{' '}
        <span className="font-mono">{t.sourceEventId ?? '—'}</span>
        {t.subjectType && ` · ${t.subjectType}`}
        <span className="ml-1 underline">{open ? '收起原始' : '看原始'}</span>
      </button>
      {open && (
        <div className="mt-1">
          {loading && <span>加载中…</span>}
          {err && <span className="text-destructive">{err}</span>}
          {raw !== undefined && <RawJson value={raw} />}
        </div>
      )}
    </div>
  );
}

// 宿主 rawPayload 里 diag/plan/dispose 这些字段本身是 JSON 字符串(双重编码)。
// 递归把能 parse 的字符串展开成对象,读起来才不是一坨转义。
function deepParseJson(value: unknown): unknown {
  if (typeof value === 'string') {
    const s = value.trim();
    if (
      (s.startsWith('{') && s.endsWith('}')) ||
      (s.startsWith('[') && s.endsWith(']'))
    ) {
      try {
        return deepParseJson(JSON.parse(s));
      } catch {
        return value;
      }
    }
    return value;
  }
  if (Array.isArray(value)) return value.map(deepParseJson);
  if (value && typeof value === 'object') {
    const out: Record<string, unknown> = {};
    for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
      out[k] = deepParseJson(v);
    }
    return out;
  }
  return value;
}

function RawJson({ value }: { value: unknown }) {
  const [expanded, setExpanded] = useState(true);
  // JSON 数据量大 → 可全屏看全(CSS 固定覆盖,Esc 退出)
  const [fullscreen, setFullscreen] = useState(false);
  const text = JSON.stringify(deepParseJson(value), null, 2);
  useEffect(() => {
    if (!fullscreen) return;
    const onKey = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setFullscreen(false);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [fullscreen]);
  return (
    <div
      className={`rounded bg-muted/60 ${
        fullscreen ? 'fixed inset-0 z-50 m-0 flex flex-col rounded-none' : ''
      }`}
    >
      <div className="flex items-center gap-2 px-2 pt-1.5">
        <button
          type="button"
          onClick={() => setExpanded((v) => !v)}
          className="text-[11px] text-muted-foreground underline hover:text-foreground"
        >
          {expanded ? '折叠嵌套' : '展开嵌套'} JSON
        </button>
        <button
          type="button"
          onClick={() => void navigator.clipboard?.writeText(text)}
          className="text-[11px] text-muted-foreground underline hover:text-foreground"
        >
          复制
        </button>
        <button
          type="button"
          onClick={() => setFullscreen((v) => !v)}
          title={fullscreen ? '退出全屏 (Esc)' : '全屏看全'}
          aria-label={fullscreen ? '退出全屏' : '全屏'}
          className="ml-auto rounded p-0.5 text-muted-foreground hover:bg-muted hover:text-foreground"
        >
          {fullscreen ? <Minimize2 className="h-3.5 w-3.5" /> : <Maximize2 className="h-3.5 w-3.5" />}
        </button>
      </div>
      <pre
        className={`whitespace-pre-wrap break-words p-2 font-mono text-[11px] leading-relaxed text-foreground/80 ${
          fullscreen ? 'flex-1 overflow-auto' : 'max-h-80 overflow-auto'
        }`}
      >
        {expanded ? text : JSON.stringify(value, null, 2)}
      </pre>
    </div>
  );
}

function WhyNot({
  notRecalled,
  engineWouldRecall,
}: {
  notRecalled: RecallDebugReport['notRecalled'];
  engineWouldRecall: boolean;
}) {
  const { compliance, signals } = notRecalled;
  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-base">为什么没召 · 闸门漏斗(现在时)</CardTitle>
      </CardHeader>
      <CardContent className="space-y-3 text-sm">
        <div className="flex items-center gap-2">
          <span className="text-muted-foreground">合规闸</span>
          {compliance.pass ? (
            <Badge variant="secondary">通过</Badge>
          ) : (
            <Badge variant="destructive">
              {[
                !compliance.active && '已停用',
                compliance.doNotContact && '勿打扰',
                compliance.deceased && '已故',
              ]
                .filter(Boolean)
                .join(' / ')}
            </Badge>
          )}
        </div>

        {signals.length === 0 && (
          <p className="text-muted-foreground">无召回信号(无 K 码诊断 / 医嘱)。</p>
        )}

        {signals.map((s) => (
          <SignalRow key={s.factId} s={s} />
        ))}

        {!engineWouldRecall && signals.some((s) => s.verdict === 'would_recall') && (
          <p className="rounded-md bg-amber-500/10 px-2 py-1.5 text-xs text-amber-700 dark:text-amber-300">
            漏斗判应召,但引擎按更细的牙位级 gap 排除了 —— 以引擎 ground truth 为准。
          </p>
        )}
      </CardContent>
    </Card>
  );
}

function SignalRow({ s }: { s: SignalFunnel }) {
  const blocked = s.verdict !== 'would_recall';
  return (
    <div className="rounded-lg border border-border/60 p-3">
      <div className="flex flex-wrap items-center gap-2">
        <span className="font-mono text-xs">{s.code}</span>
        <span className="font-medium">{s.codeName}</span>
        <span className="text-xs text-muted-foreground">{s.daysSince ?? '—'}d 前</span>
        {blocked ? (
          <Badge variant="destructive">
            卡:{GATE_ZH[s.blockingGate ?? ''] ?? s.blockingGate}
          </Badge>
        ) : s.engineConfirmed ? (
          <Badge>应召 · 引擎确认</Badge>
        ) : (
          <Badge variant="outline" title="漏斗判应召,但引擎按更细判定排除">
            应召 · 引擎未取
          </Badge>
        )}
      </div>
      <div className="mt-2 flex flex-wrap gap-1.5">
        <GateChip
          label="冷静期"
          pass={s.gates.cooldown.pass}
          blocking={s.blockingGate === 'cooldown'}
          title={s.gates.cooldown.detail}
        />
        <GateChip
          label="已治疗"
          pass={s.gates.alreadyTreated.pass}
          blocking={s.blockingGate === 'already_treated'}
          title={s.gates.alreadyTreated.detail}
        />
        <GateChip
          label="未来预约"
          pass={s.gates.futureAppointment.pass}
          blocking={s.blockingGate === 'future_appointment'}
        />
      </div>
      {s.gates.alreadyTreated.evidence.length > 0 && (
        <div className="mt-2 space-y-0.5 text-xs text-muted-foreground">
          {s.gates.alreadyTreated.evidence.map((e) => (
            <div key={e.factId}>
              ↳ 已治疗证据:{e.category} · {fmtDay(e.occurredAt)}
            </div>
          ))}
        </div>
      )}
      {s.gates.futureAppointment.evidence && (
        <div className="mt-1 text-xs text-muted-foreground">
          ↳ 未来预约:{fmt(s.gates.futureAppointment.evidence.plannedFor)}
        </div>
      )}
    </div>
  );
}

function GateChip({
  label,
  pass,
  blocking,
  title,
}: {
  label: string;
  pass: boolean;
  blocking?: boolean;
  title?: string;
}) {
  const cls = blocking
    ? 'bg-destructive/15 text-destructive'
    : pass
      ? 'bg-emerald-500/15 text-emerald-700 dark:text-emerald-300'
      : 'bg-muted text-muted-foreground';
  return (
    <span
      title={title}
      className={`rounded px-1.5 py-0.5 text-xs ${cls}`}
    >
      {label} {pass ? '✓' : '✕'}
    </span>
  );
}
