// Original: https://github.com/therealparmesh/object-to-formdata/blob/master/src/index.js
// With Multiple Form File Fix: https://github.com/therealparmesh/object-to-formdata/pull/94/files
import {
  isArray,
  isBlob,
  isBoolean,
  isDate,
  isFile,
  isNull,
  isObject,
  isUndefined,
} from './typeChecks';

// TODO: make all of these optional?
export interface ObjectToFormDataCfgProps {
  indices: boolean;
  dotNotation: boolean;
  allowEmptyArrays: boolean;
  noFileListBrackets: boolean;
  nullsAsUndefineds?: boolean;
  booleansAsIntegers?: boolean;
}

// 'pre' is not provided until this function recursively calls itself when 'obj' is an array or object
const serialize = (
  obj: any,
  cfg: ObjectToFormDataCfgProps,
  fd?: FormData,
  pre?: string
) => {
  cfg = cfg || {};

  cfg.indices = isUndefined(cfg.indices) ? false : cfg.indices;

  cfg.nullsAsUndefineds = isUndefined(cfg.nullsAsUndefineds)
    ? false
    : cfg.nullsAsUndefineds;

  cfg.booleansAsIntegers = isUndefined(cfg.booleansAsIntegers)
    ? false
    : cfg.booleansAsIntegers;

  cfg.allowEmptyArrays = isUndefined(cfg.allowEmptyArrays)
    ? false
    : cfg.allowEmptyArrays;

  //reverse of normal because we want a different default
  cfg.dotNotation = isUndefined(cfg.dotNotation) ? true : cfg.dotNotation;

  fd = fd || new FormData();

  if (isUndefined(obj)) {
    return fd;
  } else if (isNull(obj) && !!pre) {
    if (!cfg.nullsAsUndefineds) {
      fd.append(pre, '');
    }
  } else if (isBoolean(obj) && !!pre) {
    if (cfg.booleansAsIntegers) {
      fd.append(pre, obj ? '1' : '0');
    } else {
      fd.append(pre, obj);
    }
  } else if (isDate(obj) && !!pre) {
    fd.append(pre, obj.toISOString());
  } else if (isArray(obj)) {
    if (obj.length) {
      obj.forEach((value: any, index: number) => {
        let key;
        if (cfg.noFileListBrackets && isFile(value)) {
          key = pre;
        } else {
          key = pre + '[' + (cfg.indices ? index : '') + ']';
        }
        serialize(value, cfg, fd, key);
      });
    } else if (cfg.allowEmptyArrays) {
      fd.append(pre + '[]', '');
    }
  } else if (isObject(obj) && !isFile(obj) && !isBlob(obj)) {
    Object.keys(obj).forEach((prop) => {
      const value = obj[prop];

      if (isArray(value)) {
        while (prop.length > 2 && prop.lastIndexOf('[]') === prop.length - 2) {
          prop = prop.substring(0, prop.length - 2);
        }
      }

      const key = pre
        ? cfg.dotNotation
          ? pre + '.' + prop
          : pre + '[' + prop + ']'
        : prop;

      serialize(value, cfg, fd, key);
    });
  } else if (!!pre) {
    fd.append(pre, obj);
  }

  return fd;
};

export default serialize;
