/**
 * Construct a form data with the submitter value.
 * It utilizes the submitter argument on the FormData constructor from modern browsers
 * with fallback to append the submitter value in case it is not unsupported.
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData#parameters
 */
function getFormData(form, submitter) {
  var payload = new FormData(form, submitter);
  if (submitter && submitter.type === 'submit' && submitter.name !== '') {
    var entries = payload.getAll(submitter.name);

    // This assumes the submitter value to be always unique, which should be fine in most cases
    if (!entries.includes(submitter.value)) {
      payload.append(submitter.name, submitter.value);
    }
  }
  return payload;
}

/**
 * Returns the paths from a name based on the JS syntax convention
 * @example
 * ```js
 * const paths = getPaths('todos[0].content'); // ['todos', 0, 'content']
 * ```
 */
function getPaths(name) {
  if (!name) {
    return [];
  }
  return name.split(/\.|(\[\d*\])/).reduce((result, segment) => {
    if (typeof segment !== 'undefined' && segment !== '') {
      if (segment.startsWith('[') && segment.endsWith(']')) {
        var index = segment.slice(1, -1);
        result.push(Number(index));
      } else {
        result.push(segment);
      }
    }
    return result;
  }, []);
}

/**
 * Returns a formatted name from the paths based on the JS syntax convention
 * @example
 * ```js
 * const name = formatPaths(['todos', 0, 'content']); // "todos[0].content"
 * ```
 */
function formatPaths(paths) {
  return paths.reduce((name, path) => {
    if (typeof path === 'number') {
      return "".concat(name, "[").concat(Number.isNaN(path) ? '' : path, "]");
    }
    if (name === '' || path === '') {
      return [name, path].join('');
    }
    return [name, path].join('.');
  }, '');
}

/**
 * Check if a name match the prefix paths
 */
function isPrefix(name, prefix) {
  var paths = getPaths(name);
  var prefixPaths = getPaths(prefix);
  return paths.length >= prefixPaths.length && prefixPaths.every((path, index) => paths[index] === path);
}

/**
 * Assign a value to a target object by following the paths
 */
function setValue(target, name, valueFn) {
  var paths = getPaths(name);
  var length = paths.length;
  var lastIndex = length - 1;
  var index = -1;
  var pointer = target;
  while (pointer != null && ++index < length) {
    var _pointer$key;
    var key = paths[index];
    var nextKey = paths[index + 1];
    var newValue = index != lastIndex ? (_pointer$key = pointer[key]) !== null && _pointer$key !== void 0 ? _pointer$key : typeof nextKey === 'number' ? [] : {} : valueFn(pointer[key]);
    pointer[key] = newValue;
    pointer = pointer[key];
  }
}

/**
 * Retrive the value from a target object by following the paths
 */
function getValue(target, name) {
  var pointer = target;
  for (var path of getPaths(name)) {
    if (typeof pointer === 'undefined' || pointer == null) {
      break;
    }
    if (isPlainObject(pointer) && typeof path === 'string') {
      pointer = pointer[path];
    } else if (Array.isArray(pointer) && typeof path === 'number') {
      pointer = pointer[path];
    } else {
      return;
    }
  }
  return pointer;
}

/**
 * Check if the value is a plain object
 */
function isPlainObject(obj) {
  return !!obj && obj.constructor === Object && Object.getPrototypeOf(obj) === Object.prototype;
}

/**
 * Check if the value is a File
 */
function isFile(obj) {
  // Skip checking if File is not defined
  if (typeof File === 'undefined') {
    return false;
  }
  return obj instanceof File;
}

/**
 * Normalize value by removing empty object or array, empty string and null values
 */

function normalize(value) {
  var acceptFile = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
  if (isPlainObject(value)) {
    var obj = Object.keys(value).sort().reduce((result, key) => {
      var data = normalize(value[key], acceptFile);
      if (typeof data !== 'undefined') {
        result[key] = data;
      }
      return result;
    }, {});
    if (Object.keys(obj).length === 0) {
      return;
    }
    return obj;
  }
  if (Array.isArray(value)) {
    if (value.length === 0) {
      return undefined;
    }
    return value.map(item => normalize(item, acceptFile));
  }
  if (typeof value === 'string' && value === '' || value === null || isFile(value) && (!acceptFile || value.size === 0)) {
    return;
  }
  return value;
}

/**
 * Flatten a tree into a dictionary
 */
function flatten(data, options) {
  var _options$resolve;
  var result = {};
  var resolve = (_options$resolve = options === null || options === void 0 ? void 0 : options.resolve) !== null && _options$resolve !== void 0 ? _options$resolve : data => data;
  function setResult(data, name) {
    var value = normalize(resolve(data));
    if (typeof value !== 'undefined') {
      result[name] = value;
    }
  }
  function processObject(obj, prefix) {
    setResult(obj, prefix);
    for (var [key, _value] of Object.entries(obj)) {
      var name = prefix ? "".concat(prefix, ".").concat(key) : key;
      if (Array.isArray(_value)) {
        processArray(_value, name);
      } else if (_value && isPlainObject(_value)) {
        processObject(_value, name);
      } else {
        setResult(_value, name);
      }
    }
  }
  function processArray(array, prefix) {
    setResult(array, prefix);
    for (var i = 0; i < array.length; i++) {
      var item = array[i];
      var name = "".concat(prefix, "[").concat(i, "]");
      if (Array.isArray(item)) {
        processArray(item, name);
      } else if (item && isPlainObject(item)) {
        processObject(item, name);
      } else {
        setResult(item, name);
      }
    }
  }
  if (data) {
    var _options$prefix;
    var prefix = (_options$prefix = options === null || options === void 0 ? void 0 : options.prefix) !== null && _options$prefix !== void 0 ? _options$prefix : '';
    if (Array.isArray(data)) {
      processArray(data, prefix);
    } else {
      processObject(data, prefix);
    }
  }
  return result;
}

export { flatten, formatPaths, getFormData, getPaths, getValue, isFile, isPlainObject, isPrefix, normalize, setValue };
