import { readTextFile, writeFileSafe, pathExists } from "./fs.js";
import { getOmkVersionSync } from "./version.js";
import { runShell } from "./shell.js";
import { join } from "path";
import { homedir } from "os";
const CACHE_TTL_MS = 6 * 60 * 60 * 1000;
const DEFAULT_UPDATE_REMIND_HOURS = 24;
const UPDATE_PROMPT_TIMEOUT_MS = 30_000;
export const OMK_NPM_PACKAGE_NAME = "open-multi-agent-kit";
const OMK_UPDATE_INSTALL_CMD = `npm i -g ${OMK_NPM_PACKAGE_NAME}`;
const FALLBACK_INSTALL_SCRIPT = "Set KIMI_API_KEY and optionally KIMI_MODEL for direct Moonshot API access.";
/**
 * Returns true when `env.OMK_AUTO_UPDATE` is one of 1/true/yes/on/always
 * (case-insensitive), indicating the user wants non-interactive auto-updates.
 */
export function resolveAutoUpdateMode(env) {
    const raw = env.OMK_AUTO_UPDATE?.trim().toLowerCase();
    return raw === "1" || raw === "true" || raw === "yes" || raw === "on" || raw === "always";
}
function getCachePath() {
    return join(homedir(), ".omk", "update-cache.json");
}
export function getOmkUpdatePromptStatePath() {
    return join(homedir(), ".omk", "update-prompt.json");
}
function normalizeVersionForPrompt(version) {
    if (!version)
        return "unknown";
    return version.startsWith("v") ? version : `v${version}`;
}
function isTruthyCiValue(value) {
    if (value === undefined)
        return false;
    const normalized = value.trim().toLowerCase();
    return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "off";
}
function getUpdatePromptMode(env) {
    const normalized = env.OMK_UPDATE_PROMPT?.trim().toLowerCase();
    if (normalized === "0" || normalized === "false" || normalized === "off" || normalized === "never") {
        return "off";
    }
    if (normalized === "force")
        return "force";
    return "normal";
}
function getUpdateRemindHours(env) {
    const parsed = Number(env.OMK_UPDATE_REMIND_HOURS);
    return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_UPDATE_REMIND_HOURS;
}
function getUpdateRemindMs(env) {
    return getUpdateRemindHours(env) * 60 * 60 * 1000;
}
function isPromptState(value) {
    if (value === null || typeof value !== "object" || Array.isArray(value))
        return false;
    const state = value;
    return ((state.skippedVersion === undefined || typeof state.skippedVersion === "string") &&
        (state.remindAfter === undefined || typeof state.remindAfter === "string") &&
        (state.updatedAt === undefined || typeof state.updatedAt === "string"));
}
export async function readOmkUpdatePromptState(statePath = getOmkUpdatePromptStatePath()) {
    if (!(await pathExists(statePath)))
        return null;
    try {
        const parsed = JSON.parse(await readTextFile(statePath));
        return isPromptState(parsed) ? parsed : null;
    }
    catch {
        return null;
    }
}
export async function writeOmkUpdatePromptState(state, statePath = getOmkUpdatePromptStatePath()) {
    await writeFileSafe(statePath, `${JSON.stringify(state, null, 2)}\n`);
}
export function resolveOmkUpdatePromptState(options) {
    const env = options.env ?? process.env;
    const mode = getUpdatePromptMode(env);
    if (mode === "off")
        return { shouldPrompt: false, reason: "disabled" };
    const isCI = options.isCI ?? (isTruthyCiValue(env.CI) || isTruthyCiValue(env.GITHUB_ACTIONS));
    if (isCI)
        return { shouldPrompt: false, reason: "ci" };
    if (options.isTTY === false)
        return { shouldPrompt: false, reason: "non-tty" };
    const status = options.status;
    if (!status?.omk.outdated)
        return { shouldPrompt: false, reason: "not-outdated" };
    const latestVersion = status.omk.latest ?? undefined;
    if (!latestVersion)
        return { shouldPrompt: false, reason: "missing-latest" };
    if (mode === "force")
        return { shouldPrompt: true, reason: "prompt", latestVersion };
    const state = options.state;
    if (state?.skippedVersion === latestVersion) {
        return { shouldPrompt: false, reason: "skipped-version", latestVersion };
    }
    if (state?.remindAfter) {
        const remindAt = new Date(state.remindAfter).getTime();
        const now = options.now ?? new Date();
        if (Number.isFinite(remindAt) && remindAt > now.getTime()) {
            return { shouldPrompt: false, reason: "remind-active", latestVersion, remindAfter: state.remindAfter };
        }
    }
    return { shouldPrompt: true, reason: "prompt", latestVersion };
}
export function formatStartupUpdateBanner(updateStatus) {
    let banner = "";
    if (updateStatus.omk.outdated) {
        banner += `\n  ! omk ${updateStatus.omk.current} → ${updateStatus.omk.latest}  |  ${updateStatus.omk.installCmd}`;
    }
    return banner;
}
async function defaultSelectUpdatePrompt(config, options) {
    const { select } = await import("@inquirer/prompts");
    return (await select(config, options));
}
function classifyPromptError(err) {
    if (err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError"))
        return "timeout";
    return "cancelled";
}
function promptActionToDecisionAction(reason) {
    return reason === "prompt" ? "suppressed" : reason;
}
export async function maybePromptForOmkUpdate(options = {}) {
    const env = options.env ?? process.env;
    const now = options.now ?? new Date();
    const isTTY = options.isTTY ?? Boolean(process.stdout.isTTY && process.stdin.isTTY);
    const isCI = options.isCI ?? (isTruthyCiValue(env.CI) || isTruthyCiValue(env.GITHUB_ACTIONS));
    const log = options.onLog ?? ((message) => console.log(message));
    const autoUpdate = resolveAutoUpdateMode(env);
    if (autoUpdate && !isCI && getUpdatePromptMode(env) !== "off") {
        let updateStatus;
        try {
            updateStatus = options.status ?? await (options.checkUpdatesFn ?? checkUpdates)();
        }
        catch (err) {
            const message = err instanceof Error ? err.message : String(err);
            return { action: "suppressed", shouldExit: false, message };
        }
        const latestVersion = updateStatus.omk.latest;
        if (updateStatus.omk.outdated && latestVersion) {
            log(`Auto-updating OMK to ${normalizeVersionForPrompt(latestVersion)} ...`);
            const runUpdate = options.runUpdate ?? (() => runShell("npm", ["i", "-g", OMK_NPM_PACKAGE_NAME], { timeout: 120_000 }));
            const updateResult = await runUpdate();
            if (updateResult.failed) {
                const detail = updateResult.stderr.trim() || updateResult.stdout.trim() || `exit ${updateResult.exitCode}`;
                log(`Auto-update failed: ${detail}`);
                log(`Manual update command: ${OMK_UPDATE_INSTALL_CMD}`);
                return { action: "update-failed", shouldExit: true, exitCode: 1, version: latestVersion, message: detail };
            }
            log("OMK auto-update completed successfully. Restart omk chat to use the new version.");
            return { action: "updated", shouldExit: true, exitCode: 0, version: latestVersion };
        }
        return { action: "not-outdated", shouldExit: false };
    }
    const preflight = resolveOmkUpdatePromptState({
        status: null,
        env,
        isTTY,
        isCI,
        now,
    });
    if (preflight.reason === "disabled" || preflight.reason === "ci" || preflight.reason === "non-tty") {
        return { action: preflight.reason, shouldExit: false };
    }
    let updateStatus;
    try {
        updateStatus = options.status ?? await (options.checkUpdatesFn ?? checkUpdates)();
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        return { action: "suppressed", shouldExit: false, message };
    }
    const statePath = options.statePath ?? getOmkUpdatePromptStatePath();
    const state = await readOmkUpdatePromptState(statePath);
    const decision = resolveOmkUpdatePromptState({
        status: updateStatus,
        state,
        env,
        isTTY,
        isCI,
        now,
    });
    if (!decision.shouldPrompt) {
        return {
            action: promptActionToDecisionAction(decision.reason),
            shouldExit: false,
            version: decision.latestVersion,
        };
    }
    const latestVersion = decision.latestVersion ?? updateStatus.omk.latest ?? "";
    const currentVersion = updateStatus.omk.current;
    const promptMessage = `New OMK version available: ${normalizeVersionForPrompt(latestVersion)}. You are using ${normalizeVersionForPrompt(currentVersion)}. Update now?`;
    const remindHours = getUpdateRemindHours(env);
    const choices = [
        { name: "Update now", value: "update-now", description: updateStatus.omk.installCmd },
        { name: "Skip this version", value: "skip-version", description: normalizeVersionForPrompt(latestVersion) },
        { name: "Remind me later", value: "remind-later", description: `${remindHours}h` },
    ];
    let answer;
    try {
        answer = await (options.selectPrompt ?? defaultSelectUpdatePrompt)({ message: promptMessage, choices }, { signal: AbortSignal.timeout(options.promptTimeoutMs ?? UPDATE_PROMPT_TIMEOUT_MS) });
    }
    catch (err) {
        const action = classifyPromptError(err);
        log(action === "timeout" ? "Update prompt timed out; continuing without updating." : "Update prompt cancelled.");
        return { action, shouldExit: false, version: latestVersion };
    }
    const updatedAt = now.toISOString();
    if (answer === "skip-version") {
        try {
            await writeOmkUpdatePromptState({ skippedVersion: latestVersion, updatedAt }, statePath);
        }
        catch {
            // State persistence is advisory; never block startup.
        }
        log(`Skipping OMK ${normalizeVersionForPrompt(latestVersion)} update prompt for this version.`);
        return { action: "prompted-skip", shouldExit: false, version: latestVersion };
    }
    if (answer === "remind-later") {
        const remindAfter = new Date(now.getTime() + getUpdateRemindMs(env)).toISOString();
        try {
            await writeOmkUpdatePromptState({ remindAfter, updatedAt }, statePath);
        }
        catch {
            // State persistence is advisory; never block startup.
        }
        log(`OMK update reminder scheduled for ${remindAfter}.`);
        return { action: "prompted-remind", shouldExit: false, version: latestVersion };
    }
    log(`Running update: ${OMK_UPDATE_INSTALL_CMD}`);
    const runUpdate = options.runUpdate ?? (() => runShell("npm", ["i", "-g", OMK_NPM_PACKAGE_NAME], { timeout: 120_000 }));
    const updateResult = await runUpdate();
    if (updateResult.failed) {
        const detail = updateResult.stderr.trim() || updateResult.stdout.trim() || `exit ${updateResult.exitCode}`;
        log(`Update failed: ${detail}`);
        log(`Manual update command: ${OMK_UPDATE_INSTALL_CMD}`);
        return { action: "update-failed", shouldExit: true, exitCode: 1, version: latestVersion, message: detail };
    }
    log("OMK update completed successfully. Restart omk chat to use the new version.");
    return { action: "updated", shouldExit: true, exitCode: 0, version: latestVersion };
}
async function readCache() {
    const cachePath = getCachePath();
    if (!(await pathExists(cachePath)))
        return null;
    try {
        const raw = await readTextFile(cachePath);
        const parsed = JSON.parse(raw);
        const age = Date.now() - new Date(parsed.checkedAt).getTime();
        if (age > CACHE_TTL_MS)
            return null;
        return parsed;
    }
    catch {
        return null;
    }
}
async function writeCache(cache) {
    const cachePath = getCachePath();
    try {
        await writeFileSafe(cachePath, JSON.stringify(cache, null, 2));
    }
    catch {
        // non-fatal
    }
}
export function parseSemverParts(v) {
    const m = v.match(/^(\d+)\.(\d+)\.(\d+)$/);
    if (!m)
        return null;
    return [Number(m[1]), Number(m[2]), Number(m[3])];
}
export function compareVersions(a, b) {
    const pa = parseSemverParts(a);
    const pb = parseSemverParts(b);
    if (!pa || !pb)
        return 0;
    for (let i = 0; i < 3; i++) {
        if (pa[i] > pb[i])
            return 1;
        if (pa[i] < pb[i])
            return -1;
    }
    return 0;
}
export function isOutdated(current, latest) {
    return compareVersions(latest, current) > 0;
}
async function fetchOmkLatest() {
    try {
        const result = await runShell("npm", ["view", OMK_NPM_PACKAGE_NAME, "version"], {
            timeout: 8000,
        });
        if (result.failed)
            return null;
        return result.stdout.trim();
    }
    catch {
        return null;
    }
}
async function fetchKimiLatest() {
    return null;
}
async function getKimiInstalledVersion() {
    return null;
}
export async function checkUpdates(forceRefresh = false) {
    const cached = forceRefresh ? null : await readCache();
    const cacheHit = cached !== null;
    const omkCurrent = getOmkVersionSync();
    const kimiInstalled = await getKimiInstalledVersion();
    let omkLatest = cached?.omkLatest ?? null;
    let kimiLatest = cached?.kimiLatest ?? null;
    if (!cacheHit) {
        const [omk, kimi] = await Promise.all([
            fetchOmkLatest(),
            fetchKimiLatest(),
        ]);
        omkLatest = omk;
        kimiLatest = kimi;
        await writeCache({
            omkLatest,
            kimiLatest,
            checkedAt: new Date().toISOString(),
        });
    }
    const omkOutdated = !!omkLatest && isOutdated(omkCurrent, omkLatest);
    let omkError = null;
    const kimiError = null;
    if (!omkLatest && !cacheHit)
        omkError = "registry unreachable";
    return {
        omk: {
            current: omkCurrent,
            latest: omkLatest,
            outdated: omkOutdated,
            error: omkError,
            installCmd: OMK_UPDATE_INSTALL_CMD,
        },
        kimi: {
            installed: kimiInstalled,
            latest: kimiLatest,
            outdated: false,
            error: kimiError,
            installCmd: "Set KIMI_API_KEY for direct Moonshot API access.",
            installScript: FALLBACK_INSTALL_SCRIPT,
            fallbackInstallCmd: FALLBACK_INSTALL_SCRIPT,
        },
        checkedAt: new Date().toISOString(),
        cacheHit,
    };
}
