/* eslint-disable @typescript-eslint/member-ordering */
import {
  AfterViewInit,
  Component,
  ElementRef,
  HostListener,
  OnDestroy,
  OnInit,
  ViewChild,
  inject,
  input,
  output,
  signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { firstValueFrom } from 'rxjs';
import { NgIcon, provideIcons } from '@ng-icons/core';
import {
  lucideImage,
  lucideSearch,
  lucideX
} from '@ng-icons/lucide';
import { KlipyGif, KlipyService } from '../../application/services/klipy.service';
import type { RoomSignalSourceInput } from '../../../server-directory';
import { ChatImageProxyFallbackDirective } from '../chat-image-proxy-fallback.directive';

const KLIPY_CARD_MIN_WIDTH = 140;
const KLIPY_CARD_MAX_WIDTH = 248;
const KLIPY_CARD_MIN_HEIGHT = 104;
const KLIPY_CARD_MAX_HEIGHT = 220;
const KLIPY_CARD_FALLBACK_SIZE = 160;

@Component({
  selector: 'app-klipy-gif-picker',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    NgIcon,
    ChatImageProxyFallbackDirective
  ],
  viewProviders: [
    provideIcons({
      lucideImage,
      lucideSearch,
      lucideX
    })
  ],
  templateUrl: './klipy-gif-picker.component.html'
})
export class KlipyGifPickerComponent implements OnInit, AfterViewInit, OnDestroy {
  readonly signalSource = input<RoomSignalSourceInput | null>(null);

  readonly gifSelected = output<KlipyGif>();
  readonly closed = output<undefined>();

  @ViewChild('searchInput') searchInput?: ElementRef<HTMLInputElement>;

  private readonly klipy = inject(KlipyService);
  private currentPage = 1;
  private searchTimer: ReturnType<typeof setTimeout> | null = null;
  private requestId = 0;

  searchQuery = '';
  results = signal<KlipyGif[]>([]);
  loading = signal(false);
  errorMessage = signal('');
  hasNext = signal(false);

  ngOnInit(): void {
    void this.loadResults(true);
  }

  ngAfterViewInit(): void {
    requestAnimationFrame(() => {
      this.searchInput?.nativeElement.focus();
      this.searchInput?.nativeElement.select();
    });
  }

  ngOnDestroy(): void {
    this.clearSearchTimer();
  }

  @HostListener('document:keydown.escape')
  onEscape(): void {
    this.close();
  }

  onSearchQueryChanged(query: string): void {
    this.searchQuery = query;
    this.clearSearchTimer();
    this.searchTimer = setTimeout(() => {
      void this.loadResults(true);
    }, 250);
  }

  retry(): void {
    void this.loadResults(true);
  }

  async loadMore(): Promise<void> {
    if (this.loading() || !this.hasNext())
      return;

    this.currentPage += 1;
    await this.loadResults(false);
  }

  selectGif(gif: KlipyGif): void {
    this.gifSelected.emit(gif);
  }

  close(): void {
    this.closed.emit(undefined);
  }

  gifCardHeight(gif: KlipyGif): number {
    return this.getGifCardSize(gif).height;
  }

  private async loadResults(reset: boolean): Promise<void> {
    if (reset) {
      this.currentPage = 1;
    }

    const requestId = ++this.requestId;

    this.loading.set(true);
    this.errorMessage.set('');

    try {
      const response = await firstValueFrom(
        this.klipy.searchGifs(this.searchQuery, this.currentPage, undefined, this.signalSource())
      );

      if (requestId !== this.requestId)
        return;

      this.results.set(
        reset
          ? response.results
          : this.mergeResults(this.results(), response.results)
      );

      this.hasNext.set(response.hasNext);
    } catch (error) {
      if (requestId !== this.requestId)
        return;

      this.errorMessage.set(
        error instanceof Error
          ? error.message
          : 'Failed to load GIFs from KLIPY.'
      );

      if (reset) {
        this.results.set([]);
      }

      this.hasNext.set(false);
    } finally {
      if (requestId === this.requestId) {
        this.loading.set(false);
      }
    }
  }

  private mergeResults(existing: KlipyGif[], incoming: KlipyGif[]): KlipyGif[] {
    const seen = new Set(existing.map((gif) => gif.id));
    const merged = [...existing];

    for (const gif of incoming) {
      if (seen.has(gif.id))
        continue;

      merged.push(gif);
      seen.add(gif.id);
    }

    return merged;
  }

  private clearSearchTimer(): void {
    if (this.searchTimer) {
      clearTimeout(this.searchTimer);
      this.searchTimer = null;
    }
  }

  private getGifCardSize(gif: KlipyGif): { width: number; height: number } {
    if (gif.width <= 0 || gif.height <= 0) {
      return {
        width: KLIPY_CARD_FALLBACK_SIZE,
        height: KLIPY_CARD_FALLBACK_SIZE
      };
    }

    const maxScale = Math.min(
      KLIPY_CARD_MAX_WIDTH / gif.width,
      KLIPY_CARD_MAX_HEIGHT / gif.height
    );
    const minScale = Math.max(
      KLIPY_CARD_MIN_WIDTH / gif.width,
      KLIPY_CARD_MIN_HEIGHT / gif.height
    );
    const scale = minScale <= maxScale
      ? Math.min(maxScale, Math.max(minScale, 1))
      : maxScale;
    const scaledWidth = Math.round(gif.width * scale);
    const scaledHeight = Math.round(gif.height * scale);

    return {
      width: Math.min(KLIPY_CARD_MAX_WIDTH, Math.max(KLIPY_CARD_MIN_WIDTH, scaledWidth)),
      height: Math.min(KLIPY_CARD_MAX_HEIGHT, Math.max(KLIPY_CARD_MIN_HEIGHT, scaledHeight))
    };
  }
}
