'use client';

import { useState } from 'react';
import { Permission } from '@pac/types';
import { cn } from '@/lib/utils';
import { useHasPermission } from '@/hooks/use-permission';
import { usePlanSyncStore } from '@/stores/plan-sync-store';
import { useAssistantStore } from '@/stores/assistant-store';
import { EXAMPLES_LEADER } from './assistant-chat';
import { AssistantFab } from './assistant-fab';
import { CohortStream } from './cohort-stream';
import { AssistantChat } from './assistant-chat';

/**
 * AssistantWidget — 详情页右下角吸附助手(与 /assistant 同一能力,复用 AssistantChat)。
 *
 * 收起 = 右下角圆形悬浮钮;展开 = 400×~75vh 吸附小窗。
 * 关键:收起用 CSS 隐藏而非卸载 → 对话/工具步骤/听写状态全保留;
 * 挂在 plans/layout(路由段之上)→ 切患者对话也不丢。
 * 权限同 /assistant 页(AGENT_INVOKE),无权限不渲染。
 */
export function AssistantWidget() {
  const allowed = useHasPermission(Permission.AGENT_INVOKE);
  // ⭐ open 提到 store:业务代码(「移交助手」)要能从外面把它打开。
  //   原来是组件内 useState,外部完全够不着 —— 那正是移交入口此前无处落地的原因。
  //   ⚠️ 收起仍然是 CSS 隐藏**不卸载**(见文件头注释),别顺手改成条件渲染:
  //   改了主管每收一次助手就丢一次对话,而且看不出任何异常。
  const open = useAssistantStore((s) => s.open);
  const setOpen = useAssistantStore((s) => s.setOpen);
  // ⭐ 最大化也提到 store(与 open 同理):移交完成后要能从外部直接开大窗。
  //   ⚠️ 仍然只是显隐/尺寸,**不卸载**对话(见文件头)。
  const maximized = useAssistantStore((s) => s.maximized);
  const setMaximized = useAssistantStore((s) => s.setMaximized);
  // 窗口落点 + 尺寸:打开瞬间按"钮的当前位置"计算(拖钮的意义所在),并夹紧在视窗内
  const [winRect, setWinRect] = useState<{ x: number; y: number; w: number; h: number } | null>(null);

  const openAt = (fab: DOMRect) => {
    /**
     * ⚠️ **这个 480 与下面 className 里的 `w-[480px]` 是一对,改一处要改两处**
     *   (Tailwind 的任意值必须是静态字面量,没法从这里的常量插进去)。
     * 🔴 400 → 480(2026-08-11 产品:「窗口调宽一点」):确认单在 400px 里排不开 ——
     *   汇总行的「已移除 1」被挤成竖排单字,时限下拉顶出边界。
     *   ⛔ 别再往回收:那张卡是这个窗口的**主要内容**,窗口要迁就它。
     */
    const W = Math.min(480, window.innerWidth - 16);
    /**
     * ⚠️ **这个 760 / 0.88 与下面 className 里的 `h-[760px] max-h-[88vh]` 是一对,改一处要改两处**。
     * 🔴 620 → 760(2026-08-19 产品:「默认高度改高点」):这个窗口的主要内容是**确认单**,
     *   而它下面还要跟一段正文和几排引导按钮 —— 620px 里卡片一出,正文就被挤到要滚,
     *   而正文正是"这一版为什么长这样"的那半句。⛔ 别再往回收。
     */
    const H = Math.min(760, Math.round(window.innerHeight * 0.88));
    // 水平:钮在屏幕右半 → 窗口右缘对齐钮右缘;左半 → 左缘对齐
    let x = fab.left + fab.width / 2 > window.innerWidth / 2 ? fab.right - W : fab.left;
    // 垂直:优先弹在钮上方,放不下弹下方
    let y = fab.top - H - 8;
    if (y < 8) y = fab.bottom + 8;
    x = Math.min(Math.max(x, 8), window.innerWidth - W - 8);
    y = Math.min(Math.max(y, 8), window.innerHeight - H - 8);
    setWinRect({ x, y, w: W, h: H });
    setOpen(true);
  };
  // 场景化开场建议(规则,非 AI):有当前患者 → 围绕该患者;否则兜底通用
  const current = usePlanSyncStore((s) => s.current);
  /**
   * ⚠️ 主管多三条 —— 原来四条全是**客服**的活(捋事实、想开场白),
   *   主管看不出"我还能在这儿分配和看批次"。占位符同理(见 assistant-chat)。
   * ⚠️ 仍然保留患者类的两条:T16 主管本质也是客服,他一样会点开某个患者看。
   * ⛔ 别写「转化率怎么样」这类 —— 系统只报**处理率**,例句先许愿会逼着模型去圆。
   *
   * 三条各对一个工具,顺序 = 主管的实际动线(分 → 看全局 → 看单批):
   *   1. propose_assignment       出方案
   *   2. list_assignment_batches  横向总览(表格:markdown 表已支持,见 assistant-chat 的 remarkGfm)
   *   3. get_assignment_detail    单批下钻
   *
   * ⛔ 第 2、3 条里点名的指标**每一个都得是工具真返回的**,否则例句就是许愿:
   *   分配数=planned、未动过=untouched、已处理=progress.done、成功=outcomes.success、
   *   超期=expired、退回+原因=released/releaseReasons、不成功+原因=outcomes.failed/byOutcome。
   * ⚠️ 写「未动过」不写「曝光」、写「已处理」不写「完成」—— 都是服务端的原词,
   *   换个说法会把口径带偏(「已处理」≠ 谈成了,这条 note 服务端每次都在喊)。
   */
  const canDispatch = useHasPermission(Permission.PLAN_DISPATCH);
  // ⭐ 唯一一份在 assistant-chat（2026-08-15 合并）—— 此前这里抄了一份逐字相同的，
  //   而其中「窗口内」「成功/不成功」两处都是错的，两边各错一遍。
  const leaderExamples = EXAMPLES_LEADER;
  const examples = current?.patientName
    ? [
        `${current.patientName}为什么被召回?帮我捋一下关键事实`,
        `查一下${current.patientName}的画像和潜在治疗`,
        ...(canDispatch ? leaderExamples : ['这通电话怎么开口比较好?给我两句开场']),
        '今日推荐:挑几个该优先跟进的患者,并说明理由',
      ]
    : canDispatch
      ? // ⚠️ 主管空态**只给主管的活**（2026-08-15）：原来这里还挂着「今日推荐」
        //   「现在召回池里优先级最高的是谁」两条 —— 那是客服打单的动线，
        //   而「召回池」这个词他这一屏上根本没有（他看到的是「分一批新的」「我分的批次」）。
        leaderExamples
      : [
          '今日推荐:挑几个该优先跟进的患者,并说明理由',
          '现在召回池里优先级最高的是谁?',
        ];

  if (!allowed) return null;

  return (
    <>
      {/* 吸附窗:常驻挂载,关着时 invisible(状态不丢) */}
      <div
        style={maximized ? undefined : winRect ? { left: winRect.x, top: winRect.y } : undefined}
        className={cn(
          'fixed z-[60] flex flex-col overflow-hidden rounded-xl border shadow-2xl',
          'transition-[opacity,transform] duration-150',
          maximized
            ? 'inset-3' // 最大化:填满窗口(留 12px 边距),不写死宽高,随窗口自适应
            : // ⚠️ `w-[480px]` / `h-[760px]` 与上面 openAt 里的 480 / 760 是一对,改一处要改两处
              cn(!winRect && 'bottom-4 right-4', 'h-[760px] max-h-[88vh] w-[480px] max-w-[calc(100vw-2rem)]'),
          open ? 'translate-y-0 opacity-100' : 'pointer-events-none invisible translate-y-2 opacity-0',
        )}
      >
        <AssistantChat
          variant="widget"
          onClose={() => {
            setOpen(false);
            setMaximized(false); // 收起时还原尺寸,下次打开仍是小窗
          }}
          onToggleMaximize={() => setMaximized(!maximized)}
          maximized={maximized}
          examples={examples}
        />
      </div>
      {/* 助手入口(静态版):固定右下角,点击开/关助手窗;只在 AI 思考/说话、成约、切患者时原地反馈。
          完整桌宠(漫游/物理/狩猎)保留在 /pet-lab 演示页,不进工作区(老板反馈"做得太过")。 */}
      <AssistantFab open={open} onToggle={(r) => (open ? setOpen(false) : openAt(r))} />
      {/* 数据流舞台:挂在这里是因为它要的两个端点(格子 / FAB)都在同一屏,且与助手同生命周期 */}
      <CohortStream />
    </>
  );
}
