/*
 * HSRangeSlider
 * @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 type { cssClasses, target } from 'nouislider'

import { IRangeSliderCssClassesObject, IRangeSlider, IRangeSliderOptions } from './interfaces'

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

class HSRangeSlider extends HSBasePlugin<IRangeSliderOptions> implements IRangeSlider {
  private readonly concatOptions: IRangeSliderOptions
  private format: any | null

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

    const data = el.getAttribute('data-range-slider')
    const dataOptions: IRangeSliderOptions = data ? JSON.parse(data) : {}

    this.concatOptions = {
      ...dataOptions,
      ...options,
      cssClasses: {
        ...noUiSlider.cssClasses,
        ...this.processClasses(dataOptions.cssClasses)
      }
    }

    this.init()
  }

  get formattedValue() {
    const values: number | string | (string | number)[] = (this.el as target).noUiSlider.get()

    if (Array.isArray(values) && this.format) {
      const updateValues: (string | number)[] = []

      values.forEach(val => {
        updateValues.push(this.format.to(val))
      })

      return updateValues
    } else if (this.format) {
      return this.format.to(values)
    } else {
      return values
    }
  }

  private processClasses(cl: typeof cssClasses) {
    const mergedClasses: IRangeSliderCssClassesObject = {}

    Object.keys(cl).forEach((key: keyof typeof noUiSlider.cssClasses) => {
      if (key) mergedClasses[key] = `${noUiSlider.cssClasses[key]} ${cl[key]}`
    })

    return mergedClasses as typeof cssClasses
  }

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

    if (
      typeof this.concatOptions?.formatter === 'object'
        ? this.concatOptions?.formatter?.type === 'thousandsSeparatorAndDecimalPoints'
        : this.concatOptions?.formatter === 'thousandsSeparatorAndDecimalPoints'
    )
      this.thousandsSeparatorAndDecimalPointsFormatter()
    else if (
      typeof this.concatOptions?.formatter === 'object'
        ? this.concatOptions?.formatter?.type === 'integer'
        : this.concatOptions?.formatter === 'integer'
    )
      this.integerFormatter()
    else if (
      typeof this.concatOptions?.formatter === 'object' &&
      (this.concatOptions?.formatter?.prefix || this.concatOptions?.formatter?.postfix)
    )
      this.prefixOrPostfixFormatter()

    noUiSlider.create(this.el, this.concatOptions)

    if (this.concatOptions.disabled) this.setDisabled()
  }

  private formatValue(val: number | string) {
    let result = ''

    if (typeof this.concatOptions?.formatter === 'object') {
      if (this.concatOptions?.formatter?.prefix) result += this.concatOptions?.formatter?.prefix
      result += val
      if (this.concatOptions?.formatter?.postfix) result += this.concatOptions?.formatter?.postfix
    } else result += val

    return result
  }

  private integerFormatter() {
    this.format = {
      to: (val: number) => this.formatValue(Math.round(val)),
      from: (val: string) => Math.round(+val)
    }

    if (this.concatOptions?.tooltips) this.concatOptions.tooltips = this.format
  }

  private prefixOrPostfixFormatter() {
    this.format = {
      to: (val: number) => this.formatValue(val),
      from: (val: string) => +val
    }

    if (this.concatOptions?.tooltips) this.concatOptions.tooltips = this.format
  }

  private thousandsSeparatorAndDecimalPointsFormatter() {
    this.format = {
      to: (val: number) =>
        this.formatValue(
          new Intl.NumberFormat('en-US', {
            minimumFractionDigits: 2,
            maximumFractionDigits: 2
          }).format(val)
        ),
      from: (val: string) => parseFloat(val.replace(/,/g, ''))
    }

    if (this.concatOptions?.tooltips) this.concatOptions.tooltips = this.format
  }

  private setDisabled() {
    this.el.setAttribute('disabled', 'disabled')
    this.el.classList.add('disabled')
  }

  // Public methods
  public destroy() {
    ;(this.el as target).noUiSlider.destroy()

    this.format = null

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

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

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

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

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

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

declare global {
  interface Window {
    HSRangeSlider: Function
    $hsRangeSliderCollection: ICollectionItem<HSRangeSlider>[]
  }
}

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

  // Uncomment for debug
  // console.log('Range slider collection:', window.$hsRangeSliderCollection);
})

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

export default HSRangeSlider
