import { assign, setup } from "xstate";

import type { PrivateRoomMoment } from "@/data/dto/private-room";

import type { PrivateRoomEvent } from "./private-room-events";
import {
  appendMomentsResponse,
  applyMomentsResponse,
  replaceMoment,
  toPaywallRequest,
  toPrivateRoomErrorMessage,
  toUnlockErrorMessage,
} from "./private-room-machine.helpers";
import {
  loadMorePrivateRoomMomentsActor,
  loadPrivateRoomMomentsActor,
  unlockPrivateRoomMomentActor,
} from "./private-room-machine.actors";
import { initialState, type PrivateRoomState } from "./private-room-state";

export type { PrivateRoomEvent } from "./private-room-events";
export type { PrivateRoomState } from "./private-room-state";
export { initialState } from "./private-room-state";

function getPendingMoment(context: PrivateRoomState): PrivateRoomMoment | null {
  if (!context.pendingConfirmMomentId) return null;
  return (
    context.items.find(
      (item) => item.momentId === context.pendingConfirmMomentId,
    ) ?? null
  );
}

export const privateRoomMachine = setup({
  types: {
    context: {} as PrivateRoomState,
    events: {} as PrivateRoomEvent,
  },
  actors: {
    loadMoments: loadPrivateRoomMomentsActor,
    loadMoreMoments: loadMorePrivateRoomMomentsActor,
    unlockMoment: unlockPrivateRoomMomentActor,
  },
}).createMachine({
  id: "privateRoom",
  initial: "idle",
  context: initialState,
  states: {
    idle: {
      on: {
        PrivateRoomInit: "loading",
      },
    },

    loading: {
      entry: assign({
        errorMessage: null,
        unlockPaywallRequest: null,
      }),
      invoke: {
        src: "loadMoments",
        onDone: {
          target: "ready",
          actions: assign(({ event }) => applyMomentsResponse(event.output)),
        },
        onError: {
          target: "failed",
          actions: assign(({ event }) => ({
            errorMessage: toPrivateRoomErrorMessage(event.error),
          })),
        },
      },
    },

    failed: {
      on: {
        PrivateRoomInit: "loading",
        PrivateRoomRefresh: "loading",
      },
    },

    ready: {
      on: {
        PrivateRoomInit: {
          target: "loading",
        },
        PrivateRoomRefresh: {
          target: "loading",
        },
        PrivateRoomLoadMore: [
          {
            guard: ({ context }) =>
              context.hasMore && context.nextCursor !== null,
            target: "loadingMore",
          },
        ],
        PrivateRoomUnlockRequested: {
          actions: assign(({ event }) => ({
            pendingConfirmMomentId: event.momentId,
            unlockErrorMessage: null,
            unlockPaywallRequest: null,
          })),
        },
        PrivateRoomUnlockCancelled: {
          actions: assign({
            pendingConfirmMomentId: null,
          }),
        },
        PrivateRoomUnlockConfirmed: [
          {
            guard: ({ context }) => getPendingMoment(context) !== null,
            target: "unlocking",
          },
        ],
        PrivateRoomUnlockPaywallConsumed: {
          actions: assign({
            unlockPaywallRequest: null,
          }),
        },
      },
    },

    loadingMore: {
      invoke: {
        src: "loadMoreMoments",
        input: ({ context }) => ({
          cursor: context.nextCursor,
        }),
        onDone: {
          target: "ready",
          actions: assign(({ context, event }) =>
            appendMomentsResponse(context, event.output),
          ),
        },
        onError: {
          target: "ready",
          actions: assign(({ event }) => ({
            errorMessage: toPrivateRoomErrorMessage(event.error),
          })),
        },
      },
    },

    unlocking: {
      entry: assign(({ context }) => ({
        unlockingMomentId: context.pendingConfirmMomentId,
        unlockErrorMessage: null,
        unlockPaywallRequest: null,
      })),
      invoke: {
        src: "unlockMoment",
        input: ({ context }) => {
          const pendingMoment = getPendingMoment(context);
          return {
            momentId: pendingMoment?.momentId ?? "",
            expectedCost: pendingMoment?.unlockCost ?? 0,
          };
        },
        onDone: [
          {
            guard: ({ event }) => event.output.unlocked,
            target: "ready",
            actions: assign(({ context, event }) => ({
              items: replaceMoment(context.items, event.output),
              creditBalance: event.output.creditBalance || context.creditBalance,
              pendingConfirmMomentId: null,
              unlockingMomentId: null,
              unlockErrorMessage: null,
              unlockSuccessNonce: context.unlockSuccessNonce + 1,
            })),
          },
          {
            guard: ({ event }) =>
              event.output.reason === "insufficient_credits",
            target: "ready",
            actions: assign(({ context, event }) => ({
              items: replaceMoment(context.items, event.output),
              creditBalance: event.output.creditBalance || context.creditBalance,
              pendingConfirmMomentId: null,
              unlockingMomentId: null,
              unlockErrorMessage: null,
              unlockPaywallRequest: toPaywallRequest(event.output),
            })),
          },
          {
            guard: ({ event }) => event.output.reason === "cost_changed",
            target: "loading",
            actions: assign(({ event }) => ({
              pendingConfirmMomentId: null,
              unlockingMomentId: null,
              unlockErrorMessage: toUnlockErrorMessage(event.output),
            })),
          },
          {
            target: "ready",
            actions: assign(({ context, event }) => ({
              items: replaceMoment(context.items, event.output),
              pendingConfirmMomentId: null,
              unlockingMomentId: null,
              unlockErrorMessage: toUnlockErrorMessage(event.output),
            })),
          },
        ],
        onError: {
          target: "ready",
          actions: assign(({ event }) => ({
            pendingConfirmMomentId: null,
            unlockingMomentId: null,
            unlockErrorMessage: toPrivateRoomErrorMessage(event.error),
          })),
        },
      },
    },
  },
});

export type PrivateRoomMachine = typeof privateRoomMachine;
