/**
 * Chat 状态机（XState v5）
 *
 * 鉴权解耦（事件驱动）：
 *   chat 机器不感知鉴权 / 不管 WebSocket —— 由 <ChatAuthSync /> 派生 loginStatus
 *   ChatAuthSync 只派 3 个事件：
 *     - `ChatGuestLogin` → 游客会话（断 WS = 不连）
 *     - `ChatNonGuestLogin { token }` → 非游客会话（连 WS）
 *     - `ChatLogout` → 登出（机器自动 cleanup WS actor）
 *
 * 状态结构（parent state 模式）：
 *   - `idle`：屏没挂 / 登出
 *   - `guestSession`（parent）：游客会话 —— 不 invoke WS
 *   - `userSession`（parent）：非游客会话 —— invoke WS（持久）
 *
 * init 双任务（核心重构）：
 *   - guestSession.initializing：loadQuota + loadHistory 并行（`invoke: [...]` 数组）
 *   - userSession.initializing：loadHistory + parent-level chatWebSocket 并行
 *   - 每个任务都是独立 actor，不互相等待（除非 `always` barrier）
 *
 * 消息发送路径（业务事实）：
 *   - `wsConnected: true`（仅 userSession 期间）→ 走 WS，不消耗次数（仅配额规则）
 *   - `wsConnected: false` → 走 HTTP，消耗次数（仅游客有意义 —— 非游客 0-1=max(0,-1)=0 天然 no-op）
 *   - `ChatWebSocketConnected` 事件 → `wsConnected = true`
 *   - `userSession.exit` action → `wsConnected = false`（WS actor cleanup 时也会跑 exit）
 *
 * 配额（仅游客）：
 *   - 非游客"无消息限制"（业务事实）—— 永不被赋值
 *   - `appendUserMessage` 减 `context.wsConnected ? q : Math.max(0, q-1)` —— WS 已连时不递减
 *
 */

import { setup, assign } from "xstate";

import { ChatStorage } from "@/data/storage/chat/chat_storage";
import { formatDate, todayString, Logger } from "@/utils";


import { ChatState, initialState } from "./chat-state";
import type { ChatEvent } from "./chat-events";
import {
  loadQuotaActor,
  loadHistoryActor,
  sendMessageHttpActor,
  sendMessageWsActor,
  loadMoreHistoryActor,
  chatWebSocketActor,
} from "./chat-machine.actors";

const log = new Logger("StoresChatChatMachine");

/** 游客配额阈值（与 Dart `GuestChatQuota` 保持一致） */
export const GuestChatQuota = {
  warningThreshold: 5,
  quotaExhausted: 0,
} as const;

// 重新导出 State / Event 类型，保持 machine 文件的公共 API 不变
export type { ChatState } from "./chat-state";
export { initialState } from "./chat-state";
export type { ChatEvent } from "./chat-events";

// ============================================================
// Machine
// ============================================================
export const chatMachine = setup({
  types: {
    context: {} as ChatState,
    events: {} as ChatEvent,
  },
  actors: {
    loadQuota: loadQuotaActor,
    loadHistory: loadHistoryActor,
    sendMessageHttp: sendMessageHttpActor,
    sendMessageWs: sendMessageWsActor,
    loadMoreHistory: loadMoreHistoryActor,
    chatWebSocket: chatWebSocketActor,
  },
  actions: {
    appendUserMessage: assign(({ context, event }) => {
      if (event.type !== "ChatSendMessage") return {};

      // 两个额度同步更新：WS 已连不递减（实时会话不消耗），HTTP 必须减
      const newRemaining = context.wsConnected
        ? context.guestRemainingQuota
        : Math.max(0, context.guestRemainingQuota - 1);
      const newTotal = context.wsConnected
        ? context.guestTotalQuota
        : Math.max(0, context.guestTotalQuota - 1);

      // 持久化两个额度（ fire-and-forget，不 await —— assign 是 sync）
      // daily quota 需要 today 字符串（让 ChatStorage 跨天判断 reset）
      const today = todayString();
      ChatStorage.getInstance().setGuestDailyChatQuota(newRemaining, today);

      ChatStorage.getInstance().setGuestTotalQuota(newTotal);

      log.debug("[chat-machine] appendUserMessage", {
        contentLength: event.content.length,
        contentPreview: event.content.slice(0, 50),
        quotaMode: context.wsConnected ? "ws (no decrement)" : "http (decrement)",
        oldRemaining: context.guestRemainingQuota,
        newRemaining,
        oldTotal: context.guestTotalQuota,
        newTotal,
        isReplyingAI: true,
      });

      return {
        messages: [
          ...context.messages,
          {
            content: event.content,
            isFromAI: false,
            date: today,
          },
        ],
        isReplyingAI: true,
        guestRemainingQuota: newRemaining,
        guestTotalQuota: newTotal,
      };
    }),

    appendUserImage: assign(({ context, event }) => {
      if (event.type !== "ChatSendImage") return {};
      // 同 appendUserMessage：两个额度同步减
      const newRemaining = context.wsConnected
        ? context.guestRemainingQuota
        : Math.max(0, context.guestRemainingQuota - 1);
      const newTotal = context.wsConnected
        ? context.guestTotalQuota
        : Math.max(0, context.guestTotalQuota - 1);
        
      const today = todayString();
      void ChatStorage.getInstance().setGuestDailyChatQuota(newRemaining, today);

      void ChatStorage.getInstance().setGuestTotalQuota(newTotal);
      
      log.debug("[chat-machine] appendUserImage", {
        oldMessagesCount: context.messages.length,
        wsConnected: context.wsConnected,
        quotaMode: context.wsConnected ? "ws (no decrement)" : "http (decrement)",
        oldRemaining: context.guestRemainingQuota,
        newRemaining,
        oldTotal: context.guestTotalQuota,
        newTotal,
      });
      return {
        messages: [
          ...context.messages,
          {
            content: "[Image]",
            isFromAI: false,
            date: today,
            imageUrl: event.imageBase64,
          },
        ],
        isReplyingAI: true,
        guestRemainingQuota: newRemaining,
        guestTotalQuota: newTotal,
      };
    }),

    appendOrUpdateAISentence: assign(({ context, event }) => {
      if (event.type !== "ChatAISentenceReceived") return {};
      const messages = [...context.messages];
      if (event.index === 0) {
        messages.push({
          content: event.text,
          isFromAI: true,
          date: formatDate(),
        });
      } else {
        const last = messages[messages.length - 1];
        if (last && last.isFromAI) {
          messages[messages.length - 1] = {
            ...last,
            content: `${last.content} ${event.text}`,
          };
        }
      }
      log.debug("[chat-machine] appendOrUpdateAISentence", {
        index: event.index,
        total: event.total,
        done: event.done,
        messagesCount: messages.length,
      });
      return { messages, isReplyingAI: !event.done };
    }),

    appendSocketErrorMessage: assign(({ context }) => {
      const messages = [
        ...context.messages,
        {
          content: "Something went wrong. Try sending again?",
          isFromAI: true,
          date: formatDate(),
        },
      ];
      return { messages, isReplyingAI: false };
    }),

    incrementQuotaExceeded: assign(({ context }) => ({
      quotaExceededTrigger: context.quotaExceededTrigger + 1,
    })),

    setWsConnected: assign({ wsConnected: true }),
    clearWsConnected: assign({ wsConnected: false }),
  },
}).createMachine({
  id: "chat",
  initial: "idle",
  context: initialState,
  states: {
    idle: {
      on: {
        ChatGuestLogin: "#chat.guestSession",
        ChatNonGuestLogin: "#chat.userSession",
      },
    },

    guestSession: {
      on: {
        ChatLogout: "#chat.idle",
        ChatNonGuestLogin: "#chat.userSession",
      },
      initial: "initializing",
      states: {
        initializing: {
          // "always" barrier：两个任务都完成才进 ready
          always: [
            {
              target: "ready",
              guard: ({ context }) => context.quotaLoaded && context.historyLoaded,
            },
          ],
          invoke: [
            // 任务 1：加载配额（fromPromise，返结果 + onDone 设 flag）
            {
              id: "loadQuota",
              src: "loadQuota",
              onDone: {
                actions: assign(({ event }) => ({
                  guestRemainingQuota: event.output.remaining,
                  guestTotalQuota: event.output.total,
                  quotaLoaded: true,
                })),
              },
              onError: {
                // 失败也标 loaded，不卡 init（游客可能 quota 服务不可用，让 UI 进 ready）
                actions: assign({
                  quotaLoaded: true,
                }),
              },
            },
            // 任务 2：拉 history（fromPromise，返回最终 network messages）
            {
              id: "loadHistory",
              src: "loadHistory",
              onDone: {
                actions: assign(({ event }) => ({
                  messages: event.output.messages,
                  hasMore: event.output.hasMore,
                  historyOffset: event.output.newOffset,
                  historyLoaded: true,
                })),
              },
              onError: {
                // 失败也标 loaded，不卡 init（让 UI 还是能进 ready，屏幕可能空）
                actions: assign({
                  historyLoaded: true,
                }),
              },
            },
          ],
        },
        ready: {
          on: {
            // 配额检查 + 发送消息（3 条 guard，按顺序匹配）
            // 1. 总配额（guestTotalQuota）不足 → 触发 quota exceeded dialog，不发送
            // 2. 每日配额（guestRemainingQuota）不足 → 同上
            // 3. 两个配额都 OK + 内容非空 → 发送 + 减配额
            ChatSendMessage: [
              // 1. 总配额 不足（guestTotalQuota <= 0） → 触发 quota exceeded dialog
              {
                guard: ({ context }) => context.guestTotalQuota <= 0,
                actions: "incrementQuotaExceeded",
              },
              // 2. 每日配额 不足（guestRemainingQuota <= 0） → 触发 quota exceeded dialog
              {
                guard: ({ context }) => context.guestRemainingQuota <= 0,
                actions: "incrementQuotaExceeded",
              },
              // 3. 两个配额都 OK + 内容非空 → 发送（只用 appendUserMessage 自己的日志）
              {
                actions: "appendUserMessage",
                guard: ({ event }) => event.content.trim().length > 0,
                target: "sending",
              },
            ],
            // 配额检查 + 发送图片（同样 3 条 guard，不检查 "内容空"）
            ChatSendImage: [
              {
                guard: ({ context }) => context.guestTotalQuota <= 0,
                actions: "incrementQuotaExceeded",
              },
              {
                guard: ({ context }) => context.guestRemainingQuota <= 0,
                actions: "incrementQuotaExceeded",
              },
              {
                actions: "appendUserImage",
                target: "sending",
              },
            ],
            // 删除 ChatLoadMoreHistory handler —— 游客无服务端 history，不支持翻页
            ChatAISentenceReceived: { actions: "appendOrUpdateAISentence" },
            ChatWebSocketError: { actions: "appendSocketErrorMessage" },
            ChatWebSocketConnected: { actions: "setWsConnected" },
            ChatQuotaExceeded: { actions: "incrementQuotaExceeded" },
          },
        },
        sending: {
          invoke: {
            src: "sendMessageHttp",
            input: ({ event }) => ({
              content: event.type === "ChatSendMessage" ? event.content : "",
            }),
            onDone: {
              target: "ready",
              actions: assign(({ context, event }) => ({
                messages: [...context.messages, event.output.reply],
                isReplyingAI: false,
              })),
            },
            onError: {
              target: "ready",
              actions: assign({ isReplyingAI: false }),
            },
          },
        },
      },
    },

    userSession: {
      // 父级 on：把 WS / AI 流句 事件从 ready.on 上提到 userSession.on
      // —— 任何 child state（initializing / ready / sendingViaWs / sendingViaHttp / loadingMore）
      //    都能收到 `ChatAISentenceReceived` 并把 AI 句 push 到 messages，
      //    这点是 sendingViaWs 流式回 reply 的关键（machine 当时在 sendingViaWs）
      //
      // 子级 override 规则：
      //   - sendingViaWs 重新声明 `ChatWebSocketError` 加 target: "ready"（带 action）
      //     —— XState v5: child handler 替换 parent；复制 action
      on: {
        ChatLogout: "#chat.idle",
        ChatNonGuestLogin: {
          target: "#chat.userSession",
          reenter: true,
        },
        ChatAISentenceReceived: { actions: "appendOrUpdateAISentence" },
        ChatWebSocketError: { actions: "appendSocketErrorMessage" },
        ChatWebSocketConnected: { actions: "setWsConnected" },
      },
      initial: "initializing",
      exit: "clearWsConnected",
      invoke: {
        // 父级：WS 长连接（一进来就连，跨 initializing/ready/sending*/loadingMore）
        src: "chatWebSocket",
        input: ({ event }) => ({
          token: event.type === "ChatNonGuestLogin" ? event.token : "",
        }),
      },
      states: {
        initializing: {
          // barrier：WS 连上 + history 加载完成才进 ready
          always: [
            {
              target: "ready",
              guard: ({ context }) => context.wsConnected && context.historyLoaded,
            },
          ],
          invoke: {
            // 任务 2：拉 history（只调用 loadHistoryActor，不再用 loadMoreHistoryActor）
            src: "loadHistory",
            onDone: {
              actions: assign(({ event }) => ({
                messages: event.output.messages,
                isLoadingMore: false,
                hasMore: event.output.hasMore,
                historyOffset: event.output.newOffset,
                historyLoaded: true,
              })),
            },
            onError: {
              // 失败也标 loaded，不卡 init
              actions: assign({
                isLoadingMore: false,
                historyLoaded: true,
              }),
            },
          },
        },
        ready: {
          on: {
            // 发送消息：wsConnected → 走 WS（流式回 reply），否则 → 走 HTTP（一次性回 reply）
            // 两个 branch 都有 "content 非空" guard（仅作 fallback 兜底：wsConnected=true 也需
            // 内容非空；wsConnected=false 同理）
            ChatSendMessage: [
              {
                // WS 已连 + 内容非空 → 走 WS（sendingViaWs 等 AI 流式回 reply）
                guard: ({ context, event }) =>
                  context.wsConnected && event.content.trim().length > 0,
                actions: "appendUserMessage",
                target: "sendingViaWs",
              },
              {
                // WS 未连（或 fallback） + 内容非空 → 走 HTTP（sendingViaHttp 一次性回 reply）
                guard: ({ event }) => event.content.trim().length > 0,
                actions: "appendUserMessage",
                target: "sendingViaHttp",
              },
            ],
            ChatSendImage: {
              actions: "appendUserImage",
            },
            ChatLoadMoreHistory: {
              target: "loadingMore",
            },
            ChatQuotaExceeded: { actions: "incrementQuotaExceeded" },
            // 注：ChatAISentenceReceived / ChatWebSocketError / ChatWebSocketConnected
            //     已上提到 userSession.on，子级不再声明
          },
        },
        // WS 发送：invoke sendMessageWsActor 触发一次 send，
        // 之后 AI 流式回 reply 走 userSession.on 的 ChatAISentenceReceived → appendOrUpdateAISentence
        // 当句尾 done: true → isReplyingAI = false → always 跳回 ready
        sendingViaWs: {
          on: {
            // 流式回包中 WS 出错（mid-stream） → 加错误泡 + 跳回 ready（不卡在 sendingViaWs）
            // 这个 child handler 替换 userSession.on 的 ChatWebSocketError（XState v5 不合并）
            // 所以需要重复声明 action: "appendSocketErrorMessage"
            ChatWebSocketError: {
              target: "ready",
              actions: "appendSocketErrorMessage",
            },
          },
          invoke: {
            src: "sendMessageWs",
            input: ({ event }) => ({
              content: event.type === "ChatSendMessage" ? event.content : "",
            }),
            onError: {
              // sendMessageWsActor 自身抛错（无 active ws / ws not OPEN） → 回 ready + 清 typing
              target: "ready",
              actions: assign({ isReplyingAI: false }),
            },
          },
          // 监听 isReplyingAI —— appendOrUpdateAISentence 在 done: true 时设 false
          always: [
            {
              target: "ready",
              guard: ({ context }) => !context.isReplyingAI,
            },
          ],
        },
        // HTTP 发送：一次发送 = 一次返回（保留原 sending 状态语义，guest 与 ws-down fallback 走此）
        sendingViaHttp: {
          invoke: {
            src: "sendMessageHttp",
            input: ({ event }) => ({
              content: event.type === "ChatSendMessage" ? event.content : "",
            }),
            onDone: {
              target: "ready",
              actions: assign(({ context, event }) => ({
                messages: [...context.messages, event.output.reply],
                isReplyingAI: false,
              })),
            },
            onError: {
              target: "ready",
              actions: assign({ isReplyingAI: false }),
            },
          },
        },
        loadingMore: {
          invoke: {
            src: "loadMoreHistory",
            input: ({ context }) => ({ offset: context.historyOffset }),
            onDone: {
              target: "ready",
              actions: assign(({ context, event }) => ({
                messages: [...event.output.messages, ...context.messages],
                isLoadingMore: false,
                hasMore: event.output.hasMore,
                historyOffset: event.output.newOffset,
              })),
            },
            onError: {
              target: "ready",
              actions: assign({ isLoadingMore: false }),
            },
          },
        },
      },
    },
  },
});

export type ChatMachine = typeof chatMachine;
