import { transitionHiddenElement } from "@cloudfour/transition-hidden-element";
import { createPopper, Instance, Options } from "@popperjs/core";

import { keys } from "../../utils";

interface DropdownConfig {
  dataInteractive?: string;
  focusTrap?: string;
  removeDialogOnDestroy?: boolean;
  popperOptions?: Partial<Options> & { modifiers?: any[] };
}

const defaultConfig: Required<DropdownConfig> = {
  dataInteractive: "data-dropdown-interactive",
  focusTrap: "data-dropdown-focus-trap",
  removeDialogOnDestroy: false,
  popperOptions: {
    placement: "bottom-start",
    strategy: "absolute",
    modifiers: [
      {
        name: "eventListeners",
        options: {
          scroll: false,
          resize: false,
        },
      },
      {
        name: "flip",
        options: {
          fallbackPlacements: ["bottom-end", "bottom-start", "bottom"],
        },
      },
      {
        name: "offset",
        options: {
          offset: [0, -1],
        },
      },
    ],
  },
};

export default class Dropdown {
  element!: HTMLElement;
  config!: Required<DropdownConfig>;
  trigger!: HTMLElement;
  instance!: Instance;
  transitioner: any;
  focusableElements!: NodeListOf<HTMLElement>;

  constructor(element: HTMLElement, config: DropdownConfig = {}) {
    if ((element as any).ODS_Dropdown) {
      return (element as any).ODS_Dropdown;
    }
    this.element = element;
    this.config = { ...defaultConfig, ...config };
    this.config.popperOptions = {
      ...defaultConfig.popperOptions,
      ...(config?.popperOptions || {}),
    };
    this.config.popperOptions.modifiers = [
      ...((defaultConfig.popperOptions.modifiers as any[]) || []),
      ...((config?.popperOptions?.modifiers as any[]) || []),
    ];

    this.handleVisibility = this.handleVisibility.bind(this);
    this.handleKeyPress = this.handleKeyPress.bind(this);
    this.handleClick = this.handleClick.bind(this);
    (this.element as any).ODS_Dropdown = this;

    this.focusableElements = this.element.querySelectorAll(
      `a[href]:not([disabled]), button:not([disabled]),
        textarea:not([disabled]), input[type="text"]:not([disabled]),
        input[type="radio"]:not([disabled]), input[type="checkbox"]:not([disabled]),
        select:not([disabled])`,
    );

    this.init();
    return this;
  }

  static getInstance(el: HTMLElement): Dropdown | null {
    return el && (el as any).ODS_Dropdown ? (el as any).ODS_Dropdown : null;
  }

  init() {
    this.trigger = document.getElementById(
      this.element.id.replace("dropdown", "btn"),
    ) as HTMLElement;

    const customPlacement = this.element.getAttribute(
      "data-dropdown-placement",
    );
    if (customPlacement) {
      this.config.popperOptions.placement = customPlacement as any;
    }

    this.instance = createPopper(
      this.trigger,
      this.element,
      this.config.popperOptions,
    );

    this.transitioner = transitionHiddenElement({
      element: this.element,
      visibleClass: "dropdown--visible",
    });
    this.trigger.addEventListener("click", this.handleVisibility, true);
  }

  handleVisibility() {
    if (this.element.classList.contains("dropdown--visible")) {
      this.hide();
    } else {
      this.show();
    }
  }

  handleClick(e: MouseEvent) {
    if (this.trigger !== e.target && !this.trigger.contains(e.target as Node)) {
      if (
        !this.element.hasAttribute(this.config.dataInteractive) ||
        (e.target as HTMLElement).id.includes(
          this.element.id.replace("dropdown", "btn"),
        ) ||
        !this.element.contains(e.target as Node)
      ) {
        this.handleVisibility();
      }
    }
  }

  handleKeyPress(e: KeyboardEvent) {
    if (keys.ESC.includes(e.key)) {
      this.handleVisibility();
      if (this.element.contains(document.activeElement)) {
        this.trigger.focus();
      }
      this.hide();
    }
    if (this.element.hasAttribute(this.config.focusTrap)) {
      const firstFocusableEl = this.focusableElements[0];
      const lastFocusableEl =
        this.focusableElements[this.focusableElements.length - 1];
      if (
        document.activeElement === (this as any).button &&
        (e.key === "Tab" || e.keyCode === 9)
      ) {
        if (e.shiftKey) {
          this.focusableElements[this.focusableElements.length - 1].focus();
          e.preventDefault();
        }
      } else if (e.key === "Tab" || e.keyCode === 9) {
        if (e.shiftKey) {
          if (document.activeElement === firstFocusableEl) {
            lastFocusableEl.focus();
            e.preventDefault();
          }
        } else {
          if (document.activeElement === lastFocusableEl) {
            firstFocusableEl.focus();
            e.preventDefault();
          }
        }
      }
    }
  }

  show() {
    this.transitioner.show();
    this.instance.forceUpdate();
    document.addEventListener("keydown", this.handleKeyPress);
    document.addEventListener("click", this.handleClick);
    window.onresize = () => this.update();
  }

  hide() {
    this.transitioner.hide();
    document.removeEventListener("keydown", this.handleKeyPress);
    document.removeEventListener("click", this.handleClick);
    window.onresize = null;
  }

  update() {
    this.instance.forceUpdate();
  }

  destroy() {
    this.trigger.removeEventListener("click", this.handleVisibility, true);
    this.hide();
    this.instance.destroy();
    (this.element as any).ODS_Dropdown = null;
    window.onresize = null;
    if (this.config.removeDialogOnDestroy) {
      this.element.remove();
    }
  }
}
