"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createUserDevServerProxy = exports.markPreviewBridgeDocumentUncacheable = void 0;
const http_proxy_middleware_1 = require("http-proxy-middleware");
const crypto_1 = require("crypto");
const logger_1 = require("../utils/logger");
const preview_bridge_1 = require("./preview-bridge");
const log = (0, logger_1.createLogger)('ProxyConfig');
/**
 * Satisfies http-proxy-middleware's required `target` option; real routing is
 * decided in `router` and gated in `middleware` so this URL is never used when
 * there is no valid upstream (`userPort` ≤ 0 and no variant target).
 */
const PROXY_LIBRARY_PLACEHOLDER_TARGET = 'http://127.0.0.1:9';
const hasValidUserDevPort = (userPort) => Number.isInteger(userPort) && userPort > 0;
const markPreviewBridgeDocumentUncacheable = (proxyRes) => {
    proxyRes.headers['cache-control'] = 'no-cache, no-store';
};
exports.markPreviewBridgeDocumentUncacheable = markPreviewBridgeDocumentUncacheable;
const createUserDevServerProxy = (userPort, userHost = 'localhost', options = {}) => {
    let variantTarget = null;
    const hasDefaultUpstream = hasValidUserDevPort(userPort);
    const isPreviewBridgeEnabled = options.isPreviewBridgeEnabled ?? false;
    const defaultTarget = hasDefaultUpstream
        ? `http://${userHost}:${userPort}`
        : null;
    const getActiveUpstream = () => variantTarget ?? defaultTarget ?? null;
    const getRouterTargetForLibrary = () => getActiveUpstream() ?? PROXY_LIBRARY_PLACEHOLDER_TARGET;
    const getActiveTargetPort = () => {
        const active = getActiveUpstream();
        if (!active) {
            return hasDefaultUpstream ? userPort : 'not configured';
        }
        try {
            const parsed = new URL(active);
            const parsedPort = Number(parsed.port);
            if (Number.isInteger(parsedPort) && parsedPort > 0) {
                return parsedPort;
            }
        }
        catch {
            // Fall back below.
        }
        return userPort > 0 ? userPort : 'not configured';
    };
    const setVariantTarget = (port) => {
        const prior = variantTarget ?? defaultTarget ?? '(none)';
        const next = port !== null && Number.isInteger(port) && port > 0
            ? `http://localhost:${port}`
            : null;
        variantTarget = next;
        const nextLabel = next ?? defaultTarget ?? '(none)';
        log.info(`setVariantTarget: ${prior} → ${nextLabel}`);
    };
    const prepareProxyResponseHeaders = (proxyRes) => {
        // Allow iframe embedding by removing restrictive security headers
        delete proxyRes.headers['x-frame-options'];
        delete proxyRes.headers['content-security-policy-report-only'];
        // Update Content-Security-Policy to allow iframe embedding
        if (proxyRes.headers['content-security-policy']) {
            const csp = proxyRes.headers['content-security-policy'];
            if (typeof csp === 'string') {
                // Remove frame-ancestors to allow embedding anywhere during development
                proxyRes.headers['content-security-policy'] = csp.replace(/frame-ancestors[^;]*;?/, '');
            }
        }
    };
    const allowBridgeScriptInCsp = (proxyRes, scriptNonce) => {
        const csp = proxyRes.headers['content-security-policy'];
        if (typeof csp === 'string') {
            proxyRes.headers['content-security-policy'] = (0, preview_bridge_1.allowPreviewBridgeNonceInCsp)(csp, { scriptNonce });
            return;
        }
        if (Array.isArray(csp)) {
            proxyRes.headers['content-security-policy'] = csp.map((value) => (0, preview_bridge_1.allowPreviewBridgeNonceInCsp)(value, { scriptNonce }));
        }
    };
    const createProxyOptions = (isHtmlTransformProxy) => ({
        target: defaultTarget ?? PROXY_LIBRARY_PLACEHOLDER_TARGET,
        router: () => getRouterTargetForLibrary(),
        changeOrigin: true,
        selfHandleResponse: isHtmlTransformProxy,
        // No path rewriting needed when proxying all routes by default
        on: {
            proxyRes: isHtmlTransformProxy
                ? (0, http_proxy_middleware_1.responseInterceptor)(async (responseBuffer, proxyRes, req, _res) => {
                    prepareProxyResponseHeaders(proxyRes);
                    const contentType = proxyRes.headers['content-type'];
                    if (!(0, preview_bridge_1.shouldInjectPreviewBridge)({
                        method: req.method,
                        accept: req.headers.accept,
                        contentType,
                    })) {
                        return responseBuffer;
                    }
                    const scriptNonce = (0, crypto_1.randomBytes)(16).toString('base64');
                    allowBridgeScriptInCsp(proxyRes, scriptNonce);
                    (0, exports.markPreviewBridgeDocumentUncacheable)(proxyRes);
                    return (0, preview_bridge_1.injectPreviewBridge)(responseBuffer.toString('utf8'), {
                        expectedParentOrigin: options.expectedPreviewParentOrigin,
                        scriptNonce,
                    });
                })
                : (proxyRes, _req, _res) => {
                    prepareProxyResponseHeaders(proxyRes);
                },
            proxyReq: (proxyReq, req, _res) => {
                // Preserve the original host for Next.js Server Actions
                // Next.js checks that x-forwarded-host matches the origin header
                if (req.headers.host) {
                    proxyReq.setHeader('x-forwarded-host', req.headers.host);
                }
                const originalPath = req.url;
                const proxiedPath = proxyReq.path;
                log.debug(`Proxying ${req.method} ${originalPath} -> ${proxiedPath} to ${getRouterTargetForLibrary()}`);
            },
            error: (err, req, res) => {
                log.warn(`Proxy error for ${req.method} ${req.url}:`, err.message);
                // Only handle if res is a ServerResponse (not a Socket)
                if (res && 'writeHead' in res && !res.headersSent) {
                    // Contract for Rivet UI: upstream-down responses use HTTP 502 and JSON body.
                    // The preview pane avoids loading this into the iframe when health checks fail first.
                    res.writeHead(502, { 'Content-Type': 'application/json' });
                    res.end(JSON.stringify({
                        error: 'Proxy Error',
                        message: `Failed to connect to development server on port ${getActiveTargetPort()}`,
                        details: err.message,
                    }));
                }
            },
        },
        // Handle WebSocket connections for Hot Module Reload (HMR)
        ws: true,
        // Timeout configurations
        proxyTimeout: 30_000, // In MS
        timeout: 30_000, // In MS
    });
    const proxyMiddlewareInstance = (0, http_proxy_middleware_1.createProxyMiddleware)(createProxyOptions(false));
    const htmlTransformProxyMiddlewareInstance = (0, http_proxy_middleware_1.createProxyMiddleware)(createProxyOptions(true));
    const gatedHttp = (req, res, next) => {
        const upstream = getActiveUpstream();
        if (upstream === null) {
            if ('writeHead' in res && !res.headersSent) {
                log.debug(`Rejecting proxied preview request — no upstream (userPort=${userPort}).`);
                res.writeHead(503, { 'Content-Type': 'application/json' });
                res.end(JSON.stringify({
                    error: 'Preview unavailable',
                    message: 'No upstream development server yet. Variant previews will reconnect automatically when ready.',
                }));
            }
            return;
        }
        const middleware = isPreviewBridgeEnabled && (0, preview_bridge_1.isHtmlDocumentRequest)(req)
            ? htmlTransformProxyMiddlewareInstance
            : proxyMiddlewareInstance;
        void middleware(req, res, next);
    };
    const gated = gatedHttp;
    gated.upgrade = (req, socket, head) => {
        if (getActiveUpstream() === null) {
            log.debug(`Rejecting WebSocket upgrade — no upstream (userPort=${userPort}).`);
            socket.destroy();
            return;
        }
        if (proxyMiddlewareInstance.upgrade) {
            proxyMiddlewareInstance.upgrade(req, socket, head);
            return;
        }
        socket.destroy();
    };
    if (hasDefaultUpstream) {
        log.info(`Created proxy middleware → default ${userHost}:${userPort}`);
    }
    else {
        log.info(`Created proxy middleware → no default user dev port (${userPort}); variant preview must set upstream`);
    }
    return { middleware: gated, setVariantTarget };
};
exports.createUserDevServerProxy = createUserDevServerProxy;
//# sourceMappingURL=proxy-config.js.map