/**
 * Utilities Module
 *
 * Handles validation, normalization, schema conversion, and error handling utilities.
 * Extracted from BaseProvider to follow Single Responsibility Principle.
 *
 * Responsibilities:
 * - Options validation (text generation and stream options)
 * - Options normalization (string to object conversion)
 * - Provider information formatting
 * - Timeout parsing and calculation
 * - Schema utilities (Zod detection, permissive schema creation, OpenAI strict mode fixes)
 * - Tool result conversion
 * - Middleware options extraction
 * - Common error pattern handling
 *
 * @module core/modules/Utilities
 */
import { z } from "zod";
import { logger } from "../../utils/logger.js";
import { getSafeMaxTokens } from "../../utils/tokenLimits.js";
import { TimeoutError, getDefaultTimeout, parseTimeout, } from "../../utils/timeout.js";
import { validateStreamOptions as validateStreamOpts, validateTextGenerationOptions, ValidationError, createValidationSummary, } from "../../utils/parameterValidation.js";
import { STEP_LIMITS } from "../constants.js";
/**
 * Utilities class - Provides validation, normalization, and utility methods
 */
export class Utilities {
    providerName;
    modelName;
    defaultTimeout;
    middlewareOptions;
    constructor(providerName, modelName, defaultTimeout = 30000, middlewareOptions) {
        this.providerName = providerName;
        this.modelName = modelName;
        this.defaultTimeout = defaultTimeout;
        this.middlewareOptions = middlewareOptions;
    }
    /**
     * Validate text generation options
     */
    validateOptions(options) {
        const validation = validateTextGenerationOptions(options);
        if (!validation.isValid) {
            const summary = createValidationSummary(validation);
            throw new ValidationError(`Text generation options validation failed: ${summary}`, "options", "VALIDATION_FAILED", validation.suggestions);
        }
        // Log warnings if any
        if (validation.warnings.length > 0) {
            logger.warn("Text generation options validation warnings:", validation.warnings);
        }
        // Additional BaseProvider-specific validation
        if (options.maxSteps !== undefined) {
            if (options.maxSteps < STEP_LIMITS.min ||
                options.maxSteps > STEP_LIMITS.max) {
                throw new ValidationError(`maxSteps must be between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`, "maxSteps", "OUT_OF_RANGE", [
                    `Use a value between ${STEP_LIMITS.min} and ${STEP_LIMITS.max} for optimal performance`,
                ]);
            }
        }
    }
    /**
     * Validate stream options
     */
    validateStreamOptions(options) {
        const validation = validateStreamOpts(options);
        if (!validation.isValid) {
            const summary = createValidationSummary(validation);
            throw new ValidationError(`Stream options validation failed: ${summary}`, "options", "VALIDATION_FAILED", validation.suggestions);
        }
        // Log warnings if any
        if (validation.warnings.length > 0) {
            logger.warn("Stream options validation warnings:", validation.warnings);
        }
        // Additional BaseProvider-specific validation
        if (options.maxSteps !== undefined) {
            if (options.maxSteps < STEP_LIMITS.min ||
                options.maxSteps > STEP_LIMITS.max) {
                throw new ValidationError(`maxSteps must be between ${STEP_LIMITS.min} and ${STEP_LIMITS.max}`, "maxSteps", "OUT_OF_RANGE", [
                    `Use a value between ${STEP_LIMITS.min} and ${STEP_LIMITS.max} for optimal performance`,
                ]);
            }
        }
    }
    /**
     * Normalize text generation options from string or object
     */
    normalizeTextOptions(optionsOrPrompt) {
        if (typeof optionsOrPrompt === "string") {
            const safeMaxTokens = getSafeMaxTokens(this.providerName, this.modelName);
            return {
                prompt: optionsOrPrompt,
                provider: this.providerName,
                model: this.modelName,
                maxTokens: safeMaxTokens,
            };
        }
        // Handle both prompt and input.text formats
        const prompt = optionsOrPrompt.prompt || optionsOrPrompt.input?.text || "";
        const modelName = optionsOrPrompt.model || this.modelName;
        const providerName = optionsOrPrompt.provider || this.providerName;
        // Apply safe maxTokens based on provider and model
        const safeMaxTokens = getSafeMaxTokens(providerName, modelName, optionsOrPrompt.maxTokens);
        // CRITICAL FIX: Preserve the entire input object for multimodal support
        // This ensures images and content arrays are not lost during normalization
        const normalizedOptions = {
            ...optionsOrPrompt,
            prompt,
            provider: providerName,
            model: modelName,
            maxTokens: safeMaxTokens,
        };
        // Ensure input object is preserved if it exists (for multimodal support)
        if (optionsOrPrompt.input) {
            normalizedOptions.input = {
                ...optionsOrPrompt.input,
                text: prompt, // Ensure text is consistent
            };
        }
        return normalizedOptions;
    }
    /**
     * Normalize stream options from string or object
     */
    normalizeStreamOptions(optionsOrPrompt) {
        if (typeof optionsOrPrompt === "string") {
            const safeMaxTokens = getSafeMaxTokens(this.providerName, this.modelName);
            return {
                input: { text: optionsOrPrompt },
                provider: this.providerName,
                model: this.modelName,
                maxTokens: safeMaxTokens,
            };
        }
        const modelName = optionsOrPrompt.model || this.modelName;
        const providerName = optionsOrPrompt.provider || this.providerName;
        // Apply safe maxTokens based on provider and model
        const safeMaxTokens = getSafeMaxTokens(providerName, modelName, optionsOrPrompt.maxTokens);
        return {
            ...optionsOrPrompt,
            provider: providerName,
            model: modelName,
            maxTokens: safeMaxTokens,
        };
    }
    /**
     * Get provider information
     */
    getProviderInfo() {
        return {
            provider: this.providerName,
            model: this.modelName,
        };
    }
    /**
     * Get timeout value in milliseconds from options
     * Supports number or string formats (e.g., '30s', '2m', '1h')
     */
    getTimeout(options) {
        // If caller specified a timeout, use it (supports number ms and string formats)
        if (options.timeout !== undefined && options.timeout !== null) {
            const parsed = parseTimeout(options.timeout);
            if (parsed !== undefined) {
                return parsed;
            }
        }
        // Use per-provider default (e.g., vertex=60s, ollama=5m) instead of global 30s.
        // Always use "generate" operation here — streaming operations have their own
        // longer timeout (DEFAULT_TIMEOUTS.streaming = 2m) applied by the streaming
        // infrastructure in BaseProvider.stream(). Both TextGenerationOptions and
        // StreamOptions share the same `input` property, so there is no reliable
        // discriminator to detect streaming at this level.
        const providerDefault = parseTimeout(getDefaultTimeout(this.providerName, "generate"));
        return providerDefault ?? this.defaultTimeout;
    }
    /**
     * Get timeout scaled by estimated input token count.
     * For large contexts (>100K tokens), increase timeout proportionally.
     */
    getContextAwareTimeout(options, estimatedTokens) {
        const baseTimeout = this.getTimeout(options);
        if (!estimatedTokens || estimatedTokens <= 100_000) {
            return baseTimeout;
        }
        // Scale: >100K → 1.5x, >200K → 2x, >300K → 2.5x (capped at 4x)
        // Use (estimatedTokens - 1) so exact multiples stay in the lower tier
        // (e.g., 100_000 → 1x, 100_001 → 1.5x)
        const scale = 1 + Math.floor((estimatedTokens - 1) / 100_000) * 0.5;
        return Math.round(baseTimeout * Math.min(scale, 4));
    }
    /**
     * Check if a schema is a Zod schema
     */
    isZodSchema(schema) {
        return (typeof schema === "object" &&
            schema !== null &&
            // Most Zod schemas have an internal _def and a parse method
            typeof schema.parse === "function");
    }
    /**
     * Convert tool execution result from MCP format to standard format
     * Handles tool failures gracefully to prevent stream termination
     */
    async convertToolResult(result) {
        // Handle MCP-style results
        if (result && typeof result === "object" && "success" in result) {
            const mcpResult = result;
            if (mcpResult.success) {
                // If `data` field exists, return it (standard MCP format).
                // Otherwise fall back to the full result object so the LLM
                // receives the actual payload instead of `undefined`, which
                // would cause it to re-call the tool in a loop.
                return mcpResult.data !== undefined ? mcpResult.data : result;
            }
            else {
                // Instead of throwing, return a structured error result
                // This prevents tool failures from terminating streams
                const errorMsg = typeof mcpResult.error === "string"
                    ? mcpResult.error
                    : "Tool execution failed";
                // Log the error for debugging but don't throw
                logger.warn(`Tool execution failed: ${errorMsg}`);
                // Return error as structured data that can be processed by the AI
                return {
                    isError: true,
                    error: errorMsg,
                    content: [
                        {
                            type: "text",
                            text: `Tool execution failed: ${errorMsg}`,
                        },
                    ],
                };
            }
        }
        return result;
    }
    /**
     * Create a permissive Zod schema that accepts all parameters as-is
     */
    createPermissiveZodSchema() {
        // Create a permissive record that accepts any object structure
        // This allows all parameters to pass through without validation issues
        return z
            .record(z.string(), z.unknown())
            .transform((data) => {
            // Return the data as-is to preserve all parameter information
            return data;
        });
    }
    /**
     * Recursively fix JSON Schema for OpenAI strict mode compatibility
     * OpenAI requires additionalProperties: false at ALL levels and preserves required array
     */
    fixSchemaForOpenAIStrictMode(schema) {
        const fixedSchema = JSON.parse(JSON.stringify(schema));
        if (fixedSchema.type === "object" &&
            fixedSchema.properties &&
            typeof fixedSchema.properties === "object") {
            const allPropertyNames = Object.keys(fixedSchema.properties);
            if (!fixedSchema.required || !Array.isArray(fixedSchema.required)) {
                fixedSchema.required = [];
            }
            fixedSchema.additionalProperties = false;
            for (const propName of allPropertyNames) {
                const propValue = fixedSchema.properties[propName];
                if (propValue && typeof propValue === "object") {
                    if (propValue.type === "object") {
                        fixedSchema.properties[propName] =
                            this.fixSchemaForOpenAIStrictMode(propValue);
                    }
                    else if (propValue.type === "array" &&
                        propValue.items &&
                        typeof propValue.items === "object") {
                        fixedSchema.properties[propName].items =
                            this.fixSchemaForOpenAIStrictMode(propValue.items);
                    }
                }
            }
        }
        return fixedSchema;
    }
    /**
     * Extract middleware options from generation/stream options
     * This is the single source of truth for deciding if middleware should be applied
     */
    extractMiddlewareOptions(options) {
        // 1. Determine effective middleware config: per-request overrides global.
        const middlewareOpts = options.middleware ??
            this.middlewareOptions;
        if (!middlewareOpts) {
            return null;
        }
        // 2. The middleware property must be an object with configuration.
        if (typeof middlewareOpts !== "object" ||
            middlewareOpts === null ||
            middlewareOpts instanceof Date ||
            middlewareOpts instanceof RegExp) {
            return null;
        }
        // 3. Check if the middleware object has any actual configuration keys.
        const fullOpts = middlewareOpts;
        const hasArray = (arr) => Array.isArray(arr) && arr.length > 0;
        const hasConfig = !!fullOpts.middlewareConfig ||
            hasArray(fullOpts.enabledMiddleware) ||
            hasArray(fullOpts.disabledMiddleware) ||
            !!fullOpts.preset ||
            hasArray(fullOpts.middleware);
        if (!hasConfig) {
            return null;
        }
        // 4. Return the formatted options if configuration is present.
        return {
            ...fullOpts,
            global: {
                collectStats: true,
                continueOnError: true,
                ...(fullOpts.global || {}),
            },
        };
    }
    /**
     * Handle common error patterns across providers
     * Returns transformed error or null if not a common pattern
     */
    handleCommonErrors(error) {
        if (error instanceof TimeoutError) {
            return new Error(`${this.providerName} request timed out after ${error.timeout}ms. Consider increasing timeout or using a lighter model.`);
        }
        const message = error instanceof Error ? error.message : String(error);
        // Common API key errors
        if (message.includes("API_KEY_INVALID") ||
            message.includes("Invalid API key") ||
            message.includes("authentication") ||
            message.includes("unauthorized")) {
            return new Error(`Invalid API key for ${this.providerName}. Please check your API key environment variable.`);
        }
        // Common rate limit errors
        if (message.includes("rate limit") ||
            message.includes("quota") ||
            message.includes("429")) {
            return new Error(`Rate limit exceeded for ${this.providerName}. Please wait before making more requests.`);
        }
        return null; // Not a common error, let provider handle it
    }
}
