“use strict”; const idlUtils = require(“../generated/utils”); const isFormElement = require(“../generated/HTMLFormElement”).isImpl; const closest = require(“../helpers/traversal”).closest; const isDisabled = require(“../helpers/form-controls”).isDisabled; const isSubmittable = require(“../helpers/form-controls”).isSubmittable; const isButton = require(“../helpers/form-controls”).isButton; const normalizeToCRLF = require(“../helpers/form-controls”).normalizeToCRLF; const Blob = require(“../generated/Blob.js”); const File = require(“../generated/File.js”); const conversions = require(“webidl-conversions”);

exports.implementation = class FormDataImpl {

constructor(args) {
  this._entries = [];

  if (args[0] !== undefined) {
    if (!isFormElement(args[0])) {
      throw new TypeError("First argument must undefined or a HTMLFormElement");
    }

    this._entries = constructTheFormDataSet(args[0]);
  }
}

append(name, value, filename) {
  // Handling this manually for now: https://github.com/jsdom/webidl2js/issues/29
  if (!Blob.isImpl(value)) {
    value = conversions.USVString(value);
  }

  const entry = createAnEntry(name, value, filename);
  this._entries.push(entry);
}

delete(name) {
  this._entries = this._entries.filter(entry => entry.name !== name);
}

get(name) {
  const foundEntry = this._entries.find(entry => entry.name === name);
  return foundEntry !== undefined ? foundEntry.value : null;
}

getAll(name) {
  return this._entries.filter(entry => entry.name === name).map(entry => entry.value);
}

has(name) {
  return this._entries.findIndex(entry => entry.name === name) !== -1;
}

set(name, value, filename) {
  // Handling this manually for now: https://github.com/jsdom/webidl2js/issues/29
  if (!Blob.isImpl(value)) {
    value = conversions.USVString(value);
  }

  const entry = createAnEntry(name, value, filename);

  const foundIndex = this._entries.findIndex(e => e.name === name);
  if (foundIndex !== -1) {
    this._entries[foundIndex] = entry;
    this._entries = this._entries.filter((e, i) => e.name !== name || i === foundIndex);
  } else {
    this._entries.push(entry);
  }
}

};

function createAnEntry(name, value, filename) {

const entry = { name };

// https://github.com/whatwg/xhr/issues/75

if (Blob.isImpl(value) && !File.isImpl(value)) {
  const oldValue = value;
  value = File.createImpl([
    [],
    "blob",
    { type: oldValue.type }
  ]);
  // "representing the same bytes"
  value._buffer = oldValue._buffer;
}

if (File.isImpl(value) && filename !== undefined) {
  const oldValue = value;
  value = File.createImpl([
    [],
    filename,
    // spec makes no mention of `lastModified`; assume it is inherited
    // (Chrome's behavior)
    { type: oldValue.type, lastModified: oldValue.lastModified }
  ]);
  // "representing the same bytes"
  value._buffer = oldValue._buffer;
}

entry.value = value;

return entry;

}

function constructTheFormDataSet(form, submitter) {

// https://html.spec.whatwg.org/multipage/forms.html#constructing-form-data-set

const controls = Array.prototype.filter.call(form.elements, isSubmittable); // submittable is a subset of listed
const formDataSet = [];

for (const fieldWrapper of controls) {
  const field = idlUtils.implForWrapper(fieldWrapper);

  if (closest(field, "datalist") !== null) {
    continue;
  }
  if (isDisabled(field)) {
    continue;
  }
  if (isButton(field) && field !== submitter) {
    continue;
  }
  if (field.type === "checkbox" && field._checkedness === false) {
    continue;
  }
  if (field.type === "radio" && field._checkedness === false) {
    continue;
  }
  if (field.type !== "image" && (!field.hasAttribute("name") || field.getAttribute("name") === "")) {
    continue;
  }
  if (field.localName === "object") { // in jsdom, no objects are "using a plugin"
    continue;
  }

  const type = field.type;

  // Omit special processing of <input type="image"> since so far we don't actually ever pass submitter

  const nameAttr = field.getAttribute("name");
  const name = nameAttr === null ? "" : nameAttr;

  if (field.localName === "select") {
    for (let i = 0; i < field.options.length; ++i) {
      const option = idlUtils.implForWrapper(field.options[i]);
      if (option._selectedness === true && !isDisabled(field)) {
        formDataSet.push({ name, value: option.value, type });
      }
    }
  } else if (field.localName === "input" && (type === "checkbox" || type === "radio")) {
    const value = field.hasAttribute("value") ? field.getAttribute("value") : "on";
    formDataSet.push({ name, value, type });
  } else if (type === "file") {
    for (let i = 0; i < field.files.length; ++i) {
      formDataSet.push({ name, value: field.files.item(i), type });
    }

    if (field.files.length === 0) {
      formDataSet.push({ name, value: "", type: "application/octet-stream" });
    }
  } /* skip plugins */ else {
    formDataSet.push({ name, value: field._getValue(), type });
  }

  const dirname = field.getAttribute("dirname");
  if (dirname !== null && dirname !== "") {
    const dir = "ltr"; // jsdom does not (yet?) implement actual directionality
    formDataSet.push({ name: dirname, value: dir, type: "direction" });
  }
}

for (const entry of formDataSet) {
  entry.name = conversions.USVString(normalizeToCRLF(entry.name));

  if (entry.type !== "file" && entry.type !== "textarea") {
    entry.value = normalizeToCRLF(entry.value);
  }

  if (entry.type !== "file") {
    entry.value = conversions.USVString(entry.value);
  }
}

return formDataSet;

}