"use strict";
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.CodexProfileRegistry = exports.validateCodexProfileRegistryData = exports.CodexProfileRegistryReadError = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const yaml = __importStar(require("js-yaml"));
const lockfile = __importStar(require("proper-lockfile"));
const logging_1 = require("../services/logging");
const codex_profile_paths_1 = require("./codex-profile-paths");
const config_manager_1 = require("../utils/config-manager");
const types_1 = require("./types");
const logger = (0, logging_1.createLogger)('codex-auth:registry');
const REGISTRY_LOCK_STALE_MS = 10000;
const REGISTRY_LOCK_RETRIES = 40;
const REGISTRY_LOCK_RETRY_DELAY_MS = 50;
function emptyRegistry() {
    return { version: types_1.CODEX_PROFILE_SCHEMA_VERSION, default: null, profiles: {} };
}
class CodexProfileRegistryReadError extends Error {
    constructor(message) {
        super(message);
        this.name = 'CodexProfileRegistryReadError';
    }
}
exports.CodexProfileRegistryReadError = CodexProfileRegistryReadError;
function validateCodexProfileRegistryData(parsed) {
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
        throw new Error('registry YAML root is not an object');
    }
    const data = parsed;
    if (!data.profiles || typeof data.profiles !== 'object' || Array.isArray(data.profiles)) {
        throw new Error('registry YAML is missing an object profiles map');
    }
    if (data.default !== undefined && data.default !== null && typeof data.default !== 'string') {
        throw new Error('registry YAML default must be a string or null');
    }
    if (typeof data.default === 'string') {
        assertValidProfileName(data.default);
    }
    const profiles = {};
    for (const [name, profile] of Object.entries(data.profiles)) {
        assertValidProfileName(name);
        profiles[name] = validateProfileMetadata(name, profile);
    }
    if (typeof data.default === 'string' &&
        !Object.prototype.hasOwnProperty.call(profiles, data.default)) {
        throw new Error('registry YAML default profile is missing from profiles map');
    }
    return {
        version: typeof data.version === 'string' ? data.version : types_1.CODEX_PROFILE_SCHEMA_VERSION,
        default: data.default ?? null,
        profiles,
    };
}
exports.validateCodexProfileRegistryData = validateCodexProfileRegistryData;
function assertValidProfileName(name) {
    const nameError = (0, types_1.getCodexProfileNameError)(name);
    if (nameError) {
        throw new Error(`registry YAML contains invalid profile name "${name}": ${nameError}`);
    }
}
function validateProfileMetadata(name, profile) {
    if (!profile || typeof profile !== 'object' || Array.isArray(profile)) {
        throw new Error(`registry YAML profile "${name}" must be an object`);
    }
    const meta = profile;
    if (meta.type !== 'codex') {
        throw new Error(`registry YAML profile "${name}" must have type "codex"`);
    }
    if (typeof meta.created !== 'string') {
        throw new Error(`registry YAML profile "${name}" must have a string created timestamp`);
    }
    if (meta.last_used !== null && typeof meta.last_used !== 'string') {
        throw new Error(`registry YAML profile "${name}" must have a string or null last_used timestamp`);
    }
    if (meta.email !== undefined && typeof meta.email !== 'string') {
        throw new Error(`registry YAML profile "${name}" email must be a string`);
    }
    if (meta.plan_type !== undefined &&
        meta.plan_type !== null &&
        typeof meta.plan_type !== 'string') {
        throw new Error(`registry YAML profile "${name}" plan_type must be a string or null`);
    }
    if (meta.account_id !== undefined && typeof meta.account_id !== 'string') {
        throw new Error(`registry YAML profile "${name}" account_id must be a string`);
    }
    return meta;
}
function registryDisplayPath(registryPath) {
    const [source] = (0, config_manager_1.getCcsDirSource)();
    const defaultRegistryPath = path.resolve((0, codex_profile_paths_1.getCodexAuthRegistryPath)());
    if (path.resolve(registryPath) !== defaultRegistryPath) {
        return path.basename(registryPath);
    }
    if (source === 'default') {
        return process.platform === 'win32'
            ? '%USERPROFILE%\\.ccs\\codex-profiles.yaml'
            : '~/.ccs/codex-profiles.yaml';
    }
    if (source === 'CCS_HOME' || source === 'scoped:CCS_HOME') {
        return '$CCS_HOME/.ccs/codex-profiles.yaml';
    }
    if (source === 'CCS_DIR' || source === 'scoped:CCS_DIR') {
        return '$CCS_DIR/codex-profiles.yaml';
    }
    return 'codex-profiles.yaml';
}
function safeRegistryReadMessage(err) {
    if (err?.name === 'YAMLException') {
        return 'registry YAML could not be parsed';
    }
    return err instanceof Error ? err.message : String(err);
}
function sleepSync(ms) {
    try {
        Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
    }
    catch {
        const end = Date.now() + ms;
        while (Date.now() < end) {
            // Fall back for runtimes without Atomics.wait.
        }
    }
}
function isLockContentionError(err) {
    return err?.code === 'ELOCKED';
}
/**
 * Registry for codex auth profiles stored at ~/.ccs/codex-profiles.yaml.
 *
 * Writes are guarded by a registry-directory lock around the read-modify-write
 * cycle, then persisted atomically with tmp file + POSIX rename.
 *
 * Constructor accepts an optional registryPath for test isolation.
 */
class CodexProfileRegistry {
    constructor(registryPath) {
        this.registryPath = registryPath ?? (0, codex_profile_paths_1.getCodexAuthRegistryPath)();
        this._cleanOrphanTmpFiles();
    }
    // ── private read/write ──────────────────────────────────────────────────
    _read() {
        if (!fs.existsSync(this.registryPath)) {
            return emptyRegistry();
        }
        try {
            const raw = fs.readFileSync(this.registryPath, 'utf8');
            return validateCodexProfileRegistryData(yaml.load(raw));
        }
        catch (err) {
            const msg = safeRegistryReadMessage(err);
            const displayPath = registryDisplayPath(this.registryPath);
            logger.warn('codex-auth.registry.read-failed', `Registry at ${displayPath} could not be read safely; refusing empty-state rewrite: ${msg}`);
            throw new CodexProfileRegistryReadError(`Codex profile registry at ${displayPath} could not be read safely: ${msg}. Refusing to rewrite it.`);
        }
    }
    _write(data) {
        const dir = path.dirname(this.registryPath);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
        }
        // Unique tmp suffix avoids collisions on concurrent writes and orphan leaks
        const tmpPath = `${this.registryPath}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
        try {
            fs.writeFileSync(tmpPath, yaml.dump(data, { indent: 2, lineWidth: -1 }), {
                mode: 0o600,
            });
            fs.renameSync(tmpPath, this.registryPath);
        }
        catch (err) {
            if (fs.existsSync(tmpPath)) {
                try {
                    fs.unlinkSync(tmpPath);
                }
                catch {
                    // best-effort cleanup
                }
            }
            const msg = err instanceof Error ? err.message : String(err);
            throw new Error(`Failed to write codex profile registry: ${msg}`);
        }
    }
    _withRegistryWriteLock(callback) {
        const dir = path.dirname(this.registryPath);
        if (!fs.existsSync(dir)) {
            fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
        }
        let release;
        let lastLockError;
        for (let attempt = 0; attempt <= REGISTRY_LOCK_RETRIES; attempt++) {
            try {
                release = lockfile.lockSync(dir, { stale: REGISTRY_LOCK_STALE_MS });
                break;
            }
            catch (err) {
                if (!isLockContentionError(err) || attempt === REGISTRY_LOCK_RETRIES) {
                    throw err;
                }
                lastLockError = err;
                sleepSync(REGISTRY_LOCK_RETRY_DELAY_MS);
            }
        }
        if (!release) {
            const msg = lastLockError instanceof Error ? lastLockError.message : 'unknown lock error';
            throw new Error(`Failed to acquire codex profile registry lock: ${msg}`);
        }
        try {
            return callback();
        }
        finally {
            try {
                release();
            }
            catch {
                // Best-effort release.
            }
        }
    }
    // Best-effort cleanup of orphan tmp files older than 1 hour (H3 mitigation).
    _cleanOrphanTmpFiles() {
        const dir = path.dirname(this.registryPath);
        const base = path.basename(this.registryPath);
        if (!fs.existsSync(dir))
            return;
        try {
            const oneHourAgo = Date.now() - 60 * 60 * 1000;
            for (const entry of fs.readdirSync(dir)) {
                if (!entry.startsWith(`${base}.tmp.`))
                    continue;
                const full = path.join(dir, entry);
                try {
                    const stat = fs.statSync(full);
                    if (stat.mtimeMs < oneHourAgo) {
                        fs.unlinkSync(full);
                    }
                }
                catch {
                    // ignore per-file errors
                }
            }
        }
        catch {
            // ignore cleanup failure silently
        }
    }
    // ── CRUD ────────────────────────────────────────────────────────────────
    createProfile(name, meta = {}) {
        assertValidProfileName(name);
        this._withRegistryWriteLock(() => {
            const data = this._read();
            if (data.profiles[name]) {
                throw new Error(`Profile already exists: ${name}`);
            }
            data.profiles[name] = {
                type: 'codex',
                created: new Date().toISOString(),
                last_used: null,
                ...meta,
            };
            this._write(data);
            logger.stage('route', 'codex-auth.profile.created', 'Codex profile created', { name });
        });
    }
    getProfile(name) {
        assertValidProfileName(name);
        const data = this._read();
        const profile = data.profiles[name];
        if (!profile) {
            throw new Error(`Profile not found: ${name}`);
        }
        return profile;
    }
    updateProfile(name, partial) {
        assertValidProfileName(name);
        this._withRegistryWriteLock(() => {
            const data = this._read();
            if (!data.profiles[name]) {
                throw new Error(`Profile not found: ${name}`);
            }
            data.profiles[name] = { ...data.profiles[name], ...partial };
            this._write(data);
        });
    }
    removeProfile(name, options = {}) {
        assertValidProfileName(name);
        this._withRegistryWriteLock(() => {
            const data = this._read();
            if (!data.profiles[name]) {
                throw new Error(`Profile not found: ${name}`);
            }
            if (data.default === name && Object.keys(data.profiles).length > 1 && !options.forceDefault) {
                throw new Error('Cannot remove default profile while other profiles exist without --force');
            }
            delete data.profiles[name];
            if (data.default === name) {
                data.default = null;
            }
            this._write(data);
            logger.stage('cleanup', 'codex-auth.profile.deleted', 'Codex profile removed', { name });
        });
    }
    listProfiles() {
        return Object.keys(this._read().profiles);
    }
    hasProfile(name) {
        if ((0, types_1.getCodexProfileNameError)(name))
            return false;
        return Object.prototype.hasOwnProperty.call(this._read().profiles, name);
    }
    // ── Default pointer ──────────────────────────────────────────────────────
    getDefault() {
        return this._read().default;
    }
    setDefault(name) {
        assertValidProfileName(name);
        this._withRegistryWriteLock(() => {
            const data = this._read();
            if (!data.profiles[name]) {
                throw new Error(`Profile not found: ${name}`);
            }
            data.default = name;
            this._write(data);
        });
    }
    clearDefault() {
        this._withRegistryWriteLock(() => {
            const data = this._read();
            data.default = null;
            this._write(data);
        });
    }
    touchProfile(name) {
        this.updateProfile(name, { last_used: new Date().toISOString() });
    }
}
exports.CodexProfileRegistry = CodexProfileRegistry;
//# sourceMappingURL=codex-profile-registry.js.map