'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var _rollupPluginBabelHelpers = require('./_virtual/_rollupPluginBabelHelpers.js');
var formdata = require('./formdata.js');
var dom = require('./dom.js');
var util = require('./util.js');
var submission = require('./submission.js');

function createFormMeta(options, initialized) {
  var _lastResult$initialVa, _options$constraint, _lastResult$state$val, _lastResult$state, _ref;
  var lastResult = !initialized ? options.lastResult : undefined;
  var defaultValue = options.defaultValue ? submission.serialize(options.defaultValue) : {};
  var initialValue = (_lastResult$initialVa = lastResult === null || lastResult === void 0 ? void 0 : lastResult.initialValue) !== null && _lastResult$initialVa !== void 0 ? _lastResult$initialVa : defaultValue;
  var result = {
    submissionStatus: lastResult === null || lastResult === void 0 ? void 0 : lastResult.status,
    defaultValue,
    initialValue,
    value: initialValue,
    constraint: (_options$constraint = options.constraint) !== null && _options$constraint !== void 0 ? _options$constraint : {},
    validated: (_lastResult$state$val = lastResult === null || lastResult === void 0 || (_lastResult$state = lastResult.state) === null || _lastResult$state === void 0 ? void 0 : _lastResult$state.validated) !== null && _lastResult$state$val !== void 0 ? _lastResult$state$val : {},
    key: !initialized ? getDefaultKey(defaultValue) : _rollupPluginBabelHelpers.objectSpread2({
      '': util.generateId()
    }, getDefaultKey(defaultValue)),
    // The `lastResult` should comes from the server which we won't expect the error to be null
    // We can consider adding a warning if it happens
    error: (_ref = lastResult === null || lastResult === void 0 ? void 0 : lastResult.error) !== null && _ref !== void 0 ? _ref : {}
  };
  handleIntent(result, lastResult === null || lastResult === void 0 ? void 0 : lastResult.intent, lastResult === null || lastResult === void 0 ? void 0 : lastResult.fields);
  return result;
}
function getDefaultKey(defaultValue, prefix) {
  return Object.entries(formdata.flatten(defaultValue, {
    prefix
  })).reduce((result, _ref2) => {
    var [key, value] = _ref2;
    if (Array.isArray(value)) {
      for (var i = 0; i < value.length; i++) {
        result[formdata.formatPaths([...formdata.getPaths(key), i])] = util.generateId();
      }
    }
    return result;
  }, {});
}
function setFieldsValidated(meta, fields) {
  for (var _name of Object.keys(meta.error).concat(fields !== null && fields !== void 0 ? fields : [])) {
    meta.validated[_name] = true;
  }
}
function handleIntent(meta, intent, fields, initialized) {
  if (!intent) {
    setFieldsValidated(meta, fields);
    return;
  }
  switch (intent.type) {
    case 'validate':
      {
        if (intent.payload.name) {
          meta.validated[intent.payload.name] = true;
        } else {
          setFieldsValidated(meta, fields);
        }
        break;
      }
    case 'update':
      {
        var {
          name: _name2,
          validated,
          value
        } = intent.payload;
        if (typeof value !== 'undefined') {
          updateValue(meta, _name2 !== null && _name2 !== void 0 ? _name2 : '', submission.serialize(value));
        }
        if (typeof validated !== 'undefined') {
          // Clean up previous validated state
          if (_name2) {
            submission.setState(meta.validated, _name2, () => undefined);
          } else {
            meta.validated = {};
          }
          if (validated) {
            if (formdata.isPlainObject(value) || Array.isArray(value)) {
              Object.assign(meta.validated, formdata.flatten(value, {
                resolve() {
                  return true;
                },
                prefix: _name2
              }));
            }
            meta.validated[_name2 !== null && _name2 !== void 0 ? _name2 : ''] = true;
          } else if (_name2) {
            delete meta.validated[_name2];
          }
        }
        break;
      }
    case 'reset':
      {
        var _intent$payload$name;
        var _name3 = (_intent$payload$name = intent.payload.name) !== null && _intent$payload$name !== void 0 ? _intent$payload$name : '';
        var _value = formdata.getValue(meta.defaultValue, _name3);
        updateValue(meta, _name3, _value);
        if (_name3) {
          submission.setState(meta.validated, _name3, () => undefined);
          delete meta.validated[_name3];
        } else {
          meta.validated = {};
        }
        break;
      }
    case 'insert':
    case 'remove':
    case 'reorder':
      {
        if (initialized) {
          meta.initialValue = util.clone(meta.initialValue);
          meta.key = util.clone(meta.key);
          submission.setListState(meta.key, intent, util.generateId);
          submission.setListValue(meta.initialValue, intent);
        }
        submission.setListState(meta.validated, intent);
        meta.validated[intent.payload.name] = true;
        break;
      }
  }
  meta.error = Object.entries(meta.error).reduce((result, _ref3) => {
    var [name, error] = _ref3;
    if (meta.validated[name]) {
      result[name] = error;
    }
    return result;
  }, {});
}
function updateValue(meta, name, value) {
  meta.initialValue = util.clone(meta.initialValue);
  meta.value = util.clone(meta.value);
  meta.key = util.clone(meta.key);
  formdata.setValue(meta.initialValue, name, () => value);
  formdata.setValue(meta.value, name, () => value);
  if (formdata.isPlainObject(value) || Array.isArray(value)) {
    submission.setState(meta.key, name, () => undefined);
    Object.assign(meta.key, getDefaultKey(value, name));
  }
  meta.key[name] = util.generateId();
}
function createStateProxy(fn) {
  var cache = {};
  return new Proxy(cache, {
    get(_, name, receiver) {
      var _cache$name;
      if (typeof name !== 'string') {
        return;
      }
      return (_cache$name = cache[name]) !== null && _cache$name !== void 0 ? _cache$name : cache[name] = fn(name, receiver);
    }
  });
}
function createValueProxy(value) {
  var val = formdata.normalize(value);
  return createStateProxy((name, proxy) => {
    if (name === '') {
      return val;
    }
    var paths = formdata.getPaths(name);
    var basename = formdata.formatPaths(paths.slice(0, -1));
    var key = formdata.formatPaths(paths.slice(-1));
    var parentValue = proxy[basename];
    return formdata.getValue(parentValue, key);
  });
}
function createConstraintProxy(constraint) {
  return createStateProxy((name, proxy) => {
    var _result;
    var result = constraint[name];
    if (!result) {
      var paths = formdata.getPaths(name);
      for (var i = paths.length - 1; i >= 0; i--) {
        var path = paths[i];
        if (typeof path === 'number' && !Number.isNaN(path)) {
          paths[i] = Number.NaN;
          break;
        }
      }
      var alternative = formdata.formatPaths(paths);
      if (name !== alternative) {
        result = proxy[alternative];
      }
    }
    return (_result = result) !== null && _result !== void 0 ? _result : {};
  });
}
function createKeyProxy(key) {
  return createStateProxy((name, proxy) => {
    var currentKey = key[name];
    var paths = formdata.getPaths(name);
    if (paths.length === 0) {
      return currentKey;
    }
    var parentKey = proxy[formdata.formatPaths(paths.slice(0, -1))];
    if (typeof parentKey === 'undefined') {
      return currentKey;
    }
    return "".concat(parentKey, "/").concat(currentKey !== null && currentKey !== void 0 ? currentKey : paths.at(-1));
  });
}
function createValidProxy(error) {
  return createStateProxy(name => {
    var keys = Object.keys(error);
    if (name === '') {
      return keys.length === 0;
    }
    for (var key of keys) {
      if (formdata.isPrefix(key, name) && typeof error[key] !== 'undefined') {
        return false;
      }
    }
    return true;
  });
}
function createDirtyProxy(defaultValue, value, shouldDirtyConsider) {
  return createStateProxy(name => JSON.stringify(defaultValue[name]) !== JSON.stringify(value[name], (key, value) => {
    if (name === '' && key === '' && value) {
      return Object.entries(value).reduce((result, _ref4) => {
        var [name, value] = _ref4;
        if (!shouldDirtyConsider(name)) {
          return result;
        }
        return Object.assign(result !== null && result !== void 0 ? result : {}, {
          [name]: value
        });
      }, undefined);
    }
    return value;
  }));
}
function shouldNotify(prev, next, cache, scope) {
  var compareFn = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : (prev, next) => JSON.stringify(prev) !== JSON.stringify(next);
  if (scope && prev !== next) {
    var _scope$prefix, _scope$name;
    var prefixes = (_scope$prefix = scope.prefix) !== null && _scope$prefix !== void 0 ? _scope$prefix : [];
    var names = (_scope$name = scope.name) !== null && _scope$name !== void 0 ? _scope$name : [];
    var list = prefixes.length === 0 ? names : Array.from(new Set([...Object.keys(prev), ...Object.keys(next)]));
    var _loop = function _loop(_name4) {
        if (prefixes.length === 0 || names.includes(_name4) || prefixes.some(prefix => formdata.isPrefix(_name4, prefix))) {
          var _cache$_name;
          (_cache$_name = cache[_name4]) !== null && _cache$_name !== void 0 ? _cache$_name : cache[_name4] = compareFn(prev[_name4], next[_name4]);
          if (cache[_name4]) {
            return {
              v: true
            };
          }
        }
      },
      _ret;
    for (var _name4 of list) {
      _ret = _loop(_name4);
      if (_ret) return _ret.v;
    }
  }
  return false;
}
function createFormContext(options) {
  var subscribers = [];
  var latestOptions = options;
  var meta = createFormMeta(options);
  var state = createFormState(meta);
  function getFormElement() {
    return document.forms.namedItem(latestOptions.formId);
  }
  function createFormState(next) {
    var prev = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : next;
    var state = arguments.length > 2 ? arguments[2] : undefined;
    var defaultValue = !state || prev.defaultValue !== next.defaultValue ? createValueProxy(next.defaultValue) : state.defaultValue;
    var initialValue = next.initialValue === next.defaultValue ? defaultValue : !state || prev.initialValue !== next.initialValue ? createValueProxy(next.initialValue) : state.initialValue;
    var value = next.value === next.initialValue ? initialValue : !state || prev.value !== next.value ? createValueProxy(next.value) : state.value;
    return {
      submissionStatus: next.submissionStatus,
      defaultValue,
      initialValue,
      value,
      error: !state || prev.error !== next.error ? next.error : state.error,
      validated: next.validated,
      constraint: !state || prev.constraint !== next.constraint ? createConstraintProxy(next.constraint) : state.constraint,
      key: !state || prev.key !== next.key ? createKeyProxy(next.key) : state.key,
      valid: !state || prev.error !== next.error ? createValidProxy(next.error) : state.valid,
      dirty: !state || prev.defaultValue !== next.defaultValue || prev.value !== next.value ? createDirtyProxy(defaultValue, value, key => {
        var _latestOptions$should, _latestOptions$should2;
        return (_latestOptions$should = (_latestOptions$should2 = latestOptions.shouldDirtyConsider) === null || _latestOptions$should2 === void 0 ? void 0 : _latestOptions$should2.call(latestOptions, key)) !== null && _latestOptions$should !== void 0 ? _latestOptions$should : true;
      }) : state.dirty
    };
  }
  function updateFormMeta(nextMeta) {
    var prevMeta = meta;
    var prevState = state;
    var nextState = createFormState(nextMeta, prevMeta, prevState);

    // Apply change before notifying subscribers
    meta = nextMeta;
    state = nextState;
    var cache = {
      value: {},
      error: {},
      initialValue: {},
      key: {},
      valid: {},
      dirty: {}
    };
    for (var subscriber of subscribers) {
      var _subscriber$getSubjec;
      var subject = (_subscriber$getSubjec = subscriber.getSubject) === null || _subscriber$getSubjec === void 0 ? void 0 : _subscriber$getSubjec.call(subscriber);
      if (!subject || subject.status && prevState.submissionStatus !== nextState.submissionStatus || shouldNotify(prevState.error, nextState.error, cache.error, subject.error) || shouldNotify(prevState.initialValue, nextState.initialValue, cache.initialValue, subject.initialValue) || shouldNotify(prevState.key, nextState.key, cache.key, subject.key, (prev, next) => prev !== next) || shouldNotify(prevState.valid, nextState.valid, cache.valid, subject.valid, compareBoolean) || shouldNotify(prevState.dirty, nextState.dirty, cache.dirty, subject.dirty, compareBoolean) || shouldNotify(prevState.value, nextState.value, cache.value, subject.value)) {
        subscriber.callback();
      }
    }
  }
  function compareBoolean() {
    var prev = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    var next = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
    return prev !== next;
  }
  function getSerializedState() {
    return JSON.stringify({
      validated: meta.validated
    });
  }
  function submit(event) {
    var form = event.target;
    var submitter = event.submitter;
    util.invariant(form === getFormElement(), "The submit event is dispatched by form#".concat(form.id, " instead of form#").concat(latestOptions.formId));
    var formData = formdata.getFormData(form, submitter);
    var result = {
      formData,
      action: dom.getFormAction(event),
      encType: dom.getFormEncType(event),
      method: dom.getFormMethod(event)
    };
    if (typeof (latestOptions === null || latestOptions === void 0 ? void 0 : latestOptions.onValidate) === 'undefined') {
      return result;
    }
    var submission = latestOptions.onValidate({
      form,
      formData,
      submitter
    });
    if (submission.status !== 'success' && submission.error !== null) {
      report(submission.reply());
    }
    return _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, result), {}, {
      submission
    });
  }
  function resolveTarget(event) {
    var form = getFormElement();
    var element = event.target;
    if (!form || !dom.isFieldElement(element) || element.form !== form || element.name === '') {
      return null;
    }
    return element;
  }
  function willValidate(element, eventName) {
    var {
      shouldValidate = 'onSubmit',
      shouldRevalidate = shouldValidate
    } = latestOptions;
    var validated = meta.validated[element.name];
    return validated ? shouldRevalidate === eventName : shouldValidate === eventName;
  }
  function onInput(event) {
    var element = resolveTarget(event);
    if (!element || !element.form) {
      return;
    }
    if (event.defaultPrevented || !willValidate(element, 'onInput')) {
      var formData = new FormData(element.form);
      var result = submission.getSubmissionContext(formData);
      updateFormMeta(_rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, meta), {}, {
        value: result.payload
      }));
    } else {
      dispatch({
        type: 'validate',
        payload: {
          name: element.name
        }
      });
    }
  }
  function onBlur(event) {
    var element = resolveTarget(event);
    if (!element || event.defaultPrevented || !willValidate(element, 'onBlur')) {
      return;
    }
    dispatch({
      type: 'validate',
      payload: {
        name: element.name
      }
    });
  }
  function onReset(event) {
    var element = getFormElement();
    if (event.type !== 'reset' || event.target !== element || event.defaultPrevented) {
      return;
    }
    updateFormMeta(createFormMeta(latestOptions, true));
  }
  function report(result) {
    var _result$error, _result$state;
    var formElement = getFormElement();
    if (!result.initialValue) {
      formElement === null || formElement === void 0 || formElement.reset();
      return;
    }
    var error = Object.entries((_result$error = result.error) !== null && _result$error !== void 0 ? _result$error : {}).reduce((result, _ref5) => {
      var [name, newError] = _ref5;
      var error = newError === null ? meta.error[name] : newError;
      if (error) {
        result[name] = error;
      }
      return result;
    }, {});
    var update = _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, meta), {}, {
      submissionStatus: result.status,
      value: result.initialValue,
      validated: _rollupPluginBabelHelpers.objectSpread2(_rollupPluginBabelHelpers.objectSpread2({}, meta.validated), (_result$state = result.state) === null || _result$state === void 0 ? void 0 : _result$state.validated),
      error
    });
    handleIntent(update, result.intent, result.fields, true);
    updateFormMeta(update);
    if (formElement && result.status === 'error') {
      for (var element of formElement.elements) {
        if (dom.isFieldElement(element) && meta.error[element.name]) {
          element.focus();
          break;
        }
      }
    }
  }
  function onUpdate(options) {
    var currentFormId = latestOptions.formId;
    var currentResult = latestOptions.lastResult;

    // Merge new options with the latest options
    Object.assign(latestOptions, options);
    if (latestOptions.formId !== currentFormId) {
      var _getFormElement;
      (_getFormElement = getFormElement()) === null || _getFormElement === void 0 || _getFormElement.reset();
    } else if (options.lastResult && options.lastResult !== currentResult) {
      report(options.lastResult);
    }
  }
  function subscribe(callback, getSubject) {
    var subscriber = {
      callback,
      getSubject
    };
    subscribers.push(subscriber);
    return () => {
      subscribers = subscribers.filter(current => current !== subscriber);
    };
  }
  function getState() {
    return state;
  }
  function dispatch(intent) {
    var form = getFormElement();
    var submitter = document.createElement('button');
    var buttonProps = getControlButtonProps(intent);
    submitter.name = buttonProps.name;
    submitter.value = buttonProps.value;
    submitter.hidden = true;
    submitter.formNoValidate = true;
    form === null || form === void 0 || form.appendChild(submitter);
    dom.requestSubmit(form, submitter);
    form === null || form === void 0 || form.removeChild(submitter);
  }
  function getControlButtonProps(intent) {
    return {
      name: submission.INTENT,
      value: submission.serializeIntent(intent),
      form: latestOptions.formId,
      formNoValidate: true
    };
  }
  function createFormControl(type) {
    var _latestOptions$onSche;
    var schedule = (_latestOptions$onSche = latestOptions.onSchedule) !== null && _latestOptions$onSche !== void 0 ? _latestOptions$onSche : callback => callback();
    var control = function control() {
      var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      return schedule(() => dispatch({
        type,
        payload
      }));
    };
    return Object.assign(control, {
      getButtonProps() {
        var payload = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        return getControlButtonProps({
          type,
          payload
        });
      }
    });
  }
  return {
    get formId() {
      return latestOptions.formId;
    },
    submit,
    onReset,
    onInput,
    onBlur,
    onUpdate,
    validate: createFormControl('validate'),
    reset: createFormControl('reset'),
    update: createFormControl('update'),
    insert: createFormControl('insert'),
    remove: createFormControl('remove'),
    reorder: createFormControl('reorder'),
    subscribe,
    getState,
    getSerializedState
  };
}

exports.createFormContext = createFormContext;
