// Copyright 2021-2024 The Connect Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
var __asyncValues = (this && this.__asyncValues) || function (o) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var m = o[Symbol.asyncIterator], i;
    return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
    var g = generator.apply(thisArg, _arguments || []), i, q = [];
    return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
    function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
    function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
    function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
    function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
    function fulfill(value) { resume("next", value); }
    function reject(value) { resume("throw", value); }
    function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
import { Code, ConnectError } from "@connectrpc/connect";
import { nodeHeaderToWebHeader, webHeaderToNodeHeaders, } from "./node-universal-header.js";
import { connectErrorFromH2ResetCode, connectErrorFromNodeReason, } from "./node-error.js";
export function universalRequestFromNodeRequest(nodeRequest, ...rest) {
    var _a, _b;
    const nodeResponse = rest.length === 3 ? rest[0] : undefined;
    const parsedJsonBody = rest.length === 3 ? rest[1] : rest[0];
    const contextValues = rest.length === 3 ? rest[2] : rest[1];
    const encrypted = "encrypted" in nodeRequest.socket && nodeRequest.socket.encrypted;
    const protocol = encrypted ? "https" : "http";
    const authority = "authority" in nodeRequest
        ? nodeRequest.authority
        : nodeRequest.headers.host;
    const pathname = (_a = nodeRequest.url) !== null && _a !== void 0 ? _a : "";
    if (authority === undefined) {
        throw new ConnectError("unable to determine request authority from Node.js server request", Code.Internal);
    }
    const body = parsedJsonBody !== undefined
        ? parsedJsonBody
        : asyncIterableFromNodeServerRequest(nodeRequest);
    const abortController = new AbortController();
    if ("stream" in nodeRequest) {
        // HTTP/2 has error codes we want to honor
        nodeRequest.once("close", () => {
            const err = connectErrorFromH2ResetCode(nodeRequest.stream.rstCode);
            if (err !== undefined) {
                abortController.abort(err);
            }
            else {
                abortController.abort();
            }
        });
    }
    else {
        // HTTP/1.1 does not have error codes, but Node.js has ECONNRESET
        const nodeResponsOrRequest = nodeResponse !== null && nodeResponse !== void 0 ? nodeResponse : nodeRequest;
        const onH1Error = (e) => {
            nodeRequest.off("error", onH1Error);
            nodeResponsOrRequest.off("close", onH1Close);
            abortController.abort(connectErrorFromNodeReason(e));
        };
        const onH1Close = () => {
            nodeRequest.off("error", onH1Error);
            nodeResponsOrRequest.off("close", onH1Close);
            // When subscribed to the response, this can get called before "error"
            abortController.abort(nodeRequest.errored
                ? connectErrorFromNodeReason(nodeRequest.errored)
                : undefined);
        };
        nodeRequest.once("error", onH1Error);
        // Node emits close on the request as soon as all data is read.
        // We instead subscribe to the response (if available)
        //
        // Ref: https://github.com/nodejs/node/issues/40775
        nodeResponsOrRequest.once("close", onH1Close);
    }
    return {
        httpVersion: nodeRequest.httpVersion,
        method: (_b = nodeRequest.method) !== null && _b !== void 0 ? _b : "",
        url: new URL(pathname, `${protocol}://${authority}`).toString(),
        header: nodeHeaderToWebHeader(nodeRequest.headers),
        body,
        signal: abortController.signal,
        contextValues: contextValues,
    };
}
/**
 * Writes a UniversalServerResponse to a Node.js server response.
 * This function helps to implement adapters to server frameworks running
 * on Node.js. Please be careful using this function in your own code, as we
 * may have to make changes to it in the future.
 */
export async function universalResponseToNodeResponse(universalResponse, nodeResponse) {
    var _a, _b, _c;
    const it = (_a = universalResponse.body) === null || _a === void 0 ? void 0 : _a[Symbol.asyncIterator]();
    let isWriteError = false;
    try {
        if (it !== undefined) {
            let chunk = await it.next();
            isWriteError = true;
            // we deliberately send headers after first read, not before,
            // because we have to give the implementation a chance to
            // set response headers
            nodeResponse.writeHead(universalResponse.status, webHeaderToNodeHeaders(universalResponse.header));
            isWriteError = false;
            for (; chunk.done !== true; chunk = await it.next()) {
                isWriteError = true;
                await write(nodeResponse, chunk.value);
                if ("flush" in nodeResponse &&
                    typeof nodeResponse.flush == "function") {
                    // The npm package "compression" is an express middleware that is widely used,
                    // for example in next.js. It uses the npm package "compressible" to determine
                    // whether to apply compression to a response. Unfortunately, "compressible"
                    // matches every mime type that ends with "+json", causing our server-streaming
                    // RPCs to be buffered.
                    // The package modifies the response object, and adds a flush() method, which
                    // flushes the underlying gzip or deflate stream from the Node.js zlib module.
                    // The method is added here:
                    // https://github.com/expressjs/compression/blob/ad5113b98cafe1382a0ece30bb4673707ac59ce7/index.js#L70
                    nodeResponse.flush();
                }
                isWriteError = false;
            }
        }
        if (!nodeResponse.headersSent) {
            nodeResponse.writeHead(universalResponse.status, webHeaderToNodeHeaders(universalResponse.header));
        }
        if (universalResponse.trailer) {
            nodeResponse.addTrailers(webHeaderToNodeHeaders(universalResponse.trailer));
        }
        await new Promise((resolve) => {
            // The npm package "compression" crashes when a callback is passed to end()
            // https://github.com/expressjs/compression/blob/ad5113b98cafe1382a0ece30bb4673707ac59ce7/index.js#L115
            nodeResponse.once("end", resolve);
            nodeResponse.end();
        });
    }
    catch (e) {
        // Report write errors to the handler.
        if (isWriteError) {
            (_b = it === null || it === void 0 ? void 0 : it.throw) === null || _b === void 0 ? void 0 : _b.call(it, e).catch(() => { });
        }
        throw connectErrorFromNodeReason(e);
    }
    finally {
        (_c = it === null || it === void 0 ? void 0 : it.return) === null || _c === void 0 ? void 0 : _c.call(it).catch(() => { });
    }
}
function asyncIterableFromNodeServerRequest(request) {
    return __asyncGenerator(this, arguments, function* asyncIterableFromNodeServerRequest_1() {
        var _a, e_1, _b, _c;
        const it = request.iterator({
            // Node.js v16 closes request and response when this option isn't disabled.
            // When one of our handlers receives invalid data (such as an unexpected
            // compression flag in a streaming request), we're unable to write the error
            // response.
            // Later major versions have a more sensible behavior - we can revert this
            // workaround once we stop supporting v16.
            destroyOnReturn: false,
        });
        try {
            for (var _d = true, it_1 = __asyncValues(it), it_1_1; it_1_1 = yield __await(it_1.next()), _a = it_1_1.done, !_a; _d = true) {
                _c = it_1_1.value;
                _d = false;
                const chunk = _c;
                yield yield __await(chunk);
            }
        }
        catch (e_1_1) { e_1 = { error: e_1_1 }; }
        finally {
            try {
                if (!_d && !_a && (_b = it_1.return)) yield __await(_b.call(it_1));
            }
            finally { if (e_1) throw e_1.error; }
        }
    });
}
function write(stream, data) {
    return new Promise((resolve, reject) => {
        if (stream.errored) {
            return error(stream.errored);
        }
        stream.once("error", error);
        stream.once("drain", drain);
        // flushed == false: the stream wishes for the calling code to wait for
        // the 'drain' event to be emitted before continuing to write additional
        // data.
        const flushed = stream.write(data, "binary", function (err) {
            if (err && !flushed) {
                // We are never getting a "drain" nor an "error" event if the stream
                // has already ended (ERR_STREAM_WRITE_AFTER_END), so we have to
                // resolve our promise in this callback.
                error(err);
                // However, once we do that (and remove our event listeners), we _do_
                // get an "error" event, which ends up as an uncaught exception.
                // We silence this error specifically with the following listener.
                // All of this seems very fragile.
                stream.once("error", () => {
                    //
                });
            }
        });
        if (flushed) {
            drain();
        }
        function error(err) {
            stream.off("error", error);
            stream.off("drain", drain);
            reject(err);
        }
        function drain() {
            stream.off("error", error);
            stream.off("drain", drain);
            resolve();
        }
    });
}
