import { Magento2EcmsBlockApiService } from './magento2.ecms.block.api.service';
import { IPromise, ITimeoutService } from 'angular';
import { IScopedCompileService } from '../ScopedCompile';
import { DomUtils } from '../DomUtils';
import { IMagento2Block } from './magento2.block';

export interface IMenu {
  target: HTMLElement,
  category: string,
  block: IMagento2Block,
  menu: JQLite,
  openTimeout: number,
  closeTimeout: number,
  onOpen: (IMenu) => void,
  onClose: (IMenu) => void,
  active: boolean,

  fetch(): Promise<IMenu>,

  open(): void,

  close(): void
}

export class Menu implements IMenu {
  public category: string;
  public block: IMagento2Block;
  public menu: JQLite;
  public openTimeout: number = 100;
  public closeTimeout: number = 200;
  public active: boolean = false;

  private openPromise: IPromise<void> = null;
  private closePromise: IPromise<void> = null;

  constructor(
    public target,
    private readonly $timeout: ITimeoutService,
    private readonly $compile: IScopedCompileService,
    private readonly blockApi: Magento2EcmsBlockApiService,
    public onOpen = null,
    public onClose = null
  ) {
    if (!('category' in target.dataset)) {
      throw new TypeError('Element has no data-category attribute.');
    }

    this.category = target.dataset.category;
  }

  protected _renderMenu(): JQLite {
    if (!this.menu && this.block) {
      let $menu = angular.element(this.block.content);
      $menu.addClass('megamenu-menu hidden');
      document.body.appendChild($menu[0]);
      this._addEventListeners($menu[0]);
      this.menu = $menu;
    }

    let bounding = DomUtils.getOffsetBounding(this.target);
    this.menu.css('top', `${bounding.top + bounding.height}px`);

    return this.menu;
  }

  protected _addEventListeners(target: HTMLElement) {
    target.addEventListener('mouseenter', this.open.bind(this));
    target.addEventListener('mouseleave', this.close.bind(this));
  }

  protected _doOpen() {
    this.active = true;
    this._renderMenu().removeClass('hidden');
    this.target.classList.add('active');
    if (this.onOpen instanceof Function) {
      this.onOpen(this);
    }
  }

  protected _doClose() {
    this.active = false;
    this._renderMenu().addClass('hidden');
    this.target.classList.remove('active');
    if (this.onClose instanceof Function) {
      this.onClose(this);
    }
  }

  public fetch() {
    return new Promise((resolve, reject) => {
      this.blockApi
        .get(this.category)
        .then((block) => {
          if (block && 'content' in block) {
            this.block = block;
            this.block.content = this.$compile(this.block.content);
            this._addEventListeners(this.target);

            resolve(this);
          }

          reject('CMS block does not have any content.');
        });
    }) as Promise<IMenu>;
  }

  public open() {
    this.$timeout.cancel(this.closePromise);
    if (this.openTimeout > 0) {
      this.openPromise = this.$timeout(this._doOpen.bind(this), this.openTimeout);
    } else {
      this._doOpen();
    }
  }

  public close() {
    this.$timeout.cancel(this.openPromise);
    if (this.closeTimeout > 0) {
      this.closePromise = this.$timeout(this._doClose.bind(this), this.closeTimeout);
    } else {
      this._doClose();
    }
  }
}
