/* eslint-disable @typescript-eslint/member-ordering, */
import {
  Component,
  computed,
  inject,
  signal,
  DestroyRef,
  effect
} from '@angular/core';
import { AppI18nService } from '../../../../core/i18n';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Store } from '@ngrx/store';
import { RealtimeSessionFacade } from '../../../../core/realtime';
import { selectActiveChannelId, selectCurrentRoom } from '../../../../store/rooms/rooms.selectors';
import { selectCurrentUser } from '../../../../store/users/users.selectors';
import {
  merge,
  interval,
  filter,
  map,
  tap
} from 'rxjs';

const TYPING_TTL = 3_000;
const PURGE_INTERVAL = 1_000;
const MAX_SHOWN = 4;

interface TypingSignalingMessage {
  type: string;
  displayName: string;
  isTyping?: boolean;
  oderId: string;
  serverId: string;
  channelId?: string;
}

@Component({
  selector: 'app-typing-indicator',
  standalone: true,
  templateUrl: './typing-indicator.component.html',
  host: {
    class: 'block',
    style: 'background: linear-gradient(to bottom, transparent, hsl(var(--background)));'
  }
})
export class TypingIndicatorComponent {
  private readonly typingMap = new Map<string, { name: string; channelId: string; expiresAt: number }>();
  private readonly store = inject(Store);
  private readonly currentRoom = this.store.selectSignal(selectCurrentRoom);
  private readonly activeChannelId = this.store.selectSignal(selectActiveChannelId);
  private readonly currentUser = this.store.selectSignal(selectCurrentUser);
  private lastRoomId: string | null = null;
  private lastConversationKey: string | null = null;

  typingDisplay = signal<string[]>([]);
  typingOthersCount = signal<number>(0);
  private readonly appI18n = inject(AppI18nService);

  readonly typingLabel = computed(() => {
    const names = this.typingDisplay();
    const others = this.typingOthersCount();

    if (names.length === 0) {
      return '';
    }

    const namesText = names.join(', ');

    if (others > 0) {
      return this.appI18n.instant('chat.typing.andOthers', {
        names: namesText,
        count: others
      });
    }

    if (names.length === 1) {
      return this.appI18n.instant('chat.typing.one', { names: namesText });
    }

    return this.appI18n.instant('chat.typing.many', { names: namesText });
  });

  constructor() {
    const webrtc = inject(RealtimeSessionFacade);
    const destroyRef = inject(DestroyRef);
    const typing$ = webrtc.onSignalingMessage.pipe(
      filter(
        (msg): msg is TypingSignalingMessage =>
          msg?.type === 'user_typing' && typeof msg.displayName === 'string' && typeof msg.oderId === 'string' && typeof msg.serverId === 'string'
      ),
      filter((msg) => msg.serverId === this.currentRoom()?.id),
      tap((msg) => {
        const now = Date.now();
        const channelId = typeof msg.channelId === 'string' && msg.channelId.trim() ? msg.channelId.trim() : 'general';
        const typingKey = `${channelId}:${msg.oderId}`;

        if (msg.isTyping === false) {
          this.typingMap.delete(typingKey);
          return;
        }

        this.typingMap.set(typingKey, {
          name: msg.displayName,
          channelId,
          expiresAt: now + TYPING_TTL
        });
      })
    );
    const purge$ = interval(PURGE_INTERVAL).pipe(
      map(() => Date.now()),
      filter((now) => {
        let changed = false;

        for (const [key, entry] of this.typingMap) {
          if (entry.expiresAt <= now) {
            this.typingMap.delete(key);
            changed = true;
          }
        }

        return changed;
      })
    );

    merge(typing$, purge$)
      .pipe(takeUntilDestroyed(destroyRef))
      .subscribe(() => this.recomputeDisplay());

    effect(() => {
      const roomId = this.currentRoom()?.id ?? null;
      const activeChannelId = this.activeChannelId() ?? 'general';
      const conversationKey = roomId ? `${roomId}:${activeChannelId}` : null;

      if (roomId !== this.lastRoomId) {
        this.lastRoomId = roomId;
        this.typingMap.clear();
      }

      if (conversationKey === this.lastConversationKey)
        return;

      this.lastConversationKey = conversationKey;
      this.recomputeDisplay();
    });
  }

  private recomputeDisplay(): void {
    const now = Date.now();
    const activeChannelId = this.activeChannelId() ?? 'general';
    const currentUserId = this.currentUser()?.id || this.currentUser()?.oderId;
    const names = Array.from(this.typingMap.entries())
      .filter(([typingKey, entry]) => {
        if (entry.expiresAt <= now || entry.channelId !== activeChannelId) {
          return false;
        }

        if (!currentUserId) {
          return true;
        }

        const [, oderId] = typingKey.split(':');

        return oderId !== currentUserId;
      })
      .map(([, entry]) => entry.name);

    this.typingDisplay.set(names.slice(0, MAX_SHOWN));
    this.typingOthersCount.set(Math.max(0, names.length - MAX_SHOWN));
  }
}
