import {
  Directive,
  ElementRef,
  Input,
  OnDestroy,
  OnInit,
  Output,
  EventEmitter,
  NgZone,
  inject
} from '@angular/core';
import { fromEvent, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { A11yConfigService } from '../../services/a11y-config';

export type NavigationDirection = 'horizontal' | 'vertical' | 'both' | 'grid';

export interface NavigationEvent {
  direction: 'up' | 'down' | 'left' | 'right' | 'home' | 'end';
  currentIndex: number;
  nextIndex: number;
  event: KeyboardEvent;
}

@Directive({
  selector: '[uiArrowNavigation]',
  standalone: true,
  exportAs: 'uiArrowNavigation'
})
export class ArrowNavigationDirective implements OnInit, OnDestroy {
  private elementRef = inject(ElementRef);
  private ngZone = inject(NgZone);
  private config = inject(A11yConfigService);

  @Input('uiArrowNavigation') direction: NavigationDirection = 'vertical';
  @Input() wrap = true;
  @Input() skipDisabled = true;
  @Input() itemSelector = '[tabindex]:not([tabindex="-1"]), button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled])';
  @Input() gridColumns?: number;

  @Output() navigationChange = new EventEmitter<NavigationEvent>();

  private destroy$ = new Subject<void>();
  private navigableItems: HTMLElement[] = [];
  private currentIndex = 0;
  private isGridNavigation = false;

  ngOnInit(): void {
    if (this.config.isEnabled('keyboard.enableArrowNavigation')) {
      this.setupNavigation();
    }
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
  }

  /**
   * Navigate to a specific index
   */
  navigateToIndex(index: number): void {
    this.updateNavigableItems();
    
    if (index >= 0 && index < this.navigableItems.length) {
      this.currentIndex = index;
      this.focusCurrentItem();
    }
  }

  /**
   * Navigate to the first item
   */
  navigateToFirst(): void {
    this.navigateToIndex(0);
  }

  /**
   * Navigate to the last item
   */
  navigateToLast(): void {
    this.updateNavigableItems();
    this.navigateToIndex(this.navigableItems.length - 1);
  }

  /**
   * Get the current navigation index
   */
  getCurrentIndex(): number {
    return this.currentIndex;
  }

  /**
   * Get the total number of navigable items
   */
  getItemCount(): number {
    this.updateNavigableItems();
    return this.navigableItems.length;
  }

  /**
   * Set up navigation listeners
   */
  private setupNavigation(): void {
    if (typeof document === 'undefined') return;

    this.isGridNavigation = this.direction === 'grid';
    this.updateNavigableItems();
    this.setupKeyboardListeners();
    this.setupFocusTracking();
  }

  /**
   * Update the list of navigable items
   */
  private updateNavigableItems(): void {
    const container = this.elementRef.nativeElement;
    const items = Array.from(
      container.querySelectorAll(this.itemSelector)
    ) as HTMLElement[];

    this.navigableItems = this.skipDisabled 
      ? items.filter((item: HTMLElement) => !this.isDisabled(item))
      : items;

    // Update current index if current item is no longer available
    if (this.currentIndex >= this.navigableItems.length) {
      this.currentIndex = Math.max(0, this.navigableItems.length - 1);
    }
  }

  /**
   * Set up keyboard event listeners
   */
  private setupKeyboardListeners(): void {
    this.ngZone.runOutsideAngular(() => {
      fromEvent<KeyboardEvent>(this.elementRef.nativeElement, 'keydown').pipe(
        takeUntil(this.destroy$)
      ).subscribe(event => {
        this.handleKeyDown(event);
      });

      // Update items when DOM changes
      const observer = new MutationObserver(() => {
        this.updateNavigableItems();
      });

      observer.observe(this.elementRef.nativeElement, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['disabled', 'tabindex', 'hidden']
      });

      this.destroy$.subscribe(() => observer.disconnect());
    });
  }

  /**
   * Set up focus tracking to maintain current index
   */
  private setupFocusTracking(): void {
    this.ngZone.runOutsideAngular(() => {
      fromEvent(this.elementRef.nativeElement, 'focusin').pipe(
        takeUntil(this.destroy$)
      ).subscribe((event) => {
        const focusEvent = event as FocusEvent;
        const target = focusEvent.target as HTMLElement;
        const index = this.navigableItems.indexOf(target);
        if (index >= 0) {
          this.currentIndex = index;
        }
      });
    });
  }

  /**
   * Handle keyboard navigation
   */
  private handleKeyDown(event: KeyboardEvent): void {
    if (this.navigableItems.length === 0) return;

    const { key } = event;
    let nextIndex = this.currentIndex;
    let direction: NavigationEvent['direction'] | null = null;

    switch (key) {
      case 'ArrowUp':
        direction = 'up';
        nextIndex = this.isGridNavigation 
          ? this.getGridIndex('up') 
          : this.getVerticalIndex('up');
        break;
        
      case 'ArrowDown':
        direction = 'down';
        nextIndex = this.isGridNavigation 
          ? this.getGridIndex('down') 
          : this.getVerticalIndex('down');
        break;
        
      case 'ArrowLeft':
        direction = 'left';
        nextIndex = this.isGridNavigation 
          ? this.getGridIndex('left') 
          : this.getHorizontalIndex('left');
        break;
        
      case 'ArrowRight':
        direction = 'right';
        nextIndex = this.isGridNavigation 
          ? this.getGridIndex('right') 
          : this.getHorizontalIndex('right');
        break;
        
      case 'Home':
        direction = 'home';
        nextIndex = 0;
        break;
        
      case 'End':
        direction = 'end';
        nextIndex = this.navigableItems.length - 1;
        break;
        
      default:
        return; // Don't handle other keys
    }

    if (direction && this.shouldHandleNavigation(key)) {
      event.preventDefault();
      event.stopPropagation();

      const navigationEvent: NavigationEvent = {
        direction,
        currentIndex: this.currentIndex,
        nextIndex,
        event
      };

      this.ngZone.run(() => {
        this.navigationChange.emit(navigationEvent);
      });

      if (nextIndex !== this.currentIndex && nextIndex >= 0 && nextIndex < this.navigableItems.length) {
        this.currentIndex = nextIndex;
        this.focusCurrentItem();
      }
    }
  }

  /**
   * Check if we should handle this navigation key
   */
  private shouldHandleNavigation(key: string): boolean {
    switch (this.direction) {
      case 'horizontal':
        return ['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(key);
      case 'vertical':
        return ['ArrowUp', 'ArrowDown', 'Home', 'End'].includes(key);
      case 'both':
      case 'grid':
        return ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(key);
      default:
        return false;
    }
  }

  /**
   * Get next index for vertical navigation
   */
  private getVerticalIndex(direction: 'up' | 'down'): number {
    const delta = direction === 'up' ? -1 : 1;
    let nextIndex = this.currentIndex + delta;

    if (this.wrap) {
      if (nextIndex < 0) {
        nextIndex = this.navigableItems.length - 1;
      } else if (nextIndex >= this.navigableItems.length) {
        nextIndex = 0;
      }
    } else {
      nextIndex = Math.max(0, Math.min(this.navigableItems.length - 1, nextIndex));
    }

    return nextIndex;
  }

  /**
   * Get next index for horizontal navigation
   */
  private getHorizontalIndex(direction: 'left' | 'right'): number {
    const delta = direction === 'left' ? -1 : 1;
    let nextIndex = this.currentIndex + delta;

    if (this.wrap) {
      if (nextIndex < 0) {
        nextIndex = this.navigableItems.length - 1;
      } else if (nextIndex >= this.navigableItems.length) {
        nextIndex = 0;
      }
    } else {
      nextIndex = Math.max(0, Math.min(this.navigableItems.length - 1, nextIndex));
    }

    return nextIndex;
  }

  /**
   * Get next index for grid navigation
   */
  private getGridIndex(direction: 'up' | 'down' | 'left' | 'right'): number {
    if (!this.gridColumns) {
      // Fallback to regular navigation if no columns specified
      return direction === 'up' || direction === 'down' 
        ? this.getVerticalIndex(direction as 'up' | 'down')
        : this.getHorizontalIndex(direction as 'left' | 'right');
    }

    const currentRow = Math.floor(this.currentIndex / this.gridColumns);
    const currentCol = this.currentIndex % this.gridColumns;
    let nextIndex = this.currentIndex;

    switch (direction) {
      case 'up':
        if (currentRow > 0) {
          nextIndex = (currentRow - 1) * this.gridColumns + currentCol;
        } else if (this.wrap) {
          const lastRow = Math.floor((this.navigableItems.length - 1) / this.gridColumns);
          nextIndex = Math.min(lastRow * this.gridColumns + currentCol, this.navigableItems.length - 1);
        }
        break;

      case 'down':
        const nextRow = currentRow + 1;
        const nextRowIndex = nextRow * this.gridColumns + currentCol;
        if (nextRowIndex < this.navigableItems.length) {
          nextIndex = nextRowIndex;
        } else if (this.wrap) {
          nextIndex = currentCol < this.navigableItems.length ? currentCol : 0;
        }
        break;

      case 'left':
        if (currentCol > 0) {
          nextIndex = this.currentIndex - 1;
        } else if (this.wrap) {
          const lastInRow = Math.min((currentRow + 1) * this.gridColumns - 1, this.navigableItems.length - 1);
          nextIndex = lastInRow;
        }
        break;

      case 'right':
        if (currentCol < this.gridColumns - 1 && this.currentIndex + 1 < this.navigableItems.length) {
          nextIndex = this.currentIndex + 1;
        } else if (this.wrap) {
          nextIndex = currentRow * this.gridColumns;
        }
        break;
    }

    return nextIndex;
  }

  /**
   * Focus the current item
   */
  private focusCurrentItem(): void {
    if (this.navigableItems[this.currentIndex]) {
      this.navigableItems[this.currentIndex].focus();
    }
  }

  /**
   * Check if an item is disabled
   */
  private isDisabled(item: HTMLElement): boolean {
    return item.hasAttribute('disabled') || 
           item.getAttribute('aria-disabled') === 'true' ||
           item.tabIndex === -1;
  }
}