import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import { Directionality } from '@angular/cdk/bidi';
import { NgTemplateOutlet } from '@angular/common';
import { ChangeDetectionStrategy, Component, computed, contentChildren, inject, Injector, input, model, output, viewChildren, } from '@angular/core';
import { FastTabHeaderComponent } from './fast-tabheader.component';
import { FastTabPanelComponent } from './fast-tabpanel.component';
import { FastLabelComponent } from "./fast-label.component";

type TAB_ALIGNMENT = "vertical" | "horizontal";

@Component({
  selector: 'fast-tabgroup',
  imports: [NgTemplateOutlet, FastTabHeaderComponent, FastLabelComponent],
  template: `
  <div [class]="(this.alignment() === 'vertical' ? 'flex flex-row' : 'flex flex-col') + ' grow'">
    <div
      [class]="conditionalClasses()"
      #tabGroupHeader
      role="tablist"
      (keydown)="onKeydown($event)"
      tabindex="0"
      (focus)="onTabGroupFocus()"
    >
    @if (headerTitle()) {
      <div class="px-4 py-2 border-b border-base-gray-1000 dark:border-alt-blue-250">
        <fast-label>{{ headerTitle() }}</fast-label>
      </div>
    }
      @for (tab of tabs(); track $index) {
        <fast-tabheader
          [class]="(this.alignment() === 'vertical' ? 'justify-start' : 'items-center') + ' m-px'"
          [style.width.px]="headerWidth()"
          [tab]="tab"
          [isSelected]="$index === selectedIndex()"
          [tabIndex]="$index"
          [isActive]="keyManager.activeItemIndex === $index"
          [isDisabled]="tab.disabled()"
          #tabHeader
          (click)="selectTab($index)"
        ></fast-tabheader>
      }
    </div>
    <!--we need explicitely set ng-content, in order ssr work properly see (https://github.com/angular/angular/issues/50543)-->
    <ng-content></ng-content>
    <div class="flex"
      class="tab-content flex flex-col grow overflow-auto"
      data-testid="tab-content"
      role="tabpanel"
      [attr.id]="'tabpanel-' + selectedIndex()"
      [attr.aria-labelledby]="'tab-' + selectedIndex()"
    >
      @if (selectedTab(); as selectedTab) {
        <div [class.p-5]="selectedTab.gap()">
          @if (selectedTab.bodyTemplate(); as bodyTemplate) {
            <ng-container
              [ngTemplateOutlet]="bodyTemplate.templateRef"
            ></ng-container>
          } @else {
            @if (selectedTab.templateRef(); as templateRef) {
              <ng-container [ngTemplateOutlet]="templateRef"></ng-container>
            }
          }
        </div>
      }
    </div>
  </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class FastTabGroupComponent {
  /** Collection of tab panel components projected into this tab group */
  tabs = contentChildren(FastTabPanelComponent);

  /** The currently selected tab index, two-way bindable */
  selectedIndex = model(0);

  /** Whether to add padding to the tab content area */
  bodyGap = input(true);

  /** Collection of tab header components for keyboard navigation */
  tabHeaders = viewChildren(FastTabHeaderComponent);

  /** Service for determining text direction (RTL/LTR) */
  direction = inject(Directionality);

  alignment = input<TAB_ALIGNMENT>('horizontal');

  headerWidth = input<number>(null);

  headerTitle = input<string>(null);

  /**
   * Manages keyboard navigation between tab headers.
   * Handles arrow key navigation and focus management according to ARIA best practices.
   */
  keyManager = new ActiveDescendantKeyManager(
    this.tabHeaders,
    inject(Injector)
  ).withHorizontalOrientation(this.direction.value);

  /**
   * Handles keyboard navigation events for the tab group.
   * Responds to Enter key to select the currently focused tab and delegates
   * other navigation keys to the key manager.
   *
   * @param event - The keyboard event to process
   */
  onKeydown(event: KeyboardEvent) {
    if (event.key === 'Enter' && this.keyManager.activeItemIndex !== null) {
      this.selectTab(this.keyManager.activeItemIndex);
    }
    this.keyManager.onKeydown(event);
  }

  /** Event emitted when the selected tab changes, containing the new tab index */
  tabChanged = output<number>();

  /** Computed property that returns the currently selected tab panel component */
  selectedTab = computed(() =>
    this.tabs().find((_, index) => index === this.selectedIndex())
  );

  /**
   * Handles focus events on the tab group container.
   * Ensures the first tab receives focus when the tab group is focused for the first time.
   */
  onTabGroupFocus() {
    if (!this.keyManager.activeItem) {
      this.keyManager.setFirstItemActive();
    }
  }

  /**
   * Programmatically selects a tab by index.
   * Updates the key manager's active item, sets the selected index, and emits the change event.
   *
   * @param index - The zero-based index of the tab to select
   */
  selectTab(index: number) {
    this.keyManager.setActiveItem(index);
    this.selectedIndex.set(index);
    this.tabChanged.emit(index);
  }

  conditionalClasses = computed(() => {
    const classes = [] as string[];

    classes.push(...this.getCommonClasses());
    classes.push(...this.getLightBaseClasses());
    classes.push(...this.getDarkBaseClasses());

    const conditionalClasses = this.getConditionalClassesFromArrays(classes);
    return conditionalClasses;
  });

  getConditionalClassesFromArrays(classArray: string[]): { [key: string]: boolean } {
    const classes: { [key: string]: boolean } = {};
    classArray.forEach(className => {
      classes[className] = true;
    });
    return classes;
  }

  getCommonClasses() {
    const commonClasses = [] as string[];
    commonClasses.push(
      "relative",
      "flex",
      "m-0.5",
      "focus:outline-none",
      "focus-visible:[&_app-tab-header.is-active]:ring-1",
      "focus-visible:[&_app-tab-header.is-active]:ring-blue-500",
    );
    if (this.alignment() === 'vertical') {
      commonClasses.push("flex-col", "border-b-0", "justify-start");
    }
    else {
      commonClasses.push("flex-row", "border-b",);
    }

    return commonClasses;
  }

  getLightBaseClasses() {
    const lightBase = ["text-base-black-1000", "bg-base-white-500", "border-base-gray-1000", "border-r-1"]

    return lightBase;
  }

  getDarkBaseClasses() {
    const darkBase = ["dark:bg-alt-gray-750", "dark:text-base-white-500", "dark:border-alt-blue-250", "dark:border-r-1"]

    return darkBase;
  }
}
