/* eslint-disable @typescript-eslint/member-ordering */
import {
  Component,
  ElementRef,
  HostListener,
  computed,
  inject,
  OnInit,
  signal,
  viewChild
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Router, RouterLink } from '@angular/router';
import { Store } from '@ngrx/store';
import {
  debounceTime,
  distinctUntilChanged,
  Subject
} from 'rxjs';
import { NgIcon, provideIcons } from '@ng-icons/core';
import {
  lucideUsers,
  lucideCompass,
  lucidePlus,
  lucideSearch,
  lucideArrowRight,
  lucideTicket,
  lucideServer,
  lucideX
} from '@ng-icons/lucide';

import { RoomsActions } from '../../store/rooms/rooms.actions';
import {
  selectSearchResults,
  selectIsSearching,
  selectSavedRooms
} from '../../store/rooms/rooms.selectors';
import { selectAllUsers, selectCurrentUser } from '../../store/users/users.selectors';
import type { Room, User } from '../../shared-kernel';
import type { ServerInfo } from '../../domains/server-directory/domain/models/server-directory.model';
import { ServerDirectoryFacade } from '../../domains/server-directory/application/facades/server-directory.facade';
import { ViewportService } from '../../core/platform';
import { FriendService } from '../../domains/direct-message/application/services/friend.service';
import { FriendButtonComponent } from '../../domains/direct-message/feature/friend-button/friend-button.component';
import { UserAvatarComponent } from '../../shared/components/user-avatar/user-avatar.component';
import { parseInviteQuery } from './invite-query.util';
import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../core/i18n';
import {
  AutoFocusDirective,
  SelectOnFocusDirective,
  SubmitOnEnterDirective
} from '../../shared/directives';

/** Maximum quick-search rows shown per group on the dashboard. */
const QUICK_RESULT_LIMIT = 5;
/** Maximum entries shown in the discovery panels (people / popular / friends / recent servers). */
const DISCOVERY_LIMIT = 5;
/** Maximum remembered recent searches. */
const RECENT_SEARCH_LIMIT = 8;
/** localStorage key backing the recent-searches list. */
const RECENT_SEARCHES_STORAGE_KEY = 'metoyou_dashboard_recent_searches';

/**
 * Application landing page. Presents the three primary actions (find people, find
 * servers, create a server), a global quick-search across people / servers / invites,
 * discovery panels (people you might know, popular servers, recently active servers),
 * and an onboarding state for brand-new accounts.
 */
@Component({
  selector: 'app-dashboard',
  standalone: true,
  imports: [
    CommonModule,
    FormsModule,
    RouterLink,
    NgIcon,
    FriendButtonComponent,
    UserAvatarComponent,
    AutoFocusDirective,
    SelectOnFocusDirective,
    SubmitOnEnterDirective,
    ...APP_TRANSLATE_IMPORTS
  ],
  viewProviders: [
    provideIcons({
      lucideUsers,
      lucideCompass,
      lucidePlus,
      lucideSearch,
      lucideArrowRight,
      lucideTicket,
      lucideServer,
      lucideX
    })
  ],
  templateUrl: './dashboard.component.html',
  host: {
    class: 'block h-full min-h-0 min-w-0 w-full overflow-hidden'
  }
})
export class DashboardComponent implements OnInit {
  private readonly appI18n = inject(AppI18nService);
  private store = inject(Store);
  private router = inject(Router);
  private serverDirectory = inject(ServerDirectoryFacade);
  private friendsService = inject(FriendService);
  private readonly viewport = inject(ViewportService);
  private searchSubject = new Subject<string>();
  private readonly searchInputRef = viewChild<ElementRef<HTMLInputElement>>('searchInput');

  readonly isMobile = this.viewport.isMobile;
  searchQuery = signal('');
  serverResults = this.store.selectSignal(selectSearchResults);
  isSearching = this.store.selectSignal(selectIsSearching);
  savedRooms = this.store.selectSignal(selectSavedRooms);
  currentUser = this.store.selectSignal(selectCurrentUser);
  popularServers = signal<ServerInfo[]>([]);
  recentSearches = signal<string[]>(this.loadRecentSearches());
  private users = this.store.selectSignal(selectAllUsers);

  /** True while the user is actively typing a query. */
  isSearchMode = computed(() => this.searchQuery().trim().length > 0);

  /** Server matches limited for the quick-search list. */
  topServerResults = computed(() => this.serverResults().slice(0, QUICK_RESULT_LIMIT));

  /** Every distinct person known to the account (known users plus saved-room members), excluding self. */
  private discoveredPeople = computed<User[]>(() => {
    const currentKey = this.currentUserKey();
    const byKey = new Map<string, User>();

    for (const user of this.users()) {
      byKey.set(user.oderId || user.id, user);
    }

    for (const room of this.savedRooms()) {
      for (const member of room.members ?? []) {
        const key = member.oderId || member.id;

        if (byKey.has(key)) {
          continue;
        }

        byKey.set(key, {
          id: member.id,
          oderId: key,
          username: member.username,
          displayName: member.displayName,
          avatarUrl: member.avatarUrl,
          status: 'disconnected'
        } as User);
      }
    }

    return Array.from(byKey.values()).filter((user) => (user.oderId || user.id) !== currentKey);
  });

  /** People matches derived from known users and saved-room members. */
  topPeopleResults = computed<User[]>(() => {
    const query = this.searchQuery().trim()
      .toLowerCase();

    if (!query) {
      return [];
    }

    return this.discoveredPeople()
      .filter((user) => this.matchesQuery(user, query))
      .slice(0, QUICK_RESULT_LIMIT);
  });

  /** Suggested people for the discovery panel (excludes self and existing friends). */
  peopleYouMightKnow = computed<User[]>(() => {
    const friendIds = this.friendsService.friendIds();

    return this.discoveredPeople()
      .filter((user) => !friendIds.has(user.oderId || user.id))
      .slice(0, DISCOVERY_LIMIT);
  });

  /** People the user has added as friends. */
  friends = computed<User[]>(() => {
    const friendIds = this.friendsService.friendIds();

    return this.discoveredPeople().filter((user) => friendIds.has(user.oderId || user.id));
  });

  /** Recently joined servers surfaced as horizontal cards. */
  recentlyActiveServers = computed<Room[]>(() => this.savedRooms().slice(0, DISCOVERY_LIMIT));

  /** Parsed invite when the query looks like an invite code or URL. */
  inviteResult = computed(() => parseInviteQuery(this.searchQuery()));

  /** True when quick-search yielded nothing across every group. */
  hasNoQuickResults = computed(
    () => this.topServerResults().length === 0 && this.topPeopleResults().length === 0 && !this.inviteResult()
  );

  /** True for a brand-new account with no servers and no known people. */
  isNewUser = computed(() => this.savedRooms().length === 0 && this.users().length === 0);

  ngOnInit(): void {
    this.store.dispatch(RoomsActions.loadRooms());

    this.searchSubject.pipe(debounceTime(120), distinctUntilChanged()).subscribe((query) => {
      this.store.dispatch(RoomsActions.searchServers({ query }));
    });

    this.serverDirectory.getFeaturedServers(DISCOVERY_LIMIT).subscribe((servers) => {
      if (servers.length > 0) {
        this.popularServers.set(servers);
        return;
      }

      this.serverDirectory.getTrendingServers(DISCOVERY_LIMIT).subscribe((trending) => this.popularServers.set(trending));
    });
  }

  @HostListener('document:keydown', ['$event'])
  onGlobalKeydown(event: KeyboardEvent): void {
    if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k') {
      event.preventDefault();
      this.searchInputRef()?.nativeElement.focus();
    }
  }

  onSearchChange(query: string): void {
    this.searchQuery.set(query);
    this.searchSubject.next(query);
  }

  submitSearch(): void {
    this.rememberSearch(this.searchQuery());
  }

  applyRecentSearch(term: string): void {
    this.onSearchChange(term);
    this.rememberSearch(term);
    this.searchInputRef()?.nativeElement.focus();
  }

  removeRecentSearch(term: string): void {
    this.recentSearches.update((terms) => terms.filter((entry) => entry !== term));
    this.persistRecentSearches();
  }

  clearRecentSearches(): void {
    this.recentSearches.set([]);
    this.persistRecentSearches();
  }

  openServer(server: ServerInfo): void {
    const joined = this.savedRooms().find((room) => room.id === server.id);

    if (joined) {
      this.store.dispatch(RoomsActions.viewServer({ room: joined }));
      return;
    }

    this.router.navigate(['/servers']);
  }

  openSavedRoom(room: Room): void {
    this.store.dispatch(RoomsActions.viewServer({ room }));
  }

  openInvite(): void {
    const invite = this.inviteResult();

    if (!invite) {
      return;
    }

    this.router.navigate(['/invite', invite.inviteId], {
      queryParams: invite.sourceUrl ? { server: invite.sourceUrl } : undefined
    });
  }

  serverInitial(server: ServerInfo): string {
    return server.name.trim()[0]?.toUpperCase() || '?';
  }

  serverMetaLabel(server: ServerInfo): string {
    const count = server.userCount ?? 0;
    const members = this.appI18n.instant(
      count === 1 ? 'dashboard.serverMeta.member' : 'dashboard.serverMeta.members',
      { count }
    );
    const detail = server.description?.trim();

    return detail ? `${members} • ${detail}` : members;
  }

  personLabel(user: User): string {
    return user.displayName || user.username || user.oderId || user.id;
  }

  isOnline(user: User): boolean {
    return user.isOnline === true || [
      'online',
      'away',
      'busy'
    ].includes(user.status);
  }

  private rememberSearch(rawTerm: string): void {
    const term = rawTerm.trim();

    if (!term) {
      return;
    }

    this.recentSearches.update((terms) => [term, ...terms.filter((entry) => entry !== term)].slice(0, RECENT_SEARCH_LIMIT));
    this.persistRecentSearches();
  }

  private loadRecentSearches(): string[] {
    if (typeof localStorage === 'undefined') {
      return [];
    }

    try {
      const stored = localStorage.getItem(RECENT_SEARCHES_STORAGE_KEY);
      const parsed: unknown = stored ? JSON.parse(stored) : [];

      return Array.isArray(parsed) ? parsed.filter((entry): entry is string => typeof entry === 'string') : [];
    } catch {
      return [];
    }
  }

  private persistRecentSearches(): void {
    if (typeof localStorage === 'undefined') {
      return;
    }

    try {
      localStorage.setItem(RECENT_SEARCHES_STORAGE_KEY, JSON.stringify(this.recentSearches()));
    } catch {
      // Persistence is best-effort; ignore storage failures.
    }
  }

  private currentUserKey(): string {
    const currentUser = this.currentUser();

    return currentUser ? currentUser.oderId || currentUser.id : '';
  }

  private matchesQuery(user: User, query: string): boolean {
    return [
      user.displayName,
      user.username,
      user.oderId
    ]
      .filter((value): value is string => typeof value === 'string')
      .some((value) => value.toLowerCase().includes(query));
  }
}
