/* eslint-disable @angular-eslint/component-class-suffix */
import {
  Component,
  OnInit,
  OnDestroy,
  computed,
  effect,
  inject,
  HostListener,
  signal,
  Type
} from '@angular/core';
import {
  Router,
  RouterOutlet,
  NavigationEnd
} from '@angular/router';
import { CommonModule } from '@angular/common';
import { Store } from '@ngrx/store';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideX } from '@ng-icons/lucide';

import {
  DatabaseService,
  loadGeneralSettingsFromStorage,
  loadLastViewedChatFromStorage
} from './infrastructure/persistence';
import { DesktopAppUpdateService } from './core/services/desktop-app-update.service';
import { ServerDirectoryFacade } from './domains/server-directory';
import { NotificationsFacade } from './domains/notifications';
import { TimeSyncService } from './core/services/time-sync.service';
import { VoiceSessionFacade } from './domains/voice-session';
import { ExternalLinkService } from './core/platform';
import { SettingsModalService } from './core/services/settings-modal.service';
import { ElectronBridgeService } from './core/platform/electron/electron-bridge.service';
import { UserStatusService } from './core/services/user-status.service';
import { GameActivityService } from './domains/game-activity';
import { PluginBootstrapService } from './domains/plugins';
import { ServersRailComponent } from './features/servers/servers-rail/servers-rail.component';
import { TitleBarComponent } from './features/shell/title-bar/title-bar.component';
import { FloatingVoiceControlsComponent } from './domains/voice-session/feature/floating-voice-controls/floating-voice-controls.component';
import { SettingsModalComponent } from './features/settings/settings-modal/settings-modal.component';
import { DebugConsoleComponent } from './shared/components/debug-console/debug-console.component';
import { ScreenShareSourcePickerComponent } from './shared/components/screen-share-source-picker/screen-share-source-picker.component';
import { NativeContextMenuComponent } from './features/shell/native-context-menu/native-context-menu.component';
import { UsersActions } from './store/users/users.actions';
import { RoomsActions } from './store/rooms/rooms.actions';
import { selectCurrentRoom } from './store/rooms/rooms.selectors';
import { ROOM_URL_PATTERN } from './core/constants';
import { clearStoredCurrentUserId, getStoredCurrentUserId } from './core/storage/current-user-storage';
import {
  ThemeNodeDirective,
  ThemePickerOverlayComponent,
  ThemeService
} from './domains/theme';

@Component({
  selector: 'app-root',
  imports: [
    CommonModule,
    NgIcon,
    RouterOutlet,
    ServersRailComponent,
    TitleBarComponent,
    FloatingVoiceControlsComponent,
    SettingsModalComponent,
    DebugConsoleComponent,
    ScreenShareSourcePickerComponent,
    NativeContextMenuComponent,
    ThemeNodeDirective,
    ThemePickerOverlayComponent
  ],
  viewProviders: [
    provideIcons({
      lucideX
    })
  ],
  templateUrl: './app.html',
  styleUrl: './app.scss'
})
export class App implements OnInit, OnDestroy {
  private static readonly THEME_STUDIO_CONTROLS_MARGIN = 16;
  private static readonly TITLE_BAR_HEIGHT = 40;

  readonly plugins = inject(PluginBootstrapService);
  store = inject(Store);
  currentRoom = this.store.selectSignal(selectCurrentRoom);
  desktopUpdates = inject(DesktopAppUpdateService);
  desktopUpdateState = this.desktopUpdates.state;
  readonly databaseService = inject(DatabaseService);
  readonly router = inject(Router);
  readonly servers = inject(ServerDirectoryFacade);
  readonly notifications = inject(NotificationsFacade);
  readonly settingsModal = inject(SettingsModalService);
  readonly timeSync = inject(TimeSyncService);
  readonly theme = inject(ThemeService);
  readonly voiceSession = inject(VoiceSessionFacade);
  readonly externalLinks = inject(ExternalLinkService);
  readonly electronBridge = inject(ElectronBridgeService);
  readonly userStatus = inject(UserStatusService);
  readonly gameActivity = inject(GameActivityService);
  readonly dismissedDesktopUpdateNoticeKey = signal<string | null>(null);
  readonly themeStudioFullscreenComponent = signal<Type<unknown> | null>(null);
  readonly themeStudioControlsPosition = signal<{ x: number; y: number } | null>(null);
  readonly isDraggingThemeStudioControls = signal(false);
  readonly currentRouteUrl = signal(this.getCurrentRouteUrl());

  readonly appShellLayoutStyles = computed(() => this.theme.getLayoutContainerStyles('appShell'));
  readonly serversRailLayoutStyles = computed(() => this.theme.getLayoutItemStyles('serversRail'));
  readonly appWorkspaceLayoutStyles = computed(() => this.theme.getLayoutItemStyles('appWorkspace'));
  readonly isThemeStudioFullscreen = computed(() => {
    return this.settingsModal.isOpen()
      && this.settingsModal.activePage() === 'theme'
      && this.settingsModal.themeStudioFullscreen();
  });
  readonly isThemeStudioMinimized = computed(() => {
    return this.settingsModal.activePage() === 'theme'
      && this.settingsModal.themeStudioMinimized();
  });
  readonly isDirectMessageRoute = computed(() => this.getRoutePath(this.currentRouteUrl()).startsWith('/dm'));
  readonly desktopUpdateNoticeKey = computed(() => {
    const updateState = this.desktopUpdateState();

    return updateState.targetVersion?.trim()
      || updateState.latestVersion?.trim()
      || `restart:${updateState.currentVersion}`;
  });
  readonly showDesktopUpdateNotice = computed(() => {
    return this.desktopUpdateState().restartRequired
      && this.dismissedDesktopUpdateNoticeKey() !== this.desktopUpdateNoticeKey();
  });
  readonly appWorkspaceShellStyles = computed(() => {
    const workspaceStyles = this.appWorkspaceLayoutStyles();

    if (!this.isThemeStudioFullscreen()) {
      return workspaceStyles;
    }

    return {
      ...workspaceStyles,
      gridColumn: '1 / -1',
      gridRow: '1 / -1'
    };
  });
  readonly themeStudioControlsPositionStyles = computed(() => {
    const position = this.themeStudioControlsPosition();

    if (!position) {
      return {
        right: `${App.THEME_STUDIO_CONTROLS_MARGIN}px`,
        bottom: `${App.THEME_STUDIO_CONTROLS_MARGIN}px`
      };
    }

    return {
      left: `${position.x}px`,
      top: `${position.y}px`
    };
  });

  private deepLinkCleanup: (() => void) | null = null;
  private themeStudioControlsDragOffset: { x: number; y: number } | null = null;
  private themeStudioControlsBounds: { width: number; height: number } | null = null;

  constructor() {
    effect(() => {
      if (!this.isThemeStudioFullscreen() || this.themeStudioFullscreenComponent()) {
        return;
      }

      void import('./domains/theme/feature/settings/theme-settings/theme-settings.component')
        .then((module) => {
          this.themeStudioFullscreenComponent.set(module.ThemeSettingsComponent);
        });
    });

    effect(() => {
      if (this.isThemeStudioFullscreen()) {
        return;
      }

      this.isDraggingThemeStudioControls.set(false);
      this.themeStudioControlsDragOffset = null;
      this.themeStudioControlsBounds = null;
    });
  }

  @HostListener('document:click', ['$event'])
  onGlobalLinkClick(evt: MouseEvent): void {
    this.externalLinks.handleClick(evt);
  }

  @HostListener('document:keydown', ['$event'])
  onGlobalKeyDown(evt: KeyboardEvent): void {
    this.theme.handleGlobalShortcut(evt);
  }

  @HostListener('document:pointermove', ['$event'])
  onDocumentPointerMove(event: PointerEvent): void {
    if (!this.isDraggingThemeStudioControls() || !this.themeStudioControlsDragOffset || !this.themeStudioControlsBounds) {
      return;
    }

    this.themeStudioControlsPosition.set(this.clampThemeStudioControlsPosition(
      event.clientX - this.themeStudioControlsDragOffset.x,
      event.clientY - this.themeStudioControlsDragOffset.y,
      this.themeStudioControlsBounds.width,
      this.themeStudioControlsBounds.height
    ));
  }

  @HostListener('document:pointerup')
  @HostListener('document:pointercancel')
  onDocumentPointerEnd(): void {
    if (!this.isDraggingThemeStudioControls()) {
      return;
    }

    this.isDraggingThemeStudioControls.set(false);
    this.themeStudioControlsDragOffset = null;
    this.themeStudioControlsBounds = null;
  }

  async ngOnInit(): Promise<void> {
    this.theme.initialize();

    void this.desktopUpdates.initialize();

    let currentUserId = getStoredCurrentUserId();

    await this.databaseService.initialize();

    if (currentUserId) {
      const persistedUserId = await this.databaseService.getCurrentUserId();

      if (persistedUserId !== currentUserId) {
        clearStoredCurrentUserId();
        currentUserId = null;
      }
    }

    try {
      const apiBase = this.servers.getApiBaseUrl();

      await this.timeSync.syncWithEndpoint(apiBase);
    } catch {}

    await this.notifications.initialize();

    await this.setupDesktopDeepLinks();

    this.userStatus.start();
    this.gameActivity.start();
    const currentUrl = this.getCurrentRouteUrl();

    if (!currentUserId) {
      if (!this.isPublicRoute(currentUrl)) {
        this.router.navigate(['/login'], {
          queryParams: {
            returnUrl: currentUrl
          }
        }).catch(() => {});
      }
    } else {
      this.store.dispatch(UsersActions.loadCurrentUser());
      this.store.dispatch(RoomsActions.loadRooms());

      const generalSettings = loadGeneralSettingsFromStorage();
      const lastViewedChat = loadLastViewedChatFromStorage(currentUserId);

      if (
        generalSettings.reopenLastViewedChat
        && lastViewedChat
        && (currentUrl === '/' || currentUrl === '/search')
      ) {
        this.router.navigate(['/room', lastViewedChat.roomId], { replaceUrl: true }).catch(() => {});
      }
    }

    this.router.events.subscribe((evt) => {
      if (evt instanceof NavigationEnd) {
        const url = evt.urlAfterRedirects || evt.url;

        this.currentRouteUrl.set(url);
        const roomMatch = url.match(ROOM_URL_PATTERN);
        const currentRoomId = roomMatch ? roomMatch[1] : null;

        this.voiceSession.checkCurrentRoute(currentRoomId);
      }
    });
  }

  ngOnDestroy(): void {
    this.deepLinkCleanup?.();
    this.deepLinkCleanup = null;
  }

  openNetworkSettings(): void {
    this.settingsModal.open('network');
  }

  openUpdatesSettings(): void {
    this.settingsModal.open('updates');
  }

  dismissDesktopUpdateNotice(): void {
    if (!this.desktopUpdateState().restartRequired) {
      return;
    }

    this.dismissedDesktopUpdateNoticeKey.set(this.desktopUpdateNoticeKey());
  }

  startThemeStudioControlsDrag(event: PointerEvent, controlsElement: HTMLElement): void {
    if (event.button !== 0) {
      return;
    }

    const rect = controlsElement.getBoundingClientRect();

    this.themeStudioControlsBounds = {
      width: rect.width,
      height: rect.height
    };

    this.themeStudioControlsDragOffset = {
      x: event.clientX - rect.left,
      y: event.clientY - rect.top
    };

    this.themeStudioControlsPosition.set({
      x: rect.left,
      y: rect.top
    });

    this.isDraggingThemeStudioControls.set(true);
    event.preventDefault();
  }

  reopenThemeStudio(): void {
    this.settingsModal.restoreMinimizedThemeStudio();
  }

  minimizeThemeStudio(): void {
    this.settingsModal.minimizeThemeStudio();
  }

  dismissMinimizedThemeStudio(): void {
    this.settingsModal.dismissMinimizedThemeStudio();
  }

  closeThemeStudio(): void {
    this.settingsModal.close();
  }

  async refreshDesktopUpdateContext(): Promise<void> {
    await this.desktopUpdates.refreshServerContext();
  }

  async restartToApplyUpdate(): Promise<void> {
    await this.desktopUpdates.restartToApplyUpdate();
  }

  private clampThemeStudioControlsPosition(left: number, top: number, width: number, height: number): { x: number; y: number } {
    const minX = App.THEME_STUDIO_CONTROLS_MARGIN;
    const minY = App.TITLE_BAR_HEIGHT + App.THEME_STUDIO_CONTROLS_MARGIN;
    const maxX = Math.max(minX, window.innerWidth - width - App.THEME_STUDIO_CONTROLS_MARGIN);
    const maxY = Math.max(minY, window.innerHeight - height - App.THEME_STUDIO_CONTROLS_MARGIN);

    return {
      x: Math.min(Math.max(minX, left), maxX),
      y: Math.min(Math.max(minY, top), maxY)
    };
  }

  private async setupDesktopDeepLinks(): Promise<void> {
    const electronApi = this.electronBridge.getApi();

    if (!electronApi) {
      return;
    }

    this.deepLinkCleanup = electronApi.onDeepLinkReceived?.((url) => {
      void this.handleDesktopDeepLink(url);
    }) || null;

    const pendingDeepLink = await electronApi.consumePendingDeepLink?.();

    if (pendingDeepLink) {
      await this.handleDesktopDeepLink(pendingDeepLink);
    }
  }

  private async handleDesktopDeepLink(url: string): Promise<void> {
    const invite = this.parseDesktopInviteUrl(url);

    if (!invite) {
      return;
    }

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

  private isPublicRoute(url: string): boolean {
    const path = this.getRoutePath(url);

    return path === '/login' ||
      path === '/register' ||
      path.startsWith('/invite/');
  }

  private getCurrentRouteUrl(): string {
    if (typeof window === 'undefined') {
      return this.router.url;
    }

    const currentUrl = `${window.location.pathname}${window.location.search}${window.location.hash}`;

    return currentUrl || this.router.url;
  }

  private getRoutePath(url: string): string {
    if (!url) {
      return '/';
    }

    const [path] = url.split(/[?#]/, 1);

    return path || '/';
  }

  private parseDesktopInviteUrl(url: string): { inviteId: string; sourceUrl: string } | null {
    try {
      const parsedUrl = new URL(url);

      if (parsedUrl.protocol !== 'toju:') {
        return null;
      }

      const pathSegments = [parsedUrl.hostname, ...parsedUrl.pathname.split('/').filter(Boolean)]
        .map((segment) => decodeURIComponent(segment));

      if (pathSegments[0] !== 'invite' || !pathSegments[1]) {
        return null;
      }

      const sourceUrl = parsedUrl.searchParams.get('server')?.trim();

      if (!sourceUrl) {
        return null;
      }

      return {
        inviteId: pathSegments[1],
        sourceUrl
      };
    } catch {
      return null;
    }
  }
}
