/**
 * OpenAPI 3.1 Specification Generator
 * Generates OpenAPI documentation from NeuroLink server routes
 */
import { OpenAPISchemas } from "./schemas.js";
import { CommonParameters, createDeleteOperation, createGetOperation, createPostOperation, createStreamingPostOperation, DefaultServers, NeuroLinkApiInfo, StandardErrorResponses, StandardTags, BearerSecurityScheme, ApiKeySecurityScheme, } from "./templates.js";
// ============================================
// OpenAPI Generator Class
// ============================================
/**
 * OpenAPI specification generator
 * Generates OpenAPI 3.1 compliant documentation from route definitions
 */
export class OpenAPIGenerator {
    config;
    routes = [];
    constructor(config = {}) {
        this.config = {
            includeSecurity: true,
            basePath: "/api",
            ...config,
        };
        // Initialize routes from config if provided
        if (config.routes) {
            this.routes = [...config.routes];
        }
    }
    /**
     * Add routes to the generator
     */
    addRoutes(routes) {
        this.routes.push(...routes);
    }
    /**
     * Add a single route
     */
    addRoute(route) {
        this.routes.push(route);
    }
    /**
     * Generate the OpenAPI specification
     */
    generate() {
        const spec = {
            openapi: "3.1.0",
            info: this.generateInfo(),
            servers: this.generateServers(),
            tags: this.generateTags(),
            paths: this.generatePaths(),
            components: this.generateComponents(),
        };
        if (this.config.includeSecurity) {
            spec.security = [{ bearerAuth: [] }, { apiKeyAuth: [] }];
        }
        return spec;
    }
    /**
     * Generate OpenAPI spec as JSON string
     */
    toJSON(pretty = true) {
        const spec = this.generate();
        return pretty ? JSON.stringify(spec, null, 2) : JSON.stringify(spec);
    }
    /**
     * Generate OpenAPI spec as YAML string
     * Note: For full YAML support, use a YAML library
     */
    toYAML() {
        const spec = this.generate();
        return this.jsonToYaml(spec);
    }
    // ============================================
    // Private Methods
    // ============================================
    generateInfo() {
        const baseInfo = { ...NeuroLinkApiInfo };
        if (this.config.info?.title) {
            baseInfo.title = this.config.info.title;
        }
        if (this.config.info?.version) {
            baseInfo.version = this.config.info.version;
        }
        if (this.config.info?.description) {
            baseInfo.description = this.config.info.description;
        }
        return baseInfo;
    }
    generateServers() {
        if (this.config.servers && this.config.servers.length > 0) {
            return this.config.servers.map((server) => ({
                url: server.url,
                description: server.description ?? "Server",
            }));
        }
        return DefaultServers;
    }
    generateTags() {
        const tags = [...StandardTags];
        if (this.config.additionalTags) {
            tags.push(...this.config.additionalTags.map((tag) => ({
                name: tag.name,
                description: tag.description,
            })));
        }
        return tags;
    }
    generatePaths() {
        const paths = {};
        // Generate paths from registered routes
        for (const route of this.routes) {
            const openApiPath = this.convertPath(route.path);
            if (!paths[openApiPath]) {
                paths[openApiPath] = {};
            }
            const method = route.method.toLowerCase();
            paths[openApiPath][method] = this.generateOperation(route);
        }
        // Add default API paths if no routes registered
        if (Object.keys(paths).length === 0) {
            return this.generateDefaultPaths();
        }
        return paths;
    }
    generateDefaultPaths() {
        const basePath = this.config.basePath ?? "/api";
        return {
            // Agent endpoints
            [`${basePath}/agent/execute`]: {
                post: createPostOperation("Execute agent", "Execute an AI agent with the specified input and configuration", ["agent"], "AgentExecuteRequest", "AgentExecuteResponse"),
            },
            [`${basePath}/agent/stream`]: {
                post: createStreamingPostOperation("Stream agent response", "Execute an AI agent and stream the response in real-time", ["agent", "streaming"], "AgentExecuteRequest"),
            },
            [`${basePath}/agent/providers`]: {
                get: createGetOperation("List providers", "Get list of available AI providers", ["agent"], "ProviderInfo"),
            },
            // Tool endpoints
            [`${basePath}/tools`]: {
                get: createGetOperation("List tools", "Get list of all available tools", ["tools"], "ToolListResponse", [CommonParameters.limitQuery, CommonParameters.searchQuery]),
            },
            [`${basePath}/tools/{name}`]: {
                get: createGetOperation("Get tool", "Get details of a specific tool", ["tools"], "ToolDefinition", [CommonParameters.toolName]),
            },
            [`${basePath}/tools/{name}/execute`]: {
                post: createPostOperation("Execute tool", "Execute a specific tool with provided arguments", ["tools"], "ToolExecuteRequest", "ToolExecuteResponse", [CommonParameters.toolName]),
            },
            // MCP endpoints
            [`${basePath}/mcp/servers`]: {
                get: createGetOperation("List MCP servers", "Get list of all configured MCP servers", ["mcp"], "MCPServersListResponse"),
            },
            [`${basePath}/mcp/servers/{name}`]: {
                get: createGetOperation("Get MCP server", "Get details of a specific MCP server", ["mcp"], "MCPServerStatus", [CommonParameters.serverName]),
            },
            [`${basePath}/mcp/servers/{name}/tools`]: {
                get: createGetOperation("List MCP server tools", "Get tools available from a specific MCP server", ["mcp", "tools"], "ToolListResponse", [CommonParameters.serverName]),
            },
            // Memory endpoints
            [`${basePath}/memory/sessions`]: {
                get: createGetOperation("List sessions", "Get list of all conversation sessions", ["memory"], "SessionsListResponse", [CommonParameters.limitQuery, CommonParameters.offsetQuery]),
            },
            [`${basePath}/memory/sessions/{sessionId}`]: {
                get: createGetOperation("Get session", "Get a specific conversation session", ["memory"], "Session", [CommonParameters.sessionId]),
                delete: createDeleteOperation("Delete session", "Delete a conversation session", ["memory"], [CommonParameters.sessionId]),
            },
            // Health endpoints
            [`${basePath}/health`]: {
                get: createGetOperation("Health check", "Check server health status", ["health"], "HealthResponse"),
            },
            [`${basePath}/ready`]: {
                get: createGetOperation("Readiness check", "Check if server is ready to accept requests", ["health"], "ReadyResponse"),
            },
            [`${basePath}/metrics`]: {
                get: createGetOperation("Get metrics", "Get server metrics and statistics", ["health"], "MetricsResponse"),
            },
        };
    }
    generateOperation(route) {
        const operation = {
            summary: route.description ?? `${route.method} ${route.path}`,
            tags: route.tags ?? ["general"],
        };
        // Only include description if defined
        if (route.description) {
            operation.description = route.description;
        }
        // Add parameters from path
        const pathParams = this.extractPathParameters(route.path);
        if (pathParams.length > 0) {
            operation.parameters = pathParams;
        }
        // Add request body for POST/PUT/PATCH
        if (["POST", "PUT", "PATCH"].includes(route.method)) {
            if (route.requestSchema) {
                operation.requestBody = {
                    required: true,
                    content: {
                        "application/json": {
                            schema: route.requestSchema,
                        },
                    },
                };
            }
        }
        // Add responses
        if (route.streaming?.enabled) {
            operation.responses = {
                "200": {
                    description: "Streaming response",
                    content: {
                        "text/event-stream": {
                            schema: {
                                type: "string",
                            },
                        },
                    },
                },
                ...StandardErrorResponses,
            };
        }
        else {
            operation.responses = {
                "200": route.responseSchema
                    ? {
                        description: "Success",
                        content: {
                            "application/json": {
                                schema: route.responseSchema,
                            },
                        },
                    }
                    : {
                        description: "Success",
                        content: {
                            "application/json": {
                                schema: {
                                    type: "object",
                                },
                            },
                        },
                    },
                ...StandardErrorResponses,
            };
        }
        // Add security if route requires auth
        if (route.auth) {
            operation.security = [{ bearerAuth: [] }, { apiKeyAuth: [] }];
        }
        return operation;
    }
    generateComponents() {
        const components = {
            schemas: {
                ...OpenAPISchemas,
                ...this.config.customSchemas,
            },
        };
        if (this.config.includeSecurity) {
            components.securitySchemes = {
                bearerAuth: BearerSecurityScheme,
                apiKeyAuth: ApiKeySecurityScheme,
            };
        }
        // Add common parameters
        components.parameters = {
            sessionId: CommonParameters.sessionId,
            serverName: CommonParameters.serverName,
            toolName: CommonParameters.toolName,
            limit: CommonParameters.limitQuery,
            offset: CommonParameters.offsetQuery,
            search: CommonParameters.searchQuery,
        };
        return components;
    }
    convertPath(path) {
        // Convert Express-style :param to OpenAPI {param}
        return path.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, "{$1}");
    }
    extractPathParameters(path) {
        const params = [];
        const paramRegex = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
        let match;
        while ((match = paramRegex.exec(path)) !== null) {
            params.push({
                name: match[1],
                in: "path",
                required: true,
                schema: {
                    type: "string",
                },
            });
        }
        return params;
    }
    /**
     * Simple JSON to YAML converter
     * For production use, consider using a proper YAML library
     */
    jsonToYaml(obj, indent = 0) {
        const spaces = "  ".repeat(indent);
        if (obj === null || obj === undefined) {
            return "null";
        }
        if (typeof obj === "boolean" || typeof obj === "number") {
            return String(obj);
        }
        if (typeof obj === "string") {
            // Check if string needs quotes
            if (obj.includes("\n") ||
                obj.includes(":") ||
                obj.includes("#") ||
                obj.includes("'") ||
                obj.includes('"') ||
                obj.startsWith(" ") ||
                obj.endsWith(" ")) {
                // Use block scalar for multiline
                if (obj.includes("\n")) {
                    const lines = obj.split("\n");
                    return `|\n${lines.map((line) => spaces + "  " + line).join("\n")}`;
                }
                return `"${obj.replace(/"/g, '\\"')}"`;
            }
            return obj;
        }
        if (Array.isArray(obj)) {
            if (obj.length === 0) {
                return "[]";
            }
            return obj
                .map((item) => {
                const itemYaml = this.jsonToYaml(item, indent + 1);
                if (typeof item === "object" && item !== null) {
                    return `${spaces}- ${itemYaml
                        .trim()
                        .replace(/^\s+/gm, (match) => spaces + "  " + match.trim() + "\n")
                        .trim()}`;
                }
                return `${spaces}- ${itemYaml}`;
            })
                .join("\n");
        }
        if (typeof obj === "object") {
            const entries = Object.entries(obj);
            if (entries.length === 0) {
                return "{}";
            }
            return entries
                .map(([key, value]) => {
                const valueYaml = this.jsonToYaml(value, indent + 1);
                if (typeof value === "object" &&
                    value !== null &&
                    !Array.isArray(value)) {
                    return `${spaces}${key}:\n${valueYaml}`;
                }
                if (Array.isArray(value)) {
                    return `${spaces}${key}:\n${valueYaml}`;
                }
                return `${spaces}${key}: ${valueYaml}`;
            })
                .join("\n");
        }
        return String(obj);
    }
}
// ============================================
// Factory Functions
// ============================================
/**
 * Create an OpenAPI generator with default configuration
 */
export function createOpenAPIGenerator(config) {
    return new OpenAPIGenerator(config);
}
/**
 * Generate OpenAPI spec from routes
 */
export function generateOpenAPISpec(routes, config) {
    const generator = new OpenAPIGenerator(config);
    generator.addRoutes(routes);
    return generator.generate();
}
/**
 * Generate OpenAPI spec from server adapter configuration
 */
export function generateOpenAPIFromConfig(serverConfig, routes) {
    const generator = new OpenAPIGenerator({
        basePath: serverConfig.basePath ?? "/api",
        servers: [
            {
                url: `http://${serverConfig.host ?? "localhost"}:${serverConfig.port ?? 3000}`,
                description: "Configured server",
            },
        ],
    });
    if (routes) {
        generator.addRoutes(routes);
    }
    return generator.generate();
}
