import { sleep } from '../utils/sleep'
import { APIClient } from './api-client'

export class APIQueue {
	public running = false
	private queue: JobQueue[] = []

	constructor(public readonly api: APIClient) {}

	public start(id: string, date = new Date()) {
		this.getItem(id).start = { date }
	}

	public progress(id: string, buffer: Buffer) {
		const item = this.getItem(id)

		if (item.progress) {
			item.progress.chunks.push(buffer)
		} else {
			item.progress = { chunks: [buffer] }
		}
	}

	public complete(id: string, success: boolean, date = new Date()) {
		this.getItem(id).complete = { date, success }
	}

	public async wait(fn?: () => Promise<void>) {
		this.running = true

		try {
			if (fn) {
				await Promise.all([fn(), this.wait()])
			} else {
				while (this.running) {
					await sleep(500)
					await this.flush()
				}

				await this.flush()
			}
		} finally {
			this.running = false
		}
	}

	private getItem(id: string) {
		const item = this.queue.find((i) => i.id === id)

		if (item) {
			return item
		} else {
			const item: JobQueue = { id }

			this.queue.push(item)

			return item
		}
	}

	private async flush() {
		do {
			this.queue = this.queue.filter(
				(update) => update.start || update.progress || update.complete,
			)

			const update = this.queue.shift()

			if (!update) {
				break
			}

			if (update.start) {
				const { date } = update.start

				delete update.start

				await this.api.start(update.id, date)
			}

			if (update.progress) {
				const buffer = Buffer.concat(update.progress.chunks)

				delete update.progress

				await this.api.progress(update.id, buffer)
			}

			if (update.complete) {
				const { date, success } = update.complete

				delete update.complete

				await this.api.complete(update.id, date, success)
			}
		} while (this.queue.length)
	}
}

interface JobQueue {
	id: string

	start?: {
		date: Date
	}
	progress?: {
		chunks: Buffer[]
	}
	complete?: {
		date: Date
		success: boolean
	}
}
