'use client';

import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { X } from 'lucide-react';
import { TEMPERATURE_META, TEMPERATURE_ORDER, type TemperatureValue } from '@pac/types';
import { plansApi, type PoolMatrix as PoolMatrixData } from '@/components/plans/plans-api';
import { PoolMatrix } from '@/components/plans/pool-matrix';
import { useAssistantStore } from '@/stores/assistant-store';
import { cn } from '@/lib/utils';

/**
 * 「分一批新的」—— **两版**（设计稿 `newBatchMode` 那两个选项，产品要求都实现）。
 *
 *   · `window`  矩阵窗口：居中 modal 套现有 `PoolMatrix`。改动最小，就是把左栏那个浮层搬过来。
 *   · `arcade`  里世界打地鼠：全屏传送门 + 深色场 + 敲一格 + 数据粒子飞向助手。
 *
 * ⛔ **两版挑完格子走的是同一条路**：`useAssistantStore.handoff()` ——
 *   发数据流 → 粒子飞到助手 → 落地后自动开大窗并说那句话。
 *   ⚠️ 这条链路是现成的（左栏那个入口一直在用），⛔ 别在这儿另写一套移交：
 *   移交的时序（动画多长、什么时候开窗）是舞台层的事，业务侧复制一份必然漂。
 */
export type NewBatchMode = 'window' | 'arcade';

type Picked = {
  treatment: string;
  treatmentZh: string;
  temperature: TemperatureValue;
  count: number;
  rect: { x: number; y: number; w: number; h: number };
};

/** 两版共用的移交 —— ⛔ 别在各自分支里各写一遍 */
function handoff(c: Picked) {
  const tempZh = TEMPERATURE_META[c.temperature].zh;
  useAssistantStore.getState().handoff({
    text: `帮我给「${c.treatmentZh} · ${tempZh}」这批患者出一份分配方案`,
    from: c.rect,
    count: c.count,
    treatment: c.treatmentZh,
    temperature: tempZh,
  });
}

export function NewBatchOverlay({
  mode,
  clinicId,
  onClose,
  /** 「里世界」入场用的传送门圆心 = **触发按钮中心**的视口坐标；不给则从右上角进 */
  origin,
}: {
  mode: NewBatchMode;
  clinicId: string | null;
  onClose: () => void;
  origin?: { x: number; y: number } | null;
}) {
  const [matrix, setMatrix] = useState<PoolMatrixData | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    if (!clinicId) return;
    let alive = true;
    plansApi
      .matrix(clinicId)
      .then((r) => alive && setMatrix(r))
      .catch((e) => alive && setError(e instanceof Error ? e.message : '取矩阵失败'));
    return () => {
      alive = false;
    };
  }, [clinicId]);

  useEffect(() => {
    const onKey = (e: KeyboardEvent) => e.key === 'Escape' && onClose();
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);

  const pick = useCallback(
    (c: Picked) => {
      onClose();
      handoff(c);
    },
    [onClose],
  );

  if (mode === 'arcade') {
    return (
      <ArcadeMatrix
        matrix={matrix}
        error={error}
        origin={origin ?? null}
        onPick={pick}
        onClose={onClose}
      />
    );
  }

  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center bg-slate-900/30 p-4"
      onClick={onClose}
    >
      <div
        className="w-full max-w-[62rem] overflow-hidden rounded-xl bg-white shadow-2xl"
        onClick={(e) => e.stopPropagation()}
      >
        <div className="flex items-baseline gap-2 border-b border-slate-50 px-4 py-3">
          <span className="text-[13.5px] font-semibold text-slate-900">分一批新的</span>
          <span className="text-[11.5px] text-slate-400">
            点一格 = 把这批人交给助手，助手出确认单
          </span>
          <button
            type="button"
            onClick={onClose}
            aria-label="关闭"
            className="ml-auto rounded-md p-1 text-slate-500 hover:bg-slate-100"
          >
            <X className="h-4 w-4" />
          </button>
        </div>
        {/* ⚠️ 小屏兜底:矩阵有六列,窄了就横向滚,⛔ 别让它压缩到看不清数字 */}
        <div className="overflow-x-auto p-4">
          {error ? (
            <p className="py-10 text-center text-[12px] text-rose-600">{error}</p>
          ) : matrix ? (
            /* ⚠️ `w-full` 覆盖组件默认的 560px（那是给左栏 popover 的）——
               不覆盖的话 modal 有 992px 宽而矩阵只占 560，右边一大片空白（实测）。
               `min-w-[34rem]` 保证再窄也不把数字挤糊，由外层横向滚兜住。 */
            <PoolMatrix data={matrix} selected={null} onPick={pick} className="w-full min-w-[34rem]" />
          ) : (
            <div className="space-y-2 py-6">
              {Array.from({ length: 8 }).map((_, i) => (
                <div key={i} className="h-7 animate-pulse rounded bg-slate-100" />
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

/**
 * 「里世界」打地鼠版。
 *
 * ⚠️ 三个技术点是**实测会静默失效**的，别省：
 *   ① `offset-path` 粒子飞行 Safari 16 才支持 —— 低版本不崩但完全没动效，
 *      所以粒子**只是锦上添花**，移交本身不依赖它（`handoff` 立即发出）。
 *   ② 自定义光标的 data URI 在 Windows 上**上限 32×32**，超了整个 cursor 被忽略 →
 *      锤子必须画在 32×32 里（设计稿给的是 34，会被丢掉）。
 *   ③ `prefers-reduced-motion` 下把飞行与入场动画全关掉。
 *
 * ⭐ 地洞的色相**只由列位置取样**，与表世界那条 amber→emerald→sky 同一条轴 ——
 *   ⛔ 数量不参与配色（与 pool-matrix 同一条纪律）。
 */
function ArcadeMatrix({
  matrix,
  error,
  origin,
  onPick,
  onClose,
}: {
  matrix: PoolMatrixData | null;
  error: string | null;
  origin: { x: number; y: number } | null;
  onPick: (c: Picked) => void;
  onClose: () => void;
}) {
  const rootRef = useRef<HTMLDivElement>(null);
  const [hit, setHit] = useState<string | null>(null);
  const [leaving, setLeaving] = useState(false);

  /**
   * 传送门的几何：圆心（相对**幕布自己**的左上角）+ 要放大多少倍。
   *
   * 🔴 换掉了原来的 `clip-path: circle()` 方案，两个原因，都踩过：
   *   ① **定位不可靠**：clip-path 的 `at <x> <y>` 是按元素的 **reference box** 算的，
   *      不是视口。`fixed` 元素的包含块只要被祖先的 transform / filter /
   *      backdrop-filter / contain 之类劫走，这个盒子就不再是视口 ——
   *      于是圆心整体偏掉，而且**不报任何错**。产品连着两轮反馈"不是从鼠标处扩散的"，
   *      在开发面板里却量得分毫不差，就是这个差别。
   *      现在改成 `absolute` + `left/top`，量的是**同一个盒子**（`rootRef` 的 rect），
   *      偏不了 —— 见下面的 `useLayoutEffect`。
   *   ② **性能**：clip-path 不上合成层，那 0.5s 里每一帧都要重画整屏（含 48 个格子）。
   *      现在幕布是一个 200×200 的小圆靠 `transform: scale()` 放大，
   *      内容只做 `opacity` —— 全程合成层，**零重绘**。
   *      ⛔ 别图省事把幕布直接做成全屏大小再 scale：那会申请一张按未缩放尺寸算的纹理
   *      （2700² ≈ 28MB），小圆放大才是便宜的那条路。
   *
   * ⚠️ 必须在 `useLayoutEffect` 里量（不是 render 里读 `window`）：
   *   要的是幕布**实际**占的那个盒子，不是 `innerWidth/innerHeight` 这个假设。
   * ⚠️ 布局副作用里 setState 会在**绘制前**同步补一次 render，所以不会闪一帧。
   */
  const [portal, setPortal] = useState<{ x: number; y: number; s: number } | null>(null);
  useLayoutEffect(() => {
    const el = rootRef.current;
    if (!el) return;
    const r = el.getBoundingClientRect();
    // 没给圆心时从右上角进（那也是按钮所在的角）
    const p = origin ?? { x: r.left + r.width * 0.9, y: r.top + r.height * 0.06 };
    const x = p.x - r.left;
    const y = p.y - r.top;
    // 盖满 = 圆心到最远那个角的距离
    const far = Math.hypot(Math.max(x, r.width - x), Math.max(y, r.height - y));
    setPortal({ x, y, s: far / PORTAL_DISC_R });
  }, [origin]);

  /**
   * ⚠️ 幕布还没盖住之前，内容是 `opacity:0` 但**照样能点**（透明不等于穿透）——
   *   那 0.36s 里手快点中一格就会盲发一批人。所以入场期间先关掉命中。
   * ⛔ 别改用 `onAnimationEnd` 判断：格子自己也有动画、事件会冒泡上来，
   *   而且 reduced-motion 下根本不触发。
   */
  const [ready, setReady] = useState(false);
  useEffect(() => {
    const t = window.setTimeout(() => setReady(true), 360);
    return () => window.clearTimeout(t);
  }, []);

  const leave = useCallback(() => {
    setLeaving(true);
    window.setTimeout(onClose, 380);
  }, [onClose]);

  const whack = (key: string, e: React.MouseEvent, c: Picked) => {
    setHit(key);
    // ⭐ 移交**立即发**，⛔ 不等动画：动画失败（Safari / reduced-motion）时也不能卡住业务
    onPick({ ...c, rect: rectOf(e.currentTarget as HTMLElement) });
  };

  return (
    /* ⚠️ 根节点**不能有背景** —— 传送门没张开的地方要露出下面的工作台，
         "从按钮里钻进去"的因果关系全靠这个。 */
    <div ref={rootRef} className="fixed inset-0 z-50 overflow-hidden">
      {portal && (
        <div
          aria-hidden
          className={leaving ? 'pac-portal-disc-out' : 'pac-portal-disc'}
          style={
            { left: portal.x, top: portal.y, '--s': portal.s } as React.CSSProperties
          }
        />
      )}
      <div
        className={cn(
          'absolute inset-0 flex flex-col items-center justify-center gap-4 px-6 py-8',
          leaving ? 'pac-portal-body-out' : 'pac-portal-body',
          !ready && 'pointer-events-none',
        )}
        style={{
          background:
            'radial-gradient(120% 90% at 50% 12%, #0B2E7A 0%, #031C53 45%, #010F32 100%)',
        }}
      >
        <div className="flex w-full max-w-[74rem] items-baseline gap-3">
          <span className="text-[17px] font-semibold tracking-wide text-slate-50">
            挑一格，把这批人交给助手
          </span>
          <span className="text-[12px] text-brand-200/60">
            行是潜在治疗，列是召回窗口 · 敲下去就出确认单
          </span>
          <button
            type="button"
            onClick={leave}
            className="ml-auto rounded-lg border border-brand-200/25 bg-white/5 px-2.5 py-1 text-[11.5px] text-brand-100 hover:bg-white/15 hover:text-white"
          >
            退回
          </button>
        </div>

        {error ? (
          <p className="text-[12.5px] text-rose-300">{error}</p>
        ) : !matrix ? (
          <p className="text-[12.5px] text-brand-200/60">正在点亮地洞…</p>
        ) : (
          <div className="w-full max-w-[74rem]">
            {/* 列头 —— 六个档位的光点，色相与下面的地洞同源 */}
            <div className="flex items-center pb-2.5">
              <div className="w-[4.75rem] flex-none" />
              <div className="flex flex-1 gap-2.5">
                {TEMPERATURE_ORDER.map((t, i) => (
                  <div
                    key={t}
                    className="flex flex-1 items-center justify-center gap-1.5 whitespace-nowrap text-[12px] font-semibold"
                    style={{ color: bandTint((i + 0.5) / TEMPERATURE_ORDER.length, 0.95) }}
                  >
                    <span
                      className="h-1.5 w-1.5 rounded-sm"
                      style={{
                        background: bandTint((i + 0.5) / TEMPERATURE_ORDER.length, 1),
                        boxShadow: `0 0 8px ${bandTint((i + 0.5) / TEMPERATURE_ORDER.length, 1)}`,
                      }}
                    />
                    {TEMPERATURE_META[t].zh}
                  </div>
                ))}
              </div>
            </div>

            <div className="flex items-start">
              <div className="flex w-[4.75rem] flex-none flex-col gap-2.5">
                {matrix.rows.map((r) => (
                  <div
                    key={r.key}
                    className="flex h-14 items-center text-[13px] font-medium text-slate-100/85"
                  >
                    {r.zh}
                  </div>
                ))}
              </div>
              <div
                className="flex-1 rounded-2xl p-2.5"
                style={{
                  background:
                    'linear-gradient(to right, rgba(252,211,77,.42), rgba(110,231,183,.34) 45%, rgba(125,211,252,.40))',
                  boxShadow: 'inset 0 0 0 1px rgba(255,255,255,.06)',
                }}
              >
                <div className="flex flex-col gap-2.5">
                  {matrix.rows.map((row) => (
                    <div key={row.key} className="flex gap-2.5">
                      {TEMPERATURE_ORDER.map((t, ci) => {
                        const n = row.counts[t] ?? 0;
                        const key = `${row.key}-${t}`;
                        const tint = bandTint((ci + 0.5) / TEMPERATURE_ORDER.length, 0.34);
                        if (n === 0) {
                          return (
                            <span
                              key={t}
                              title="这一格没有人"
                              className="relative h-14 flex-1 rounded-xl"
                              style={{
                                background:
                                  'radial-gradient(closest-side, rgba(2,10,32,.55), rgba(2,10,32,.18))',
                                boxShadow: 'inset 0 2px 6px rgba(0,0,0,.45)',
                              }}
                            />
                          );
                        }
                        return (
                          <button
                            key={t}
                            type="button"
                            title={`${row.zh} · ${TEMPERATURE_META[t].zh} · ${n} 人`}
                            onClick={(e) =>
                              whack(key, e, {
                                treatment: row.key,
                                treatmentZh: row.zh,
                                temperature: t,
                                count: n,
                                rect: { x: 0, y: 0, w: 0, h: 0 },
                              })
                            }
                            className={cn(
                              'relative h-14 flex-1 rounded-xl border-0 text-[17px] font-semibold text-slate-50 transition-transform',
                              'hover:-translate-y-[3px] focus-visible:outline focus-visible:outline-2 focus-visible:outline-white',
                              hit === key && 'animate-[whack_.42s_ease-in_forwards]',
                            )}
                            style={{
                              cursor: HAMMER_CURSOR,
                              backgroundColor: tint,
                              backgroundImage:
                                'linear-gradient(180deg, rgba(255,255,255,.14), rgba(2,10,32,.20))',
                              boxShadow:
                                'inset 0 0 0 1px rgba(255,255,255,.14), 0 6px 14px -6px rgba(1,15,50,.8)',
                              textShadow: '0 1px 8px rgba(1,15,50,.6)',
                            }}
                          >
                            {n.toLocaleString()}
                            {hit === key && (
                              <span className="pointer-events-none absolute -inset-1.5 rounded-2xl border-2 border-white/90 animate-[ringOut_.5s_ease-out_forwards]" />
                            )}
                          </button>
                        );
                      })}
                    </div>
                  ))}
                </div>
              </div>
            </div>

            <p className="mt-3 text-[11.5px] leading-relaxed text-brand-200/50">
              前两档按该治疗自己的周期算，后四档按诊断距今多久算。一个人有几个潜在治疗就出现在几行
              —— 所以各行相加会大于池子总人数。
            </p>
          </div>
        )}
      </div>
    </div>
  );
}

/**
 * 传送门幕布的**未缩放**半径（px），与 globals.css 的 `.pac-portal-disc` 尺寸一一对应。
 * ⚠️ 改这里就要同步改那边的 `width/height/margin`，否则放大倍数算错、盖不满或过冲。
 */
const PORTAL_DISC_R = 100;

/** 元素在视口里的矩形 —— 移交动画的起点 */
function rectOf(el: HTMLElement) {
  const r = el.getBoundingClientRect();
  return { x: r.left, y: r.top, w: r.width, h: r.height };
}

/**
 * 地洞色相 —— 沿 amber→emerald→sky 这条轴按**列中心**取样。
 * ⛔ 数量不参与（与 pool-matrix 同一条纪律）：否则"这格亮是因为久没来、还是因为人多"分不清。
 */
const STOPS: Array<[number, [number, number, number]]> = [
  [0, [252, 211, 77]],
  [0.45, [110, 231, 183]],
  [1, [125, 211, 252]],
];
function bandTint(f: number, alpha: number): string {
  let a = STOPS[0]!;
  let b = STOPS[STOPS.length - 1]!;
  for (let i = 0; i < STOPS.length - 1; i++) {
    if (f >= STOPS[i]![0] && f <= STOPS[i + 1]![0]) {
      a = STOPS[i]!;
      b = STOPS[i + 1]!;
      break;
    }
  }
  const t = (f - a[0]) / (b[0] - a[0] || 1);
  const c = a[1].map((v, i) => Math.round(v + (b[1][i]! - v) * t));
  return `rgba(${c.join(',')},${alpha})`;
}

/**
 * 锤子光标。
 * 🔴 **必须画在 32×32 之内** —— Windows 上自定义光标超过 32×32 会被**整个忽略**
 *   （设计稿给的是 34×34，那份在 Windows 上等于没有光标效果）。
 */
const HAMMER_SVG =
  '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">' +
  '<g transform="rotate(-28 16 16)">' +
  '<rect x="14" y="10" width="4" height="18" rx="2" fill="#DCE7FE" stroke="#01102F" stroke-width="1.6"/>' +
  '<rect x="6" y="4" width="19" height="8.5" rx="2.5" fill="#F8FAFC" stroke="#01102F" stroke-width="1.6"/>' +
  '<rect x="9" y="6" width="5" height="4.5" rx="1" fill="#85A9FA" opacity=".8"/>' +
  '</g></svg>';
const HAMMER_CURSOR = `url("data:image/svg+xml,${encodeURIComponent(HAMMER_SVG)}") 12 8, pointer`;
