/*!
 * (C) Gasco https://gasco-web-component.herokuapp.com/ - MIT License
 */
import { Component, Element, Host, Prop, State, h } from '@stencil/core';
import { chevronDown } from 'ionicons/icons';
import { config } from '../../global/config';
import { addEventListener, getElementRoot, raf, removeEventListener, transitionEndAsync } from '../../utils/helpers';
/**
 * @slot header - Content is placed at the top and is used to
 * expand or collapse the accordion item.
 * @slot content - Content is placed below the header and is
 * shown or hidden based on expanded state.
 *
 * @part header - The wrapper element for the header slot.
 * @part content - The wrapper element for the content slot.
 * @part expanded - The expanded element. Can be used in combination
 * with the `header` and `content` parts (i.e. `::part(header expanded)`).
 */
export class GascoAccordion {
  constructor() {
    this.updateListener = () => this.updateState(false);
    this.state = 1 /* Collapsed */;
    this.isNext = false;
    this.isPrevious = false;
    /**
     * The value of the accordion. Defaults to an autogenerated
     * value.
     */
    this.value = `gasco-accordion-${accordionIds++}`;
    /**
     * If `true`, the accordion cannot be interacted with.
     */
    this.disabled = false;
    /**
     * If `true`, the accordion cannot be interacted with,
     * but does not alter the opacity.
     */
    this.readonly = false;
    /**
     * The toggle icon to use. This icon will be
     * rotated when the accordion is expanded
     * or collapsed.
     */
    this.toggleIcon = chevronDown;
    /**
     * The slot inside of `gasco-item` to
     * place the toggle icon. Defaults to `'end'`.
     */
    this.toggleIconSlot = 'end';
    this.setItemDefaults = () => {
      const gascoItem = this.getSlottedHeaderGascoItem();
      if (!gascoItem) {
        return;
      }
      /**
       * For a11y purposes, we make
       * the ion-item a button so users
       * can tab to it and use keyboard
       * navigation to get around.
       */
      gascoItem.button = true;
      gascoItem.detail = false;
      /**
       * By default, the lines in an
       * item should be full here, but
       * only do that if a user has
       * not explicitly overridden them
       */
      if (gascoItem.lines === undefined) {
        gascoItem.lines = 'full';
      }
    };
    this.getSlottedHeaderGascoItem = () => {
      const { headerEl } = this;
      if (!headerEl) {
        return;
      }
      /**
       * Get the first gasco-item
       * slotted in the header slot
       */
      const slot = headerEl.querySelector('slot');
      if (!slot) {
        return;
      }
      // This is not defined in unit tests
      const gascoItem = slot.assignedElements &&
        slot.assignedElements().find((el) => el.tagName === 'GASCO-ITEM');
      return gascoItem;
    };
    this.setAria = (expanded = false) => {
      const gascoItem = this.getSlottedHeaderGascoItem();
      if (!gascoItem) {
        return;
      }
      /**
       * Get the native <button> element inside of
       * gasco-item because that is what will be focused
       */
      const root = getElementRoot(gascoItem);
      const button = root.querySelector('button');
      if (!button) {
        return;
      }
      button.setAttribute('aria-expanded', `${expanded}`);
    };
    this.slotToggleIcon = () => {
      const gascoItem = this.getSlottedHeaderGascoItem();
      if (!gascoItem) {
        return;
      }
      // const { toggleIconSlot, toggleIcon } = this;
      const { toggleIconSlot, toggleIcon } = this;
      /**
       * Check if there already is a toggle icon.
       * If so, do not add another one.
       */
      const existingToggleIcon = gascoItem.querySelector('.gasco-accordion-toggle-icon');
      if (existingToggleIcon) {
        return;
      }
      const iconEl = document.createElement('ion-icon');
      iconEl.slot = toggleIconSlot;
      iconEl.lazy = false;
      iconEl.classList.add('gasco-accordion-toggle-icon');
      iconEl.icon = toggleIcon;
      iconEl.setAttribute('aria-hidden', 'true');
      // const iconEl = document.createElement('i');
      // iconEl.slot = toggleIconSlot;
      // iconEl.textContent = 'expand_more';
      // // iconEl.lazy = false;
      // // iconEl.icon = toggleIcon;
      // iconEl.classList.add('material-icons');
      // iconEl.classList.add('gasco-accordion-toggle-icon');
      // iconEl.setAttribute('aria-hidden', 'true');
      gascoItem.appendChild(iconEl);
    };
    this.expandAccordion = (initialUpdate = false) => {
      const { contentEl, contentElWrapper } = this;
      if (initialUpdate || contentEl === undefined || contentElWrapper === undefined) {
        this.state = 4 /* Expanded */;
        return;
      }
      if (this.state === 4 /* Expanded */) {
        return;
      }
      if (this.currentRaf !== undefined) {
        cancelAnimationFrame(this.currentRaf);
      }
      if (this.shouldAnimate()) {
        raf(() => {
          this.state = 8 /* Expanding */;
          this.currentRaf = raf(async () => {
            const contentHeight = contentElWrapper.offsetHeight;
            const waitForTransition = transitionEndAsync(contentEl, 2000);
            contentEl.style.setProperty('max-height', `${contentHeight}px`);
            await waitForTransition;
            this.state = 4 /* Expanded */;
            contentEl.style.removeProperty('max-height');
          });
        });
      }
      else {
        this.state = 4 /* Expanded */;
      }
    };
    this.collapseAccordion = (initialUpdate = false) => {
      const { contentEl } = this;
      if (initialUpdate || contentEl === undefined) {
        this.state = 1 /* Collapsed */;
        return;
      }
      if (this.state === 1 /* Collapsed */) {
        return;
      }
      if (this.currentRaf !== undefined) {
        cancelAnimationFrame(this.currentRaf);
      }
      if (this.shouldAnimate()) {
        this.currentRaf = raf(async () => {
          const contentHeight = contentEl.offsetHeight;
          contentEl.style.setProperty('max-height', `${contentHeight}px`);
          raf(async () => {
            const waitForTransition = transitionEndAsync(contentEl, 2000);
            this.state = 2 /* Collapsing */;
            await waitForTransition;
            this.state = 1 /* Collapsed */;
            contentEl.style.removeProperty('max-height');
          });
        });
      }
      else {
        this.state = 1 /* Collapsed */;
      }
    };
    /**
     * Helper function to determine if
     * something should animate.
     * If prefers-reduced-motion is set
     * then we should not animate, regardless
     * of what is set in the config.
     */
    this.shouldAnimate = () => {
      if (typeof window === 'undefined') {
        return false;
      }
      const prefersReducedMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
      if (prefersReducedMotion) {
        return false;
      }
      const animated = config.get('animated', true);
      if (!animated) {
        return false;
      }
      if (this.accordionGroupEl && !this.accordionGroupEl.animated) {
        return false;
      }
      return true;
    };
    this.updateState = async (initialUpdate = false) => {
      const accordionGroup = this.accordionGroupEl;
      const accordionValue = this.value;
      if (!accordionGroup) {
        return;
      }
      const value = accordionGroup.value;
      const shouldExpand = Array.isArray(value) ? value.includes(accordionValue) : value === accordionValue;
      if (shouldExpand) {
        this.expandAccordion(initialUpdate);
        this.isNext = this.isPrevious = false;
      }
      else {
        this.collapseAccordion(initialUpdate);
        /**
         * When using popout or inset,
         * the collapsed accordion items
         * may need additional border radius
         * applied. Check to see if the
         * next or previous accordion is selected.
         */
        const nextAccordion = this.getNextSibling();
        const nextAccordionValue = nextAccordion === null || nextAccordion === void 0 ? void 0 : nextAccordion.value;
        if (nextAccordionValue !== undefined) {
          this.isPrevious = Array.isArray(value) ? value.includes(nextAccordionValue) : value === nextAccordionValue;
        }
        const previousAccordion = this.getPreviousSibling();
        const previousAccordionValue = previousAccordion === null || previousAccordion === void 0 ? void 0 : previousAccordion.value;
        if (previousAccordionValue !== undefined) {
          this.isNext = Array.isArray(value) ? value.includes(previousAccordionValue) : value === previousAccordionValue;
        }
      }
    };
    this.getNextSibling = () => {
      if (!this.el) {
        return;
      }
      const nextSibling = this.el.nextElementSibling;
      if ((nextSibling === null || nextSibling === void 0 ? void 0 : nextSibling.tagName) !== 'GASCO-ACCORDION') {
        return;
      }
      return nextSibling;
    };
    this.getPreviousSibling = () => {
      if (!this.el) {
        return;
      }
      const previousSibling = this.el.previousElementSibling;
      if ((previousSibling === null || previousSibling === void 0 ? void 0 : previousSibling.tagName) !== 'GASCO-ACCORDION') {
        return;
      }
      return previousSibling;
    };
  }
  connectedCallback() {
    var _a;
    const accordionGroupEl = (this.accordionGroupEl = (_a = this.el) === null || _a === void 0 ? void 0 : _a.closest('gasco-accordion-group'));
    if (accordionGroupEl) {
      this.updateState(true);
      addEventListener(accordionGroupEl, 'gascoChange', this.updateListener);
    }
  }
  disconnectedCallback() {
    const accordionGroupEl = this.accordionGroupEl;
    if (accordionGroupEl) {
      removeEventListener(accordionGroupEl, 'gascoChange', this.updateListener);
    }
  }
  componentDidLoad() {
    this.setItemDefaults();
    this.slotToggleIcon();
    /**
     * We need to wait a tick because we
     * just set gascoItem.button = true and
     * the button has not have been rendered yet.
     */
    raf(() => {
      /**
       * Set aria label on button inside of ion-item
       * once the inner content has been rendered.
       */
      const expanded = this.state === 4 /* Expanded */ || this.state === 8 /* Expanding */;
      this.setAria(expanded);
    });
  }
  toggleExpanded() {
    const { accordionGroupEl, value, state } = this;
    if (accordionGroupEl) {
      /**
       * Because the accordion group may or may
       * not allow multiple accordions open, we
       * need to request the toggling of this
       * accordion and the accordion group will
       * make the decision on whether or not
       * to allow it.
       */
      const expand = state === 1 /* Collapsed */ || state === 2 /* Collapsing */;
      accordionGroupEl.requestAccordionToggle(value, expand);
    }
  }
  render() {
    const { disabled, readonly } = this;
    const expanded = this.state === 4 /* Expanded */ || this.state === 8 /* Expanding */;
    const headerPart = expanded ? 'header expanded' : 'header';
    const contentPart = expanded ? 'content expanded' : 'content';
    this.setAria(expanded);
    return (h(Host, { class: {
        'accordion-expanding': this.state === 8 /* Expanding */,
        'accordion-expanded': this.state === 4 /* Expanded */,
        'accordion-collapsing': this.state === 2 /* Collapsing */,
        'accordion-collapsed': this.state === 1 /* Collapsed */,
        'accordion-next': this.isNext,
        'accordion-previous': this.isPrevious,
        'accordion-disabled': disabled,
        'accordion-readonly': readonly,
        'accordion-animated': config.getBoolean('animated', true),
      } },
      h("div", { onClick: () => this.toggleExpanded(), id: "header", part: headerPart, "aria-controls": "content", ref: (headerEl) => (this.headerEl = headerEl) },
        h("slot", { name: "header" })),
      h("div", { id: "content", part: contentPart, role: "region", "aria-labelledby": "header", ref: (contentEl) => (this.contentEl = contentEl) },
        h("div", { id: "content-wrapper", ref: (contentElWrapper) => (this.contentElWrapper = contentElWrapper) },
          h("slot", null)))));
  }
  static get is() { return "gasco-accordion"; }
  static get encapsulation() { return "shadow"; }
  static get delegatesFocus() { return true; }
  static get originalStyleUrls() { return {
    "$": ["gasco-accordion.md.scss"]
  }; }
  static get styleUrls() { return {
    "$": ["gasco-accordion.md.css"]
  }; }
  static get properties() { return {
    "value": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "The value of the accordion. Defaults to an autogenerated\nvalue."
      },
      "attribute": "value",
      "reflect": false,
      "defaultValue": "`gasco-accordion-${accordionIds++}`"
    },
    "disabled": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "If `true`, the accordion cannot be interacted with."
      },
      "attribute": "disabled",
      "reflect": false,
      "defaultValue": "false"
    },
    "readonly": {
      "type": "boolean",
      "mutable": false,
      "complexType": {
        "original": "boolean",
        "resolved": "boolean",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "If `true`, the accordion cannot be interacted with,\nbut does not alter the opacity."
      },
      "attribute": "readonly",
      "reflect": false,
      "defaultValue": "false"
    },
    "toggleIcon": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "string",
        "resolved": "string",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "The toggle icon to use. This icon will be\nrotated when the accordion is expanded\nor collapsed."
      },
      "attribute": "toggle-icon",
      "reflect": false,
      "defaultValue": "chevronDown"
    },
    "toggleIconSlot": {
      "type": "string",
      "mutable": false,
      "complexType": {
        "original": "'start' | 'end'",
        "resolved": "\"end\" | \"start\"",
        "references": {}
      },
      "required": false,
      "optional": false,
      "docs": {
        "tags": [],
        "text": "The slot inside of `gasco-item` to\nplace the toggle icon. Defaults to `'end'`."
      },
      "attribute": "toggle-icon-slot",
      "reflect": false,
      "defaultValue": "'end'"
    }
  }; }
  static get states() { return {
    "state": {},
    "isNext": {},
    "isPrevious": {}
  }; }
  static get elementRef() { return "el"; }
}
let accordionIds = 0;
