import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
export function resolveAntigravityAliasReuseCooldownMs(config) {
    const cfg = config.loadBalancing?.aliasSelection?.sessionLeaseCooldownMs;
    if (typeof cfg === 'number' && Number.isFinite(cfg)) {
        return Math.max(0, Math.floor(cfg));
    }
    return 5 * 60_000;
}
function resolveAntigravityLeaseScope(providerRegistry, providerKey) {
    try {
        const profile = providerRegistry.get(providerKey);
        const modelId = typeof profile?.modelId === 'string' ? profile.modelId.trim().toLowerCase() : '';
        if (modelId.startsWith('gemini-')) {
            return 'gemini';
        }
        return null;
    }
    catch {
        return null;
    }
}
function buildScopedSessionKey(sessionKey) {
    return `${sessionKey}::gemini`;
}
function buildAntigravityLeaseRuntimeKey(runtimeKey) {
    return `${runtimeKey}::gemini`;
}
function extractRuntimeKey(providerKey) {
    const value = typeof providerKey === 'string' ? providerKey.trim() : '';
    if (!value)
        return null;
    const firstDot = value.indexOf('.');
    if (firstDot <= 0 || firstDot === value.length - 1)
        return null;
    const secondDot = value.indexOf('.', firstDot + 1);
    if (secondDot <= firstDot + 1)
        return null;
    const providerId = value.slice(0, firstDot);
    const alias = value.slice(firstDot + 1, secondDot);
    if (!providerId || !alias)
        return null;
    return `${providerId}.${alias}`;
}
function resolveAntigravityAliasLeasePersistPath() {
    try {
        const enabledRaw = process.env.ROUTECODEX_ANTIGRAVITY_ALIAS_LEASE_PERSIST;
        const enabled = typeof enabledRaw === 'string' &&
            ['1', 'true', 'yes', 'on'].includes(enabledRaw.trim().toLowerCase());
        if (!enabled) {
            return null;
        }
        const home = os.homedir();
        if (!home)
            return null;
        return path.join(home, '.routecodex', 'state', 'antigravity-alias-leases.json');
    }
    catch {
        return null;
    }
}
export function hydrateAntigravityAliasLeaseStoreIfNeeded(options) {
    const filePath = resolveAntigravityAliasLeasePersistPath();
    if (!filePath) {
        return;
    }
    const force = options.force === true;
    let stat = null;
    try {
        stat = fs.statSync(filePath);
    }
    catch {
        options.persistence.loadedOnce = true;
        options.persistence.loadedMtimeMs = null;
        return;
    }
    const mtimeMs = typeof stat.mtimeMs === 'number' && Number.isFinite(stat.mtimeMs) ? stat.mtimeMs : stat.mtime.getTime();
    if (!force && options.persistence.loadedOnce && options.persistence.loadedMtimeMs === mtimeMs) {
        return;
    }
    let parsed;
    try {
        parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
    }
    catch {
        options.persistence.loadedOnce = true;
        options.persistence.loadedMtimeMs = mtimeMs;
        return;
    }
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        options.persistence.loadedOnce = true;
        options.persistence.loadedMtimeMs = mtimeMs;
        return;
    }
    const leasesRaw = parsed.leases;
    if (!leasesRaw || typeof leasesRaw !== 'object' || Array.isArray(leasesRaw)) {
        options.persistence.loadedOnce = true;
        options.persistence.loadedMtimeMs = mtimeMs;
        return;
    }
    const now = Date.now();
    const cooldownMs = options.aliasReuseCooldownMs;
    const nextLeaseStore = new Map();
    for (const [runtimeKeyRaw, entryRaw] of Object.entries(leasesRaw)) {
        const runtimeKeyBase = typeof runtimeKeyRaw === 'string' ? runtimeKeyRaw.trim() : '';
        if (!runtimeKeyBase || !runtimeKeyBase.startsWith('antigravity.'))
            continue;
        if (!entryRaw || typeof entryRaw !== 'object' || Array.isArray(entryRaw))
            continue;
        const rawSessionKey = typeof entryRaw.sessionKey === 'string' ? String(entryRaw.sessionKey).trim() : '';
        const lastSeenAt = typeof entryRaw.lastSeenAt === 'number' && Number.isFinite(entryRaw.lastSeenAt)
            ? Math.floor(entryRaw.lastSeenAt)
            : 0;
        if (!rawSessionKey || !lastSeenAt)
            continue;
        if (cooldownMs > 0 && now - lastSeenAt >= cooldownMs)
            continue;
        // Backwards compatible: legacy lease keys were unscoped ("antigravity.<alias>").
        // Policy: only keep Gemini-scoped leases. Drop any non-gemini scoped entries.
        const hasExplicitScope = runtimeKeyBase.includes('::');
        if (hasExplicitScope && !runtimeKeyBase.endsWith('::gemini'))
            continue;
        const runtimeKey = hasExplicitScope ? runtimeKeyBase : buildAntigravityLeaseRuntimeKey(runtimeKeyBase);
        if (rawSessionKey.includes('::') && !rawSessionKey.endsWith('::gemini'))
            continue;
        const sessionKey = rawSessionKey.includes('::') ? rawSessionKey : buildScopedSessionKey(rawSessionKey);
        nextLeaseStore.set(runtimeKey, { sessionKey, lastSeenAt });
    }
    options.leaseStore.clear();
    for (const [key, val] of nextLeaseStore.entries()) {
        options.leaseStore.set(key, val);
    }
    options.persistence.loadedOnce = true;
    options.persistence.loadedMtimeMs = mtimeMs;
}
function flushAntigravityAliasLeaseStoreSync(options) {
    const filePath = resolveAntigravityAliasLeasePersistPath();
    if (!filePath) {
        return;
    }
    try {
        fs.mkdirSync(path.dirname(filePath), { recursive: true });
    }
    catch {
        // ignore
    }
    const now = Date.now();
    const cooldownMs = options.aliasReuseCooldownMs;
    const leases = {};
    for (const [runtimeKey, entry] of options.leaseStore.entries()) {
        if (!runtimeKey || !entry)
            continue;
        if (cooldownMs > 0 && now - entry.lastSeenAt >= cooldownMs)
            continue;
        if (!entry.sessionKey || !entry.lastSeenAt)
            continue;
        leases[runtimeKey] = { sessionKey: entry.sessionKey, lastSeenAt: entry.lastSeenAt };
    }
    try {
        const tmpPath = `${filePath}.tmp.${process.pid}.${now}`;
        fs.writeFileSync(tmpPath, JSON.stringify({ version: 1, updatedAt: now, leases }, null, 2), 'utf8');
        fs.renameSync(tmpPath, filePath);
        try {
            const stat = fs.statSync(filePath);
            const mtimeMs = typeof stat.mtimeMs === 'number' && Number.isFinite(stat.mtimeMs) ? stat.mtimeMs : stat.mtime.getTime();
            options.persistence.loadedOnce = true;
            options.persistence.loadedMtimeMs = mtimeMs;
        }
        catch {
            // ignore
        }
    }
    catch {
        // best-effort persistence: must not affect routing
    }
}
function scheduleAntigravityAliasLeaseStoreFlush(options) {
    if (options.persistence.flushTimer) {
        return;
    }
    options.persistence.flushTimer = setTimeout(() => {
        options.persistence.flushTimer = null;
        flushAntigravityAliasLeaseStoreSync(options);
    }, 250);
    options.persistence.flushTimer.unref?.();
}
export function recordAntigravitySessionLease(options) {
    // Per-request runtime override: allow callers (e.g. servertool followups) to disable
    // Antigravity gemini session binding/lease without changing global config.
    try {
        const rt = options.metadata?.__rt;
        if (rt && typeof rt === 'object' && !Array.isArray(rt)) {
            const disable = rt.disableAntigravitySessionBinding;
            const mode = rt.antigravitySessionBinding;
            if (disable === true || mode === false) {
                return;
            }
            if (typeof mode === 'string' && ['0', 'false', 'off', 'disabled', 'none'].includes(mode.trim().toLowerCase())) {
                return;
            }
        }
    }
    catch {
        // ignore metadata parse errors
    }
    const sessionKey = options.sessionKey;
    if (!sessionKey) {
        return;
    }
    const runtimeKeyBase = extractRuntimeKey(options.providerKey);
    if (!runtimeKeyBase || !runtimeKeyBase.startsWith('antigravity.')) {
        return;
    }
    // Policy: antigravity alias leasing/binding applies only to Gemini models.
    const scope = resolveAntigravityLeaseScope(options.providerRegistry, options.providerKey);
    if (scope !== 'gemini') {
        return;
    }
    const scopedSessionKey = buildScopedSessionKey(sessionKey);
    const runtimeKey = buildAntigravityLeaseRuntimeKey(runtimeKeyBase);
    const now = Date.now();
    const cooldownMs = options.aliasReuseCooldownMs;
    const existing = options.leaseStore.get(runtimeKey);
    // Do not steal a lease from another active session. This prevents a "forced fallback" route
    // (e.g. default route bypass) from evicting the original session and causing churn.
    if (existing && existing.sessionKey !== scopedSessionKey && cooldownMs > 0 && now - existing.lastSeenAt < cooldownMs) {
        return;
    }
    options.leaseStore.set(runtimeKey, { sessionKey: scopedSessionKey, lastSeenAt: now });
    scheduleAntigravityAliasLeaseStoreFlush(options);
    if (!options.commitSessionBinding) {
        return;
    }
    // Bind sessionKey → alias runtimeKey so subsequent routing will prefer this alias.
    options.sessionAliasStore.set(scopedSessionKey, runtimeKey);
    try {
        options.debug?.log?.('[virtual-router][antigravity-session-binding] commit', {
            sessionKey: scopedSessionKey,
            runtimeKey,
            prev: existing?.sessionKey ?? null
        });
    }
    catch {
        // ignore
    }
}
