import { normalizeToolsConfig, } from "./tools-config-normalizer.js";
import { frameworkErrorFromToolConfigDiagnostics, frameworkErrorFromUnknown, } from "./framework-error.js";
import { createPackagedToolRegistry } from "./createDefaultToolRegistry.js";
export function frameworkErrorFromToolConfigValidation(args) {
    if (Array.isArray(args.diagnostics) && args.diagnostics.length > 0) {
        return frameworkErrorFromToolConfigDiagnostics({
            source: args.source,
            diagnostics: args.diagnostics,
            recoverable: args.recoverable,
        });
    }
    return frameworkErrorFromUnknown({
        kind: "tool-config",
        source: args.source,
        error: args.error ?? new Error("Invalid tools config"),
        recoverable: args.recoverable,
    });
}
const DEFAULT_STRICTNESS = "error";
export function normalizeToolConfigStrictness(value) {
    if (value === "off" || value === "warn" || value === "error") {
        return value;
    }
    return DEFAULT_STRICTNESS;
}
function createDiagnostic(diagnostic) {
    return diagnostic;
}
function getRegistryToolMap(toolRegistry) {
    if (!toolRegistry)
        return new Map();
    return new Map(toolRegistry
        .getAllTools()
        .slice()
        .sort((left, right) => left.toolId.localeCompare(right.toolId))
        .map((tool) => [tool.toolId, tool]));
}
function sanitizeProviderConfig(providerId, providerConfig, tool, diagnostics) {
    if (!providerConfig || !tool?.provider?.sanitizeConfig)
        return providerConfig;
    try {
        const sanitized = tool.provider.sanitizeConfig(providerConfig);
        if (!sanitized ||
            typeof sanitized !== "object" ||
            Array.isArray(sanitized)) {
            diagnostics.push(createDiagnostic({
                code: "tools.providerSanitizeFailed",
                severity: "error",
                path: `providers.${providerId}`,
                message: `Provider sanitizer for "${providerId}" must return an object.`,
                providerId,
                toolId: providerId,
            }));
            return providerConfig;
        }
        return sanitized;
    }
    catch (error) {
        diagnostics.push(createDiagnostic({
            code: "tools.providerSanitizeFailed",
            severity: "error",
            path: `providers.${providerId}`,
            message: `Provider sanitizer failed for "${providerId}": ${error instanceof Error ? error.message : String(error)}`,
            providerId,
            toolId: providerId,
        }));
        return providerConfig;
    }
}
function validateProviderConfig(providerId, providerConfig, tool, diagnostics) {
    if (!providerConfig || !tool?.provider?.validateConfig)
        return;
    try {
        const providerDiagnostics = tool.provider.validateConfig(providerConfig);
        if (!Array.isArray(providerDiagnostics)) {
            diagnostics.push(createDiagnostic({
                code: "tools.invalidProviderValidation",
                severity: "error",
                path: `providers.${providerId}`,
                message: `Provider validator for "${providerId}" must return an array.`,
                providerId,
                toolId: providerId,
            }));
            return;
        }
        for (const diagnostic of providerDiagnostics) {
            diagnostics.push({
                ...diagnostic,
                path: diagnostic.path || `providers.${providerId}`,
                providerId: diagnostic.providerId || providerId,
                toolId: diagnostic.toolId || providerId,
            });
        }
    }
    catch (error) {
        diagnostics.push(createDiagnostic({
            code: "tools.providerValidateFailed",
            severity: "error",
            path: `providers.${providerId}`,
            message: `Provider validator failed for "${providerId}": ${error instanceof Error ? error.message : String(error)}`,
            providerId,
            toolId: providerId,
        }));
    }
}
function collectPlacementDiagnostics(config, toolMap, diagnostics) {
    if (toolMap.size === 0)
        return;
    const levelOrder = ["section", "item", "passage"];
    for (const level of levelOrder) {
        for (const toolId of config.placement[level]) {
            const tool = toolMap.get(toolId);
            if (!tool) {
                diagnostics.push(createDiagnostic({
                    code: "tools.unknownToolId",
                    severity: "error",
                    path: `placement.${level}`,
                    message: `Unknown tool id "${toolId}" in placement.${level}.`,
                    toolId,
                }));
                continue;
            }
            const supportsLevel = tool.supportedLevels.includes(level);
            // Leniency policy: section-capable tools are tolerated in item placement.
            // This avoids strict failures for common host config patterns while still
            // enforcing upward-invalid placements (for example item-only -> section).
            const allowSectionToolInItemPlacement = level === "item" && tool.supportedLevels.includes("section");
            if (!supportsLevel && !allowSectionToolInItemPlacement) {
                diagnostics.push(createDiagnostic({
                    code: "tools.unsupportedLevel",
                    severity: "error",
                    path: `placement.${level}`,
                    message: `Tool "${toolId}" does not support level "${level}".`,
                    toolId,
                }));
            }
        }
    }
}
function collectPolicyDiagnostics(config, toolMap, diagnostics) {
    if (toolMap.size === 0)
        return;
    for (const toolId of config.policy.allowed || []) {
        if (toolMap.has(toolId))
            continue;
        diagnostics.push(createDiagnostic({
            code: "tools.unknownToolId",
            severity: "error",
            path: "policy.allowed",
            message: `Unknown tool id "${toolId}" in policy.allowed.`,
            toolId,
        }));
    }
    for (const toolId of config.policy.blocked || []) {
        if (toolMap.has(toolId))
            continue;
        diagnostics.push(createDiagnostic({
            code: "tools.unknownToolId",
            severity: "error",
            path: "policy.blocked",
            message: `Unknown tool id "${toolId}" in policy.blocked.`,
            toolId,
        }));
    }
}
function collectProviderKeyDiagnostics(config, toolMap, hasRemovedTtsKey, diagnostics) {
    if (hasRemovedTtsKey) {
        diagnostics.push(createDiagnostic({
            code: "tools.removedProviderKey",
            severity: "error",
            path: "providers.tts",
            message: `Provider key "tts" is no longer supported. Use "providers.textToSpeech".`,
            providerId: "tts",
            toolId: "textToSpeech",
        }));
    }
    for (const providerId of Object.keys(config.providers).sort()) {
        if (toolMap.size === 0 || toolMap.has(providerId))
            continue;
        diagnostics.push(createDiagnostic({
            code: "tools.unknownProviderKey",
            severity: "error",
            path: `providers.${providerId}`,
            message: `Unknown provider key "${providerId}".`,
            providerId,
            toolId: providerId,
        }));
    }
}
function emitWarnings(diagnostics, source) {
    if (diagnostics.length === 0)
        return;
    for (const diagnostic of diagnostics) {
        const prefix = `[tool-config-validation:${source}]`;
        console.warn(`${prefix} ${diagnostic.path} - ${diagnostic.message}`);
    }
}
function throwValidationError(diagnostics, source) {
    const formatted = diagnostics
        .map((diagnostic) => `- ${diagnostic.path}: ${diagnostic.message}`)
        .join("\n");
    throw new Error(`[tool-config-validation:${source}] Invalid tools config:\n${formatted}`);
}
export function normalizeAndValidateToolsConfig(input, options = {}) {
    const strictness = normalizeToolConfigStrictness(options.strictness);
    const source = options.source ?? "tools";
    const registryTools = getRegistryToolMap(options.toolRegistry ?? createPackagedToolRegistry());
    const normalized = normalizeToolsConfig(input);
    const diagnostics = [];
    const hasRemovedTtsKey = Object.hasOwn(normalized.providers, "tts");
    const nextProviders = {
        ...(normalized.providers || {}),
    };
    for (const providerId of Object.keys(nextProviders).sort()) {
        if (providerId === "tts") {
            delete nextProviders.tts;
            continue;
        }
        const tool = registryTools.get(providerId);
        nextProviders[providerId] = sanitizeProviderConfig(providerId, nextProviders[providerId], tool, diagnostics);
        validateProviderConfig(providerId, nextProviders[providerId], tool, diagnostics);
    }
    const nextConfig = {
        ...normalized,
        providers: nextProviders,
    };
    collectPlacementDiagnostics(nextConfig, registryTools, diagnostics);
    collectPolicyDiagnostics(nextConfig, registryTools, diagnostics);
    collectProviderKeyDiagnostics(nextConfig, registryTools, hasRemovedTtsKey, diagnostics);
    if (hasRemovedTtsKey) {
        throwValidationError(diagnostics.filter((entry) => entry.code === "tools.removedProviderKey"), source);
    }
    if (strictness === "warn") {
        emitWarnings(diagnostics, source);
    }
    if (strictness === "error" && diagnostics.length > 0) {
        throwValidationError(diagnostics, source);
    }
    return {
        config: nextConfig,
        diagnostics,
    };
}
//# sourceMappingURL=tool-config-validation.js.map