"use client";

import { type Dispatch, type ReactNode, useMemo } from "react";
import { createActorContext, shallowEqual } from "@xstate/react";
import type { SnapshotFrom } from "xstate";

import { chatMachine } from "./chat-machine";
import type { ChatEvent, ChatState as MachineContext } from "./chat-machine";
import { appendPromotionMessage } from "./helper/promotion";

/** Chat UI reads this projection instead of the machine snapshot directly. */
interface ChatState {
  characterId: string;
  characterErrorCode: MachineContext["characterErrorCode"];
  messages: MachineContext["messages"];
  historyMessages: MachineContext["messages"];
  promotion: MachineContext["promotion"];
  outgoingMessageRevision: number;
  isReplyingAI: boolean;
  canSendMessage: boolean;
  upgradePromptVisible: boolean;
  upgradeReason: MachineContext["upgradeReason"];
  paymentGuidance: MachineContext["paymentGuidance"];
  /** True as soon as a local snapshot or the first network result is renderable. */
  historyLoaded: boolean;
  /** True after the network history request has completed or failed. */
  networkHistoryLoaded: boolean;
  hasMoreHistory: boolean;
  isLoadingMoreHistory: boolean;
  unlockHistoryPromptVisible: boolean;
  lockedHistoryCount: number;
  isUnlockingHistory: boolean;
  unlockHistoryError: string | null;
  isUnlockingMessage: boolean;
  unlockingMessageId: string | null;
  unlockMessageError: string | null;
  unlockPaywallRequest: MachineContext["unlockPaywallRequest"];
}

type ChatSnapshot = SnapshotFrom<typeof chatMachine>;
type ChatSelector<T> = (snapshot: ChatSnapshot) => T;

const ChatActorContext = createActorContext(chatMachine);

export interface ChatProviderProps {
  children: ReactNode;
  characterId: string;
  emptyChatGreeting: string;
}

export function ChatProvider({
  children,
  characterId,
  emptyChatGreeting,
}: ChatProviderProps) {
  return (
    <ChatActorContext.Provider
      options={{ input: { characterId, emptyChatGreeting } }}
    >
      {children}
    </ChatActorContext.Provider>
  );
}

export function useChatState(): ChatState {
  const selected = useChatSelector(selectChatState, shallowEqual);
  const messages = useMemo(
    () => appendPromotionMessage(selected.historyMessages, selected.promotion),
    [selected.historyMessages, selected.promotion],
  );
  return useMemo(() => ({ ...selected, messages }), [messages, selected]);
}

export function useChatDispatch(): Dispatch<ChatEvent> {
  return ChatActorContext.useActorRef().send;
}

export function useChatSelector<T>(
  selector: ChatSelector<T>,
  compare?: (previous: T, next: T) => boolean,
): T {
  return ChatActorContext.useSelector(selector, compare);
}

type SelectedChatState = Omit<ChatState, "messages">;

function selectChatState(state: ChatSnapshot): SelectedChatState {
  return {
    characterId: state.context.characterId,
    characterErrorCode: state.context.characterErrorCode,
    historyMessages: state.context.messages,
    promotion: state.context.promotion,
    outgoingMessageRevision: state.context.outgoingMessageRevision,
    isReplyingAI: state.context.isReplyingAI,
    canSendMessage: state.context.canSendMessage,
    upgradePromptVisible: state.context.upgradePromptVisible,
    upgradeReason: state.context.upgradeReason,
    paymentGuidance: state.context.paymentGuidance,
    historyLoaded: state.context.historyLoaded,
    networkHistoryLoaded: state.context.networkHistoryLoaded,
    hasMoreHistory:
      state.context.historyLoaded &&
      state.context.nextHistoryOffset < state.context.historyTotal,
    isLoadingMoreHistory: state.context.isLoadingMoreHistory,
    unlockHistoryPromptVisible: state.context.unlockHistoryPromptVisible,
    lockedHistoryCount: state.context.lockedHistoryCount,
    isUnlockingHistory: state.matches({ userSession: "unlockingHistory" }),
    unlockHistoryError: state.context.unlockHistoryError,
    isUnlockingMessage: state.matches({ userSession: "unlockingMessage" }),
    unlockingMessageId:
      state.context.unlockingMessage?.displayMessageId ?? null,
    unlockMessageError: state.context.unlockMessageError,
    unlockPaywallRequest: state.context.unlockPaywallRequest,
  };
}
