// SPDX-License-Identifier: Apache-2.0
// Copyright 2024-2026 Vorion LLC
/**
 * Cognigate Resource Interceptors
 *
 * Interceptors for network and file system operations that enforce
 * resource limits and track usage.
 *
 * @packageDocumentation
 * @module @vorionsys/cognigate/resource-interceptors
 */
import * as fs from 'node:fs/promises';
import { createLogger } from '../common/logger.js';
const logger = createLogger({ component: 'cognigate-interceptors' });
/**
 * Error thrown when a resource limit is exceeded
 */
export class ResourceLimitExceededError extends Error {
    limitType;
    current;
    limit;
    constructor(message, limitType, current, limit) {
        super(message);
        this.limitType = limitType;
        this.current = current;
        this.limit = limit;
        this.name = 'ResourceLimitExceededError';
    }
}
/**
 * Create a network interceptor that wraps fetch to track and limit requests.
 *
 * @param executionId - Unique execution identifier
 * @param limits - Resource limits to enforce
 * @param stateProvider - State provider for distributed tracking
 * @param signal - Abort signal for termination
 * @returns Wrapped fetch function
 */
export function createNetworkInterceptor(executionId, limits, stateProvider, signal) {
    const maxRequests = limits.maxNetworkRequests ?? Infinity;
    return async (input, init) => {
        // Check if already aborted
        if (signal.aborted) {
            throw new ResourceLimitExceededError('Execution terminated', 'network', 0, maxRequests);
        }
        // Check and increment request count
        if (maxRequests !== Infinity) {
            const result = await stateProvider.incrementNetworkRequests(executionId, maxRequests);
            if (!result.allowed) {
                logger.warn({ executionId, current: result.current, limit: maxRequests }, 'Network request limit exceeded');
                throw new ResourceLimitExceededError(`Network request limit exceeded: ${result.current} >= ${maxRequests}`, 'network', result.current, maxRequests);
            }
            logger.debug({ executionId, requestCount: result.current, limit: maxRequests }, 'Network request allowed');
        }
        // Merge abort signal with any existing signal
        const combinedInit = {
            ...init,
            signal: combineAbortSignals(signal, init?.signal),
        };
        // Make the actual fetch request
        return fetch(input, combinedInit);
    };
}
/**
 * Create file system interceptors that wrap fs functions to track and limit operations.
 *
 * @param executionId - Unique execution identifier
 * @param limits - Resource limits to enforce
 * @param stateProvider - State provider for distributed tracking
 * @param signal - Abort signal for termination
 * @returns Object with wrapped file system functions
 */
export function createFileSystemInterceptors(executionId, limits, stateProvider, signal) {
    const maxOps = limits.maxFileSystemOps ?? Infinity;
    /**
     * Check limit and track operation before execution
     */
    async function checkAndTrack() {
        // Check if already aborted
        if (signal.aborted) {
            throw new ResourceLimitExceededError('Execution terminated', 'filesystem', 0, maxOps);
        }
        // Check and increment operation count
        if (maxOps !== Infinity) {
            const result = await stateProvider.incrementFileSystemOps(executionId, maxOps);
            if (!result.allowed) {
                logger.warn({ executionId, current: result.current, limit: maxOps }, 'File system operation limit exceeded');
                throw new ResourceLimitExceededError(`File system operation limit exceeded: ${result.current} >= ${maxOps}`, 'filesystem', result.current, maxOps);
            }
            logger.debug({ executionId, opCount: result.current, limit: maxOps }, 'File system operation allowed');
        }
    }
    /**
     * Create a wrapped version of an fs function
     */
    function wrapFsFunction(fn, operationName) {
        return (async (...args) => {
            await checkAndTrack();
            logger.debug({ executionId, operation: operationName }, 'File system operation');
            return fn(...args);
        });
    }
    return {
        readFile: wrapFsFunction(fs.readFile, 'readFile'),
        writeFile: wrapFsFunction(fs.writeFile, 'writeFile'),
        appendFile: wrapFsFunction(fs.appendFile, 'appendFile'),
        unlink: wrapFsFunction(fs.unlink, 'unlink'),
        mkdir: wrapFsFunction(fs.mkdir, 'mkdir'),
        rmdir: wrapFsFunction(fs.rmdir, 'rmdir'),
        readdir: wrapFsFunction(fs.readdir, 'readdir'),
        stat: wrapFsFunction(fs.stat, 'stat'),
        access: wrapFsFunction(fs.access, 'access'),
        rename: wrapFsFunction(fs.rename, 'rename'),
        copyFile: wrapFsFunction(fs.copyFile, 'copyFile'),
    };
}
/**
 * Combine multiple abort signals into one.
 * The combined signal will abort when any of the input signals abort.
 */
function combineAbortSignals(...signals) {
    const controller = new AbortController();
    const validSignals = signals.filter((s) => s != null);
    for (const signal of validSignals) {
        if (signal.aborted) {
            controller.abort(signal.reason);
            return controller.signal;
        }
        signal.addEventListener('abort', () => {
            controller.abort(signal.reason);
        }, { once: true });
    }
    return controller.signal;
}
/**
 * Create all interceptors for an execution
 */
export function createExecutionInterceptors(executionId, limits, stateProvider, signal) {
    return {
        fetch: createNetworkInterceptor(executionId, limits, stateProvider, signal),
        fs: createFileSystemInterceptors(executionId, limits, stateProvider, signal),
        signal,
    };
}
//# sourceMappingURL=resource-interceptors.js.map