import React from "react";
import PropTypes from "prop-types";
import styling from "@devcake/styling";
import "../init";
import { FormElement } from "../formElement";
import { Label } from "../Label";
import { ErrorMessage } from "../Error";

styling.link("components.input.disabled", "components.form.disabled");
styling.link("components.input.invalid", "components.form.invalid");
styling.setDefault(
  "components.input",
  "shadow-sm block w-full sm:text-sm rounded-md border-0"
);
styling.setDefault("components.input.container", "rounded-md border border-gray-200 focus-within:border-indigo-500 ring-0 focus-within:ring-1 focus-within:ring-indigo-500");
styling.setDefault("components.input.with-inset", "rounded-r-none focus:ring-transparent");
styling.setDefault(
  "components.input.inset",
  "relative flex-grow-0 flex items-center shadow-sm rounded-r-md border-l"
);

export type InputProps = FormElement & {
  inputType?: "email" | "text" | "password" | "tel";
  inset?: React.ReactNode;
};

export const Input = ({
                        className,
                        disabled,
                        label,
                        nullable,
                        onChange,
                        placeholder,
                        inputType,
                        inset,
                        validation,
                        validationMessage,
                        value
                      }: InputProps) => {
  const [innerValue, setInnerValue] = React.useState(value);
  const [error, setError] = React.useState(null);

  const update = React.useMemo(() => {
    let validator = typeof validation === "function" ? validation : value => validation.test(value);

    return (param: string | React.ChangeEvent<HTMLInputElement>) => {
      let newValue = ((typeof param === "string" || !param) ? param : param.target.value) as string;
      if (nullable && newValue == "") newValue = null;
      setInnerValue(newValue);

      let error = validator(newValue);
      if (error === false && validationMessage) error = validationMessage;
      if (error === true) error = null;

      if (error) {
        setError(error);
      } else {
        setError(null);
        if (onChange) onChange(newValue);
      }
    };
  }, [onChange, validation, validationMessage, setInnerValue, setError]);
  // Update inner value when outer value changes
  React.useEffect(() => update(value), [value]);

  let normalizedValue = innerValue;
  if (nullable && normalizedValue === null) normalizedValue = "";

  return (
    <>
      {label && <Label className="mb-1">{label}</Label>}
      <div className={["flex", styling.get("components.input.container")].join(" ")}>
        <input
          type={inputType}
          disabled={disabled}
          className={[
            className,
            disabled ? styling.get("components.input.disabled") : "",
            styling.get("components.input"),
            inset ? styling.get("components.input.with-inset") : "",
            error ? styling.get("components.input.invalid") : ""
          ].join(" ")}
          placeholder={placeholder}
          value={normalizedValue}
          onChange={update}
        />
        {inset && (
          <div className={styling.get("components.input.inset")}>
            {inset}
          </div>
        )}
      </div>
      {error && <ErrorMessage>{error}</ErrorMessage>}
    </>
  );
};

Input.propTypes = {
  label: PropTypes.node,
  nullable: PropTypes.bool,
  value: PropTypes.string,
  disabled: PropTypes.bool,
  onChange: PropTypes.func,
  className: PropTypes.string,
  placeholder: PropTypes.string,
  inputType: PropTypes.oneOf(["text","password", "email", "tel"]),
  inset: PropTypes.node,
  validation: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.instanceOf(RegExp)
  ]),
  validationMessage: PropTypes.string
};

Input.defaultProps = {
  disabled: false,
  inputType: "text",
  nullable: true,
  onChange: () => null,
  validation: () => true,
  validationMessage: ""
};
