/* eslint-disable @typescript-eslint/member-ordering */
import { Injectable, inject } from '@angular/core';
import {
  Actions,
  createEffect,
  ofType
} from '@ngrx/effects';
import { Store } from '@ngrx/store';
import {
  filter,
  tap,
  withLatestFrom
} from 'rxjs/operators';
import type { Message } from '../../../../shared-kernel';
import { MessagesActions } from '../../../../store/messages/messages.actions';
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
import { selectCurrentRoom, selectSavedRooms } from '../../../../store/rooms/rooms.selectors';
import { UsersActions } from '../../../../store/users/users.actions';
import { selectCurrentUser } from '../../../../store/users/users.selectors';
import { NotificationsFacade } from '../facades/notifications.facade';

export function groupMessagesByRoom(messages: Message[]): Map<string, Message[]> {
  const messagesByRoom = new Map<string, Message[]>();

  for (const message of messages) {
    const roomMessages = messagesByRoom.get(message.roomId);

    if (roomMessages) {
      roomMessages.push(message);
      continue;
    }

    messagesByRoom.set(message.roomId, [message]);
  }

  return messagesByRoom;
}

@Injectable()
export class NotificationsEffects {
  private readonly actions$ = inject(Actions);
  private readonly store = inject(Store);
  private readonly notifications = inject(NotificationsFacade);

  syncRoomCatalog$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(
          RoomsActions.loadRoomsSuccess,
          RoomsActions.createRoomSuccess,
          RoomsActions.joinRoomSuccess,
          RoomsActions.viewServerSuccess,
          RoomsActions.updateRoom,
          RoomsActions.addChannel,
          RoomsActions.removeChannel,
          RoomsActions.renameChannel,
          RoomsActions.forgetRoomSuccess,
          RoomsActions.deleteRoomSuccess
        ),
        withLatestFrom(this.store.select(selectSavedRooms)),
        tap(([, rooms]) => {
          this.notifications.syncRoomCatalog(rooms);
        })
      ),
    { dispatch: false }
  );

  hydrateUnreadCounts$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(RoomsActions.loadRoomsSuccess, UsersActions.loadCurrentUserSuccess),
        withLatestFrom(
          this.store.select(selectCurrentUser),
          this.store.select(selectSavedRooms)
        ),
        filter(([, currentUser]) => !!currentUser),
        tap(([
          , , rooms
        ]) => {
          void this.notifications.hydrateUnreadCounts(rooms);
        })
      ),
    { dispatch: false }
  );

  markVisibleChannelRead$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(
          RoomsActions.createRoomSuccess,
          RoomsActions.joinRoomSuccess,
          RoomsActions.viewServerSuccess,
          RoomsActions.selectChannel
        ),
        tap(() => {
          this.notifications.markCurrentChannelReadIfActive();
        })
      ),
    { dispatch: false }
  );

  handleIncomingMessage$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(MessagesActions.receiveMessage),
        tap(({ message }) => {
          void this.notifications.handleIncomingMessage(message);
        })
      ),
    { dispatch: false }
  );

  refreshViewedRoomUnread$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(MessagesActions.loadMessagesSuccess),
        withLatestFrom(this.store.select(selectCurrentRoom)),
        tap(([{ messages }, room]) => {
          if (room) {
            this.notifications.refreshRoomUnreadFromMessages(room.id, messages);
          }
        })
      ),
    { dispatch: false }
  );

  refreshSyncedRoomUnread$ = createEffect(
    () =>
      this.actions$.pipe(
        ofType(MessagesActions.syncMessages),
        tap(({ messages }) => {
          for (const [roomId, roomMessages] of groupMessagesByRoom(messages)) {
            this.notifications.refreshRoomUnreadFromMessages(roomId, roomMessages);
          }
        })
      ),
    { dispatch: false }
  );
}
