import { createHash, randomUUID } from "node:crypto";
import { lstatSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { basename, dirname, join, resolve } from "node:path";
import * as Schema from "typebox/schema";

export const RLM_CONTRACT_FORMAT_VERSION = 1;
export const RLM_RESULT_FILE_NAME = "rlm-result.json";
export const RLM_CONTRACT_FILE_NAME = ".rlm-contract.json";
const MAX_SCHEMA_BYTES = 64 * 1024;
const MAX_CONTRACT_BYTES = 256 * 1024;
const MAX_RESULT_BYTES = 1024 * 1024;
const MAX_PROJECTED_RESULT_BYTES = 32 * 1024;

export interface RlmPayloadContextPolicy {
	payload: boolean;
	notify: boolean;
}

export interface RlmResultContextPolicy {
	fields: string[];
}

export interface RlmChildContextContract {
	terminal?: RlmPayloadContextPolicy;
	direct_messages?: RlmPayloadContextPolicy;
	observe?: boolean;
	result?: RlmResultContextPolicy;
}

export interface RlmResultContract {
	schema: Record<string, unknown>;
	sink: { kind: "file" };
	required: boolean;
}

export interface RlmChildContract {
	result?: RlmResultContract;
	context?: RlmChildContextContract;
}

export type RlmResultTurnStatus = "active" | "settled" | "failed";

interface PersistedRlmResultTurn {
	turn_key: string;
	generation: number;
	status: RlmResultTurnStatus;
	provider_completed: boolean;
	replied_to_parent: boolean;
	notice_admitted: boolean;
	error?: string;
}

interface PersistedRlmResultState {
	next_generation: number;
	latest_settled?: number;
	turn?: PersistedRlmResultTurn;
}

interface PersistedRlmChildContract extends RlmChildContract {
	format_version: typeof RLM_CONTRACT_FORMAT_VERSION;
	child_id: string;
	session_name: string;
	session_id?: string;
	result_state?: PersistedRlmResultState;
}

export interface PreparedRlmChildContract {
	childId: string;
	sessionName: string;
	sessionId?: string;
	sessionDir: string;
	contractPath: string;
	resultState?: PersistedRlmResultState;
	resultPath?: string;
	contract: RlmChildContract;
}

export interface RlmResultTurn {
	turnKey: string;
	generation: number;
	path: string;
	status: RlmResultTurnStatus;
	providerCompleted: boolean;
	repliedToParent: boolean;
	noticeAdmitted: boolean;
	error?: string;
}

export interface RlmResultTurnSettlement {
	prepared: PreparedRlmChildContract;
	turn: RlmResultTurn;
	error?: string;
}

export interface RlmResultRead {
	child_id: string;
	generation: number;
	path: string;
	sha256: string;
	result: Record<string, unknown>;
}

export interface RlmResultSubmission {
	child_id: string;
	generation: number;
	path: string;
	sha256: string;
}

function isRecord(value: unknown): value is Record<string, unknown> {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}

function assertKnownKeys(value: Record<string, unknown>, keys: readonly string[], label: string): void {
	const unknown = Object.keys(value).filter((key) => !keys.includes(key));
	if (unknown.length > 0) throw new Error(`${label} has unsupported fields: ${unknown.sort().join(", ")}`);
}

function cloneJsonRecord(value: Record<string, unknown>, label: string, maxBytes: number): Record<string, unknown> {
	assertJsonValue(value, label);
	let encoded: string;
	try {
		encoded = JSON.stringify(value);
	} catch (error) {
		throw new Error(`${label} must be JSON-serializable: ${error instanceof Error ? error.message : String(error)}`);
	}
	if (Buffer.byteLength(encoded) > maxBytes) throw new Error(`${label} exceeds ${maxBytes} bytes`);
	const parsed: unknown = JSON.parse(encoded);
	if (!isRecord(parsed)) throw new Error(`${label} must be an object`);
	return parsed;
}

const JSON_SCHEMA_TYPES = new Set(["array", "boolean", "integer", "null", "number", "object", "string"]);
const JSON_SCHEMA_KEYS = new Set([
	"$schema",
	"additionalProperties",
	"allOf",
	"anyOf",
	"const",
	"default",
	"description",
	"enum",
	"exclusiveMaximum",
	"exclusiveMinimum",
	"items",
	"maxItems",
	"maxLength",
	"maxProperties",
	"maximum",
	"minItems",
	"minLength",
	"minProperties",
	"minimum",
	"multipleOf",
	"not",
	"oneOf",
	"properties",
	"required",
	"title",
	"type",
	"uniqueItems",
]);

function assertJsonValue(value: unknown, label: string, depth = 0): void {
	if (depth > 32) throw new Error(`${label} exceeds the maximum JSON nesting depth`);
	if (value === null || typeof value === "string" || typeof value === "boolean") return;
	if (typeof value === "number") {
		if (!Number.isFinite(value)) throw new Error(`${label} contains a non-finite number`);
		if (Number.isInteger(value) && !Number.isSafeInteger(value)) {
			throw new Error(`${label} contains an integer outside the JavaScript safe range`);
		}
		return;
	}
	if (Array.isArray(value)) {
		value.forEach((item, index) => {
			assertJsonValue(item, `${label}[${index}]`, depth + 1);
		});
		return;
	}
	if (isRecord(value)) {
		for (const [key, item] of Object.entries(value)) assertJsonValue(item, `${label}.${key}`, depth + 1);
		return;
	}
	throw new Error(`${label} contains a non-JSON value`);
}

function assertNonNegativeInteger(value: unknown, label: string): void {
	if (!Number.isInteger(value) || (value as number) < 0) throw new Error(`${label} must be a non-negative integer`);
}

function assertFiniteNumber(value: unknown, label: string): void {
	if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${label} must be a finite number`);
}

function assertSupportedSchema(value: unknown, label: string, depth = 0): void {
	if (!isRecord(value)) throw new Error(`${label} must be an object`);
	if (depth > 16) throw new Error(`${label} exceeds the supported schema nesting depth`);
	assertKnownKeys(value, [...JSON_SCHEMA_KEYS], label);
	if (value.type !== undefined) {
		const types = Array.isArray(value.type) ? value.type : [value.type];
		if (types.length === 0 || !types.every((type) => typeof type === "string" && JSON_SCHEMA_TYPES.has(type))) {
			throw new Error(`${label}.type is unsupported`);
		}
	}
	if (value.properties !== undefined) {
		if (!isRecord(value.properties)) throw new Error(`${label}.properties must be an object`);
		for (const [key, schema] of Object.entries(value.properties)) {
			assertSupportedSchema(schema, `${label}.properties.${key}`, depth + 1);
		}
	}
	if (value.required !== undefined) {
		if (!Array.isArray(value.required) || !value.required.every((field) => typeof field === "string")) {
			throw new Error(`${label}.required must be an array of strings`);
		}
		if (new Set(value.required).size !== value.required.length) {
			throw new Error(`${label}.required must not contain duplicates`);
		}
	}
	if (value.additionalProperties !== undefined && typeof value.additionalProperties !== "boolean") {
		assertSupportedSchema(value.additionalProperties, `${label}.additionalProperties`, depth + 1);
	}
	if (value.items !== undefined) assertSupportedSchema(value.items, `${label}.items`, depth + 1);
	for (const keyword of ["allOf", "anyOf", "oneOf"] as const) {
		const schemas = value[keyword];
		if (schemas === undefined) continue;
		if (!Array.isArray(schemas) || schemas.length === 0) throw new Error(`${label}.${keyword} must be non-empty`);
		schemas.forEach((schema, index) => {
			assertSupportedSchema(schema, `${label}.${keyword}[${index}]`, depth + 1);
		});
	}
	if (value.not !== undefined) assertSupportedSchema(value.not, `${label}.not`, depth + 1);
	if (value.enum !== undefined && (!Array.isArray(value.enum) || value.enum.length === 0)) {
		throw new Error(`${label}.enum must be a non-empty array`);
	}
	for (const keyword of [
		"minItems",
		"maxItems",
		"minLength",
		"maxLength",
		"minProperties",
		"maxProperties",
	] as const) {
		if (value[keyword] !== undefined) assertNonNegativeInteger(value[keyword], `${label}.${keyword}`);
	}
	for (const keyword of ["minimum", "maximum", "exclusiveMinimum", "exclusiveMaximum", "multipleOf"] as const) {
		if (value[keyword] !== undefined) assertFiniteNumber(value[keyword], `${label}.${keyword}`);
	}
	if (value.multipleOf !== undefined && (value.multipleOf as number) <= 0) {
		throw new Error(`${label}.multipleOf must be greater than zero`);
	}
	if (value.uniqueItems !== undefined && typeof value.uniqueItems !== "boolean") {
		throw new Error(`${label}.uniqueItems must be a boolean`);
	}
	for (const keyword of ["$schema", "description", "title"] as const) {
		if (value[keyword] !== undefined && typeof value[keyword] !== "string") {
			throw new Error(`${label}.${keyword} must be a string`);
		}
	}
}

function normalizeSchema(value: unknown): Record<string, unknown> {
	if (!isRecord(value)) throw new Error("rlm.run result.schema must be an object");
	const schema = cloneJsonRecord(value, "rlm.run result.schema", MAX_SCHEMA_BYTES);
	assertSupportedSchema(schema, "rlm.run result.schema");
	if (schema.type !== "object") throw new Error('rlm.run result.schema type must be "object"');
	if (!isRecord(schema.properties)) throw new Error("rlm.run result.schema properties must be an object");
	if (schema.required !== undefined) {
		if (!Array.isArray(schema.required) || !schema.required.every((field) => typeof field === "string")) {
			throw new Error("rlm.run result.schema required must be an array of strings");
		}
		for (const field of schema.required) {
			if (!Object.hasOwn(schema.properties, field)) {
				throw new Error(`rlm.run result.schema requires unknown field ${field}`);
			}
		}
	}
	return schema;
}

function normalizeResultContract(value: unknown): RlmResultContract | undefined {
	if (value === undefined) return undefined;
	if (!isRecord(value)) throw new Error("rlm.run result must be an object");
	assertKnownKeys(value, ["schema", "sink", "required"], "rlm.run result");
	const sink = value.sink ?? { kind: "file" };
	if (!isRecord(sink) || sink.kind !== "file" || Object.keys(sink).some((key) => key !== "kind")) {
		throw new Error('rlm.run result.sink must be {"kind": "file"}');
	}
	const required = value.required ?? true;
	if (typeof required !== "boolean") throw new Error("rlm.run result.required must be a boolean");
	return { schema: normalizeSchema(value.schema), sink: { kind: "file" }, required };
}

function normalizePayloadPolicy(value: unknown, label: string): RlmPayloadContextPolicy | undefined {
	if (value === undefined) return undefined;
	if (!isRecord(value)) throw new Error(`${label} must be an object`);
	assertKnownKeys(value, ["payload", "notify"], label);
	if (typeof value.payload !== "boolean") throw new Error(`${label}.payload must be a boolean`);
	const notify = value.notify ?? true;
	if (typeof notify !== "boolean") throw new Error(`${label}.notify must be a boolean`);
	if (value.payload && !notify) throw new Error(`${label}.notify cannot be false while payload is true`);
	return { payload: value.payload, notify };
}

function normalizeResultContextPolicy(
	value: unknown,
	result: RlmResultContract | undefined,
): RlmResultContextPolicy | undefined {
	if (value === undefined) return undefined;
	if (!result) throw new Error("rlm.run context.result requires a result contract");
	if (!isRecord(value)) throw new Error("rlm.run context.result must be an object");
	assertKnownKeys(value, ["fields"], "rlm.run context.result");
	if (!Array.isArray(value.fields) || !value.fields.every((field) => typeof field === "string" && field.length > 0)) {
		throw new Error("rlm.run context.result.fields must be an array of non-empty strings");
	}
	const fields = [...new Set(value.fields)];
	if (fields.length !== value.fields.length)
		throw new Error("rlm.run context.result.fields must not contain duplicates");
	const properties = result.schema.properties as Record<string, unknown>;
	for (const field of fields) {
		if (!Object.hasOwn(properties, field)) {
			throw new Error(`rlm.run context.result admits unknown schema field ${field}`);
		}
	}
	return { fields };
}

function normalizeContextContract(
	value: unknown,
	result: RlmResultContract | undefined,
): RlmChildContextContract | undefined {
	if (value === undefined) return undefined;
	if (!isRecord(value)) throw new Error("rlm.run context must be an object");
	assertKnownKeys(value, ["terminal", "direct_messages", "observe", "result"], "rlm.run context");
	if (value.observe !== undefined && typeof value.observe !== "boolean") {
		throw new Error("rlm.run context.observe must be a boolean");
	}
	return {
		...(value.terminal !== undefined
			? { terminal: normalizePayloadPolicy(value.terminal, "rlm.run context.terminal")! }
			: {}),
		...(value.direct_messages !== undefined
			? { direct_messages: normalizePayloadPolicy(value.direct_messages, "rlm.run context.direct_messages")! }
			: {}),
		...(value.observe !== undefined ? { observe: value.observe } : {}),
		...(value.result !== undefined ? { result: normalizeResultContextPolicy(value.result, result)! } : {}),
	};
}

export const DEFAULT_GUARDED_CONTEXT: RlmChildContextContract = {
	terminal: { payload: false, notify: true },
	direct_messages: { payload: false, notify: true },
	observe: false,
};

/**
 * Guarded children communicate through their declared result contract only:
 * terminal and direct-message payloads are withheld behind fixed host notices
 * and observation previews are stripped. Explicit context entries override the
 * corresponding default; unlisted entries inherit the guarded default.
 */
export function normalizeRlmChildContract(
	result: unknown,
	context: unknown,
	options: { guardedByDefault?: boolean } = {},
): RlmChildContract | undefined {
	const normalizedResult = normalizeResultContract(result);
	const guarded = options.guardedByDefault === true && normalizedResult !== undefined;
	const mergedContext = guarded ? { ...DEFAULT_GUARDED_CONTEXT, ...(isRecord(context) ? context : {}) } : context;
	const normalizedContext = normalizeContextContract(mergedContext, normalizedResult);
	if (!normalizedResult && !normalizedContext) return undefined;
	return {
		...(normalizedResult ? { result: normalizedResult } : {}),
		...(normalizedContext ? { context: normalizedContext } : {}),
	};
}

function resultPathForGeneration(sessionDir: string, generation: number): string {
	return join(sessionDir, generation === 0 ? RLM_RESULT_FILE_NAME : `rlm-result-${generation}.json`);
}

function resultSealPathForGeneration(sessionDir: string, generation: number): string {
	return `${resultPathForGeneration(sessionDir, generation)}.settled`;
}

function pathExists(path: string): boolean {
	try {
		lstatSync(path);
		return true;
	} catch (error) {
		if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
		throw error;
	}
}

function preparedForGeneration(prepared: PreparedRlmChildContract, generation: number): PreparedRlmChildContract {
	if (!prepared.contract.result || !prepared.resultState) throw new Error("RLM child has no result contract");
	if (!Number.isSafeInteger(generation) || generation < 0 || generation >= prepared.resultState.next_generation) {
		throw new Error(`RLM result generation ${generation} is not available`);
	}
	return { ...prepared, resultPath: resultPathForGeneration(prepared.sessionDir, generation) };
}

function persistedContract(prepared: PreparedRlmChildContract): PersistedRlmChildContract {
	return {
		format_version: RLM_CONTRACT_FORMAT_VERSION,
		child_id: prepared.childId,
		session_name: prepared.sessionName,
		...(prepared.sessionId ? { session_id: prepared.sessionId } : {}),
		...(prepared.resultState ? { result_state: prepared.resultState } : {}),
		...prepared.contract,
	};
}

function replaceContractFile(path: string, value: PersistedRlmChildContract): void {
	const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
	writeFileSync(
		temporary,
		`${JSON.stringify(value)}
`,
		{ encoding: "utf8", flag: "wx", mode: 0o600 },
	);
	renameSync(temporary, path);
}

export function prepareRlmChildContract(
	sessionDir: string,
	childId: string,
	sessionName: string,
	contract: RlmChildContract | undefined,
): PreparedRlmChildContract | undefined {
	if (!contract) return undefined;
	const resolvedDir = resolve(sessionDir);
	const prepared: PreparedRlmChildContract = {
		childId,
		sessionName,
		sessionDir: resolvedDir,
		contractPath: join(resolvedDir, RLM_CONTRACT_FILE_NAME),
		resultState: { next_generation: 0 },
		...(contract.result ? { resultPath: resultPathForGeneration(resolvedDir, 0) } : {}),
		contract,
	};
	writeFileSync(
		prepared.contractPath,
		`${JSON.stringify(persistedContract(prepared))}
`,
		{
			encoding: "utf8",
			flag: "wx",
			mode: 0o600,
		},
	);
	return prepared;
}

export function bindRlmChildContractSession(
	prepared: PreparedRlmChildContract,
	sessionId: string,
): PreparedRlmChildContract {
	const bound = { ...prepared, sessionId };
	replaceContractFile(bound.contractPath, persistedContract(bound));
	return bound;
}

export function currentRlmResultTurn(prepared: PreparedRlmChildContract): RlmResultTurn | undefined {
	const turn = prepared.resultState?.turn;
	return turn
		? {
				turnKey: turn.turn_key,
				generation: turn.generation,
				path: prepared.contract.result ? resultPathForGeneration(prepared.sessionDir, turn.generation) : "",
				status: turn.status,
				providerCompleted: turn.provider_completed,
				repliedToParent: turn.replied_to_parent,
				noticeAdmitted: turn.notice_admitted,
				...(turn.error ? { error: turn.error } : {}),
			}
		: undefined;
}

export function beginRlmChildResultTurn(
	prepared: PreparedRlmChildContract,
	turnKey: string,
): { prepared: PreparedRlmChildContract; turn: RlmResultTurn } {
	if (!prepared.resultState) throw new Error("RLM child has no persisted turn state");
	if (!turnKey) throw new Error("RLM result turn key must not be empty");
	const current = prepared.resultState.turn;
	if (current?.turn_key === turnKey) {
		const turn = currentRlmResultTurn(prepared)!;
		return { prepared, turn };
	}
	if (current?.status === "active") {
		throw new Error(`RLM result turn ${current.turn_key} is still active`);
	}
	const generation = prepared.resultState.next_generation;
	if (generation >= Number.MAX_SAFE_INTEGER) throw new Error("RLM result generation limit reached");
	const resultPath = prepared.contract.result ? resultPathForGeneration(prepared.sessionDir, generation) : undefined;
	if (
		resultPath &&
		(pathExists(resultPath) || pathExists(resultSealPathForGeneration(prepared.sessionDir, generation)))
	) {
		throw new Error(`RLM result generation ${generation} already exists without a matching turn`);
	}
	const turnState: PersistedRlmResultTurn = {
		turn_key: turnKey,
		generation,
		status: "active",
		provider_completed: false,
		replied_to_parent: false,
		notice_admitted: false,
	};
	const advanced: PreparedRlmChildContract = {
		...prepared,
		resultState: {
			...prepared.resultState,
			next_generation: generation + 1,
			turn: turnState,
		},
		...(resultPath ? { resultPath } : {}),
	};
	replaceContractFile(advanced.contractPath, persistedContract(advanced));
	return { prepared: advanced, turn: currentRlmResultTurn(advanced)! };
}

export function completeRlmChildResultTurn(
	prepared: PreparedRlmChildContract,
	turnKey: string,
	repliedToParent: boolean,
): { prepared: PreparedRlmChildContract; turn: RlmResultTurn } {
	const turn = prepared.resultState?.turn;
	if (!turn || turn.turn_key !== turnKey) throw new Error(`Unknown RLM result turn ${turnKey}`);
	if (turn.status !== "active") return { prepared, turn: currentRlmResultTurn(prepared)! };
	const completed: PreparedRlmChildContract = {
		...prepared,
		resultState: {
			...prepared.resultState!,
			turn: {
				...turn,
				provider_completed: true,
				replied_to_parent: turn.replied_to_parent || repliedToParent,
			},
		},
	};
	replaceContractFile(completed.contractPath, persistedContract(completed));
	return { prepared: completed, turn: currentRlmResultTurn(completed)! };
}

export function markRlmChildResultTurnNotified(
	prepared: PreparedRlmChildContract,
	turnKey: string,
): PreparedRlmChildContract {
	const turn = prepared.resultState?.turn;
	if (!turn || turn.turn_key !== turnKey) throw new Error(`Unknown RLM result turn ${turnKey}`);
	if (turn.notice_admitted) return prepared;
	const notified: PreparedRlmChildContract = {
		...prepared,
		resultState: { ...prepared.resultState!, turn: { ...turn, notice_admitted: true } },
	};
	replaceContractFile(notified.contractPath, persistedContract(notified));
	return notified;
}

export function failRlmChildResultTurn(
	prepared: PreparedRlmChildContract,
	turnKey: string,
	error?: string,
): PreparedRlmChildContract {
	const turn = prepared.resultState?.turn;
	if (!turn || turn.turn_key !== turnKey) throw new Error(`Unknown RLM result turn ${turnKey}`);
	if (turn.status === "settled") return prepared;
	const normalizedError = error?.slice(0, 4096);
	if (turn.status === "failed" && (!normalizedError || turn.error === normalizedError)) return prepared;
	const failed: PreparedRlmChildContract = {
		...prepared,
		resultState: {
			...prepared.resultState!,
			turn: {
				...turn,
				status: "failed",
				...(normalizedError ? { error: normalizedError } : {}),
			},
		},
	};
	replaceContractFile(failed.contractPath, persistedContract(failed));
	return failed;
}

export function loadRlmChildContract(sessionDir: string): PreparedRlmChildContract | undefined {
	const resolvedDir = resolve(sessionDir);
	const contractPath = join(resolvedDir, RLM_CONTRACT_FILE_NAME);
	let raw: string;
	try {
		const stats = lstatSync(contractPath);
		if (!stats.isFile() || stats.isSymbolicLink()) {
			throw new Error(`RLM child contract is not a regular file: ${contractPath}`);
		}
		if (stats.size > MAX_CONTRACT_BYTES) {
			throw new Error(`RLM child contract exceeds ${MAX_CONTRACT_BYTES} bytes`);
		}
		raw = readFileSync(contractPath, "utf8");
	} catch (error) {
		if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
		throw error;
	}
	const value: unknown = JSON.parse(raw);
	if (!isRecord(value) || value.format_version !== RLM_CONTRACT_FORMAT_VERSION) {
		throw new Error(`Invalid RLM child contract at ${contractPath}`);
	}
	assertKnownKeys(
		value,
		["format_version", "child_id", "session_name", "session_id", "result_state", "result", "context"],
		"persisted RLM child contract",
	);
	if (
		typeof value.child_id !== "string" ||
		!value.child_id ||
		typeof value.session_name !== "string" ||
		!value.session_name ||
		(value.session_id !== undefined && (typeof value.session_id !== "string" || !value.session_id))
	) {
		throw new Error(`Invalid RLM child identity at ${contractPath}`);
	}
	const contract = normalizeRlmChildContract(value.result, value.context) ?? {};
	let resultState: PersistedRlmResultState | undefined;
	{
		const rawState = value.result_state ?? { next_generation: 0 };
		if (!isRecord(rawState)) throw new Error(`Invalid RLM result state at ${contractPath}`);
		assertKnownKeys(rawState, ["next_generation", "latest_settled", "turn"], "persisted RLM result state");
		if (!Number.isSafeInteger(rawState.next_generation) || (rawState.next_generation as number) < 0) {
			throw new Error(`Invalid RLM result generation state at ${contractPath}`);
		}
		const nextGeneration = rawState.next_generation as number;
		const latestSettled = rawState.latest_settled;
		if (!contract.result && latestSettled !== undefined) {
			throw new Error(`Result generation without an RLM result contract at ${contractPath}`);
		}
		if (
			latestSettled !== undefined &&
			(!Number.isSafeInteger(latestSettled) ||
				(latestSettled as number) < 0 ||
				(latestSettled as number) >= nextGeneration)
		) {
			throw new Error(`Invalid latest RLM result generation at ${contractPath}`);
		}
		let turn: PersistedRlmResultTurn | undefined;
		if (rawState.turn !== undefined) {
			if (!isRecord(rawState.turn)) throw new Error(`Invalid RLM result turn at ${contractPath}`);
			assertKnownKeys(
				rawState.turn,
				["turn_key", "generation", "status", "provider_completed", "replied_to_parent", "notice_admitted", "error"],
				"persisted RLM result turn",
			);
			if (
				typeof rawState.turn.turn_key !== "string" ||
				!rawState.turn.turn_key ||
				!Number.isSafeInteger(rawState.turn.generation) ||
				(rawState.turn.generation as number) < 0 ||
				(rawState.turn.generation as number) >= nextGeneration ||
				!(["active", "settled", "failed"] as unknown[]).includes(rawState.turn.status) ||
				(rawState.turn.provider_completed !== undefined && typeof rawState.turn.provider_completed !== "boolean") ||
				(rawState.turn.replied_to_parent !== undefined && typeof rawState.turn.replied_to_parent !== "boolean") ||
				(rawState.turn.notice_admitted !== undefined && typeof rawState.turn.notice_admitted !== "boolean") ||
				(rawState.turn.error !== undefined &&
					(typeof rawState.turn.error !== "string" || rawState.turn.error.length > 4096))
			) {
				throw new Error(`Invalid RLM result turn at ${contractPath}`);
			}
			const status = rawState.turn.status as RlmResultTurnStatus;
			turn = {
				turn_key: rawState.turn.turn_key,
				generation: rawState.turn.generation as number,
				status,
				provider_completed:
					rawState.turn.provider_completed === undefined ? status !== "active" : rawState.turn.provider_completed,
				replied_to_parent: rawState.turn.replied_to_parent === true,
				notice_admitted: rawState.turn.notice_admitted === true,
				...(typeof rawState.turn.error === "string" ? { error: rawState.turn.error } : {}),
			};
		}
		resultState = {
			next_generation: nextGeneration,
			...(typeof latestSettled === "number" ? { latest_settled: latestSettled } : {}),
			...(turn ? { turn } : {}),
		};
	}
	const resultGeneration = resultState?.turn?.generation ?? resultState?.next_generation;
	return {
		childId: value.child_id,
		sessionName: value.session_name,
		...(typeof value.session_id === "string" ? { sessionId: value.session_id } : {}),
		sessionDir: resolvedDir,
		contractPath,
		...(resultState ? { resultState } : {}),
		...(contract.result && resultGeneration !== undefined
			? { resultPath: resultPathForGeneration(resolvedDir, resultGeneration) }
			: {}),
		contract,
	};
}

export function formatRlmResultContractInstructions(prepared: PreparedRlmChildContract | undefined): string {
	if (!prepared?.resultPath || !prepared.contract.result) return "";
	const generation = prepared.resultState?.turn?.generation;
	const generationLabel = generation === undefined ? "" : ` for result generation ${generation}`;
	return [
		"",
		"[host result contract]",
		prepared.contract.result.required
			? `Submit exactly one JSON object with \`await rlm.submit_result(value)\`${generationLabel} before finishing.`
			: `Structured result submission is optional${generationLabel}. If you produce one, submit exactly one JSON object with \`await rlm.submit_result(value)\`.`,
		"The host validates and persists any submitted result. It must match this JSON Schema:",
		JSON.stringify(prepared.contract.result.schema),
	].join("\n");
}

function resultValidationError(schema: Record<string, unknown>, value: unknown): string | undefined {
	let valid: boolean;
	try {
		valid = Schema.Check(schema as Schema.XSchema, value);
	} catch (error) {
		throw new Error(
			`RLM result schema could not be evaluated: ${error instanceof Error ? error.message : String(error)}`,
		);
	}
	if (valid) return undefined;
	const [, errors] = Schema.Errors(schema as Schema.XSchema, value);
	return errors
		.slice(0, 3)
		.map((error) => `${error.instancePath || "/"} ${error.message}`)
		.join("; ");
}

export function submitRlmChildResult(prepared: PreparedRlmChildContract, value: unknown): RlmResultSubmission {
	const resultContract = prepared.contract.result;
	const turn = prepared.resultState?.turn;
	if (!prepared.resultPath || !resultContract || !turn) throw new Error("RLM child has no active result turn");
	if (turn.status !== "active") throw new Error(`RLM result turn ${turn.turn_key} is already ${turn.status}`);
	if (!isRecord(value)) throw new Error("RLM result must be a JSON object");
	const normalized = cloneJsonRecord(value, "RLM result", MAX_RESULT_BYTES);
	const validationError = resultValidationError(resultContract.schema, normalized);
	if (validationError !== undefined) {
		throw new Error(`RLM result does not match its schema${validationError ? `: ${validationError}` : ""}`);
	}
	const encoded = Buffer.from(`${JSON.stringify(normalized)}
`);
	try {
		writeFileSync(prepared.resultPath, encoded, { flag: "wx", mode: 0o600 });
	} catch (error) {
		if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
		const existing = readResultFile(prepared.resultPath);
		if (!existing.equals(encoded)) {
			throw new Error(`RLM result generation ${turn.generation} was already submitted with different content`);
		}
	}
	return {
		child_id: prepared.childId,
		generation: turn.generation,
		path: prepared.resultPath,
		sha256: createHash("sha256").update(encoded).digest("hex"),
	};
}

function readResultFile(path: string): Buffer {
	const stats = lstatSync(path);
	if (!stats.isFile() || stats.isSymbolicLink()) throw new Error(`RLM result is not a regular file: ${path}`);
	if (stats.size > MAX_RESULT_BYTES) throw new Error(`RLM result exceeds ${MAX_RESULT_BYTES} bytes`);
	return readFileSync(path);
}

function validatedRlmChildResult(prepared: PreparedRlmChildContract): {
	bytes: Buffer;
	path: string;
	value: Record<string, unknown>;
} {
	const resultContract = prepared.contract.result;
	if (!prepared.resultPath || !resultContract) throw new Error("RLM child has no result contract");
	const bytes = readResultFile(prepared.resultPath);
	let value: unknown;
	try {
		value = JSON.parse(bytes.toString("utf8"));
	} catch (error) {
		throw new Error(`RLM result is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
	}
	assertJsonValue(value, "RLM result");
	if (!isRecord(value)) throw new Error("RLM result must be a JSON object");
	const validationError = resultValidationError(resultContract.schema, value);
	if (validationError !== undefined) {
		throw new Error(`RLM result does not match its schema${validationError ? `: ${validationError}` : ""}`);
	}
	return { bytes, path: prepared.resultPath, value };
}

function resultSeal(prepared: PreparedRlmChildContract, generation: number, sha256: string): Buffer {
	return Buffer.from(
		`${JSON.stringify({
			child_id: prepared.childId,
			...(prepared.sessionId ? { session_id: prepared.sessionId } : {}),
			generation,
			sha256,
		})}
`,
	);
}

function assertSettledResult(prepared: PreparedRlmChildContract, generation: number, bytes: Buffer): void {
	const sealPath = resultSealPathForGeneration(prepared.sessionDir, generation);
	const seal = readResultFile(sealPath);
	const expected = resultSeal(prepared, generation, createHash("sha256").update(bytes).digest("hex"));
	if (!seal.equals(expected)) throw new Error(`RLM result generation ${generation} has an invalid settlement seal`);
}

export function settleRlmChildResultTurn(prepared: PreparedRlmChildContract, turnKey: string): RlmResultTurnSettlement {
	const turn = prepared.resultState?.turn;
	if (!turn || turn.turn_key !== turnKey) throw new Error(`Unknown RLM result turn ${turnKey}`);
	if (turn.status === "settled") return { prepared, turn: currentRlmResultTurn(prepared)! };
	if (turn.status === "failed") {
		return { prepared, turn: currentRlmResultTurn(prepared)!, error: `RLM result turn ${turnKey} already failed` };
	}
	if (!prepared.contract.result) {
		const settled: PreparedRlmChildContract = {
			...prepared,
			resultState: {
				...prepared.resultState!,
				turn: { ...turn, status: "settled" },
			},
		};
		replaceContractFile(settled.contractPath, persistedContract(settled));
		return { prepared: settled, turn: currentRlmResultTurn(settled)! };
	}
	let validated: ReturnType<typeof validatedRlmChildResult> | undefined;
	try {
		validated = validatedRlmChildResult(preparedForGeneration(prepared, turn.generation));
	} catch (error) {
		const missing = error instanceof Error && "code" in error && error.code === "ENOENT";
		if (!missing || prepared.contract.result?.required) {
			const message = error instanceof Error ? error.message : String(error);
			const failed = failRlmChildResultTurn(prepared, turnKey, message);
			return {
				prepared: failed,
				turn: currentRlmResultTurn(failed)!,
				error: message,
			};
		}
	}
	if (validated) {
		const sha256 = createHash("sha256").update(validated.bytes).digest("hex");
		const sealPath = resultSealPathForGeneration(prepared.sessionDir, turn.generation);
		const encodedSeal = resultSeal(prepared, turn.generation, sha256);
		try {
			writeFileSync(sealPath, encodedSeal, { flag: "wx", mode: 0o600 });
		} catch (error) {
			if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
			const existing = readResultFile(sealPath);
			if (!existing.equals(encodedSeal)) throw new Error(`RLM result generation ${turn.generation} seal conflicts`);
		}
	}
	const settled: PreparedRlmChildContract = {
		...prepared,
		resultState: {
			...prepared.resultState!,
			...(validated ? { latest_settled: turn.generation } : {}),
			turn: { ...turn, status: "settled" },
		},
	};
	replaceContractFile(settled.contractPath, persistedContract(settled));
	return { prepared: settled, turn: currentRlmResultTurn(settled)! };
}

export function validateRlmChildResult(prepared: PreparedRlmChildContract, generation?: number): void {
	const selectedGeneration = generation ?? prepared.resultState?.latest_settled;
	if (selectedGeneration === undefined) throw new Error("RLM child has no settled result");
	const selected = preparedForGeneration(prepared, selectedGeneration);
	const { bytes } = validatedRlmChildResult(selected);
	assertSettledResult(prepared, selectedGeneration, bytes);
}

export function readRlmChildResult(
	prepared: PreparedRlmChildContract,
	requestedFields?: readonly string[],
	generation?: number,
): RlmResultRead {
	const resultPolicy = prepared.contract.context?.result;
	if (!resultPolicy) throw new Error("RLM child contract does not admit result fields into parent context");
	const fields = requestedFields ?? resultPolicy.fields;
	if (new Set(fields).size !== fields.length || fields.some((field) => !resultPolicy.fields.includes(field))) {
		throw new Error("Requested RLM result fields exceed the child context contract");
	}
	const selectedGeneration = generation ?? prepared.resultState?.latest_settled;
	if (selectedGeneration === undefined) throw new Error("RLM child has no settled result");
	const selected = preparedForGeneration(prepared, selectedGeneration);
	const { bytes, path, value } = validatedRlmChildResult(selected);
	assertSettledResult(prepared, selectedGeneration, bytes);
	const projected = Object.fromEntries(
		fields.filter((field) => Object.hasOwn(value, field)).map((field) => [field, value[field]]),
	);
	if (Buffer.byteLength(JSON.stringify(projected)) > MAX_PROJECTED_RESULT_BYTES) {
		throw new Error(`Projected RLM result exceeds ${MAX_PROJECTED_RESULT_BYTES} bytes`);
	}
	return {
		child_id: prepared.childId,
		generation: selectedGeneration,
		path,
		sha256: createHash("sha256").update(bytes).digest("hex"),
		result: projected,
	};
}
