"use strict";
/**
 * Dashboard service for codex-auth profile summary.
 *
 * Reads the profile registry, decodes each profile's auth.json JWT,
 * resolves the active profile via env precedence, and returns the
 * API response shape for GET /api/codex/profiles.
 *
 * Security: tokens NEVER appear in the returned object. Only display-safe
 * fields (email, plan, accountId) are extracted from JWT. auth.json is
 * read/decoded and then discarded.
 *
 * Cache: 5-second in-memory cache reduces full registry/auth reads during
 * dashboard polling, but each call still stats the registry so corruption or
 * edits are surfaced immediately instead of serving stale success.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCodexAuthProfilesSummary = exports.invalidateCodexAuthProfilesCache = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const logging_1 = require("../services/logging");
const codex_account_identity_1 = require("./codex-account-identity");
const decode_id_token_1 = require("./decode-id-token");
const codex_profile_paths_1 = require("./codex-profile-paths");
const types_1 = require("./types");
const codex_profile_registry_1 = require("./codex-profile-registry");
const logger = (0, logging_1.createLogger)('codex-auth:dashboard');
// ── Cache ───────────────────────────────────────────────────────────────────
let cache = null;
const TTL_MS = 5000;
/**
 * Invalidate the in-process cache so the next call re-reads from disk.
 * Useful for Phase 2 CLI commands running in the same process as the dashboard.
 * Out-of-process invocations rely on the 5s TTL.
 */
function invalidateCodexAuthProfilesCache() {
    cache = null;
}
exports.invalidateCodexAuthProfilesCache = invalidateCodexAuthProfilesCache;
// ── Registry helpers ────────────────────────────────────────────────────────
function getRegistryCacheSignature() {
    const registryPath = (0, codex_profile_paths_1.getCodexAuthRegistryPath)();
    try {
        const stat = fs.statSync(registryPath);
        return ['present', stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs].join(':');
    }
    catch (err) {
        if (err?.code === 'ENOENT') {
            return 'missing';
        }
        const msg = err instanceof Error ? err.message : String(err);
        logger.warn('codex-auth.dashboard.registry-stat-failed', `Registry stat failed: ${msg}`);
        throw new Error('Codex auth profile registry could not be checked safely');
    }
}
function readRegistry() {
    const registryPath = (0, codex_profile_paths_1.getCodexAuthRegistryPath)();
    if (!fs.existsSync(registryPath)) {
        return { version: types_1.CODEX_PROFILE_SCHEMA_VERSION, default: null, profiles: {} };
    }
    try {
        const registry = new codex_profile_registry_1.CodexProfileRegistry(registryPath);
        const profiles = {};
        for (const name of registry.listProfiles()) {
            profiles[name] = registry.getProfile(name);
        }
        return {
            version: types_1.CODEX_PROFILE_SCHEMA_VERSION,
            default: registry.getDefault(),
            profiles,
        };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        logger.warn('codex-auth.dashboard.registry-read-failed', `Registry read failed: ${msg}`);
        throw new Error(`Codex auth profile registry could not be read safely: ${msg}`);
    }
}
// ── Profile entry builder ───────────────────────────────────────────────────
function buildProfileEntry(name) {
    const codexHome = path.join((0, codex_profile_paths_1.getCodexInstancesDir)(), name);
    const authJsonPath = path.join(codexHome, 'auth.json');
    let authValid = false;
    let email = null;
    let plan = null;
    let accountId = null;
    try {
        if (fs.existsSync(authJsonPath)) {
            // decodeAccountIdentity never throws; returns {} on any error
            const identity = (0, codex_account_identity_1.decodeAccountIdentity)(authJsonPath);
            authValid = Object.keys(identity).length > 0 || _hasStructurallyValidIdToken(authJsonPath);
            email = identity.email ?? null;
            plan = identity.plan_type ?? null;
            accountId = identity.account_id ?? null;
            logger.debug('codex-auth.dashboard.decoded', 'Decoded auth profile summary', {
                profileName: name,
                hasEmail: email !== null,
                hasPlan: plan !== null,
                hasAccountId: accountId !== null,
            });
        }
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        logger.warn('codex-auth.dashboard.decode-error', `Failed to decode auth for profile=${name}: ${msg}`);
    }
    return {
        name,
        codexHome,
        email,
        plan,
        accountId,
        // lastUsed is set below by caller from registry metadata
        lastUsed: null,
        authValid,
    };
}
/**
 * Check whether auth.json has a parseable JWT payload, even if it has no
 * display fields. A non-empty but malformed token is not valid auth.
 * This sets authValid=true for valid-but-sparse tokens.
 */
function _hasStructurallyValidIdToken(authJsonPath) {
    try {
        const raw = fs.readFileSync(authJsonPath, 'utf8');
        const parsed = JSON.parse(raw);
        const idToken = parsed?.tokens?.id_token;
        return typeof idToken === 'string' && (0, decode_id_token_1.hasStructurallyValidIdToken)(idToken);
    }
    catch {
        return false;
    }
}
// ── Active resolution ───────────────────────────────────────────────────────
function resolveActive(registry) {
    const instancesDir = (0, codex_profile_paths_1.getCodexInstancesDir)();
    // Precedence 1: explicit $CODEX_HOME set by env (ccsxp, manual)
    const codexHome = (process.env.CODEX_HOME ?? '').trim();
    if (codexHome) {
        // Attempt reverse-map: does any registered profile's codexHome match?
        const matchedName = Object.keys(registry.profiles).find((name) => path.join(instancesDir, name) === codexHome) ??
            null;
        return {
            name: matchedName,
            source: 'explicit-codex-home',
            codexHome,
        };
    }
    // Precedence 2: $CCS_CODEX_PROFILE env var
    const profileEnv = (process.env.CCS_CODEX_PROFILE ?? '').trim();
    if (profileEnv) {
        if (!Object.prototype.hasOwnProperty.call(registry.profiles, profileEnv)) {
            logger.warn('codex-auth.dashboard.stale-env-profile', 'Ignoring CCS_CODEX_PROFILE because it is not registered', { profileName: profileEnv });
            return null;
        }
        return {
            name: profileEnv,
            source: 'env',
            codexHome: path.join(instancesDir, profileEnv),
        };
    }
    // Precedence 3: registry default
    const defaultProfile = registry.default;
    if (defaultProfile) {
        return {
            name: defaultProfile,
            source: 'default',
            codexHome: path.join(instancesDir, defaultProfile),
        };
    }
    // Precedence 4: no active profile (legacy ~/.codex mode)
    return null;
}
// ── Core builder ────────────────────────────────────────────────────────────
async function buildSummary() {
    const registry = readRegistry();
    const active = resolveActive(registry);
    const profiles = Object.entries(registry.profiles).map(([name, meta]) => {
        const entry = buildProfileEntry(name);
        entry.lastUsed = meta.last_used ?? null;
        return entry;
    });
    return {
        active,
        default: registry.default,
        profiles,
    };
}
// ── Public API ──────────────────────────────────────────────────────────────
/**
 * Returns the codex-auth profiles summary, using a 5s in-memory cache.
 * Tokens are never included in the returned object.
 */
async function getCodexAuthProfilesSummary() {
    const now = Date.now();
    const registrySignature = getRegistryCacheSignature();
    if (cache && cache.expiresAt > now && cache.registrySignature === registrySignature) {
        return cache.value;
    }
    const value = await buildSummary();
    cache = { value, expiresAt: now + TTL_MS, registrySignature };
    return value;
}
exports.getCodexAuthProfilesSummary = getCodexAuthProfilesSummary;
//# sourceMappingURL=codex-auth-dashboard-service.js.map