import { Overlay, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import { Directive, ElementRef, HostListener, Input, OnDestroy, ViewContainerRef } from '@angular/core';
import { merge, Observable, Subscription } from 'rxjs';
import { DropdownPanel } from './dropdown-panel';

@Directive({
  selector: '[msfaDropdownTriggerFor]',
})
export class DropdownTriggerForDirective implements OnDestroy {
  @Input('msfaDropdownTriggerFor') public dropdownPanel: DropdownPanel;

  private isDropdownOpen = false;
  public overlayRef: OverlayRef;
  private dropdownClosingActionsSub = Subscription.EMPTY;

  private onClosedCallback: () => void = () => {
    return;
  };
  private onOpenCallback: () => void = () => {
    return;
  };

  @HostListener('click') onClick(): void {
    return this.toggleDropdown();
  }
  @HostListener('document:keyup.escape', ['$event']) onKeydownHandler(): void {
    this.closeDropdown();
  }

  constructor(
    private overlay: Overlay,
    private elementRef: ElementRef<HTMLElement>,
    private viewContainerRef: ViewContainerRef
  ) {}

  toggleDropdown(): void {
    this.isDropdownOpen ? this.closeDropdown() : this.openDropdown();
  }

  openDropdown(): void {
    this.isDropdownOpen = true;
    this.overlayRef = this.overlay.create({
      hasBackdrop: true,
      backdropClass: 'cdk-overlay-transparent-backdrop',
      scrollStrategy: this.overlay.scrollStrategies.close(),
      positionStrategy: this.overlay
        .position()
        .flexibleConnectedTo(this.elementRef)
        .withPositions([
          {
            originX: 'start',
            originY: 'bottom',
            overlayX: 'start',
            overlayY: 'top',
            offsetY: 3,
          },
        ]),
    });

    const templatePortal = new TemplatePortal(this.dropdownPanel.templateRef, this.viewContainerRef);

    this.overlayRef.attach(templatePortal);
    this.onOpenCallback();

    this.dropdownClosingActionsSub = this.dropdownClosingActions().subscribe(() => this.closeDropdown());
  }

  private dropdownClosingActions(): Observable<MouseEvent | void> {
    const backdropClick$ = this.overlayRef.backdropClick();
    const detachment$ = this.overlayRef.detachments();
    const dropdownClosed = this.dropdownPanel.closed;

    return merge(backdropClick$, detachment$, dropdownClosed);
  }

  closeDropdown(): void {
    if (!this.overlayRef || !this.isDropdownOpen) {
      return;
    }

    this.onClosedCallback();
    this.dropdownClosingActionsSub.unsubscribe();
    this.isDropdownOpen = false;
    this.overlayRef.detach();
  }

  ngOnDestroy(): void {
    if (this.overlayRef) {
      this.overlayRef.dispose();
    }
  }

  onClosed(fn: () => void): void {
    this.onClosedCallback = fn;
  }

  onOpen(fn: () => void): void {
    this.onOpenCallback = fn;
  }
}
