/**
 * QE Gating Config (ADR-PIPELINE-086)
 *
 * Resolves the AGENTICS_QE_GATING mode from either the env var or a persisted
 * config file in ~/.agentics/qe-gating.json. Emits structured bypass records
 * when the mode is not `on`.
 *
 * Source precedence (highest → lowest):
 *   1. AGENTICS_QE_GATING env var
 *   2. Persisted config at <configDir>/qe-gating.json
 *   3. Default: `on`
 *
 * The persisted file is the mechanism on-call uses outside of env-var
 * environments (e.g., managed deployments where env vars require a redeploy).
 * Both paths require a non-empty reason when setting anything other than `on`
 * — callers that persist without a reason will throw.
 */
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { homedir } from 'node:os';
import { join, dirname } from 'node:path';
export const DEFAULT_MODE = 'on';
export const ENV_VAR_NAME = 'AGENTICS_QE_GATING';
export const CONFIG_FILE = 'qe-gating.json';
export const DEFAULT_CONFIG_DIR = join(homedir(), '.agentics');
// ============================================================================
// Parsing
// ============================================================================
export class InvalidGatingValueError extends Error {
    code = 'INVALID_QE_GATING_VALUE';
    value;
    constructor(value) {
        super(`Invalid ${ENV_VAR_NAME} value "${value}" — must be one of: on, advisory, off`);
        this.value = value;
        this.name = 'InvalidGatingValueError';
    }
}
export class EmptyReasonError extends Error {
    code = 'EMPTY_QE_GATING_REASON';
    constructor() {
        super(`Non-empty reason is required when setting ${ENV_VAR_NAME} to advisory or off (ADR-086 §2 audit trail requirement)`);
        this.name = 'EmptyReasonError';
    }
}
/**
 * Parse a raw env-var value. Accepts exactly one of: on, advisory, off
 * (case-insensitive; whitespace-trimmed). Empty / undefined returns null so
 * callers can fall through to persisted / default resolution.
 */
export function parseGatingValue(raw) {
    if (raw === undefined || raw === null)
        return null;
    const trimmed = raw.trim().toLowerCase();
    if (trimmed === '')
        return null;
    if (trimmed === 'on' || trimmed === 'advisory' || trimmed === 'off')
        return trimmed;
    throw new InvalidGatingValueError(raw);
}
// ============================================================================
// Persisted config (file-backed)
// ============================================================================
function persistedPath(configDir) {
    return join(configDir, CONFIG_FILE);
}
export function loadPersistedQeGating(opts = {}) {
    const configDir = opts.configDir ?? DEFAULT_CONFIG_DIR;
    const path = persistedPath(configDir);
    if (!existsSync(path))
        return null;
    try {
        const raw = readFileSync(path, 'utf-8');
        const parsed = JSON.parse(raw);
        // Round-trip validation: mode must be a legal value.
        if (parseGatingValue(parsed.mode) === null)
            return null;
        return parsed;
    }
    catch {
        return null;
    }
}
/**
 * Persist a gating decision. Throws `EmptyReasonError` if mode is not `on`
 * and reason is empty. Creates the config dir if missing.
 */
export function persistQeGating(mode, reason, opts = {}) {
    if (mode !== 'on' && (!reason || reason.trim() === '')) {
        throw new EmptyReasonError();
    }
    const configDir = opts.configDir ?? DEFAULT_CONFIG_DIR;
    const path = persistedPath(configDir);
    mkdirSync(dirname(path), { recursive: true });
    const now = opts.now ?? new Date();
    const record = {
        mode,
        reason: reason.trim(),
        applied_at: now.toISOString(),
        ...(opts.appliedBy !== undefined ? { applied_by: opts.appliedBy } : {}),
        ...(opts.correlationId !== undefined ? { correlation_id: opts.correlationId } : {}),
    };
    writeFileSync(path, JSON.stringify(record, null, 2) + '\n', 'utf-8');
    return record;
}
// ============================================================================
// Resolution
// ============================================================================
/**
 * Resolve the effective gating mode from env > persisted > default.
 */
export function resolveQeGatingMode(opts = {}) {
    const env = opts.env ?? process.env;
    const rawEnv = env[ENV_VAR_NAME];
    const fromEnv = parseGatingValue(rawEnv);
    if (fromEnv !== null) {
        const result = { mode: fromEnv, source: 'env' };
        if (rawEnv !== undefined)
            result.rawEnv = rawEnv;
        return result;
    }
    const persisted = loadPersistedQeGating(opts);
    if (persisted !== null) {
        return { mode: persisted.mode, source: 'persisted', reason: persisted.reason };
    }
    return { mode: DEFAULT_MODE, source: 'default' };
}
// ============================================================================
// Short-hand helpers
// ============================================================================
export function isGatingOn(opts = {}) {
    return resolveQeGatingMode(opts).mode === 'on';
}
export function isGatingAdvisory(opts = {}) {
    return resolveQeGatingMode(opts).mode === 'advisory';
}
export function isGatingOff(opts = {}) {
    return resolveQeGatingMode(opts).mode === 'off';
}
//# sourceMappingURL=qe-gating.js.map