/*
 * HSPinInput
 * @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 { dispatch } from '../../utils'

import { IPinInputOptions, IPinInput } from './interfaces'

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

class HSPinInput extends HSBasePlugin<IPinInputOptions> implements IPinInput {
  private items: NodeListOf<HTMLElement> | null
  private currentItem: HTMLInputElement | null
  private currentValue: string[] | null
  private readonly placeholders: string[] | null
  private readonly availableCharsRE: RegExp | null

  private onElementInputListener:
    | {
        el: HTMLElement
        fn: (evt: Event) => void
      }[]
    | null
  private onElementPasteListener:
    | {
        el: HTMLElement
        fn: (evt: Event) => void
      }[]
    | null
  private onElementKeydownListener:
    | {
        el: HTMLElement
        fn: (evt: Event) => void
      }[]
    | null
  private onElementFocusinListener:
    | {
        el: HTMLElement
        fn: () => void
      }[]
    | null
  private onElementFocusoutListener:
    | {
        el: HTMLElement
        fn: () => void
      }[]
    | null

  private elementInput(evt: Event, index: number) {
    this.onInput(evt, index)
  }

  private elementPaste(evt: ClipboardEvent) {
    this.onPaste(evt)
  }

  private elementKeydown(evt: KeyboardEvent, index: number) {
    this.onKeydown(evt, index)
  }

  private elementFocusin(index: number) {
    this.onFocusIn(index)
  }

  private elementFocusout(index: number) {
    this.onFocusOut(index)
  }

  constructor(el: HTMLElement, options?: IPinInputOptions) {
    super(el, options)

    const data = el.getAttribute('data-pin-input')
    const dataOptions: IPinInputOptions = data ? JSON.parse(data) : {}
    const concatOptions = {
      ...dataOptions,
      ...options
    }

    this.items = this.el.querySelectorAll('[data-pin-input-item]')
    this.currentItem = null
    this.currentValue = new Array(this.items.length).fill('')
    this.placeholders = []
    this.availableCharsRE = new RegExp(concatOptions?.availableCharsRE || '^[a-zA-Z0-9]+$') // '^[0-9]+$'

    this.onElementInputListener = []
    this.onElementPasteListener = []
    this.onElementKeydownListener = []
    this.onElementFocusinListener = []
    this.onElementFocusoutListener = []

    this.init()
  }

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

    if (this.items.length) this.build()
  }

  private build() {
    this.buildInputItems()
  }

  private buildInputItems() {
    this.items.forEach((el, index) => {
      this.placeholders.push(el.getAttribute('placeholder') || '')

      if (el.hasAttribute('autofocus')) this.onFocusIn(index)

      this.onElementInputListener.push({
        el,
        fn: (evt: Event) => this.elementInput(evt, index)
      })
      this.onElementPasteListener.push({
        el,
        fn: (evt: ClipboardEvent) => this.elementPaste(evt)
      })
      this.onElementKeydownListener.push({
        el,
        fn: (evt: KeyboardEvent) => this.elementKeydown(evt, index)
      })
      this.onElementFocusinListener.push({
        el,
        fn: () => this.elementFocusin(index)
      })
      this.onElementFocusoutListener.push({
        el,
        fn: () => this.elementFocusout(index)
      })

      el.addEventListener('input', this.onElementInputListener.find(elI => elI.el === el).fn)
      el.addEventListener('paste', this.onElementPasteListener.find(elI => elI.el === el).fn)
      el.addEventListener('keydown', this.onElementKeydownListener.find(elI => elI.el === el).fn)
      el.addEventListener('focusin', this.onElementFocusinListener.find(elI => elI.el === el).fn)
      el.addEventListener('focusout', this.onElementFocusoutListener.find(elI => elI.el === el).fn)
    })
  }

  private checkIfNumber(value: string) {
    return value.match(this.availableCharsRE)
  }

  private autoFillAll(text: string) {
    Array.from(text).forEach((n, i) => {
      if (!this?.items[i]) return false
      ;(this.items[i] as HTMLInputElement).value = n

      this.items[i].dispatchEvent(new Event('input', { bubbles: true }))
    })
  }

  private setCurrentValue() {
    this.currentValue = Array.from(this.items).map(el => (el as HTMLInputElement).value)
  }

  private toggleCompleted() {
    if (!this.currentValue.includes('')) this.el.classList.add('active')
    else this.el.classList.remove('active')
  }

  private onInput(evt: Event, index: number) {
    const originalValue = (evt.target as HTMLInputElement).value
    this.currentItem = evt.target as HTMLInputElement
    this.currentItem.value = ''
    this.currentItem.value = originalValue[originalValue.length - 1]

    if (!this.checkIfNumber(this.currentItem.value)) {
      this.currentItem.value = this.currentValue[index] || ''

      return false
    }

    this.setCurrentValue()

    if (this.currentItem.value) {
      if (index < this.items.length - 1) this.items[index + 1].focus()
      if (!this.currentValue.includes('')) {
        const payload = { currentValue: this.currentValue }

        this.fireEvent('completed', payload)
        dispatch('completed.pinInput', this.el, payload)
      }

      this.toggleCompleted()
    } else {
      if (index > 0) this.items[index - 1].focus()
    }
  }

  private onKeydown(evt: KeyboardEvent, index: number) {
    if (evt.key === 'Backspace' && index > 0) {
      if ((this.items[index] as HTMLInputElement).value === '') {
        ;(this.items[index - 1] as HTMLInputElement).value = ''
        ;(this.items[index - 1] as HTMLInputElement).focus()
      } else {
        ;(this.items[index] as HTMLInputElement).value = ''
      }
    }

    this.setCurrentValue()
    this.toggleCompleted()
  }

  private onFocusIn(index: number) {
    this.items[index].setAttribute('placeholder', '')
  }

  private onFocusOut(index: number) {
    this.items[index].setAttribute('placeholder', this.placeholders[index])
  }

  private onPaste(evt: ClipboardEvent) {
    evt.preventDefault()

    this.items.forEach(el => {
      if (document.activeElement === el) this.autoFillAll(evt.clipboardData.getData('text'))
    })
  }

  // Public methods
  public destroy() {
    // Remove classes
    this.el.classList.remove('active')

    // Remove listeners
    if (this.items.length)
      this.items.forEach(el => {
        el.removeEventListener('input', this.onElementInputListener.find(elI => elI.el === el).fn)
        el.removeEventListener('paste', this.onElementPasteListener.find(elI => elI.el === el).fn)
        el.removeEventListener('keydown', this.onElementKeydownListener.find(elI => elI.el === el).fn)
        el.removeEventListener('focusin', this.onElementFocusinListener.find(elI => elI.el === el).fn)
        el.removeEventListener('focusout', this.onElementFocusoutListener.find(elI => elI.el === el).fn)
      })

    this.items = null
    this.currentItem = null
    this.currentValue = null

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

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

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

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

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

    document.querySelectorAll('[data-pin-input]:not(.--prevent-on-load-init)').forEach((el: HTMLElement) => {
      if (!window.$hsPinInputCollection.find(elC => (elC?.element?.el as HTMLElement) === el)) new HSPinInput(el)
    })
  }
}

declare global {
  interface Window {
    HSPinInput: Function
    $hsPinInputCollection: ICollectionItem<HSPinInput>[]
  }
}

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

  // Uncomment for debug
  // console.log('PIN input collection:', window.$hsPinInputCollection);
})

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

export default HSPinInput
