// SPDX-License-Identifier: Apache-2.0
// Copyright 2024-2026 Vorion LLC
/**
 * INTENT - Goal Processing
 *
 * Production-grade intent intake with validation, persistence, and audit events.
 */
import { createHash, createHmac } from 'node:crypto';
import { z } from 'zod';
import { createLogger } from '../common/logger.js';
import { getConfig } from '../common/config.js';
import { getRedis } from '../common/redis.js';
import { getLockService } from '../common/lock.js';
import { VorionError, TrustInsufficientError, } from '../common/types.js';
import { validateTransition, StateMachineError, canCancel, getTransitionEvent, } from './state-machine.js';
import { IntentRepository, } from './repository.js';
import { enqueueIntentSubmission } from './queues.js';
import { ConsentService, ConsentRequiredError, } from './consent.js';
import { recordIntentSubmission, recordTrustGateEvaluation, recordStatusTransition, recordError, recordLockContention, recordTrustGateBypass, recordDeduplication, recordIntentContextSize, } from './metrics.js';
import { traceDedupeCheck, traceLockAcquire, recordDedupeResult, recordLockResult, } from './tracing.js';
const logger = createLogger({ component: 'intent' });
// Payload size limits for security and performance
const MAX_PAYLOAD_SIZE_BYTES = 1024 * 1024; // 1MB max total payload
const MAX_CONTEXT_BYTES = 64 * 1024; // 64KB max context (backward compatible)
const MAX_CONTEXT_KEYS = 100;
const MAX_STRING_LENGTH = 10000;
const REDACTION_PLACEHOLDER = '[REDACTED]';
/** Export constants for use in tests and configuration */
export const PAYLOAD_LIMITS = {
    MAX_PAYLOAD_SIZE_BYTES,
    MAX_CONTEXT_BYTES,
    MAX_CONTEXT_KEYS,
    MAX_STRING_LENGTH,
};
/**
 * Schema for validating intent payload records with size limits
 */
export const intentPayloadSchema = z.record(z.unknown())
    .refine((payload) => {
    const size = Buffer.byteLength(JSON.stringify(payload), 'utf8');
    return size <= MAX_PAYLOAD_SIZE_BYTES;
}, { message: `Payload exceeds maximum size of ${MAX_PAYLOAD_SIZE_BYTES} bytes` })
    .refine((payload) => Object.keys(payload).length <= MAX_CONTEXT_KEYS, { message: `Payload exceeds maximum of ${MAX_CONTEXT_KEYS} keys` });
export const intentSubmissionSchema = z
    .object({
    entityId: z.string().uuid(),
    goal: z.string().min(1).max(MAX_STRING_LENGTH),
    context: intentPayloadSchema,
    metadata: z.record(z.unknown()).optional(),
    intentType: z.string().max(100).optional(),
    priority: z.number().int().min(0).max(10).default(0),
    idempotencyKey: z.string().max(255).optional(),
})
    .superRefine((value, ctx) => {
    // Check total payload size
    const totalPayloadBytes = Buffer.byteLength(JSON.stringify(value), 'utf8');
    if (totalPayloadBytes > MAX_PAYLOAD_SIZE_BYTES) {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: `Total payload exceeds maximum size of ${MAX_PAYLOAD_SIZE_BYTES} bytes`,
            path: [],
        });
    }
    // Check context size (backward compatible with original limit)
    const contextBytes = Buffer.byteLength(JSON.stringify(value.context ?? {}), 'utf8');
    if (contextBytes > MAX_CONTEXT_BYTES) {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: `Context payload exceeds ${MAX_CONTEXT_BYTES} bytes`,
            path: ['context'],
        });
    }
});
/**
 * Schema for bulk intent submission options
 */
export const bulkIntentOptionsSchema = z.object({
    /** Stop processing on first error (default: false - continue processing all items) */
    stopOnError: z.boolean().default(false),
    /** Return successful items even if some fail (default: true) */
    returnPartial: z.boolean().default(true),
});
/**
 * Schema for bulk intent submission request
 */
export const bulkIntentSubmissionSchema = z.object({
    /** Array of intents to submit (1-100 items) */
    intents: z.array(intentSubmissionSchema).min(1).max(100),
    /** Optional processing options */
    options: bulkIntentOptionsSchema.optional(),
});
/**
 * Intent service for managing intent lifecycle
 */
export class IntentService {
    config;
    redis;
    lockService;
    consentService;
    repository;
    /**
     * Create a new IntentService instance.
     *
     * @param deps - Optional dependencies for dependency injection.
     *               If not provided, uses global singletons (backward compatible).
     *
     * @example
     * // Default usage (backward compatible)
     * const service = new IntentService();
     *
     * @example
     * // With dependency injection (for testing)
     * const service = new IntentService({
     *   repository: mockRepository,
     *   consentService: mockConsentService,
     *   config: testConfig,
     *   redis: mockRedis,
     *   lockService: mockLockService,
     * });
     */
    constructor(deps = {}) {
        this.config = deps.config ?? getConfig();
        this.redis = deps.redis ?? getRedis();
        this.lockService = deps.lockService ?? getLockService();
        this.repository = deps.repository ?? new IntentRepository();
        this.consentService = deps.consentService ?? new ConsentService();
    }
    /**
     * Check if user has a privileged role that allows bypass flags
     */
    hasPrivilegedRole(roles) {
        if (!roles || roles.length === 0)
            return false;
        return roles.some(role => role === 'admin' || role === 'system');
    }
    /**
     * Validate and authorize bypass flag usage
     * Returns true if bypass is allowed, false otherwise
     */
    validateBypassAuthorization(bypassType, options) {
        const hasPrivilege = this.hasPrivilegedRole(options.userRoles);
        if (!hasPrivilege) {
            logger.warn({
                tenantId: options.tenantId,
                userId: options.userId,
                userRoles: options.userRoles,
                bypassType,
            }, `SECURITY: Unauthorized bypass${bypassType === 'trustGate' ? 'TrustGate' : 'ConsentCheck'} attempt - requires admin or system role`);
            return false;
        }
        // Log deprecation warning
        logger.warn({
            tenantId: options.tenantId,
            userId: options.userId,
            userRoles: options.userRoles,
            bypassType,
        }, `DEPRECATION: bypass${bypassType === 'trustGate' ? 'TrustGate' : 'ConsentCheck'} flag is deprecated and will be removed in a future version`);
        // Audit log for bypass usage
        logger.info({
            tenantId: options.tenantId,
            userId: options.userId,
            userRoles: options.userRoles,
            bypassType,
            timestamp: new Date().toISOString(),
        }, `AUDIT: Bypass flag used by privileged user`);
        return true;
    }
    /**
     * Submit a new intent with trust gate validation and transaction support
     */
    async submit(payload, options) {
        // Record context size for observability
        const contextBytes = Buffer.byteLength(JSON.stringify(payload.context ?? {}), 'utf8');
        recordIntentContextSize(options.tenantId, payload.intentType, contextBytes);
        // Validate bypass flags authorization
        const effectiveBypassTrustGate = options.bypassTrustGate &&
            this.validateBypassAuthorization('trustGate', options);
        const effectiveBypassConsentCheck = options.bypassConsentCheck &&
            this.validateBypassAuthorization('consentCheck', options);
        // Consent validation (GDPR/SOC2 compliance)
        // Check data_processing consent before processing any intent
        if (!effectiveBypassConsentCheck && options.userId) {
            const consentValidation = await this.validateDataProcessingConsent(options.userId, options.tenantId);
            if (!consentValidation.valid) {
                logger.warn({ userId: options.userId, tenantId: options.tenantId, reason: consentValidation.reason }, 'Intent submission rejected: data processing consent not granted');
                recordIntentSubmission(options.tenantId, payload.intentType, 'rejected', options.trustLevel);
                throw new ConsentRequiredError(options.userId, options.tenantId, 'data_processing', consentValidation.reason);
            }
            logger.debug({ userId: options.userId, tenantId: options.tenantId, consentVersion: consentValidation.version }, 'Data processing consent validated');
        }
        // Trust gate validation with metrics
        if (!effectiveBypassTrustGate) {
            try {
                this.validateTrustGate(payload.intentType, options.trustLevel);
                recordTrustGateEvaluation(options.tenantId, payload.intentType, 'passed');
            }
            catch (error) {
                recordTrustGateEvaluation(options.tenantId, payload.intentType, 'rejected');
                recordIntentSubmission(options.tenantId, payload.intentType, 'rejected', options.trustLevel);
                throw error;
            }
        }
        else {
            recordTrustGateEvaluation(options.tenantId, payload.intentType, 'bypassed');
            // Record trust gate bypass for observability
            recordTrustGateBypass(options.tenantId, payload.intentType);
        }
        const dedupeHash = this.computeDedupeHash(options.tenantId, payload);
        const existing = await traceDedupeCheck(options.tenantId, payload.entityId, dedupeHash, async (span) => {
            const result = await this.repository.findByDedupeHash(dedupeHash, options.tenantId);
            recordDedupeResult(span, result !== null);
            return result;
        });
        if (existing) {
            logger.info({ intentId: existing.id }, 'Returning existing intent (dedupe)');
            recordIntentSubmission(options.tenantId, payload.intentType, 'duplicate', options.trustLevel);
            recordDeduplication(options.tenantId, 'duplicate');
            return existing;
        }
        await this.enforceTenantLimits(options.tenantId);
        // Reserve dedupe key with race condition handling
        const raceResolved = await this.reserveDedupeKey(options.tenantId, dedupeHash);
        if (raceResolved) {
            recordIntentSubmission(options.tenantId, payload.intentType, 'duplicate', options.trustLevel);
            recordDeduplication(options.tenantId, 'race_resolved');
            return raceResolved; // Another request completed the insert
        }
        // Record successful new intent deduplication
        recordDeduplication(options.tenantId, 'new');
        // Use transaction to create intent and initial event atomically
        const intent = await this.repository.createIntentWithEvent({
            tenantId: options.tenantId,
            entityId: payload.entityId,
            goal: payload.goal,
            intentType: payload.intentType ?? null,
            priority: payload.priority,
            status: 'pending',
            trustSnapshot: options.trustSnapshot ?? null,
            context: this.redactStructure(payload.context, 'context'),
            metadata: this.redactStructure(payload.metadata ?? {}, 'metadata'),
            dedupeHash,
        }, {
            eventType: 'intent.submitted',
            payload: {
                goal: payload.goal,
                intentType: payload.intentType,
                priority: payload.priority,
                trustLevel: options.trustLevel,
            },
        });
        // Record metrics
        recordIntentSubmission(options.tenantId, payload.intentType, 'success', options.trustLevel);
        recordStatusTransition(options.tenantId, 'new', 'pending');
        logger.info({ intentId: intent.id, tenantId: options.tenantId }, 'Intent submitted');
        try {
            await enqueueIntentSubmission(intent, {
                namespace: this.resolveNamespace(intent.intentType ?? undefined),
            });
        }
        catch (error) {
            logger.error({ error, intentId: intent.id }, 'Failed to enqueue intent submission');
            recordError('ENQUEUE_FAILED', 'intent-service');
        }
        return intent;
    }
    async get(id, tenantId) {
        return this.repository.findById(id, tenantId);
    }
    async getWithEvents(id, tenantId) {
        const intent = await this.repository.findById(id, tenantId);
        if (!intent)
            return null;
        const eventsResult = await this.repository.getRecentEvents(intent.id);
        const evaluationsResult = await this.repository.listEvaluations(intent.id);
        return { intent, events: eventsResult.items, evaluations: evaluationsResult.items };
    }
    async updateStatus(id, tenantId, status, previousStatus, options) {
        // Get current intent to determine previous status if not provided
        const currentIntent = await this.repository.findById(id, tenantId);
        if (!currentIntent)
            return null;
        const fromStatus = previousStatus ?? currentIntent.status;
        // Always validate state machine transition
        // skipValidation is deprecated and now only logs a warning
        if (options?.skipValidation) {
            logger.warn({ intentId: id, fromStatus, toStatus: status }, 'DEPRECATION: skipValidation option is deprecated and will be removed. Validation is now always enforced.');
        }
        const validationOptions = {};
        if (options?.hasReason !== undefined) {
            validationOptions.hasReason = options.hasReason;
        }
        if (options?.hasPermission !== undefined) {
            validationOptions.hasPermission = options.hasPermission;
        }
        const validationResult = validateTransition(fromStatus, status, validationOptions);
        if (!validationResult.valid) {
            throw new StateMachineError(validationResult, fromStatus, status);
        }
        const intent = await this.repository.updateStatus(id, tenantId, status);
        if (intent) {
            // Record status transition metric
            recordStatusTransition(tenantId, fromStatus, status);
            // Get event type from state machine
            const eventType = getTransitionEvent(fromStatus, status) ?? 'intent.status.changed';
            await this.repository.recordEvent({
                intentId: intent.id,
                eventType,
                payload: { status, previousStatus: fromStatus },
            });
            logger.info({ intentId: intent.id, status, previousStatus: fromStatus }, 'Intent status updated');
        }
        return intent;
    }
    /**
     * Cancel an in-flight intent
     */
    async cancel(id, options) {
        // Get current status for metrics before cancellation
        const currentIntent = await this.repository.findById(id, options.tenantId);
        if (!currentIntent)
            return null;
        const previousStatus = currentIntent.status;
        // Validate cancellation is allowed from current state
        if (!canCancel(previousStatus)) {
            throw new VorionError(`Cannot cancel intent in '${previousStatus}' status`, 'INVALID_STATE_TRANSITION');
        }
        const intent = await this.repository.cancelIntent(id, options.tenantId, options.reason);
        if (intent) {
            // Record status transition metric
            recordStatusTransition(options.tenantId, previousStatus ?? 'pending', 'cancelled');
            const evaluationPayload = options.cancelledBy
                ? { stage: 'cancelled', reason: options.reason, cancelledBy: options.cancelledBy }
                : { stage: 'cancelled', reason: options.reason };
            await this.repository.recordEvaluation({
                intentId: intent.id,
                tenantId: options.tenantId,
                result: evaluationPayload,
            });
            await this.repository.recordEvent({
                intentId: intent.id,
                eventType: 'intent.cancelled',
                payload: {
                    reason: options.reason,
                    cancelledBy: options.cancelledBy,
                },
            });
            logger.info({ intentId: intent.id, reason: options.reason }, 'Intent cancelled');
        }
        return intent;
    }
    /**
     * Soft delete an intent (GDPR compliant)
     */
    async delete(id, tenantId) {
        const intent = await this.repository.softDelete(id, tenantId);
        if (intent) {
            await this.repository.recordEvent({
                intentId: intent.id,
                eventType: 'intent.deleted',
                payload: { deletedAt: intent.deletedAt },
            });
            logger.info({ intentId: intent.id }, 'Intent soft deleted');
        }
        return intent;
    }
    /**
     * List intents with pagination.
     * Returns paginated results with metadata for cursor/offset-based pagination.
     *
     * @param options - List options including pagination parameters
     * @returns Paginated result with intents and pagination metadata
     */
    async list(options) {
        return this.repository.listIntents(options);
    }
    /**
     * Submit multiple intents in bulk for batch processing efficiency.
     *
     * This method processes intents sequentially, recording success/failure for each.
     * By default, it continues processing even if some intents fail.
     *
     * @param submissions - Array of intent submissions to process
     * @param options - Bulk submission options including tenantId and stopOnError flag
     * @returns BulkIntentResult with successful intents, failed intents, and statistics
     *
     * @example
     * ```typescript
     * const result = await intentService.submitBulk(
     *   [
     *     { entityId: 'uuid1', goal: 'Goal 1', context: {} },
     *     { entityId: 'uuid2', goal: 'Goal 2', context: {} },
     *   ],
     *   { tenantId: 'tenant-1', stopOnError: false }
     * );
     * // result.stats.succeeded === 2
     * ```
     */
    async submitBulk(submissions, options) {
        const results = {
            successful: [],
            failed: [],
            stats: { total: submissions.length, succeeded: 0, failed: 0 },
        };
        logger.info({ tenantId: options.tenantId, count: submissions.length }, 'Starting bulk intent submission');
        for (let i = 0; i < submissions.length; i++) {
            const submission = submissions[i];
            if (!submission)
                continue;
            try {
                const intent = await this.submit(submission, {
                    tenantId: options.tenantId,
                    trustSnapshot: options.trustSnapshot,
                    trustLevel: options.trustLevel,
                    bypassTrustGate: options.bypassTrustGate,
                    userId: options.userId,
                    bypassConsentCheck: options.bypassConsentCheck,
                });
                results.successful.push(intent);
                results.stats.succeeded++;
            }
            catch (error) {
                const errorMessage = error instanceof Error ? error.message : 'Unknown error';
                results.failed.push({
                    index: i,
                    input: submission,
                    error: errorMessage,
                });
                results.stats.failed++;
                logger.warn({ tenantId: options.tenantId, index: i, error: errorMessage }, 'Bulk intent submission failed for item');
                if (options.stopOnError) {
                    logger.info({ tenantId: options.tenantId, stoppedAtIndex: i, processed: i + 1, total: submissions.length }, 'Bulk intent submission stopped due to stopOnError flag');
                    break;
                }
            }
        }
        logger.info({
            tenantId: options.tenantId,
            total: results.stats.total,
            succeeded: results.stats.succeeded,
            failed: results.stats.failed,
        }, 'Bulk intent submission completed');
        return results;
    }
    async updateTrustMetadata(id, tenantId, trustSnapshot, trustLevel, trustScore) {
        return this.repository.updateTrustMetadata(id, tenantId, trustSnapshot, trustLevel, trustScore);
    }
    async recordEvaluation(intentId, tenantId, payload) {
        return this.repository.recordEvaluation({
            intentId,
            tenantId,
            result: payload,
        });
    }
    /**
     * Verify event chain integrity for an intent
     */
    async verifyEventChain(intentId) {
        return this.repository.verifyEventChain(intentId);
    }
    /**
     * Get minimum required trust level for an intent type
     */
    getRequiredTrustLevel(intentType) {
        if (!intentType) {
            return this.config.intent.defaultMinTrustLevel;
        }
        const gates = this.config.intent.trustGates;
        return (gates[intentType] ?? this.config.intent.defaultMinTrustLevel);
    }
    /**
     * Validate trust gate for intent submission
     */
    validateTrustGate(intentType, trustLevel) {
        const requiredLevel = this.getRequiredTrustLevel(intentType);
        const actualLevel = trustLevel ?? 0;
        if (actualLevel < requiredLevel) {
            throw new TrustInsufficientError(requiredLevel, actualLevel);
        }
    }
    /**
     * Validate data processing consent for a user
     * Required for GDPR/SOC2 compliance before processing any intent
     */
    async validateDataProcessingConsent(userId, tenantId) {
        return this.consentService.validateConsent(userId, tenantId, 'data_processing');
    }
    /**
     * Get the consent service instance for direct consent management
     */
    getConsentService() {
        return this.consentService;
    }
    async enforceTenantLimits(tenantId) {
        const maxInFlight = this.config.intent.tenantMaxInFlight[tenantId] ??
            this.config.intent.defaultMaxInFlight;
        if (!maxInFlight)
            return;
        const inFlight = await this.repository.countActiveIntents(tenantId);
        if (inFlight >= maxInFlight) {
            throw new VorionError(`Tenant ${tenantId} exceeded concurrent intent limit (${maxInFlight})`, 'INTENT_RATE_LIMIT');
        }
    }
    /**
     * Reserve dedupe key with proper distributed locking.
     * Uses exponential backoff and proper lock semantics to handle race conditions.
     * Returns null if reservation succeeded, or an existing Intent if found after conflict.
     */
    async reserveDedupeKey(tenantId, dedupeHash) {
        const lockKey = `intent:dedupe:${tenantId}:${dedupeHash}`;
        // Try to acquire a distributed lock with tracing
        const lockResult = await traceLockAcquire(tenantId, lockKey, async (span) => {
            const result = await this.lockService.acquire(lockKey, {
                lockTimeoutMs: 30000, // Hold lock for max 30 seconds
                acquireTimeoutMs: 5000, // Wait up to 5 seconds to acquire
                retryDelayMs: 50, // Start with 50ms retry delay
                maxRetryDelayMs: 500, // Max 500ms between retries
                jitterFactor: 0.25, // 25% jitter
            });
            recordLockResult(span, result.acquired, 5000);
            return result;
        });
        if (!lockResult.acquired || !lockResult.lock) {
            // Record lock contention - timeout scenario
            recordLockContention(tenantId, 'timeout');
            // Could not acquire lock - check if intent was created by another request
            const existing = await this.repository.findByDedupeHash(dedupeHash, tenantId);
            if (existing) {
                // Record lock contention - conflict scenario (another request won)
                recordLockContention(tenantId, 'conflict');
                logger.info({ intentId: existing.id }, 'Race resolved: returning existing intent');
                return existing;
            }
            // Lock acquisition timed out and no existing intent found
            throw new VorionError('Intent submission in progress, please retry', 'INTENT_LOCKED');
        }
        // Record successful lock acquisition
        recordLockContention(tenantId, 'acquired');
        try {
            // Double-check database while holding lock
            const existing = await this.repository.findByDedupeHash(dedupeHash, tenantId);
            if (existing) {
                logger.info({ intentId: existing.id }, 'Intent already exists (found under lock)');
                return existing;
            }
            // Also set a Redis key for faster dedupe checks (optimization)
            const ttl = this.config.intent.dedupeTtlSeconds;
            const dedupeKey = `intent:dedupe:marker:${tenantId}:${dedupeHash}`;
            await this.redis.set(dedupeKey, '1', 'EX', ttl);
            return null; // Reservation succeeded, caller should proceed with insert
        }
        finally {
            // Release the lock after insert is complete (caller will release via callback)
            // Note: In a more robust implementation, we'd pass the lock to the caller
            // For now, we release immediately since the DB unique constraint is the final guard
            await lockResult.lock.release();
        }
    }
    resolveNamespace(intentType) {
        if (!intentType)
            return this.config.intent.defaultNamespace;
        return (this.config.intent.namespaceRouting[intentType] ??
            this.config.intent.defaultNamespace);
    }
    redactStructure(payload, prefix) {
        const cloned = JSON.parse(JSON.stringify(payload ?? {}));
        const prefixWithDot = `${prefix}.`;
        const relevant = this.config.intent.sensitivePaths
            .filter((path) => path.startsWith(prefixWithDot))
            .map((path) => path.slice(prefixWithDot.length));
        for (const path of relevant) {
            this.applyRedaction(cloned, path.split('.'));
        }
        return cloned;
    }
    applyRedaction(target, parts) {
        if (!parts.length) {
            return;
        }
        let current = target;
        for (let i = 0; i < parts.length; i++) {
            const part = parts[i];
            if (!part)
                continue;
            if (i === parts.length - 1) {
                if (current && typeof current === 'object' && part in current) {
                    current[part] = REDACTION_PLACEHOLDER;
                }
                return;
            }
            if (current && typeof current === 'object' && part in current) {
                const next = current[part];
                if (next && typeof next === 'object') {
                    current = next;
                }
                else {
                    return;
                }
            }
            else {
                return;
            }
        }
    }
    /**
     * Compute a secure deduplication hash using HMAC-SHA256.
     *
     * Security features:
     * - Uses HMAC with a secret key to prevent hash prediction attacks
     * - Includes a timestamp bucket to limit replay window
     * - Falls back to plain SHA-256 only in development (with warning)
     *
     * @param tenantId - The tenant identifier
     * @param payload - The intent submission payload
     * @returns A hex-encoded HMAC digest for deduplication
     */
    computeDedupeHash(tenantId, payload) {
        const secret = this.config.intent.dedupeSecret;
        const windowSeconds = this.config.intent.dedupeTimestampWindowSeconds;
        // Compute timestamp bucket for replay protection
        // Requests within the same bucket will have the same hash component
        const timestampBucket = Math.floor(Date.now() / 1000 / windowSeconds);
        // Build the data to hash
        const dataComponents = [
            tenantId,
            payload.entityId,
            payload.goal,
            JSON.stringify(payload.context ?? {}),
            payload.intentType ?? '',
            payload.idempotencyKey ?? '',
            timestampBucket.toString(),
        ];
        const data = dataComponents.join('|');
        if (secret) {
            // Production: Use HMAC-SHA256 with secret
            const hmac = createHmac('sha256', secret);
            hmac.update(data);
            return hmac.digest('hex');
        }
        // Development fallback: Use plain SHA-256 (with warning logged once)
        if (!this.warnedAboutMissingDedupeSecret) {
            logger.warn('VORION_DEDUPE_SECRET not set - using insecure SHA-256 for deduplication. ' +
                'This is acceptable for development but MUST be configured in production.');
            this.warnedAboutMissingDedupeSecret = true;
        }
        const hash = createHash('sha256');
        hash.update(data);
        return hash.digest('hex');
    }
    warnedAboutMissingDedupeSecret = false;
}
/**
 * Create a new intent service instance with dependency injection.
 *
 * This is the preferred way to create services in production code
 * as it makes dependencies explicit and testable.
 *
 * @param deps - Optional dependencies. If not provided, uses global singletons.
 * @returns Configured IntentService instance
 *
 * @example
 * // Default usage (backward compatible)
 * const service = createIntentService();
 *
 * @example
 * // With custom dependencies
 * const service = createIntentService({
 *   repository: customRepository,
 *   config: customConfig,
 *   redis: customRedis,
 * });
 */
export function createIntentService(deps = {}) {
    return new IntentService(deps);
}
// Re-export consent management types and functions
export { ConsentService, ConsentRequiredError, ConsentPolicyNotFoundError, createConsentService, } from './consent.js';
// Re-export OpenAPI specification
// NOTE: registerIntentRoutes is NOT re-exported here to avoid circular dependency.
// Import it directly from './routes.js' instead.
export { intentOpenApiSpec, getOpenApiSpec, getOpenApiSpecJson, } from './openapi.js';
// NOTE: Graceful shutdown utilities are NOT re-exported here to avoid circular dependency.
// Import them directly from './shutdown.js' instead.
// Re-export pagination constants and types from repository
export { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, } from './repository.js';
// Re-export intent classification system
export { IntentClassifier, createIntentClassifier, RiskAssessor, createRiskAssessor, ACTION_PATTERNS, RESOURCE_SENSITIVITY, matchActionPattern, matchResourceSensitivity, getResourceSensitivityLevel, getResourceRiskTier, inferCategoryFromAction, scoreToTier, tierToMinScore, requiresApproval, } from './classifier/index.js';
//# sourceMappingURL=index.js.map