import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import {
  Directive,
  ElementRef,
  EventEmitter,
  HostListener,
  Input,
  OnDestroy,
  Output,
  ViewContainerRef,
} from '@angular/core';
import { merge, Observable, Subscription } from 'rxjs';
import { OverlayPanel } from './overlay.model';

@Directive({
  selector: '[uiOverlayTriggerFor]',
})
export class OverlayTriggerForDirective implements OnDestroy {
  @Input() public uiOverlayTriggerFor: OverlayPanel;

  @Input() public uiOverlayPositions: ConnectedPosition[] = [
    {
      originX: 'start',
      originY: 'bottom',
      overlayX: 'start',
      overlayY: 'top',
      offsetY: 3,
    },
  ];

  /*
   * uiOverlayConfig will override uiOverlayPreferredPositions
   * */
  @Input() public uiOverlayConfig: OverlayConfig;

  @Output() public uiOverlayRealPositions = new EventEmitter<ConnectedPosition[]>();
  private isOverlayOpen = false;
  public overlayRef: OverlayRef;
  private overlayClosingActionsSub = Subscription.EMPTY;

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

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

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

  toggleOverlay(): void {
    this.isOverlayOpen ? this.closeOverlay() : this.openOverlay();
  }

  openOverlay(): void {
    this.isOverlayOpen = true;

    const positionStrategy = this.overlay
      .position()
      .flexibleConnectedTo(this.triggerRef)
      .withPositions(this.uiOverlayPositions);

    this.overlayRef = this.overlay.create({
      hasBackdrop: true,
      backdropClass: 'cdk-overlay-transparent-backdrop',
      scrollStrategy: this.overlay.scrollStrategies.close(),
      positionStrategy,
      ...(this.uiOverlayConfig ?? {}),
    });

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

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

    this.overlayClosingActionsSub = this.dropdownClosingActions().subscribe(() => this.closeOverlay());
  }

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

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

  closeOverlay(): void {
    if (!this.overlayRef || !this.isOverlayOpen) {
      return;
    }

    this.onClosedCallback();
    this.overlayClosingActionsSub.unsubscribe();
    this.isOverlayOpen = 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;
  }
}
