"use strict";
((window) => {
  const webidl = window.__bootstrap.webidl;
  const { InnerBody } = window.__bootstrap.fetchBody;
  const { setEventTargetData } = window.__bootstrap.eventTarget;
  const {
    Response,
    fromInnerRequest,
    toInnerResponse,
    newInnerRequest,
    newInnerResponse,
    fromInnerResponse,
  } = window.__bootstrap.fetch;
  const core = window.Deno.core;
  const { BadResource, Interrupted } = core;
  const { ReadableStream } = window.__bootstrap.streams;
  const abortSignal = window.__bootstrap.abortSignal;
  const { WebSocket, _rid, _readyState, _eventLoop, _protocol, _server } =
    window.__bootstrap.webSocket;
  const {
    ArrayPrototypeIncludes,
    ArrayPrototypePush,
    ArrayPrototypeSome,
    Promise,
    Set,
    SetPrototypeAdd,
    SetPrototypeDelete,
    SetPrototypeValues,
    StringPrototypeIncludes,
    StringPrototypeToLowerCase,
    StringPrototypeSplit,
    Symbol,
    SymbolAsyncIterator,
    TypedArrayPrototypeSubarray,
    TypeError,
    Uint8Array,
  } = window.__bootstrap.primordials;
  const connErrorSymbol = Symbol("connError");
  class HttpConn {
    #rid = 0;
                    managedResources = new Set();
    constructor(rid) {
      this.#rid = rid;
    }
    
    get rid() {
      return this.#rid;
    }
    
    async nextRequest() {
      let nextRequest;
      try {
        nextRequest = await core.opAsync(
          "op_http_request_next",
          this.#rid,
        );
      } catch (error) {
                                this[connErrorSymbol] = error;
        if (
          error instanceof BadResource ||
          error instanceof Interrupted ||
          StringPrototypeIncludes(error.message, "connection closed")
        ) {
          return null;
        }
        throw error;
      }
      if (nextRequest === null) return null;
      const [
        requestRid,
        responseSenderRid,
        method,
        headersList,
        url,
      ] = nextRequest;
      
      let body = null;
      if (typeof requestRid === "number") {
        SetPrototypeAdd(this.managedResources, requestRid);
                                if (method !== "GET" && method !== "HEAD") {
          body = createRequestBodyStream(this, requestRid);
        }
      }
      const innerRequest = newInnerRequest(
        method,
        url,
        headersList,
        body !== null ? new InnerBody(body) : null,
        false,
      );
      const signal = abortSignal.newSignal();
      const request = fromInnerRequest(innerRequest, signal, "immutable");
      SetPrototypeAdd(this.managedResources, responseSenderRid);
      const respondWith = createRespondWith(
        this,
        responseSenderRid,
        requestRid,
      );
      return { request, respondWith };
    }
    
    close() {
      for (const rid of SetPrototypeValues(this.managedResources)) {
        core.tryClose(rid);
      }
      core.close(this.#rid);
    }
    [SymbolAsyncIterator]() {
            const httpConn = this;
      return {
        async next() {
          const reqEvt = await httpConn.nextRequest();
                    return { value: reqEvt, done: reqEvt === null };
        },
      };
    }
  }
  function readRequest(requestRid, zeroCopyBuf) {
    return core.opAsync(
      "op_http_request_read",
      requestRid,
      zeroCopyBuf,
    );
  }
  function createRespondWith(httpConn, responseSenderRid, requestRid) {
    return async function respondWith(resp) {
      if (resp instanceof Promise) {
        resp = await resp;
      }
      if (!(resp instanceof Response)) {
        throw new TypeError(
          "First argument to respondWith must be a Response or a promise resolving to a Response.",
        );
      }
      const innerResp = toInnerResponse(resp);
                        
      let respBody = null;
      if (innerResp.body !== null) {
        if (innerResp.body.unusable()) throw new TypeError("Body is unusable.");
        if (innerResp.body.streamOrStatic instanceof ReadableStream) {
          if (
            innerResp.body.length === null ||
            innerResp.body.source instanceof Blob
          ) {
            respBody = innerResp.body.stream;
          } else {
            const reader = innerResp.body.stream.getReader();
            const r1 = await reader.read();
            if (r1.done) {
              respBody = new Uint8Array(0);
            } else {
              respBody = r1.value;
              const r2 = await reader.read();
              if (!r2.done) throw new TypeError("Unreachable");
            }
          }
        } else {
          innerResp.body.streamOrStatic.consumed = true;
          respBody = innerResp.body.streamOrStatic.body;
        }
      } else {
        respBody = new Uint8Array(0);
      }
      SetPrototypeDelete(httpConn.managedResources, responseSenderRid);
      let responseBodyRid;
      try {
        responseBodyRid = await core.opAsync("op_http_response", [
          responseSenderRid,
          innerResp.status ?? 200,
          innerResp.headerList,
        ], respBody instanceof Uint8Array ? respBody : null);
      } catch (error) {
        const connError = httpConn[connErrorSymbol];
        if (error instanceof BadResource && connError != null) {
                    error = new connError.constructor(connError.message);
        }
        if (respBody !== null && respBody instanceof ReadableStream) {
          await respBody.cancel(error);
        }
        throw error;
      }
                  if (responseBodyRid !== null) {
        SetPrototypeAdd(httpConn.managedResources, responseBodyRid);
        try {
          if (respBody === null || !(respBody instanceof ReadableStream)) {
            throw new TypeError("Unreachable");
          }
          const reader = respBody.getReader();
          while (true) {
            const { value, done } = await reader.read();
            if (done) break;
            if (!(value instanceof Uint8Array)) {
              await reader.cancel(new TypeError("Value not a Uint8Array"));
              break;
            }
            try {
              await core.opAsync(
                "op_http_response_write",
                responseBodyRid,
                value,
              );
            } catch (error) {
              const connError = httpConn[connErrorSymbol];
              if (error instanceof BadResource && connError != null) {
                                error = new connError.constructor(connError.message);
              }
              await reader.cancel(error);
              throw error;
            }
          }
        } finally {
                              SetPrototypeDelete(httpConn.managedResources, responseBodyRid);
          try {
            await core.opAsync("op_http_response_close", responseBodyRid);
          } catch {  }
        }
      }
      const ws = resp[_ws];
      if (ws) {
        if (typeof requestRid !== "number") {
          throw new TypeError(
            "This request can not be upgraded to a websocket connection.",
          );
        }
        const wsRid = await core.opAsync(
          "op_http_upgrade_websocket",
          requestRid,
        );
        ws[_rid] = wsRid;
        ws[_protocol] = resp.headers.get("sec-websocket-protocol");
        if (ws[_readyState] === WebSocket.CLOSING) {
          await core.opAsync("op_ws_close", { rid: wsRid });
          ws[_readyState] = WebSocket.CLOSED;
          const errEvent = new ErrorEvent("error");
          ws.dispatchEvent(errEvent);
          const event = new CloseEvent("close");
          ws.dispatchEvent(event);
          core.tryClose(wsRid);
        } else {
          ws[_readyState] = WebSocket.OPEN;
          const event = new Event("open");
          ws.dispatchEvent(event);
          ws[_eventLoop]();
        }
      } else if (typeof requestRid === "number") {
                                SetPrototypeDelete(httpConn.managedResources, requestRid);
        core.tryClose(requestRid);
      }
    };
  }
  function createRequestBodyStream(httpConn, requestRid) {
    return new ReadableStream({
      type: "bytes",
      async pull(controller) {
        try {
                              const chunk = new Uint8Array(16 * 1024 + 256);
          const read = await readRequest(
            requestRid,
            chunk,
          );
          if (read > 0) {
                        controller.enqueue(TypedArrayPrototypeSubarray(chunk, 0, read));
          } else {
                        controller.close();
            SetPrototypeDelete(httpConn.managedResources, requestRid);
            core.close(requestRid);
          }
        } catch (err) {
                              controller.error(err);
          controller.close();
          SetPrototypeDelete(httpConn.managedResources, requestRid);
          core.close(requestRid);
        }
      },
      cancel() {
        SetPrototypeDelete(httpConn.managedResources, requestRid);
        core.close(requestRid);
      },
    });
  }
  const _ws = Symbol("[[associated_ws]]");
  function upgradeWebSocket(request, options = {}) {
    const upgrade = request.headers.get("upgrade");
    if (!upgrade || StringPrototypeToLowerCase(upgrade) !== "websocket") {
      throw new TypeError(
        "Invalid Header: 'upgrade' header must be 'websocket'",
      );
    }
    const connection = request.headers.get("connection");
    const connectionHasUpgradeOption = connection !== null &&
      ArrayPrototypeSome(
        StringPrototypeSplit(connection, /\s*,\s*/),
        (option) => StringPrototypeToLowerCase(option) === "upgrade",
      );
    if (!connectionHasUpgradeOption) {
      throw new TypeError(
        "Invalid Header: 'connection' header must be 'Upgrade'",
      );
    }
    const websocketKey = request.headers.get("sec-websocket-key");
    if (websocketKey === null) {
      throw new TypeError(
        "Invalid Header: 'sec-websocket-key' header must be set",
      );
    }
    const accept = core.opSync("op_http_websocket_accept_header", websocketKey);
    const r = newInnerResponse(101);
    r.headerList = [
      ["upgrade", "websocket"],
      ["connection", "Upgrade"],
      ["sec-websocket-accept", accept],
    ];
    const protocolsStr = request.headers.get("sec-websocket-protocol") || "";
    const protocols = StringPrototypeSplit(protocolsStr, ", ");
    if (protocols && options.protocol) {
      if (ArrayPrototypeIncludes(protocols, options.protocol)) {
        ArrayPrototypePush(r.headerList, [
          "sec-websocket-protocol",
          options.protocol,
        ]);
      } else {
        throw new TypeError(
          `Protocol '${options.protocol}' not in the request's protocol list (non negotiable)`,
        );
      }
    }
    const response = fromInnerResponse(r, "immutable");
    const socket = webidl.createBranded(WebSocket);
    setEventTargetData(socket);
    socket[_server] = true;
    response[_ws] = socket;
    return { response, socket };
  }
  window.__bootstrap.http = {
    HttpConn,
    upgradeWebSocket,
  };
})(this);