'use client';

import { create } from 'zustand';
import { emitPetEvent } from '@/lib/pet-events';
import { STREAM_TOTAL_MS } from '@/components/assistant/cohort-stream';

/**
 * 助手窗的外部控制口 —— 让**业务代码**能打开助手并塞一句话进去。
 *
 * ── 为什么必须有这个 store ──────────────────────────────────
 * 在此之前助手窗是完全自闭的:
 *   · `AssistantWidget` 的 `open` 是组件内 `useState`,外面打不开;
 *   · `useAssistantChat()` 在 `AssistantChat` 内部实例化,`send` 不外露。
 * 于是「在召回池点一下,把这批人移交给助手」这件事**根本无处落地** ——
 * 不是难写,是没有接口。这 30 行就是那个接口。
 *
 * ── 为什么用 seq 而不是"消费完置空" ─────────────────────────
 * 与 `plan-sync-store` 同款:自增序号触发订阅,消费方按 seq 变化执行一次。
 * 用"置空"表达已消费需要消费方回写 store,那是双向数据流,
 * StrictMode 双执行下会变成"发一次跑两次 / 或一次都不跑"。
 *
 * ⚠️ `AssistantWidget` 收起时是 **CSS 隐藏而非卸载**(见那里的注释:对话/工具步骤/
 * 听写状态全靠这个保留)。store 化之后**必须保持这个特性** ——
 * 若顺手改成条件渲染,主管每收一次助手就丢一次对话,而这在代码上看不出任何异常。
 */
interface AssistantState {
  open: boolean;
  setOpen: (open: boolean) => void;
  /**
   * 最大化。⚠️ 原先是 `AssistantWidget` 的组件内 `useState` —— 与 `open` 当初同一个毛病:
   * 业务侧要"移交后直接开大窗"就够不着。提到 store 后两者一起由外部驱动。
   */
  maximized: boolean;
  setMaximized: (maximized: boolean) => void;
  /**
   * 待发送的一句话。`seq` 自增即为"有新请求";`text` 是要替主管说的话。
   * null = 没有待处理请求。
   */
  pending: { text: string; seq: number } | null;
  /** 打开助手并发一句话(移交入口用);maximize=true 时同时开大窗 */
  ask: (text: string, opts?: { maximize?: boolean }) => void;
  /**
   * 人群移交 —— 发数据流动画,**等它飞完**再开助手。
   *
   * ⚠️ 为什么不在业务侧自己 setTimeout:那样每个调用点都要知道动画多长,
   *   改一次时长要满仓找。时长的真理源在 cohort-stream(舞台层),这里引它。
   * ⚠️ 为什么不让舞台层自己开助手:那会让"怎么演"反过来决定"接下来做什么" ——
   *   业务语义(要说哪句话、开不开大窗)必须留在业务这边。
   *   舞台只负责演,时长是它对外的唯一契约。
   */
  handoff: (input: {
    text: string;
    from: { x: number; y: number; w: number; h: number };
    count: number;
    treatment?: string;
    temperature?: string;
  }) => void;
  /** 消费方(AssistantChat)发完后调,避免重复发送 */
  consume: (seq: number) => void;
}

export const useAssistantStore = create<AssistantState>((set, get) => ({
  open: false,
  setOpen: (open) => set({ open }),
  maximized: false,
  setMaximized: (maximized) => set({ maximized }),
  pending: null,
  ask: (text, opts) => {
    const seq = (get().pending?.seq ?? 0) + 1;
    // 先开窗再挂待办:窗没开就发消息,主管会看到助手"自己动了"却不知道在哪
    set({ open: true, pending: { text, seq }, ...(opts?.maximize ? { maximized: true } : {}) });
  },
  handoff: ({ text, from, count, treatment, temperature }) => {
    // ① 先发语义事件:舞台层据此放数据流,大脑层据此进"吸收"姿态
    emitPetEvent({ type: 'cohort_handoff', payload: { count, treatment, temperature, from } });
    // ② 粒子飞完再开窗 —— 先开窗会把飞行路径整个盖住,演了等于没演
    window.setTimeout(() => get().ask(text, { maximize: true }), STREAM_TOTAL_MS);
  },
  consume: (seq) => {
    // 只清掉自己那一条 —— 期间若又来了新请求(seq 更大),不能连它一起清
    if (get().pending?.seq === seq) set({ pending: null });
  },
}));
