/**
 * The `HttpServerResponse` module provides immutable response values for Effect
 * HTTP servers. Handlers return an {@link HttpServerResponse}, then the runtime
 * converts it to a platform response such as a Web `Response`.
 *
 * **Mental model**
 *
 * - A response is a status, optional status text, headers, cookies, and an HTTP
 *   body.
 * - Constructors set practical defaults: {@link empty} uses `204`,
 *   {@link redirect} uses `302`, and body constructors use `200`.
 * - Body metadata is reflected into headers when a response is created or when
 *   the body is replaced.
 * - Cookies are stored separately from the normal header map and become
 *   `Set-Cookie` headers during conversion.
 * - Constructors that encode data can return effects when encoding may fail.
 *
 * **Common tasks**
 *
 * - Create empty, redirect, text, HTML, JSON, or URL-encoded responses with
 *   {@link empty}, {@link redirect}, {@link text}, {@link html}, {@link json},
 *   {@link schemaJson}, or {@link urlParams}.
 * - Send binary, raw, form-data, stream, or file bodies with {@link uint8Array},
 *   {@link raw}, {@link formData}, {@link stream}, {@link file}, or
 *   {@link fileWeb}.
 * - Adjust status, headers, and bodies with {@link setStatus},
 *   {@link setHeader}, {@link setHeaders}, and {@link setBody}.
 * - Manage cookies with {@link setCookie}, {@link expireCookie},
 *   {@link removeCookie}, and their unsafe variants.
 * - Convert responses with {@link toWeb}, {@link fromWeb},
 *   {@link toClientResponse}, and {@link fromClientResponse}.
 *
 * **Gotchas**
 *
 * - Body content type and content length override existing `content-type` and
 *   `content-length` headers when body metadata is present.
 * - {@link json} and {@link schemaJson} capture encoding failures in the Effect
 *   error channel; {@link jsonUnsafe} throws instead.
 * - {@link setCookie} and {@link expireCookie} validate cookie output in the
 *   Effect error channel; unsafe cookie helpers throw on invalid cookies.
 * - `Set-Cookie` headers are produced from the response cookie collection, not
 *   by manually editing the normal header map.
 *
 * @since 4.0.0
 */
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import * as ErrorReporter from "../../ErrorReporter.js";
import { dual } from "../../Function.js";
import * as Inspectable from "../../Inspectable.js";
import { PipeInspectableProto } from "../../internal/core.js";
import * as Option from "../../Option.js";
import { pipeArguments } from "../../Pipeable.js";
import { hasProperty } from "../../Predicate.js";
import { redact } from "../../Redactable.js";
import * as Stream from "../../Stream.js";
import * as Cookies from "./Cookies.js";
import * as Headers from "./Headers.js";
import * as Body from "./HttpBody.js";
import * as HttpClientError from "./HttpClientError.js";
import * as HttpClientRequest from "./HttpClientRequest.js";
import * as HttpClientResponse from "./HttpClientResponse.js";
import * as HttpIncomingMessage from "./HttpIncomingMessage.js";
import * as Template from "./Template.js";
import * as UrlParams from "./UrlParams.js";
const TypeId = "~effect/http/HttpServerResponse";
/**
 * Returns `true` when the supplied value is an `HttpServerResponse`.
 *
 * @category guards
 * @since 4.0.0
 */
export const isHttpServerResponse = u => hasProperty(u, TypeId);
/**
 * Creates an empty HTTP response.
 *
 * **Details**
 *
 * The default status is `204`.
 *
 * @category constructors
 * @since 4.0.0
 */
export const empty = options => makeResponse({
  status: options?.status ?? 204,
  statusText: options?.statusText,
  headers: options?.headers ? Headers.fromInput(options.headers) : undefined,
  cookies: options?.cookies
});
/**
 * Creates a redirect response with a `Location` header.
 *
 * **Details**
 *
 * The default status is `302`; custom headers are merged with the generated
 * `Location` header.
 *
 * @category constructors
 * @since 4.0.0
 */
export const redirect = (location, options) => {
  const headers = Headers.fromRecordUnsafe({
    location: location.toString()
  });
  return makeResponse({
    status: options?.status ?? 302,
    statusText: options?.statusText,
    headers: options?.headers ? Headers.merge(headers, Headers.fromInput(options.headers)) : headers,
    cookies: options?.cookies ?? Cookies.empty
  });
};
/**
 * Creates an HTTP response whose body is a `Uint8Array`.
 *
 * @category constructors
 * @since 4.0.0
 */
export const uint8Array = (body, options) => {
  const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty;
  return makeResponse({
    status: options?.status ?? 200,
    statusText: options?.statusText,
    headers,
    cookies: options?.cookies ?? Cookies.empty,
    body: Body.uint8Array(body, getContentType(options, headers))
  });
};
const getContentType = (options, headers) => {
  if (options?.contentType) {
    return options.contentType;
  } else if (options?.headers) {
    return headers["content-type"];
  }
};
/**
 * Creates an HTTP response whose body is a string.
 *
 * @category constructors
 * @since 4.0.0
 */
export const text = (body, options) => {
  const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty;
  return makeResponse({
    status: options?.status ?? 200,
    statusText: options?.statusText,
    headers,
    cookies: options?.cookies ?? Cookies.empty,
    body: Body.text(body, getContentType(options, headers))
  });
};
/**
 * Creates an HTML response with the `text/html` content type.
 *
 * **Details**
 *
 * Passing a string returns a response directly. Using it as a template tag returns
 * an effect so interpolated values can be rendered with their required services
 * and errors.
 *
 * @category constructors
 * @since 4.0.0
 */
export const html = (strings, ...args) => {
  if (typeof strings === "string") {
    return text(strings, {
      contentType: "text/html"
    });
  }
  return Effect.map(Template.make(strings, ...args), _ => text(_, {
    contentType: "text/html"
  }));
};
/**
 * Creates a streaming HTML response from a template.
 *
 * **Details**
 *
 * The template is encoded as a byte stream and can use streaming interpolated
 * values from the current context.
 *
 * @category constructors
 * @since 4.0.0
 */
export const htmlStream = (strings, ...args) => Effect.map(Effect.context(), context => stream(Stream.provideContext(Stream.encodeText(Template.stream(strings, ...args)), context), {
  contentType: "text/html"
}));
/**
 * Creates a JSON HTTP response.
 *
 * **Details**
 *
 * The body is serialized with `JSON.stringify`; serialization errors are captured
 * as `HttpBodyError` failures.
 *
 * @category constructors
 * @since 4.0.0
 */
export const json = (body, options) => {
  const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty;
  return Effect.map(Body.json(body, getContentType(options, headers)), body => makeResponse({
    status: options?.status ?? 200,
    statusText: options?.statusText,
    headers,
    cookies: options?.cookies,
    body
  }));
};
/**
 * Creates a JSON response constructor backed by a schema encoder.
 *
 * **Details**
 *
 * The returned function encodes the value with the supplied schema before
 * serializing it as JSON, and can fail with `HttpBodyError` if schema encoding or
 * JSON serialization fails.
 *
 * @category constructors
 * @since 4.0.0
 */
export const schemaJson = (schema, options) => {
  const encode = Body.jsonSchema(schema, options);
  return (body, options) => {
    const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty;
    return Effect.map(encode(body, getContentType(options, headers)), body => makeResponse({
      status: options?.status ?? 200,
      statusText: options?.statusText,
      headers,
      cookies: options?.cookies,
      body
    }));
  };
};
/**
 * Creates a JSON HTTP response synchronously.
 *
 * **Gotchas**
 *
 * Unlike `json`, serialization errors from `JSON.stringify` are not captured in
 * `Effect`.
 *
 * @category constructors
 * @since 4.0.0
 */
export const jsonUnsafe = (body, options) => {
  const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty;
  return makeResponse({
    status: options?.status ?? 200,
    statusText: options?.statusText,
    headers,
    cookies: options?.cookies,
    body: Body.jsonUnsafe(body, getContentType(options, headers))
  });
};
/**
 * Creates a response from URL parameters using the
 * `application/x-www-form-urlencoded` content type by default.
 *
 * @category constructors
 * @since 4.0.0
 */
export const urlParams = (body, options) => {
  const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty;
  return makeResponse({
    status: options?.status ?? 200,
    statusText: options?.statusText,
    headers,
    cookies: options?.cookies,
    body: Body.text(UrlParams.toString(UrlParams.fromInput(body)), getContentType(options, headers) ?? "application/x-www-form-urlencoded")
  });
};
/**
 * Creates a response with a raw body value.
 *
 * **When to use**
 *
 * Use when the underlying runtime already understands the body value, such
 * as a Web `Response`, `Blob`, or `ReadableStream`; the body is passed through
 * for later platform conversion.
 *
 * @category constructors
 * @since 4.0.0
 */
export const raw = (body, options) => makeResponse({
  status: options?.status ?? 200,
  statusText: options?.statusText,
  headers: options?.headers && Headers.fromInput(options.headers),
  cookies: options?.cookies,
  body: Body.raw(body, {
    contentType: options?.contentType,
    contentLength: options?.contentLength
  })
});
/**
 * Creates a response whose body is a Web `FormData` value.
 *
 * @category constructors
 * @since 4.0.0
 */
export const formData = (body, options) => makeResponse({
  status: options?.status ?? 200,
  statusText: options?.statusText,
  headers: options?.headers && Headers.fromInput(options.headers),
  cookies: options?.cookies,
  body: Body.formData(body)
});
/**
 * Creates a streaming response from a stream of byte chunks.
 *
 * **Details**
 *
 * Optional response metadata can supply the status, headers, content type, and
 * content length.
 *
 * @category constructors
 * @since 4.0.0
 */
export const stream = (body, options) => {
  const headers = options?.headers ? Headers.fromInput(options.headers) : Headers.empty;
  return makeResponse({
    status: options?.status ?? 200,
    statusText: options?.statusText,
    headers,
    cookies: options?.cookies,
    body: Body.stream(body, getContentType(options, headers), options?.contentLength)
  });
};
const HttpPlatformKey = /*#__PURE__*/Context.Service("effect/http/HttpPlatform");
/**
 * Creates a streamed file response for a file system path.
 *
 * **Details**
 *
 * The effect requires `HttpPlatform`, can fail with a platform error, and supports
 * options for status, headers, offset, and byte range.
 *
 * @category constructors
 * @since 4.0.0
 */
export const file = (path, options) => Effect.flatMap(HttpPlatformKey, platform => platform.fileResponse(path, options));
/**
 * Creates a streamed file response for a Web `File`-like value.
 *
 * **Details**
 *
 * The effect requires `HttpPlatform` and supports options for status, headers,
 * offset, and byte range.
 *
 * @category constructors
 * @since 4.0.0
 */
export const fileWeb = (file, options) => Effect.flatMap(HttpPlatformKey, platform => platform.fileWebResponse(file, options));
/**
 * Returns a response with the specified header set to the supplied value.
 *
 * @category combinators
 * @since 4.0.0
 */
export const setHeader = /*#__PURE__*/dual(3, (self, key, value) => makeResponse({
  ...self,
  headers: Headers.set(self.headers, key, value)
}));
/**
 * Returns a response with all supplied headers set on the existing header map.
 *
 * @category combinators
 * @since 4.0.0
 */
export const setHeaders = /*#__PURE__*/dual(2, (self, input) => makeResponse({
  ...self,
  headers: Headers.setAll(self.headers, input)
}));
/**
 * Returns a response with the cookie of the specified name removed.
 *
 * @category combinators
 * @since 4.0.0
 */
export const removeCookie = /*#__PURE__*/dual(2, (self, name) => makeResponse({
  ...self,
  cookies: Cookies.remove(self.cookies, name)
}));
/**
 * Returns a response with its cookie collection replaced by the supplied cookies.
 *
 * @category combinators
 * @since 4.0.0
 */
export const replaceCookies = /*#__PURE__*/dual(2, (self, cookies) => makeResponse({
  ...self,
  cookies
}));
/**
 * Sets a cookie on the response.
 *
 * **Details**
 *
 * The effect fails with `CookiesError` if the cookie name, value, or options are
 * invalid.
 *
 * @category combinators
 * @since 4.0.0
 */
export const setCookie = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, name, value, options) => Effect.map(Effect.fromResult(Cookies.set(self.cookies, name, value, options)), cookies => makeResponse({
  ...self,
  cookies
})));
/**
 * Sets an expired cookie on an `HttpServerResponse`.
 *
 * **Details**
 *
 * Returns an effect because cookie encoding can fail. The original response is not
 * mutated; the effect succeeds with a response containing the updated cookie set.
 *
 * @category combinators
 * @since 4.0.0
 */
export const expireCookie = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, name, options) => Effect.map(Effect.fromResult(Cookies.expireCookie(self.cookies, name, options)), cookies => makeResponse({
  ...self,
  cookies
})));
/**
 * Sets a cookie on an `HttpServerResponse`, throwing if the cookie cannot be
 * encoded.
 *
 * **When to use**
 *
 * Use when setting one trusted cookie and encoding failures should throw
 * instead of being represented as `CookiesError` failures.
 *
 * @category combinators
 * @since 4.0.0
 */
export const setCookieUnsafe = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, name, value, options) => makeResponse({
  ...self,
  cookies: Cookies.setUnsafe(self.cookies, name, value, options)
}));
/**
 * Sets an expired cookie on an `HttpServerResponse`, throwing if the expiration cookie
 * cannot be encoded.
 *
 * **When to use**
 *
 * Use when expiring one trusted cookie and encoding failures should throw
 * instead of being represented as `CookiesError` failures.
 *
 * @category combinators
 * @since 4.0.0
 */
export const expireCookieUnsafe = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, name, options) => makeResponse({
  ...self,
  cookies: Cookies.expireCookieUnsafe(self.cookies, name, options)
}));
/**
 * Updates the cookies attached to an `HttpServerResponse` using the supplied
 * function.
 *
 * **Details**
 *
 * The original response is not mutated; a new response is returned with the
 * callback result as its cookie collection.
 *
 * @category combinators
 * @since 4.0.0
 */
export const updateCookies = /*#__PURE__*/dual(2, (self, f) => makeResponse({
  ...self,
  cookies: f(self.cookies)
}));
/**
 * Merges additional cookies into the cookies attached to an
 * `HttpServerResponse`.
 *
 * **Details**
 *
 * The original response is not mutated; a new response is returned with the merged
 * cookie collection.
 *
 * @category combinators
 * @since 4.0.0
 */
export const mergeCookies = /*#__PURE__*/dual(2, (self, cookies) => makeResponse({
  ...self,
  cookies: Cookies.merge(self.cookies, cookies)
}));
/**
 * Sets multiple cookies on an `HttpServerResponse`.
 *
 * **Details**
 *
 * Each input entry contains a cookie name, value, and optional cookie options. The
 * returned effect fails with `CookiesError` if any cookie cannot be encoded.
 *
 * @category combinators
 * @since 4.0.0
 */
export const setCookies = /*#__PURE__*/dual(2, (self, cookies) => Effect.map(Effect.fromResult(Cookies.setAll(self.cookies, cookies)), cookies => makeResponse({
  ...self,
  cookies
})));
/**
 * Sets multiple cookies on an `HttpServerResponse`, throwing if any cookie cannot
 * be encoded.
 *
 * **When to use**
 *
 * Use when setting multiple trusted cookies and encoding failures should throw
 * instead of being represented as `CookiesError` failures.
 *
 * @category combinators
 * @since 4.0.0
 */
export const setCookiesUnsafe = /*#__PURE__*/dual(2, (self, cookies) => makeResponse({
  ...self,
  cookies: Cookies.setAllUnsafe(self.cookies, cookies)
}));
/**
 * Replaces the body of an `HttpServerResponse`.
 *
 * **Details**
 *
 * When the body carries a content type or content length, the returned response
 * includes the corresponding headers.
 *
 * @category combinators
 * @since 4.0.0
 */
export const setBody = /*#__PURE__*/dual(2, (self, body) => makeResponse({
  ...self,
  body
}));
/**
 * Sets the HTTP status code of an `HttpServerResponse`.
 *
 * **Details**
 *
 * When `statusText` is omitted, the existing status text is preserved.
 *
 * @category combinators
 * @since 4.0.0
 */
export const setStatus = /*#__PURE__*/dual(args => isHttpServerResponse(args[0]), (self, status, statusText) => makeResponse({
  ...self,
  status,
  statusText: statusText ?? self.statusText
}));
/**
 * Converts an `HttpServerResponse` to a Web `Response`.
 *
 * **Details**
 *
 * Cookies are appended as `Set-Cookie` headers. Stream bodies are converted using
 * the supplied context, and `withoutBody` can be used for responses such as HEAD
 * responses.
 *
 * @category converting
 * @since 4.0.0
 */
export const toWeb = (response, options) => {
  const headers = new globalThis.Headers(response.headers);
  if (!Cookies.isEmpty(response.cookies)) {
    const toAdd = Cookies.toSetCookieHeaders(response.cookies);
    for (const header of toAdd) {
      headers.append("set-cookie", header);
    }
  }
  if (options?.withoutBody) {
    return new Response(undefined, {
      status: response.status,
      statusText: response.statusText,
      headers
    });
  }
  const body = response.body;
  switch (body._tag) {
    case "Empty":
      {
        return new Response(undefined, {
          status: response.status,
          statusText: response.statusText,
          headers
        });
      }
    case "Uint8Array":
    case "Raw":
      {
        if (body.body instanceof Response) {
          for (const [key, value] of headers) {
            body.body.headers.set(key, value);
          }
          return body.body;
        }
        return new Response(body.body, {
          status: response.status,
          statusText: response.statusText,
          headers
        });
      }
    case "FormData":
      {
        return new Response(body.formData, {
          status: response.status,
          statusText: response.statusText,
          headers
        });
      }
    case "Stream":
      {
        return new Response(Stream.toReadableStreamWith(body.stream, options?.context ?? Context.empty()), {
          status: response.status,
          statusText: response.statusText,
          headers
        });
      }
  }
};
/**
 * Wraps an `HttpServerResponse` as an `HttpClientResponse`.
 *
 * **Details**
 *
 * An optional request can be supplied for client-response metadata and decode
 * errors.
 *
 * @category converting
 * @since 4.0.0
 */
export const toClientResponse = (response, options) => new ServerHttpClientResponse(options?.request ?? HttpClientRequest.empty, response);
class ServerHttpClientResponse extends Inspectable.Class {
  [HttpIncomingMessage.TypeId];
  [HttpClientResponse.TypeId];
  request;
  response;
  constructor(request, response) {
    super();
    this.request = request;
    this.response = response;
    this[HttpIncomingMessage.TypeId] = HttpIncomingMessage.TypeId;
    this[HttpClientResponse.TypeId] = HttpClientResponse.TypeId;
  }
  toJSON() {
    return HttpIncomingMessage.inspect(this, {
      _id: "HttpClientResponse",
      request: this.request.toJSON(),
      status: this.status
    });
  }
  get status() {
    return this.response.status;
  }
  cachedHeaders;
  get headers() {
    return this.cachedHeaders ??= this.response.body._tag === "FormData" ? Headers.merge(this.response.headers, Headers.fromInput(this.getFormDataResponse().headers)) : this.response.headers;
  }
  get cookies() {
    return this.response.cookies;
  }
  get remoteAddress() {
    return Option.none();
  }
  get stream() {
    const body = this.response.body;
    switch (body._tag) {
      case "Empty":
        {
          return Stream.empty;
        }
      case "Stream":
        {
          return Stream.mapError(body.stream, cause => this.decodeError(cause));
        }
      case "Uint8Array":
        {
          return Stream.succeed(body.body);
        }
      case "Raw":
        {
          const rawBody = body.body;
          if (rawBody instanceof Response) {
            return rawBody.body ? Stream.fromReadableStream({
              evaluate: () => rawBody.body,
              onError: cause => this.decodeError(cause)
            }) : Stream.empty;
          }
          if (isReadableStream(rawBody)) {
            return Stream.fromReadableStream({
              evaluate: () => rawBody,
              onError: cause => this.decodeError(cause)
            });
          }
          if (rawBody instanceof Blob) {
            return Stream.fromReadableStream({
              evaluate: () => rawBody.stream(),
              onError: cause => this.decodeError(cause)
            });
          }
          return Stream.unwrap(Effect.map(this.bytes, Stream.succeed));
        }
      case "FormData":
        {
          const response = this.getFormDataResponse();
          return Stream.fromReadableStream({
            evaluate: () => response.body,
            onError: cause => this.decodeError(cause)
          });
        }
    }
  }
  get json() {
    return Effect.flatMap(this.text, text => Effect.try({
      try: () => text === "" ? null : JSON.parse(text),
      catch: cause => new HttpClientError.HttpClientError({
        reason: new HttpClientError.DecodeError({
          request: this.request,
          response: this,
          cause
        })
      })
    }));
  }
  get bytes() {
    const body = this.response.body;
    switch (body._tag) {
      case "Empty":
        {
          return Effect.succeed(new Uint8Array(0));
        }
      case "Uint8Array":
        {
          return Effect.succeed(body.body);
        }
      case "Stream":
        {
          return Stream.mkUint8Array(this.stream);
        }
      case "Raw":
        {
          const rawBody = body.body;
          if (rawBody instanceof Response) {
            return Effect.tryPromise({
              try: () => rawBody.arrayBuffer().then(buffer => new Uint8Array(buffer)),
              catch: cause => this.decodeError(cause)
            });
          }
          return Effect.tryPromise({
            try: () => new Response(rawBody).arrayBuffer().then(buffer => new Uint8Array(buffer)),
            catch: cause => this.decodeError(cause)
          });
        }
      case "FormData":
        {
          return Effect.tryPromise({
            try: () => new Response(body.formData).arrayBuffer().then(buffer => new Uint8Array(buffer)),
            catch: cause => this.decodeError(cause)
          });
        }
    }
  }
  get text() {
    return Effect.map(this.bytes, bytes => textDecoder.decode(bytes));
  }
  get urlParamsBody() {
    return Effect.flatMap(this.text, _ => Effect.try({
      try: () => UrlParams.fromInput(new URLSearchParams(_)),
      catch: cause => new HttpClientError.HttpClientError({
        reason: new HttpClientError.DecodeError({
          request: this.request,
          response: this,
          cause
        })
      })
    }));
  }
  get formData() {
    const body = this.response.body;
    if (body._tag === "FormData") {
      return Effect.succeed(body.formData);
    }
    return Effect.contextWith(context => {
      const readableStream = Stream.toReadableStreamWith(this.stream, context);
      return Effect.tryPromise({
        try: () => new Response(readableStream, {
          headers: this.headers
        }).formData(),
        catch: cause => this.decodeError(cause)
      });
    });
  }
  get arrayBuffer() {
    return Effect.map(this.bytes, bytes => bytes.slice().buffer);
  }
  decodeError(cause) {
    return new HttpClientError.HttpClientError({
      reason: new HttpClientError.DecodeError({
        request: this.request,
        response: this,
        cause
      })
    });
  }
  formDataResponse;
  getFormDataResponse() {
    return this.formDataResponse ??= new Response(this.response.body.formData);
  }
  pipe() {
    return pipeArguments(this, arguments);
  }
}
const textDecoder = /*#__PURE__*/new TextDecoder();
/**
 * Converts an `HttpClientResponse` to an `HttpServerResponse`.
 *
 * **Details**
 *
 * The response body is streamed from the client response. `Set-Cookie` headers are
 * removed from the header map and represented in the response cookie collection.
 *
 * @category converting
 * @since 4.0.0
 */
export const fromClientResponse = response => {
  const headers = Headers.remove(response.headers, "set-cookie");
  return makeResponse({
    status: response.status,
    headers,
    cookies: response.cookies,
    body: Body.stream(Stream.catchIf(response.stream, isEmptyBodyError, () => Stream.empty), Option.getOrUndefined(Headers.get(headers, "content-type")), getContentLength(headers))
  });
};
const isReadableStream = u => typeof ReadableStream !== "undefined" && u instanceof ReadableStream;
const isEmptyBodyError = error => HttpClientError.isHttpClientError(error) && error.reason._tag === "EmptyBodyError";
const getContentLength = headers => {
  const contentLength = Option.getOrUndefined(Headers.get(headers, "content-length"));
  if (contentLength === undefined) {
    return undefined;
  }
  const parsed = Number(contentLength);
  return Number.isFinite(parsed) && parsed >= 0 ? parsed : undefined;
};
const Proto = {
  ...PipeInspectableProto,
  [TypeId]: TypeId,
  [ErrorReporter.ignore]: true,
  toJSON() {
    return {
      _id: "HttpServerResponse",
      status: this.status,
      statusText: this.statusText,
      headers: redact(this.headers),
      cookies: this.cookies.toJSON(),
      body: this.body.toJSON()
    };
  }
};
const makeResponse = options => {
  const self = Object.create(Proto);
  self.status = options.status;
  self.statusText = options.statusText;
  self.cookies = options.cookies ?? Cookies.empty;
  self.body = options.body ?? Body.empty;
  if (self.body._tag !== "Empty" && (self.body.contentType || self.body.contentLength)) {
    const newHeaders = Headers.fromRecordUnsafe({
      ...options.headers
    });
    if (self.body.contentType) {
      newHeaders["content-type"] = self.body.contentType;
    }
    if (self.body.contentLength) {
      newHeaders["content-length"] = self.body.contentLength.toString();
    }
    self.headers = newHeaders;
  } else {
    self.headers = options.headers ?? Headers.empty;
  }
  return self;
};
/**
 * Converts a Web `Response` to an `HttpServerResponse`.
 *
 * **Details**
 *
 * `Set-Cookie` headers are parsed into the response cookie collection and removed
 * from the header map. A present Web body is exposed as a stream body.
 *
 * @category converting
 * @since 4.0.0
 */
export const fromWeb = response => {
  const headers = new globalThis.Headers(response.headers);
  const setCookieHeaders = headers.getSetCookie();
  headers.delete("set-cookie");
  let self = empty({
    status: response.status,
    statusText: response.statusText,
    headers: headers,
    cookies: Cookies.fromSetCookie(setCookieHeaders)
  });
  if (response.body) {
    const contentType = response.headers.get("content-type");
    self = setBody(self, Body.stream(Stream.fromReadableStream({
      evaluate: () => response.body,
      onError: e => e
    }), contentType ?? undefined));
  }
  return self;
};
//# sourceMappingURL=HttpServerResponse.js.map