"use strict";
((window) => {
  const core = window.Deno.core;
  const webidl = globalThis.__bootstrap.webidl;
  const { Blob, BlobPrototype, File, FilePrototype } =
    globalThis.__bootstrap.file;
  const {
    ArrayPrototypeMap,
    ArrayPrototypePush,
    ArrayPrototypeSlice,
    ArrayPrototypeSplice,
    ArrayPrototypeFilter,
    ArrayPrototypeForEach,
    Map,
    MapPrototypeGet,
    MapPrototypeSet,
    MathRandom,
    ObjectPrototypeIsPrototypeOf,
    Symbol,
    StringFromCharCode,
    StringPrototypeTrim,
    StringPrototypeSlice,
    StringPrototypeSplit,
    StringPrototypeReplace,
    StringPrototypeIndexOf,
    StringPrototypePadStart,
    StringPrototypeCodePointAt,
    StringPrototypeReplaceAll,
    TypeError,
    TypedArrayPrototypeSubarray,
  } = window.__bootstrap.primordials;
  const entryList = Symbol("entry list");
  
  function createEntry(name, value, filename) {
    if (
      ObjectPrototypeIsPrototypeOf(BlobPrototype, value) &&
      !ObjectPrototypeIsPrototypeOf(FilePrototype, value)
    ) {
      value = new File([value], "blob", { type: value.type });
    }
    if (
      ObjectPrototypeIsPrototypeOf(FilePrototype, value) &&
      filename !== undefined
    ) {
      value = new File([value], filename, {
        type: value.type,
        lastModified: value.lastModified,
      });
    }
    return {
      name,
            value,
    };
  }
  
  class FormData {
    
    [entryList] = [];
    
    constructor(form) {
      if (form !== undefined) {
        webidl.illegalConstructor();
      }
      this[webidl.brand] = webidl.brand;
    }
    
    append(name, valueOrBlobValue, filename) {
      webidl.assertBranded(this, FormDataPrototype);
      const prefix = "Failed to execute 'append' on 'FormData'";
      webidl.requiredArguments(arguments.length, 2, { prefix });
      name = webidl.converters["USVString"](name, {
        prefix,
        context: "Argument 1",
      });
      if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) {
        valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, {
          prefix,
          context: "Argument 2",
        });
        if (filename !== undefined) {
          filename = webidl.converters["USVString"](filename, {
            prefix,
            context: "Argument 3",
          });
        }
      } else {
        valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, {
          prefix,
          context: "Argument 2",
        });
      }
      const entry = createEntry(name, valueOrBlobValue, filename);
      ArrayPrototypePush(this[entryList], entry);
    }
    
    delete(name) {
      webidl.assertBranded(this, FormDataPrototype);
      const prefix = "Failed to execute 'name' on 'FormData'";
      webidl.requiredArguments(arguments.length, 1, { prefix });
      name = webidl.converters["USVString"](name, {
        prefix,
        context: "Argument 1",
      });
      const list = this[entryList];
      for (let i = 0; i < list.length; i++) {
        if (list[i].name === name) {
          ArrayPrototypeSplice(list, i, 1);
          i--;
        }
      }
    }
    
    get(name) {
      webidl.assertBranded(this, FormDataPrototype);
      const prefix = "Failed to execute 'get' on 'FormData'";
      webidl.requiredArguments(arguments.length, 1, { prefix });
      name = webidl.converters["USVString"](name, {
        prefix,
        context: "Argument 1",
      });
      for (const entry of this[entryList]) {
        if (entry.name === name) return entry.value;
      }
      return null;
    }
    
    getAll(name) {
      webidl.assertBranded(this, FormDataPrototype);
      const prefix = "Failed to execute 'getAll' on 'FormData'";
      webidl.requiredArguments(arguments.length, 1, { prefix });
      name = webidl.converters["USVString"](name, {
        prefix,
        context: "Argument 1",
      });
      const returnList = [];
      for (const entry of this[entryList]) {
        if (entry.name === name) ArrayPrototypePush(returnList, entry.value);
      }
      return returnList;
    }
    
    has(name) {
      webidl.assertBranded(this, FormDataPrototype);
      const prefix = "Failed to execute 'has' on 'FormData'";
      webidl.requiredArguments(arguments.length, 1, { prefix });
      name = webidl.converters["USVString"](name, {
        prefix,
        context: "Argument 1",
      });
      for (const entry of this[entryList]) {
        if (entry.name === name) return true;
      }
      return false;
    }
    
    set(name, valueOrBlobValue, filename) {
      webidl.assertBranded(this, FormDataPrototype);
      const prefix = "Failed to execute 'set' on 'FormData'";
      webidl.requiredArguments(arguments.length, 2, { prefix });
      name = webidl.converters["USVString"](name, {
        prefix,
        context: "Argument 1",
      });
      if (ObjectPrototypeIsPrototypeOf(BlobPrototype, valueOrBlobValue)) {
        valueOrBlobValue = webidl.converters["Blob"](valueOrBlobValue, {
          prefix,
          context: "Argument 2",
        });
        if (filename !== undefined) {
          filename = webidl.converters["USVString"](filename, {
            prefix,
            context: "Argument 3",
          });
        }
      } else {
        valueOrBlobValue = webidl.converters["USVString"](valueOrBlobValue, {
          prefix,
          context: "Argument 2",
        });
      }
      const entry = createEntry(name, valueOrBlobValue, filename);
      const list = this[entryList];
      let added = false;
      for (let i = 0; i < list.length; i++) {
        if (list[i].name === name) {
          if (!added) {
            list[i] = entry;
            added = true;
          } else {
            ArrayPrototypeSplice(list, i, 1);
            i--;
          }
        }
      }
      if (!added) {
        ArrayPrototypePush(list, entry);
      }
    }
  }
  webidl.mixinPairIterable("FormData", FormData, entryList, "name", "value");
  webidl.configurePrototype(FormData);
  const FormDataPrototype = FormData.prototype;
  const escape = (str, isFilename) =>
    StringPrototypeReplace(
      StringPrototypeReplace(
        StringPrototypeReplace(
          isFilename ? str : StringPrototypeReplace(str, /\r?\n|\r/g, "\r\n"),
          /\n/g,
          "%0A",
        ),
        /\r/g,
        "%0D",
      ),
      /"/g,
      "%22",
    );
  
  function formDataToBlob(formData) {
    const boundary = StringPrototypePadStart(
      StringPrototypeSlice(
        StringPrototypeReplaceAll(`${MathRandom()}${MathRandom()}`, ".", ""),
        -28,
      ),
      32,
      "-",
    );
    const chunks = [];
    const prefix = `--${boundary}\r\nContent-Disposition: form-data; name="`;
    for (const [name, value] of formData) {
      if (typeof value === "string") {
        ArrayPrototypePush(
          chunks,
          prefix + escape(name) + '"' + CRLF + CRLF +
            StringPrototypeReplace(value, /\r(?!\n)|(?<!\r)\n/g, CRLF) + CRLF,
        );
      } else {
        ArrayPrototypePush(
          chunks,
          prefix + escape(name) + `"; filename="${escape(value.name, true)}"` +
            CRLF +
            `Content-Type: ${value.type || "application/octet-stream"}\r\n\r\n`,
          value,
          CRLF,
        );
      }
    }
    ArrayPrototypePush(chunks, `--${boundary}--`);
    return new Blob(chunks, {
      type: "multipart/form-data; boundary=" + boundary,
    });
  }
  
  function parseContentDisposition(value) {
    
    const params = new Map();
        ArrayPrototypeForEach(
      ArrayPrototypeMap(
        ArrayPrototypeFilter(
          ArrayPrototypeMap(
            ArrayPrototypeSlice(StringPrototypeSplit(value, ";"), 1),
            (s) => StringPrototypeSplit(StringPrototypeTrim(s), "="),
          ),
          (arr) => arr.length > 1,
        ),
        ([k, v]) => [k, StringPrototypeReplace(v, /^"([^"]*)"$/, "$1")],
      ),
      ([k, v]) => MapPrototypeSet(params, k, v),
    );
    return params;
  }
  const CRLF = "\r\n";
  const LF = StringPrototypeCodePointAt(CRLF, 1);
  const CR = StringPrototypeCodePointAt(CRLF, 0);
  class MultipartParser {
    
    constructor(body, boundary) {
      if (!boundary) {
        throw new TypeError("multipart/form-data must provide a boundary");
      }
      this.boundary = `--${boundary}`;
      this.body = body;
      this.boundaryChars = core.encode(this.boundary);
    }
    
    #parseHeaders(headersText) {
      const headers = new Headers();
      const rawHeaders = StringPrototypeSplit(headersText, "\r\n");
      for (const rawHeader of rawHeaders) {
        const sepIndex = StringPrototypeIndexOf(rawHeader, ":");
        if (sepIndex < 0) {
          continue;         }
        const key = StringPrototypeSlice(rawHeader, 0, sepIndex);
        const value = StringPrototypeSlice(rawHeader, sepIndex + 1);
        headers.set(key, value);
      }
      const disposition = parseContentDisposition(
        headers.get("Content-Disposition") ?? "",
      );
      return { headers, disposition };
    }
    
    parse() {
            if (this.body.length < (this.boundary.length * 2) + 4) {
        throw new TypeError("Form data too short to be valid.");
      }
      const formData = new FormData();
      let headerText = "";
      let boundaryIndex = 0;
      let state = 0;
      let fileStart = 0;
      for (let i = 0; i < this.body.length; i++) {
        const byte = this.body[i];
        const prevByte = this.body[i - 1];
        const isNewLine = byte === LF && prevByte === CR;
        if (state === 1 || state === 2 || state == 3) {
          headerText += StringFromCharCode(byte);
        }
        if (state === 0 && isNewLine) {
          state = 1;
        } else if (state === 1 && isNewLine) {
          state = 2;
          const headersDone = this.body[i + 1] === CR &&
            this.body[i + 2] === LF;
          if (headersDone) {
            state = 3;
          }
        } else if (state === 2 && isNewLine) {
          state = 3;
        } else if (state === 3 && isNewLine) {
          state = 4;
          fileStart = i + 1;
        } else if (state === 4) {
          if (this.boundaryChars[boundaryIndex] !== byte) {
            boundaryIndex = 0;
          } else {
            boundaryIndex++;
          }
          if (boundaryIndex >= this.boundary.length) {
            const { headers, disposition } = this.#parseHeaders(headerText);
            const content = TypedArrayPrototypeSubarray(
              this.body,
              fileStart,
              i - boundaryIndex - 1,
            );
                        const filename = MapPrototypeGet(disposition, "filename");
            const name = MapPrototypeGet(disposition, "name");
            state = 5;
                        boundaryIndex = 0;
            headerText = "";
            if (!name) {
              continue;             }
            if (filename) {
              const blob = new Blob([content], {
                type: headers.get("Content-Type") || "application/octet-stream",
              });
              formData.append(name, blob, filename);
            } else {
              formData.append(name, core.decode(content));
            }
          }
        } else if (state === 5 && isNewLine) {
          state = 1;
        }
      }
      return formData;
    }
  }
  
  function parseFormData(body, boundary) {
    const parser = new MultipartParser(body, boundary);
    return parser.parse();
  }
  
  function formDataFromEntries(entries) {
    const fd = new FormData();
    fd[entryList] = entries;
    return fd;
  }
  webidl.converters["FormData"] = webidl
    .createInterfaceConverter("FormData", FormDataPrototype);
  globalThis.__bootstrap.formData = {
    FormData,
    FormDataPrototype,
    formDataToBlob,
    parseFormData,
    formDataFromEntries,
  };
})(globalThis);