import "./style.pcss";

import type {
  Ref, ChangeEvent, FocusEvent, TextareaHTMLAttributes, FC, ReactElement,
} from "react";
import {
  useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, createRef,
} from "react";
import type {
  IInput,
  IInputStateActions,
  TInputType,
  IResetEventParams,
  IInputRef,
  IInputEventParams,
} from "./config/types";
import type { IComponentBaseProps } from "../../types/components";
import { useCN } from "../../utils/react/useCN";
import { useFormValidation } from "../../utils/react/useFormValidation";
import { useListeners } from "../../utils/react/useListeners";
import { getFieldProps } from "../form/lib/getFieldProps";

/**
 * Поле ввода
 * @returns {ReactElement}
 */
const Input: FC<IComponentBaseProps & IInput> = ({
  baseClass = "formInput",
  name = "",
  id,
  type = "text",
  placeholder,
  title,
  isRequired = false,
  autoComplete = "off",
  maxLength,
  isDisabled = false,
  isChecked = false,
  isReadOnly = false,
  value = "",
  tabIndex,
  inputMode,
  form = null,
  listeners = {},
  onChange,
  onValidate,
  onBlur,
  onFocus,
  onReset,
  getCN,
  onKeyDown,
  defaultValue,
  autoFocus,
  extraClasses = {},
  extraAttrs = {},
  ref,
}): ReactElement => {
  const {
    isCanChecked, isTextArea, isHidden, isFile,
  } = getFieldProps({ type });

  const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement>(null);

  const originalValue = useRef(value);
  const originalChecked = useRef(isChecked);

  const { getCN: getClassName } = useCN(baseClass);

  const [ inputValueState, setInputValueState ] = useState<string>(value);
  const [ inputCheckedState, setInputCheckedState ] = useState<boolean>(isChecked);
  const [ inputDisabledState, setInputDisabledState ] = useState<boolean>(isDisabled);
  const [ inputRequiredState, setInputRequiredState ] = useState<boolean | string>(isRequired);
  const [ inputReadOnlyState, setInputReadOnlyState ] = useState<boolean>(isReadOnly);
  const [ inputPlaceholderState, setInputPlaceholderState ] = useState<string | undefined>(placeholder);
  const [ inputTypeState, setInputTypeState ] = useState<TInputType>(type);

  const setValue = useCallback(
    (value: boolean | string) => {
      isCanChecked ? setInputCheckedState(!!value) : setInputValueState(typeof value === "string" ? value : "");
    }, [ isCanChecked ]
  );

  const stateActions: IInputStateActions = useMemo(
    () => ({
      setValue,
      setInputCheckedState,
      setInputRequiredState,
      setInputValueState,
      setInputDisabledState,
      setInputPlaceholderState,
      setInputTypeState,
      setInputReadOnlyState,
    }), []
  );

  const handleChange = useCallback(
    (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
      if (typeof onChange === "function") {
        onChange({ event, stateActions });
      } else {
        const target = event.target as HTMLInputElement;
        const { checked, value } = target;
        setValue(isCanChecked ? checked : value);
      }
    }, [ onChange, isCanChecked, setValue, stateActions ]
  );

  const handleBlur = useCallback(
    (event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
      onBlur?.({ event, stateActions });
    }, [ onBlur, stateActions ]
  );

  const handleFocus = useCallback(
    (event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
      onFocus?.({ event, stateActions });
    }, [ onFocus, stateActions ]
  );

  const handleReset = useCallback(
    (event: Event) => {
      if (typeof onReset === "function") {
        onReset({
          event,
          stateActions,
          data: {
            original: {
              isChecked: originalChecked.current,
              value: originalValue.current,
            },
          },
        });
      } else {
        setValue(isCanChecked ? originalChecked.current : originalValue.current);
      }
    }, [ onReset, isCanChecked, setValue ]
  );

  const commonProps = useMemo(
    () => {
      return {
        id: id === null ? undefined : (id || name || undefined),
        name: name || undefined,
        placeholder: isCanChecked || isHidden ? undefined : inputPlaceholderState,
        maxLength: isCanChecked || isFile || isHidden ? undefined : maxLength,
        tabIndex: isFile || isHidden ? -1 : tabIndex,
        disabled: inputDisabledState,
        required: isRequired ? inputRequiredState : undefined,
        readOnly: inputReadOnlyState,
        autoComplete: isHidden || isFile || isCanChecked ? undefined : autoComplete,
        "data-js-input-required": isRequired ? inputRequiredState : undefined,
        form: (typeof form === "string" && !!form.length) ? form : undefined,
        title: typeof title === "string" ? title : undefined,
        inputMode,
        autoFocus,
        ...extraAttrs,
      };
    }, [
      id,
      name,
      isCanChecked,
      isHidden,
      isFile,
      inputPlaceholderState,
      maxLength,
      tabIndex,
      inputDisabledState,
      isRequired,
      inputRequiredState,
      inputReadOnlyState,
      autoComplete,
      form,
      title,
      inputMode,
      extraAttrs,
    ]
  );

  const inputSpecificProps = useMemo(
    () => {
      let className: string | undefined;

      if (isHidden || !baseClass) {
        className = undefined;
      } else if (typeof getCN === "function") {
        className = getCN("", extraClasses);
      } else {
        className = getClassName("", extraClasses);
      }

      return {
        onChange: isFile || isHidden ? undefined : handleChange,
        onBlur: isFile || isHidden ? undefined : handleBlur,
        onFocus: isFile || isHidden ? undefined : handleFocus,
        onKeyDown: isFile || isHidden ? undefined : onKeyDown,
        value: defaultValue === undefined ? inputValueState : undefined,
        defaultValue: defaultValue === undefined ? undefined : defaultValue,
        className,
        checked: isCanChecked ? inputCheckedState : undefined,
      };
    }, [
      isFile,
      isHidden,
      handleChange,
      handleBlur,
      handleFocus,
      onKeyDown,
      defaultValue,
      inputValueState,
      baseClass,
      getCN,
      extraClasses,
      getClassName,
      isCanChecked,
      inputCheckedState,
    ]
  );

  useListeners({
    listeners: {
      reset: handleReset,
      ...listeners,
    },
    ref: inputRef,
  });

  useFormValidation(inputRef, onValidate || undefined);

  useEffect(() => {
    setInputPlaceholderState(placeholder);
  }, [ placeholder ]);

  useEffect(() => {
    setInputCheckedState(isChecked);
  }, [ isChecked ]);

  useEffect(() => {
    setInputDisabledState(isDisabled);
  }, [ isDisabled ]);

  useEffect(() => {
    setInputRequiredState(isRequired);
  }, [ isRequired ]);

  useEffect(() => {
    setInputValueState(value);
  }, [ value ]);

  useEffect(() => {
    setInputTypeState(type);
  }, [ type ]);

  useEffect(() => {
    setInputReadOnlyState(isReadOnly);
  }, [ isReadOnly ]);

  useImperativeHandle(
    ref, (): IInputRef => ({
      el: inputRef.current,
      getData: () => ({
        value: inputValueState,
        isChecked: inputCheckedState,
      }),
      ref: createRef(),
      utils: null,
      stateActions,
    }), [ inputValueState, inputCheckedState, stateActions ]
  );

  if (isTextArea) {
    interface ICustomTextareaProps
      extends TextareaHTMLAttributes<HTMLTextAreaElement> {
      isRequired?: string | boolean;
      "data-js-input-required": string | boolean | undefined;
    }

    const textareaProps: ICustomTextareaProps = {
      ...commonProps,
      ...inputSpecificProps,
      required: typeof inputRequiredState === "string"
        ? inputRequiredState === "true"
        : inputRequiredState,
      "data-js-input-required": inputRequiredState
        ? String(inputRequiredState)
        : undefined,
    };

    return (
      <textarea
        {...textareaProps}
        ref={inputRef as Ref<HTMLTextAreaElement>}
      />
    );
  }

  const inputProps = {
    ...commonProps,
    ...inputSpecificProps,
    type: inputTypeState,
    required: Boolean(inputRequiredState),
  };

  return <input {...inputProps} ref={inputRef as Ref<HTMLInputElement>} />;
};

export type {
  IInput,
  IInputEventParams,
  IInputRef,
  IResetEventParams,
};
export { Input };
