/*
 * HSAccordion
 * @version: 3.0.1
 * @author: Preline Labs Ltd.
 * @license: Licensed under MIT and Preline UI Fair Use License (https://preline.co/docs/license.html)
 * Copyright 2024 Preline Labs Ltd.
 */

import { getClassProperty, stringToBoolean, dispatch, afterTransition } from '../../utils'

import { IAccordionOptions, IAccordion, IAccordionTreeView, IAccordionTreeViewStaticOptions } from './interfaces'

import HSBasePlugin from '../base-plugin'
import { ICollectionItem } from '../../interfaces'

class HSAccordion extends HSBasePlugin<IAccordionOptions> implements IAccordion {
  private toggle: HTMLElement | null
  public content: HTMLElement | null
  private group: HTMLElement | null
  private isAlwaysOpened: boolean
  private keepOneOpen: boolean
  private isToggleStopPropagated: boolean

  private onToggleClickListener: (evt: Event) => void

  static selectable: IAccordionTreeView[]

  constructor(el: HTMLElement, options?: IAccordionOptions, events?: {}) {
    super(el, options, events)

    this.toggle = this.el.querySelector('.accordion-toggle') || null
    this.content = this.el.querySelector('.accordion-content') || null
    this.group = this.el.closest('.accordion') || null
    this.update()

    this.isToggleStopPropagated = stringToBoolean(
      getClassProperty(this.toggle, '--stop-propagation', 'false') || 'false'
    )
    this.keepOneOpen = this.group
      ? stringToBoolean(getClassProperty(this.group, '--keep-one-open', 'false') || 'false')
      : false

    if (this.toggle && this.content) this.init()
  }

  private init() {
    this.createCollection(window.$hsAccordionCollection, this)

    this.onToggleClickListener = (evt: Event) => this.toggleClick(evt)

    this.toggle.addEventListener('click', this.onToggleClickListener)
  }

  // Public methods
  public toggleClick(evt: Event) {
    if (this.el.classList.contains('active') && this.keepOneOpen) return false

    if (this.isToggleStopPropagated) evt.stopPropagation()

    if (this.el.classList.contains('active')) {
      this.hide()
    } else {
      this.show()
    }
  }

  public show() {
    if (
      this.group &&
      !this.isAlwaysOpened &&
      this.group.querySelector(':scope > .accordion-item.active') &&
      this.group.querySelector(':scope > .accordion-item.active') !== this.el
    ) {
      const currentlyOpened = window.$hsAccordionCollection.find(
        el => el.element.el === this.group.querySelector(':scope > .accordion-item.active')
      )

      currentlyOpened.element.hide()
    }

    if (this.el.classList.contains('active')) return false

    this.el.classList.add('active')
    if (this?.toggle?.ariaExpanded) this.toggle.ariaExpanded = 'true'

    this.fireEvent('beforeOpen', this.el)
    dispatch('beforeOpen.accordion.item', this.el, this.el)

    this.content.style.display = 'block'
    this.content.style.height = '0'
    setTimeout(() => {
      this.content.style.height = `${this.content.scrollHeight}px`

      afterTransition(this.content, () => {
        this.content.style.display = 'block'
        this.content.style.height = ''

        this.fireEvent('open', this.el)
        dispatch('open.accordion.item', this.el, this.el)
      })
    })
  }

  public hide() {
    if (!this.el.classList.contains('active')) return false

    this.el.classList.remove('active')
    if (this?.toggle?.ariaExpanded) this.toggle.ariaExpanded = 'false'

    this.fireEvent('beforeClose', this.el)
    dispatch('beforeClose.accordion.item', this.el, this.el)

    this.content.style.height = `${this.content.scrollHeight}px`
    setTimeout(() => {
      this.content.style.height = '0'
    })

    afterTransition(this.content, () => {
      this.content.style.display = 'none'
      this.content.style.height = ''

      this.fireEvent('close', this.el)
      dispatch('close.accordion.item', this.el, this.el)
    })
  }

  public update() {
    this.group = this.el.closest('.accordion') || null

    if (!this.group) return false

    this.isAlwaysOpened = this.group.hasAttribute('data-accordion-always-open') || false

    window.$hsAccordionCollection.map(el => {
      if (el.id === this.el.id) {
        el.element.group = this.group
        el.element.isAlwaysOpened = this.isAlwaysOpened
      }

      return el
    })
  }

  public destroy() {
    if (HSAccordion?.selectable?.length) {
      HSAccordion.selectable.forEach(item => {
        item.listeners.forEach(({ el, listener }) => {
          el.removeEventListener('click', listener)
        })
      })
    }

    if (this.onToggleClickListener) {
      this.toggle.removeEventListener('click', this.onToggleClickListener)
    }

    this.toggle = null
    this.content = null
    this.group = null

    this.onToggleClickListener = null

    window.$hsAccordionCollection = window.$hsAccordionCollection.filter(({ element }) => element.el !== this.el)
  }

  // Static methods
  private static findInCollection(target: HSAccordion | HTMLElement | string): ICollectionItem<HSAccordion> | null {
    return (
      window.$hsAccordionCollection.find(el => {
        if (target instanceof HSAccordion) return el.element.el === target.el
        else if (typeof target === 'string') return el.element.el === document.querySelector(target)
        else return el.element.el === target
      }) || null
    )
  }

  static autoInit() {
    if (!window.$hsAccordionCollection) window.$hsAccordionCollection = []

    if (window.$hsAccordionCollection) {
      window.$hsAccordionCollection = window.$hsAccordionCollection.filter(({ element }) =>
        document.contains(element.el)
      )
    }

    document.querySelectorAll('.accordion-item:not(.--prevent-on-load-init)').forEach((el: HTMLElement) => {
      if (!window.$hsAccordionCollection.find(elC => (elC?.element?.el as HTMLElement) === el)) new HSAccordion(el)
    })
  }

  static getInstance(target: HTMLElement | string, isInstance?: boolean) {
    const elInCollection = window.$hsAccordionCollection.find(
      el => el.element.el === (typeof target === 'string' ? document.querySelector(target) : target)
    )

    return elInCollection ? (isInstance ? elInCollection : elInCollection.element.el) : null
  }

  static show(target: HSAccordion | HTMLElement | string) {
    const instance = HSAccordion.findInCollection(target)

    if (instance && instance.element.content.style.display !== 'block') instance.element.show()
  }

  static hide(target: HSAccordion | HTMLElement | string) {
    const instance = HSAccordion.findInCollection(target)
    const style = instance ? window.getComputedStyle(instance.element.content) : null

    if (instance && style.display !== 'none') instance.element.hide()
  }

  static onSelectableClick = (evt: Event, item: IAccordionTreeView, el: HTMLElement) => {
    evt.stopPropagation()

    HSAccordion.toggleSelected(item, el)
  }

  static treeView() {
    if (!document.querySelectorAll('.accordion-treeview-root').length) return false

    this.selectable = []

    document.querySelectorAll('.accordion-treeview-root').forEach((el: HTMLElement) => {
      const data = el?.getAttribute('data-accordion-options')
      const options: IAccordionTreeViewStaticOptions = data ? JSON.parse(data) : {}

      this.selectable.push({
        el,
        options: { ...options },
        listeners: []
      })
    })

    if (this.selectable.length)
      this.selectable.forEach(item => {
        const { el } = item

        el.querySelectorAll('.accordion-selectable').forEach((_el: HTMLElement) => {
          const listener = (evt: Event) => this.onSelectableClick(evt, item, _el)

          _el.addEventListener('click', listener)

          item.listeners.push({ el: _el, listener })
        })
      })
  }

  static toggleSelected(root: IAccordionTreeView, item: HTMLElement) {
    if (item.classList.contains('selected')) item.classList.remove('selected')
    else {
      root.el.querySelectorAll('.accordion-selectable').forEach((el: HTMLElement) => el.classList.remove('selected'))
      item.classList.add('selected')
    }
  }

  // Backward compatibility
  static on(evt: string, target: HSAccordion | HTMLElement | string, cb: Function) {
    const instance = HSAccordion.findInCollection(target)

    if (instance) instance.element.events[evt] = cb
  }
}

declare global {
  interface Window {
    HSAccordion: Function
    $hsAccordionCollection: ICollectionItem<HSAccordion>[]
  }
}

window.addEventListener('load', () => {
  HSAccordion.autoInit()

  if (document.querySelectorAll('.accordion-treeview-root').length) HSAccordion.treeView()

  // Uncomment for debug
  // console.log('Accordion collection:', window.$hsAccordionCollection);
})

if (typeof window !== 'undefined') {
  window.HSAccordion = HSAccordion
}

export default HSAccordion
