import {
  h,
  ref,
  computed,
  withDirectives,
  onBeforeUnmount,
  onBeforeUpdate,
  getCurrentInstance
} from 'vue'

import TouchPan from '../../directives/touch-pan/TouchPan.js'

import useDark, {
  useDarkProps
} from '../../composables/private.use-dark/use-dark.js'
import useRenderCache from '../../composables/use-render-cache/use-render-cache.js'

import { createComponent } from '../../utils/private.create/create.js'
import { hSlot } from '../../utils/private.render/render.js'

const slotsDef = [
  ['left', 'center', 'start', 'width'],
  ['right', 'center', 'end', 'width'],
  ['top', 'start', 'center', 'height'],
  ['bottom', 'end', 'center', 'height']
]

export default createComponent({
  name: 'QSlideItem',

  props: {
    ...useDarkProps,

    leftColor: String,
    rightColor: String,
    topColor: String,
    bottomColor: String,

    onSlide: Function
  },

  emits: ['action', 'top', 'right', 'bottom', 'left'],

  setup(props, { slots, emit }) {
    const { proxy } = getCurrentInstance()
    const { $q } = proxy

    const isDark = useDark(props, $q)
    const { getCache } = useRenderCache()

    const contentRef = ref(null)

    let timer = null,
      pan = {},
      dirRefs = {},
      dirContentRefs = {}

    const langDir = computed(() =>
      $q.lang.rtl === true
        ? { left: 'right', right: 'left' }
        : { left: 'left', right: 'right' }
    )

    const classes = computed(
      () =>
        'q-slide-item q-item-type overflow-hidden' +
        (isDark.value === true ? ' q-slide-item--dark q-dark' : '')
    )

    function reset() {
      contentRef.value.style.transform = 'translate(0,0)'
    }

    function emitSlide(side, ratio, isReset) {
      if (props.onSlide !== void 0) emit('slide', { side, ratio, isReset })
    }

    function onPan(evt) {
      const node = contentRef.value

      if (evt.isFirst) {
        pan = {
          dir: null,
          size: { left: 0, right: 0, top: 0, bottom: 0 },
          scale: 0
        }

        node.classList.add('no-transition')

        slotsDef.forEach(slotName => {
          if (slots[slotName[0]] !== void 0) {
            const localNode = dirContentRefs[slotName[0]]
            localNode.style.transform = 'scale(1)'
            pan.size[slotName[0]] =
              localNode.getBoundingClientRect()[slotName[3]]
          }
        })

        pan.axis =
          evt.direction === 'up' || evt.direction === 'down' ? 'Y' : 'X'
      } else if (evt.isFinal) {
        node.classList.remove('no-transition')

        if (pan.scale === 1) {
          node.style.transform = `translate${pan.axis}(${pan.dir * 100}%)`

          if (timer !== null) clearTimeout(timer)
          timer = setTimeout(() => {
            timer = null
            emit(pan.showing, { reset })
            emit('action', { side: pan.showing, reset })
          }, 230)
        } else {
          node.style.transform = 'translate(0,0)'
          emitSlide(pan.showing, 0, true)
        }

        return
      } else {
        evt.direction =
          pan.axis === 'X'
            ? evt.offset.x < 0
              ? 'left'
              : 'right'
            : evt.offset.y < 0
              ? 'up'
              : 'down'
      }

      if (
        (slots.left === void 0 && evt.direction === langDir.value.right) ||
        (slots.right === void 0 && evt.direction === langDir.value.left) ||
        (slots.top === void 0 && evt.direction === 'down') ||
        (slots.bottom === void 0 && evt.direction === 'up')
      ) {
        node.style.transform = 'translate(0,0)'
        return
      }

      let showing, dir, dist

      if (pan.axis === 'X') {
        dir = evt.direction === 'left' ? -1 : 1
        showing = dir === 1 ? langDir.value.left : langDir.value.right
        dist = evt.distance.x
      } else {
        dir = evt.direction === 'up' ? -2 : 2
        showing = dir === 2 ? 'top' : 'bottom'
        dist = evt.distance.y
      }

      if (pan.dir !== null && Math.abs(dir) !== Math.abs(pan.dir)) return

      if (pan.dir !== dir) {
        ;['left', 'right', 'top', 'bottom'].forEach(d => {
          if (dirRefs[d]) {
            dirRefs[d].style.visibility = showing === d ? 'visible' : 'hidden'
          }
        })
        pan.showing = showing
        pan.dir = dir
      }

      pan.scale = Math.max(0, Math.min(1, (dist - 40) / pan.size[showing]))

      node.style.transform = `translate${pan.axis}(${(dist * dir) / Math.abs(dir)}px)`
      dirContentRefs[showing].style.transform = `scale(${pan.scale})`

      emitSlide(showing, pan.scale, false)
    }

    onBeforeUpdate(() => {
      dirRefs = {}
      dirContentRefs = {}
    })

    onBeforeUnmount(() => {
      if (timer !== null) clearTimeout(timer)
    })

    // expose public methods
    Object.assign(proxy, { reset })

    return () => {
      const content = [],
        slotsList = {
          left: slots[langDir.value.right] !== void 0,
          right: slots[langDir.value.left] !== void 0,
          up: slots.bottom !== void 0,
          down: slots.top !== void 0
        },
        dirs = Object.keys(slotsList).filter(key => slotsList[key] === true)

      slotsDef.forEach(slotName => {
        const dir = slotName[0]

        if (slots[dir] !== void 0) {
          content.push(
            h(
              'div',
              {
                key: dir,
                ref: el => {
                  dirRefs[dir] = el
                },
                class:
                  `q-slide-item__${dir} absolute-full row no-wrap items-${slotName[1]} justify-${slotName[2]}` +
                  (props[dir + 'Color'] !== void 0
                    ? ` bg-${props[dir + 'Color']}`
                    : '')
              },
              [
                h(
                  'div',
                  {
                    ref: el => {
                      dirContentRefs[dir] = el
                    }
                  },
                  slots[dir]()
                )
              ]
            )
          )
        }
      })

      const node = h(
        'div',
        {
          key: `${dirs.length === 0 ? 'only-' : ''} content`,
          ref: contentRef,
          class: 'q-slide-item__content'
        },
        hSlot(slots.default)
      )

      if (dirs.length === 0) {
        content.push(node)
      } else {
        content.push(
          withDirectives(
            node,
            getCache('dir#' + dirs.join(''), () => {
              const modifiers = {
                prevent: true,
                stop: true,
                mouse: true
              }

              dirs.forEach(dir => {
                modifiers[dir] = true
              })

              return [[TouchPan, onPan, void 0, modifiers]]
            })
          )
        )
      }

      return h('div', { class: classes.value }, content)
    }
  }
})
