// The decisions behind `npm run assert-bundled`, kept separate from the script
// that does the I/O so they can be unit-tested (milestone-14 deviation 24).
//
// The licence inventory has to cover every package whose bytes ship, and the
// lockfile does not answer that question: it lists what could be reached, not
// what rollup kept. Several packages of the non-dev closure are recorded as
// tree-shaken away, and if application code starts importing one of them, no
// lockfile, no version and no dependency set changes — only the bundle does. So
// the bundle is what this reads.

const sectionHeading = "[npm packages bundled into admin/dist]";

// A sourcemap `sources` entry for a dependency ends in
// `node_modules/<name>/<file>` or `node_modules/@<scope>/<name>/<file>`. Only
// the last `node_modules/` matters: a nested dependency's path carries two.
export function packageFromSource(source) {
	const marker = "node_modules/";
	const at = source.lastIndexOf(marker);
	if (at === -1) return null;
	const rest = source.slice(at + marker.length);
	const parts = rest.split("/");
	if (parts.length === 0 || parts[0] === "") return null;
	if (parts[0].startsWith("@")) {
		if (parts.length < 2 || parts[1] === "") return null;
		return `${parts[0]}/${parts[1]}`;
	}
	return parts[0];
}

/// The sorted, deduplicated package set of a list of sourcemap `sources` arrays.
export function bundledPackages(sourceLists) {
	const found = new Set();
	for (const sources of sourceLists) {
		for (const source of sources) {
			const name = packageFromSource(source);
			if (name !== null) found.add(name);
		}
	}
	return [...found].sort();
}

/// The recorded section of `licenses/dependency-identity.txt`: every non-blank
/// line after the heading, up to the next `[section]`.
export function recordedPackages(text) {
	const recorded = new Set();
	let grabbing = false;
	for (const raw of text.split("\n")) {
		const line = raw.trim();
		if (!grabbing) {
			if (line === sectionHeading) grabbing = true;
			continue;
		}
		if (line.startsWith("[")) break;
		if (line !== "") recorded.add(line);
	}
	return grabbing ? [...recorded].sort() : null;
}

/// What changed, in the two directions that mean different things: a package
/// that started shipping needs a licence decision, and one that stopped needs
/// the record corrected.
export function comparePackages(recorded, bundled) {
	const inBundle = new Set(bundled);
	const inRecord = new Set(recorded);
	return {
		added: bundled.filter((name) => !inRecord.has(name)),
		removed: recorded.filter((name) => !inBundle.has(name)),
	};
}

export function formatDiff(recorded, bundled) {
	const { added, removed } = comparePackages(recorded, bundled);
	const lines = [];
	for (const name of removed) lines.push(`-${name}`);
	for (const name of added) lines.push(`+${name}`);
	return lines.join("\n");
}
