import { throttle, isEqual } from '@opentiny/utils'

const CONTEXT_KEY = '@@observevisibilityContext'

const processOptions = (value) => {
  let options

  if (typeof value === 'function') {
    options = { callback: value }
  } else {
    options = value
  }

  return options
}

const createObserver = ({ options, instance, state }) => {
  if (state.observer) {
    destroyObserver(state)
  }

  if (state.frozen) return

  state.options = processOptions(options)
  state.callback = (result, entry) => {
    state.options.callback(result, entry)

    if (result && state.options.once) {
      state.frozen = true
      destroyObserver(state)
    }
  }

  if (state.callback && state.options.throttle) {
    state.callback = throttle(state.options.throttleDelay || 20, state.callback)
  }

  state.observer = new IntersectionObserver((entries) => {
    let entry = entries[0]

    if (entries.length > 1) {
      const intersectingEntry = entries.find((e) => e.isIntersecting)

      if (intersectingEntry) {
        entry = intersectingEntry
      }
    }

    if (state.callback) {
      state.callback(entry.isIntersecting, entry)
    }
  }, state.options.intersection)

  instance.$nextTick(() => {
    if (state.observer) {
      state.observer.observe(state.el)
    }
  })
}

const destroyObserver = (state) => {
  if (state.observer) {
    state.observer.disconnect()
    state.observer = null
  }

  if (state.callback) {
    state.callback = null
  }
}

const createVisibilityState = ({ el, options, instance }) => {
  const state = { el, observer: null, frozen: false }

  createObserver({ options, instance, state })

  return state
}

const bind = (el, { value, instance }, { context }) => {
  if (!value) return

  if (typeof IntersectionObserver === 'undefined') {
    throw new TypeError('[TINY Error][ObserveVisibility] IntersectionObserver API is not available in your browser')
  } else {
    instance = instance || context

    el[CONTEXT_KEY] = createVisibilityState({ el, options: value, instance })
  }
}

const update = (el, { value, oldValue, instance }, { context }) => {
  if (isEqual(value, oldValue)) return

  const state = el[CONTEXT_KEY]

  if (!value) {
    unbind(el)
    return
  }

  instance = instance || context

  if (state) {
    createObserver({ options: value, instance, state })
  } else {
    bind(el, { value, instance }, { context })
  }
}

const unbind = (el) => {
  const state = el[CONTEXT_KEY]

  if (state) {
    destroyObserver(state)
    delete el[CONTEXT_KEY]
  }
}

const ObserveVisibility = {
  bind,
  update,
  unbind,
  beforeMount: bind,
  updated: update,
  unmounted: unbind
}

export default ObserveVisibility
