import { fromCallback, fromPromise } from "xstate";

import { ExceptionHandler } from "@/core/errors";
import { MessageQueue } from "@/core/net/message-queue";
import type { ChatSendResponse } from "@/data/dto/chat";
import { getChatRepository } from "@/data/repositories/chat_repository";
import { resolveChatCacheOwnerKey } from "@/data/repositories/chat_cache_identity";
import { Logger } from "@/utils/logger";
import { Result } from "@/utils/result";

import type { ChatEvent } from "../../chat-events";
import { sendResponseToUiMessage } from "../../helper/message-mappers";

const log = new Logger("StoresChatChatSendFlow");

type UiMessage = import("@/data/dto/chat").UiMessage;

export const sendMessageHttpActor = fromPromise<
  { response: ChatSendResponse; reply: UiMessage | null },
  { content: string }
>(async ({ input }) => {
  return sendMessageViaHttp(input.content);
});

export const httpMessageQueueActor = fromCallback<ChatEvent>(
  ({ sendBack, receive }) => {
    return createMessageQueueActor(sendBack, receive);
  },
);

function createMessageQueueActor(
  sendBack: (event: ChatEvent) => void,
  receive: (listener: (event: ChatEvent) => void) => void,
): () => void {
  const queue = new MessageQueue();

  queue.setConsumer(async (content) => {
    sendBack({ type: "ChatQueuedSendStarted" });
    try {
      const output = await sendMessageViaHttp(content);
      sendBack({ type: "ChatQueuedHttpDone", output });
    } catch (error) {
      const errorMessage = ExceptionHandler.message(error);
      log.error("[chat-machine] message queue send failed", {
        error,
      });
      sendBack({
        type: "ChatQueuedSendError",
        content,
        errorMessage,
      });
    }
  });

  receive((event) => {
    if (event.type !== "ChatSendMessage") return;
    const content = event.content.trim();
    if (content.length === 0) return;
    queue.enqueue(content);
  });

  return () => queue.dispose();
}

async function sendMessageViaHttp(content: string): Promise<{
  response: ChatSendResponse;
  reply: UiMessage | null;
}> {
  const chatRepo = getChatRepository();
  const cacheIdentityResult = await resolveChatCacheOwnerKey();
  const result = await chatRepo.sendMessage(content);
  if (Result.isErr(result)) {
    log.error("[chat-machine] sendMessageHttpActor failed", {
      error: result.error,
    });
    throw result.error;
  }
  if (Result.isOk(cacheIdentityResult)) {
    void chatRepo.prefetchMediaForSendResponse(
      result.data,
      cacheIdentityResult.data,
    );
  }
  const isInsufficientCredits =
    result.data.canSendMessage === false &&
    !hasRenderableSendResponse(result.data);
  return {
    response: result.data,
    reply:
      isInsufficientCredits ? null : sendResponseToUiMessage(result.data),
  };
}

function hasRenderableSendResponse(response: ChatSendResponse): boolean {
  const isLockedPaidMessage =
    response.lockDetail.locked &&
    (response.lockDetail.reason === "private_message" ||
      response.lockDetail.reason === "voice_message" ||
      response.lockDetail.reason === "image_paywall" ||
      response.lockDetail.reason === "image");

  return (
    response.reply.trim().length > 0 ||
    (!response.lockDetail.locked && response.audioUrl.trim().length > 0) ||
    Boolean(response.image.url) ||
    isLockedPaidMessage
  );
}
