/* eslint-disable @typescript-eslint/member-ordering */
import {
  Component,
  computed,
  effect,
  inject,
  OnDestroy,
  signal
} from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { CommonModule } from '@angular/common';
import { NavigationEnd, Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { NgIcon, provideIcons } from '@ng-icons/core';
import {
  lucideLogOut,
  lucideMessageCircle,
  lucideTrash2,
  lucideUser,
  lucideUsers
} from '@ng-icons/lucide';
import { filter, map } from 'rxjs';
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../../../core/i18n';
import { ContextMenuComponent } from '../../../../shared';
import { DirectMessageService } from '../../application/services/direct-message.service';
import { FriendService } from '../../application/services/friend.service';
import { selectAllUsers, selectCurrentUser } from '../../../../store/users/users.selectors';
import type { DirectMessageConversation } from '../../domain/models/direct-message.model';
import type { User } from '../../../../shared-kernel';

interface DmRailItem {
  id: string;
  label: string;
  avatarUrl?: string;
  conversation: DirectMessageConversation | null;
  isExiting: boolean;
  user: User | null;
  unreadCount: number;
}

interface DmRailContextMenuState {
  x: number;
  y: number;
  item: DmRailItem;
}

const EXIT_ANIMATION_MS = 160;

@Component({
  selector: 'app-dm-rail',
  standalone: true,
  imports: [
    CommonModule,
    ContextMenuComponent,
    NgIcon,
    ...APP_TRANSLATE_IMPORTS
  ],
  viewProviders: [provideIcons({ lucideLogOut, lucideMessageCircle, lucideTrash2, lucideUser, lucideUsers })],
  templateUrl: './dm-rail.component.html',
  styleUrl: './dm-rail.component.scss'
})
export class DmRailComponent implements OnDestroy {
  private readonly router = inject(Router);
  private readonly store = inject(Store);
  private readonly exitTimers = new Map<string, ReturnType<typeof setTimeout>>();
  private readonly i18n = inject(AppI18nService);
  readonly directMessages = inject(DirectMessageService);
  readonly friends = inject(FriendService);
  readonly users = this.store.selectSignal(selectAllUsers);
  readonly currentUser = this.store.selectSignal(selectCurrentUser);
  readonly currentUserId = computed(() => this.currentUser()?.oderId || this.currentUser()?.id || '');
  readonly activeConversationId = toSignal(
    this.router.events.pipe(
      filter((navigationEvent): navigationEvent is NavigationEnd => navigationEvent instanceof NavigationEnd),
      map((navigationEvent) => this.getConversationIdFromUrl(navigationEvent.urlAfterRedirects))
    ),
    { initialValue: this.getConversationIdFromUrl(this.router.url) }
  );
  readonly friendUsers = computed(() => this.users().filter((user) =>
    this.friends.isFriend(user.oderId || user.id) && (user.oderId || user.id) !== this.currentUserId()
  ));
  readonly railItems = signal<DmRailItem[]>([]);
  readonly contextMenu = signal<DmRailContextMenuState | null>(null);
  readonly unreadRailItems = computed<DmRailItem[]>(() => {
    const currentUserId = this.currentUserId();
    const items = new Map<string, DmRailItem>();

    for (const conversation of this.directMessages.conversations()) {
      if (conversation.unreadCount === 0) {
        continue;
      }

      if (this.isGroupConversation(conversation)) {
        items.set(conversation.id, {
          id: conversation.id,
          label: this.titleFor(conversation),
          conversation,
          isExiting: false,
          user: null,
          unreadCount: conversation.unreadCount
        });

        continue;
      }

      const peerId = conversation.participants.find((participantId) => participantId !== currentUserId);

      if (!peerId) {
        continue;
      }

      const knownUser = this.users().find((user) => user.id === peerId || user.oderId === peerId) ?? null;
      const profile = conversation.participantProfiles[peerId];

      items.set(peerId, {
        id: peerId,
        label: knownUser?.displayName || profile?.displayName || peerId,
        avatarUrl: knownUser?.avatarUrl || profile?.avatarUrl,
        conversation,
        isExiting: false,
        user: knownUser,
        unreadCount: conversation.unreadCount
      });
    }

    for (const user of this.friendUsers()) {
      const userId = user.oderId || user.id;

      if (items.has(userId)) {
        continue;
      }

      items.set(userId, {
        id: userId,
        label: user.displayName || user.username,
        avatarUrl: user.avatarUrl,
        conversation: null,
        isExiting: false,
        user,
        unreadCount: 0
      });
    }

    return Array.from(items.values()).filter((item) => item.conversation && item.unreadCount > 0);
  });
  readonly isOnDirectMessages = toSignal(
    this.router.events.pipe(
      filter((navigationEvent): navigationEvent is NavigationEnd => navigationEvent instanceof NavigationEnd),
      map((navigationEvent) => navigationEvent.urlAfterRedirects.startsWith('/dm'))
    ),
    { initialValue: this.router.url.startsWith('/dm') }
  );

  constructor() {
    effect(() => {
      const unreadItems = this.unreadRailItems();

      queueMicrotask(() => this.syncRailItems(unreadItems));
    });
  }

  ngOnDestroy(): void {
    for (const timer of this.exitTimers.values()) {
      clearTimeout(timer);
    }

    this.exitTimers.clear();
  }

  async openConversation(conversation: DirectMessageConversation): Promise<void> {
    await this.router.navigate(['/dm', conversation.id]);
  }

  async openFriend(user: User): Promise<void> {
    const conversation = await this.directMessages.createConversation(user);

    await this.router.navigate(['/dm', conversation.id]);
  }

  async openItem(item: DmRailItem): Promise<void> {
    this.closeContextMenu();

    if (item.conversation) {
      await this.openConversation(item.conversation);
      return;
    }

    if (item.user) {
      await this.openFriend(item.user);
    }
  }

  openDirectMessages(): void {
    void this.router.navigate(['/dm']);
  }

  titleFor(conversation: DirectMessageConversation): string {
    if (this.isGroupConversation(conversation)) {
      return conversation.title || this.groupConversationTitle(conversation);
    }

    const peerId = conversation.participants.find((participantId) => participantId !== this.currentUserId());

    return peerId ? conversation.participantProfiles[peerId]?.displayName || peerId : this.i18n.instant('dm.chat.railFallback');
  }

  initial(label: string): string {
    return label.trim()[0]?.toUpperCase() || '?';
  }

  conversationForFriend(user: User): DirectMessageConversation | null {
    const userId = user.oderId || user.id;

    return this.directMessages.conversations().find((conversation) => conversation.participants.includes(userId)) ?? null;
  }

  isSelectedConversation(conversation: DirectMessageConversation): boolean {
    return this.activeConversationId() === conversation.id;
  }

  isSelectedFriend(user: User): boolean {
    const conversation = this.conversationForFriend(user);

    return !!conversation && this.isSelectedConversation(conversation);
  }

  isSelectedItem(item: DmRailItem): boolean {
    return !!item.conversation && this.isSelectedConversation(item.conversation);
  }

  iconFor(item: DmRailItem): string {
    return item.conversation && this.isGroupConversation(item.conversation) ? 'lucideUsers' : 'lucideUser';
  }

  openContextMenu(event: MouseEvent, item: DmRailItem): void {
    if (!item.conversation) {
      return;
    }

    event.preventDefault();
    event.stopPropagation();
    this.contextMenu.set({
      x: event.clientX,
      y: event.clientY,
      item
    });
  }

  closeContextMenu(): void {
    this.contextMenu.set(null);
  }

  async forgetContextItem(): Promise<void> {
    const item = this.contextMenu()?.item;

    if (!item?.conversation) {
      return;
    }

    await this.directMessages.forgetConversation(item.conversation.id);
    this.closeContextMenu();

    if (this.isSelectedConversation(item.conversation)) {
      await this.router.navigate(['/dm']);
    }
  }

  forgetContextLabel(item: DmRailItem): string {
    return item.conversation && this.isGroupConversation(item.conversation)
      ? this.i18n.instant('dm.rail.leaveChat')
      : this.i18n.instant('dm.rail.forgetChat');
  }

  forgetContextIcon(item: DmRailItem): string {
    return item.conversation && this.isGroupConversation(item.conversation) ? 'lucideLogOut' : 'lucideTrash2';
  }

  formatUnreadCount(count: number): string {
    return count > 99 ? '99+' : String(count);
  }

  private getConversationIdFromUrl(url: string): string | null {
    const match = /^\/dm\/([^/?#]+)/.exec(url);

    return match ? decodeURIComponent(match[1]) : null;
  }

  private syncRailItems(unreadItems: DmRailItem[]): void {
    const unreadById = new Map(unreadItems.map((item) => [item.id, item]));
    const currentItems = this.railItems();
    const nextItems: DmRailItem[] = [];

    for (const item of unreadItems) {
      const timer = this.exitTimers.get(item.id);

      if (timer) {
        clearTimeout(timer);
        this.exitTimers.delete(item.id);
      }

      nextItems.push({ ...item, isExiting: false });
    }

    for (const item of currentItems) {
      if (unreadById.has(item.id)) {
        continue;
      }

      nextItems.push({ ...item, isExiting: true });

      if (!this.exitTimers.has(item.id)) {
        this.exitTimers.set(item.id, setTimeout(() => {
          this.exitTimers.delete(item.id);
          this.railItems.update((items) => items.filter((entry) => entry.id !== item.id));
        }, EXIT_ANIMATION_MS));
      }
    }

    this.railItems.set(nextItems);
  }

  private isGroupConversation(conversation: DirectMessageConversation): boolean {
    return conversation.kind === 'group' || conversation.participants.length > 2;
  }

  private groupConversationTitle(conversation: DirectMessageConversation): string {
    const names = conversation.participants
      .filter((participantId) => participantId !== this.currentUserId())
      .map((participantId) => conversation.participantProfiles[participantId]?.displayName || participantId);

    if (names.length <= 3) {
      return names.join(', ');
    }

    return `${names.slice(0, 3).join(', ')} +${names.length - 3}`;
  }
}
