import { Logger } from '@refloat/logger'

import { APIQueue } from '../api/api-queue'
import { Graph } from './graph'
import { RunEnv } from './run-env'
import { Task, Tasks, TaskState } from './task'

const logger = Logger.create('scheduler')

export class Scheduler {
	constructor(private readonly queue: APIQueue) {}

	public async run(graph: Graph) {
		const tasks = new Tasks(graph)
		const env = new RunEnv(graph, this.queue.api)

		await env.init()

		try {
			while (!tasks.done()) {
				for (const task of tasks.queue(graph)) {
					const start = Date.now()

					task.state = TaskState.Running

					logger.info('running', { step: task.node.step.path })

					tasks.run(async () => {
						this.queue.start(task.node.step.id)

						const code = await this.exec(task, env)
						const success = code === 0

						this.queue.complete(task.node.step.id, success)

						task.state = success ? TaskState.Done : TaskState.Failed

						logger.info(success ? 'completed' : 'failed', {
							step: task.node.step.path,
							duration: `${Date.now() - start}ms`,
						})
					})
				}

				logger.debug('waiting for steps to run')

				await tasks.wait()
			}

			return tasks.state()
		} finally {
			await env.clean()
		}
	}

	private async exec(task: Task, env: RunEnv) {
		try {
			return await env.exec(task.node, (chunk) =>
				this.queue.progress(task.node.step.id, chunk),
			)
		} catch (error) {
			return null
		}
	}
}
