/* COPYRIGHT Esri - https://js.arcgis.com/5.1/LICENSE.txt */
import { makeGenericController, bypassReadOnly } from "@arcgis/lumina/controllers";
import { uncapitalize, kebabToPascal } from "@arcgis/toolkit/string";
import { r as isCalciteFocusable } from "./dom.js";
import "lit";
const minMaxStepTypes = ["date", "datetime-local", "month", "number", "range", "time", "week"];
const patternTypes = ["email", "password", "search", "tel", "text", "url"];
const minMaxLengthTypes = ["email", "password", "search", "tel", "text", "textarea", "url"];
function updateConstraintValidation(inputComponent, input, propName, matchesType) {
  const attributeName = propName.toLowerCase();
  const value = inputComponent[propName];
  if (matchesType && value != null) {
    input.setAttribute(attributeName, `${value}`);
  } else {
    input.removeAttribute(attributeName);
  }
}
function syncInputDelegate(type, inputComponent, input) {
  input.type = type;
  const isMinMaxStepType = minMaxStepTypes.includes(type);
  const numericInputComponent = inputComponent;
  updateConstraintValidation(numericInputComponent, input, "min", isMinMaxStepType);
  updateConstraintValidation(numericInputComponent, input, "max", isMinMaxStepType);
  updateConstraintValidation(numericInputComponent, input, "step", isMinMaxStepType);
  const isMinMaxLengthType = minMaxLengthTypes.includes(type);
  const textualInputComponent = inputComponent;
  updateConstraintValidation(textualInputComponent, input, "minLength", isMinMaxLengthType);
  updateConstraintValidation(textualInputComponent, input, "maxLength", isMinMaxLengthType);
  const isPatternType = patternTypes.includes(type);
  updateConstraintValidation(textualInputComponent, input, "pattern", isPatternType);
}
function isSupportedType(type) {
  const effectiveType = type === "textarea" ? "text" : type;
  return minMaxStepTypes.includes(effectiveType) || patternTypes.includes(effectiveType) || minMaxLengthTypes.includes(effectiveType);
}
const joinableValueTypes = ["text", "email", "search", "hidden", "tel", "url"];
const allValid = Object.freeze({ validity: {}, validationMessage: "" });
function validate({
  component,
  input,
  value
}) {
  if (!Array.isArray(value)) {
    if (component && input.type === "radio") {
      let group = component.elementInternals.form?.elements[component.name];
      if (group?.length > 0) {
        group = Array.from(group).filter(
          (element) => element.tagName === component.el.tagName
        );
        const isRequired = group.some((radioTypeElement) => radioTypeElement.required);
        const isChecked = group.some((radioTypeElement) => radioTypeElement.checked);
        const others = group.filter((radioTypeElement) => radioTypeElement !== component.el);
        const valueMissing = isRequired && !isChecked;
        input.required = !!valueMissing;
        const validity = getValidityFlags(input.validity);
        const validationMessage = input.validationMessage;
        if (others?.length > 0) {
          others.forEach((other) => {
            if (valueMissing !== other.validity?.valueMissing && other.setValidity) {
              other.setValidity(validity, validationMessage);
            }
          });
        }
      }
    }
    if (validateValue(input, value)) {
      return allValid;
    }
    return {
      validity: getValidityFlags(input.validity),
      validationMessage: input.validationMessage
    };
  }
  if (joinableValueTypes.includes(input.type)) {
    if (validateValue(input, value.join(","))) {
      return allValid;
    }
    return {
      validity: getValidityFlags(input.validity),
      validationMessage: input.validationMessage
    };
  }
  const mergedValidity = {};
  const validationMessages = [];
  for (const item of value) {
    if (validateValue(input, item)) {
      continue;
    }
    Object.assign(mergedValidity, getValidityFlags(input.validity));
    if (input.validationMessage) {
      validationMessages.push(input.validationMessage);
    }
  }
  return {
    validity: mergedValidity,
    validationMessage: validationMessages.join("; ")
  };
}
function validateValue(inputDelegate, valueToValidate) {
  if (inputDelegate.type === "file") {
    inputDelegate.value = "";
    const isMissingValue = !valueToValidate || valueToValidate instanceof FileList && valueToValidate.length === 0;
    return !inputDelegate.required || !isMissingValue;
  }
  inputDelegate.value = valueToValidate == null ? "" : String(valueToValidate);
  return inputDelegate.validity.valid;
}
function getValidityFlags(validityState) {
  const validityFlags = {};
  for (const key in validityState) {
    if (key !== "valid" && validityState[key]) {
      validityFlags[key] = true;
    }
  }
  return validityFlags;
}
const componentsWithInputEvent = [
  "calcite-input",
  "calcite-input-number",
  "calcite-input-text",
  "calcite-text-area"
];
function getClearValidationEventName(componentTag) {
  const componentTagCamelCase = uncapitalize(kebabToPascal(componentTag));
  return `${componentTagCamelCase}${componentsWithInputEvent.includes(componentTag) ? "Input" : "Change"}`;
}
function isFormComponentEl(el) {
  return "form" in el && "name" in el && isCalciteFocusable(el);
}
function displayValidationMessage(component, { status, message, icon }) {
  if ("status" in component) {
    component.status = status;
  }
  if ("validationIcon" in component && typeof component.validationIcon !== "string") {
    component.validationIcon = icon;
  }
  if ("validationMessage" in component && !component.validationMessage) {
    component.validationMessage = message;
  }
}
function clearValidationMessage(component, validationMessage) {
  if ("status" in component) {
    component.status = "idle";
  }
  if ("validationIcon" in component && (!component.validationIcon || component.validationIcon === true)) {
    component.validationIcon = false;
  }
  if ("validationMessage" in component && component.validationMessage === validationMessage) {
    component.validationMessage = "";
  }
}
function syncInternalInput(component, input) {
  const { disabled, name, required } = component;
  input.disabled = disabled;
  input.name = name || "";
  input.required = !!required;
  if (isCheckable(component)) {
    input.checked = component.checked;
  } else if (isInputComponent(component, input)) {
    syncInputDelegate(input.type, component, input);
  }
}
function isCheckable(component) {
  return "checked" in component;
}
function isInputComponent(component, input) {
  return component && isSupportedType(input.type);
}
function focusFirstInvalidFormElement(form) {
  const formElements = Array.from(form.elements);
  requestAnimationFrame(() => {
    const invalidEls = formElements.filter(
      (el) => el.matches("[status=invalid]") && isFormComponentEl(el)
    );
    for (const el of invalidEls) {
      if (el.validationMessage) {
        el.setFocus();
        break;
      }
    }
  });
}
const useForm = (options) => {
  return makeGenericController((component, controller) => {
    let customValidityMessage = "";
    let inputDelegate;
    let lastAssociatedForm = null;
    let effectiveInputType = options.inputType;
    if (effectiveInputType) {
      inputDelegate = document.createElement("input");
    }
    function invalidFormHandler(event) {
      if (event.defaultPrevented) {
        return;
      }
      event.preventDefault();
      const form = event.currentTarget;
      focusFirstInvalidFormElement(form);
    }
    function onFormReset() {
      if ("status" in component) {
        component.status = "idle";
      }
      if ("validationIcon" in component) {
        component.validationIcon = false;
      }
      if ("validationMessage" in component) {
        component.validationMessage = "";
      }
      if (isCheckable(component)) {
        component.checked = component.defaultChecked;
      }
      component.value = component.defaultValue;
    }
    component.listen("luminaFormResetCallback", () => {
      onFormReset();
    });
    component.listen("luminaFormAssociatedCallback", ({ detail: [form] }) => {
      if (form) {
        form.addEventListener("invalid", invalidFormHandler, { capture: true });
      } else {
        lastAssociatedForm?.removeEventListener("invalid", invalidFormHandler, { capture: true });
      }
      lastAssociatedForm = form;
    });
    function handleInvalidInput() {
      const validationMessage = customValidityMessage || inputDelegate?.validationMessage || "";
      displayValidationMessage(component, {
        message: validationMessage,
        icon: true,
        status: "invalid"
      });
      component.el.dispatchEvent(
        // allows users to set custom validation messages
        new CustomEvent("calciteInvalid", { bubbles: true, composed: true })
      );
      const clearValidationEvent = getClearValidationEventName(component.el.tagName.toLowerCase());
      component.listen(
        clearValidationEvent,
        () => {
          clearValidationMessage(component, validationMessage);
          if (inputDelegate?.type === "radio") {
            let group = component.elementInternals.form?.elements[component.name];
            if (group?.length > 0) {
              group = Array.from(group).filter(
                (element) => element.tagName === component.el.tagName
              );
              const others = group.filter((radioTypeElement) => radioTypeElement !== component.el);
              if (others?.length > 0) {
                others.forEach((other) => {
                  clearValidationMessage(other);
                });
              }
            }
          }
        },
        { once: true }
      );
    }
    controller.onConnected(() => {
      component.el.addEventListener("invalid", handleInvalidInput);
    });
    controller.onDisconnected(() => {
      component.el.removeEventListener("invalid", handleInvalidInput);
    });
    controller.onUpdate((changes) => {
      if (!component.hasUpdated) {
        component.defaultValue = component.value;
        if (isCheckable(component)) {
          component.defaultChecked = component.checked;
        }
      }
      if (changes.has("name") || changes.has("value") || isCheckable(component) && changes.has("checked")) {
        component.elementInternals.setFormValue(getFormValue());
      }
      if (component.hasUpdated) {
        updateValidity();
      }
    });
    controller.onLoaded(() => updateValidity());
    function updateValidity() {
      const { disabled, elementInternals } = component;
      let validity = {};
      let validationMessage = "";
      if (!disabled) {
        if (inputDelegate) {
          inputDelegate.type = effectiveInputType;
          syncInternalInput(component, inputDelegate);
          ({ validity, validationMessage } = validate({ component, input: inputDelegate, value: getComponentValue() }));
        }
        if (customValidityMessage) {
          validity = { ...validity, customError: true };
          validationMessage = customValidityMessage;
        }
      }
      elementInternals.setValidity(validity, validationMessage);
      if ("validity" in component) {
        bypassReadOnly(() => {
          component.validity = elementInternals.validity;
        });
      }
    }
    function getComponentValue() {
      if (options.getValue) {
        return options.getValue();
      }
      return component.value;
    }
    function getFormValue() {
      const value = getComponentValue();
      if (Array.isArray(value) || value instanceof FileList) {
        const formData = new FormData();
        for (const item of value) {
          formData.append(component.name, item);
        }
        return formData;
      }
      if (isCheckable(component)) {
        if (component.checked) {
          return value || "on";
        }
        return null;
      }
      return value;
    }
    return {
      get active() {
        return !!component.elementInternals.form;
      },
      overrideDefaultValue: (value) => {
        component.defaultValue = value;
      },
      overrideInputType: (type) => {
        effectiveInputType = type;
        updateValidity();
      },
      requestSubmit: () => {
        component.elementInternals.form?.requestSubmit();
      },
      setCustomValidity: (message) => {
        customValidityMessage = message;
        updateValidity();
      }
    };
  });
};
export {
  clearValidationMessage as c,
  displayValidationMessage as d,
  useForm as u
};
