/**
 * @file vue-awesome-swiper
 * @module directive
 * @author Surmon <https://github.com/surmon-china>
 */

import { DirectiveOptions, VNode } from 'vue'
import { DirectiveBinding } from 'vue/types/options'
import Swiper, { SwiperOptions } from 'swiper'
import { DEFAULT_CLASSES, CoreNames, ComponentEvents, ComponentPropNames } from './constants'
import { handleClickSlideEvent, bindSwiperEvents } from './event'
import { kebabcase } from './utils'

const INSTANCE_NAME_KEY = 'instanceName'

export default function getDirective(SwiperClass: typeof Swiper, globalOptions?: SwiperOptions): DirectiveOptions {

  const getStandardisedOptionByAttrs = (vnode: VNode, key: string): any => {
    const value = vnode.data?.attrs?.[key]
    return value !== undefined
      ? value
      : vnode.data?.attrs?.[kebabcase(key)]
  }

  // Get swiper instace name in directive
  const getSwiperInstanceName = (element: HTMLElement, binding: DirectiveBinding, vnode: VNode): string => {
    return (
      binding.arg ||
      getStandardisedOptionByAttrs(vnode, INSTANCE_NAME_KEY) ||
      element.id ||
      CoreNames.SwiperInstance
    )
  }

  const getSwiperInstance = (element: HTMLElement, binding: DirectiveBinding, vnode: VNode): Swiper | null => {
    const instanceName = getSwiperInstanceName(element, binding, vnode)
    return (vnode.context as any)[instanceName] || null
  }

  const getSwipeOptions = (binding: DirectiveBinding): SwiperOptions => {
    return binding.value || globalOptions
  }

  const getBooleanValueByInput = (input: any): boolean => {
    return [true, undefined, null, ''].includes(input)
  }

  // Emit event in Vue directive
  const getEventEmiter = (vnode: VNode) => {
    const handlers = vnode.data?.on || vnode.componentOptions?.listeners
    return (name: string, ...args: any[]) => {
      const handle = (handlers as any)?.[name]
      if (handle) {
        handle.fns(...args)
      }
    }
  }

  return {
    // Init
    bind(element, binding, vnode) {
      // auto class name
      if (element.className.indexOf(DEFAULT_CLASSES.containerClass) === -1) {
        element.className += ((element.className ? ' ' : '') + DEFAULT_CLASSES.containerClass)
      }
      // bind click event
      element.addEventListener('click', event => {
        const emitEvent = getEventEmiter(vnode)
        const swiper = getSwiperInstance(element, binding, vnode)
        handleClickSlideEvent(swiper, event, emitEvent)
      })
    },
    // DOM inserted
    inserted(element, binding, vnode) {
      const context = vnode.context
      const swiperOptions = getSwipeOptions(binding)
      const instanceName = getSwiperInstanceName(element, binding, vnode)
      const emitEvent = getEventEmiter(vnode)

      const vueContext = context as any
      let swiper: Swiper = vueContext?.[instanceName]

      // Swiper will destroy but not delete instance, when used <keep-alive>
      if (!swiper || (swiper as any).destroyed) {
        swiper = new SwiperClass(element, swiperOptions)
        vueContext[instanceName] = swiper
        bindSwiperEvents(swiper, emitEvent)
        emitEvent(ComponentEvents.Ready, swiper)
        // MARK: Reinstance when the nexttick with <keep-alive>
        // Vue.nextTick(instancing) | setTimeout(instancing)
      }
    },
    // On options changed or DOM updated
    componentUpdated(element, binding, vnode) {
      const autoUpdate = getStandardisedOptionByAttrs(
        vnode,
        ComponentPropNames.AutoUpdate
      )
      if (getBooleanValueByInput(autoUpdate)) {
        const swiper = getSwiperInstance(element, binding, vnode)
        if (swiper) {
          const swiperOptions = getSwipeOptions(binding)
          const isLoop = swiperOptions.loop
          if (isLoop) {
            ;(swiper as any)?.loopDestroy?.()
          }
          swiper?.update?.()
          swiper.navigation?.update?.()
          swiper.pagination?.render?.()
          swiper.pagination?.update?.()
          if (isLoop) {
            ;(swiper as any)?.loopCreate?.()
            swiper?.update?.()
          }
        }
      }
    },
    // Destroy this directive
    unbind(element, binding, vnode) {
      const autoDestroy = getStandardisedOptionByAttrs(
        vnode,
        ComponentPropNames.AutoDestroy
      )
      if (getBooleanValueByInput(autoDestroy)) {
        const swiper = getSwiperInstance(element, binding, vnode)
        if (swiper && (swiper as any).initialized) {
          swiper?.destroy?.(
            getBooleanValueByInput(
              getStandardisedOptionByAttrs(
                vnode,
                ComponentPropNames.DeleteInstanceOnDestroy
              )
            ),
            getBooleanValueByInput(
              getStandardisedOptionByAttrs(
                vnode,
                ComponentPropNames.CleanupStylesOnDestroy
              )
            )
          )
        }
      }
    }
  }
}
