import { Router } from 'express';
const OPENCODE_URL = process.env.OPENCODE_URL || 'http://localhost:4096';
const router = Router();
// Routes that should return 202 immediately without waiting for OpenCode response.
// These are "fire-and-forget" - the browser doesn't need the response body,
// and waiting would cause the UI to hang while OpenCode processes the request.
const FIRE_AND_FORGET_PATTERNS = [
    { method: 'POST', pattern: /^\/session\/[^/]+\/shell$/ },
    { method: 'POST', pattern: /^\/session\/[^/]+\/command$/ },
    { method: 'POST', pattern: /^\/session\/[^/]+\/abort$/ },
    { method: 'POST', pattern: /^\/session\/[^/]+\/revert$/ },
    { method: 'POST', pattern: /^\/session\/[^/]+\/unrevert$/ },
    { method: 'POST', pattern: /^\/session\/[^/]+\/summarize$/ },
    // Permission endpoints (old and new SDK versions)
    { method: 'POST', pattern: /^\/session\/[^/]+\/permissions\/[^/]+$/ },
    { method: 'POST', pattern: /^\/permission\/[^/]+\/reply$/ },
];
const isFireAndForget = (method, path) => {
    // Strip query string for matching
    const pathWithoutQuery = path.split('?')[0];
    return FIRE_AND_FORGET_PATTERNS.some((route) => route.method === method && route.pattern.test(pathWithoutQuery));
};
const buildTargetUrl = (req) => {
    const url = new URL(req.url, OPENCODE_URL);
    return url;
};
const buildHeaders = (req, includeBody) => {
    const headers = {};
    if (typeof req.headers['accept'] === 'string') {
        headers['accept'] = req.headers['accept'];
    }
    if (includeBody) {
        headers['content-type'] = 'application/json';
    }
    return headers;
};
const hasBody = (method) => {
    return ['POST', 'PUT', 'PATCH'].includes(method);
};
// Fire-and-forget: send request to OpenCode but return 202 immediately
const fireAndForget = (req, label) => {
    const url = buildTargetUrl(req);
    const headers = buildHeaders(req, hasBody(req.method));
    fetch(url, {
        method: req.method,
        headers,
        body: hasBody(req.method) ? JSON.stringify(req.body ?? {}) : undefined,
    }).catch((error) => {
        console.error(`[proxy] ${label} fire-and-forget error:`, error);
    });
};
// Stream proxy: forward request and stream response back
const streamProxy = async (req, res) => {
    const url = buildTargetUrl(req);
    const headers = buildHeaders(req, hasBody(req.method));
    try {
        const response = await fetch(url, {
            method: req.method,
            headers,
            body: hasBody(req.method) ? JSON.stringify(req.body ?? {}) : undefined,
        });
        // Forward status
        res.status(response.status);
        // Forward headers (except content-length which may be wrong after buffering)
        response.headers.forEach((value, key) => {
            if (key.toLowerCase() !== 'content-length') {
                res.setHeader(key, value);
            }
        });
        // Handle 204 No Content
        if (response.status === 204 || !response.body) {
            res.end();
            return;
        }
        // Check if this is SSE
        const contentType = response.headers.get('content-type') ?? '';
        const isSSE = contentType.includes('text/event-stream');
        if (isSSE) {
            // SSE: stream chunks as they arrive
            res.setHeader('Cache-Control', 'no-cache');
            res.setHeader('Connection', 'keep-alive');
            const reader = response.body.getReader();
            try {
                while (true) {
                    const { done, value } = await reader.read();
                    if (done)
                        break;
                    res.write(value);
                }
            }
            finally {
                reader.releaseLock();
            }
            res.end();
        }
        else {
            // Non-SSE: buffer and send
            const buffer = Buffer.from(await response.arrayBuffer());
            res.send(buffer);
        }
    }
    catch (error) {
        console.error(`[proxy] ${req.method} ${req.url} error:`, error);
        res.status(502).json({ error: 'Failed to proxy request to OpenCode' });
    }
};
// Log all proxied requests
router.use((req, _res, next) => {
    console.log(`[proxy] ${req.method} ${req.url}`);
    next();
});
// Main proxy handler
router.use('/', async (req, res) => {
    if (isFireAndForget(req.method, req.url)) {
        const label = req.url.split('?')[0];
        fireAndForget(req, label);
        res.status(202).end();
        return;
    }
    await streamProxy(req, res);
});
export default router;
