import classnames from 'classnames'
import Vue from 'vue'
import ref from 'vue-ref'
import BaseMixin from '../../_util/BaseMixin'
import { initDefaultProps, getEvents, getClass } from '../../_util/props-util'
import { cloneElement } from '../../_util/vnode'
import ContainerRender from '../../_util/ContainerRender'
import getScrollBarSize from '../../_util/getScrollBarSize'
import drawerProps from './drawerProps'
import {
  dataToArray,
  transitionEnd,
  transitionStr,
  addEventListener,
  removeEventListener,
  transformArguments,
  isNumeric,
} from './utils'

function noop () {}

const currentDrawer = {}
const windowIsUndefined = !(
  typeof window !== 'undefined' &&
  window.document &&
  window.document.createElement
)

Vue.use(ref, { name: 'ant-ref' })
const Drawer = {
  mixins: [BaseMixin],
  props: initDefaultProps(drawerProps, {
    prefixCls: 'drawer',
    placement: 'left',
    getContainer: 'body',
    level: 'all',
    duration: '.3s',
    ease: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
    firstEnter: false, // 记录首次进入.
    showMask: true,
    handler: true,
    maskStyle: {},
    wrapperClassName: '',
  }),
  data () {
    this.levelDom = []
    this.contentDom = null
    this.maskDom = null
    this.handlerdom = null
    this.mousePos = null
    this.sFirstEnter = this.firstEnter
    this.timeout = null
    this.children = null
    this.drawerId = Number(
      (Date.now() + Math.random()).toString().replace('.', Math.round(Math.random() * 9)),
    ).toString(16)
    const open = this.open !== undefined ? this.open : !!this.defaultOpen
    currentDrawer[this.drawerId] = open
    this.orignalOpen = this.open
    this.preProps = { ...this.$props }
    return {
      sOpen: open,
    }
  },
  mounted () {
    this.$nextTick(() => {
      if (!windowIsUndefined) {
        let passiveSupported = false
        window.addEventListener(
          'test',
          null,
          Object.defineProperty({}, 'passive', {
            get: () => {
              passiveSupported = true
              return null
            },
          })
        )
        this.passive = passiveSupported ? { passive: false } : false
      }
      const open = this.getOpen()
      if (this.handler || open || this.sFirstEnter) {
        this.getDefault(this.$props)
        if (open) {
          this.isOpenChange = true
        }
        this.$forceUpdate()
      }
    })
  },
  watch: {
    open (val) {
      if (val !== undefined && val !== this.preProps.open) {
        this.isOpenChange = true
        // 没渲染 dom 时,获取默认数据;
        if (!this.container) {
          this.getDefault(this.$props)
        }
        this.setState({
          sOpen: open,
        })
      }
      this.preProps.open = val
    },
    placement (val) {
      if (val !== this.preProps.placement) {
        // test 的 bug, 有动画过场,删除 dom
        this.contentDom = null
      }
      this.preProps.placement = val
    },
    level (val) {
      if (this.preProps.level !== val) {
        this.getParentAndLevelDom(this.$props)
      }
      this.preProps.level = val
    },
  },
  updated () {
    this.$nextTick(() => {
      // dom 没渲染时,重走一遍。
      if (!this.sFirstEnter && this.container) {
        this.$forceUpdate()
        this.sFirstEnter = true
      }
    })
  },
  beforeDestroy () {
    delete currentDrawer[this.drawerId]
    delete this.isOpenChange
    if (this.container) {
      if (this.sOpen) {
        this.setLevelDomTransform(false, true)
      }
      document.body.style.overflow = ''
      // 拦不住。。直接删除;
      if (this.getSelfContainer) {
        this.container.parentNode.removeChild(this.container)
      }
    }
    this.sFirstEnter = false
    clearTimeout(this.timeout)
    // 需要 didmount 后也会渲染,直接 unmount 将不会渲染,加上判断.
    if (this.renderComponent) {
      this.renderComponent({
        afterClose: this.removeContainer,
        onClose () { },
        visible: false,
      })
    }
  },
  methods: {
    onMaskTouchEnd (e) {
      this.$emit('maskClick', e)
      this.onTouchEnd(e, true)
    },
    onIconTouchEnd (e) {
      this.$emit('handleClick', e)
      this.onTouchEnd(e)
    },
    onTouchEnd (e, close) {
      if (this.open !== undefined) {
        return
      }
      const open = close || this.sOpen
      this.isOpenChange = true
      this.setState({
        sOpen: !open,
      })
    },
    onWrapperTransitionEnd (e) {
      if (e.target === this.contentWrapper) {
        this.dom.style.transition = ''
        if (!this.sOpen && this.getCurrentDrawerSome()) {
          document.body.style.overflowX = ''
          if (this.maskDom) {
            this.maskDom.style.left = ''
            this.maskDom.style.width = ''
          }
        }
      }
    },
    getDefault (props) {
      this.getParentAndLevelDom(props)
      if (props.getContainer || props.parent) {
        this.container = this.defaultGetContainer()
      }
    },
    getCurrentDrawerSome () {
      return !Object.keys(currentDrawer).some(key => currentDrawer[key])
    },
    getSelfContainer () {
      return this.container
    },
    getParentAndLevelDom (props) {
      if (windowIsUndefined) {
        return
      }
      const { level, getContainer } = props
      this.levelDom = []
      if (getContainer) {
        if (typeof getContainer === 'string') {
          const dom = document.querySelectorAll(getContainer)[0]
          this.parent = dom
        }
        if (typeof getContainer === 'function') {
          this.parent = getContainer()
        }
        if (typeof getContainer === 'object' && getContainer instanceof window.HTMLElement) {
          this.parent = getContainer
        }
      }
      if (!getContainer && this.container) {
        this.parent = this.container.parentNode
      }
      if (level === 'all') {
        const children = Array.prototype.slice.call(this.parent.children)
        children.forEach(child => {
          if (child.nodeName !== 'SCRIPT' &&
            child.nodeName !== 'STYLE' &&
            child.nodeName !== 'LINK' &&
            child !== this.container
          ) {
            this.levelDom.push(child)
          }
        })
      } else if (level) {
        dataToArray(level).forEach(key => {
          document.querySelectorAll(key).forEach(item => {
            this.levelDom.push(item)
          })
        })
      }
    },
    setLevelDomTransform (open, openTransition, placementName, value) {
      const { placement, levelMove, duration, ease, getContainer } = this.$props
      if (!windowIsUndefined) {
        this.levelDom.forEach(dom => {
          if (this.isOpenChange || openTransition) {
            /* eslint no-param-reassign: "error" */
            dom.style.transition = `transform ${duration} ${ease}`
            addEventListener(dom, transitionEnd, this.trnasitionEnd)
            let levelValue = open ? value : 0
            if (levelMove) {
              const $levelMove = transformArguments(levelMove, { target: dom, open })
              levelValue = open ? $levelMove[0] : $levelMove[1] || 0
            }
            const $value = typeof levelValue === 'number' ? `${levelValue}px` : levelValue
            const placementPos = placement === 'left' || placement === 'top' ? $value : `-${$value}`
            dom.style.transform = levelValue ? `${placementName}(${placementPos})` : ''
            dom.style.msTransform = levelValue ? `${placementName}(${placementPos})` : ''
          }
        })
        // 处理 body 滚动
        if (getContainer === 'body') {
          const eventArray = ['touchstart']
          const domArray = [document.body, this.maskDom, this.handlerdom, this.contentDom]
          const right =
           document.body.scrollHeight >
            (window.innerHeight || document.documentElement.clientHeight) &&
          window.innerWidth > document.body.offsetWidth
             ? getScrollBarSize(1)
             : 0
          let widthTransition = `width ${duration} ${ease}`
          const trannsformTransition = `transform ${duration} ${ease}`
          if (open && document.body.style.overflow !== 'hidden') {
            document.body.style.overflow = 'hidden'
            if (right) {
              document.body.style.position = 'relative'
              document.body.style.width = `calc(100% - ${right}px)`
              this.dom.style.transition = 'none'
              switch (placement) {
                case 'right':
                  this.dom.style.transform = `translateX(-${right}px)`
                  this.dom.style.msTransform = `translateX(-${right}px)`
                  break
                case 'top':
                case 'bottom':
                  this.dom.style.width = `calc(100% - ${right}px)`
                  this.dom.style.transform = 'translateZ(0)'
                  break
                default:
                  break
              }
              clearTimeout(this.timeout)
              this.timeout = setTimeout(() => {
                this.dom.style.transition = `${trannsformTransition},${widthTransition}`
                this.dom.style.width = ''
                this.dom.style.transform = ''
                this.dom.style.msTransform = ''
              })
            }
            // 手机禁滚
            domArray.forEach((item, i) => {
              if (!item) {
                return
              }
              addEventListener(
                item,
                eventArray[i] || 'touchmove',
                i ? this.removeMoveHandler : this.removeStartHandler,
                this.passive
              )
            })
          } else if (this.getCurrentDrawerSome()) {
            document.body.style.overflow = ''
            if ((this.isOpenChange || openTransition) && right) {
              document.body.style.position = ''
              document.body.style.width = ''
              if (transitionStr) {
                document.body.style.overflowX = 'hidden'
              }
              this.dom.style.transition = 'none'
              let heightTransition
              switch (placement) {
                case 'right': {
                  this.dom.style.transform = `translateX(${right}px)`
                  this.dom.style.msTransform = `translateX(${right}px)`
                  this.dom.style.width = '100%'
                  widthTransition = `width 0s ${ease} ${duration}`
                  if (this.maskDom) {
                    this.maskDom.style.left = `-${right}px`
                    this.maskDom.style.width = `calc(100% + ${right}px)`
                  }
                  break
                }
                case 'top':
                case 'bottom': {
                  this.dom.style.width = `calc(100% + ${right}px)`
                  this.dom.style.height = '100%'
                  this.dom.style.transform = 'translateZ(0)'
                  heightTransition = `height 0s ${ease} ${duration}`
                  break
                }
                default:
                  break
              }
              clearTimeout(this.timeout)
              this.timeout = setTimeout(() => {
                this.dom.style.transition = `${trannsformTransition},${
                  heightTransition ? `${heightTransition},` : ''}${widthTransition}`
                this.dom.style.transform = ''
                this.dom.style.msTransform = ''
                this.dom.style.width = ''
                this.dom.style.height = ''
              })
            }
            domArray.forEach((item, i) => {
              if (!item) {
                return
              }
              removeEventListener(
                item,
                eventArray[i] || 'touchmove',
                i ? this.removeMoveHandler : this.removeStartHandler,
                this.passive
              )
            })
          }
        }
      }
      const { change } = this.$listeners
      if (change && this.isOpenChange && this.sFirstEnter) {
        change(open)
        this.isOpenChange = false
      }
    },
    getChildToRender (open) {
      const {
        prefixCls,
        placement,
        handler,
        showMask,
        maskStyle,
        width,
        height,
      } = this.$props
      const children = this.$slots.default
      const wrapperClassname = classnames(prefixCls, {
        [`${prefixCls}-${placement}`]: true,
        [`${prefixCls}-open`]: open,
        ...getClass(this),
      })
      const isOpenChange = this.isOpenChange
      const isHorizontal = placement === 'left' || placement === 'right'
      const placementName = `translate${isHorizontal ? 'X' : 'Y'}`
      // 百分比与像素动画不同步,第一次打用后全用像素动画。
      // const defaultValue = !this.contentDom || !level ? '100%' : `${value}px`;
      const placementPos = placement === 'left' || placement === 'top' ? '-100%' : '100%'
      const transform = open ? '' : `${placementName}(${placementPos})`
      if (isOpenChange === undefined || isOpenChange) {
        const contentValue = this.contentDom ? this.contentDom.getBoundingClientRect()[
          isHorizontal ? 'width' : 'height'
        ] : 0
        const value = (isHorizontal ? width : height) || contentValue
        this.setLevelDomTransform(open, false, placementName, value)
      }
      let handlerChildren
      if (handler !== false) {
        const handlerDefalut = (
          <div class='drawer-handle'>
            <i class='drawer-handle-icon' />
          </div>
        )
        const { handler: handlerSlot } = this.$slots
        const handlerSlotVnode = handlerSlot || handlerDefalut
        const { click: handleIconClick } = getEvents(handlerSlotVnode)
        handlerChildren = cloneElement(handlerSlotVnode, {
          on: {
            click: (e) => {
              handleIconClick && handleIconClick()
              this.onIconTouchEnd(e)
            },
          },
          directives: [{
            name: 'ant-ref',
            value: (c) => {
              this.handlerdom = c
            },
          }],
        })
      }
      const domContProps = {
        class: wrapperClassname,
        directives: [{
          name: 'ant-ref',
          value: (c) => {
            this.dom = c
          },
        }],
        on: {
          transitionend: this.onWrapperTransitionEnd,
        },
      }
      const directivesMaskDom = [{
        name: 'ant-ref',
        value: (c) => {
          this.maskDom = c
        },
      }]
      const directivesContentWrapper = [{
        name: 'ant-ref',
        value: (c) => {
          this.contentWrapper = c
        },
      }]
      const directivesContentDom = [{
        name: 'ant-ref',
        value: (c) => {
          this.contentDom = c
        },
      }]
      return (
        <div
          {...domContProps}
        >
          {showMask && (
            <div
              class={`${prefixCls}-mask`}
              onClick={this.onMaskTouchEnd}
              style={maskStyle}
              {...{ directives: directivesMaskDom }}
            />
          )}
          <div
            class={`${prefixCls}-content-wrapper`}
            style={{
              transform,
              msTransform: transform,
              width: isNumeric(width) ? `${width}px` : width,
              height: isNumeric(height) ? `${height}px` : height,
            }}
            {...{ directives: directivesContentWrapper }}
          >
            <div
              class={`${prefixCls}-content`}
              {...{ directives: directivesContentDom }}
              onTouchstart={open ? this.removeStartHandler : noop} // 跑用例用
              onTouchmove={open ? this.removeMoveHandler : noop} // 跑用例用
            >
              {children}
            </div>
            {handlerChildren}
          </div>
        </div>
      )
    },
    getOpen () {
      return this.open !== undefined ? this.open : this.sOpen
    },
    getTouchParentScroll (root, currentTarget, differX, differY) {
      if (!currentTarget || currentTarget === document) {
        return false
      }
      // root 为 drawer-content 设定了 overflow, 判断为 root 的 parent 时结束滚动;
      if (currentTarget === root.parentNode) {
        return true
      }

      const isY = Math.max(Math.abs(differX), Math.abs(differY)) === Math.abs(differY)
      const isX = Math.max(Math.abs(differX), Math.abs(differY)) === Math.abs(differX)

      const scrollY = currentTarget.scrollHeight - currentTarget.clientHeight
      const scrollX = currentTarget.scrollWidth - currentTarget.clientWidth
      /**
       * <div style="height: 300px">
       *   <div style="height: 900px"></div>
       * </div>
       * 在没设定 overflow: auto 或 scroll 时,currentTarget 里获取不到 scrollTop 或 scrollLeft,
       * 预先用 scrollTo 来滚动,如果取出的值跟滚动前取出不同,则 currnetTarget 被设定了 overflow; 否则就是上面这种。
       */
      const t = currentTarget.scrollTop
      const l = currentTarget.scrollLeft
      if (currentTarget.scrollTo) {
        currentTarget.scrollTo(currentTarget.scrollLeft + 1, currentTarget.scrollTop + 1)
      }
      const currentT = currentTarget.scrollTop
      const currentL = currentTarget.scrollLeft
      if (currentTarget.scrollTo) {
        currentTarget.scrollTo(currentTarget.scrollLeft - 1, currentTarget.scrollTop - 1)
      }
      if (
        (isY &&
          (!scrollY ||
            !(currentT - t) ||
            (scrollY &&
              ((currentTarget.scrollTop >= scrollY && differY < 0) ||
                (currentTarget.scrollTop <= 0 && differY > 0))))) ||
        (isX &&
          (!scrollX ||
            !(currentL - l) ||
            (scrollX &&
              ((currentTarget.scrollLeft >= scrollX && differX < 0) ||
                (currentTarget.scrollLeft <= 0 && differX > 0)))))
      ) {
        return this.getTouchParentScroll(root, currentTarget.parentNode, differX, differY)
      }
      return false
    },
    removeStartHandler (e) {
      if (e.touches.length > 1) {
        return
      }
      this.startPos = {
        x: e.touches[0].clientX,
        y: e.touches[0].clientY,
      }
    },
    removeMoveHandler (e) {
      if (e.changedTouches.length > 1) {
        return
      }
      const currentTarget = e.currentTarget
      const differX = e.changedTouches[0].clientX - this.startPos.x
      const differY = e.changedTouches[0].clientY - this.startPos.y
      if (
        currentTarget === this.maskDom ||
        currentTarget === this.handlerdom ||
        (currentTarget === this.contentDom &&
        this.getTouchParentScroll(currentTarget, e.target, differX, differY))
      ) {
        e.preventDefault()
      }
    },
    trnasitionEnd (e) {
      removeEventListener(e.target, transitionEnd, this.trnasitionEnd)
      e.target.style.transition = ''
    },
    defaultGetContainer () {
      if (windowIsUndefined) {
        return null
      }
      const container = document.createElement('div')
      this.parent.appendChild(container)
      if (this.wrapperClassName) {
        container.className = this.wrapperClassName
      }
      return container
    },
  },

  render () {
    const { getContainer, wrapperClassName } = this.$props
    const open = this.getOpen()
    currentDrawer[this.drawerId] = open ? this.container : open
    const children = this.getChildToRender(this.sFirstEnter ? open : false)
    if (!getContainer) {
      const directives = [{
        name: 'ant-ref',
        value: (c) => {
          this.container = c
        },
      }]
      return (
        <div
          class={wrapperClassName}
          {...{ directives }}
        >
          {children}
        </div>
      )
    }
    if (!this.container || (!open && !this.sFirstEnter)) {
      return null
    }
    return (
      <ContainerRender
        parent={this}
        visible
        autoMount
        autoDestroy={false}
        getComponent={() => children}
        getContainer={this.getSelfContainer}
        children={({ renderComponent, removeContainer }) => {
          this.renderComponent = renderComponent
          this.removeContainer = removeContainer
          return null
        }}
      >
      </ContainerRender>
    )
  },
}

export default Drawer