#!/usr/bin/env node
/**
 * Enforce valid JSON everywhere and schemaVersion markers on SF-owned contracts.
 *
 * Ecosystem JSON such as package.json, tsconfig.json, lockfiles, and extension
 * manifests are parsed for validity but are not treated as SF data contracts.
 * Their `version` fields belong to their owning tools or component release
 * lifecycle. SF-owned runtime/data contracts use `schemaVersion` for shape
 * compatibility.
 */

import { execFileSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";

const CONTRACT_EXACT_PATHS = new Set([
	"src/resources/extensions/sf/workflow-templates/registry.json",
]);

const CONTRACT_PREFIXES = ["src/resources/extensions/sf/learning/data/"];

function trackedJsonFiles() {
	try {
		const out = execFileSync("git", ["ls-files", "*.json"], {
			encoding: "utf8",
			stdio: ["ignore", "pipe", "pipe"],
		});
		return out
			.split("\n")
			.map((line) => line.trim())
			.filter((line) => line && existsSync(line));
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		throw new Error(`failed to list tracked JSON files: ${message}`);
	}
}

export function isSfOwnedJsonContract(path) {
	return (
		CONTRACT_EXACT_PATHS.has(path) ||
		CONTRACT_PREFIXES.some((prefix) => path.startsWith(prefix))
	);
}

export function hasOwn(object, key) {
	return Object.hasOwn(object, key);
}

export function getSchemaVersion(parsed) {
	if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
		return false;
	if (hasOwn(parsed, "schemaVersion")) return parsed.schemaVersion;

	const meta = parsed._meta;
	if (
		meta &&
		typeof meta === "object" &&
		!Array.isArray(meta) &&
		hasOwn(meta, "schemaVersion")
	) {
		return meta.schemaVersion;
	}

	return undefined;
}

export function hasValidSchemaVersion(parsed) {
	const schemaVersion = getSchemaVersion(parsed);
	return (
		typeof schemaVersion === "number" &&
		Number.isInteger(schemaVersion) &&
		schemaVersion >= 1
	);
}

export function checkJsonPolicy(paths, readText) {
	const failures = [];
	let contractsChecked = 0;
	let filesParsed = 0;

	for (const path of paths) {
		filesParsed++;

		let parsed;
		try {
			parsed = JSON.parse(readText(path));
		} catch (error) {
			if (
				error &&
				typeof error === "object" &&
				"code" in error &&
				error.code === "ENOENT"
			) {
				filesParsed--;
				continue;
			}
			const message = error instanceof Error ? error.message : String(error);
			failures.push(`${path}: invalid JSON (${message})`);
			continue;
		}

		if (!isSfOwnedJsonContract(path)) continue;
		contractsChecked++;

		if (!hasValidSchemaVersion(parsed)) {
			failures.push(
				`${path}: missing numeric schemaVersion marker (top-level or _meta)`,
			);
		}
	}

	return { failures, filesParsed, contractsChecked };
}

export function run() {
	const result = checkJsonPolicy(trackedJsonFiles(), (path) =>
		readFileSync(path, "utf8"),
	);

	if (result.failures.length > 0) {
		console.error("Versioned JSON check failed:");
		for (const failure of result.failures) {
			console.error(`  - ${failure}`);
		}
		process.exit(1);
	}

	console.log(
		`Versioned JSON check passed (${result.filesParsed} JSON file${result.filesParsed === 1 ? "" : "s"} parsed, ` +
			`${result.contractsChecked} SF contract${result.contractsChecked === 1 ? "" : "s"} checked).`,
	);
}

if (import.meta.url === `file://${process.argv[1]}`) {
	run();
}
