import { PACKAGE_VERSION, init_package_version } from "../package-version.js";
import { channelToNpmTag } from "./update-channels.js";
import { access, readFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
//#region src/infra/update-check.ts
init_package_version();
const REGISTRY_BASE = "https://registry.npmjs.org";
const PACKAGE_NAME = "@xopcai/xopc";
const REGISTRY_TIMEOUT_MS = 3500;
const MAX_REGISTRY_RETRIES = 2;
const INITIAL_REGISTRY_RETRY_DELAY_MS = 500;
/**
* Fetch the version published under a specific npm dist-tag.
* Uses the abbreviated packument endpoint: `GET /<pkg>/<tag>`.
*/
async function fetchNpmTagVersion(params) {
	const timeoutMs = params.timeoutMs ?? REGISTRY_TIMEOUT_MS;
	const url = `${REGISTRY_BASE}/${encodeURIComponent(PACKAGE_NAME).replace("%40", "@")}/${encodeURIComponent(params.tag)}`;
	let lastError;
	for (let attempt = 0; attempt <= MAX_REGISTRY_RETRIES; attempt++) {
		if (attempt > 0) {
			const delayMs = INITIAL_REGISTRY_RETRY_DELAY_MS * Math.pow(2, attempt - 1);
			await new Promise((r) => setTimeout(r, delayMs));
		}
		try {
			const response = await fetch(url, { signal: AbortSignal.timeout(timeoutMs) });
			if (!response.ok) {
				lastError = `HTTP ${response.status}`;
				if (response.status >= 400 && response.status < 500) return {
					tag: params.tag,
					version: null,
					error: lastError
				};
				continue;
			}
			const json = await response.json();
			const version = typeof json?.version === "string" ? json.version : null;
			return {
				tag: params.tag,
				version
			};
		} catch (err) {
			lastError = String(err);
		}
	}
	return {
		tag: params.tag,
		version: null,
		error: lastError
	};
}
/**
* Resolve the best version for the given update channel.
* For beta channel: if the beta tag version is older than latest, return latest instead.
*/
async function resolveNpmChannelTag(params) {
	const channelTag = channelToNpmTag(params.channel);
	const channelResult = await fetchNpmTagVersion({
		tag: channelTag,
		timeoutMs: params.timeoutMs
	});
	if (params.channel !== "beta") return {
		tag: channelTag,
		version: channelResult.version
	};
	const latestResult = await fetchNpmTagVersion({
		tag: "latest",
		timeoutMs: params.timeoutMs
	});
	if (!latestResult.version) return {
		tag: channelTag,
		version: channelResult.version
	};
	if (!channelResult.version) return {
		tag: "latest",
		version: latestResult.version
	};
	const comparison = compareSemver(channelResult.version, latestResult.version);
	if (comparison !== null && comparison < 0) return {
		tag: "latest",
		version: latestResult.version
	};
	return {
		tag: channelTag,
		version: channelResult.version
	};
}
/**
* Parse a version string into comparable numeric parts.
* Handles formats like "1.2.3", "1.2.3-beta.1".
* Returns null for unparseable strings.
*/
function parseSemverParts(version) {
	const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/.exec(version.trim());
	if (!match) return null;
	return {
		major: parseInt(match[1], 10),
		minor: parseInt(match[2], 10),
		patch: parseInt(match[3], 10),
		prerelease: match[4] ?? null
	};
}
/**
* Compare two semver strings. Returns:
* -1 if a < b, 0 if equal, 1 if a > b, null if either is unparseable.
* Prerelease versions are considered older than the same version without prerelease.
*/
function compareSemver(a, b) {
	if (!a || !b) return null;
	const parsedA = parseSemverParts(a);
	const parsedB = parseSemverParts(b);
	if (!parsedA || !parsedB) return null;
	for (const field of [
		"major",
		"minor",
		"patch"
	]) if (parsedA[field] !== parsedB[field]) return parsedA[field] < parsedB[field] ? -1 : 1;
	if (parsedA.prerelease === null && parsedB.prerelease === null) return 0;
	if (parsedA.prerelease !== null && parsedB.prerelease === null) return -1;
	if (parsedA.prerelease === null && parsedB.prerelease !== null) return 1;
	return comparePrereleaseIdentifiers(parsedA.prerelease, parsedB.prerelease);
}
/**
* Compare prerelease strings per semver 2.0 §11.
*/
function comparePrereleaseIdentifiers(a, b) {
	const partsA = a.split(".");
	const partsB = b.split(".");
	const length = Math.min(partsA.length, partsB.length);
	for (let i = 0; i < length; i++) {
		const segA = partsA[i];
		const segB = partsB[i];
		if (segA === segB) continue;
		const numA = /^\d+$/.test(segA) ? parseInt(segA, 10) : null;
		const numB = /^\d+$/.test(segB) ? parseInt(segB, 10) : null;
		if (numA !== null && numB !== null) return numA < numB ? -1 : 1;
		if (numA !== null && numB === null) return -1;
		if (numA === null && numB !== null) return 1;
		return segA < segB ? -1 : 1;
	}
	if (partsA.length !== partsB.length) return partsA.length < partsB.length ? -1 : 1;
	return 0;
}
/**
* Detect whether the current process is running from a git checkout or npm-installed package.
* Checks for `.git` directory in the package root.
*/
async function detectInstallKind(packageRoot) {
	try {
		await access(join(packageRoot, ".git"));
		return "git";
	} catch {
		try {
			await access(join(packageRoot, "package.json"));
			return "package";
		} catch {
			return "unknown";
		}
	}
}
/**
* Resolve the xopc package root directory.
* Walks up from this file to find package.json with name @xopcai/xopc.
*/
async function resolvePackageRoot() {
	let current = dirname(fileURLToPath(import.meta.url));
	for (let depth = 0; depth < 20; depth++) {
		const pkgPath = join(current, "package.json");
		try {
			const raw = await readFile(pkgPath, "utf-8");
			const parsed = JSON.parse(raw);
			if (typeof parsed.name === "string" && parsed.name === PACKAGE_NAME) return current;
		} catch {}
		const parent = dirname(current);
		if (parent === current) return null;
		current = parent;
	}
	return null;
}
/** Get the current running version from package.json. */
function getCurrentVersion() {
	return PACKAGE_VERSION;
}
//#endregion
export { compareSemver, detectInstallKind, fetchNpmTagVersion, getCurrentVersion, resolveNpmChannelTag, resolvePackageRoot };

//# sourceMappingURL=update-check.js.map