import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy, ViewEncapsulation, OnInit, OnDestroy, AfterViewInit, inject, ElementRef, Renderer2, ViewChild, HostListener } from '@angular/core';
import { CommonModule, DOCUMENT } from '@angular/common';
import { signal, computed } from '@angular/core';

type PopoverPosition = 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
type PopoverSize = 'sm' | 'md' | 'lg';
type PopoverVariant = 'default' | 'elevated' | 'floating' | 'menu' | 'tooltip';
type PopoverTrigger = 'click' | 'hover' | 'focus' | 'manual';

export interface PopoverConfig {
  position?: PopoverPosition;
  size?: PopoverSize;
  variant?: PopoverVariant;
  trigger?: PopoverTrigger;
  visible?: boolean;
  closable?: boolean;
  backdropClosable?: boolean;
  escapeClosable?: boolean;
  showArrow?: boolean;
  showBackdrop?: boolean;
  autoPosition?: boolean;
  offset?: number;
  delay?: number;
  hoverDelay?: number;
  focusable?: boolean;
  preventBodyScroll?: boolean;
}

export interface PopoverPositionData {
  top: number;
  left: number;
  position: PopoverPosition;
}

@Component({
  selector: 'ui-popover',
  standalone: true,
  imports: [CommonModule],
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  template: `
    @if (isVisible()) {
      <!-- Backdrop (optional) -->
      @if (showBackdrop) {
        <div 
          class="ui-popover-backdrop"
          [class.ui-popover-backdrop--visible]="isVisible()"
          (click)="handleBackdropClick($event)"
        ></div>
      }

      <!-- Popover Container -->
      <div 
        class="ui-popover"
        [class.ui-popover--{{size}}]="size"
        [class.ui-popover--{{variant}}]="variant"
        [class.ui-popover--{{computedPosition()}}]="computedPosition()"
        [class.ui-popover--visible]="isVisible()"
        [class.ui-popover--entering]="isEntering()"
        [class.ui-popover--leaving]="isLeaving()"
        [class.ui-popover--disabled]="disabled"
        [class.ui-popover--slide-in]="animationType === 'slide'"
        [style.top.px]="positionData().top"
        [style.left.px]="positionData().left"
        [style.transform-origin]="transformOrigin()"
        [attr.role]="variant === 'tooltip' ? 'tooltip' : 'dialog'"
        [attr.aria-labelledby]="title ? titleId : null"
        [attr.aria-describedby]="contentId"
        [attr.aria-hidden]="!isVisible()"
        [attr.tabindex]="focusable ? 0 : -1"
        (keydown)="handleKeydown($event)"
        (click)="$event.stopPropagation()"
        #popoverElement
      >
        <!-- Arrow -->
        @if (showArrow && variant !== 'elevated' && variant !== 'floating') {
          <div class="ui-popover__arrow"></div>
        }

        <!-- Header (optional) -->
        @if (title || headerSlot) {
          <div class="ui-popover__header">
            @if (title) {
              <h3 class="ui-popover__title" [id]="titleId">{{ title }}</h3>
            } @else {
              <ng-content select="[slot='header']"></ng-content>
            }
          </div>
        }

        <!-- Content -->
        <div 
          class="ui-popover__content"
          [id]="contentId"
        >
          <ng-content></ng-content>
        </div>

        <!-- Footer (optional) -->
        @if (footerSlot) {
          <div 
            class="ui-popover__footer"
            [class.ui-popover__footer--start]="footerAlignment === 'start'"
            [class.ui-popover__footer--center]="footerAlignment === 'center'"
            [class.ui-popover__footer--between]="footerAlignment === 'between'"
          >
            <ng-content select="[slot='footer']"></ng-content>
          </div>
        }
      </div>
    }
  `,
  styleUrl: './popover.component.scss'
})
export class PopoverComponent implements OnInit, OnDestroy, AfterViewInit {
  // Dependencies
  private elementRef = inject(ElementRef);
  private renderer = inject(Renderer2);
  private document = inject(DOCUMENT);

  // ViewChild references
  @ViewChild('popoverElement', { static: false }) popoverElement?: ElementRef<HTMLElement>;

  // Inputs
  @Input() position: PopoverPosition = 'bottom';
  @Input() size: PopoverSize = 'md';
  @Input() variant: PopoverVariant = 'default';
  @Input() trigger: PopoverTrigger = 'click';
  @Input() title = '';
  @Input() disabled = false;
  @Input() closable = true;
  @Input() backdropClosable = false;
  @Input() escapeClosable = true;
  @Input() showArrow = true;
  @Input() showBackdrop = false;
  @Input() autoPosition = true;
  @Input() offset = 8;
  @Input() delay = 0;
  @Input() hoverDelay = 300;
  @Input() focusable = true;
  @Input() preventBodyScroll = false;
  @Input() animationType: 'scale' | 'slide' = 'scale';
  @Input() footerAlignment: 'start' | 'center' | 'end' | 'between' = 'end';

  // Reference element (trigger)
  @Input() triggerElement?: HTMLElement;

  // State Inputs
  @Input() set visible(value: boolean) {
    if (value !== this._visible()) {
      this._visible.set(value);
      if (value) {
        this.show();
      } else {
        this.hide();
      }
    }
  }

  get visible(): boolean {
    return this._visible();
  }

  // Outputs
  @Output() visibleChange = new EventEmitter<boolean>();
  @Output() shown = new EventEmitter<void>();
  @Output() hidden = new EventEmitter<void>();
  @Output() positionChange = new EventEmitter<PopoverPosition>();
  @Output() backdropClicked = new EventEmitter<MouseEvent>();
  @Output() escapePressed = new EventEmitter<KeyboardEvent>();

  // Internal state signals
  private _visible = signal(false);
  private _entering = signal(false);
  private _leaving = signal(false);
  private _computedPosition = signal<PopoverPosition>(this.position);
  private _positionData = signal<PopoverPositionData>({ top: 0, left: 0, position: this.position });

  // Computed signals
  readonly isVisible = computed(() => this._visible() || this._entering() || this._leaving());
  readonly isEntering = computed(() => this._entering());
  readonly isLeaving = computed(() => this._leaving());
  readonly computedPosition = computed(() => this._computedPosition());
  readonly positionData = computed(() => this._positionData());

  // Internal properties
  private showTimer?: number;
  private hideTimer?: number;
  private resizeObserver?: ResizeObserver;
  private mutationObserver?: MutationObserver;
  private originalBodyOverflow = '';
  private animationDuration = 200; // ms
  private positionUpdateCounter = 0;

  // Template helpers
  readonly headerSlot = false; // Could be enhanced to detect slotted content
  readonly footerSlot = false; // Could be enhanced to detect slotted content

  // Unique IDs for accessibility
  readonly titleId = `popover-title-${Math.random().toString(36).substr(2, 9)}`;
  readonly contentId = `popover-content-${Math.random().toString(36).substr(2, 9)}`;

  readonly transformOrigin = computed(() => {
    const position = this.computedPosition();
    switch (position) {
      case 'top': return 'bottom center';
      case 'top-start': return 'bottom left';
      case 'top-end': return 'bottom right';
      case 'bottom': return 'top center';
      case 'bottom-start': return 'top left';
      case 'bottom-end': return 'top right';
      case 'left': return 'center right';
      case 'right': return 'center left';
      default: return 'center';
    }
  });

  ngOnInit(): void {
    if (this.visible) {
      this.show();
    }
    this.setupTriggerListeners();
  }

  ngAfterViewInit(): void {
    if (this.visible) {
      setTimeout(() => this.updatePosition(), 0);
    }
  }

  ngOnDestroy(): void {
    this.clearTimers();
    this.cleanup();
    this.restoreBodyScroll();
  }

  /**
   * Shows the popover with animation and positioning
   */
  show(): void {
    if (this._visible() || this.disabled) return;

    this.clearTimers();
    
    this.showTimer = window.setTimeout(() => {
      this.preventBodyScrollIfEnabled();
      this._visible.set(true);
      this._entering.set(true);
      this.visibleChange.emit(true);
      
      this.updatePosition();
      this.setupPositionObservers();

      // Animation timing
      setTimeout(() => {
        this._entering.set(false);
        this.shown.emit();
      }, this.animationDuration);
    }, this.delay);
  }

  /**
   * Hides the popover with animation
   */
  hide(): void {
    if (!this._visible()) return;

    this.clearTimers();
    this.cleanup();

    this._leaving.set(true);

    // Animation timing
    setTimeout(() => {
      this._visible.set(false);
      this._leaving.set(false);
      this.restoreBodyScroll();
      this.visibleChange.emit(false);
      this.hidden.emit();
    }, this.animationDuration);
  }

  /**
   * Toggles popover visibility
   */
  toggle(): void {
    if (this._visible()) {
      this.hide();
    } else {
      this.show();
    }
  }

  /**
   * Updates the popover position relative to the trigger element
   */
  updatePosition(): void {
    if (!this.triggerElement || !this.popoverElement) return;

    const triggerRect = this.triggerElement.getBoundingClientRect();
    const popoverEl = this.popoverElement.nativeElement;
    const popoverRect = popoverEl.getBoundingClientRect();
    const viewportWidth = window.innerWidth;
    const viewportHeight = window.innerHeight;
    const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
    const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

    let position = this.position;
    let top = 0;
    let left = 0;

    // Calculate initial position
    const positions = this.calculatePositions(triggerRect, popoverRect, scrollLeft, scrollTop);
    const initialPos = positions[position];
    top = initialPos.top;
    left = initialPos.left;

    // Auto-position if enabled and popover goes outside viewport
    if (this.autoPosition) {
      const bestPosition = this.findBestPosition(positions, viewportWidth, viewportHeight, popoverRect);
      position = bestPosition.position;
      top = bestPosition.top;
      left = bestPosition.left;
    }

    // Ensure popover stays within viewport bounds
    const finalPosition = this.constrainToViewport(
      { top, left, position },
      popoverRect,
      viewportWidth,
      viewportHeight
    );

    // Update state
    this._computedPosition.set(finalPosition.position);
    this._positionData.set(finalPosition);

    // Emit position change if it changed
    if (finalPosition.position !== this.position && this.positionUpdateCounter > 0) {
      this.positionChange.emit(finalPosition.position);
    }
    
    this.positionUpdateCounter++;
  }

  /**
   * Calculates all possible positions for the popover
   */
  private calculatePositions(
    triggerRect: DOMRect,
    popoverRect: DOMRect,
    scrollLeft: number,
    scrollTop: number
  ): Record<PopoverPosition, PopoverPositionData> {
    const triggerCenterX = triggerRect.left + triggerRect.width / 2;
    const triggerCenterY = triggerRect.top + triggerRect.height / 2;

    return {
      'top': {
        top: triggerRect.top - popoverRect.height - this.offset + scrollTop,
        left: triggerCenterX - popoverRect.width / 2 + scrollLeft,
        position: 'top'
      },
      'top-start': {
        top: triggerRect.top - popoverRect.height - this.offset + scrollTop,
        left: triggerRect.left + scrollLeft,
        position: 'top-start'
      },
      'top-end': {
        top: triggerRect.top - popoverRect.height - this.offset + scrollTop,
        left: triggerRect.right - popoverRect.width + scrollLeft,
        position: 'top-end'
      },
      'bottom': {
        top: triggerRect.bottom + this.offset + scrollTop,
        left: triggerCenterX - popoverRect.width / 2 + scrollLeft,
        position: 'bottom'
      },
      'bottom-start': {
        top: triggerRect.bottom + this.offset + scrollTop,
        left: triggerRect.left + scrollLeft,
        position: 'bottom-start'
      },
      'bottom-end': {
        top: triggerRect.bottom + this.offset + scrollTop,
        left: triggerRect.right - popoverRect.width + scrollLeft,
        position: 'bottom-end'
      },
      'left': {
        top: triggerCenterY - popoverRect.height / 2 + scrollTop,
        left: triggerRect.left - popoverRect.width - this.offset + scrollLeft,
        position: 'left'
      },
      'right': {
        top: triggerCenterY - popoverRect.height / 2 + scrollTop,
        left: triggerRect.right + this.offset + scrollLeft,
        position: 'right'
      }
    };
  }

  /**
   * Finds the best position that fits within the viewport
   */
  private findBestPosition(
    positions: Record<PopoverPosition, PopoverPositionData>,
    viewportWidth: number,
    viewportHeight: number,
    popoverRect: DOMRect
  ): PopoverPositionData {
    const preferenceOrder: PopoverPosition[] = [
      this.position,
      ...Object.keys(positions).filter(p => p !== this.position) as PopoverPosition[]
    ];

    for (const pos of preferenceOrder) {
      const posData = positions[pos];
      const fitsHorizontally = posData.left >= 0 && posData.left + popoverRect.width <= viewportWidth;
      const fitsVertically = posData.top >= 0 && posData.top + popoverRect.height <= viewportHeight;

      if (fitsHorizontally && fitsVertically) {
        return posData;
      }
    }

    // If no position fits perfectly, return the preferred position
    return positions[this.position];
  }

  /**
   * Constrains the popover position to stay within viewport
   */
  private constrainToViewport(
    position: PopoverPositionData,
    popoverRect: DOMRect,
    viewportWidth: number,
    viewportHeight: number
  ): PopoverPositionData {
    const margin = 8; // Keep some margin from viewport edges
    
    let { top, left } = position;

    // Constrain horizontally
    if (left < margin) {
      left = margin;
    } else if (left + popoverRect.width > viewportWidth - margin) {
      left = viewportWidth - popoverRect.width - margin;
    }

    // Constrain vertically
    if (top < margin) {
      top = margin;
    } else if (top + popoverRect.height > viewportHeight - margin) {
      top = viewportHeight - popoverRect.height - margin;
    }

    return { ...position, top, left };
  }

  /**
   * Sets up trigger event listeners based on trigger type
   */
  private setupTriggerListeners(): void {
    if (!this.triggerElement) return;

    switch (this.trigger) {
      case 'click':
        this.renderer.listen(this.triggerElement, 'click', (e) => {
          e.preventDefault();
          this.toggle();
        });
        break;

      case 'hover':
        this.renderer.listen(this.triggerElement, 'mouseenter', () => {
          this.clearTimers();
          this.showTimer = window.setTimeout(() => this.show(), this.hoverDelay);
        });
        this.renderer.listen(this.triggerElement, 'mouseleave', () => {
          this.clearTimers();
          this.hideTimer = window.setTimeout(() => this.hide(), this.hoverDelay);
        });
        // Keep popover open when hovering over it
        this.renderer.listen(this.elementRef.nativeElement, 'mouseenter', () => {
          this.clearTimers();
        });
        this.renderer.listen(this.elementRef.nativeElement, 'mouseleave', () => {
          this.hideTimer = window.setTimeout(() => this.hide(), this.hoverDelay);
        });
        break;

      case 'focus':
        this.renderer.listen(this.triggerElement, 'focus', () => this.show());
        this.renderer.listen(this.triggerElement, 'blur', () => this.hide());
        break;

      case 'manual':
        // No automatic triggers, controlled programmatically
        break;
    }
  }

  /**
   * Sets up observers for position updates
   */
  private setupPositionObservers(): void {
    // Resize observer to update position on window resize
    this.resizeObserver = new ResizeObserver(() => {
      if (this._visible()) {
        this.updatePosition();
      }
    });
    this.resizeObserver.observe(document.body);

    // Listen for window scroll
    window.addEventListener('scroll', this.updatePositionThrottled, { passive: true });
    window.addEventListener('resize', this.updatePositionThrottled, { passive: true });
  }

  private updatePositionThrottled = this.throttle(() => {
    if (this._visible()) {
      this.updatePosition();
    }
  }, 16); // ~60fps

  /**
   * Handles backdrop click
   */
  handleBackdropClick(event: MouseEvent): void {
    if (this.backdropClosable) {
      this.hide();
      this.backdropClicked.emit(event);
    }
  }

  /**
   * Handles keyboard events
   */
  handleKeydown(event: KeyboardEvent): void {
    if (event.key === 'Escape' && this.escapeClosable) {
      event.preventDefault();
      this.hide();
      this.escapePressed.emit(event);
    }
  }

  /**
   * Global keyboard listener for ESC key
   */
  @HostListener('document:keydown', ['$event'])
  onDocumentKeydown(event: KeyboardEvent): void {
    if (this._visible() && event.key === 'Escape' && this.escapeClosable) {
      event.preventDefault();
      this.hide();
      this.escapePressed.emit(event);
    }
  }

  /**
   * Global click listener to close popover on outside clicks
   */
  @HostListener('document:click', ['$event'])
  onDocumentClick(event: MouseEvent): void {
    if (!this._visible() || !this.closable) return;

    const target = event.target as HTMLElement;
    const isInsidePopover = this.elementRef.nativeElement.contains(target);
    const isInsideTrigger = this.triggerElement?.contains(target);

    if (!isInsidePopover && !isInsideTrigger && this.trigger === 'click') {
      this.hide();
    }
  }

  /**
   * Prevents body scroll when popover is modal-like
   */
  private preventBodyScrollIfEnabled(): void {
    if (!this.preventBodyScroll) return;

    this.originalBodyOverflow = document.body.style.overflow;
    document.body.style.overflow = 'hidden';
  }

  /**
   * Restores body scroll
   */
  private restoreBodyScroll(): void {
    if (!this.preventBodyScroll) return;

    document.body.style.overflow = this.originalBodyOverflow;
  }

  /**
   * Clears any active timers
   */
  private clearTimers(): void {
    if (this.showTimer) {
      clearTimeout(this.showTimer);
      this.showTimer = undefined;
    }
    if (this.hideTimer) {
      clearTimeout(this.hideTimer);
      this.hideTimer = undefined;
    }
  }

  /**
   * Cleanup observers and listeners
   */
  private cleanup(): void {
    this.resizeObserver?.disconnect();
    window.removeEventListener('scroll', this.updatePositionThrottled);
    window.removeEventListener('resize', this.updatePositionThrottled);
  }

  /**
   * Throttle function for performance
   */
  private throttle<T extends (...args: any[]) => void>(func: T, limit: number): T {
    let inThrottle: boolean;
    return ((...args: any[]) => {
      if (!inThrottle) {
        func.apply(this, args);
        inThrottle = true;
        setTimeout(() => inThrottle = false, limit);
      }
    }) as T;
  }

  /**
   * Public API method to apply configuration
   */
  configure(config: PopoverConfig): void {
    Object.assign(this, config);
  }

  /**
   * Public API method to set trigger element
   */
  setTriggerElement(element: HTMLElement): void {
    this.triggerElement = element;
    this.setupTriggerListeners();
  }
}