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

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

  @Input('uiFocusTrap') enabled = true;
  @Input() autoFocus = true;
  @Input() restoreFocus = true;

  private destroy$ = new Subject<void>();
  private previouslyFocusedElement: HTMLElement | null = null;
  private focusableElements: HTMLElement[] = [];
  private firstFocusable: HTMLElement | null = null;
  private lastFocusable: HTMLElement | null = null;

  ngOnInit(): void {
    if (this.enabled) {
      this.setupFocusTrap();
    }
  }

  ngOnDestroy(): void {
    this.destroy$.next();
    this.destroy$.complete();
    
    if (this.restoreFocus && this.previouslyFocusedElement) {
      this.previouslyFocusedElement.focus();
    }
  }

  /**
   * Enable the focus trap
   */
  enable(): void {
    this.enabled = true;
    this.setupFocusTrap();
  }

  /**
   * Disable the focus trap
   */
  disable(): void {
    this.enabled = false;
    this.destroy$.next();
    
    if (this.restoreFocus && this.previouslyFocusedElement) {
      this.previouslyFocusedElement.focus();
    }
  }

  /**
   * Manually focus the first focusable element
   */
  focusFirst(): void {
    this.updateFocusableElements();
    if (this.firstFocusable) {
      this.firstFocusable.focus();
    }
  }

  /**
   * Manually focus the last focusable element
   */
  focusLast(): void {
    this.updateFocusableElements();
    if (this.lastFocusable) {
      this.lastFocusable.focus();
    }
  }

  /**
   * Set up the focus trap
   */
  private setupFocusTrap(): void {
    if (!this.enabled || typeof document === 'undefined') return;

    // Store the currently focused element to restore later
    this.previouslyFocusedElement = document.activeElement as HTMLElement;

    // Set up the trap
    setTimeout(() => {
      this.updateFocusableElements();
      this.setupKeyboardListeners();
      
      if (this.autoFocus) {
        this.focusFirst();
      }
    }, 0);
  }

  /**
   * Update the list of focusable elements
   */
  private updateFocusableElements(): void {
    const container = this.elementRef.nativeElement;
    
    const focusableSelectors = [
      'a[href]:not([disabled])',
      'button:not([disabled])',
      'textarea:not([disabled])',
      'input:not([disabled])',
      'select:not([disabled])',
      '[tabindex]:not([tabindex="-1"]):not([disabled])',
      '[contenteditable="true"]:not([disabled])',
      'audio[controls]:not([disabled])',
      'video[controls]:not([disabled])',
      'iframe:not([disabled])',
      'object:not([disabled])',
      'embed:not([disabled])',
      'area[href]:not([disabled])',
      'summary:not([disabled])'
    ].join(', ');

    const elements = container.querySelectorAll(focusableSelectors);
    this.focusableElements = Array.from(elements)
      .filter((element) => {
        const htmlElement = element as HTMLElement;
        // Filter out hidden elements
        return (
          htmlElement.offsetWidth > 0 ||
          htmlElement.offsetHeight > 0 ||
          htmlElement.getClientRects().length > 0
        );
      }) as HTMLElement[];

    this.firstFocusable = this.focusableElements[0] || null;
    this.lastFocusable = this.focusableElements[this.focusableElements.length - 1] || null;
  }

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

      // Update focusable elements when DOM changes
      const observer = new MutationObserver(() => {
        this.updateFocusableElements();
      });

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

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

  /**
   * Handle Tab key press
   */
  private handleTabKey(event: KeyboardEvent): void {
    if (!this.enabled || this.focusableElements.length === 0) return;

    // Check if focus is within our trap
    const activeElement = document.activeElement as HTMLElement;
    const isWithinTrap = this.elementRef.nativeElement.contains(activeElement);
    
    if (!isWithinTrap) {
      // Focus escaped the trap, bring it back
      event.preventDefault();
      this.focusFirst();
      return;
    }

    // Handle normal tab navigation within trap
    if (event.shiftKey) {
      // Shift + Tab (backward)
      if (activeElement === this.firstFocusable) {
        event.preventDefault();
        this.focusLast();
      }
    } else {
      // Tab (forward)
      if (activeElement === this.lastFocusable) {
        event.preventDefault();
        this.focusFirst();
      }
    }
  }
}