/**
 * collapsible.js
 * Script to expand and collapse elements
 */

import ElementHelpers from '../helpers/element-helpers';

/**
 * Declare constants
 */
const attributeBase = 'data-rs-collapsible';

export class Collapsible {
  constructor(element) {
    this.element = element;
    this.content = this.element.parentElement.querySelector(
      `[${this.attributes.content}]`
    );
    this.triggerElement = this.element;
    this.initialExpanded =
      this.content.getAttribute(this.attributes.content) ===
      this.states.expanded;

    this.elementsToClose = ElementHelpers.getElementsByAttribute(
      this.attributes.closeOnScroll
    );

    this.addEventHandlers();
  }

  /**
   * Declare attribute constants
   */
  get attributes() {
    return {
      content: `${attributeBase}-content`,
      button: `${attributeBase}-button`,
      navigation: 'navigation',
      ariaExpanded: 'aria-expanded',
      filter: 'filter',
      closeOnOutsideClick: `${attributeBase}-close-on-outside-click`,
      closeOnScroll: `${attributeBase}-close-on-scroll`
    };
  }

  /**
   * Declare classes
   */
  get classes() {
    return {
      collapsibleTriggerExpanded: 'collapsible__trigger--expanded'
    };
  }

  /**
   * Declare state constants
   */
  get states() {
    return {
      expanded: 'expanded'
    };
  }

  /**
   * Add event handlers
   */
  addEventHandlers() {
    const initialHeight = this.content.offsetHeight;
    const width = window.innerWidth;

    if (
      this.element.getAttribute(attributeBase) === this.attributes.navigation
    ) {
      this.triggerElement = this.element.querySelector(
        `[${this.attributes.button}]`
      );
    }

    if (this.triggerElement != null) {
      ['click', 'keydown'].forEach((evt) => {
        this.triggerElement.addEventListener(evt, (e) => {
          if (
            (e.type === 'keydown' && (e.keyCode === 13 || e.keyCode === 32)) ||
            e.type === 'click'
          ) {
            e.preventDefault();
            this.toggle(initialHeight);
          }
        });
      });
    }

    this.isInitiallyExpanded(initialHeight);
    this.isOverflown(this.content, this.element);

    // on orientation change, recalculate content height
    window.addEventListener('orientationchange', () => {
      this.recalculateContentHeight();
      this.isOverflown(this.content, this.element);
    });

    window.addEventListener('resize', () => {
      // Fix: Chrome mobile browser triggers a resize because of appearing and disappearing navigation
      if (window.innerWidth !== width) {
        this.recalculateContentHeight();
        this.isOverflown(this.content, this.element);
      }
    });

    if (this.elementsToClose) {
      window.addEventListener('scroll', () => {
        this.simulateClickOnScroll();
      });
    }
  }

  /*
   * function set the initial values
   */
  setInitialValues() {}

  /*
   * function to get all the elements that needs to be closed on scroll and are currently expanded
   */
  isExpandedElementToClose() {
    if (!this.elementsToClose) {
      return;
    }

    let elArr = [];

    this.elementsToClose.forEach((element) => {
      if (
        element.classList.contains(this.classes.collapsibleTriggerExpanded) &&
        element.hasAttribute(this.attributes.closeOnScroll)
      ) {
        elArr.push(element);
      }
    });

    return elArr;
  }

  /*
   * if the element is expanded and needs to be closed on scroll, simulate a click
   */
  simulateClickOnScroll() {
    const elements = this.isExpandedElementToClose();

    const windowScrollPos = window.scrollY;

    elements.forEach((element) => {
      const elementPos = element.parentElement.offsetTop;

      if (windowScrollPos > elementPos) {
        element.click();
      }
    });
  }

  toggle(initialHeight) {
    const setHeight = this.initialExpanded ? '0' : initialHeight;

    if (this.isTriggerExpanded) {
      this.collapseTrigger();
      this.collapseContent(setHeight);
    } else {
      const contentHeight = this.content.scrollHeight;
      this.expandTrigger();
      this.expandContent(contentHeight);
    }
  }

  get isTriggerExpanded() {
    return this.element.classList.contains(
      this.classes.collapsibleTriggerExpanded
    );
  }

  collapseTrigger() {
    this.element.classList.remove(this.classes.collapsibleTriggerExpanded);
    this.element.setAttribute(this.attributes.ariaExpanded, 'false');
  }

  collapseContent(initialHeight) {
    this.content.style.maxHeight = initialHeight + 'px';
    this.content.setAttribute(this.attributes.content, '');
    this.content.setAttribute(this.attributes.ariaExpanded, 'false');
  }

  /**
   * Handles element on expand - add class and set aria expanded attribute
   */
  expandTrigger() {
    this.element.classList.add(this.classes.collapsibleTriggerExpanded);
    this.element.setAttribute(this.attributes.ariaExpanded, 'true');
  }

  /**
   * Handles content on expand - set maxheight, expanded state en aria hidden attribute
   *
   * @param {number} contentHeight - height of content element
   */
  expandContent(contentHeight) {
    this.content.style.maxHeight = `${contentHeight + 30}px`; // add 30px to fit on IOS mobile (gives back different scrollHeight)
    this.content.setAttribute(this.attributes.content, this.states.expanded);
    this.content.setAttribute(this.attributes.ariaExpanded, 'true');
  }

  /**
   * Handles the component when it is initially expanded
   */
  isInitiallyExpanded() {
    if (this.initialExpanded) {
      this.expandTrigger();
    }
  }

  /**
   * Recalculate content height and set max height
   */
  recalculateContentHeight() {
    this.content = this.element.parentElement.querySelector(
      `[${this.attributes.content}]`
    );
    this.contentHeight = this.content.scrollHeight;

    // set maxheight on expanded content
    if (
      this.content.getAttribute(this.attributes.content) ===
      this.states.expanded
    ) {
      this.content.style.maxHeight = `${this.contentHeight}px`;
    }
  }

  /**
   * check if the content is not overflown
   */
  isOverflown(content, trigger) {
    if (trigger.getAttribute(attributeBase) === 'hide-trigger') {
      const isOverflown =
        content.scrollHeight > content.clientHeight ||
        content.scrollWidth > content.clientWidth;

      if (!isOverflown) {
        trigger.classList.add('display-none');
      }

      if (isOverflown) {
        trigger.classList.remove('display-none');
      }
    }
  }

  /**
   * Get selector
   */
  static getSelector() {
    return `[${attributeBase}]`;
  }
}
