import { Component, ViewChild, ChangeDetectionStrategy, ElementRef, ChangeDetectorRef, AfterViewInit, inject, computed, signal, effect } from '@angular/core';
import { CommonData, CommonService, ScreenItem } from '../services/common.service';
import { Title } from '@angular/platform-browser';
import { MessageService, promptAction } from '../services/message.service';
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
import { AuthClass } from '../utils/authClass';
import { KENDO_AUTOCOMPLETE } from '@progress/kendo-angular-dropdowns';
import { BehaviorSubject, Observable } from 'rxjs';
import { catchError, map, tap, delay, switchMap, filter } from 'rxjs/operators';
import { NotifyService } from '../services/notify.service';
import * as util from '../utils/util';
import { KENDO_MENU, MenuSelectEvent } from '@progress/kendo-angular-menu';
import { AsyncPipe } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { GlobalFavoritesComponent } from '../global-favorites/global-favorites.component';
import { GlobalBarIconPipe } from '../pipes/global-bar-icon.pipe';
import { FastWindowComponent } from "../elements/fast-window.component";
import { FastTipDirective } from '../elements/fast-tip.directive';
import { pressedKeys, uid } from '../utils/util';
import { toObservable } from '@angular/core/rxjs-interop';
import { FastAutocompleteComponent } from '../elements/fast-autocomplete.component';
import { FastSVGComponent } from '../elements/fast-svg.component';
import { AuthService } from '../services/auth.service';

interface menuItem {
  text: string;
  path: string;
  type: 'item' | 'category' | 'link' | 'entry';
  items: menuItem[];
}
@Component({
  selector: 'global-bar',
  templateUrl: './global-bar.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  styles: `
    .homeImage:hover {
      filter: drop-shadow(0 0 4px var(--color-highlight)) drop-shadow(0 0 4px var(--color-highlight)) drop-shadow(0 0 4px var(--color-highlight));
    }

    .homeImage:hover:active {
      filter: drop-shadow(0 0 4px var(--color-highlight)) drop-shadow(0 0 4px var(--color-highlight)) drop-shadow(0 0 4px var(--color-highlight)) blur(1px);
    }
  `,
  imports: [AsyncPipe, KENDO_AUTOCOMPLETE, KENDO_MENU, FormsModule, GlobalFavoritesComponent, GlobalBarIconPipe, FastWindowComponent, FastTipDirective, FastAutocompleteComponent, FastSVGComponent, RouterLink,]
})
export class GlobalBarComponent implements AfterViewInit {
  @ViewChild("autoComplete") autoComplete: FastAutocompleteComponent<object>;
  @ViewChild("copyInfoElem") copyInfoElem: ElementRef;
  @ViewChild("copyErrorElem") copyErrorElem: ElementRef;
  @ViewChild("searchTip") searchTip: FastTipDirective;
  @ViewChild("searchAnchor") searchAnchorElem: ElementRef;

  commonService = inject(CommonService);
  messageService = inject(MessageService);
  titleService = inject(Title);
  notify = inject(NotifyService);
  activatedRoute = inject(ActivatedRoute);
  router = inject(Router);
  ref = inject(ChangeDetectorRef);
  private readonly authService = inject(AuthService);


  readonly minFilterLength = 3;

  areFavoritesOpen$: Observable<boolean>
  isToplessSubject = new BehaviorSubject<boolean>(false)
  isTopless$: Observable<boolean>
  errorText$: Observable<string>
  infoText$: Observable<string>
  filterAction$ = new BehaviorSubject<string>(null)

  selectedItemText = signal<string>(null)
  screenItems$: Observable<ScreenItem[]>
  selectedScreenItem$: Observable<[CommonData, string]>
  searchText: string = null;

  promptTitle = this.messageService.promptTitle;
  promptMessage = this.messageService.promptMessage;
  promptType = this.messageService.promptType;

  constructor() {
    this.commonService.refreshCommonData.set({ uid: uid() });

    effect(() => {
      if (this.appName())
        util.trySetTitle(this.titleService, this.appName(), false)
    });

    this.areFavoritesOpen$ = this.commonService.areFavoritesOpenResult$;
    this.isTopless$ = this.isToplessSubject.asObservable();
    this.errorText$ = this.messageService.errorMessage$;
    this.infoText$ = this.messageService.infoMessage$;

    const commonData$ = toObservable(this.commonService.commonDataRes.value);

    this.screenItems$ = this.filterAction$.pipe(
      tap((filterText: string) => {
        if (!filterText || (filterText.length < this.minFilterLength)) {
          if (this.autoComplete?.isOpen)
            this.autoComplete.toggle(false);
        }
      }),
      switchMap((filterText: string) => commonData$.pipe(
        filter(data => data != null),
        delay(100),
        map(data => {
          let screenItems: ScreenItem[] = data.screenItems.filter(x => x.isVisible && x.userHasPermission) ?? [];
          if (screenItems && filterText && filterText.length >= this.minFilterLength) {
            screenItems = screenItems.filter(s => {
              const searchterms = filterText.split(/\s+/);
              //loops through searchterms and returns true only if the list item has every term
              const ismatch: boolean = searchterms.reduce(function (result, term) {
                const nameAndKeywords = `${s.name};${s.keywords};`
                return result && nameAndKeywords.toUpperCase().includes(term.toUpperCase());;
              }, true);
              return ismatch;
            })
            return screenItems;
          }
        })
      )),
      catchError(err => util.handleError(err, this.messageService))
    )

    effect(() => {
      const data = this.commonService.commonDataRes.value();
      const selectedItemText = this.selectedItemText();
      if (!data)
        return undefined;

      const screenItems = data.screenItems.filter(x => x.isVisible && x.userHasPermission);
      if (selectedItemText && selectedItemText.length >= this.minFilterLength) {
        const screenItem = screenItems.find((s) => s.name === selectedItemText);
        if (screenItem)
          this.externalNavigate(screenItem.path);
        setTimeout(() => {
          this.searchText = null;
          this.ref.detectChanges();
        }, 100);
      }
    })

    effect(() => {
      const data = this.commonService.commonDataRes.value();
      if (!data)
        return undefined;

      const userInfo = data.userInfo;
      localStorage.setItem('displayName', userInfo.displayName);
    })
  }

  appName = computed(() => {
    return AuthClass.ServerSettings.appName;
  });

  companyHeaderStr = computed(() => {
    return AuthClass.ServerSettings.companyHeaderStr;
  });

  isDashModule = computed(() => {
    return AuthClass.ServerSettings.moduleName.toLowerCase().includes('dash');
  });

  homeUrl = computed(() => {
    const data = this.commonService.commonDataRes.value();
    if (!data)
      return undefined;

    return data.homeUrl;
  });

  modulesMenus = computed(() => {
    const data = this.commonService.commonDataRes.value();
    if (!data)
      return undefined;

    const entryMenu: menuItem = { text: '', items: null, path: null, type: 'entry' };

    //if data is null, then commonData$ has not yet emitted, but we still want to render the entry menu immediately
    if (data === null)
      return [entryMenu];

    const menuItems: menuItem[] = data.moduleMenus.map(topMenu => {
      const childItems: menuItem[] = topMenu.children.map(childMenu => {
        const childItem: menuItem = { text: childMenu.name, path: childMenu.path, type: 'item', items: null };
        return childItem;
      });
      const topItem: menuItem = { text: topMenu.name, path: childItems.length === 1 ? childItems[0].path : null, type: 'category', items: childItems };
      return topItem;
    });

    entryMenu.items = menuItems;
    return [entryMenu];
  });

  ngAfterViewInit(): void {
    this.activatedRoute.queryParams.subscribe(x => {
      const isTopless = x['topless'] ? true : false;
      this.isToplessSubject.next(isTopless);
    });
  }

  public onMenuSelect({ item }: MenuSelectEvent): void {
    if (item.path) {
      this.externalNavigate(item.path);
    }
  }

  signOut(): void {
    this.authService.logout();
  }

  closeError(): void {
    //throwing a null error closes the error dialog
    this.messageService.throw(null);
  }

  closeInfo(): void {
    //raising a null info closes the info dialog
    this.messageService.info(null);
  }

  async copyInfo() {
    this.copyInfoElem.nativeElement.select();
    const selection = window.getSelection()?.toString();
    if (selection)
      await navigator.clipboard.writeText(selection);
    this.notify.show('copied', 'success', false, 'top');
  }

  async copyError() {
    this.copyErrorElem.nativeElement.select();
    const selection = window.getSelection()?.toString();
    if (selection)
      await navigator.clipboard.writeText(selection);
    this.notify.show('copied', 'success', false, 'top');
  }

  valueChange(value: string): void {
    if (value && value.length >= this.minFilterLength)
      this.selectedItemText.set(value);
  }

  filterChange(filter: string): void {
    this.filterAction$.next(filter);
    if (filter.length > 0 && filter.length < this.minFilterLength)
      this.searchTip.show(this.searchAnchorElem);
    else
      this.searchTip.hide();
  }

  externalNavigate(path: string) {
    this.router.navigate(['/externalRedirect', { externalUrl: path }], {
      skipLocationChange: true,
    });
  }

  setMouseOver(isMouseOver: boolean, force: boolean = false) {
    this.commonService.setMouseOver(isMouseOver, force);
  }

  toggleFavorites() {
    this.commonService.toggleFavorites();
  }

  onHomeClick(): void {
    //in case the home url and the current page are the same, then we need to manually trigger a window reload
    //otherwise, the browser does nothing when clicked since the URL has a hashtag
    const isShiftPressed = pressedKeys['Shift'];
    const isCtrlPressed = pressedKeys['Control'];
    const data = this.commonService.commonDataRes.value();
    const homeUrl = data.homeUrl;
    const homeUrlFull = new URL(homeUrl, window.location.origin).href;
    const currentUrl = window.location.href;
    if (currentUrl === homeUrlFull && !isShiftPressed && !isCtrlPressed)
      window.location.reload();
  }

  setPromptResult(result: promptAction) {
    this.messageService.promptResult.set(result);
  }
}
