import type MqttClient from './client'
import getTimer, { type Timer } from './get-timer'
import type { TimerVariant } from './shared'

export default class KeepaliveManager {
	private _keepalive: number

	private timerId: number

	private timer: Timer

	private destroyed = false

	private counter: number

	private client: MqttClient

	private _keepaliveTimeoutTimestamp: number

	private _intervalEvery: number

	/** Timestamp of next keepalive timeout */
	get keepaliveTimeoutTimestamp() {
		return this._keepaliveTimeoutTimestamp
	}

	/** Milliseconds of the actual interval */
	get intervalEvery() {
		return this._intervalEvery
	}

	get keepalive() {
		return this._keepalive
	}

	constructor(client: MqttClient, variant: TimerVariant | Timer) {
		this.client = client
		this.timer =
			typeof variant === 'object' &&
			'set' in variant &&
			'clear' in variant
				? variant
				: getTimer(variant)
		this.setKeepalive(client.options.keepalive)
	}

	private clear() {
		if (this.timerId) {
			this.timer.clear(this.timerId)
			this.timerId = null
		}
	}

	/** Change the keepalive */
	setKeepalive(value: number) {
		// keepalive is in seconds
		value *= 1000

		if (
			// eslint-disable-next-line no-restricted-globals
			isNaN(value) ||
			value <= 0 ||
			value > 2147483647
		) {
			throw new Error(
				`Keepalive value must be an integer between 0 and 2147483647. Provided value is ${value}`,
			)
		}

		this._keepalive = value

		this.reschedule()

		this.client['log'](`KeepaliveManager: set keepalive to ${value}ms`)
	}

	destroy() {
		this.clear()
		this.destroyed = true
	}

	reschedule() {
		if (this.destroyed) {
			return
		}

		this.clear()
		this.counter = 0

		// https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Figure_3.5_Keep
		const keepAliveTimeout = Math.ceil(this._keepalive * 1.5)

		this._keepaliveTimeoutTimestamp = Date.now() + keepAliveTimeout
		this._intervalEvery = Math.ceil(this._keepalive / 2)

		this.timerId = this.timer.set(() => {
			// this should never happen, but just in case
			if (this.destroyed) {
				return
			}

			this.counter += 1

			// after keepalive seconds, send a pingreq
			if (this.counter === 2) {
				this.client.sendPing()
			} else if (this.counter > 2) {
				this.client.onKeepaliveTimeout()
			}
		}, this._intervalEvery)
	}
}
