import { SxProps, Theme } from '@mui/material';
import {
  forwardRef,
  useCallback,
  useImperativeHandle,
  useMemo,
  useRef,
  useState,
} from 'react';

import INPUT_TYPES from '../lib/consts/INPUT_TYPES';

import ContainedButton from './ContainedButton';
import FlexBox from './FlexBox';
import Grid from './Grid';
import InputWithRef, { InputForwardedRefContent } from './InputWithRef';
import MessageGroup, { MessageGroupForwardedRefContent } from './MessageGroup';
import OutlinedInputWithLabel from './OutlinedInputWithLabel';
import Spinner from './Spinner';
import {
  buildPeacefulStringTestBatch,
  createTestInputFunction,
} from '../lib/test_input';

const INPUT_ROOT_SX: SxProps<Theme> = { width: '100%' };
const IT_IDS = {
  identifier: 'identifier',
  passphrase: 'passphrase',
};
const MESSAGE_KEY: GateFormMessageKey = {
  accessError: 'accessError',
  identifierInputError: 'identifierInputError',
  passphraseInputError: 'passphraseInputError',
};

const GateForm = forwardRef<GateFormForwardedRefContent, GateFormProps>(
  (
    {
      allowSubmit: isAllowSubmit = true,
      gridProps: {
        columns: gridColumns = { xs: 1, sm: 2 },
        layout,
        spacing: gridSpacing = '1em',
        ...restGridProps
      } = {},
      identifierLabel,
      identifierOutlinedInputWithLabelProps: {
        formControlProps: identifierFormControlProps = {},
        inputProps: identifierInputProps,
        ...restIdentifierOutlinedInputWithLabelProps
      } = {},
      identifierInputTestBatchBuilder: overwriteIdentifierInputTestBatch,
      onIdentifierBlurAppend,
      onSubmit,
      onSubmitAppend,
      passphraseLabel,
      passphraseOutlinedInputWithLabelProps: {
        formControlProps: passphraseFormControlProps = {},
        inputProps: passphraseInputProps,
        ...restPassphraseOutlinedInputWithLabelProps
      } = {},
      submitLabel,
    },
    ref,
  ) => {
    const { sx: identifierSx, ...restIdentifierFormControlProps } =
      identifierFormControlProps;
    const { sx: passphraseSx, ...restPassphraseFormControlProps } =
      passphraseFormControlProps;

    const inputIdentifierRef = useRef<InputForwardedRefContent<'string'>>({});
    const inputPassphraseRef = useRef<InputForwardedRefContent<'string'>>({});
    const messageGroupRef = useRef<MessageGroupForwardedRefContent>({});

    const [isInputIdentifierValid, setIsInputIdentifierValid] =
      useState<boolean>(false);
    const [isInputPassphraseValid, setIsInputPassphraseValid] =
      useState<boolean>(false);
    const [isSubmitting, setIsSubmitting] = useState<boolean>(false);

    const setAccessErrorMessage: GateFormMessageSetter = useCallback(
      (message?) => {
        messageGroupRef.current.setMessage?.call(
          null,
          MESSAGE_KEY.accessError,
          message,
        );
      },
      [],
    );
    const setIdentifierInputErrorMessage: GateFormMessageSetter = useCallback(
      (message?) => {
        messageGroupRef.current.setMessage?.call(
          null,
          MESSAGE_KEY.identifierInputError,
          message,
        );
      },
      [],
    );
    const setPassphraseInputErrorMessage: GateFormMessageSetter = useCallback(
      (message?) => {
        messageGroupRef.current.setMessage?.call(
          null,
          MESSAGE_KEY.passphraseInputError,
          message,
        );
      },
      [],
    );

    const messagesGroupSxDisplay = useMemo(
      () => (isAllowSubmit ? undefined : 'none'),
      [isAllowSubmit],
    );
    const identifierInputTestBatch = useMemo(
      () =>
        overwriteIdentifierInputTestBatch?.call(
          null,
          setIdentifierInputErrorMessage,
          inputIdentifierRef.current,
        ) ??
        buildPeacefulStringTestBatch(
          identifierLabel,
          () => {
            setIdentifierInputErrorMessage();
          },
          { getValue: inputIdentifierRef.current.getValue },
          (message) => {
            setIdentifierInputErrorMessage({
              children: message,
              type: 'warning',
            });
          },
        ),
      [
        identifierLabel,
        overwriteIdentifierInputTestBatch,
        setIdentifierInputErrorMessage,
      ],
    );
    const inputTests: InputTestBatches = useMemo(
      () => ({
        [IT_IDS.identifier]: identifierInputTestBatch,
        [IT_IDS.passphrase]: buildPeacefulStringTestBatch(
          passphraseLabel,
          () => {
            setPassphraseInputErrorMessage();
          },
          { getValue: inputPassphraseRef.current.getValue },
          (message) => {
            setPassphraseInputErrorMessage({
              children: message,
              type: 'warning',
            });
          },
        ),
      }),
      [
        identifierInputTestBatch,
        passphraseLabel,
        setPassphraseInputErrorMessage,
      ],
    );
    const submitHandler: ContainedButtonProps['onClick'] = useMemo(
      () =>
        onSubmit ??
        ((...args) => {
          setAccessErrorMessage();
          setIsSubmitting(true);
          onSubmitAppend?.call(
            null,
            inputIdentifierRef.current,
            inputPassphraseRef.current,
            setAccessErrorMessage,
            setIsSubmitting,
            messageGroupRef.current,
            ...args,
          );
        }),
      [onSubmit, onSubmitAppend, setAccessErrorMessage],
    );
    const submitElement = useMemo(
      () =>
        isSubmitting ? (
          <Spinner mt={0} />
        ) : (
          <FlexBox row sx={{ justifyContent: 'flex-end' }}>
            <ContainedButton
              disabled={!isInputIdentifierValid || !isInputPassphraseValid}
              onClick={submitHandler}
            >
              {submitLabel}
            </ContainedButton>
          </FlexBox>
        ),
      [
        isInputIdentifierValid,
        isInputPassphraseValid,
        isSubmitting,
        submitHandler,
        submitLabel,
      ],
    );
    const submitGrid = useMemo(
      () =>
        isAllowSubmit
          ? {
              children: submitElement,
              sm: 2,
            }
          : undefined,
      [isAllowSubmit, submitElement],
    );

    const testInput = useMemo(
      () => createTestInputFunction(inputTests),
      [inputTests],
    );

    useImperativeHandle(ref, () => ({
      get: () => ({
        identifier: inputIdentifierRef.current.getValue?.call(null) ?? '',
        passphrase: inputPassphraseRef.current.getValue?.call(null) ?? '',
      }),
      messageGroup: {
        ...messageGroupRef.current,
      },
      setIsSubmitting: (value) => {
        setIsSubmitting(value);
      },
    }));

    return (
      <Grid
        columns={gridColumns}
        layout={{
          'credential-identifier': {
            children: (
              <InputWithRef
                input={
                  <OutlinedInputWithLabel
                    formControlProps={{
                      ...restIdentifierFormControlProps,
                      sx: { ...INPUT_ROOT_SX, ...identifierSx },
                    }}
                    id="credential-identifier-input"
                    inputProps={{
                      onBlur: (event) => {
                        const {
                          target: { value },
                        } = event;

                        const valid = testInput({
                          inputs: { [IT_IDS.identifier]: { value } },
                        });
                        setIsInputIdentifierValid(valid);

                        onIdentifierBlurAppend?.call(null, event);
                      },
                      onFocus: () => {
                        setIdentifierInputErrorMessage();
                      },
                      ...identifierInputProps,
                    }}
                    label={identifierLabel}
                    {...restIdentifierOutlinedInputWithLabelProps}
                  />
                }
                ref={inputIdentifierRef}
              />
            ),
          },
          'credential-passphrase': {
            children: (
              <InputWithRef
                input={
                  <OutlinedInputWithLabel
                    formControlProps={{
                      ...restPassphraseFormControlProps,
                      sx: { ...INPUT_ROOT_SX, ...passphraseSx },
                    }}
                    id="credential-passphrase-input"
                    inputProps={{
                      onBlur: ({ target: { value } }) => {
                        const valid = testInput({
                          inputs: { [IT_IDS.passphrase]: { value } },
                        });
                        setIsInputPassphraseValid(valid);
                      },
                      onFocus: () => {
                        setPassphraseInputErrorMessage();
                      },
                      type: INPUT_TYPES.password,
                      ...passphraseInputProps,
                    }}
                    label={passphraseLabel}
                    {...restPassphraseOutlinedInputWithLabelProps}
                  />
                }
                ref={inputPassphraseRef}
              />
            ),
          },
          'credential-message-group': {
            children: <MessageGroup count={1} ref={messageGroupRef} />,
            sm: 2,
            sx: { display: messagesGroupSxDisplay },
          },
          'credential-submit': submitGrid,
        }}
        spacing={gridSpacing}
        {...restGridProps}
      />
    );
  },
);

GateForm.displayName = 'GateForm';

export default GateForm;
