import type { Directive } from 'vue'
import './index.less'

export interface RippleOptions {
    event: string
    transition: number
}

export interface RippleProto {
    background?: string
    zIndex?: string
}

export type EventType = Event & MouseEvent & TouchEvent

const options: RippleOptions = {
    event: 'mousedown',
    transition: 400,
}

const RippleDirective: Directive & RippleProto = {
    beforeMount: (el: HTMLElement, binding) => {
        if (binding.value === false) return

        const bg = el.getAttribute('ripple-background')
        setProps(Object.keys(binding.modifiers), options)

        const background = bg || RippleDirective.background
        const zIndex = RippleDirective.zIndex

        el.addEventListener(options.event, (event: EventType) => {
            rippler({
                event,
                el,
                background,
                zIndex,
            })
        })
    },
    updated(el, binding) {
        if (!binding.value) {
            el?.clearRipple?.()
            return
        }
        const bg = el.getAttribute('ripple-background')
        el?.setBackground?.(bg)
    },
}

function rippler({ event, el, zIndex, background }: { event: EventType; el: HTMLElement } & RippleProto) {
    const targetBorder = Number.parseInt(getComputedStyle(el).borderWidth.replace('px', ''))
    const clientX = event.clientX || event.touches[0].clientX
    const clientY = event.clientY || event.touches[0].clientY

    const rect = el.getBoundingClientRect()
    const { left, top } = rect
    const { offsetWidth: width, offsetHeight: height } = el
    const { transition } = options
    const dx = clientX - left
    const dy = clientY - top
    const maxX = Math.max(dx, width - dx)
    const maxY = Math.max(dy, height - dy)
    const style = window.getComputedStyle(el)
    const radius = Math.sqrt(maxX * maxX + maxY * maxY)
    const border = targetBorder > 0 ? targetBorder : 0

    const ripple = document.createElement('div')
    const rippleContainer = document.createElement('div')

    // Styles for ripple
    ripple.className = 'ripple'

    Object.assign(ripple.style ?? {}, {
        marginTop: '0px',
        marginLeft: '0px',
        width: '1px',
        height: '1px',
        transition: `all ${transition}ms cubic-bezier(0.4, 0, 0.2, 1)`,
        borderRadius: '50%',
        pointerEvents: 'none',
        position: 'relative',
        zIndex: zIndex ?? '9999',
        backgroundColor: background ?? 'rgba(0, 0, 0, 0.12)',
    })

    // Styles for rippleContainer
    rippleContainer.className = 'ripple-container'
    Object.assign(rippleContainer.style ?? {}, {
        position: 'absolute',
        left: `${0 - border}px`,
        top: `${0 - border}px`,
        height: '0',
        width: '0',
        pointerEvents: 'none',
        overflow: 'hidden',
    })

    const storedTargetPosition = el.style.position.length > 0 ? el.style.position : getComputedStyle(el).position

    if (storedTargetPosition !== 'relative') {
        el.style.position = 'relative'
    }

    rippleContainer.appendChild(ripple)
    el.appendChild(rippleContainer)

    Object.assign(ripple.style, {
        marginTop: `${dy}px`,
        marginLeft: `${dx}px`,
    })

    const { borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius } = style
    Object.assign(rippleContainer.style, {
        width: `${width}px`,
        height: `${height}px`,
        direction: 'ltr',
        borderTopLeftRadius,
        borderTopRightRadius,
        borderBottomLeftRadius,
        borderBottomRightRadius,
    })

    setTimeout(() => {
        const wh = `${radius * 2}px`
        Object.assign(ripple.style ?? {}, {
            width: wh,
            height: wh,
            marginLeft: `${dx - radius}px`,
            marginTop: `${dy - radius}px`,
        })
    }, 0)

    function clearRipple() {
        setTimeout(() => {
            ripple.style.backgroundColor = 'rgba(0, 0, 0, 0)'
        }, 250)

        setTimeout(() => {
            rippleContainer?.parentNode?.removeChild(rippleContainer)
        }, 850)
        el.removeEventListener('mouseup', clearRipple, false)
        el.removeEventListener('mouseleave', clearRipple, false)
        el.removeEventListener('dragstart', clearRipple, false)
        setTimeout(() => {
            let clearPosition = true
            for (let i = 0; i < el.childNodes.length; i++) {
                if ((el.childNodes[i] as Recordable).className === 'ripple-container') {
                    clearPosition = false
                }
            }

            if (clearPosition) {
                el.style.position = storedTargetPosition !== 'static' ? storedTargetPosition : ''
            }
        }, options.transition + 260)
    }

    if (event.type === 'mousedown') {
        el.addEventListener('mouseup', clearRipple, false)
        el.addEventListener('mouseleave', clearRipple, false)
        el.addEventListener('dragstart', clearRipple, false)
    } else {
        clearRipple()
    }

    ;(el as Recordable).setBackground = (bgColor: string) => {
        if (!bgColor) {
            return
        }
        ripple.style.backgroundColor = bgColor
    }
}

function setProps(modifiers: Recordable, props: Recordable) {
    modifiers.forEach((item: Recordable) => {
        if (Number.isNaN(Number(item))) props.event = item
        else props.transition = item
    })
}

export default RippleDirective
