/**
 * <v-clicks/> click animations component
 *
 * Learn more: https://sli.dev/guide/animations.html#click-animation
 */

import type { VNode, VNodeArrayChildren } from 'vue'
import { toArray } from '@antfu/utils'
import { Comment, createVNode, defineComponent, h, isVNode, resolveDirective, withDirectives } from 'vue'
import { normalizeSingleAtValue } from '../composables/useClicks'
import VClickGap from './VClickGap.vue'

const listTags = ['ul', 'ol']
const RE_WHITESPACE_OR_COMMA = /[\s,]+/

export default defineComponent({
  props: {
    depth: {
      type: [Number, String],
      default: 1,
    },
    every: {
      type: [Number, String],
      default: 1,
    },
    at: {
      type: [Number, String],
      default: '+1',
    },
    hide: {
      type: Boolean,
      default: false,
    },
    /**
     * @deprecated use `animation` instead
     */
    fade: {
      type: Boolean,
      default: false,
    },
    animation: {
      type: [String, Array<string>],
      default: undefined,
    },
    handleSpecialElements: {
      type: Boolean,
      default: true,
    },
  },
  render() {
    const every = +this.every
    const at = normalizeSingleAtValue(this.at)
    const isRelative = typeof at === 'string'

    let elements = this.$slots.default?.()

    if (at == null || !elements) {
      return elements
    }

    const click = resolveDirective('click')!

    const applyDirective = (node: VNode, value: number | string) => {
      const modifiers: Record<string, boolean> = {
        hide: this.hide,
      }
      if (typeof this.animation === 'string') {
        this.animation.split(RE_WHITESPACE_OR_COMMA).forEach((a) => {
          if (a)
            modifiers[a] = true
        })
      }
      else if (Array.isArray(this.animation)) {
        this.animation.forEach(a => modifiers[a] = true)
      }
      if (this.fade)
        modifiers.fade = true

      return withDirectives(node, [[
        click,
        value,
        '',
        modifiers,
      ]])
    }

    const openAllTopLevelSlots = <T extends VNodeArrayChildren | VNode[]>(children: T): T => {
      return children.flatMap((i) => {
        if (isVNode(i) && typeof i.type === 'symbol' && Array.isArray(i.children))
          return openAllTopLevelSlots(i.children)
        else
          return [i]
      }) as T
    }

    elements = openAllTopLevelSlots(toArray(elements))

    const mapSubList = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
      const vNodes = openAllTopLevelSlots(children).map((i) => {
        if (!isVNode(i))
          return i
        if (listTags.includes(i.type as string) && Array.isArray(i.children)) {
          // eslint-disable-next-line ts/no-use-before-define
          const vNodes = mapChildren(i.children, depth + 1)
          return h(i, {}, vNodes)
        }
        return h(i)
      })
      return vNodes
    }

    let globalIdx = 1
    let execIdx = 0
    const mapChildren = (children: VNodeArrayChildren, depth = 1): VNodeArrayChildren => {
      const vNodes = openAllTopLevelSlots(children).map((i) => {
        if (!isVNode(i) || i.type === Comment)
          return i

        const thisShowIdx = +at + Math.ceil(globalIdx++ / every) - 1

        let vNode
        if (depth < +this.depth && Array.isArray(i.children))
          vNode = h(i, {}, mapSubList(i.children, depth))
        else
          vNode = h(i)

        const delta = thisShowIdx - execIdx
        execIdx = thisShowIdx

        return applyDirective(
          vNode,
          isRelative
            ? delta >= 0 ? `+${delta}` : `${delta}`
            : thisShowIdx,
        )
      })
      return vNodes
    }

    const lastGap = () => createVNode(VClickGap, {
      size: +at + Math.ceil((globalIdx - 1) / every) - 1 - execIdx,
    })

    if (this.handleSpecialElements) {
      // handle ul, ol list
      if (elements.length === 1 && listTags.includes(elements[0].type as string) && Array.isArray(elements[0].children))
        return h(elements[0], {}, [...mapChildren(elements[0].children), lastGap()])

      if (elements.length === 1 && elements[0].type as string === 'table') {
        const tableNode = elements[0]
        if (Array.isArray(tableNode.children)) {
          return h(tableNode, {}, tableNode.children.map((i) => {
            if (!isVNode(i))
              return i
            else if (i.type === 'tbody' && Array.isArray(i.children))
              return h(i, {}, [...mapChildren(i.children), lastGap()])
            else
              return h(i)
          }))
        }
      }
    }

    return [...mapChildren(elements), lastGap()]
  },
})
