/**
 * Schedule Autonomous Dispatch — first-class execution for due project schedule entries.
 *
 * Purpose: let autonomous mode consume repo-owned scheduled work without a
 * human approval loop while preserving append-only evidence in the schedule log.
 *
 * Consumer: auto-dispatch.js and commands-schedule.js.
 */
import { execSync } from "node:child_process";
import { createScheduleStore } from "./schedule-store.js";

const MAX_RESULT_CHARS = 12_000;

/**
 * Return true when a schedule entry is allowed to run from autonomous mode.
 *
 * Purpose: keep autonomous execution explicit; passive reminders and global schedule
 * entries remain visible but do not become repo cron jobs accidentally.
 *
 * Consumer: auto-dispatch.js schedule rule.
 *
 * @param {import("./schedule-types.js").ScheduleEntry} entry
 * @returns {boolean}
 */
export function isAutonomousDispatchScheduleEntry(entry) {
	return (
		entry?.autonomous_dispatch === true &&
		(entry.kind === "command" || entry.kind === "prompt")
	);
}

/**
 * Mark a project schedule entry done with a bounded result note.
 *
 * Purpose: make schedule consumption durable so due autonomous dispatch entries do
 * not repeat forever after a successful autonomous tick.
 *
 * Consumer: executeProjectScheduleCommand and prompt schedule dispatch.
 *
 * @param {string} basePath
 * @param {import("./schedule-types.js").ScheduleEntry} entry
 * @param {Record<string, unknown>} [payloadPatch]
 * @returns {import("./schedule-types.js").ScheduleEntry}
 */
export function markProjectScheduleDone(basePath, entry, payloadPatch = {}) {
	const updated = {
		...entry,
		status: "done",
		created_at: new Date().toISOString(),
		payload: {
			...(entry.payload ?? {}),
			...payloadPatch,
		},
	};
	createScheduleStore(basePath).appendEntry("project", updated);
	return updated;
}

/**
 * Mark a project schedule entry cancelled with a bounded failure note.
 *
 * Purpose: preserve failed cron evidence and prevent an invalid command from
 * hot-looping through autonomous dispatch forever.
 *
 * Consumer: executeProjectScheduleCommand and schedule dispatch failure paths.
 *
 * @param {string} basePath
 * @param {import("./schedule-types.js").ScheduleEntry} entry
 * @param {string} reason
 * @returns {import("./schedule-types.js").ScheduleEntry}
 */
export function markProjectScheduleCancelled(basePath, entry, reason) {
	const updated = {
		...entry,
		status: "cancelled",
		created_at: new Date().toISOString(),
		payload: {
			...(entry.payload ?? {}),
			result_note: _truncate(reason),
		},
	};
	createScheduleStore(basePath).appendEntry("project", updated);
	return updated;
}

/**
 * Execute one project-scoped command schedule entry from the repo root.
 *
 * Purpose: make `kind: "command", autonomous_dispatch: true` behave like a repo cron
 * job in autonomous mode, with durable success/failure status in `.sf`.
 *
 * Consumer: auto-dispatch.js schedule rule and `/schedule run`.
 *
 * @param {string} basePath
 * @param {import("./schedule-types.js").ScheduleEntry} entry
 * @returns {{ok: true, status: "done", stdout?: string} | {ok: false, status: "cancelled", reason: string}}
 */
export function executeProjectScheduleCommand(basePath, entry) {
	const payload = entry.payload ?? {};
	const command = payload.command;
	if (typeof command !== "string" || command.trim().length === 0) {
		const reason = `Command entry ${entry.id} has no payload.command.`;
		markProjectScheduleCancelled(basePath, entry, reason);
		return { ok: false, status: "cancelled", reason };
	}

	try {
		const stdout = execSync(command, {
			cwd: basePath,
			encoding: "utf-8",
			stdio: ["ignore", "pipe", "pipe"],
			env: process.env,
			maxBuffer: 16 * 1024 * 1024,
		});
		const captured =
			payload.capture === "stdout" ? _truncate(stdout) : undefined;
		markProjectScheduleDone(basePath, entry, {
			result_note: "command completed",
			...(captured !== undefined ? { stdout: captured } : {}),
		});
		return captured !== undefined
			? { ok: true, status: "done", stdout: captured }
			: { ok: true, status: "done" };
	} catch (err) {
		const reason = _errorText(err);
		markProjectScheduleCancelled(basePath, entry, reason);
		return { ok: false, status: "cancelled", reason };
	}
}

/**
 * Build the autonomous prompt for a due prompt schedule entry.
 *
 * Purpose: turn scheduled intent into a normal custom-step unit while keeping
 * schedule storage responsible only for when the trigger fires.
 *
 * Consumer: auto-dispatch.js schedule rule.
 *
 * @param {import("./schedule-types.js").ScheduleEntry} entry
 * @returns {string}
 */
export function buildScheduledPrompt(entry) {
	const payload = entry.payload ?? {};
	const text = payload.prompt ?? payload.message ?? entry.id;
	return [
		`Scheduled autonomous prompt ${entry.id} is due.`,
		"",
		"Treat this as repo-owned scheduled work. Execute it without asking the user unless a safety gate requires a pause.",
		"",
		"Scheduled work:",
		String(text),
	].join("\n");
}

function _errorText(err) {
	if (err && typeof err === "object") {
		const stderr = "stderr" in err ? err.stderr : undefined;
		if (stderr) return _truncate(String(stderr));
		const stdout = "stdout" in err ? err.stdout : undefined;
		const message = "message" in err ? err.message : undefined;
		return _truncate(String(message ?? stdout ?? err));
	}
	return _truncate(String(err));
}

function _truncate(value) {
	const text = String(value ?? "");
	return text.length > MAX_RESULT_CHARS
		? `${text.slice(0, MAX_RESULT_CHARS)}\n[truncated]`
		: text;
}
