export class SubmenuControl {
  constructor() { }
  //---------------------------------------------
  // Public methods
  //---------------------------------------------
  /**
   * Método responsável por fechar submenus abertos e abrir o submenu desejado.
   * @param menu - Item de menu selecionado para abrir o submenu.
   */
  show(menu) {
    const submenu = menu === null || menu === void 0 ? void 0 : menu.querySelector(".ez-dropdown__submenu");
    if (menu == undefined || submenu == undefined) {
      return;
    }
    submenu.classList.remove("ez-dropdown__submenu--hide");
    this.setPosition(menu);
    this.setScrollContainer(menu);
    this.setMaxHeight(submenu);
  }
  /**
   * Método responsável por fechar um determinado submenu aberto.
   * @param menu - Item de menu selecionado com o submenu aberto.
   */
  hide(menu) {
    if (menu == undefined) {
      return;
    }
    const submenuSelector = ".ez-dropdown__submenu:not(.ez-dropdown__submenu--hide)";
    const submenuElement = menu.querySelector(submenuSelector);
    this.reset(submenuElement);
    this.hideChild(submenuElement);
  }
  /**
   * Método responsável por fechar todos os submenus filhos de um determinado submenu.
   * @param submenu - Elemento HTML do submenu pai.
   */
  hideChild(submenu) {
    if (submenu == undefined) {
      return;
    }
    submenu.querySelectorAll(".ez-dropdown__submenu")
      .forEach((child) => {
      this.reset(child);
    });
  }
  /**
   * Método responsável por posicionar o submenu na tela.
   * @param menu - Item da lista selecionado para abrir o submenu.
   */
  setPosition(menu) {
    const submenu = menu === null || menu === void 0 ? void 0 : menu.querySelector(".ez-dropdown__submenu");
    const dropdown = menu === null || menu === void 0 ? void 0 : menu.closest(".ez-dropdown");
    if (submenu == undefined || dropdown == undefined) {
      return;
    }
    submenu.style.top = this.getPositionTop(menu);
    submenu.style.left = this.getPositionRight(dropdown);
    const bounding = submenu === null || submenu === void 0 ? void 0 : submenu.getBoundingClientRect();
    const docWidth = document.body.clientWidth;
    if ((bounding === null || bounding === void 0 ? void 0 : bounding.right) > docWidth) {
      this.updatePosition(dropdown, submenu);
    }
  }
  /**
   * Método responsável por controlar a altura máxima do dropdown.
   * @param element - Elemento HTML pai do dropdown que será renderizado.
   */
  setMaxHeight(element) {
    var _a;
    const parentElement = (element === null || element === void 0 ? void 0 : element.shadowRoot) || element;
    const dropdown = parentElement === null || parentElement === void 0 ? void 0 : parentElement.querySelector(".ez-dropdown");
    const docHeight = document.body.clientHeight;
    let submenu;
    let bounding;
    if ((parentElement === null || parentElement === void 0 ? void 0 : parentElement["tagName"]) != undefined) {
      submenu = parentElement;
      bounding = submenu === null || submenu === void 0 ? void 0 : submenu.getBoundingClientRect();
    }
    else {
      bounding = dropdown === null || dropdown === void 0 ? void 0 : dropdown.getBoundingClientRect();
    }
    if (dropdown == undefined || bounding == undefined) {
      return;
    }
    /**
     * Menu aberto irá ultrapassar a altura da página?
     */
    if ((bounding.y + bounding.height) > docHeight) {
      /**
       * Menu aberto é um submenu?
       * Sim: realiza o calculo com base na altura do menu pai
       * Não: recalcula a altura do menu para atualizar a barra de rolagem
       */
      if (submenu != undefined) {
        const dropdownParent = submenu.closest(".ez-dropdown");
        const boundingParent = dropdownParent === null || dropdownParent === void 0 ? void 0 : dropdownParent.getBoundingClientRect();
        const marginTop = parseFloat(((_a = getComputedStyle(submenu)) === null || _a === void 0 ? void 0 : _a.marginTop) || "0");
        if (dropdownParent == undefined || boundingParent == undefined) {
          return;
        }
        /**
         * Submenu aberto ao ser reposicionado ficará acima da altura do menu pai?
         * Sim: posiciona o submenu na altura do menu pai e recalcula a sua altura para atualizar a barra de rolagem
         * Não: posiciona o submenu entre a altura do menu pai e o rodapé da página
         */
        if ((boundingParent.y + bounding.height) > docHeight) {
          submenu.style.top = (boundingParent.y - marginTop) + "px";
          dropdown.style.maxHeight = (docHeight - boundingParent.y) + "px";
        }
        else {
          submenu.style.top = (docHeight - bounding.height - marginTop) + "px";
        }
      }
      else {
        let maxHeight = docHeight - bounding.y;
        if (maxHeight < 0) {
          maxHeight = bounding.height - docHeight;
        }
        dropdown.style.maxHeight = maxHeight + "px";
      }
    }
    this.setSpacerContainer(dropdown);
  }
  /**
   * Método responsável por atribuir a classe de espaçamento entre o conteúdo e o scroll.
   * @param dropdown - Elemento HTML pai do container que receberá a classe.
   */
  setSpacerContainer(dropdown) {
    const container = dropdown === null || dropdown === void 0 ? void 0 : dropdown.querySelector(".ez-dropdown__container");
    if (container == undefined) {
      return;
    }
    if (container.scrollHeight > container.clientHeight) {
      container.classList.add("ez-dropdown__container--spacer");
    }
    else {
      container.classList.remove("ez-dropdown__container--spacer");
    }
  }
  /**
   * Método responsável por resetar um determinado submenu aberto.
   * @param submenu - Elemento HTML do submenu que será resetado.
   */
  reset(submenu) {
    if (submenu == undefined) {
      return;
    }
    submenu.classList.add("ez-dropdown__submenu--hide");
    submenu.style.left = "";
    submenu.style.top = "";
    const dropdown = submenu.querySelector(".ez-dropdown");
    this.resetDropdown(dropdown);
  }
  /**
   * Método responsável por resetar um determinado dropdown renderizado.
   * @param dropdown - Elemento HTML do dropdown que será resetado.
   */
  resetDropdown(dropdown) {
    if (dropdown == undefined) {
      return;
    }
    dropdown.style.maxHeight = "";
    this.setSpacerContainer(dropdown);
  }
  //---------------------------------------------
  // Private methods
  //---------------------------------------------
  /**
   * Método responsável por controlar o scroll do container do submenu aberto.
   * @param menu - Item da lista selecionado para abrir o submenu.
   */
  setScrollContainer(menu) {
    const container = menu === null || menu === void 0 ? void 0 : menu.closest(".ez-dropdown__container");
    if (container == undefined) {
      return;
    }
    container.onscroll = this.onScrollContainer.bind(this);
  }
  /**
   * Método responsável por fechar os submenus filhos ao movimentar o scroll.
   * @param event - Evento de scroll disparado pelo Elemento HTML pai.
   */
  onScrollContainer(event) {
    const target = event === null || event === void 0 ? void 0 : event.target;
    this.hideChild(target);
  }
  /**
   * Método responsável por retornar a posição superior (top) de um elemento HTML na tela.
   * @param hostElement - Elemento HTML que será capturado o valor de sua posição superior (top) na tela.
   * @returns Valor da posição superior (top) de um determinado elemento HTML.
   */
  getPositionTop(hostElement) {
    const boundingHost = hostElement === null || hostElement === void 0 ? void 0 : hostElement.getBoundingClientRect();
    if (boundingHost == undefined) {
      return;
    }
    return boundingHost.y + "px";
  }
  /**
   * Método responsável por retornar a posição direita (right) de um elemento HTML na tela.
   * @param hostElement - Elemento HTML que será capturado o valor de sua posição direita (right) na tela.
   * @returns Valor da posição direita (right) de um determinado elemento HTML.
   */
  getPositionRight(hostElement) {
    const boundingHost = hostElement === null || hostElement === void 0 ? void 0 : hostElement.getBoundingClientRect();
    if (boundingHost == undefined) {
      return;
    }
    return (boundingHost.x + boundingHost.width) + "px";
  }
  /**
   * Método responsável por retornar a posição esquerda (left) de um elemento HTML desconsiderando a largura de outro elemento HTML.
   * @param hostElement - Elemento HTML que será capturado o valor de sua posição esquerda (left) na tela.
   * @param targetElement - Elemento HTML que será capturado o valor de sua largura (width).
   * @returns Valor da posição esquerda (left) de um determinado elemento HTML desconsiderando a largura de outro elemento HTML.
   */
  getPositionLeft(hostElement, targetElement) {
    const boundingHost = hostElement === null || hostElement === void 0 ? void 0 : hostElement.getBoundingClientRect();
    const boundingTarget = targetElement === null || targetElement === void 0 ? void 0 : targetElement.getBoundingClientRect();
    if (boundingHost == undefined || boundingTarget == undefined) {
      return;
    }
    return (boundingHost.x - boundingTarget.width) + "px";
  }
  /**
   * Método responsável por atualizar a posição de um elemento HTML que renderizou fora da tela.
   * @param hostElement - Elemento HTML que será a referência de posição dentro da tela.
   * @param targetElement - Elemento HTML que será reposicionado na tela.
   */
  updatePosition(hostElement, targetElement) {
    const boundingHost = hostElement === null || hostElement === void 0 ? void 0 : hostElement.getBoundingClientRect();
    const boundingTarget = targetElement === null || targetElement === void 0 ? void 0 : targetElement.getBoundingClientRect();
    if (boundingHost == undefined || boundingTarget == undefined) {
      return;
    }
    if (boundingHost.x < boundingTarget.right) {
      targetElement.style.left = this.getPositionLeft(hostElement, targetElement);
      this.updatePosition(hostElement, targetElement);
    }
  }
}
