/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { loadMetadataIndex, queryDependencies } from '../services/metadata-index/index.js';
import { PLUGIN_VERSION } from '../version.js';
const METADATA_TYPES = [
    'ApexClass',
    'ApexTrigger',
    'LightningComponentBundle',
    'CustomObject',
    'CustomField',
    'Flow',
    'PermissionSet',
    'CustomLabel',
];
// ─── Index Loader ──────────────────────────────────────────────────────────
let cachedIndex = null;
let cwd = process.cwd();
function getIndex() {
    if (!cachedIndex) {
        cachedIndex = loadMetadataIndex(cwd);
    }
    return cachedIndex;
}
function requireIndex() {
    const index = getIndex();
    if (!index) {
        throw new Error('No metadata index found. Run `sf setup-agents index` first to generate .setup-agents/sf-metadata-index.json');
    }
    return index;
}
// ─── Tool Implementations ──────────────────────────────────────────────────
function queryMetadataImpl(apiName, direction) {
    const index = requireIndex();
    const result = queryDependencies(index, apiName);
    if (!result.entry) {
        return { found: false, apiName, message: `No entry found for "${apiName}"` };
    }
    const response = {
        found: true,
        entry: {
            apiName: result.entry.apiName,
            type: result.entry.type,
            path: result.entry.path,
            category: result.entry.category ?? null,
        },
    };
    if (direction === 'down' || direction === 'both') {
        response.dependencies = result.dependencies;
    }
    if (direction === 'up' || direction === 'both') {
        response.dependents = result.dependents;
    }
    return response;
}
function listMetadataImpl(type, filter, parent) {
    const index = requireIndex();
    let entries = index.entries;
    if (type) {
        entries = entries.filter((e) => e.type === type);
    }
    if (filter) {
        const lower = filter.toLowerCase();
        entries = entries.filter((e) => e.apiName.toLowerCase().includes(lower));
    }
    if (parent) {
        entries = entries.filter((e) => e.dependencies.includes(parent) || e.path.includes(parent));
    }
    return {
        count: entries.length,
        entries: entries.slice(0, 100).map((e) => ({
            apiName: e.apiName,
            type: e.type,
            path: e.path,
            category: e.category ?? null,
        })),
        truncated: entries.length > 100,
    };
}
function suggestCompletionsImpl(context, prefix) {
    const index = requireIndex();
    let candidates = [];
    switch (context) {
        case 'SOQL:FROM':
            candidates = index.entries.filter((e) => e.type === 'CustomObject');
            break;
        case 'SOQL:SELECT': {
            candidates = index.entries.filter((e) => e.type === 'CustomField');
            break;
        }
        case 'apex:type':
            candidates = index.entries.filter((e) => e.type === 'ApexClass');
            break;
        case 'apex:import':
            candidates = index.entries.filter((e) => e.type === 'ApexClass');
            break;
        case 'lwc:wire':
            candidates = index.entries.filter((e) => e.type === 'ApexClass' && (e.category === 'service' || e.category === 'utility'));
            break;
        case 'flow:action':
            candidates = index.entries.filter((e) => e.type === 'ApexClass' && e.category === 'invocable');
            break;
        case 'label':
            candidates = index.entries.filter((e) => e.type === 'CustomLabel');
            break;
        case 'permission:object':
            candidates = index.entries.filter((e) => e.type === 'CustomObject');
            break;
        default:
            candidates = index.entries;
    }
    if (prefix) {
        const lower = prefix.toLowerCase();
        candidates = candidates.filter((e) => e.apiName.toLowerCase().startsWith(lower));
    }
    return {
        context,
        count: candidates.length,
        suggestions: candidates.slice(0, 50).map((e) => ({
            apiName: e.apiName,
            type: e.type,
            category: e.category ?? null,
            path: e.path,
        })),
        truncated: candidates.length > 50,
    };
}
function dependencyGraphImpl(apiName, depth) {
    const index = requireIndex();
    const visited = new Set();
    const edges = [];
    function traverse(name, level, direction) {
        if (level > depth || visited.has(`${name}:${direction}:${level}`))
            return;
        visited.add(`${name}:${direction}:${level}`);
        const entry = index.entries.find((e) => e.apiName === name);
        if (!entry)
            return;
        const targets = direction === 'down' ? entry.dependencies : entry.dependents ?? [];
        for (const target of targets) {
            edges.push({
                from: direction === 'down' ? name : target,
                to: direction === 'down' ? target : name,
                level,
            });
            traverse(target, level + 1, direction);
        }
    }
    traverse(apiName, 1, 'down');
    traverse(apiName, 1, 'up');
    const rootEntry = index.entries.find((e) => e.apiName === apiName);
    return {
        root: apiName,
        rootType: rootEntry?.type ?? null,
        rootPath: rootEntry?.path ?? null,
        depth,
        edgeCount: edges.length,
        edges: edges.slice(0, 200),
        truncated: edges.length > 200,
    };
}
// ─── Server Setup ──────────────────────────────────────────────────────────
export function createServer(workingDir) {
    if (workingDir)
        cwd = workingDir;
    const server = new McpServer({
        name: 'setup-agents-metadata',
        version: PLUGIN_VERSION,
    });
    server.tool('query_metadata', 'Query a specific metadata entry by API name. Returns type, path, category, dependencies, and dependents.', {
        apiName: z.string().describe('The API name to look up (e.g., "AccountService", "Account__c")'),
        direction: z
            .enum(['up', 'down', 'both'])
            .default('both')
            .describe('Direction: "down" = dependencies, "up" = dependents, "both" = all'),
    }, ({ apiName, direction }) => {
        try {
            const result = queryMetadataImpl(apiName, direction);
            return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
        }
        catch (err) {
            return { content: [{ type: 'text', text: err.message }], isError: true };
        }
    });
    server.tool('list_metadata', 'List metadata entries with optional filtering by type, name pattern, or parent relationship.', {
        type: z
            .enum(METADATA_TYPES)
            .optional()
            .describe('Filter by metadata type'),
        filter: z.string().optional().describe('Filter by API name substring (case-insensitive)'),
        parent: z.string().optional().describe('Filter entries that depend on this API name'),
    }, ({ type, filter, parent }) => {
        try {
            const result = listMetadataImpl(type, filter, parent);
            return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
        }
        catch (err) {
            return { content: [{ type: 'text', text: err.message }], isError: true };
        }
    });
    server.tool('suggest_completions', 'Get contextual completion suggestions for code editing. Use when writing SOQL, Apex references, LWC imports, or Flow actions.', {
        context: z
            .enum([
            'SOQL:FROM',
            'SOQL:SELECT',
            'apex:type',
            'apex:import',
            'lwc:wire',
            'flow:action',
            'label',
            'permission:object',
        ])
            .describe('The editing context where completions are needed'),
        prefix: z.string().optional().describe('Type-ahead prefix to narrow results'),
    }, ({ context, prefix }) => {
        try {
            const result = suggestCompletionsImpl(context, prefix);
            return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
        }
        catch (err) {
            return { content: [{ type: 'text', text: err.message }], isError: true };
        }
    });
    server.tool('dependency_graph', 'Get the transitive dependency graph for a metadata entry. Shows what it depends on and what depends on it, up to a configurable depth.', {
        apiName: z.string().describe('The root API name to explore'),
        depth: z.number().min(1).max(5).default(2).describe('Maximum traversal depth (1-5)'),
    }, ({ apiName, depth }) => {
        try {
            const result = dependencyGraphImpl(apiName, depth);
            return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
        }
        catch (err) {
            return { content: [{ type: 'text', text: err.message }], isError: true };
        }
    });
    return server;
}
// ─── CLI Entry ─────────────────────────────────────────────────────────────
async function main() {
    const workingDir = process.argv[2] ?? process.cwd();
    const server = createServer(workingDir);
    const transport = new StdioServerTransport();
    await server.connect(transport);
}
main().catch((err) => {
    process.stderr.write(`MCP server error: ${err.message}\n`);
    process.exit(1);
});
//# sourceMappingURL=index.js.map