import { processOptions, throttle, deepEqual } from '../utils'

class VisibilityState {
	constructor (el, options, vnode) {
		this.el = el
		this.observer = null
		this.frozen = false
		this.createObserver(options, vnode)
	}

	get threshold () {
		return (this.options.intersection && this.options.intersection.threshold) || 0
	}

	createObserver (options, vnode) {
		if (this.observer) {
			this.destroyObserver()
		}

		if (this.frozen) return

		this.options = processOptions(options)

		this.callback = (result, entry) => {
			this.options.callback(result, entry)
			if (result && this.options.once) {
				this.frozen = true
				this.destroyObserver()
			}
		}
		// Throttle
		if (this.callback && this.options.throttle) {
			const { leading } = this.options.throttleOptions || {}
			this.callback = throttle(this.callback, this.options.throttle, {
				leading: (state) => {
					return leading === 'both' || (leading === 'visible' && state) || (leading === 'hidden' && !state)
				},
			})
		}

		this.oldResult = undefined

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

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

			if (this.callback) {
				// Use isIntersecting if possible because browsers can report isIntersecting as true, but intersectionRatio as 0, when something very slowly enters the viewport.
				const result = entry.isIntersecting && entry.intersectionRatio >= this.threshold
				if (result === this.oldResult) return
				this.oldResult = result
				this.callback(result, entry)
			}
		}, this.options.intersection)

		// Wait for the element to be in document
		vnode.context.$nextTick(() => {
			if (this.observer) {
				this.observer.observe(this.el)
			}
		})
	}

	destroyObserver () {
		if (this.observer) {
			this.observer.disconnect()
			this.observer = null
		}

		// Cancel throttled call
		if (this.callback && this.callback._clear) {
			this.callback._clear()
			this.callback = null
		}
	}
}

function bind (el, { value }, vnode) {
	if (!value) return
	if (typeof IntersectionObserver === 'undefined') {
		console.warn('[vue-observe-visibility] IntersectionObserver API is not available in your browser. Please install this polyfill: https://github.com/w3c/IntersectionObserver/tree/master/polyfill')
	} else {
		const state = new VisibilityState(el, value, vnode)
		el._vue_visibilityState = state
	}
}

function update (el, { value, oldValue }, vnode) {
	if (deepEqual(value, oldValue)) return
	const state = el._vue_visibilityState
	if (!value) {
		unbind(el)
		return
	}
	if (state) {
		state.createObserver(value, vnode)
	} else {
		bind(el, { value }, vnode)
	}
}

function unbind (el) {
	const state = el._vue_visibilityState
	if (state) {
		state.destroyObserver()
		delete el._vue_visibilityState
	}
}

export default {
	bind,
	update,
	unbind,
}
