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