import {CommonModule} from '@angular/common';
import {
  AfterViewInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChildren,
  EventEmitter,
  Input,
  OnDestroy,
  Output,
  QueryList,
  ViewEncapsulation
} from '@angular/core';
import {Subject, takeUntil} from 'rxjs';
import {Utils} from '../../utils/utils.util';
import {TabComponent} from '../tab/tab.component';
import {TabsDensity} from './tabs.model';

@Component({
  selector: 'nj-tabs',
  templateUrl: './tabs.component.html',
  styleUrls: ['./tabs.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  standalone: true,
  imports: [TabComponent, CommonModule]
})
export class TabsComponent implements AfterViewInit, OnDestroy {

  private activeTabIndex;

  private readonly tabClass = 'nj-tab';

  private unsubscribe: Subject<void> = new Subject<void>();

  /**
   * @ignore
   */
  public tabToRender: TabComponent;

  /**
   * @ignore
   */
  public tabs: TabComponent[];

  /**
   * Label for the tablist element
   */
  @Input() label: string;

  /**
   * Tabs density
   */
  @Input() density: TabsDensity = 'normal';

  /**
   * @ignore
   */
  @ContentChildren(TabComponent) tabsList: QueryList<TabComponent>;

  /**
   * Output that emits selected tab index
   */
  @Output() selectedTab: EventEmitter<number> = new EventEmitter<number>();

  constructor(private cdr: ChangeDetectorRef) {
  }

  ngAfterViewInit() {
    // A render is being done after view init so setTimeout allows us to delay any attribute modification
    // in the call stack so it can be taken account on next render only
    setTimeout(() => {
      this.tabs = this.tabsList?.toArray();

      this.initializeClickListener();
      this.initializeKeydownListener();

      const activeIndex = this.tabs?.findIndex(tab => tab?.isActive);
      this.goToTab(activeIndex === -1 ? 0 : activeIndex, false);
    });
  }

  ngOnDestroy() {
    this.unsubscribe.next();
    this.unsubscribe.complete();
  }

  /**
   * Check if tab is active
   * @param index index of tab to check
   */
  isActiveTab(index: number): boolean {
    return this.activeTabIndex === index;
  }

  /**
   * Allows you to navigate to tab
   * @param index index of tab to select
   * @param emit emits if set to true
   */
  goToTab(index: number, emit = true) {
    const validIndex = (Utils.isUndefinedOrNull(index) || index < 0) ? 0 : index;

    this.activeTabIndex = validIndex;
    this.renderTemplate(validIndex);
    if (emit) {
      this.selectedTab.emit(validIndex);
    }
    this.cdr.markForCheck();
  }

  /**
   * @ignore
   */
  focusNextFocusableTab() {
    const focusableTabs = Array.from(this.tabs).filter(tab => !tab.isDisabled);
    const focusedTabIndex = focusableTabs.findIndex(tab => document.activeElement === tab.tab.nativeElement);
    const nextFocusableTab = focusableTabs[(focusedTabIndex + 1) % focusableTabs.length];

    nextFocusableTab.tab.nativeElement.focus();
  }

  /**
   * @ignore
   */
  focusPreviousFocusableTab() {
    const focusableTabs = Array.from(this.tabs).filter(tab => !tab.isDisabled);
    const focusedTabIndex = focusableTabs.findIndex(tab => document.activeElement === tab.tab.nativeElement);

    const previousFocusableTabIndex = focusedTabIndex === 0
      ? focusableTabs.length - 1
      : focusedTabIndex - 1;

    const previousFocusableTab = focusableTabs[previousFocusableTabIndex];

    previousFocusableTab.tab.nativeElement.focus();
  }

  /**
   * @ignore
   */
  handleTabKeydown(event: KeyboardEvent) {
    switch (event.key) {
      case 'ArrowRight':
        event.preventDefault();
        this.focusNextFocusableTab();
        break;
      case 'ArrowLeft':
        event.preventDefault();
        this.focusPreviousFocusableTab();
        break;
      default:
    }
  }

  getDensityClass() {
    if(!this.density || this.density === 'normal') {
      return;
    }
    return `${this.tabClass}--${this.density}`;
  }

  private initializeClickListener() {
    if (Utils.isUndefinedOrNull(this.tabs)) {
      return;
    }
    this.tabs.forEach((tab, index) => {
      tab.tabSelect
        .pipe(takeUntil(this.unsubscribe))
        .subscribe(_ => {
          this.goToTab(index);
        });
    });
  }

  private initializeKeydownListener() {
    if (Utils.isUndefinedOrNull(this.tabs)) {
      return;
    }
    this.tabs.forEach(tab => {
      tab.tabMove.pipe(takeUntil(this.unsubscribe))
      .subscribe(event => {
        this.handleTabKeydown(event);
      });
    });
  }

  private renderTemplate(index: number) {
    for (const tab of this.tabs) {
      if (tab?.isActive) {
        tab.setIsActive(false);
      }
    }
    this.tabToRender = this.tabs?.[index];
    this.tabToRender.setIsActive(true);
  }
}
