import { core, primordials } from "ext:core/mod.js";
const {
  isAnyArrayBuffer,
  isArrayBuffer,
} = core;
const {
  ArrayBufferIsView,
  ArrayPrototypeMap,
  DataViewPrototypeGetBuffer,
  DataViewPrototypeGetByteLength,
  DataViewPrototypeGetByteOffset,
  JSONParse,
  ObjectDefineProperties,
  ObjectPrototypeIsPrototypeOf,
  TypedArrayPrototypeGetBuffer,
  TypedArrayPrototypeGetByteLength,
  TypedArrayPrototypeGetByteOffset,
  TypedArrayPrototypeGetSymbolToStringTag,
  TypedArrayPrototypeSlice,
  TypeError,
  Uint8Array,
} = primordials;
import * as webidl from "ext:deno_webidl/00_webidl.js";
import {
  parseUrlEncoded,
  URLSearchParamsPrototype,
} from "ext:deno_url/00_url.js";
import {
  formDataFromEntries,
  FormDataPrototype,
  formDataToBlob,
  parseFormData,
} from "ext:deno_fetch/21_formdata.js";
import * as mimesniff from "ext:deno_web/01_mimesniff.js";
import { BlobPrototype } from "ext:deno_web/09_file.js";
import {
  createProxy,
  errorReadableStream,
  isReadableStreamDisturbed,
  readableStreamClose,
  readableStreamCollectIntoUint8Array,
  readableStreamDisturb,
  ReadableStreamPrototype,
  readableStreamTee,
  readableStreamThrowIfErrored,
} from "ext:deno_web/06_streams.js";
function chunkToU8(chunk) {
  return typeof chunk === "string" ? core.encode(chunk) : chunk;
}
function chunkToString(chunk) {
  return typeof chunk === "string" ? chunk : core.decode(chunk);
}
class InnerBody {
  
  constructor(stream) {
    
    this.streamOrStatic = stream ??
      { body: new Uint8Array(), consumed: false };
    
    this.source = null;
    
    this.length = null;
  }
  get stream() {
    if (
      !ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      const { body, consumed } = this.streamOrStatic;
      if (consumed) {
        this.streamOrStatic = new ReadableStream();
        this.streamOrStatic.getReader();
        readableStreamDisturb(this.streamOrStatic);
        readableStreamClose(this.streamOrStatic);
      } else {
        this.streamOrStatic = new ReadableStream({
          start(controller) {
            controller.enqueue(chunkToU8(body));
            controller.close();
          },
        });
      }
    }
    return this.streamOrStatic;
  }
  
  unusable() {
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      return this.streamOrStatic.locked ||
        isReadableStreamDisturbed(this.streamOrStatic);
    }
    return this.streamOrStatic.consumed;
  }
  
  consumed() {
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      return isReadableStreamDisturbed(this.streamOrStatic);
    }
    return this.streamOrStatic.consumed;
  }
  
  consume() {
    if (this.unusable()) throw new TypeError("Body already consumed.");
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      readableStreamThrowIfErrored(this.stream);
      return readableStreamCollectIntoUint8Array(this.stream);
    } else {
      this.streamOrStatic.consumed = true;
      return this.streamOrStatic.body;
    }
  }
  cancel(error) {
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      this.streamOrStatic.cancel(error);
    } else {
      this.streamOrStatic.consumed = true;
    }
  }
  error(error) {
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      errorReadableStream(this.streamOrStatic, error);
    } else {
      this.streamOrStatic.consumed = true;
    }
  }
  
  clone() {
    const { 0: out1, 1: out2 } = readableStreamTee(this.stream, true);
    this.streamOrStatic = out1;
    const second = new InnerBody(out2);
    second.source = core.deserialize(core.serialize(this.source));
    second.length = this.length;
    return second;
  }
  
  createProxy() {
    let proxyStreamOrStatic;
    if (
      ObjectPrototypeIsPrototypeOf(
        ReadableStreamPrototype,
        this.streamOrStatic,
      )
    ) {
      proxyStreamOrStatic = createProxy(this.streamOrStatic);
    } else {
      proxyStreamOrStatic = { ...this.streamOrStatic };
      this.streamOrStatic.consumed = true;
    }
    const proxy = new InnerBody(proxyStreamOrStatic);
    proxy.source = this.source;
    proxy.length = this.length;
    return proxy;
  }
}
function mixinBody(prototype, bodySymbol, mimeTypeSymbol) {
  async function consumeBody(object, type) {
    webidl.assertBranded(object, prototype);
    const body = object[bodySymbol] !== null
      ? await object[bodySymbol].consume()
      : new Uint8Array();
    const mimeType = type === "Blob" || type === "FormData"
      ? object[mimeTypeSymbol]
      : null;
    return packageData(body, type, mimeType);
  }
  
  const mixin = {
    body: {
      
      get() {
        webidl.assertBranded(this, prototype);
        if (this[bodySymbol] === null) {
          return null;
        } else {
          return this[bodySymbol].stream;
        }
      },
      configurable: true,
      enumerable: true,
    },
    bodyUsed: {
      
      get() {
        webidl.assertBranded(this, prototype);
        if (this[bodySymbol] !== null) {
          return this[bodySymbol].consumed();
        }
        return false;
      },
      configurable: true,
      enumerable: true,
    },
    arrayBuffer: {
      
      value: function arrayBuffer() {
        return consumeBody(this, "ArrayBuffer");
      },
      writable: true,
      configurable: true,
      enumerable: true,
    },
    blob: {
      
      value: function blob() {
        return consumeBody(this, "Blob");
      },
      writable: true,
      configurable: true,
      enumerable: true,
    },
    formData: {
      
      value: function formData() {
        return consumeBody(this, "FormData");
      },
      writable: true,
      configurable: true,
      enumerable: true,
    },
    json: {
      
      value: function json() {
        return consumeBody(this, "JSON");
      },
      writable: true,
      configurable: true,
      enumerable: true,
    },
    text: {
      
      value: function text() {
        return consumeBody(this, "text");
      },
      writable: true,
      configurable: true,
      enumerable: true,
    },
  };
  return ObjectDefineProperties(prototype, mixin);
}
function packageData(bytes, type, mimeType) {
  switch (type) {
    case "ArrayBuffer":
      return TypedArrayPrototypeGetBuffer(chunkToU8(bytes));
    case "Blob":
      return new Blob([bytes], {
        type: mimeType !== null ? mimesniff.serializeMimeType(mimeType) : "",
      });
    case "FormData": {
      if (mimeType !== null) {
        const essence = mimesniff.essence(mimeType);
        if (essence === "multipart/form-data") {
          const boundary = mimeType.parameters.get("boundary");
          if (boundary === null) {
            throw new TypeError(
              "Missing boundary parameter in mime type of multipart formdata.",
            );
          }
          return parseFormData(chunkToU8(bytes), boundary);
        } else if (essence === "application/x-www-form-urlencoded") {
                    const entries = parseUrlEncoded(chunkToU8(bytes));
          return formDataFromEntries(
            ArrayPrototypeMap(
              entries,
              (x) => ({ name: x[0], value: x[1] }),
            ),
          );
        }
        throw new TypeError("Body can not be decoded as form data");
      }
      throw new TypeError("Missing content type");
    }
    case "JSON":
      return JSONParse(chunkToString(bytes));
    case "text":
      return chunkToString(bytes);
  }
}
function extractBody(object) {
  
  let stream;
  let source = null;
  let length = null;
  let contentType = null;
  if (typeof object === "string") {
    source = object;
    contentType = "text/plain;charset=UTF-8";
  } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, object)) {
    stream = object.stream();
    source = object;
    length = object.size;
    if (object.type.length !== 0) {
      contentType = object.type;
    }
  } else if (ArrayBufferIsView(object)) {
    const tag = TypedArrayPrototypeGetSymbolToStringTag(object);
    if (tag !== undefined) {
            if (tag !== "Uint8Array") {
                object = new Uint8Array(
          TypedArrayPrototypeGetBuffer( (object)),
          TypedArrayPrototypeGetByteOffset( (object)),
          TypedArrayPrototypeGetByteLength( (object)),
        );
      }
    } else {
            object = new Uint8Array(
        DataViewPrototypeGetBuffer( (object)),
        DataViewPrototypeGetByteOffset( (object)),
        DataViewPrototypeGetByteLength( (object)),
      );
    }
    source = TypedArrayPrototypeSlice(object);
  } else if (isArrayBuffer(object)) {
    source = TypedArrayPrototypeSlice(new Uint8Array(object));
  } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, object)) {
    const res = formDataToBlob(object);
    stream = res.stream();
    source = res;
    length = res.size;
    contentType = res.type;
  } else if (
    ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, object)
  ) {
            source = object.toString();
    contentType = "application/x-www-form-urlencoded;charset=UTF-8";
  } else if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, object)) {
    stream = object;
    if (object.locked || isReadableStreamDisturbed(object)) {
      throw new TypeError("ReadableStream is locked or disturbed");
    }
  }
  if (typeof source === "string") {
                stream = { body: source, consumed: false };
    length = null;   } else if (TypedArrayPrototypeGetSymbolToStringTag(source) === "Uint8Array") {
    stream = { body: source, consumed: false };
    length = TypedArrayPrototypeGetByteLength(source);
  }
  const body = new InnerBody(stream);
  body.source = source;
  body.length = length;
  return { body, contentType };
}
webidl.converters["BodyInit_DOMString"] = (V, prefix, context, opts) => {
    if (ObjectPrototypeIsPrototypeOf(ReadableStreamPrototype, V)) {
    return webidl.converters["ReadableStream"](V, prefix, context, opts);
  } else if (ObjectPrototypeIsPrototypeOf(BlobPrototype, V)) {
    return webidl.converters["Blob"](V, prefix, context, opts);
  } else if (ObjectPrototypeIsPrototypeOf(FormDataPrototype, V)) {
    return webidl.converters["FormData"](V, prefix, context, opts);
  } else if (ObjectPrototypeIsPrototypeOf(URLSearchParamsPrototype, V)) {
    return webidl.converters["URLSearchParams"](V, prefix, context, opts);
  }
  if (typeof V === "object") {
    if (isAnyArrayBuffer(V)) {
      return webidl.converters["ArrayBuffer"](V, prefix, context, opts);
    }
    if (ArrayBufferIsView(V)) {
      return webidl.converters["ArrayBufferView"](V, prefix, context, opts);
    }
  }
        return webidl.converters["DOMString"](V, prefix, context, opts);
};
webidl.converters["BodyInit_DOMString?"] = webidl.createNullableConverter(
  webidl.converters["BodyInit_DOMString"],
);
export { extractBody, InnerBody, mixinBody };