'use client';

import { useEffect, useMemo, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { toast } from 'sonner';
import { plansApi } from './plans-api';
import { ChevronDown, Filter, Loader2, PhoneCall, Search, X } from 'lucide-react';
import {
  EXECUTION_OUTCOME_GROUP_META,
  EXECUTION_OUTCOME_META,
  PERSONA_TAG_FILTER_DIMS,
  Permission,
  type ExecutionOutcome,
  type ExecutionOutcomeGroup,
  type PlanListItem,
} from '@pac/types';
import { cn, formatGender } from '@/lib/utils';
import { useHasPermission } from '@/hooks/use-permission';
import { useAuthStore, visibleClinics } from '@/stores/auth-store';
import { PriorityHover, type PriorityBreakdown } from '@/components/priority-hover';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { usePlanSyncStore } from '@/stores/plan-sync-store';
import { usePatientPicker, type PickerFilters } from './use-patient-picker';

/**
 * PatientPickerRail — 详情页固定左栏(第 4 列):一页式工作台的"选患者"列。
 *
 * 吸收列表页核心要素(列表页将弃用):view tab / 搜索 / 排序 / 真实号码 /
 * ⭐画像标签筛选(价值分群 / 生命周期 / 紧迫度 / 潜在治疗,同维 OR 跨维 AND,服务端 SQL)。
 * 不带 KPI 面板(按需求剔除)。点患者 → 路由切详情;本组件挂在 plans/layout(路由段之上),
 * 切患者不重挂、列表状态(筛选/滚动位置/已加载页)全保留。
 */

type View = PickerFilters['view'];
// '全部' tab 已下线(信息过载;召回池 + 我的 足够日常)。view='all' 逻辑保留备用。
const VIEW_TABS: Array<{ v: View; label: string }> = [
  { v: 'pool', label: '召回池' },
  { v: 'mine', label: '我的' },
];

export function PatientPickerRail({
  currentPlanId,
  onNavigated,
}: {
  currentPlanId: string;
  /** 行点击跳转后回调(小屏抽屉模式用来收起) */
  onNavigated?: () => void;
}) {
  const router = useRouter();
  const canViewAll = useHasPermission(Permission.PLAN_VIEW_ALL);

  const [view, setView] = useState<View>('mine');
  const [search, setSearch] = useState('');
  const [keyword, setKeyword] = useState(''); // 防抖后的值(真正上服务端)
  const [sort, setSort] = useState<PickerFilters['sort']>('priority_desc');
  const [realPhoneOnly, setRealPhoneOnly] = useState(false);
  const [clinics, setClinics] = useState<string[]>([]);
  const [tags, setTags] = useState<Set<string>>(new Set()); // "key:value"

  const user = useAuthStore((st) => st.user);
  const clinicDict = user?.dictionary?.clinics; // 显示 plan 诊所名用(字典查表,非 scope)
  const clinicOptions = useMemo(() => visibleClinics(user), [user]);

  useEffect(() => {
    const t = setTimeout(() => setKeyword(search.trim()), 300);
    return () => clearTimeout(t);
  }, [search]);

  const filters: PickerFilters = useMemo(
    () => ({
      view,
      keyword: keyword || undefined,
      sort,
      phoneVerified: realPhoneOnly ? true : undefined,
      targetClinicIds: clinics.length ? clinics : undefined,
      personaTags: tags.size ? [...tags].join(',') : undefined,
    }),
    [view, keyword, sort, realPhoneOnly, clinics, tags],
  );
  const { items, total, loading, hasMore, error, loadMore, patchItem } = usePatientPicker(filters);

  // 右侧详情动作(提交归档等)→ 同步左栏对应行状态(不重拉)
  const sync = usePlanSyncStore();
  useEffect(() => {
    if (!sync.planId) return;
    const patch: Partial<PlanListItem> = {};
    if (sync.status) patch.status = sync.status as PlanListItem['status'];
    if (sync.outcome) patch.lastOutcome = sync.outcome as ExecutionOutcome;
    if (Object.keys(patch).length) patchItem(sync.planId, patch);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sync.seq]);

  // 认领:assign 给自己 → 原地把行改成"进行中"(不重拉,保滚动/筛选)
  const claim = async (planId: string, name: string) => {
    const sub = user?.sub;
    if (!sub) return;
    try {
      await plansApi.assign(planId, sub);
      patchItem(planId, { status: 'assigned', assigneeUserId: sub });
      toast.success(`已认领 ${name}`);
    } catch (err) {
      toast.error(err instanceof Error ? err.message : '认领失败');
    }
  };

  // 触底加载
  const listRef = useRef<HTMLDivElement>(null);
  const onScroll = () => {
    const el = listRef.current;
    if (!el || loading || !hasMore) return;
    if (el.scrollHeight - el.scrollTop - el.clientHeight < 120) loadMore();
  };

  const toggleTag = (k: string) =>
    setTags((prev) => {
      const next = new Set(prev);
      if (next.has(k)) next.delete(k);
      else next.add(k);
      return next;
    });

  return (
    <aside className="flex h-full w-[300px] flex-none flex-col border-r border-slate-100 bg-white">
      {/* view tabs */}
      <div className="flex flex-none gap-1 border-b border-slate-100 px-2 pt-2">
        {VIEW_TABS.filter((t) => t.v !== 'all' || canViewAll).map((t) => (
          <button
            key={t.v}
            type="button"
            onClick={() => setView(t.v)}
            className={cn(
              'rounded-t-md px-3 py-1.5 text-[12.5px] transition-colors',
              view === t.v
                ? 'border-b-2 border-teal-600 font-semibold text-teal-700'
                : 'text-slate-500 hover:text-slate-800',
            )}
          >
            {t.label}
          </button>
        ))}
        <span className="ml-auto self-center pr-1 text-[10.5px] tabular-nums text-slate-400">
          {total} 人
        </span>
      </div>

      {/* 搜索 + 工具行 */}
      <div className="flex-none space-y-1.5 border-b border-slate-100 p-2">
        <div className="relative">
          <Search className="absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-slate-400" />
          <input
            type="search"
            value={search}
            onChange={(e) => setSearch(e.target.value)}
            placeholder="姓名 / 手机 / 病历号"
            className="h-7 w-full rounded-md border border-slate-100 bg-slate-50 pl-7 pr-2 text-[12px] outline-none placeholder:text-slate-400 focus:border-teal-300 focus:bg-white"
          />
        </div>
        <div className="flex items-center gap-1.5">
          <select
            value={sort}
            onChange={(e) => setSort(e.target.value as PickerFilters['sort'])}
            className="h-6.5 rounded-md border border-slate-100 bg-white px-1.5 py-0.5 text-[11px] text-slate-600"
          >
            <option value="priority_desc">优先级 高→低</option>
            <option value="priority_asc">优先级 低→高</option>
            <option value="created_desc">最新生成</option>
          </select>
          <button
            type="button"
            onClick={() => setRealPhoneOnly((v) => !v)}
            title="只看已核实的真实手机号"
            className={cn(
              'inline-flex h-6 items-center gap-1 rounded-md border px-1.5 text-[11px] transition-colors',
              realPhoneOnly
                ? 'border-teal-300 bg-teal-50 font-medium text-teal-700'
                : 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50',
            )}
          >
            <PhoneCall className="h-3 w-3" />真
          </button>
          <PersonaTagFilter selected={tags} onToggle={toggleTag} onClear={() => setTags(new Set())} />
        </div>
        {clinicOptions.length > 1 && (
          <RailClinicFilter options={clinicOptions} selected={clinics} onChange={setClinics} />
        )}
        {/* 已选标签 chips */}
        {tags.size > 0 && (
          <div className="flex flex-wrap gap-1">
            {[...tags].map((kv) => {
              const [k, v] = kv.split(':');
              const dim = PERSONA_TAG_FILTER_DIMS.find((d) => d.key === k);
              const opt = dim?.options.find((o) => o.value === v);
              return (
                <button
                  key={kv}
                  type="button"
                  onClick={() => toggleTag(kv)}
                  className="inline-flex items-center gap-0.5 rounded-full bg-teal-50 px-1.5 py-0.5 text-[10.5px] text-teal-700 ring-1 ring-inset ring-teal-200 hover:bg-teal-100"
                  title={`${dim?.nameZh}:${opt?.zh}(点击移除)`}
                >
                  {opt?.zh ?? v}
                  <X className="h-2.5 w-2.5" />
                </button>
              );
            })}
          </div>
        )}
      </div>

      {/* 患者列表 */}
      <div ref={listRef} onScroll={onScroll} className="min-h-0 flex-1 overflow-y-auto">
        {error && <div className="p-3 text-center text-[11.5px] text-rose-500">{error}</div>}
        {items.map((p) => (
          <PatientRow
            key={p.id}
            plan={p}
            active={p.id === currentPlanId}
            clinicName={p.targetClinicId ? String(clinicDict?.[p.targetClinicId] ?? '') : ''}
            onClick={() => {
              router.push(`/plans/${p.id}`);
              onNavigated?.();
            }}
            onClaim={
              p.status === 'active' && !p.assigneeUserId
                ? () => void claim(p.id, p.patient.name ?? '')
                : undefined
            }
          />
        ))}
        {loading && (
          <div className="flex justify-center py-3">
            <Loader2 className="h-4 w-4 animate-spin text-slate-300" />
          </div>
        )}
        {!loading && items.length === 0 && !error && (
          <div className="p-6 text-center text-[11.5px] text-slate-400">没有匹配的患者</div>
        )}
        {/* data-pet-perch:助手宠物的栖息点(滚到列表底时它会跳上来蹲;按钮被压住也无妨,滚动加载兜底) */}
        {!loading && hasMore && (
          <button
            type="button"
            data-pet-perch
            onClick={loadMore}
            className="block w-full py-2 text-center text-[11px] text-teal-700 hover:bg-teal-50/50"
          >
            加载更多({items.length}/{total})
          </button>
        )}
      </div>
    </aside>
  );
}

// ── 画像标签筛选 popover(4 维度分组多选)────────────────────
function PersonaTagFilter({
  selected,
  onToggle,
  onClear,
}: {
  selected: Set<string>;
  onToggle: (kv: string) => void;
  onClear: () => void;
}) {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <button
          type="button"
          className={cn(
            'inline-flex h-6 items-center gap-1 rounded-md border px-1.5 text-[11px] transition-colors',
            selected.size > 0
              ? 'border-teal-300 bg-teal-50 font-medium text-teal-700'
              : 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50',
          )}
        >
          <Filter className="h-3 w-3" />
          画像标签{selected.size > 0 ? ` ${selected.size}` : ''}
        </button>
      </PopoverTrigger>
      <PopoverContent align="start" className="w-64 p-2">
        <div className="mb-1 flex items-center justify-between px-1">
          <span className="text-[11px] font-medium text-slate-500">按画像圈人</span>
          {selected.size > 0 && (
            <button type="button" onClick={onClear} className="text-[10.5px] text-teal-700 hover:underline">
              清空
            </button>
          )}
        </div>
        <div className="max-h-80 space-y-2 overflow-y-auto">
          {PERSONA_TAG_FILTER_DIMS.map((dim) => (
            <div key={dim.key}>
              <div className="px-1 py-0.5 text-[10.5px] font-medium uppercase tracking-wide text-slate-400">
                {dim.nameZh}
              </div>
              <div className="flex flex-wrap gap-1 px-1 pb-1">
                {dim.options.map((o) => {
                  const kv = `${dim.key}:${o.value}`;
                  const on = selected.has(kv);
                  return (
                    <button
                      key={kv}
                      type="button"
                      onClick={() => onToggle(kv)}
                      className={cn(
                        'rounded-full border px-2 py-0.5 text-[11px] transition-colors',
                        on
                          ? 'border-teal-300 bg-teal-50 font-medium text-teal-700'
                          : 'border-slate-100 bg-white text-slate-600 hover:border-teal-200 hover:bg-teal-50/40',
                      )}
                    >
                      {o.zh}
                    </button>
                  );
                })}
              </div>
            </div>
          ))}
        </div>
      </PopoverContent>
    </Popover>
  );
}

// ── 诊所筛选(popover 多选,样式与列表页 ClinicFilter 同源紧凑版)──────
function RailClinicFilter({
  options,
  selected,
  onChange,
}: {
  options: Array<{ id: string; name: string }>;
  selected: string[];
  onChange: (ids: string[]) => void;
}) {
  const sel = new Set(selected);
  const toggle = (id: string) => {
    const next = new Set(sel);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    onChange([...next]);
  };
  return (
    <Popover>
      <PopoverTrigger asChild>
        <button
          type="button"
          className={cn(
            'inline-flex h-6 w-full items-center justify-between rounded-md border px-1.5 text-[11px] transition-colors',
            selected.length
              ? 'border-teal-300 bg-teal-50 font-medium text-teal-700'
              : 'border-slate-100 bg-white text-slate-500 hover:bg-slate-50',
          )}
        >
          <span className="truncate">
            {selected.length === 0
              ? '全部诊所'
              : selected.map((id) => options.find((o) => o.id === id)?.name ?? id).join('、')}
          </span>
          <ChevronDown className="h-3 w-3 flex-none text-slate-400" />
        </button>
      </PopoverTrigger>
      <PopoverContent align="start" className="max-h-72 w-56 overflow-y-auto p-1.5">
        <div className="mb-1 flex items-center justify-between border-b border-slate-100 px-1.5 py-1">
          <span className="text-[10.5px] text-slate-400">按诊所筛选(可多选)</span>
          {selected.length > 0 && (
            <button type="button" onClick={() => onChange([])} className="text-[10.5px] text-teal-600 hover:underline">
              清空
            </button>
          )}
        </div>
        {options.map((o) => (
          <label key={o.id} className="flex cursor-pointer items-center gap-2 rounded px-1.5 py-1.5 hover:bg-slate-50">
            <input
              type="checkbox"
              className="h-3.5 w-3.5 accent-teal-600"
              checked={sel.has(o.id)}
              onChange={() => toggle(o.id)}
            />
            <span className="truncate text-[12px] text-slate-700">{o.name}</span>
          </label>
        ))}
      </PopoverContent>
    </Popover>
  );
}

// ── 状态 pill(与列表页 STATUS_META 同款)─────────────────────
const STATUS_META: Record<string, { label: string; tone: string }> = {
  active: { label: '待认领', tone: 'bg-sky-100 text-sky-700' },
  assigned: { label: '进行中', tone: 'bg-amber-100 text-amber-700' },
  completed: { label: '已完成', tone: 'bg-emerald-100 text-emerald-700' },
  abandoned: { label: '已放弃', tone: 'bg-rose-100 text-rose-700' },
  superseded: { label: '已替代', tone: 'bg-slate-100 text-slate-600' },
};

function StatusPill({ status }: { status: string }) {
  const m = STATUS_META[status] ?? { label: status, tone: 'bg-slate-100 text-slate-700' };
  return <span className={cn('inline-block flex-none rounded px-1.5 py-0.5 text-[10px] font-medium', m.tone)}>{m.label}</span>;
}

// ── 回访结果角标(最近一次执行的分组:成功 / 不成功 / 保持)────────
// 分组 / 文案 / 色板全部取自 @pac/types 单一真理源(EXECUTION_OUTCOME_META)。
const OUTCOME_GROUP_TONE: Record<ExecutionOutcomeGroup, string> = {
  close: 'bg-emerald-100 text-emerald-700 ring-emerald-200', // 成功
  give_up: 'bg-rose-100 text-rose-700 ring-rose-200', // 不成功
  keep: 'bg-amber-100 text-amber-700 ring-amber-200', // 保持
};

function OutcomeTag({ outcome }: { outcome: string }) {
  const meta = EXECUTION_OUTCOME_META[outcome as ExecutionOutcome];
  if (!meta) return null;
  const group = EXECUTION_OUTCOME_GROUP_META[meta.group];
  return (
    <span
      title={`回访结果:${group.labelZh} · ${meta.labelZh}`}
      className={cn(
        'flex-none rounded px-1 text-[9.5px] font-medium leading-4 ring-1 ring-inset',
        OUTCOME_GROUP_TONE[meta.group],
      )}
    >
      {group.labelZh}
    </span>
  );
}

// ── 优先级(与列表页 PriorityBar 同款:五格条 + 10 分制)────────
function PriorityBar({ score, raw }: { score: number; raw?: number }) {
  const pct = Math.max(0, Math.min(1, score / 100));
  const filled = Math.max(1, Math.round(pct * 5));
  const colors = ['bg-emerald-400', 'bg-emerald-500', 'bg-amber-400', 'bg-amber-500', 'bg-rose-500'];
  const labelTone =
    pct >= 0.6 ? (pct >= 0.8 ? 'text-rose-700' : 'text-amber-700') : pct >= 0.2 ? 'text-emerald-700' : 'text-slate-500';
  const disp = (raw ?? score / 10).toFixed(2);
  return (
    <span className="inline-flex flex-none items-center gap-1" title={`优先级 ${disp} / 10`}>
      <span className="inline-flex items-center gap-0.5">
        {Array.from({ length: 5 }).map((_, i) => (
          <span key={i} className={cn('h-1.5 w-[6px] rounded-sm', i < filled ? colors[i] : 'bg-slate-200')} />
        ))}
      </span>
      <span className={cn('text-[10.5px] font-semibold tabular-nums', labelTone)}>{disp}</span>
    </span>
  );
}

// ── 患者行(紧凑单列卡)────────────────────────────────────
function PatientRow({
  plan: p,
  active,
  clinicName,
  onClick,
  onClaim,
}: {
  plan: PlanListItem;
  active: boolean;
  clinicName: string;
  onClick: () => void;
  onClaim?: () => void;
}) {
  const breakdown = (p.reasons[0]?.breakdown as { priority?: PriorityBreakdown } | null | undefined)?.priority;
  return (
    <div
      role="button"
      tabIndex={0}
      onClick={onClick}
      onKeyDown={(e) => e.key === 'Enter' && onClick()}
      className={cn(
        'group block w-full cursor-pointer border-b border-slate-100 px-3 py-2 text-left transition-colors',
        active ? 'bg-teal-50/70 ring-1 ring-inset ring-teal-200' : 'hover:bg-slate-50',
      )}
    >
      <div className="flex items-center gap-1.5">
        {p.lastOutcome && <OutcomeTag outcome={p.lastOutcome} />}
        <span className={cn('truncate text-[13px] font-semibold', active ? 'text-teal-800' : 'text-slate-900')}>
          {p.patient.name ?? p.patient.nameMasked ?? '—'}
        </span>
        <span className="flex-none text-[10.5px] tabular-nums text-slate-500">
          {formatGender(p.patient.gender)} · {p.patient.age ?? '?'} 岁
        </span>
        {p.patient.phoneVerified && (
          <span className="flex-none rounded bg-teal-50 px-1 text-[9.5px] font-medium leading-4 text-teal-700 ring-1 ring-inset ring-teal-200">
            真
          </span>
        )}
        <span className="ml-auto flex-none">
          <PriorityHover score={p.priorityScore} breakdown={breakdown}>
            <span className="cursor-help">
              <PriorityBar score={p.priorityScore} raw={breakdown?.raw} />
            </span>
          </PriorityHover>
        </span>
      </div>
      <div className="mt-0.5 flex items-center gap-1.5">
        <span className="truncate font-mono text-[10.5px] tabular-nums text-slate-400">
          {p.patient.phoneMasked ?? ''}
        </span>
        {clinicName && (
          <span className="truncate text-[10px] text-slate-400" title={clinicName}>
            · {clinicName}
          </span>
        )}
        <span className="ml-auto" />
        {onClaim ? (
          <>
            <button
              type="button"
              onClick={(e) => {
                e.stopPropagation();
                onClaim();
              }}
              className="hidden flex-none rounded-md bg-teal-600 px-2 py-0.5 text-[10.5px] font-medium text-white hover:bg-teal-700 group-hover:inline-block"
            >
              认领
            </button>
            <span className="group-hover:hidden">
              <StatusPill status={p.status} />
            </span>
          </>
        ) : (
          <StatusPill status={p.status} />
        )}
      </div>
    </div>
  );
}
