// Styles
import './VNavigationDrawer.sass'

// Components
import VImg, { srcObject } from '../VImg/VImg'

// Mixins
import Applicationable from '../../mixins/applicationable'
import Colorable from '../../mixins/colorable'
import Dependent from '../../mixins/dependent'
import Mobile from '../../mixins/mobile'
import Overlayable from '../../mixins/overlayable'
import SSRBootable from '../../mixins/ssr-bootable'
import Themeable from '../../mixins/themeable'

// Directives
import ClickOutside from '../../directives/click-outside'
import Resize from '../../directives/resize'
import Touch from '../../directives/touch'

// Utilities
import { convertToUnit, getSlot } from '../../util/helpers'
import mixins from '../../util/mixins'

// Types
import { VNode, VNodeDirective, PropType } from 'vue'
import { TouchWrapper } from 'vuetify/types'

const baseMixins = mixins(
  Applicationable('left', [
    'isActive',
    'isMobile',
    'miniVariant',
    'expandOnHover',
    'permanent',
    'right',
    'temporary',
    'width',
  ]),
  Colorable,
  Dependent,
  Mobile,
  Overlayable,
  SSRBootable,
  Themeable
)

/* @vue/component */
export default baseMixins.extend({
  name: 'v-navigation-drawer',

  directives: {
    ClickOutside,
    Resize,
    Touch,
  },

  provide (): object {
    return {
      isInNav: this.tag === 'nav',
    }
  },

  props: {
    bottom: Boolean,
    clipped: Boolean,
    disableResizeWatcher: Boolean,
    disableRouteWatcher: Boolean,
    expandOnHover: Boolean,
    floating: Boolean,
    height: {
      type: [Number, String],
      default (): string {
        return this.app ? '100vh' : '100%'
      },
    },
    miniVariant: Boolean,
    miniVariantWidth: {
      type: [Number, String],
      default: 56,
    },
    permanent: Boolean,
    right: Boolean,
    src: {
      type: [String, Object] as PropType<string | srcObject>,
      default: '',
    },
    stateless: Boolean,
    tag: {
      type: String,
      default (): string {
        return this.app ? 'nav' : 'aside'
      },
    },
    temporary: Boolean,
    touchless: Boolean,
    width: {
      type: [Number, String],
      default: 256,
    },
    value: null as unknown as PropType<any>,
  },

  data: () => ({
    isMouseover: false,
    touchArea: {
      left: 0,
      right: 0,
    },
    stackMinZIndex: 6,
  }),

  computed: {
    /**
     * Used for setting an app value from a dynamic
     * property. Called from applicationable.js
     */
    applicationProperty (): string {
      return this.right ? 'right' : 'left'
    },
    classes (): object {
      return {
        'v-navigation-drawer': true,
        'v-navigation-drawer--absolute': this.absolute,
        'v-navigation-drawer--bottom': this.bottom,
        'v-navigation-drawer--clipped': this.clipped,
        'v-navigation-drawer--close': !this.isActive,
        'v-navigation-drawer--fixed': !this.absolute && (this.app || this.fixed),
        'v-navigation-drawer--floating': this.floating,
        'v-navigation-drawer--is-mobile': this.isMobile,
        'v-navigation-drawer--is-mouseover': this.isMouseover,
        'v-navigation-drawer--mini-variant': this.isMiniVariant,
        'v-navigation-drawer--custom-mini-variant': Number(this.miniVariantWidth) !== 56,
        'v-navigation-drawer--open': this.isActive,
        'v-navigation-drawer--open-on-hover': this.expandOnHover,
        'v-navigation-drawer--right': this.right,
        'v-navigation-drawer--temporary': this.temporary,
        ...this.themeClasses,
      }
    },
    computedMaxHeight (): number | null {
      if (!this.hasApp) return null

      const computedMaxHeight = (
        this.$vuetify.application.bottom +
        this.$vuetify.application.footer +
        this.$vuetify.application.bar
      )

      if (!this.clipped) return computedMaxHeight

      return computedMaxHeight + this.$vuetify.application.top
    },
    computedTop (): number {
      if (!this.hasApp) return 0

      let computedTop = this.$vuetify.application.bar

      computedTop += this.clipped
        ? this.$vuetify.application.top
        : 0

      return computedTop
    },
    computedTransform (): number {
      if (this.isActive) return 0
      if (this.isBottom) return 100
      return this.right ? 100 : -100
    },
    computedWidth (): string | number {
      return this.isMiniVariant ? this.miniVariantWidth : this.width
    },
    hasApp (): boolean {
      return (
        this.app &&
        (!this.isMobile && !this.temporary)
      )
    },
    isBottom (): boolean {
      return this.bottom && this.isMobile
    },
    isMiniVariant (): boolean {
      return (
        !this.expandOnHover &&
        this.miniVariant
      ) || (
        this.expandOnHover &&
        !this.isMouseover
      )
    },
    isMobile (): boolean {
      return (
        !this.stateless &&
        !this.permanent &&
        Mobile.options.computed.isMobile.call(this)
      )
    },
    reactsToClick (): boolean {
      return (
        !this.stateless &&
        !this.permanent &&
        (this.isMobile || this.temporary)
      )
    },
    reactsToMobile (): boolean {
      return (
        this.app &&
        !this.disableResizeWatcher &&
        !this.permanent &&
        !this.stateless &&
        !this.temporary
      )
    },
    reactsToResize (): boolean {
      return !this.disableResizeWatcher && !this.stateless
    },
    reactsToRoute (): boolean {
      return (
        !this.disableRouteWatcher &&
        !this.stateless &&
        (this.temporary || this.isMobile)
      )
    },
    showOverlay (): boolean {
      return (
        !this.hideOverlay &&
        this.isActive &&
        (this.isMobile || this.temporary)
      )
    },
    styles (): object {
      const translate = this.isBottom ? 'translateY' : 'translateX'
      return {
        height: convertToUnit(this.height),
        top: !this.isBottom ? convertToUnit(this.computedTop) : 'auto',
        maxHeight: this.computedMaxHeight != null
          ? `calc(100% - ${convertToUnit(this.computedMaxHeight)})`
          : undefined,
        transform: `${translate}(${convertToUnit(this.computedTransform, '%')})`,
        width: convertToUnit(this.computedWidth),
      }
    },
  },

  watch: {
    $route: 'onRouteChange',
    isActive (val) {
      this.$emit('input', val)
    },
    /**
     * When mobile changes, adjust the active state
     * only when there has been a previous value
     */
    isMobile (val, prev) {
      !val &&
        this.isActive &&
        !this.temporary &&
        this.removeOverlay()

      if (prev == null ||
        !this.reactsToResize ||
        !this.reactsToMobile
      ) return

      this.isActive = !val
    },
    permanent (val) {
      // If enabling prop enable the drawer
      if (val) this.isActive = true
    },
    showOverlay (val) {
      if (val) this.genOverlay()
      else this.removeOverlay()
    },
    value (val) {
      if (this.permanent) return

      if (val == null) {
        this.init()
        return
      }

      if (val !== this.isActive) this.isActive = val
    },
    expandOnHover: 'updateMiniVariant',
    isMouseover (val) {
      this.updateMiniVariant(!val)
    },
  },

  beforeMount () {
    this.init()
  },

  methods: {
    calculateTouchArea () {
      const parent = this.$el.parentNode as Element

      if (!parent) return

      const parentRect = parent.getBoundingClientRect()

      this.touchArea = {
        left: parentRect.left + 50,
        right: parentRect.right - 50,
      }
    },
    closeConditional () {
      return this.isActive && !this._isDestroyed && this.reactsToClick
    },
    genAppend () {
      return this.genPosition('append')
    },
    genBackground () {
      const props = {
        height: '100%',
        width: '100%',
        src: this.src,
      }

      const image = this.$scopedSlots.img
        ? this.$scopedSlots.img(props)
        : this.$createElement(VImg, { props })

      return this.$createElement('div', {
        staticClass: 'v-navigation-drawer__image',
      }, [image])
    },
    genDirectives (): VNodeDirective[] {
      const directives = [{
        name: 'click-outside',
        value: {
          handler: () => { this.isActive = false },
          closeConditional: this.closeConditional,
          include: this.getOpenDependentElements,
        },
      }]

      if (!this.touchless && !this.stateless) {
        directives.push({
          name: 'touch',
          value: {
            parent: true,
            left: this.swipeLeft,
            right: this.swipeRight,
          },
        } as any)
      }

      return directives
    },
    genListeners () {
      const on: Record<string, (e: Event) => void> = {
        transitionend: (e: Event) => {
          if (e.target !== e.currentTarget) return
          this.$emit('transitionend', e)

          // IE11 does not support new Event('resize')
          const resizeEvent = document.createEvent('UIEvents')
          resizeEvent.initUIEvent('resize', true, false, window, 0)
          window.dispatchEvent(resizeEvent)
        },
      }

      if (this.miniVariant) {
        on.click = () => this.$emit('update:mini-variant', false)
      }

      if (this.expandOnHover) {
        on.mouseenter = () => (this.isMouseover = true)
        on.mouseleave = () => (this.isMouseover = false)
      }

      return on
    },
    genPosition (name: 'prepend' | 'append') {
      const slot = getSlot(this, name)

      if (!slot) return slot

      return this.$createElement('div', {
        staticClass: `v-navigation-drawer__${name}`,
      }, slot)
    },
    genPrepend () {
      return this.genPosition('prepend')
    },
    genContent () {
      return this.$createElement('div', {
        staticClass: 'v-navigation-drawer__content',
      }, this.$slots.default)
    },
    genBorder () {
      return this.$createElement('div', {
        staticClass: 'v-navigation-drawer__border',
      })
    },
    init () {
      if (this.permanent) {
        this.isActive = true
      } else if (this.stateless ||
        this.value != null
      ) {
        this.isActive = this.value
      } else if (!this.temporary) {
        this.isActive = !this.isMobile
      }
    },
    onRouteChange () {
      if (this.reactsToRoute && this.closeConditional()) {
        this.isActive = false
      }
    },
    swipeLeft (e: TouchWrapper) {
      if (this.isActive && this.right) return
      this.calculateTouchArea()

      if (Math.abs(e.touchendX - e.touchstartX) < 100) return
      if (this.right &&
        e.touchstartX >= this.touchArea.right
      ) this.isActive = true
      else if (!this.right && this.isActive) this.isActive = false
    },
    swipeRight (e: TouchWrapper) {
      if (this.isActive && !this.right) return
      this.calculateTouchArea()

      if (Math.abs(e.touchendX - e.touchstartX) < 100) return
      if (!this.right &&
        e.touchstartX <= this.touchArea.left
      ) this.isActive = true
      else if (this.right && this.isActive) this.isActive = false
    },
    /**
     * Update the application layout
     */
    updateApplication () {
      if (
        !this.isActive ||
        this.isMobile ||
        this.temporary ||
        !this.$el
      ) return 0

      const width = Number(this.computedWidth)

      return isNaN(width) ? this.$el.clientWidth : width
    },
    updateMiniVariant (val: boolean) {
      if (this.miniVariant !== val) this.$emit('update:mini-variant', val)
    },
  },

  render (h): VNode {
    const children = [
      this.genPrepend(),
      this.genContent(),
      this.genAppend(),
      this.genBorder(),
    ]

    if (this.src || getSlot(this, 'img')) children.unshift(this.genBackground())

    return h(this.tag, this.setBackgroundColor(this.color, {
      class: this.classes,
      style: this.styles,
      directives: this.genDirectives(),
      on: this.genListeners(),
    }), children)
  },
})
