'use strict';

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

var react = require('react');

function getFormElement(formId) {
  return document.forms.namedItem(formId);
}
function getFieldElements(form, name) {
  var field = form === null || form === void 0 ? void 0 : form.elements.namedItem(name);
  var elements = !field ? [] : field instanceof Element ? [field] : Array.from(field.values());
  return elements.filter(element => element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement);
}
function getEventTarget(form, name, value) {
  var _elements$;
  var elements = getFieldElements(form, name);
  if (elements.length > 1) {
    var options = typeof value === 'string' ? [value] : value;
    for (var element of elements) {
      if (typeof options !== 'undefined' && element instanceof HTMLInputElement && element.type === 'checkbox' && (element.checked ? options.includes(element.value) : !options.includes(element.value))) {
        continue;
      }
      return element;
    }
  }
  return (_elements$ = elements[0]) !== null && _elements$ !== void 0 ? _elements$ : null;
}
function createDummySelect(form, name, value) {
  var select = document.createElement('select');
  var options = typeof value === 'string' ? [value] : value !== null && value !== void 0 ? value : [];
  select.name = name;
  select.multiple = true;
  select.dataset.conform = 'true';

  // To make sure the input is hidden but still focusable
  select.setAttribute('aria-hidden', 'true');
  select.tabIndex = -1;
  select.style.position = 'absolute';
  select.style.width = '1px';
  select.style.height = '1px';
  select.style.padding = '0';
  select.style.margin = '-1px';
  select.style.overflow = 'hidden';
  select.style.clip = 'rect(0,0,0,0)';
  select.style.whiteSpace = 'nowrap';
  select.style.border = '0';
  for (var option of options) {
    select.options.add(new Option(option, option, true, true));
  }
  form.appendChild(select);
  return select;
}
function isDummySelect(element) {
  return element.dataset.conform === 'true';
}
function updateFieldValue(element, value) {
  if (element instanceof HTMLInputElement && (element.type === 'checkbox' || element.type === 'radio')) {
    element.checked = Array.isArray(value) ? value.includes(element.value) : element.value === value;
  } else if (element instanceof HTMLSelectElement && element.multiple) {
    var selectedValue = Array.isArray(value) ? [...value] : [value];
    for (var option of element.options) {
      var index = selectedValue.indexOf(option.value);
      var selected = index > -1;

      // Update the selected state of the option
      option.selected = selected;
      // Remove the option from the selected array
      if (selected) {
        selectedValue.splice(index, 1);
      }
    }

    // Add the remaining options to the select element only if it's a dummy element managed by conform
    if (isDummySelect(element)) {
      for (var _option of selectedValue) {
        element.options.add(new Option(_option, _option, false, true));
      }
    }
  } else if (element.value !== value) {
    // No `change` event will be triggered on React if `element.value` is already updated

    /**
     * Triggering react custom change event
     * Solution based on dom-testing-library
     * @see https://github.com/facebook/react/issues/10135#issuecomment-401496776
     * @see https://github.com/testing-library/dom-testing-library/blob/main/src/events.js#L104-L123
     */
    var {
      set: valueSetter
    } = Object.getOwnPropertyDescriptor(element, 'value') || {};
    var prototype = Object.getPrototypeOf(element);
    var {
      set: prototypeValueSetter
    } = Object.getOwnPropertyDescriptor(prototype, 'value') || {};
    if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
      prototypeValueSetter.call(element, value);
    } else {
      if (valueSetter) {
        valueSetter.call(element, value);
      } else {
        throw new Error('The given element does not have a value setter');
      }
    }
  }
}
function useInputEvent() {
  var ref = react.useRef(null);
  var eventDispatched = react.useRef({
    change: false,
    focus: false,
    blur: false
  });
  react.useEffect(() => {
    var createEventListener = listener => {
      return event => {
        var element = ref.current;
        if (element && event.target === element) {
          eventDispatched.current[listener] = true;
        }
      };
    };
    var inputHandler = createEventListener('change');
    var focusHandler = createEventListener('focus');
    var blurHandler = createEventListener('blur');
    document.addEventListener('input', inputHandler, true);
    document.addEventListener('focusin', focusHandler, true);
    document.addEventListener('focusout', blurHandler, true);
    return () => {
      document.removeEventListener('input', inputHandler, true);
      document.removeEventListener('focusin', focusHandler, true);
      document.removeEventListener('focusout', blurHandler, true);
    };
  }, [ref]);
  return react.useMemo(() => {
    return {
      change(value) {
        if (!eventDispatched.current.change) {
          eventDispatched.current.change = true;
          var element = ref.current;
          if (element) {
            updateFieldValue(element, value);

            // Dispatch input event with the updated input value
            element.dispatchEvent(new InputEvent('input', {
              bubbles: true
            }));
            // Dispatch change event (necessary for select to update the selected option)
            element.dispatchEvent(new Event('change', {
              bubbles: true
            }));
          }
        }
        eventDispatched.current.change = false;
      },
      focus() {
        if (!eventDispatched.current.focus) {
          eventDispatched.current.focus = true;
          var element = ref.current;
          if (element) {
            element.dispatchEvent(new FocusEvent('focusin', {
              bubbles: true
            }));
            element.dispatchEvent(new FocusEvent('focus'));
          }
        }
        eventDispatched.current.focus = false;
      },
      blur() {
        if (!eventDispatched.current.blur) {
          eventDispatched.current.blur = true;
          var element = ref.current;
          if (element) {
            element.dispatchEvent(new FocusEvent('focusout', {
              bubbles: true
            }));
            element.dispatchEvent(new FocusEvent('blur'));
          }
        }
        eventDispatched.current.blur = false;
      },
      register(element) {
        ref.current = element;
      }
    };
  }, []);
}
function useInputValue(options) {
  var initializeValue = () => {
    var _options$initialValue;
    if (typeof options.initialValue === 'string') {
      // @ts-expect-error FIXME: To ensure that the type of value is also `string | undefined` if initialValue is not an array
      return options.initialValue;
    }

    // @ts-expect-error Same as above
    return (_options$initialValue = options.initialValue) === null || _options$initialValue === void 0 ? void 0 : _options$initialValue.map(value => value !== null && value !== void 0 ? value : '');
  };
  var [key, setKey] = react.useState(options.key);
  var [value, setValue] = react.useState(initializeValue);
  if (key !== options.key) {
    setValue(initializeValue);
    setKey(options.key);
  }
  return [value, setValue];
}
function useControl(meta) {
  var [value, setValue] = useInputValue(meta);
  var {
    register,
    change,
    focus,
    blur
  } = useInputEvent();
  var handleChange = value => {
    setValue(value);
    change(value);
  };
  return {
    register,
    value,
    change: handleChange,
    focus,
    blur
  };
}
function useInputControl(meta) {
  var [value, setValue] = useInputValue(meta);
  var initializedRef = react.useRef(false);
  var {
    register,
    change,
    focus,
    blur
  } = useInputEvent();
  react.useEffect(() => {
    var form = getFormElement(meta.formId);
    if (!form) {
      // eslint-disable-next-line no-console
      console.warn("useInputControl is unable to find form#".concat(meta.formId, " and identify if a dummy input is required"));
      return;
    }
    var element = getEventTarget(form, meta.name);
    if (!element && typeof value !== 'undefined' && (!Array.isArray(value) || value.length > 0)) {
      element = createDummySelect(form, meta.name, value);
    }
    register(element);
    if (!initializedRef.current) {
      initializedRef.current = true;
    } else {
      change(value !== null && value !== void 0 ? value : '');
    }
    return () => {
      register(null);
      var elements = getFieldElements(form, meta.name);
      for (var _element of elements) {
        if (isDummySelect(_element)) {
          _element.remove();
        }
      }
    };
  }, [meta.formId, meta.name, value, change, register]);
  return {
    value,
    change: setValue,
    focus,
    blur
  };
}
function Control(props) {
  var control = useControl(props.meta);
  return props.render(control);
}

exports.Control = Control;
exports.createDummySelect = createDummySelect;
exports.getEventTarget = getEventTarget;
exports.getFieldElements = getFieldElements;
exports.getFormElement = getFormElement;
exports.isDummySelect = isDummySelect;
exports.updateFieldValue = updateFieldValue;
exports.useControl = useControl;
exports.useInputControl = useInputControl;
exports.useInputEvent = useInputEvent;
exports.useInputValue = useInputValue;
