'use client';

import { useEffect, useState } from 'react';
import { Loader2 } from 'lucide-react';
import {
  TEMPERATURE_META,
  TEMPERATURE_ORDER,
  potentialTreatmentItemName,
  temperatureWindowHint,
  type TemperatureValue,
} from '@pac/types';
import { cn } from '@/lib/utils';
import { Card, CardContent, CardHeader } from '@/components/ui/card';
import { plansApi, type PoolMatrix, type PoolMatrixRow } from './plans-api';

/**
 * PoolMatrix — 初选矩阵(8 潜在治疗 × 3 召回窗口)。排版按 claude design 的 **1b「连续色带」**。
 *
 * 生产线的**第一环**:主管在这里点一格,就完成了「初选」,人群随即交给助手出确认单。
 * 矩阵是召回池的一个**视图模式**(tab 行「分配」浮层展开),⛔ 不新开路由 ——
 * 主管本质也是客服、也要执行,割裂成两个页面会把他劈成两个身份(T16)。
 *
 * ═══ 1b 的核心:矩阵区是**一张连续的渐变面**,不是 24 个色块 ═══════════
 * 三列共用一条 `amber → emerald → sky` 的横向渐变,**只用色相区分窗口**;
 * 格子本身透明,浮在这张面上。列与列之间没有缝,整片读起来是一条"由烫到冷"的轴。
 *
 * ⛔ **数量绝不参与配色**(这条纪律一直没变,1b 也遵守):
 *   渐变只由**列的位置**决定 —— 否则「这格暖是因为在黄金期、还是因为人多」分不清。
 *   人数已经用数字表达了,不需要颜色再说一遍。
 * ⚠️ 色相承载的是**窗口档位**,与列头文字是同一个信息的两种编码 —— 这是刻意的冗余
 *   (色盲用户靠文字,扫视靠颜色),不是重复。
 *
 * ⚠️ 用 `/nn` 透明度而不是设计稿里的 `color-mix(in oklch, …, white)`:
 *   两者在白底卡片上视觉等价(Tailwind v4 的透明度修饰符本身就编译成 color-mix),
 *   而透明度写法**不写死任何十六进制**,符合 globals.css 那条「别在组件里写死色值」。
 */

/// 列序走共享的 TEMPERATURE_ORDER,中文走共享的 TEMPERATURE_META —— ⛔ 别在这里内联中文表
const COLUMNS = TEMPERATURE_ORDER;
type TempKey = TemperatureValue;

/**
 * 每档的**色相**。⚠️ 只用于「这一列是哪个窗口」的标识(列头圆点 + 汇总数字),
 * 格子的底色来自下方那张整片渐变,不是从这里取的 —— 两处若各上各的色就会对不齐。
 */
const HUE: Record<TempKey, { dot: string; text: string }> = {
  hot: { dot: 'bg-amber-600', text: 'text-amber-700' },
  warm: { dot: 'bg-emerald-600', text: 'text-emerald-700' },
  cold: { dot: 'bg-sky-600', text: 'text-sky-700' },
};

/// 整片矩阵那条渐变 —— 一处定义,列头刻度与格子面共用;写两遍必然漂
const PLANE = 'bg-linear-to-r from-amber-600/55 via-emerald-600/45 to-sky-600/40';

/// 行标签列宽 + 行高:列头、标签列、格子面三者靠它们对齐,别在某一处手改
const LABEL_W = 'w-[54px]';
const ROW_H = 'h-[30px]';

export function PoolMatrix({
  clinicId,
  selected,
  onPick,
}: {
  clinicId?: string;
  /** 当前选中的格子(回显用) */
  selected?: { treatment: string; temperature: TempKey } | null;
  /**
   * 点格子 → 初选完成,把这一格的人群交出去。
   * ⚠️ 带 `rect`(该格子的视口矩形):数据流动画的**起点**。
   *   必须在点击那一刻取 —— 浮层紧接着就关了,回头再查只能拿到 null。
   */
  onPick: (cell: {
    treatment: string;
    treatmentZh: string;
    temperature: TempKey;
    count: number;
    rect: { x: number; y: number; w: number; h: number };
  }) => void;
}) {
  const [data, setData] = useState<PoolMatrix | null>(null);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let alive = true;
    setError(null);
    plansApi
      .matrix(clinicId)
      .then((d) => alive && setData(d))
      .catch((e: unknown) => alive && setError(e instanceof Error ? e.message : String(e)));
    return () => {
      alive = false;
    };
  }, [clinicId]);

  if (error) {
    return <div className="p-4 text-xs text-rose-600">矩阵加载失败:{error}</div>;
  }
  if (!data) {
    // 骨架与真身**同高**,浮层不会先窄后宽跳一下(浮层的抖动比列表更刺眼)
    return (
      <div className="flex h-[318px] items-center justify-center gap-2 text-xs text-muted-foreground">
        <Loader2 className="h-3.5 w-3.5 animate-spin" />
        正在数人…
      </div>
    );
  }

  const showUnknown = data.unknownTotal > 0;
  /// 列汇总 = 该列 8 行之和。⚠️ 现算不请求:后端给的是格子,汇总是纯展示派生,不该多一个字段去漂
  const colTotal = (c: TempKey) => data.rows.reduce((a, r) => a + r[c], 0);

  return (
    <Card className="w-[380px] border-0 shadow-none">
      <CardHeader className="flex-row items-baseline justify-between space-y-0 p-3 pb-2.5">
        <div className="text-[13px] font-semibold">潜在治疗</div>
        <div className="flex items-baseline gap-2.5 whitespace-nowrap text-[11px] text-muted-foreground nums">
          {COLUMNS.map((c) => (
            <span key={c}>
              {TEMPERATURE_META[c].zh}{' '}
              <span className={cn('font-semibold', HUE[c].text)}>{colTotal(c).toLocaleString()}</span>
            </span>
          ))}
        </div>
      </CardHeader>

      <CardContent className="p-3 pt-0">
        {/* 列头:圆点 + 档位名,颜色与下方渐变面的对应段同族 */}
        <div className="flex items-center pb-1.5">
          <div className={cn(LABEL_W, 'shrink-0')} />
          <div className="flex flex-1">
            {COLUMNS.map((c) => (
              <div
                key={c}
                title={TEMPERATURE_META[c].hint}
                className={cn(
                  'flex flex-1 items-center justify-center gap-1.5 text-[11px] font-semibold',
                  HUE[c].text,
                )}
              >
                <span className={cn('h-1.5 w-1.5 rounded-sm', HUE[c].dot)} />
                {TEMPERATURE_META[c].zh}
              </div>
            ))}
          </div>
          {showUnknown && (
            <div className="w-10 shrink-0 text-center text-[11px] text-muted-foreground">待重算</div>
          )}
        </div>

        <div className="flex items-start">
          {/* 行标签独立成列 —— 它不该压在渐变面上(压上去白底文字会跟着色相变脏) */}
          <div className={cn(LABEL_W, 'shrink-0')}>
            {data.rows.map((row) => (
              <div key={row.key} className={cn(ROW_H, 'flex items-center text-xs font-medium')}>
                {potentialTreatmentItemName(row.key)}
              </div>
            ))}
          </div>

          {/* ⭐ 1b 的主体:**一整张**渐变面,8×3 个格子透明浮在上面 */}
          <div className={cn('flex-1 overflow-hidden rounded', PLANE)}>
            {data.rows.map((row) => (
              <MatrixRow key={row.key} row={row} selected={selected} onPick={onPick} />
            ))}
          </div>

          {showUnknown && (
            <div className="w-10 shrink-0">
              {data.rows.map((row) => (
                <div
                  key={row.key}
                  title="温度待重算 —— 画像还没算出窗口边界"
                  className={cn(
                    ROW_H,
                    'flex items-center justify-center text-[11px] text-muted-foreground nums',
                  )}
                >
                  {row.unknown || '—'}
                </div>
              ))}
            </div>
          )}
        </div>

        {data.note && (
          <p className="mt-2 rounded bg-amber-50 px-2 py-1.5 text-[10.5px] leading-relaxed text-amber-800">
            {data.note}
          </p>
        )}
      </CardContent>
    </Card>
  );
}

function MatrixRow({
  row,
  selected,
  onPick,
}: {
  row: PoolMatrixRow;
  selected?: { treatment: string; temperature: TempKey } | null;
  onPick: (cell: {
    treatment: string;
    treatmentZh: string;
    temperature: TempKey;
    count: number;
    rect: { x: number; y: number; w: number; h: number };
  }) => void;
}) {
  // ⛔ 中文一律查 labels.ts,别在组件里另写一张表 —— 改措辞要即时生效、且全站一处。
  //   用**项目名**(种植 / 早矫 / 拔牙)而不是卡片措辞(种植治疗):
  //   8×3 的表里每行都拖着「治疗」两个字纯属噪声,还挤掉列宽。
  const zh = potentialTreatmentItemName(row.key);
  return (
    <div className="flex">
      {COLUMNS.map((c) => {
        const n = row[c];
        const isSelected = selected?.treatment === row.key && selected.temperature === c;
        return (
          <button
            key={c}
            type="button"
            disabled={n === 0}
            onClick={(e) => {
              const r = e.currentTarget.getBoundingClientRect();
              onPick({
                treatment: row.key,
                treatmentZh: zh,
                temperature: c,
                count: n,
                rect: { x: r.left, y: r.top, w: r.width, h: r.height },
              });
            }}
            // ⭐ hover 文案保持原样:只给**天数区间**,不给解释。
            //   格子里已经有数字、列头已经写了档位名,再叠一段说明就是噪声;
            //   区间是主管唯一看不出来的那个信息。
            title={temperatureWindowHint(row.key, c)}
            className={cn(
              ROW_H,
              'flex-1 text-[13px] text-slate-900 nums',
              // ⭐ hover/选中用**内描边**而不是换底色 —— 换底色会在渐变面上戳一个洞,
              //   整片"连续"的观感当场破掉。描边只加一圈框,底下的渐变原样透出来。
              // ⚠️ 描边用**品牌主色**不是中性黑:这一圈框表达的是"你正指着谁 / 选中了谁",
              //   属于交互态,交互态在全站都该是主色(黑框会读成边界线,像是数据的一部分)。
              // ⚠️ 用 outline 而不是 shadow-[inset_…_var(--color-…)]:后者 Tailwind v4
              //   会把带 var() 的任意值解析成"只有颜色的阴影",偏移和扩散全丢,
              //   实测算出来是 `oklab(0 0 0 / 0) 0 0 0 0 inset` —— 完全透明,**看不出坏了**。
              //   负 offset 让描边画在格子内侧,与相邻格子不互相挤压(设计稿也是这么写的)。
              n === 0
                ? 'cursor-default text-slate-900/35'
              //   ⚠️ 必须带 `outline-solid`:基态的 `outline-none` 把 outline-style 钉成 none,
              //     只加 `outline-2` 只改宽度 —— 实测算出来是 width:2px 但 style:none,**一条线都不画**。
                : 'cursor-pointer font-medium outline-none hover:outline-solid hover:outline-2 hover:-outline-offset-2 hover:outline-brand-600 focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-brand-600',
              isSelected && 'outline-solid outline-2 -outline-offset-2 outline-brand-600',
            )}
          >
            {n ? n.toLocaleString() : '—'}
          </button>
        );
      })}
    </div>
  );
}
