import { fromCallback } from "xstate";

import type { UiMessage } from "@/stores/chat/ui-message";
import { loadChatRepository } from "@/data/repositories/chat_repository_loader";
import { Logger } from "@/utils/logger";
import { Result } from "@/utils/result";
import { isAbortError } from "@/utils/abort";

import {
  readLocalHistorySnapshot,
  resolveHistoryCacheIdentity,
  syncNetworkHistory,
} from "../../chat-history-sync";
import type { ChatEvent } from "../../chat-events";
import { normalizeHistoryLimit } from "../../helper/history";
import { localMessagesToUi } from "../../helper/message-mappers";

const log = new Logger("StoresChatChatHistoryFlow");

export interface LoadMoreHistoryActorEvent {
  type: "LoadMoreHistoryPageRequested";
  offset: number;
  limit: number;
}

export interface ChatHistoryActorInput {
  characterId: string;
  emptyChatGreeting: string;
}

export interface LoadMoreHistoryOutput {
  messages: UiMessage[];
  offset: number;
  total: number;
  limit: number;
}

export const loadHistoryActor = fromCallback<
  ChatEvent,
  ChatHistoryActorInput
>(({ input, sendBack }) => {
  let cancelled = false;
  const controller = new AbortController();

  void (async () => {
    const cacheIdentity = await resolveHistoryCacheIdentity(input.characterId);
    const localSnapshot = await readLocalHistorySnapshot(
      cacheIdentity,
      input.characterId,
      input.emptyChatGreeting,
    );
    if (cancelled) return;
    sendBack({
      type: "ChatLocalHistoryLoaded",
      output: localSnapshot,
    });

    const networkSnapshot = await syncNetworkHistory(
      input.characterId,
      localSnapshot.localCount,
      localSnapshot.messages.map((message) => message.displayId),
      cacheIdentity,
      input.emptyChatGreeting,
      controller.signal,
    );
    if (cancelled || !networkSnapshot) return;
    sendBack({
      type: "ChatNetworkHistoryLoaded",
      output: networkSnapshot,
    });
  })().catch((error: unknown) => {
    if (cancelled || isAbortError(error)) return;
    log.error("[chat-machine] loadHistoryActor failed", { error });
    sendBack({ type: "ChatHistoryLoadFailed", error });
  });

  return () => {
    cancelled = true;
    controller.abort();
  };
});

export const loadMoreHistoryActor =
  fromCallback<LoadMoreHistoryActorEvent, ChatHistoryActorInput>(
    ({ input, receive, sendBack }) => {
      let cancelled = false;
      let running = false;
      let activeController: AbortController | null = null;

      receive((event) => {
        if (running || cancelled) return;
        running = true;
        const controller = new AbortController();
        activeController = controller;

        void (async () => {
          const chatRepo = await loadChatRepository();
          const historyResult = await chatRepo.getHistory(
            input.characterId,
            event.limit,
            event.offset,
            { signal: controller.signal },
          );
          if (Result.isErr(historyResult)) throw historyResult.error;
          if (cancelled) return;

          const response = historyResult.data;
          sendBack({
            type: "ChatOlderHistoryLoaded",
            output: {
              messages: localMessagesToUi(response.messages),
              offset: event.offset,
              total: response.total,
              limit: normalizeHistoryLimit(response.limit, event.limit),
            } satisfies LoadMoreHistoryOutput,
          });
          void resolveHistoryCacheIdentity(input.characterId).then(
            (cacheIdentity) => {
              if (!cacheIdentity) return;
              void chatRepo.prefetchMediaForMessages(
                response.messages,
                input.characterId,
                cacheIdentity,
              );
            },
          );
        })()
          .catch((error: unknown) => {
            if (cancelled || isAbortError(error)) return;
            log.warn("[chat-machine] loadMoreHistoryActor failed", { error });
            sendBack({ type: "ChatOlderHistoryLoadFailed", error });
          })
          .finally(() => {
            if (activeController === controller) activeController = null;
            running = false;
          });
      });

      return () => {
        cancelled = true;
        activeController?.abort();
      };
    },
  );
