/**
 * Session enforcement - require active sessions for write operations.
 *
 * Part of the Epic-Bound Session architecture. Enforces that write operations
 * (add, update, complete) require an active session.
 *
 * @task T4454
 * @epic T4454
 */
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { ExitCode } from '@cleocode/contracts';
import { CleoError } from '../errors.js';
import { getCleoDir } from '../paths.js';
import { sessionStatus } from './index.js';
/** Synchronous config value reader. */
function readConfigValueSync(path, defaultValue, cwd) {
    try {
        const configPath = join(getCleoDir(cwd), 'config.json');
        if (!existsSync(configPath))
            return defaultValue;
        const config = JSON.parse(readFileSync(configPath, 'utf-8'));
        const keys = path.split('.');
        let value = config;
        for (const key of keys) {
            if (value == null || typeof value !== 'object')
                return defaultValue;
            value = value[key];
        }
        return value ?? defaultValue;
    }
    catch {
        return defaultValue;
    }
}
/** Get the current enforcement mode. */
export function getEnforcementMode(cwd) {
    // CLEO_TEST_MODE disables enforcement in vitest — tests validate enforcement
    // logic directly via unit tests, not via side effects during data setup.
    if (process.env.VITEST)
        return 'none';
    try {
        const requiredForMutate = readConfigValueSync('enforcement.session.requiredForMutate', true, cwd);
        if (requiredForMutate === false)
            return 'none';
        // Legacy mode fallback from config
        const mode = readConfigValueSync('session.enforcement', 'strict', cwd);
        if (mode === 'strict' || mode === 'warn' || mode === 'none')
            return mode;
        return 'strict'; // Default
    }
    catch {
        return 'strict';
    }
}
/** Check if session enforcement is enabled. */
export function isSessionEnforcementEnabled(cwd) {
    return getEnforcementMode(cwd) !== 'none';
}
/** Get active session info. Returns null if no active session. */
export async function getActiveSessionInfo(cwd) {
    const session = await sessionStatus(cwd);
    if (!session)
        return null;
    return {
        id: session.id,
        name: session.name,
        scope: session.scope,
    };
}
/**
 * Require an active session for write operations.
 * In strict mode, throws if no session is active.
 * In warn mode, returns a warning but allows the operation.
 * In none mode, always allows.
 */
export async function requireActiveSession(operation, cwd) {
    const mode = getEnforcementMode(cwd);
    if (mode === 'none') {
        return { allowed: true, mode, session: null };
    }
    const session = await getActiveSessionInfo(cwd);
    if (session) {
        return { allowed: true, mode, session };
    }
    // No active session
    if (mode === 'strict') {
        throw new CleoError(ExitCode.SESSION_REQUIRED, `Operation '${operation}' requires an active session`, {
            fix: 'Start a session with \'cleo session start --scope epic:T### --auto-start --name "Work"\'',
            alternatives: [
                {
                    action: 'Start session',
                    command: 'cleo session start --scope epic:T001 --auto-start --name "Work"',
                },
                { action: 'List sessions', command: 'cleo session list' },
            ],
        });
    }
    // Warn mode
    return {
        allowed: true,
        mode,
        session: null,
        warning: `No active session for operation '${operation}'. Consider starting one.`,
    };
}
/**
 * Validate that a task is within the current session's scope.
 * Only enforced when a session is active.
 */
export async function validateTaskInScope(taskId, taskEpicId, cwd) {
    const mode = getEnforcementMode(cwd);
    if (mode === 'none')
        return { inScope: true };
    const session = await getActiveSessionInfo(cwd);
    if (!session)
        return { inScope: true }; // No session = no scope enforcement
    // Global scope allows everything
    if (session.scope.type === 'global')
        return { inScope: true };
    // Epic scope: task must be within the session's epic
    if (session.scope.type === 'epic' && session.scope.epicId) {
        const epicId = session.scope.epicId;
        // Task is the epic itself or is a child
        if (taskId === epicId || taskEpicId === epicId) {
            return { inScope: true };
        }
        if (mode === 'strict') {
            throw new CleoError(ExitCode.TASK_NOT_IN_SCOPE, `Task ${taskId} is not in scope of session ${session.id} (epic: ${epicId})`, {
                fix: `Focus on tasks within epic ${epicId} or start a new session`,
                alternatives: [
                    { action: 'View session', command: `cleo session status` },
                    { action: 'End session', command: `cleo session end` },
                ],
            });
        }
        return {
            inScope: false,
            warning: `Task ${taskId} is outside session scope (epic: ${epicId})`,
        };
    }
    return { inScope: true };
}
//# sourceMappingURL=session-enforcement.js.map