import * as http2 from 'http2';
import { extractErrorMsg } from '@ibgib/helper-gib/dist/helpers/utils-helper.mjs';
import { GLOBAL_LOG_A_LOT } from '../../../core-constants.mjs';
const logalot = GLOBAL_LOG_A_LOT;
/**
 * A basic Node HTTP adapter for the Symmetric Sync Protocol.
 *
 * Handles establishing the SSE EventSource stream (GET /sync/events)
 * and receiving the Uplink (POST /sync/frame).
 *
 * NOTE: This is built using Node's standard `http` module. In a real
 * production app (e.g. `node-gib` or a consumer app), you might adapt this
 * to Express, Fastify, or directly to an HTTP/2 server.
 */
export class SyncHttpNodeAdapter_V1 {
    opts;
    lc = `[${SyncHttpNodeAdapter_V1.name}]`;
    server;
    receiverPeer;
    port;
    basePath;
    /**
     * Map of clientId to their active Server-Sent Events HTTP Response streams.
     */
    activeSseStreams = new Map();
    constructor(opts) {
        this.opts = opts;
        this.receiverPeer = opts.receiverPeer;
        this.port = opts.port || 8080;
        this.basePath = opts.basePath || '/sync';
    }
    /**
     * Starts the HTTP Server listening on the configured port.
     */
    async start() {
        const lc = `[${this.start.name}]`;
        return new Promise((resolve, reject) => {
            try {
                if (logalot) {
                    console.log(`${lc} starting... (I: fc09bcef98bda9d91429161355c2e626)`);
                }
                const requestHandler = (req, res) => {
                    this.handleRequest(req, res).catch(e => {
                        console.error(`${lc} Uncaught request error: ${extractErrorMsg(e)}`);
                        if (!res.headersSent) {
                            res.writeHead(500);
                            res.end('Internal Server Error');
                        }
                    });
                };
                // Use Secure Server if TLS credentials are provided, otherwise fallback to unencrypted (h2c)
                if ((this.opts.key && this.opts.cert) || this.opts.pfx) {
                    const secureOpts = {
                        allowHTTP1: true // Fallback to HTTP/1.1 for clients that don't support HTTP/2
                    };
                    if (this.opts.key) {
                        secureOpts.key = this.opts.key;
                    }
                    if (this.opts.cert) {
                        secureOpts.cert = this.opts.cert;
                    }
                    if (this.opts.pfx) {
                        secureOpts.pfx = this.opts.pfx;
                    }
                    if (this.opts.passphrase) {
                        secureOpts.passphrase = this.opts.passphrase;
                    }
                    this.server = http2.createSecureServer(secureOpts, requestHandler);
                }
                else {
                    this.server = http2.createServer({}, requestHandler);
                }
                this.server.listen(this.port, () => {
                    const protocol = ((this.opts.key && this.opts.cert) || this.opts.pfx) ? 'https/h2' : 'http/h2c';
                    if (logalot) {
                        console.log(`${lc} Server listening on port ${this.port} (${protocol}) (I: 67a5ce98074c5aaaff022f5b3804c226)`);
                    }
                    resolve();
                });
            }
            catch (error) {
                console.error(`${lc} ${extractErrorMsg(error)}`);
                reject(error);
            }
            finally {
                if (logalot) {
                    console.log(`${lc} complete. (I: 1d9e40501e6c191b186ae32edf151526)`);
                }
            }
        });
    }
    /**
     * Stops the HTTP Server and closes all active streams.
     */
    async stop() {
        const lc = `[${this.stop.name}]`;
        return new Promise((resolve, reject) => {
            try {
                if (logalot) {
                    console.log(`${lc} starting... (I: d7bca060bb02a9fa0cbd4fb65971fb26)`);
                }
                for (const res of this.activeSseStreams.values()) {
                    res.end(); // close all open streams
                }
                this.activeSseStreams.clear();
                if (this.server) {
                    this.server.close((err) => {
                        if (err) {
                            return reject(err);
                        }
                        resolve();
                    });
                }
                else {
                    resolve();
                }
            }
            catch (error) {
                console.error(`${lc} ${extractErrorMsg(error)}`);
                reject(error);
            }
            finally {
                if (logalot) {
                    console.log(`${lc} complete. (I: afa81c5b3a3f88fb6fe0066dd096ff26)`);
                }
            }
        });
    }
    async handleRequest(req, res) {
        const lc = `${this.lc}[${this.handleRequest.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 286853cd380d13292d2d36171ca11a26)`);
            }
            // Set basic CORS headers for browser clients
            res.setHeader('Access-Control-Allow-Origin', '*');
            res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
            res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
            if (req.method === 'OPTIONS') {
                res.writeHead(204);
                res.end();
                return;
            }
            const url = new URL(req.url || '/', `http://${req.headers.host}`);
            // Route: GET /sync/events
            if (req.method === 'GET' && url.pathname.startsWith(`${this.basePath}/events`)) {
                return this.handleSseStreamRequest(req, res, url);
            }
            // Route: POST /sync/frame
            if (req.method === 'POST' && url.pathname.startsWith(`${this.basePath}/frame`)) {
                return this.handleSyncFrameRequest(req, res, url);
            }
            res.writeHead(404);
            res.end('Not Found');
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async handleSseStreamRequest(req, res, url) {
        const lc = `${this.lc}[${this.handleSseStreamRequest.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: 17800c6b501b3b07436303e97b930626)`);
            }
            // Get clientId from query param, or generate one
            const clientId = url.searchParams.get('clientId') || Math.random().toString(36).substring(2, 10);
            if (logalot) {
                console.log(`${lc} New SSE Connection: ${clientId} (I: c1ebeeb91763b76ed0a64cf8af7c6326)`);
            }
            res.writeHead(200, {
                'Content-Type': 'text/event-stream',
                'Cache-Control': 'no-cache',
                'Connection': 'keep-alive'
            });
            // Register the stream
            this.activeSseStreams.set(clientId, res);
            // Keep connection alive
            const keepAliveInterval = setInterval(() => {
                res.write(`:\n\n`); // comment line
            }, 30000);
            req.on('close', () => {
                if (logalot) {
                    console.log(`${lc} SSE Connection Closed: ${clientId} (I: 44aea766eb8b7279e5d9cf699a12cb26)`);
                }
                clearInterval(keepAliveInterval);
                this.activeSseStreams.delete(clientId);
            });
            // Send an initial connected message (optional)
            res.write(`data: ${JSON.stringify({ type: 'connected', clientId })}\n\n`);
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            throw error;
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    async handleSyncFrameRequest(req, res, url) {
        const lc = `${this.lc}[${this.handleSyncFrameRequest.name}]`;
        try {
            if (logalot) {
                console.log(`${lc} starting... (I: fee247b53b894f2b6cf3f1f2d4dec726)`);
            }
            // Assume the client ID is passed via query param or header
            const clientId = url.searchParams.get('clientId') || req.headers['x-client-id'];
            if (!clientId) {
                res.writeHead(400);
                res.end('clientId required');
                return;
            }
            const streamRes = this.activeSseStreams.get(clientId);
            if (!streamRes) {
                res.writeHead(400);
                res.end(`No active SSE stream found for clientId: ${clientId}`);
                return;
            }
            // 1. Parse incoming POST body
            const body = await this.readRequestBody(req);
            // TODO [USER]: Deserialize your incoming JSON request body into the expected IbGib variables
            const parsedBody = JSON.parse(body);
            // EXPECTED TYPES:
            const context = parsedBody.context;
            const payloadIbGibsControl = parsedBody.payloadIbGibsControl || [];
            // 2. Hand it off to the HTTP Receiver Peer
            if (logalot) {
                console.log(`${lc} Handling Incoming Sync Request from ${clientId} (I: d89d3d28a418d9f6b261b61a164bd326)`);
            }
            const responseContext = await this.receiverPeer.handleIncomingSyncRequest({
                context,
                payloadIbGibsControl
            });
            if (!responseContext) {
                res.writeHead(200);
                res.end('OK - No Response Context');
                return;
            }
            // 3. We successfully processed it and got a responseContext.
            // Now, we must stream the response down the `streamRes` (SSE Connection).
            // Stream Domain IbGibs first
            if (responseContext.payloadIbGibsDomain) {
                for (const domainIbGib of responseContext.payloadIbGibsDomain) {
                    // TODO [USER]: Serialize Domain IbGib for transport
                    const payloadChunk = JSON.stringify({
                        type: 'domain',
                        ibGib: domainIbGib
                    });
                    streamRes.write(`data: ${payloadChunk}\n\n`);
                }
                // Clean it up so we don't send the entire domain payload list again in the control context
                delete responseContext.payloadIbGibsDomain;
            }
            // Stream Control Context response
            // TODO [USER]: Serialize Response Context for transport
            const contextChunk = JSON.stringify({
                type: 'context',
                context: responseContext
            });
            streamRes.write(`data: ${contextChunk}\n\n`);
            // Ack the POST request
            res.writeHead(200);
            res.end('OK');
        }
        catch (error) {
            console.error(`${lc} ${extractErrorMsg(error)}`);
            res.writeHead(500);
            res.end(`Internal Server Error`);
        }
        finally {
            if (logalot) {
                console.log(`${lc} complete.`);
            }
        }
    }
    readRequestBody(req) {
        const lc = `${this.lc}[${this.readRequestBody.name}]`;
        return new Promise((resolve, reject) => {
            try {
                if (logalot) {
                    console.log(`${lc} starting... (I: 95cf4f489e1b7174dc5294096c651326)`);
                }
                let body = '';
                req.on('data', chunk => {
                    body += chunk.toString();
                });
                req.on('end', () => {
                    resolve(body);
                });
                req.on('error', (err) => {
                    console.error(`${lc} ${extractErrorMsg(err)}`);
                    reject(err);
                });
            }
            catch (error) {
                console.error(`${lc} ${extractErrorMsg(error)}`);
                reject(error);
            }
            finally {
                if (logalot) {
                    console.log(`${lc} complete.`);
                }
            }
        });
    }
}
//# sourceMappingURL=sync-http-node-adapter.mjs.map