const CORE_PACKAGE = '@actuate-media/cms-core';
const ADMIN_PACKAGE = '@actuate-media/cms-admin';
const NPM_REGISTRY = 'https://registry.npmjs.org';
const DEFAULT_UPDATE_SERVER = 'https://updates.actuatecms.com';
const FETCH_TIMEOUT_MS = 10_000;
/**
 * Resolve the update status with the production-grade hybrid strategy:
 * query the Actuate update server first (rich, real release notes derived from
 * the published CHANGELOG), and transparently fall back to a direct npm
 * registry check if the server is unreachable or misconfigured. The server URL
 * is a fixed, trusted host (overridable via `ACTUATE_UPDATE_SERVER_URL`) — never
 * user-supplied — so this is not an SSRF surface.
 */
export async function getUpdateStatus(currentVersion) {
    const serverUrl = (process.env.ACTUATE_UPDATE_SERVER_URL ?? DEFAULT_UPDATE_SERVER).trim();
    if (serverUrl) {
        const viaServer = await checkForUpdatesViaServer(currentVersion, serverUrl);
        if (viaServer)
            return viaServer;
    }
    const viaNpm = await checkForUpdates(currentVersion);
    return { ...viaNpm, source: 'npm' };
}
/**
 * Query the update server. Returns null on ANY failure (network, timeout,
 * non-2xx, malformed body) so the caller falls back to the npm check.
 */
export async function checkForUpdatesViaServer(currentVersion, serverUrl) {
    // The server only accepts a bare `x.y.z`; strip any pre-release/build suffix.
    const normalized = currentVersion.match(/^\d+\.\d+\.\d+/)?.[0];
    if (!normalized)
        return null;
    try {
        const url = new URL('/api/updates', serverUrl);
        url.searchParams.set('currentVersion', normalized);
        const res = await fetch(url.toString(), {
            headers: { Accept: 'application/json' },
            signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
        });
        if (!res.ok)
            return null;
        const data = (await res.json());
        if (!data.latestVersion)
            return null;
        if (data.upToDate || compareSemver(data.latestVersion, currentVersion) <= 0) {
            return {
                current: currentVersion,
                latest: data.latestVersion,
                updateAvailable: false,
                source: 'update-server',
            };
        }
        return {
            current: currentVersion,
            latest: data.latestVersion,
            updateAvailable: true,
            severity: data.severity ?? determineSeverity(currentVersion, data.latestVersion),
            releaseDate: data.changelog?.[0]?.date || undefined,
            changelog: data.changelog,
            updateCommand: data.updateCommand ??
                `npm update ${CORE_PACKAGE} ${ADMIN_PACKAGE} && npx prisma migrate deploy`,
            breakingChanges: data.breakingChanges,
            hasMigrations: data.hasMigrations,
            pluginCompatibility: data.pluginCompatibility,
            source: 'update-server',
        };
    }
    catch {
        return null;
    }
}
/**
 * Check the npm registry directly for the latest published version.
 * No external update server needed — works everywhere.
 */
export async function checkForUpdates(currentVersion) {
    const res = await fetch(`${NPM_REGISTRY}/${encodeURIComponent(CORE_PACKAGE)}`, {
        headers: { Accept: 'application/json' },
        signal: AbortSignal.timeout(10_000),
    });
    if (!res.ok) {
        throw new Error(`Update check failed: ${res.status}`);
    }
    const data = (await res.json());
    const latest = data['dist-tags'].latest;
    const updateAvailable = compareSemver(latest, currentVersion) > 0;
    let changelog;
    if (updateAvailable) {
        const newVersions = Object.keys(data.versions)
            .filter((v) => /^\d+\.\d+\.\d+$/.test(v))
            .filter((v) => compareSemver(v, currentVersion) > 0)
            .sort((a, b) => compareSemver(b, a))
            .slice(0, 20);
        changelog = newVersions.map((v) => ({
            version: v,
            date: data.time[v]?.split('T')[0] ?? '',
            summary: `Update to ${v}`,
        }));
    }
    return {
        current: currentVersion,
        latest,
        updateAvailable,
        severity: updateAvailable ? determineSeverity(currentVersion, latest) : undefined,
        releaseDate: data.time[latest]?.split('T')[0],
        changelog,
        updateCommand: updateAvailable
            ? `npm update ${CORE_PACKAGE} ${ADMIN_PACKAGE} && npx prisma migrate deploy`
            : undefined,
    };
}
/** Parse a semver string into its components. */
export function parseSemver(version) {
    const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
    if (!match)
        return null;
    return {
        major: parseInt(match[1], 10),
        minor: parseInt(match[2], 10),
        patch: parseInt(match[3], 10),
    };
}
function compareSemver(a, b) {
    const pa = a.split('.').map(Number);
    const pb = b.split('.').map(Number);
    for (let i = 0; i < 3; i++) {
        const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
        if (diff !== 0)
            return diff;
    }
    return 0;
}
function determineSeverity(current, latest) {
    const cur = parseSemver(current);
    const lat = parseSemver(latest);
    if (!cur || !lat)
        return 'patch';
    if (lat.major > cur.major)
        return 'major';
    if (lat.minor > cur.minor)
        return 'minor';
    return 'patch';
}
//# sourceMappingURL=version-check.js.map