import {
  AfterViewInit,
  Directive,
  ElementRef,
  HostListener,
  Input,
  NgZone,
  OnDestroy,
} from '@angular/core'

const DRAG_REACTION_DELAY = 100

const NO_OP = () => {
}

@Directive({
  selector: '[appDroptarget]',
})
export class DroptargetDirective implements AfterViewInit, OnDestroy {
  @Input() onDragReact: (DragEvent) => void = NO_OP
  @Input() clearDragReact: (DragEvent) => void = NO_OP

  private dragReactionDelayHandle?: any

  private onDragEnter = (event: DragEvent) => {
    event.preventDefault()
  }

  private onDragOver = (event: DragEvent) => {
    event.preventDefault()

    if (this.dragReactionDelayHandle !== undefined) return

    this.dragReactionDelayHandle = setTimeout(() => {
      this.onDragReact(event)
      this.dragReactionDelayHandle = undefined
    }, DRAG_REACTION_DELAY)
  }

  private onDragLeave = (event: DragEvent) => {
    if (this.dragReactionDelayHandle !== undefined) {
      clearTimeout(this.dragReactionDelayHandle)
      this.dragReactionDelayHandle = undefined
    }
    this.clearDragReact(event)
  }

  @HostListener('drop', ['$event']) onDrop(event: DragEvent) {
    this.onDragLeave(event)
  }

  constructor(private readonly el: ElementRef, private readonly zone: NgZone) {
  }

  ngAfterViewInit() {
    this.zone.runOutsideAngular(() => {
      this.el.nativeElement?.addEventListener('dragenter', this.onDragEnter)
      this.el.nativeElement?.addEventListener('dragover', this.onDragOver)
      this.el.nativeElement?.addEventListener('dragleave', this.onDragLeave)
    })
  }

  ngOnDestroy() {
    this.el.nativeElement?.removeEventListener('dragenter', this.onDragEnter)
    this.el.nativeElement?.removeEventListener('dragover', this.onDragOver)
    this.el.nativeElement?.removeEventListener('dragleave', this.onDragLeave)
  }
}