import { join, resolve } from "node:path";

/**
 * Supervises one repository as an independent SF compilation unit.
 *
 * Purpose: keep repo-local autonomous state isolated so a multi-repo operator can
 * tick one repo without sharing dispatch queues, database handles, or guard state
 * with another repo.
 *
 * Consumer: future `sf supervisor` machine and CLI surfaces that coordinate many
 * repository supervisors from one process.
 */
export class RepoSupervisor {
	constructor({ repoPath, supervisorId, options = {} }) {
		if (!repoPath) {
			throw new Error("repoPath is required");
		}
		if (!supervisorId) {
			throw new Error("supervisorId is required");
		}

		this.repoPath = resolve(repoPath);
		this.supervisorId = supervisorId;
		this.repoId = options.repoId ?? this.repoPath;
		this.dbPath = options.dbPath ?? join(this.repoPath, ".sf", "sf.db");
		this.dbConnection = { path: this.dbPath, open: false };
		this.dispatchQueue = [...(options.dispatchQueue ?? [{ id: "stub-unit" }])];
		this.runawayGuard = {
			consecutiveErrors: 0,
			circuitOpen: false,
		};
		this.logger = options.logger ?? console;
		this.shouldFailTick = options.shouldFailTick;
		this.now = options.now ?? (() => new Date());
		this.started = false;
		this.paused = false;
		this.stopped = false;
		this.lastTickAt = null;
		this.lastTickResult = null;
		this.ticksTotal = 0;
		this.errorsTotal = 0;
	}

	async start() {
		this.started = true;
		this.stopped = false;
		this.dbConnection.open = true;
	}

	async tick() {
		if (this.paused) {
			const result = { dispatched: false, errors: [], status: "paused" };
			this.recordTick(result);
			return result;
		}

		try {
			if (this.stopped) {
				throw new Error("supervisor is stopped");
			}
			if (!this.started) {
				await this.start();
			}
			if (this.runawayGuard.circuitOpen) {
				const result = {
					dispatched: false,
					errors: [],
					status: "circuit_open",
				};
				this.recordTick(result);
				return result;
			}
			if (typeof this.shouldFailTick === "function") {
				await this.shouldFailTick();
			}

			const unit = this.dispatchQueue.shift();
			const now = this.now().toISOString();
			this.lastTickAt = now;
			this.ticksTotal += 1;

			if (!unit) {
				const result = { dispatched: false, errors: [], status: "idle" };
				this.lastTickResult = result;
				this.runawayGuard.consecutiveErrors = 0;
				return result;
			}

			this.logger.info?.(
				`[${this.supervisorId}] ${this.repoPath}: would dispatch unit ${unit.id}`,
			);
			const result = {
				dispatched: true,
				errors: [],
				status: "ok",
				unitId: unit.id,
			};
			this.lastTickResult = result;
			this.runawayGuard.consecutiveErrors = 0;
			return result;
		} catch (error) {
			const message = error instanceof Error ? error.message : String(error);
			const result = {
				dispatched: false,
				error: message,
				errors: [message],
				status: "error",
			};
			this.errorsTotal += 1;
			this.runawayGuard.consecutiveErrors += 1;
			this.lastTickAt = this.now().toISOString();
			this.lastTickResult = result;
			return result;
		}
	}

	async pause() {
		this.paused = true;
	}

	async resume() {
		this.paused = false;
	}

	async stop() {
		this.stopped = true;
		this.started = false;
		this.dbConnection.open = false;
	}

	getStatus() {
		return {
			repoId: this.repoId,
			lastTickAt: this.lastTickAt,
			lastTickResult: this.lastTickResult,
			paused: this.paused,
			ticksTotal: this.ticksTotal,
			errorsTotal: this.errorsTotal,
		};
	}

	recordTick(result) {
		this.lastTickAt = this.now().toISOString();
		this.lastTickResult = result;
		this.ticksTotal += 1;
	}
}
