import appSettings from '@/core/config/app-settings'

const OPEN = 1
const PING_INTERVAL = 30000
const CONNECT_TIMEOUT = 8000

export class SocketSession {
	constructor(handlers = {}) {
		this.handlers = handlers
		this.socket = null
		this.connecting = false
		this.connected = false
		this.allowReconnect = true
		this.paused = false
		this.reconnectAttempt = 0
		this.reconnecting = false
		this.heartbeatId = null
		this.connectTimer = null
	}

	open() {
		if (this.paused || !this.allowReconnect) return
		if (this.connecting) return
		if (this.connected && this.socket?.readyState === OPEN) return

		this.teardownSocket(false)
		this.connecting = true
		this.connected = false

		this.connectTimer = setTimeout(() => {
			if (!this.connected) this.handlers.onError?.()
		}, CONNECT_TIMEOUT)

		try {
			this.socket = uni.connectSocket({
				url: appSettings.socketurl,
				fail: () => {
					this.connecting = false
					this.clearConnectTimer()
					this.handlers.onError?.()
				}
			})

			this.socket.onOpen(() => {
				this.clearConnectTimer()
				this.connecting = false
				this.connected = true
				this.reconnectAttempt = 0
				this.startHeartbeat()
				this.handlers.onOpen?.()
			})

			this.socket.onMessage((msg) => {
				this.handlers.onMessage?.(msg)
			})

			this.socket.onClose((evt) => {
				this.onDisconnected(evt)
			})

			this.socket.onError(() => {
				this.clearConnectTimer()
				this.connecting = false
				this.connected = false
				this.handlers.onError?.()
			})
		} catch (err) {
			this.connecting = false
			this.clearConnectTimer()
			this.handlers.onError?.(err)
		}
	}

	send(data) {
		if (!this.socket || this.socket.readyState !== OPEN) return false
		try {
			const payload = typeof data === 'string' ? data : JSON.stringify(data)
			this.socket.send({ data: payload })
			return true
		} catch (_) {
			return false
		}
	}

	pause(temporary = true) {
		if (!temporary) {
			this.paused = true
			this.allowReconnect = false
		}
		this.stopHeartbeat()
		this.clearConnectTimer()
		if (this.socket) {
			try { this.socket.close() } catch (_) {}
			if (!temporary) this.socket = null
		}
		this.connecting = false
		this.connected = false
	}

	resume() {
		if (this.paused && !this.allowReconnect) {
			this.paused = false
			this.allowReconnect = true
		}
		this.open()
	}

	scheduleReconnect() {
		if (!this.allowReconnect || this.paused || this.reconnecting) return
		if (this.reconnectAttempt >= 10) {
			this.reconnectAttempt = 0
			return
		}
		this.reconnecting = true
		const delay = Math.min(1000 * Math.pow(1.5, this.reconnectAttempt), 5000)
		setTimeout(() => {
			this.reconnecting = false
			if (!this.allowReconnect || this.paused) return
			this.reconnectAttempt += 1
			this.open()
		}, delay)
	}

	startHeartbeat() {
		this.stopHeartbeat()
		this.heartbeatId = setInterval(() => {
			if (!this.socket || this.socket.readyState !== OPEN) return
			try {
				this.socket.send({
					data: '{"type":"ping"}',
					fail: () => this.handlers.onError?.()
				})
			} catch (_) {
				this.handlers.onError?.()
			}
		}, PING_INTERVAL)
	}

	stopHeartbeat() {
		if (this.heartbeatId) {
			clearInterval(this.heartbeatId)
			this.heartbeatId = null
		}
	}

	clearConnectTimer() {
		if (this.connectTimer) {
			clearTimeout(this.connectTimer)
			this.connectTimer = null
		}
	}

	onDisconnected(evt) {
		this.connecting = false
		this.connected = false
		this.stopHeartbeat()
		this.clearConnectTimer()
		this.handlers.onClose?.(evt)
		if (evt?.code === 1000 && this.paused) {
			this.socket = null
			return
		}
		this.scheduleReconnect()
	}

	teardownSocket(clearRef) {
		this.stopHeartbeat()
		this.clearConnectTimer()
		if (this.socket) {
			try { this.socket.close() } catch (_) {}
			if (clearRef) this.socket = null
		}
	}
}
