/**
 * context-pack-projection.js - worker context pack projection shapes.
 *
 * Purpose: expose a Droid-like worker context pack view without making that
 * view executable or canonical; SF DB/runtime records remain the source of
 * truth for dispatch, validation, and lifecycle state.
 *
 * Consumer: UOK projection tests and future worker prompt/rendering adapters.
 */

const PROJECTION_SCHEMA_VERSION = 1;
const REQUIRED_SECTIONS = [
	"services",
	"validationContract",
	"unitContract",
	"workerSkill",
	"library",
];

function stringOrNull(value) {
	return typeof value === "string" && value.trim().length > 0
		? value.trim()
		: null;
}

function stringList(value) {
	if (Array.isArray(value)) {
		return value
			.map((item) => stringOrNull(item))
			.filter((item) => item !== null);
	}
	const single = stringOrNull(value);
	return single === null ? [] : [single];
}

function idList(...values) {
	return [...new Set(values.flatMap((value) => stringList(value)))];
}

function objectOrEmpty(value) {
	return value && typeof value === "object" && !Array.isArray(value)
		? value
		: {};
}

/**
 * Builds a Droid-like worker context pack projection from a unit or feature contract.
 *
 * Purpose: give workers a stable read model with service, validation, unit,
 * skill, and library sections while preventing prompt-facing context from
 * becoming authoritative SF state.
 *
 * Consumer: worker context pack tests and future prompt projection adapters.
 *
 * @param {Record<string, unknown>} contract
 * @returns {Record<string, unknown>}
 */
export function buildWorkerContextPackProjection(contract = {}) {
	const source = objectOrEmpty(contract);
	const validation = objectOrEmpty(source.validationContract);
	const unit = objectOrEmpty(source.unitContract);
	const workerSkill = objectOrEmpty(source.workerSkill);
	const library = objectOrEmpty(source.library);
	const services = objectOrEmpty(source.services);

	const fulfills = idList(
		source.fulfills,
		source.fulfillmentIds,
		unit.fulfills,
	);
	const assertions = idList(
		source.assertions,
		source.assertionIds,
		validation.assertions,
		validation.assertionIds,
	);
	const allowedCommands = idList(
		source.allowedCommands,
		services.allowedCommands,
		workerSkill.allowedCommands,
	);
	const allowedServices = idList(
		source.allowedServices,
		services.allowedServices,
		workerSkill.allowedServices,
	);

	return {
		schemaVersion: PROJECTION_SCHEMA_VERSION,
		origin: "worker-context-pack-projection",
		canonicalState: {
			source: "sf-db",
			runtimeAuthority: "sf-runtime",
			projectionOnly: true,
			note: "This object is a worker-facing projection only; DB/runtime state remains canonical.",
		},
		unitRef: {
			milestoneId: stringOrNull(source.milestoneId),
			sliceId: stringOrNull(source.sliceId),
			taskId: stringOrNull(source.taskId),
			unitId: stringOrNull(source.unitId ?? unit.unitId),
			unitType: stringOrNull(source.unitType ?? unit.unitType),
			featureId: stringOrNull(source.featureId),
		},
		services: {
			allowedServices,
			allowedCommands,
			disallowedByDefault: true,
			notes: stringList(services.notes ?? source.serviceNotes),
		},
		validationContract: {
			preconditions: stringList(
				validation.preconditions ?? source.preconditions,
			),
			expectedBehavior: stringList(
				validation.expectedBehavior ?? source.expectedBehavior,
			),
			verificationSteps: stringList(
				validation.verificationSteps ?? source.verificationSteps,
			),
			assertionIds: assertions,
			fulfills,
		},
		unitContract: {
			purpose: stringOrNull(unit.purpose ?? source.purpose),
			consumer: stringOrNull(unit.consumer ?? source.consumer),
			preconditions: stringList(unit.preconditions ?? source.preconditions),
			expectedBehavior: stringList(
				unit.expectedBehavior ?? source.expectedBehavior,
			),
			verificationSteps: stringList(
				unit.verificationSteps ?? source.verificationSteps,
			),
			fulfills,
			assertionIds: assertions,
		},
		workerSkill: {
			id: stringOrNull(workerSkill.id ?? source.workerSkillId),
			name: stringOrNull(workerSkill.name ?? source.workerSkillName),
			instructions: stringList(
				workerSkill.instructions ?? source.workerInstructions,
			),
			allowedCommands,
			allowedServices,
		},
		library: {
			references: stringList(library.references ?? source.references),
			files: stringList(library.files ?? source.files),
			assumptions: stringList(library.assumptions ?? source.assumptions),
		},
	};
}

/**
 * Validates the worker context pack projection shape without reading runtime state.
 *
 * Purpose: catch malformed projection objects before a prompt adapter consumes
 * them while keeping DB/runtime records authoritative for actual execution.
 *
 * Consumer: worker context pack tests and future prompt projection adapters.
 *
 * @param {unknown} projection
 * @returns {{ valid: true; issues: [] } | { valid: false; issues: string[] }}
 */
export function validateWorkerContextPackProjection(projection) {
	const issues = [];
	if (
		!projection ||
		typeof projection !== "object" ||
		Array.isArray(projection)
	) {
		return { valid: false, issues: ["projection must be an object"] };
	}
	const pack = projection;
	if (pack.schemaVersion !== PROJECTION_SCHEMA_VERSION) {
		issues.push("schemaVersion must be 1");
	}
	if (pack.origin !== "worker-context-pack-projection") {
		issues.push("origin must be worker-context-pack-projection");
	}
	if (pack.canonicalState?.projectionOnly !== true) {
		issues.push("canonicalState.projectionOnly must be true");
	}
	if (pack.canonicalState?.source !== "sf-db") {
		issues.push("canonicalState.source must be sf-db");
	}
	for (const section of REQUIRED_SECTIONS) {
		if (!pack[section] || typeof pack[section] !== "object") {
			issues.push(`${section} section is required`);
		}
	}
	for (const [section, fields] of [
		["services", ["allowedServices", "allowedCommands"]],
		[
			"validationContract",
			[
				"preconditions",
				"expectedBehavior",
				"verificationSteps",
				"assertionIds",
				"fulfills",
			],
		],
		[
			"unitContract",
			[
				"preconditions",
				"expectedBehavior",
				"verificationSteps",
				"assertionIds",
				"fulfills",
			],
		],
	]) {
		const value = pack[section];
		if (!value || typeof value !== "object") continue;
		for (const field of fields) {
			if (!Array.isArray(value[field])) {
				issues.push(`${section}.${field} must be an array`);
			}
		}
	}
	return issues.length === 0
		? { valid: true, issues: [] }
		: { valid: false, issues };
}
