"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveMcpScopedTransforms = resolveMcpScopedTransforms;
exports.parseMcpYaml = parseMcpYaml;
const yaml_1 = __importDefault(require("yaml"));
const log_1 = require("../log");
const log = (0, log_1.createLogger)('mcp/config');
function resolveMcpScopedTransforms(globalTransforms, scopedTransform) {
    switch (scopedTransform.kind) {
        case 'inherit':
            return globalTransforms;
        case 'override':
            return scopedTransform.transform;
        default: {
            const _exhaustive = scopedTransform;
            throw new Error(`Invalid MCP scoped transform config: ${String(_exhaustive)}`);
        }
    }
}
/**
 * NOTE: This loader parses external YAML (untrusted input). Runtime type checks are unavoidable
 * here to keep the core code statically safe.
 */
function parseMcpYaml(rawText) {
    const doc = yaml_1.default.parseDocument(rawText, { prettyErrors: true });
    if (doc.errors.length > 0) {
        const errText = doc.errors.map((e) => String(e)).join('\n');
        return { ok: false, errorText: errText, rawText };
    }
    const parsed = doc.toJS();
    try {
        const res = parseWorkspaceConfig(parsed);
        return {
            ok: true,
            config: res.config,
            invalidServers: res.invalidServers,
            serverIdsInYamlOrder: res.serverIdsInYamlOrder,
            validServerIdsInYamlOrder: res.validServerIdsInYamlOrder,
            disabledServerIdsInYamlOrder: res.disabledServerIdsInYamlOrder,
            rawText,
        };
    }
    catch (err) {
        return { ok: false, errorText: err instanceof Error ? err.message : String(err), rawText };
    }
}
function parseWorkspaceConfig(value) {
    const root = asRecord(value, 'mcp.yaml root');
    const version = root.version;
    if (version !== 1) {
        throw new Error(`Invalid mcp.yaml: expected version: 1`);
    }
    const serversVal = root.servers;
    const serversRecord = serversVal === undefined ? {} : asRecord(serversVal, 'servers');
    const servers = {};
    const invalidServers = [];
    const serverIdsInYamlOrder = [];
    const validServerIdsInYamlOrder = [];
    const disabledServerIdsInYamlOrder = [];
    for (const [serverId, serverRaw] of Object.entries(serversRecord)) {
        serverIdsInYamlOrder.push(serverId);
        try {
            if (isDisabledServerConfig(serverId, serverRaw)) {
                disabledServerIdsInYamlOrder.push(serverId);
                continue;
            }
            servers[serverId] = parseServerConfig(serverId, serverRaw);
            validServerIdsInYamlOrder.push(serverId);
        }
        catch (err) {
            invalidServers.push({
                serverId,
                errorText: err instanceof Error ? err.message : String(err),
            });
        }
    }
    return {
        config: { version: 1, servers },
        invalidServers,
        serverIdsInYamlOrder,
        validServerIdsInYamlOrder,
        disabledServerIdsInYamlOrder,
    };
}
function isDisabledServerConfig(serverId, value) {
    const obj = asRecord(value, `servers.${serverId}`);
    const enabled = obj.enabled;
    if (enabled === undefined || enabled === true)
        return false;
    if (enabled === false)
        return true;
    throw new Error(`Invalid mcp.yaml: servers.${serverId}.enabled must be a boolean`);
}
function parseServerConfig(serverId, value) {
    const obj = asRecord(value, `servers.${serverId}`);
    const truelyStatelessVal = obj['truely-stateless'];
    const truelyStateless = truelyStatelessVal === undefined
        ? false
        : typeof truelyStatelessVal === 'boolean'
            ? truelyStatelessVal
            : (() => {
                throw new Error(`Invalid mcp.yaml: servers.${serverId}.truely-stateless must be a boolean`);
            })();
    const transport = obj.transport;
    if (transport !== 'stdio' && transport !== 'streamable_http') {
        throw new Error(`Invalid mcp.yaml: servers.${serverId}.transport must be 'stdio' or 'streamable_http'`);
    }
    const toolsVal = obj.tools;
    const toolsObj = toolsVal === undefined ? {} : asRecord(toolsVal, `servers.${serverId}.tools`);
    const whitelist = parseStringArrayOptional(toolsObj.whitelist, `servers.${serverId}.tools.whitelist`);
    const blacklist = parseStringArrayOptional(toolsObj.blacklist, `servers.${serverId}.tools.blacklist`);
    const toolTransformVal = toolsObj.transform;
    const toolTransform = parseScopedTransformConfig(serverId, 'tools', toolTransformVal);
    const transformVal = obj.transform;
    const transform = transformVal === undefined
        ? []
        : parseTransformArray(transformVal, `servers.${serverId}.transform`);
    const manualVal = obj.manual;
    const manual = manualVal === undefined ? undefined : parseManualConfigLenient(manualVal);
    const prompts = parsePromptConfig(serverId, obj.prompts);
    const resources = parseResourceConfig(serverId, obj.resources);
    if (transport === 'stdio') {
        const command = obj.command;
        if (typeof command !== 'string' || !command.trim()) {
            throw new Error(`Invalid mcp.yaml: servers.${serverId}.command must be a non-empty string`);
        }
        const argsVal = obj.args;
        const args = argsVal === undefined
            ? []
            : Array.isArray(argsVal) && argsVal.every((a) => typeof a === 'string')
                ? argsVal
                : (() => {
                    throw new Error(`Invalid mcp.yaml: servers.${serverId}.args must be string[]`);
                })();
        const cwdVal = obj.cwd;
        const cwd = cwdVal === undefined
            ? undefined
            : typeof cwdVal === 'string' && cwdVal.trim().length > 0
                ? cwdVal.trim()
                : (() => {
                    throw new Error(`Invalid mcp.yaml: servers.${serverId}.cwd must be a non-empty string`);
                })();
        const envVal = obj.env;
        const envRecord = envVal === undefined ? {} : asRecord(envVal, `servers.${serverId}.env`);
        const env = {};
        for (const [k, v] of Object.entries(envRecord)) {
            if (typeof v === 'string') {
                env[k] = { kind: 'literal', value: v };
                continue;
            }
            const mapped = asRecord(v, `servers.${serverId}.env.${k}`);
            const fromEnv = mapped.env;
            if (typeof fromEnv !== 'string' || !fromEnv.trim()) {
                throw new Error(`Invalid mcp.yaml: servers.${serverId}.env.${k} must be a string or { env: 'NAME' }`);
            }
            env[k] = { kind: 'from_env', env: fromEnv };
        }
        return {
            serverId,
            truelyStateless,
            transport: 'stdio',
            command,
            args,
            cwd,
            env,
            tools: { whitelist, blacklist, transform: toolTransform },
            transform,
            manual,
            prompts,
            resources,
        };
    }
    const url = obj.url;
    if (typeof url !== 'string' || !url.trim()) {
        throw new Error(`Invalid mcp.yaml: servers.${serverId}.url must be a non-empty string`);
    }
    const parsedUrl = safeParseUrl(url);
    if (!parsedUrl) {
        throw new Error(`Invalid mcp.yaml: servers.${serverId}.url must be a valid http(s) URL`);
    }
    const headersVal = obj.headers;
    const headersRecord = headersVal === undefined ? {} : asRecord(headersVal, `servers.${serverId}.headers`);
    const headers = {};
    for (const [k, v] of Object.entries(headersRecord)) {
        if (typeof v === 'string') {
            headers[k] = { kind: 'literal', value: v };
            continue;
        }
        const mapped = asRecord(v, `servers.${serverId}.headers.${k}`);
        const fromEnv = mapped.env;
        if (typeof fromEnv !== 'string' || !fromEnv.trim()) {
            throw new Error(`Invalid mcp.yaml: servers.${serverId}.headers.${k} must be a string, { env: 'NAME' }, or { prefix: 'Bearer ', env: 'NAME' }`);
        }
        const prefixVal = mapped.prefix;
        if (prefixVal !== undefined && typeof prefixVal !== 'string') {
            throw new Error(`Invalid mcp.yaml: servers.${serverId}.headers.${k}.prefix must be a string`);
        }
        const prefix = prefixVal ?? '';
        headers[k] = { kind: 'from_env', env: fromEnv, prefix };
    }
    const sessionIdVal = obj.sessionId;
    const sessionId = sessionIdVal === undefined
        ? undefined
        : typeof sessionIdVal === 'string' && sessionIdVal.trim()
            ? sessionIdVal
            : (() => {
                throw new Error(`Invalid mcp.yaml: servers.${serverId}.sessionId must be a string`);
            })();
    return {
        serverId,
        truelyStateless,
        transport: 'streamable_http',
        url: parsedUrl.toString(),
        headers,
        sessionId,
        tools: { whitelist, blacklist, transform: toolTransform },
        transform,
        manual,
        prompts,
        resources,
    };
}
function parsePromptConfig(serverId, value) {
    const obj = value === undefined ? {} : asRecord(value, `servers.${serverId}.prompts`);
    const whitelist = parseStringArrayOptional(obj.whitelist, `servers.${serverId}.prompts.whitelist`);
    const blacklist = parseStringArrayOptional(obj.blacklist, `servers.${serverId}.prompts.blacklist`);
    const transformVal = obj.transform;
    const transform = parseScopedTransformConfig(serverId, 'prompts', transformVal);
    return { whitelist, blacklist, transform };
}
function parseResourceConfig(serverId, value) {
    const obj = value === undefined ? {} : asRecord(value, `servers.${serverId}.resources`);
    const whitelist = parseStringArrayOptional(obj.whitelist, `servers.${serverId}.resources.whitelist`);
    const blacklist = parseStringArrayOptional(obj.blacklist, `servers.${serverId}.resources.blacklist`);
    const transformVal = obj.transform;
    const transform = parseScopedTransformConfig(serverId, 'resources', transformVal);
    const mimeTypes = parseStringArrayOptional(obj.mimeTypes, `servers.${serverId}.resources.mimeTypes`);
    const maxBytesVal = obj.maxBytes;
    const maxBytes = maxBytesVal === undefined
        ? undefined
        : typeof maxBytesVal === 'number' && Number.isInteger(maxBytesVal) && maxBytesVal > 0
            ? maxBytesVal
            : (() => {
                throw new Error(`Invalid mcp.yaml: servers.${serverId}.resources.maxBytes must be a positive integer`);
            })();
    const skillsVal = obj.skills;
    const skillsObj = skillsVal === undefined ? {} : asRecord(skillsVal, `servers.${serverId}.resources.skills`);
    const enabledVal = skillsObj.enabled;
    const enabled = enabledVal === undefined
        ? false
        : typeof enabledVal === 'boolean'
            ? enabledVal
            : (() => {
                throw new Error(`Invalid mcp.yaml: servers.${serverId}.resources.skills.enabled must be a boolean`);
            })();
    const skillWhitelist = parseStringArrayOptional(skillsObj.whitelist, `servers.${serverId}.resources.skills.whitelist`);
    const skillBlacklist = parseStringArrayOptional(skillsObj.blacklist, `servers.${serverId}.resources.skills.blacklist`);
    const skillTransformVal = skillsObj.transform;
    const skillTransform = parseScopedTransformConfig(serverId, 'resources.skills', skillTransformVal);
    return {
        whitelist,
        blacklist,
        transform,
        mimeTypes,
        maxBytes,
        skills: {
            enabled,
            whitelist: skillWhitelist,
            blacklist: skillBlacklist,
            transform: skillTransform,
        },
    };
}
function parseTransformArrayForPath(value, serverId, configPath) {
    return parseTransformArray(value, `servers.${serverId}.${configPath}.transform`);
}
function parseScopedTransformConfig(serverId, configPath, value) {
    if (value === undefined)
        return { kind: 'inherit' };
    return { kind: 'override', transform: parseTransformArrayForPath(value, serverId, configPath) };
}
function parseManualConfigLenient(value) {
    if (typeof value === 'string') {
        const content = value.trim();
        if (content === '')
            return undefined;
        return { content, sections: [] };
    }
    if (!isRecord(value))
        return undefined;
    const m = value;
    const contentVal = m.content;
    const content = typeof contentVal === 'string' && contentVal.trim().length > 0 ? contentVal.trim() : undefined;
    const contentFileVal = m.contentFile;
    const contentFile = typeof contentFileVal === 'string' && contentFileVal.trim().length > 0
        ? contentFileVal.trim()
        : undefined;
    const sectionsVal = m.sections;
    const sections = [];
    if (sectionsVal !== undefined) {
        if (Array.isArray(sectionsVal)) {
            for (let i = 0; i < sectionsVal.length; i++) {
                const sectionVal = sectionsVal[i];
                if (!isRecord(sectionVal))
                    return undefined;
                const section = sectionVal;
                const title = section.title;
                const sectionContent = section.content;
                if (typeof title !== 'string' || title.trim() === '')
                    return undefined;
                if (typeof sectionContent !== 'string' || sectionContent.trim() === '')
                    return undefined;
                sections.push({ title: title.trim(), content: sectionContent.trim() });
            }
        }
        else {
            if (!isRecord(sectionsVal))
                return undefined;
            const sectionsObj = sectionsVal;
            for (const [title, sectionContent] of Object.entries(sectionsObj)) {
                if (title.trim() === '' ||
                    typeof sectionContent !== 'string' ||
                    sectionContent.trim() === '') {
                    return undefined;
                }
                sections.push({ title: title.trim(), content: sectionContent.trim() });
            }
        }
    }
    if (content === undefined && contentFile === undefined && sections.length === 0)
        return undefined;
    return {
        ...(content !== undefined ? { content } : {}),
        ...(contentFile !== undefined ? { contentFile } : {}),
        sections,
    };
}
function parseTransformArray(value, pathLabel) {
    if (!Array.isArray(value)) {
        throw new Error(`Invalid mcp.yaml: ${pathLabel} must be an array`);
    }
    const out = [];
    for (let i = 0; i < value.length; i++) {
        const itemPath = `${pathLabel}[${i}]`;
        const item = asRecord(value[i], itemPath);
        if ('prefix' in item) {
            const p = item.prefix;
            if (typeof p === 'string') {
                out.push({ kind: 'prefix_add', add: p });
                continue;
            }
            const po = asRecord(p, `${itemPath}.prefix`);
            const remove = po.remove;
            const add = po.add;
            if (typeof remove !== 'string' || typeof add !== 'string') {
                throw new Error(`Invalid mcp.yaml: ${itemPath}.prefix must be string or { remove, add }`);
            }
            out.push({ kind: 'prefix_replace', remove, add });
            continue;
        }
        if ('suffix' in item) {
            const s = item.suffix;
            if (typeof s !== 'string') {
                throw new Error(`Invalid mcp.yaml: ${itemPath}.suffix must be a string`);
            }
            out.push({ kind: 'suffix_add', add: s });
            continue;
        }
        throw new Error(`Invalid mcp.yaml: ${itemPath} must contain 'prefix' or 'suffix' transform entry`);
    }
    return out;
}
function parseStringArrayOptional(value, pathLabel) {
    if (value === undefined)
        return [];
    if (!Array.isArray(value) || !value.every((v) => typeof v === 'string')) {
        throw new Error(`Invalid mcp.yaml: ${pathLabel} must be string[]`);
    }
    return value.filter((s) => s.trim().length > 0);
}
function asRecord(value, label) {
    if (!isRecord(value) || Array.isArray(value)) {
        throw new Error(`Invalid mcp.yaml: expected object at ${label}`);
    }
    return value;
}
function isRecord(value) {
    return typeof value === 'object' && value !== null;
}
function safeParseUrl(value) {
    try {
        const u = new URL(value);
        if (u.protocol !== 'http:' && u.protocol !== 'https:')
            return undefined;
        return u;
    }
    catch {
        return undefined;
    }
}
