/**
 * Hono Server Adapter
 * Primary server adapter implementation using Hono framework
 * Hono is chosen for its performance, TypeScript-first design, and edge compatibility
 */
import { Hono } from "hono";
import { cors } from "hono/cors";
import { HTTPException } from "hono/http-exception";
import { logger as honoLogger } from "hono/logger";
import { secureHeaders } from "hono/secure-headers";
import { streamSSE } from "hono/streaming";
import { timeout } from "hono/timeout";
import { logger } from "../../utils/logger.js";
import { AlreadyRunningError, ServerStopError, wrapError } from "../errors.js";
import { BaseServerAdapter } from "../abstract/baseServerAdapter.js";
import { isErrorResponse } from "../utils/validation.js";
/**
 * Hono-specific server adapter
 * Supports multiple runtimes: Bun, Deno, Node.js
 */
export class HonoServerAdapter extends BaseServerAdapter {
    app;
    server;
    rateLimitStore = new Map();
    rateLimitCleanupInterval;
    // Store context by request ID for sharing between middleware and route handlers
    requestContextStore = new Map();
    constructor(neurolink, config = {}) {
        super(neurolink, config);
    }
    /**
     * Create rate limiter middleware for Hono
     */
    createRateLimiter() {
        return async (c, next) => {
            // Skip rate limiting for excluded paths
            if (this.config.rateLimit.skipPaths) {
                for (const skipPath of this.config.rateLimit.skipPaths) {
                    if (c.req.path.startsWith(skipPath)) {
                        return next();
                    }
                }
            }
            // Use custom keyGenerator if provided, otherwise default to IP-based key
            let key;
            if (this.config.rateLimit.keyGenerator) {
                // Build minimal context for keyGenerator
                const headers = {};
                c.req.raw.headers.forEach((value, name) => {
                    headers[name] = value;
                });
                const minCtx = {
                    requestId: c.req.header("X-Request-ID") || this.generateRequestId(),
                    method: c.req.method,
                    path: c.req.path,
                    headers,
                    query: this.extractQuery(c),
                    params: c.req.param(),
                    neurolink: this.neurolink,
                    toolRegistry: this.toolRegistry,
                    externalServerManager: this.externalServerManager,
                    timestamp: Date.now(),
                    metadata: {},
                };
                key = this.config.rateLimit.keyGenerator(minCtx);
            }
            else {
                key =
                    c.req.header("x-forwarded-for") ||
                        c.req.header("x-real-ip") ||
                        "unknown";
            }
            const now = Date.now();
            const windowMs = this.config.rateLimit.windowMs;
            const maxRequests = this.config.rateLimit.maxRequests;
            let record = this.rateLimitStore.get(key);
            if (!record || now > record.resetAt) {
                record = { count: 0, resetAt: now + windowMs };
                this.rateLimitStore.set(key, record);
            }
            record.count++;
            // Set rate limit headers
            c.header("X-RateLimit-Limit", String(maxRequests));
            c.header("X-RateLimit-Remaining", String(Math.max(0, maxRequests - record.count)));
            c.header("X-RateLimit-Reset", String(Math.ceil(record.resetAt / 1000)));
            if (record.count > maxRequests) {
                const retryAfter = Math.ceil((record.resetAt - now) / 1000);
                c.header("Retry-After", String(retryAfter));
                return c.json({
                    error: {
                        code: "RATE_LIMIT_EXCEEDED",
                        message: this.config.rateLimit.message,
                    },
                    metadata: {
                        timestamp: new Date().toISOString(),
                        retryAfter,
                    },
                }, 429);
            }
            return next();
        };
    }
    /**
     * Periodically clean up expired rate limit entries
     */
    cleanupRateLimitStore() {
        const now = Date.now();
        for (const [key, entry] of this.rateLimitStore.entries()) {
            if (now > entry.resetAt) {
                this.rateLimitStore.delete(key);
            }
        }
    }
    /**
     * Initialize Hono framework
     */
    initializeFramework() {
        this.app = new Hono();
        // Add secure headers
        this.app.use("*", secureHeaders());
        // Add CORS if enabled
        if (this.config.cors.enabled) {
            this.app.use("*", cors({
                origin: this.config.cors.origins,
                allowMethods: this.config.cors.methods,
                allowHeaders: this.config.cors.headers,
                credentials: this.config.cors.credentials,
                maxAge: this.config.cors.maxAge,
            }));
        }
        // Add timeout middleware
        this.app.use("*", timeout(this.config.timeout));
        // Add logging if enabled
        if (this.config.logging.enabled) {
            this.app.use("*", honoLogger());
        }
        // Add rate limiting if enabled
        if (this.config.rateLimit.enabled) {
            this.app.use("*", this.createRateLimiter());
            // Schedule periodic cleanup of expired rate limit entries (every minute)
            this.rateLimitCleanupInterval = setInterval(() => this.cleanupRateLimitStore(), 60000);
        }
        // Global error handler
        this.app.onError((error, c) => {
            const requestId = c.req.header("X-Request-ID") || this.generateRequestId();
            logger.error("[HonoAdapter] Request error", {
                requestId,
                error: error.message,
                stack: error.stack,
            });
            this.emit("error", {
                requestId,
                error,
                timestamp: new Date(),
            });
            if (error instanceof HTTPException) {
                return c.json({
                    error: {
                        code: `HTTP_${error.status}`,
                        message: error.message,
                    },
                    metadata: {
                        requestId,
                        timestamp: new Date().toISOString(),
                    },
                }, error.status);
            }
            return c.json({
                error: {
                    code: "INTERNAL_ERROR",
                    message: "An internal error occurred",
                },
                metadata: {
                    requestId,
                    timestamp: new Date().toISOString(),
                },
            }, 500);
        });
        // 404 handler
        this.app.notFound((c) => {
            return c.json({
                error: {
                    code: "NOT_FOUND",
                    message: `Route ${c.req.method} ${c.req.path} not found`,
                },
            }, 404);
        });
    }
    /**
     * Register route with Hono
     */
    registerFrameworkRoute(route) {
        const method = route.method.toLowerCase();
        this.app[method](route.path, async (c) => {
            const requestId = c.req.header("X-Request-ID") || this.generateRequestId();
            const connectionId = `conn-${requestId}`;
            const startTime = Date.now();
            // Track connection for graceful shutdown
            this.trackConnection(connectionId, undefined, requestId);
            try {
                // Extract path parameters
                const params = c.req.param();
                // Reuse existing context from middleware if available, otherwise create new one
                const existingCtx = this.requestContextStore.get(requestId);
                const ctx = existingCtx ||
                    this.createContext({
                        requestId,
                        method: c.req.method,
                        path: c.req.path,
                        headers: this.extractHeaders(c),
                        query: this.extractQuery(c),
                        params,
                        body: await this.extractBody(c),
                    });
                // Emit request event
                this.emit("request", {
                    requestId,
                    method: ctx.method,
                    path: ctx.path,
                    timestamp: new Date(),
                });
                // Handle streaming if configured
                if (route.streaming?.enabled) {
                    return await this.handleStreamingResponse(c, ctx, route);
                }
                // Execute handler
                const result = await route.handler(ctx);
                const duration = Date.now() - startTime;
                // Check if result is an error response
                if (isErrorResponse(result)) {
                    const statusCode = result.httpStatus ?? 500;
                    // Emit response event with error status
                    this.emit("response", {
                        requestId,
                        statusCode,
                        duration,
                        timestamp: new Date(),
                    });
                    // Apply response headers from middleware
                    if (ctx.responseHeaders) {
                        for (const [key, value] of Object.entries(ctx.responseHeaders)) {
                            c.header(key, value);
                        }
                    }
                    // Return error response with proper status code
                    // Cast to ContentfulStatusCode since we know error codes are valid HTTP status codes
                    return c.json({
                        error: result.error,
                        metadata: {
                            ...result.metadata,
                            requestId,
                            timestamp: new Date().toISOString(),
                            duration,
                        },
                    }, statusCode);
                }
                // Emit response event
                this.emit("response", {
                    requestId,
                    statusCode: 200,
                    duration,
                    timestamp: new Date(),
                });
                // Apply response headers from middleware
                if (ctx.responseHeaders) {
                    for (const [key, value] of Object.entries(ctx.responseHeaders)) {
                        c.header(key, value);
                    }
                }
                // Return formatted response
                return c.json({
                    data: result,
                    metadata: {
                        requestId,
                        timestamp: new Date().toISOString(),
                        duration,
                    },
                });
            }
            catch (error) {
                const errorMessage = error instanceof Error ? error.message : String(error);
                logger.error("[HonoAdapter] Handler error", {
                    requestId,
                    route: route.path,
                    error: errorMessage,
                });
                throw new HTTPException(500, { message: errorMessage });
            }
            finally {
                // Untrack connection when request completes
                this.untrackConnection(connectionId);
                // Clean up context store to prevent memory leaks
                this.requestContextStore.delete(requestId);
            }
        });
    }
    /**
     * Handle streaming response using SSE
     */
    async handleStreamingResponse(c, ctx, route) {
        // Apply middleware response headers to streaming response before starting the stream
        if (ctx.responseHeaders) {
            for (const [key, value] of Object.entries(ctx.responseHeaders)) {
                c.header(key, value);
            }
        }
        return streamSSE(c, async (stream) => {
            try {
                // Get streaming result from handler
                const result = await route.handler(ctx);
                // If result is an async iterable, stream it
                if (result &&
                    typeof result === "object" &&
                    Symbol.asyncIterator in result) {
                    for await (const chunk of result) {
                        // Transform raw provider chunks into StreamEvent format
                        // The client SDK expects { type, content, timestamp, ... } objects
                        const streamEvent = this.toStreamEvent(chunk);
                        await stream.writeSSE({
                            data: JSON.stringify(streamEvent),
                        });
                    }
                }
                else if (result && isErrorResponse(result)) {
                    // Error result, send as error event
                    const errorResult = result;
                    const statusCode = errorResult.httpStatus ?? 500;
                    await stream.writeSSE({
                        data: JSON.stringify({
                            type: "error",
                            error: {
                                code: errorResult.error?.code ?? "STREAM_ERROR",
                                message: errorResult.error?.message ?? "Stream error",
                                status: statusCode,
                            },
                            timestamp: Date.now(),
                        }),
                    });
                }
                else {
                    // Single result, normalize into a StreamEvent so the client can
                    // match on the `type` field via handleEvent()
                    const streamEvent = this.toStreamEvent(result);
                    await stream.writeSSE({
                        data: JSON.stringify(streamEvent),
                    });
                }
                // Send [DONE] signal that the client SDK expects
                await stream.writeSSE({
                    data: "[DONE]",
                });
            }
            catch (error) {
                await stream.writeSSE({
                    data: JSON.stringify({
                        type: "error",
                        error: {
                            code: "STREAM_ERROR",
                            message: error instanceof Error ? error.message : "Stream error",
                            status: 500,
                        },
                        timestamp: Date.now(),
                    }),
                });
                // Still send [DONE] after error so the client terminates cleanly
                await stream.writeSSE({
                    data: "[DONE]",
                });
            }
        });
    }
    /**
     * Transform a raw provider chunk into a StreamEvent
     * Provider chunks are typically { content: string } objects;
     * the client SDK expects { type: "text", content: string, timestamp: number }
     */
    toStreamEvent(chunk) {
        // Already a StreamEvent (has a type field)
        if (chunk &&
            typeof chunk === "object" &&
            "type" in chunk &&
            typeof chunk.type === "string") {
            return {
                ...chunk,
                timestamp: chunk.timestamp ?? Date.now(),
            };
        }
        // Raw text chunk from provider: { content: string }
        if (chunk && typeof chunk === "object" && "content" in chunk) {
            return {
                type: "text",
                content: String(chunk.content),
                timestamp: Date.now(),
            };
        }
        // String chunk
        if (typeof chunk === "string") {
            return {
                type: "text",
                content: chunk,
                timestamp: Date.now(),
            };
        }
        // Unknown shape — wrap as metadata event (StreamEventType has no "data" variant)
        return {
            type: "metadata",
            metadata: chunk,
            timestamp: Date.now(),
        };
    }
    /**
     * Register middleware with Hono
     */
    registerFrameworkMiddleware(middleware) {
        const paths = middleware.paths || ["*"];
        for (const path of paths) {
            this.app.use(path, async (c, next) => {
                // Skip excluded paths
                if (middleware.excludePaths?.some((p) => c.req.path.startsWith(p))) {
                    return next();
                }
                // Extract path parameters
                const params = c.req.param();
                // Get or generate request ID for context lookup
                const requestId = c.req.header("X-Request-ID") || this.generateRequestId();
                // Reuse existing context from previous middleware if available
                let ctx = this.requestContextStore.get(requestId);
                if (!ctx) {
                    // Create new context for the first middleware
                    ctx = this.createContext({
                        requestId,
                        method: c.req.method,
                        path: c.req.path,
                        headers: this.extractHeaders(c),
                        query: this.extractQuery(c),
                        params,
                        body: await this.extractBody(c),
                    });
                    // Store context for sharing between middleware and route handlers
                    this.requestContextStore.set(requestId, ctx);
                }
                // Execute middleware
                return middleware.handler(ctx, next);
            });
        }
    }
    /**
     * Start the Hono server
     */
    async start() {
        // Validate lifecycle state
        this.validateLifecycleState("start", ["initialized", "stopped"]);
        if (this.isRunning) {
            throw new AlreadyRunningError(this.config.port, this.config.host);
        }
        this.lifecycleState = "starting";
        const { port, host } = this.config;
        try {
            // Check if running in Bun environment
            if (typeof Bun !== "undefined") {
                this.server = Bun.serve({
                    port,
                    hostname: host,
                    fetch: this.app.fetch,
                });
            }
            else if (typeof Deno !== "undefined") {
                // Deno runtime
                this.server = Deno.serve({
                    port,
                    hostname: host,
                }, this.app.fetch);
            }
            else {
                // Fallback to Node.js http module via @hono/node-server
                const { serve } = await import("@hono/node-server");
                this.server = serve({
                    fetch: this.app.fetch,
                    port,
                    hostname: host,
                });
            }
            this.isRunning = true;
            this.startTime = new Date();
            this.lifecycleState = "running";
            logger.info(`[HonoAdapter] Server started on ${host}:${port}`);
            this.emit("started", {
                port,
                host,
                timestamp: this.startTime,
            });
        }
        catch (error) {
            this.lifecycleState = "error";
            throw error;
        }
    }
    /**
     * Stop the Hono server with graceful shutdown
     */
    async stop() {
        if (!this.isRunning) {
            return; // Already stopped, return gracefully (idempotent)
        }
        const uptime = this.startTime ? Date.now() - this.startTime.getTime() : 0;
        try {
            // Use graceful shutdown from base class
            await this.gracefulShutdown();
            // Clean up rate limit cleanup interval
            if (this.rateLimitCleanupInterval) {
                clearInterval(this.rateLimitCleanupInterval);
                this.rateLimitCleanupInterval = undefined;
            }
            logger.info("[HonoAdapter] Server stopped", { uptime });
            this.emit("stopped", {
                uptime,
                timestamp: new Date(),
            });
            // Reset state for restart capability
            this.resetServerState();
            this.server = undefined;
            this.rateLimitStore.clear();
        }
        catch (error) {
            const wrappedError = wrapError(error);
            throw new ServerStopError(wrappedError.message, wrappedError);
        }
    }
    // ============================================
    // Lifecycle Methods (Framework-Specific)
    // ============================================
    /**
     * Stop accepting new connections
     */
    async stopAcceptingConnections() {
        // For Hono, this is handled by the underlying server
        logger.debug("[HonoAdapter] Stopping acceptance of new connections");
    }
    /**
     * Close the underlying server
     */
    async closeServer() {
        if (!this.server) {
            return;
        }
        // Handle different server types (Bun, Deno, Node.js)
        const serverObj = this.server;
        if (typeof serverObj.stop === "function") {
            // Bun server
            serverObj.stop();
        }
        else if (typeof serverObj.shutdown === "function") {
            // Deno server
            await serverObj.shutdown();
        }
        else if (typeof serverObj.close === "function") {
            // Node.js http server
            await new Promise((resolve, reject) => {
                serverObj.close?.((err) => {
                    if (err) {
                        reject(err);
                    }
                    else {
                        resolve();
                    }
                });
            });
        }
    }
    /**
     * Force close all active connections
     */
    async forceCloseConnections() {
        logger.info("[HonoAdapter] Force closing connections", {
            count: this.activeConnections.size,
        });
        if (this.server) {
            const serverObj = this.server;
            if (typeof serverObj.closeAllConnections === "function") {
                serverObj.closeAllConnections();
            }
        }
        this.activeConnections.clear();
    }
    /**
     * Get the Hono app instance
     */
    getFrameworkInstance() {
        return this.app;
    }
    // ============================================
    // Helper Methods
    // ============================================
    extractHeaders(c) {
        const headers = {};
        c.req.raw.headers.forEach((value, key) => {
            headers[key] = value;
        });
        return headers;
    }
    extractQuery(c) {
        const query = {};
        const url = new URL(c.req.url);
        url.searchParams.forEach((value, key) => {
            query[key] = value;
        });
        return query;
    }
    async extractBody(c) {
        if (!this.config.bodyParser.enabled) {
            return undefined;
        }
        const contentType = c.req.header("Content-Type") || "";
        if (contentType.includes("application/json")) {
            try {
                return await c.req.json();
            }
            catch {
                return undefined;
            }
        }
        if (contentType.includes("application/x-www-form-urlencoded")) {
            try {
                return await c.req.parseBody();
            }
            catch {
                return undefined;
            }
        }
        return undefined;
    }
}
