import { createReducer, on } from '@ngrx/store';
import {
  EntityState,
  EntityAdapter,
  createEntityAdapter
} from '@ngrx/entity';
import { DELETED_MESSAGE_CONTENT, Message } from '../../shared-kernel';
import { MessagesActions } from './messages.actions';

/** State shape for the messages feature slice, extending NgRx EntityState. */
export interface MessagesState extends EntityState<Message> {
  /** Whether messages are being loaded from the database. */
  loading: boolean;
  /** Whether a peer-to-peer sync cycle is in progress. */
  syncing: boolean;
  /** Whether a scroll-up older-page fetch is currently in flight. */
  loadingOlder: boolean;
  /** Most recent error message from message operations. */
  error: string | null;
  /** ID of the room whose messages are currently loaded. */
  currentRoomId: string | null;
  /**
   * Conversation keys (`${roomId}:${channelId}`) that have been paginated
   * all the way back to the start of the local DB history. Used by the
   * scroll-up handler to stop issuing further DB pages.
   */
  exhaustedConversations: Record<string, true>;
}

export const messagesAdapter: EntityAdapter<Message> = createEntityAdapter<Message>({
  selectId: (message) => message.id,
  sortComparer: (messageA, messageB) => messageA.timestamp - messageB.timestamp
});

export const initialState: MessagesState = messagesAdapter.getInitialState({
  loading: false,
  syncing: false,
  loadingOlder: false,
  error: null,
  currentRoomId: null,
  exhaustedConversations: {}
});

export const messagesReducer = createReducer(
  initialState,

  // Load messages - keep cached messages from other rooms in the slice so a
  // return visit (or a prefetched room) renders immediately from memory. The
  // selectors (`selectChannelMessages`, `channelMessages` computed) already
  // filter by `currentRoom.id`, so leaving stale rooms in the entity adapter
  // is safe. Memory cost is ~30 messages per saved room; tracked at
  // /memories/repo/electron-server-switch-performance.md.
  on(MessagesActions.loadMessages, (state, { roomId }) => ({
    ...state,
    loading: true,
    error: null,
    currentRoomId: roomId,
    exhaustedConversations: state.currentRoomId === roomId
      ? state.exhaustedConversations
      : {}
  })),

  on(MessagesActions.loadMessagesSuccess, (state, { messages }) =>
    messagesAdapter.upsertMany(messages, {
      ...state,
      loading: false
    })
  ),

  // Background prefetch result: merge into the cache without touching the
  // active-room loading flag or currentRoomId.
  on(MessagesActions.prefetchRoomMessagesSuccess, (state, { messages }) =>
    messagesAdapter.upsertMany(messages, state)
  ),

  on(MessagesActions.loadMessagesFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error
  })),

  // Load older messages - paginate backwards from the DB on scroll-up.
  on(MessagesActions.loadOlderMessages, (state) => ({
    ...state,
    loadingOlder: true,
    error: null
  })),

  on(MessagesActions.loadOlderMessagesSuccess, (state, { conversationKey, messages, reachedEnd }) =>
    messagesAdapter.upsertMany(messages, {
      ...state,
      loadingOlder: false,
      exhaustedConversations: reachedEnd
        ? { ...state.exhaustedConversations,
          [conversationKey]: true }
        : state.exhaustedConversations
    })
  ),

  on(MessagesActions.loadOlderMessagesFailure, (state, { error }) => ({
    ...state,
    loadingOlder: false,
    error
  })),

  // Send message
  on(MessagesActions.sendMessage, (state) => ({
    ...state,
    loading: true
  })),

  on(MessagesActions.sendMessageSuccess, (state, { message }) =>
    messagesAdapter.addOne(message, {
      ...state,
      loading: false
    })
  ),

  on(MessagesActions.sendMessageFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error
  })),

  // Receive message from peer
  on(MessagesActions.receiveMessage, (state, { message }) =>
    messagesAdapter.upsertOne(message, state)
  ),

  // Edit message
  on(MessagesActions.editMessageSuccess, (state, { messageId, content, editedAt }) =>
    messagesAdapter.updateOne(
      {
        id: messageId,
        changes: { content,
          editedAt }
      },
      state
    )
  ),

  // Delete message
  on(MessagesActions.deleteMessageSuccess, (state, { messageId }) =>
    messagesAdapter.updateOne(
      {
        id: messageId,
        changes: {
          content: DELETED_MESSAGE_CONTENT,
          isDeleted: true,
          reactions: []
        }
      },
      state
    )
  ),

  // Add reaction
  on(MessagesActions.addReactionSuccess, (state, { reaction }) => {
    const message = state.entities[reaction.messageId];

    if (!message)
      return state;

    const existingReaction = message.reactions.find(
      (existing) => existing.emoji === reaction.emoji && existing.userId === reaction.userId
    );

    if (existingReaction)
      return state;

    return messagesAdapter.updateOne(
      {
        id: reaction.messageId,
        changes: {
          reactions: [...message.reactions, reaction]
        }
      },
      state
    );
  }),

  // Remove reaction
  on(MessagesActions.removeReactionSuccess, (state, { messageId, emoji, oderId }) => {
    const message = state.entities[messageId];

    if (!message)
      return state;

    return messagesAdapter.updateOne(
      {
        id: messageId,
        changes: {
          reactions: message.reactions.filter(
            (existingReaction) => !(existingReaction.emoji === emoji && existingReaction.userId === oderId)
          )
        }
      },
      state
    );
  }),

  // Sync lifecycle
  on(MessagesActions.startSync, (state) => ({
    ...state,
    syncing: true
  })),

  on(MessagesActions.syncComplete, (state) => ({
    ...state,
    syncing: false
  })),

  // Sync messages from peer (merge reactions to avoid losing local-only reactions)
  on(MessagesActions.syncMessages, (state, { messages }) => {
    const merged = messages.map(message => {
      const existing = state.entities[message.id];

      if (existing?.reactions?.length) {
        const combined = [...(message.reactions ?? [])];

        for (const existingReaction of existing.reactions) {
          const alreadyExists = combined.some((combinedReaction) =>
            combinedReaction.userId === existingReaction.userId &&
            combinedReaction.emoji === existingReaction.emoji &&
            combinedReaction.messageId === existingReaction.messageId
          );

          if (!alreadyExists) {
            combined.push(existingReaction);
          }
        }

        return { ...message,
          reactions: combined };
      }

      return message;
    });

    return messagesAdapter.upsertMany(merged, {
      ...state,
      syncing: false,
      // Peer sync may have inserted messages older than our current oldest;
      // reopen pagination so the scroll-up handler revisits the DB.
      exhaustedConversations: {}
    });
  }),

  // Update link metadata on a message
  on(MessagesActions.updateLinkMetadata, (state, { messageId, linkMetadata }) =>
    messagesAdapter.updateOne(
      {
        id: messageId,
        changes: { linkMetadata }
      },
      state
    )
  ),

  // Clear messages
  on(MessagesActions.clearMessages, (state) =>
    messagesAdapter.removeAll({
      ...state,
      currentRoomId: null,
      exhaustedConversations: {}
    })
  )
);
