// Modified from https://github.com/jackmoore/autosize/blob/113f1b3/src/autosize.js
// The only change is to remove IE-specific hacks,
// remove parent overflow checks, make page resizes more performant,
// add deferredUpdate, and add perf marks.

import { mark, stop } from './marks'
import debounce from 'lodash-es/debounce'
import throttle from 'lodash-es/throttle'

const map = new Map()
let createEvent = (name) => new Event(name, {bubbles: true})

function assign (ta) {
  if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) {
    return
  }

  // TODO: hack - grab our scroll container so we can maintain the scrollTop
  let container = document.getElementsByClassName('container')[0]
  let heightOffset = null
  let cachedHeight = null

  function init () {
    const style = window.getComputedStyle(ta, null)

    if (style.boxSizing === 'content-box') {
      heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom))
    } else {
      heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth)
    }

    update()
  }

  function resize () {
    mark('autosize:resize()')
    let res = _resize()
    stop('autosize:resize()')
    return res
  }

  function _resize () {
    const originalHeight = ta.style.height
    const scrollTop = container.scrollTop

    ta.style.height = '' // this may change the scrollTop in Firefox

    const endHeight = ta.scrollHeight + heightOffset

    if (ta.scrollHeight === 0) {
      // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
      ta.style.height = originalHeight
      return
    }

    ta.style.height = endHeight + 'px'
    container.scrollTop = scrollTop // Firefox jiggles if we don't reset the scrollTop of the container
    return endHeight
  }

  const deferredUpdate = throttle(() => requestAnimationFrame(update), 100)

  function update () {
    mark('autosize:update()')
    _update()
    stop('autosize:update()')
  }

  function _update () {
    let newHeight = resize()
    if (cachedHeight !== newHeight) {
      cachedHeight = newHeight
      const evt = createEvent('autosize:resized')
      try {
        ta.dispatchEvent(evt)
      } catch (err) {
        // Firefox will throw an error on dispatchEvent for a detached element
        // https://bugzilla.mozilla.org/show_bug.cgi?id=889376
      }
    }
  }

  const pageResize = debounce(() => requestAnimationFrame(update), 1000)

  const destroy = () => {
    window.removeEventListener('resize', pageResize, false)
    ta.removeEventListener('input', deferredUpdate, false)
    ta.removeEventListener('autosize:destroy', destroy, false)
    ta.removeEventListener('autosize:update', update, false)

    map.delete(ta)
  }

  ta.addEventListener('autosize:destroy', destroy, false)

  window.addEventListener('resize', pageResize, false)
  ta.addEventListener('input', deferredUpdate, false)
  ta.addEventListener('autosize:update', update, false)

  map.set(ta, {
    destroy,
    update
  })

  init()
}

function destroy (ta) {
  const methods = map.get(ta)
  if (methods) {
    methods.destroy()
  }
}

function update (ta) {
  const methods = map.get(ta)
  if (methods) {
    methods.update()
  }
}

let autosize = (el, options) => {
  if (el) {
    Array.prototype.forEach.call(el.length ? el : [el], x => assign(x, options))
  }
  return el
}
autosize.destroy = el => {
  if (el) {
    Array.prototype.forEach.call(el.length ? el : [el], destroy)
  }
  return el
}
autosize.update = el => {
  if (el) {
    Array.prototype.forEach.call(el.length ? el : [el], update)
  }
  return el
}

export { autosize }