// SF — Onboarding completion record (~/.sf/agent/onboarding.json)
//
// First-class state for the onboarding wizard so re-entry, resume, and the
// web boot probe all read the same source of truth. Replaces the implicit
// "settings.defaultProvider exists" heuristic.
import {
	existsSync,
	mkdirSync,
	readFileSync,
	renameSync,
	unlinkSync,
	writeFileSync,
} from "node:fs";
import { dirname, join } from "node:path";
import { getErrorMessage } from "./error-utils.js";
import { sfHome } from "./sf-home.js";
import { logWarning } from "./workflow-logger.js";
/**
 * Bump `FLOW_VERSION` whenever a new required step is added to ONBOARDING_STEPS.
 * Records with an older flowVersion are treated as "needs partial re-onboarding"
 * by isOnboardingComplete().
 */
/** Current onboarding flow version. Bump when adding required onboarding steps. */
export const FLOW_VERSION = 1;
const RECORD_VERSION = 1;
// Inline agentDir computation — keep this module rootDir-clean for the
// resources tsconfig; importing from src/ pulls files outside src/resources
// and breaks the build.
const AGENT_DIR = process.env.SF_CODING_AGENT_DIR || join(sfHome(), "agent");
const FILE = join(AGENT_DIR, "onboarding.json");
const DEFAULT = {
	version: RECORD_VERSION,
	flowVersion: FLOW_VERSION,
	completedAt: null,
	completedSteps: [],
	skippedSteps: [],
	lastResumePoint: null,
};
/**
 * Read the onboarding completion record. Returns defaults if file doesn't exist.
 */
export function readOnboardingRecord() {
	if (!existsSync(FILE)) return { ...DEFAULT };
	try {
		const raw = JSON.parse(readFileSync(FILE, "utf-8"));
		return {
			version: typeof raw.version === "number" ? raw.version : RECORD_VERSION,
			flowVersion: typeof raw.flowVersion === "number" ? raw.flowVersion : 0,
			completedAt: typeof raw.completedAt === "string" ? raw.completedAt : null,
			completedSteps: Array.isArray(raw.completedSteps)
				? raw.completedSteps.filter((s) => typeof s === "string")
				: [],
			skippedSteps: Array.isArray(raw.skippedSteps)
				? raw.skippedSteps.filter((s) => typeof s === "string")
				: [],
			lastResumePoint:
				typeof raw.lastResumePoint === "string" ? raw.lastResumePoint : null,
		};
	} catch {
		return { ...DEFAULT };
	}
}
function atomicWrite(record) {
	mkdirSync(dirname(FILE), { recursive: true });
	const tmp = `${FILE}.tmp.${process.pid}.${Date.now()}`;
	try {
		writeFileSync(tmp, JSON.stringify(record, null, 2), "utf-8");
		renameSync(tmp, FILE);
	} catch (err) {
		try {
			if (existsSync(tmp)) unlinkSync(tmp);
		} catch {
			/* swallow secondary error */
		}
		throw err;
	}
}
/**
 * Write or update the onboarding completion record with a partial update.
 */
export function writeOnboardingRecord(patch) {
	const current = readOnboardingRecord();
	const next = {
		...current,
		...patch,
		version: RECORD_VERSION,
		flowVersion:
			typeof patch.flowVersion === "number"
				? patch.flowVersion
				: current.flowVersion,
	};
	try {
		atomicWrite(next);
	} catch (err) {
		logWarning(
			"state",
			`Failed to persist onboarding record: ${getErrorMessage(err)}`,
			{
				file: FILE,
			},
		);
	}
	return next;
}
/**
 * Onboarding is "complete" when there's a completedAt timestamp AND the
 * flowVersion matches the current FLOW_VERSION.
 */
/**
 * Check if onboarding is complete at the current flow version.
 */
export function isOnboardingComplete() {
	const r = readOnboardingRecord();
	return r.completedAt !== null && r.flowVersion === FLOW_VERSION;
}
export function markStepCompleted(stepId) {
	const r = readOnboardingRecord();
	if (r.completedSteps.includes(stepId)) {
		writeOnboardingRecord({ lastResumePoint: stepId });
		return;
	}
	writeOnboardingRecord({
		completedSteps: [...r.completedSteps, stepId],
		skippedSteps: r.skippedSteps.filter((s) => s !== stepId),
		lastResumePoint: stepId,
	});
}
export function markStepSkipped(stepId) {
	const r = readOnboardingRecord();
	if (r.skippedSteps.includes(stepId) || r.completedSteps.includes(stepId))
		return;
	writeOnboardingRecord({
		skippedSteps: [...r.skippedSteps, stepId],
		lastResumePoint: stepId,
	});
}
export function markOnboardingComplete(completedSteps) {
	writeOnboardingRecord({
		completedAt: new Date().toISOString(),
		flowVersion: FLOW_VERSION,
		completedSteps,
	});
}
export function resetOnboarding() {
	writeOnboardingRecord({
		completedAt: null,
		completedSteps: [],
		skippedSteps: [],
		lastResumePoint: null,
	});
}
