"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 File = require("../file");
const blobSymbols = require("../blob-symbols");
const fileSymbols = require("../file-symbols");
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 (!isBlob(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 (!isBlob(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 (isBlob(value) && !isFile(value)) {
    const oldType = value[blobSymbols.type];
    value = new File([value], "blob");
    value[blobSymbols.type] = oldType;
  }

  if (isFile(value) && filename !== undefined) {
    const oldType = value[blobSymbols.type];
    value = new File([value], filename);
    value[blobSymbols.type] = oldType;
  }

  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;
}

function isBlob(obj) {
  return obj !== null && typeof obj === "object" && blobSymbols.buffer in obj;
}

function isFile(obj) {
  return obj !== null && typeof obj === "object" && fileSymbols.name in obj;
}
