import { Subscription } from 'aurelia-event-aggregator';
import { bindable, bindingMode, containerless, customElement, inlineView } from 'aurelia-framework';
import ResizeObserver from 'resize-observer-polyfill';
import { UIInternal } from '../../utils/ui-internal';
import { UITether } from '../../utils/ui-tether';

@containerless()
@customElement('ui-drop')
@inlineView(
  `<template>
    <div ref="vmContainerElement" slot="ui-drop" class="ui-drop" click.delegate="closeDrop()" data-open.bind="isOpen">
      <div ref="vmElement" class="ui-drop__body \${class}" click.delegate="close($event)" mouseup.trigger="closeChildUIDropElements($event)">
        <slot></slot>
      </div>
    </div>
  </template>`
)
export class UIDrop {
  /**
   * Add a css class(es) to the ui-drop inner div
   * @type {string}
   */
  @bindable()
  public class: string = '';

  /**
   * Bindable property indicating if the ui-drop is opened or not
   * @type booleal
   * @default false
   */
  @bindable({ defaultBindingMode: bindingMode.toView })
  public isOpen: boolean = false;

  /**
   * Define the min-width of the ui-drop element. When true the min-width = width of the anchor element
   * @type {boolean}
   * @default true
   */
  @bindable()
  public stretch: boolean = true;

  /**
   * Indicating if the ui-drop will be closed when the use click on the viewport outside of the current context
   * @type {boolean}
   * @default true
   */
  public closeOnClick: boolean = true;

  /**
   * HTML element used to attach the ui-drop
   * @type {Element}
   * @default null
   */
  @bindable()
  public anchorEl: Element = null;

  @bindable()
  public attachToViewport: boolean = false;

  /**
   * Close the ui-drop when the user use the mouse wheel outside the current context
   * @type {boolean}
   * @default false ui-drop will be closed by default when scrolling
   */
  @bindable
  public closeOnScroll: boolean = true;

  /**
   * Auto open ui-drop content when clicking on the anchor element
   *
   * @public
   * @return {*}
   * @memberof UIDrop
   */
  @bindable
  public autoOpen: boolean = true;

  /**
   * Position of the ui-drop relative to the anchor element
   * @type {UITether.Position}
   * @default tl
   */
  public position: UITether.Position;

  /**
   * Indicate the position in the anchor element to treat as the origin point for positionning the ui-drop
   * @type {UITether.Position}
   * @default bl
   */
  public anchorPosition: UITether.Position;

  private vmContainerElement: Element;
  protected vmElement: HTMLDivElement;
  private tetherObj: UITether.Tether;

  private obClick: Subscription;
  private obResize: ResizeObserver;
  private obViewportResize: Subscription;
  private obWheel: Subscription;

  constructor(protected element: Element) {
    this.position = (element.getAttribute('position') as UITether.Position) || 'tl';
    this.anchorPosition = (element.getAttribute('anchor') as UITether.Position) || 'bl';
    this.closeOnClick = !isFalse(element.getAttribute('close-on-click'));
  }
  public tether(anchorEl: Element) {
    this.tetherObj = UITether.tether((this.anchorEl = anchorEl), this.vmElement, {
      anchorPosition: this.anchorPosition,
      attachToViewport: this.attachToViewport,
      position: this.position,
      resize: this.stretch,
    });
  }

  public updatePosition() {
    this.tetherObj.updatePosition();
  }

  public toggleDrop(open?: boolean): void {
    this.disposeListeners();
    this.vmElement.dataset.show = 'false';
    this.isOpen = open === undefined ? !this.isOpen : open;
    if (this.isOpen) {
      this.obClick = UIInternal.subscribe(UIInternal.EVT_VIEWPORT_CLICK, (t) => this.canClose(t));
      this.obWheel = UIInternal.subscribe(UIInternal.EVT_VIEWPORT_WHEEL, (t) => this.canClose(t));
      // observe viewport resize
      this.obViewportResize = UIInternal.subscribe(UIInternal.EVT_VIEWPORT_RESIZE, () => this.updatePosition());
      // observe body resize
      this.obResize = new ResizeObserver(() => this.updatePosition());
      this.obResize.observe(this.vmElement);
      this.obResize.observe(this.anchorEl);
      this.element.dispatchEvent(UIInternal.createEvent('open'));
      UIInternal.queueMicroTask(() => {
        this.tetherObj.updatePosition();
        this.vmElement.dataset.show = 'true';
      });
    }
  }

  public closeDrop() {
    UIInternal.queueTask(() => {
      this.isOpen = false;
      this.disposeListeners();
      this.element.dispatchEvent(UIInternal.createEvent('close'));
    });
  }

  protected disposeListeners(): void {
    if (this.obClick) {
      this.obClick.dispose();
    }
    if (this.obResize) {
      this.obResize.disconnect();
    }
    if (this.obViewportResize) {
      this.obViewportResize.dispose();
    }
    if (this.obWheel) {
      this.obWheel.dispose();
    }
  }

  private toggleDropListener = () => {
    this.toggleDrop();
  };

  protected attached(): void {
    UIInternal.queueTask(() => {
      if (this.anchorEl != null && this.tetherObj == null && this.vmElement != null) {
        this.tether(this.anchorEl);
        if (this.autoOpen === true) {
          this.anchorEl.addEventListener('click', this.toggleDropListener);
        }
      }
    });
  }

  anchorElChanged(n, o) {
    if (o == null && n != null) {
      this.attached();
    }
  }
  protected detached(): void {
    this.anchorEl?.removeEventListener('click', this.toggleDropListener);
    this.disposeListeners();
    if (this.tetherObj) {
      this.tetherObj.dispose();
      this.tetherObj = null;
    }
  }

  protected close($event: UIEvent) {
    if (this.closeOnClick) {
      this.closeDrop();
    } else {
      $event.stopEvent(true);
    }
  }

  protected closeChildUIDropElements($event: MouseEvent) {
    $event.stopEvent();
    let closestOpenenedUiDrop = $event.target.closest('[data-open="true"]');
    let childUIDrop: Array<HTMLElement> = Array.from(this.vmElement?.querySelectorAll('[data-open="true"]')).filter((b) => (<string>b.tagName)?.toLowerCase() == 'a' && b.click != null);

    for (let child of childUIDrop) {
      if (closestOpenenedUiDrop == this.vmContainerElement || !hasParent(child, closestOpenenedUiDrop)) {
        child.click();
      }
    }
  }

  private canClose(t: Element): void {
    if (t == null || (!hasParent(t, this.vmElement) && !hasParent(t, this.anchorEl))) {
      this.closeDrop();
    }
  }
}
