/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {Css} from "./tobago-css";

export enum DropdownMenuAlignment {
  start,
  centerFullWidth,
  end
}

export class DropdownMenu {

  private dropdownMenuElementId: string;
  private referenceElement: HTMLElement;
  private parent: HTMLElement; //initial parent of the dropdown menu
  private localMenu: boolean;
  private alignment: DropdownMenuAlignment;
  private resizeEventListener: EventListenerOrEventListenerObject = this.resizeEventListenerEvent.bind(this);
  private scrollEventListener: EventListenerOrEventListenerObject = this.scrollEventListenerEvent.bind(this);

  /**
   * @param dropdownMenuElement dropdown menu element
   * @param referenceElement the element which the dropdown menu is aligned for
   * @param localMenu if true, do not use the menu store
   * @param alignment on the 'start' or 'end' of the reference element or in the center and the min-width is the same as
   * the reference element width
   */
  constructor(dropdownMenuElement: HTMLElement, referenceElement: HTMLElement,
              localMenu: boolean, alignment: DropdownMenuAlignment) {
    this.dropdownMenuElementId = dropdownMenuElement.getAttribute("name");
    this.referenceElement = referenceElement;
    this.parent = dropdownMenuElement.parentElement;
    this.localMenu = localMenu;
    this.alignment = alignment;
  }

  private resizeEventListenerEvent(event: Event): void {
    this.updatePosition();
  }

  private scrollEventListenerEvent(event: Event): void {
    const target = event.target as HTMLElement;
    if (!this.insideDropdownMenuElement(target)) {
      this.hide();
    }
  }

  private insideDropdownMenuElement(element: HTMLElement): boolean {
    if (element && element.classList) {
      if (element.classList.contains(Css.TOBAGO_DROPDOWN_MENU)
          && element.getAttribute("name") === this.dropdownMenuElementId) {
        return true;
      } else {
        return this.insideDropdownMenuElement(element.parentElement);
      }
    } else {
      return false;
    }
  }

  /**
   * Call this in the disconnectedCallback() method.
   */
  disconnect(): void {
    this.hide();
    if (this.dropdownMenuElement?.parentElement.classList.contains(Css.TOBAGO_PAGE_MENU_STORE)) {
      this.dropdownMenuElement?.remove();
    }

    delete this.dropdownMenuElementId;
    delete this.referenceElement;
    delete this.parent;
    delete this.localMenu;
    delete this.alignment;
  }

  show(): void {
    if (this.dropdownMenuElement && !this.dropdownMenuElement.classList.contains(Css.SHOW)) {
      window.addEventListener("resize", this.resizeEventListener);
      window.addEventListener("scroll", this.scrollEventListener, true);

      if (!this.localMenu) {
        this.menuStore.appendChild(this.dropdownMenuElement);
      }
      this.dropdownMenuElement.classList.add(Css.SHOW);
      this.updatePosition();
    }
  }

  hide(): void {
    if (this.dropdownMenuElement && this.dropdownMenuElement.classList.contains(Css.SHOW)) {
      window.removeEventListener("resize", this.resizeEventListener);
      window.removeEventListener("scroll", this.scrollEventListener, true);

      this.dropdownMenuElement.classList.remove(Css.SHOW);
      if (!this.localMenu) {
        this.parent?.appendChild(this.dropdownMenuElement);
      }
    }
  }

  private updatePosition(): void {
    const refElementRect = this.referenceElement.getBoundingClientRect();

    //calc horizontal positioning and max-width
    const rightBorder = this.body.offsetWidth;
    switch (this.alignment) {
      case DropdownMenuAlignment.start:
      case DropdownMenuAlignment.centerFullWidth:
        this.dropdownMenuElement.style.left = refElementRect.left + "px";
        this.dropdownMenuElement.style.right = null;
        this.dropdownMenuElement.style.marginLeft = "0px";
        this.dropdownMenuElement.style.marginRight = null;
        this.dropdownMenuElement.style.maxWidth = rightBorder - refElementRect.left
            - parseFloat(getComputedStyle(this.dropdownMenuElement).marginRight) + "px";
        break;
      case DropdownMenuAlignment.end:
        this.dropdownMenuElement.style.left = null;
        this.dropdownMenuElement.style.right = rightBorder - refElementRect.right + "px";
        this.dropdownMenuElement.style.marginLeft = null;
        this.dropdownMenuElement.style.marginRight = "0px";
        this.dropdownMenuElement.style.maxWidth = refElementRect.right
            - parseFloat(getComputedStyle(this.dropdownMenuElement).marginLeft) + "px";
        break;
    }

    //calc width
    switch (this.alignment) {
      case DropdownMenuAlignment.start:
      case DropdownMenuAlignment.end:
        this.dropdownMenuElement.style.width = null;
        break;
      case DropdownMenuAlignment.centerFullWidth:
        this.dropdownMenuElement.style.width = refElementRect.width + "px";
        break;
    }

    //calc vertical positioning and max-height
    const upperBorder = this.stickyHeader ? this.stickyHeader.offsetHeight : 0;
    const lowerBorder = this.fixedFooter ? this.fixedFooter.offsetTop : window.innerHeight;
    const spaceAbove = refElementRect.top - upperBorder;
    const spaceBelow = lowerBorder - refElementRect.bottom;
    if (spaceBelow >= spaceAbove) {
      this.dropdownMenuElement.style.top = refElementRect.bottom + "px";
      this.dropdownMenuElement.style.bottom = null;
      this.dropdownMenuElement.style.marginTop = "var(--tobago-dropdown-menu-component-offset)";
      this.dropdownMenuElement.style.marginBottom = null;
      this.dropdownMenuElement.style.maxHeight = spaceBelow
          - parseFloat(getComputedStyle(this.dropdownMenuElement).marginBottom) + "px";
    } else {
      this.dropdownMenuElement.style.top = null;
      this.dropdownMenuElement.style.bottom = (window.innerHeight - refElementRect.top) + "px";
      this.dropdownMenuElement.style.marginTop = null;
      this.dropdownMenuElement.style.marginBottom = "var(--tobago-dropdown-menu-component-offset)";
      this.dropdownMenuElement.style.maxHeight = spaceAbove
          - parseFloat(getComputedStyle(this.dropdownMenuElement).marginTop) + "px";
    }
  }

  private get body(): HTMLElement {
    const root = document.getRootNode() as ShadowRoot | Document;
    return root.querySelector("body");
  }

  private get stickyHeader(): HTMLElement {
    const root = document.getRootNode() as ShadowRoot | Document;
    return root.querySelector("tobago-header.sticky-top");
  }

  private get menuStore(): HTMLDivElement {
    const root = document.getRootNode() as ShadowRoot | Document;
    return root.querySelector<HTMLDivElement>(`.${Css.TOBAGO_PAGE_MENU_STORE}`);
  }

  private get dropdownMenuElement(): HTMLElement {
    const root = document.getRootNode() as ShadowRoot | Document;
    return root.querySelector(`.${Css.TOBAGO_DROPDOWN_MENU}[name='${this.dropdownMenuElementId}']`);
  }

  private get fixedFooter(): HTMLElement {
    const root = document.getRootNode() as ShadowRoot | Document;
    return root.querySelector("tobago-footer.fixed-bottom");
  }
}
