import { filterApiGroups } from '../core/request-scope.js';
/**
 * ExplorerService provides filesystem-like navigation of API groups.
 * Enables progressive discovery of APIs by allowing agents to explore
 * tool hierarchies on-demand rather than loading everything upfront.
 */
export class ExplorerService {
    root;
    apiGroups;
    apiGroupMap;
    constructor(apiGroups) {
        this.apiGroups = apiGroups;
        this.apiGroupMap = new Map(apiGroups.map((group) => [group.name, group]));
        this.root = { type: 'directory', name: '/', children: new Map() };
        this.buildTree(apiGroups);
    }
    /**
     * Get filtering context based on current request's tool rules.
     * Returns sets for allowed items and all known items (for filtering).
     */
    getFilterContext() {
        const allowedGroups = filterApiGroups(this.apiGroups);
        const context = {
            allowedTypes: new Set(),
            allowedGroups: new Set(),
            allowedTools: new Set(),
            allGroups: new Set(this.apiGroups.map((g) => g.name)),
        };
        for (const group of allowedGroups) {
            if (group.type !== 'graphql') {
                context.allowedTypes.add(group.type);
            }
            context.allowedGroups.add(group.name);
        }
        for (const group of this.apiGroups) {
            if (!context.allowedGroups.has(group.name))
                continue;
            if (group.functions) {
                for (const func of group.functions) {
                    context.allowedTools.add(`${group.name}:${func.name}`);
                }
            }
        }
        return context;
    }
    /**
     * Check if a directory should be visible based on filter context.
     */
    isDirectoryAllowed(name, currentPath, ctx) {
        const API_TYPES = ['openapi', 'mcp', 'custom', 'graphql'];
        if (currentPath === '/' && API_TYPES.includes(name)) {
            return ctx.allowedTypes.has(name) || ctx.allowedGroups.has(name);
        }
        if (ctx.allGroups.has(name)) {
            return ctx.allowedGroups.has(name);
        }
        return true;
    }
    /**
     * Builds the virtual filesystem tree from API groups
     */
    buildTree(apiGroups) {
        for (const group of apiGroups) {
            if (!group.functions || group.functions.length === 0)
                continue;
            let groupFolder;
            if (group.type === 'graphql') {
                groupFolder = this.ensureDirectory(this.root, group.name, group.description);
            }
            else {
                groupFolder = this.ensureDirectory(this.ensureDirectory(this.root, group.type), group.name, group.description);
            }
            groupFolder.apiGroupName = group.name;
            for (const func of group.functions) {
                const segments = this.extractSegments(func, group);
                if (segments.length > 1) {
                    let current = groupFolder;
                    for (let i = 0; i < segments.length - 1; i++) {
                        current = this.ensureDirectory(current, segments[i]);
                    }
                    this.addFunction(current, segments[segments.length - 1], func, group.name);
                }
                else {
                    this.addFunction(groupFolder, func.name, func, group.name);
                }
            }
        }
    }
    /**
     * Extract path segments for organizing functions
     */
    extractSegments(func, group) {
        if (group.type === 'graphql') {
            const name = func.name;
            if (name.startsWith('query_')) {
                return ['query', name.replace('query_', '')];
            }
            if (name.startsWith('mutation_')) {
                return ['mutation', name.replace('mutation_', '')];
            }
        }
        if (group.type === 'openapi') {
            const name = func.name;
            const camelSplit = name.match(/[A-Z]?[a-z]+|[A-Z]+(?=[A-Z][a-z]|\b)/g);
            if (camelSplit && camelSplit.length > 2) {
                const verb = camelSplit[0].toLowerCase();
                const isVerb = [
                    'get',
                    'list',
                    'create',
                    'update',
                    'delete',
                    'post',
                    'put',
                    'patch',
                ].includes(verb);
                if (isVerb) {
                    const resource = camelSplit.slice(1).join('_').toLowerCase();
                    return [resource, name];
                }
            }
            if (name.includes('_')) {
                const parts = name.split('_');
                if (parts.length > 1 && parts[0]) {
                    return [parts[0], name];
                }
            }
            if (name.includes('-')) {
                const parts = name.split('-');
                if (parts.length > 1 && parts[0]) {
                    return [parts[0], name];
                }
            }
        }
        return [func.name];
    }
    /**
     * Ensures a directory exists at the given path
     */
    ensureDirectory(parent, name, description) {
        if (!parent.children) {
            parent.children = new Map();
        }
        let child = parent.children.get(name);
        if (!child) {
            child = { type: 'directory', name, description, children: new Map() };
            parent.children.set(name, child);
        }
        else if (description && !child.description) {
            child.description = description;
        }
        return child;
    }
    /**
     * Adds a function to the tree
     */
    addFunction(parent, name, func, group) {
        if (!parent.children) {
            parent.children = new Map();
        }
        parent.children.set(name, {
            type: 'function',
            name,
            functionDef: { func, group },
        });
    }
    /**
     * Explores the filesystem at the given path.
     * Tool rules are automatically applied from the request scope.
     * @param path - The path to explore
     */
    explore(path) {
        const ctx = this.getFilterContext();
        const normalizedPath = this.normalizePath(path);
        const segments = normalizedPath === '/' ? [] : normalizedPath.split('/').filter((s) => s);
        let current = this.root;
        let currentPath = '/';
        for (const segment of segments) {
            if (!current.children || !current.children.has(segment)) {
                return null;
            }
            current = current.children.get(segment);
            currentPath = currentPath === '/' ? `/${segment}` : `${currentPath}/${segment}`;
        }
        if (current.type === 'directory') {
            const items = [];
            if (current.children) {
                for (const [name, node] of current.children) {
                    if (node.type === 'directory') {
                        if (!this.isDirectoryAllowed(name, currentPath, ctx))
                            continue;
                    }
                    else if (node.type === 'function' && node.functionDef) {
                        if (!ctx.allowedTools.has(`${node.functionDef.group}:${node.functionDef.func.name}`))
                            continue;
                    }
                    const item = {
                        name,
                        type: node.type,
                    };
                    if (node.description) {
                        item.description = node.description;
                    }
                    else if (node.type === 'function' && node.functionDef?.func.description) {
                        item.description = node.functionDef.func.description;
                    }
                    items.push(item);
                }
            }
            items.sort((a, b) => {
                if (a.type === b.type) {
                    return a.name.localeCompare(b.name);
                }
                return a.type === 'directory' ? -1 : 1;
            });
            const result = {
                type: 'directory',
                path: currentPath,
                items,
            };
            if (current.apiGroupName) {
                const apiGroup = this.apiGroupMap.get(current.apiGroupName);
                if (apiGroup?.documentation) {
                    result.documentation = apiGroup.documentation;
                }
            }
            return result;
        }
        else {
            if (!current.functionDef) {
                return null;
            }
            const { func, group } = current.functionDef;
            if (!ctx.allowedTools.has(`${group}:${func.name}`)) {
                return null;
            }
            const definition = this.generateFunctionDefinition(func, group);
            return {
                type: 'function',
                path: currentPath,
                name: func.name,
                description: func.description,
                definition,
                group,
                inputSchema: func.inputSchema,
                outputSchema: func.outputSchema,
            };
        }
    }
    /**
     * Normalizes a path (removes trailing slashes, ensures leading slash)
     */
    normalizePath(path) {
        if (!path || path === '')
            return '/';
        if (!path.startsWith('/')) {
            path = '/' + path;
        }
        if (path !== '/' && path.endsWith('/')) {
            path = path.slice(0, -1);
        }
        return path;
    }
    /**
     * Generates TypeScript function signature showing how to call the function.
     */
    generateFunctionDefinition(func, group) {
        const inputType = this.generateInputType(func.inputSchema);
        const groupPath = group.replace(/\//g, '.');
        const outputType = func.outputSchema ? this.generateOutputType(func.outputSchema) : 'unknown';
        return `async function api.${groupPath}.${func.name}(${inputType}): Promise<${outputType}>`;
    }
    /**
     * Generates TypeScript type from JSON schema
     */
    generateInputType(schema) {
        if (!schema || !schema.properties) {
            return '{}';
        }
        const props = [];
        const required = schema.required || [];
        for (const [key, value] of Object.entries(schema.properties)) {
            const isRequired = required.includes(key);
            const prop = value;
            const tsType = this.jsonSchemaTypeToTS(prop.type ?? 'any');
            const optional = isRequired ? '' : '?';
            props.push(`${key}${optional}: ${tsType}`);
        }
        return `{ ${props.join('; ')} }`;
    }
    /**
     * Generates output type from JSON schema
     */
    generateOutputType(schema) {
        if (!schema.properties) {
            return 'unknown';
        }
        const props = [];
        for (const [key, value] of Object.entries(schema.properties)) {
            const prop = value;
            const tsType = this.jsonSchemaTypeToTS(prop.type ?? 'any');
            props.push(`${key}: ${tsType}`);
        }
        return `{ ${props.join('; ')} }`;
    }
    /**
     * Converts JSON Schema type to TypeScript type
     */
    jsonSchemaTypeToTS(type) {
        switch (type) {
            case 'string':
                return 'string';
            case 'number':
            case 'integer':
                return 'number';
            case 'boolean':
                return 'boolean';
            case 'array':
                return 'any[]';
            case 'object':
                return 'Record<string, any>';
            default:
                return 'any';
        }
    }
}
//# sourceMappingURL=index.js.map