import { spawn } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
import os from "node:os";
const PACKAGE_NAME = "@prestyj/cli";
const REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
const CHECK_INTERVAL_MS = 1 * 60 * 60 * 1000; // 1 hour
const FETCH_TIMEOUT_MS = 10_000; // 10s — npm can be slow
var PackageManager;
(function (PackageManager) {
    PackageManager["NPM"] = "npm";
    PackageManager["PNPM"] = "pnpm";
    PackageManager["YARN"] = "yarn";
    PackageManager["UNKNOWN"] = "unknown";
})(PackageManager || (PackageManager = {}));
function getStateFilePath() {
    return path.join(os.homedir(), ".ezcoder", "update-state.json");
}
function readState() {
    try {
        const raw = fs.readFileSync(getStateFilePath(), "utf-8");
        return JSON.parse(raw);
    }
    catch {
        return null;
    }
}
function writeState(state) {
    try {
        const dir = path.dirname(getStateFilePath());
        fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(getStateFilePath(), JSON.stringify(state));
    }
    catch {
        // Non-fatal
    }
}
function compareVersions(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 detectInstallInfo() {
    const scriptPath = (process.argv[1] ?? "").replace(/\\/g, "/");
    // npx — skip (ephemeral)
    if (scriptPath.includes("/_npx/")) {
        return { packageManager: PackageManager.UNKNOWN, updateCommand: null };
    }
    // pnpm global
    if (scriptPath.includes("/.pnpm") || scriptPath.includes("/pnpm/global")) {
        return {
            packageManager: PackageManager.PNPM,
            updateCommand: `pnpm add -g ${PACKAGE_NAME}@latest`,
        };
    }
    // yarn global
    if (scriptPath.includes("/.yarn/") || scriptPath.includes("/yarn/global")) {
        return {
            packageManager: PackageManager.YARN,
            updateCommand: `yarn global add ${PACKAGE_NAME}@latest`,
        };
    }
    // npm global (default)
    return {
        packageManager: PackageManager.NPM,
        updateCommand: `npm install -g ${PACKAGE_NAME}@latest`,
    };
}
/**
 * Fetch latest version from npm registry asynchronously.
 */
async function fetchLatestVersion() {
    try {
        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
        const response = await fetch(REGISTRY_URL, { signal: controller.signal });
        clearTimeout(timeout);
        const data = (await response.json());
        const version = data.version?.trim();
        return version && /^\d+\.\d+\.\d+/.test(version) ? version : null;
    }
    catch {
        return null;
    }
}
/**
 * Perform the update in a detached background process so it doesn't block
 * or interfere with the current CLI session. The update takes effect on
 * the next launch.
 */
function performUpdateInBackground(command) {
    try {
        const parts = command.split(" ");
        const child = spawn(parts[0], parts.slice(1), {
            detached: true,
            stdio: "ignore",
            env: { ...process.env, npm_config_loglevel: "silent" },
        });
        child.unref();
    }
    catch {
        // Non-fatal — will retry next startup
    }
}
/**
 * Check for updates at CLI startup. Two-phase approach:
 *
 * Phase 1 (instant): If a previous background check found a newer version,
 *   kick off a background update now. Non-blocking.
 *
 * Phase 2 (async): Fire off a background version check for the *next* startup.
 *
 * Returns a message to display, or null.
 */
export function checkAndAutoUpdate(currentVersion) {
    try {
        const state = readState();
        let message = null;
        // Phase 1: Apply pending update from previous check
        if (state?.updatePending && state.latestVersion) {
            if (compareVersions(state.latestVersion, currentVersion) > 0) {
                const info = detectInstallInfo();
                if (info.updateCommand) {
                    // Run update in background — takes effect next launch
                    performUpdateInBackground(info.updateCommand);
                    message = `Nolan just shipped ${state.latestVersion}! Installing in the background — takes effect next launch.`;
                    writeState({
                        ...state,
                        lastCheckedAt: Date.now(),
                        updatePending: false,
                        lastUpdateAttempt: Date.now(),
                    });
                }
            }
            else {
                // Already on latest (user may have updated manually)
                writeState({ ...state, updatePending: false });
            }
        }
        // Phase 2: Schedule background check for next startup
        const shouldCheck = !state || Date.now() - state.lastCheckedAt > CHECK_INTERVAL_MS;
        if (shouldCheck) {
            scheduleBackgroundCheck(currentVersion);
        }
        return message;
    }
    catch {
        return null;
    }
}
/**
 * Read the current state file and report whether a newer version has been
 * downloaded / is pending install. Used by the TUI to show a persistent
 * "update ready" indicator in the status row.
 */
export function getPendingUpdate(currentVersion) {
    try {
        const state = readState();
        if (!state?.latestVersion)
            return null;
        if (compareVersions(state.latestVersion, currentVersion) <= 0)
            return null;
        return { latestVersion: state.latestVersion };
    }
    catch {
        return null;
    }
}
/**
 * Fire-and-forget async version check. Updates the state file so the
 * next startup knows whether an update is available.
 */
function scheduleBackgroundCheck(currentVersion) {
    fetchLatestVersion()
        .then((latestVersion) => {
        const newState = {
            lastCheckedAt: Date.now(),
            latestVersion: latestVersion ?? undefined,
            updatePending: false,
        };
        if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
            newState.updatePending = true;
        }
        writeState(newState);
    })
        .catch(() => {
        // Non-fatal — will retry next time
    });
}
// ── In-session periodic check ──────────────────────────────────────────
let periodicTimer = null;
/**
 * Start periodic update checks during a long-running session.
 * Checks every hour. If an update is found, calls `onUpdate` with a message.
 */
export function startPeriodicUpdateCheck(currentVersion, onUpdate) {
    if (periodicTimer)
        return; // Already running
    periodicTimer = setInterval(() => {
        fetchLatestVersion()
            .then((latestVersion) => {
            if (!latestVersion)
                return;
            if (compareVersions(latestVersion, currentVersion) <= 0)
                return;
            const info = detectInstallInfo();
            if (!info.updateCommand)
                return;
            // Mark pending for next startup
            writeState({
                lastCheckedAt: Date.now(),
                latestVersion,
                updatePending: true,
            });
            onUpdate(`Nolan just pushed a fresh update — ${currentVersion} → ${latestVersion}! I'll grab it on next launch (or run ${info.updateCommand} if you can't wait).`);
            // Stop checking once we've notified
            stopPeriodicUpdateCheck();
        })
            .catch(() => {
            // Non-fatal
        });
    }, CHECK_INTERVAL_MS);
    // Don't keep the process alive just for update checks
    periodicTimer.unref();
}
/**
 * Stop periodic update checks.
 */
export function stopPeriodicUpdateCheck() {
    if (periodicTimer) {
        clearInterval(periodicTimer);
        periodicTimer = null;
    }
}
//# sourceMappingURL=auto-update.js.map