import { HorizontalConnectionPos, Overlay, OverlayPositionBuilder, OverlayRef, VerticalConnectionPos } from '@angular/cdk/overlay';
import { TemplatePortal } from '@angular/cdk/portal';
import {
  ChangeDetectorRef,
  Directive,
  ElementRef,
  EmbeddedViewRef,
  HostBinding,
  HostListener,
  Input,
  OnChanges,
  OnDestroy,
  OnInit,
  SimpleChanges,
  ViewContainerRef,
} from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { UiOverlayTrigger } from './overlay-trigger';

@Directive({ selector: '[uiOverlayTrigger]', exportAs: 'uiOverlayTrigger' })
export class UiOverlayTriggerDirective implements OnInit, OnDestroy, OnChanges {
  @Input('uiOverlayTrigger')
  component: UiOverlayTrigger;

  @Input()
  @HostBinding('disabled')
  disabled: boolean;

  private overlayRef: OverlayRef;
  private viewRef: EmbeddedViewRef<any>;
  private _onDestroy$ = new Subject<void>();

  get opened() {
    return !!this.viewRef;
  }

  constructor(
    private overlayPositionBuilder: OverlayPositionBuilder,
    private viewContainerRef: ViewContainerRef,
    private elementRef: ElementRef,
    private overlay: Overlay,
    private cdr: ChangeDetectorRef
  ) {}

  ngOnChanges({ position }: SimpleChanges): void {
    if (position) {
      this.updatePosition();
    }
  }

  ngOnInit() {
    this.createOverlay();
  }

  ngOnDestroy() {
    this.overlayRef.dispose();
    this._onDestroy$.next();
    this._onDestroy$.complete();
  }

  @HostListener('click')
  toggle() {
    if (this.viewRef) {
      this.close();
    } else {
      this.open();
    }
  }

  open() {
    const dropdownPortal = new TemplatePortal(this.component.templateRef, this.viewContainerRef);

    if (!!this.viewRef) {
      this.close();
    }

    this.updatePositionStrategy();

    this.viewRef = this.overlayRef.attach(dropdownPortal);
    this.component.close = () => this.close();

    this.cdr.markForCheck();
  }

  close() {
    this.viewRef?.destroy();
    this.overlayRef.detach();
    delete this.viewRef;
    this.cdr.markForCheck();
  }

  createOverlay() {
    this.overlayRef = this.overlay.create({
      positionStrategy: this.getPositionStrategy(),
      hasBackdrop: true,
      backdropClass: 'cdk-overlay-transparent-backdrop',
    });
    this.overlayRef
      .backdropClick()
      .pipe(takeUntil(this._onDestroy$))
      .subscribe(() => this.close());
  }

  updatePositionStrategy() {
    this.overlayRef.updatePositionStrategy(this.getPositionStrategy());
  }

  getPositionStrategy() {
    let [originX, originFallbackX]: HorizontalConnectionPos[] = [this.component.xPosition === 'before' ? 'start' : 'end', 'start'];

    let [originY, overlayFallbackY]: VerticalConnectionPos[] = [this.component.yPosition === 'above' ? 'top' : 'bottom', 'top'];

    let [overlayY, originFallbackY]: VerticalConnectionPos[] = [originY === 'bottom' ? 'top' : 'bottom', overlayFallbackY];
    let [overlayX, overlayFallbackX] = [originX, originFallbackX];
    let offsetY = this.component.yOffset ?? 0;
    let offsetX = this.component.xOffset ?? 0;

    return this.overlayPositionBuilder.flexibleConnectedTo(this.elementRef).withPositions([
      { originX, originY, overlayX, overlayY, offsetY, offsetX },
      { originX: originFallbackX, originY, overlayX: overlayFallbackX, overlayY, offsetY, offsetX },
      {
        originX,
        originY: originFallbackY,
        overlayX,
        overlayY: overlayFallbackY,
        offsetY: -offsetY,
        offsetX: -offsetX,
      },
      {
        originX: originFallbackX,
        originY: originFallbackY,
        overlayX: overlayFallbackX,
        overlayY: overlayFallbackY,
        offsetY: -offsetY,
        offsetX: -offsetX,
      },
    ]);
  }

  updatePosition() {
    this.overlayRef?.updatePositionStrategy(this.getPositionStrategy());
  }
}
