import { inBrowser, isWeChatDevTools, supportsPassive } from './env'
import { extend } from './lang'

export type safeCSSStyleDeclaration = {
  [key: string]: string
} & CSSStyleDeclaration
export interface DOMRect {
  left: number
  top: number
  width: number
  height: number
  [key: string]: number
}

let elementStyle = (inBrowser &&
  document.createElement('div').style) as safeCSSStyleDeclaration

let vendor = (() => {
  /* istanbul ignore if  */
  if (!inBrowser) {
    return false
  }
  const transformNames = [
    {
      key: 'standard',
      value: 'transform',
    },
    {
      key: 'webkit',
      value: 'webkitTransform',
    },
    {
      key: 'Moz',
      value: 'MozTransform',
    },
    {
      key: 'O',
      value: 'OTransform',
    },
    {
      key: 'ms',
      value: 'msTransform',
    },
  ]
  for (let obj of transformNames) {
    if (elementStyle[obj.value] !== undefined) {
      return obj.key
    }
  }
  /* istanbul ignore next  */
  return false
})()

/* istanbul ignore next  */
function prefixStyle(style: string): string {
  if (vendor === false) {
    return style
  }

  if (vendor === 'standard') {
    if (style === 'transitionEnd') {
      return 'transitionend'
    }
    return style
  }

  return vendor + style.charAt(0).toUpperCase() + style.substr(1)
}

export function getElement(el: HTMLElement | string) {
  return (
    typeof el === 'string' ? document.querySelector(el) : el
  ) as HTMLElement
}

export function addEvent(
  el: HTMLElement,
  type: string,
  fn: EventListenerOrEventListenerObject,
  capture?: AddEventListenerOptions
) {
  const useCapture = supportsPassive
    ? {
        passive: false,
        capture: !!capture,
      }
    : !!capture
  el.addEventListener(type, fn, useCapture)
}

export function removeEvent(
  el: HTMLElement,
  type: string,
  fn: EventListenerOrEventListenerObject,
  capture?: EventListenerOptions
) {
  el.removeEventListener(type, fn, {
    capture: !!capture,
  })
}

export function maybePrevent(e: Event) {
  if (e.cancelable) {
    e.preventDefault()
  }
}

export function offset(el: HTMLElement | null) {
  let left = 0
  let top = 0

  while (el) {
    left -= el.offsetLeft
    top -= el.offsetTop
    el = el.offsetParent as HTMLElement
  }

  return {
    left,
    top,
  }
}

export function offsetToBody(el: HTMLElement) {
  let rect = el.getBoundingClientRect()

  return {
    left: -(rect.left + window.pageXOffset),
    top: -(rect.top + window.pageYOffset),
  }
}

export const cssVendor =
  vendor && vendor !== 'standard' ? '-' + vendor.toLowerCase() + '-' : ''

let transform = prefixStyle('transform')
let transition = prefixStyle('transition')

export const hasPerspective =
  inBrowser && prefixStyle('perspective') in elementStyle
// fix issue #361
export const hasTouch =
  inBrowser && ('ontouchstart' in window || isWeChatDevTools)
export const hasTransition = inBrowser && transition in elementStyle

export const style = {
  transform,
  transition,
  transitionTimingFunction: prefixStyle('transitionTimingFunction'),
  transitionDuration: prefixStyle('transitionDuration'),
  transitionDelay: prefixStyle('transitionDelay'),
  transformOrigin: prefixStyle('transformOrigin'),
  transitionEnd: prefixStyle('transitionEnd'),
  transitionProperty: prefixStyle('transitionProperty'),
}

export const eventTypeMap: {
  [key: string]: number
  touchstart: number
  touchmove: number
  touchend: number
  touchcancel: number
  mousedown: number
  mousemove: number
  mouseup: number
} = {
  touchstart: 1,
  touchmove: 1,
  touchend: 1,
  touchcancel: 1,

  mousedown: 2,
  mousemove: 2,
  mouseup: 2,
}

export function getRect(el: HTMLElement): DOMRect {
  /* istanbul ignore if  */
  if (el instanceof (window as any).SVGElement) {
    let rect = el.getBoundingClientRect()
    return {
      top: rect.top,
      left: rect.left,
      width: rect.width,
      height: rect.height,
    }
  } else {
    return {
      top: el.offsetTop,
      left: el.offsetLeft,
      width: el.offsetWidth,
      height: el.offsetHeight,
    }
  }
}

export function preventDefaultExceptionFn(
  el: any,
  exceptions: {
    tagName?: RegExp
    className?: RegExp
    [key: string]: any
  }
) {
  for (let i in exceptions) {
    if (exceptions[i].test(el[i])) {
      return true
    }
  }
  return false
}

export const tagExceptionFn = preventDefaultExceptionFn

export function tap(e: any, eventName: string) {
  let ev = document.createEvent('Event') as any
  ev.initEvent(eventName, true, true)
  ev.pageX = e.pageX
  ev.pageY = e.pageY
  e.target.dispatchEvent(ev)
}

export function click(e: any, event = 'click') {
  let eventSource
  if (e.type === 'mouseup') {
    eventSource = e
  } else if (e.type === 'touchend' || e.type === 'touchcancel') {
    eventSource = e.changedTouches[0]
  }
  let posSrc: {
    screenX?: number
    screenY?: number
    clientX?: number
    clientY?: number
  } = {}
  if (eventSource) {
    posSrc.screenX = eventSource.screenX || 0
    posSrc.screenY = eventSource.screenY || 0
    posSrc.clientX = eventSource.clientX || 0
    posSrc.clientY = eventSource.clientY || 0
  }
  let ev: any
  const bubbles = true
  const cancelable = true
  const { ctrlKey, shiftKey, altKey, metaKey } = e
  const pressedKeysMap = {
    ctrlKey,
    shiftKey,
    altKey,
    metaKey,
  }
  if (typeof MouseEvent !== 'undefined') {
    try {
      ev = new MouseEvent(
        event,
        extend(
          {
            bubbles,
            cancelable,
            ...pressedKeysMap,
          },
          posSrc
        )
      )
    } catch (e) {
      /* istanbul ignore next */
      createEvent()
    }
  } else {
    createEvent()
  }

  function createEvent() {
    ev = document.createEvent('Event')
    ev.initEvent(event, bubbles, cancelable)
    extend(ev, posSrc)
  }

  // forwardedTouchEvent set to true in case of the conflict with fastclick
  ev.forwardedTouchEvent = true
  ev._constructed = true
  e.target.dispatchEvent(ev)
}

export function dblclick(e: Event) {
  click(e, 'dblclick')
}

export function prepend(el: HTMLElement, target: HTMLElement) {
  const firstChild = target.firstChild as HTMLElement
  if (firstChild) {
    before(el, firstChild)
  } else {
    target.appendChild(el)
  }
}

export function before(el: HTMLElement, target: HTMLElement) {
  const parentNode = target.parentNode as HTMLElement
  parentNode.insertBefore(el, target)
}

export function removeChild(el: HTMLElement, child: HTMLElement) {
  el.removeChild(child)
}

export function hasClass(el: HTMLElement, className: string) {
  let reg = new RegExp('(^|\\s)' + className + '(\\s|$)')
  return reg.test(el.className)
}

export function addClass(el: HTMLElement, className: string) {
  if (hasClass(el, className)) {
    return
  }

  let newClass = el.className.split(' ')
  newClass.push(className)
  el.className = newClass.join(' ')
}

export function removeClass(el: HTMLElement, className: string) {
  if (!hasClass(el, className)) {
    return
  }

  let reg = new RegExp('(^|\\s)' + className + '(\\s|$)', 'g')
  el.className = el.className.replace(reg, ' ')
}

export function HTMLCollectionToArray(el: HTMLCollection) {
  return Array.prototype.slice.call(el, 0)
}

export function getClientSize(el: HTMLElement) {
  return {
    width: el.clientWidth,
    height: el.clientHeight,
  }
}
