/**
 * assertion-coverage.js - validation assertion coverage helpers.
 *
 * Purpose: preserve Droid's useful `fulfills: ["VAL-*"]` accounting as
 * structured SF data so units can prove which validation claims they satisfy.
 *
 * Consumer: autonomous unit contract projections, handoff validation, and
 * future closeout gates before marking a unit complete.
 */

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 uniqueIds(...values) {
	return [...new Set(values.flatMap((value) => stringList(value)))];
}

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

/**
 * Normalize assertion IDs required by a unit contract.
 *
 * Purpose: let SF accept Droid-style `fulfills` as well as SF-style
 * `assertionIds` while projecting one canonical required assertion list.
 *
 * Consumer: assertion coverage checks and worker context-pack builders.
 */
export function requiredAssertionIdsFromContract(contract = {}) {
	const source = objectOrEmpty(contract);
	const validationContract = objectOrEmpty(source.validationContract);
	const unitContract = objectOrEmpty(source.unitContract);
	return uniqueIds(
		source.assertions,
		source.assertionIds,
		source.fulfills,
		source.fulfillmentIds,
		validationContract.assertions,
		validationContract.assertionIds,
		validationContract.fulfills,
		unitContract.assertions,
		unitContract.assertionIds,
		unitContract.fulfills,
	);
}

/**
 * Normalize assertion IDs fulfilled by a unit handoff.
 *
 * Purpose: give closeout gates one canonical fulfilled assertion list even
 * when agents use Droid-style or SF-style field names.
 *
 * Consumer: assertion coverage checks and autonomous closeout validation.
 */
export function fulfilledAssertionIdsFromHandoff(handoff = {}) {
	const source = objectOrEmpty(handoff);
	return uniqueIds(
		source.fulfilledAssertions,
		source.assertionsFulfilled,
		source.fulfills,
		source.assertionIds,
	);
}

/**
 * Compare a unit contract with a worker handoff and report assertion coverage.
 *
 * Purpose: prevent units from being treated as complete when their structured
 * handoff did not cover every validation claim the unit contract required.
 *
 * Consumer: future autonomous closeout gates and tests that need deterministic
 * validation coverage evidence without parsing prose.
 */
export function assessAssertionCoverage(contract = {}, handoff = {}) {
	const required = requiredAssertionIdsFromContract(contract);
	const fulfilled = fulfilledAssertionIdsFromHandoff(handoff);
	const fulfilledSet = new Set(fulfilled);
	const missing = required.filter((id) => !fulfilledSet.has(id));
	const extra = fulfilled.filter((id) => !required.includes(id));
	return {
		ok: missing.length === 0,
		required,
		fulfilled,
		missing,
		extra,
	};
}
