import { FC, useEffect } from 'react';
import { Controller, useFormContext } from 'react-hook-form';

import type { CountryCode } from 'libphonenumber-js';

import Box from '@material-hu/mui/Box';
import Rating from '@material-hu/mui/Rating';
import type { SelectChangeEvent } from '@material-hu/mui/Select';

import { useAuth } from 'src/contexts/JWTContext';
import { useStackStepForms } from 'src/contexts/StackStepFormsContext';
import useRules from 'src/hooks/useRules';
import { useFormFileUpload } from 'src/pages/dashboard/forms/FormFileUploadContext';
import { InputType } from 'src/types/forms';
import {
  buildOptionsToDropDown,
  buildCheckboxGridAnswer,
  getDayMonthYear,
  getMonthDayYear,
  getMonthYear,
  modifyStackStepForms,
  formatDecimalAnswer,
} from 'src/utils/form';
import { getThousandSeparator, getDecimalSeparator } from 'src/utils/form';
import { useLokaliseTranslation } from 'src/utils/i18n';

import FormAutocompleteField from 'src/components/FormInputs/FormAutocompleteField';
import FormSelect from 'src/components/FormInputs/FormSelect';
import FormTextField from 'src/components/FormInputs/FormTextField';

import CheckboxGrid from './inputs/CheckboxGrid';
import DatePickerInput from './inputs/DatePickerInput';
import DigitalSignInput from './inputs/DigitalSignInput';
import FileInput from './inputs/FileInput';
import FormDecimalNumber from './inputs/FormDecimalNumber';
import FormPhoneNumber from './inputs/FormPhoneNumber';
import { InputWrapper, InputWrapperProps } from './inputs/InputWrapper';
import MediaInput from './inputs/MediaInput';
import MultipleCheckBox from './inputs/MultipleCheckbox';
import MultipleRadioButton from './inputs/MultipleRadioButton';
import TimePickerInput from './inputs/TimePickerInput';

const TEXT_MAX_LENGTH = 2048;

export type Input = {
  name?: any;
  required?: any;
  answerInput?: any;
  properties?: {
    defaultCountryCode?: CountryCode;
  };
  formFilled?: any;
  multipleAnswer?: boolean;
  options?: any;
  lang?: string;
  onAddCurrentAmount?: () => void;
};

const getInput = {
  [InputType.MEDIA]: ({ name, required, answerInput, formFilled }: Input) => {
    const { control } = useFormContext();
    type MediaBucket = { images: unknown[]; videos: unknown[] };
    const defaultValue = answerInput?.answer?.attachments?.reduce(
      (acc: MediaBucket, media: { type?: string }) => {
        if (media.type === 'IMAGE') {
          return { ...acc, images: [...acc.images, media] };
        } else {
          return { ...acc, videos: [...acc.videos, media] };
        }
      },
      { images: [], videos: [] },
    );

    const { setUploadingFiles, isUploadingByFieldName } = useFormFileUpload();
    const isUploadingFile = isUploadingByFieldName(name);

    return (
      <Controller
        name={name}
        rules={{ required: required && !formFilled }}
        control={control}
        defaultValue={defaultValue}
        render={({ field: { onChange, value } }) => (
          <MediaInput
            onChange={onChange}
            inputFile={formFilled ? null : value}
            answerInput={formFilled ? answerInput?.answer?.attachments : null}
            disabled={formFilled || isUploadingFile}
            setUploadingFiles={setUploadingFiles}
            loading={isUploadingFile}
            name={name}
          />
        )}
      />
    );
  },
  [InputType.FILE]: ({ name, required, answerInput, formFilled }: Input) => {
    const { control } = useFormContext();
    const defaultValue = answerInput?.answer?.attachments;
    const { setUploadingFiles, isUploadingByFieldName } = useFormFileUpload();
    const isUploadingFile = isUploadingByFieldName(name);

    return (
      <Controller
        name={name}
        rules={{ required: required && !formFilled }}
        control={control}
        defaultValue={defaultValue}
        render={({ field: { onChange, value } }) => (
          <FileInput
            onChange={onChange}
            inputFile={formFilled ? null : value}
            answerInput={formFilled ? defaultValue : null}
            disabled={formFilled || isUploadingFile}
            setUploadingFiles={setUploadingFiles}
            loading={isUploadingFile}
            name={name}
          />
        )}
      />
    );
  },
  [InputType.DIGITAL_SIGN]: ({
    name,
    required,
    answerInput,
    formFilled,
  }: Input) => {
    const { control } = useFormContext();
    const firstSign = answerInput?.answer?.attachments[0];
    const defaultValue = firstSign
      ? { answer: [{ url: firstSign.url, type: firstSign.type }] }
      : null;

    return (
      <Controller
        name={name}
        rules={{ required: required && !formFilled }}
        control={control}
        defaultValue={defaultValue}
        render={({ field: { onChange, value } }) => (
          <DigitalSignInput
            onChange={onChange}
            inputDigitalSign={value}
            answerInput={answerInput?.answer?.attachments}
            disabled={formFilled}
          />
        )}
      />
    );
  },
  [InputType.RATE]: ({ name, required, answerInput, formFilled }: Input) => {
    const { control } = useFormContext();
    return (
      <Controller
        control={control}
        name={name}
        rules={{ required: required && !formFilled }}
        defaultValue={answerInput?.answer}
        render={({ field: { onChange, value } }) => (
          <Box sx={{ textAlign: 'center' }}>
            <Rating
              name={name}
              value={value}
              disabled={formFilled}
              onChange={(_, newValue) => {
                onChange(newValue?.toString());
              }}
            />
          </Box>
        )}
      />
    );
  },
  [InputType.STAR_RATING]: ({
    name,
    required,
    answerInput,
    formFilled,
  }: Input) => {
    const { control } = useFormContext();
    return (
      <Controller
        control={control}
        name={name}
        rules={{ required: required && !formFilled }}
        defaultValue={answerInput?.answer}
        render={({ field: { onChange, value } }) => (
          <Box sx={{ textAlign: 'center' }}>
            <Rating
              name={name}
              value={value}
              disabled={formFilled}
              onChange={(_, newValue) => {
                onChange(newValue?.toString());
              }}
            />
          </Box>
        )}
      />
    );
  },
  [InputType.DATE_MMAAAA]: ({
    name,
    required,
    answerInput,
    formFilled,
  }: Input) => {
    const { getValues } = useFormContext();

    const defaultValue = answerInput?.answer
      ? getMonthYear(answerInput?.answer)
      : null;

    return (
      <Controller
        name={name}
        rules={{
          required: required && !formFilled,
          validate: (): boolean => {
            const v = getValues(name);
            if (!v) return true;
            return v instanceof Date && !Number.isNaN(v.valueOf());
          },
        }}
        defaultValue={defaultValue}
        render={({ field: { value, onChange, ...rest } }) => (
          <DatePickerInput
            disabled={formFilled}
            value={value}
            views={['month', 'year']}
            onChange={onChange}
            {...rest}
          />
        )}
      />
    );
  },
  [InputType.DATE]: ({ name, required, answerInput, formFilled }: Input) => {
    const validDateRules = (isRequired: boolean) =>
      useRules({
        required: isRequired,
        checkValidFormatDate: true,
      });

    const defaultValue = answerInput?.answer
      ? getDayMonthYear(answerInput?.answer)
      : null;

    return (
      <Controller
        name={name}
        rules={validDateRules(required && !formFilled)}
        defaultValue={defaultValue}
        render={({ field: { value, onChange, ...rest } }) => (
          <DatePickerInput
            disabled={formFilled}
            value={value}
            onChange={onChange}
            {...rest}
          />
        )}
      />
    );
  },
  [InputType.DATE_MMDDYYYY]: ({
    name,
    required,
    answerInput,
    formFilled,
  }: Input) => {
    const validDateRules = (isRequired: boolean) =>
      useRules({
        required: isRequired,
        checkValidFormatDate: true,
      });

    const defaultValue = answerInput?.answer
      ? getMonthDayYear(answerInput?.answer)
      : null;

    return (
      <Controller
        name={name}
        rules={validDateRules(required && !formFilled)}
        defaultValue={defaultValue}
        render={({ field: { value, onChange, ...rest } }) => (
          <DatePickerInput
            disabled={formFilled}
            value={value}
            onChange={onChange}
            inputType={InputType.DATE_MMDDYYYY}
            inputFormat="MM/dd/yyyy"
            {...rest}
          />
        )}
      />
    );
  },
  [InputType.TIME]: ({ name, required, answerInput, formFilled }: Input) => {
    const { getValues } = useFormContext();

    const defaultValue = answerInput?.answer
      ? new Date(`1970-01-01 ${answerInput?.answer}`)
      : null;

    return (
      <Controller
        name={name}
        rules={{
          required: required && !formFilled,
          validate: (): boolean => {
            const v = getValues(name);
            if (!v) return true;
            return v instanceof Date && !Number.isNaN(v.valueOf());
          },
        }}
        defaultValue={defaultValue}
        render={({ field: { value, onChange, ...rest } }) => (
          <TimePickerInput
            value={value}
            disabled={formFilled}
            onChange={onChange}
            {...rest}
          />
        )}
      />
    );
  },
  [InputType.TEXT]: ({
    name,
    required,
    answerInput,
    formFilled,
    ...props
  }: Input) => (
    <FormTextField
      name={name}
      rules={{ required: required && !formFilled }}
      defaultValue={answerInput?.answer}
      disabled={formFilled}
      max={TEXT_MAX_LENGTH}
      {...props}
    />
  ),
  [InputType.AUTOCOMPLETE]: ({
    name,
    answerInput,
    formFilled,
    ...props
  }: Input) => (
    <FormAutocompleteField
      name={name}
      {...props}
    />
  ),
  [InputType.PHONE]: ({
    name,
    required,
    answerInput,
    formFilled,
    properties,
    ...props
  }: Input) => (
    <FormPhoneNumber
      name={name}
      defaultCountry={properties?.defaultCountryCode}
      codeName="phoneCode"
      defaultValue={answerInput?.answer}
      disabled={formFilled}
      rules={{
        phoneNumber: true,
        requiredPhoneNumber: required && !formFilled,
      }}
      inputProps={{
        maxLength: 16,
      }}
      {...props}
    />
  ),
  [InputType.INTEGER]: ({
    name,
    required,
    answerInput,
    formFilled,
    onAddCurrentAmount,
    ...props
  }: Input) => {
    const formatNumberRules = (isRequired: boolean) =>
      useRules({
        required: isRequired,
        numberFormat: true,
      });

    return (
      <FormTextField
        name={name}
        rules={formatNumberRules(required && !formFilled)}
        defaultValue={answerInput?.answer}
        disabled={formFilled}
        {...(onAddCurrentAmount && { onAddCurrentAmount })}
        {...props}
      />
    );
  },
  [InputType.DECIMAL]: ({
    name,
    required,
    answerInput,
    formFilled,
    lang = 'en',
  }: Input) => {
    const formatNumberRules = (isRequired: boolean) =>
      useRules({
        required: isRequired,
        decimalFormat: true,
      });

    const formatAnswer = formatDecimalAnswer(answerInput?.answer, lang);
    const thousandSeparator = getThousandSeparator(lang);
    const decimalSeparator = getDecimalSeparator(lang);

    return (
      <Controller
        name={name}
        defaultValue={formatAnswer}
        rules={formatNumberRules(required && !formFilled)}
        render={({ field, fieldState: { error } }) => (
          <FormDecimalNumber
            {...field}
            thousandSeparator={thousandSeparator}
            decimalSeparator={decimalSeparator}
            disabled={formFilled}
            error={error}
            variant="outlined"
            defaultValue={formatAnswer}
            lang={lang ?? ''}
          />
        )}
      />
    );
  },
  [InputType.DROPDOWN]: ({
    name,
    options,
    required,
    answerInput,
    formFilled,
    ...props
  }: Input) => {
    const { getValues } = useFormContext();
    const { stackStepForms, hasBranching, stepsList } = useStackStepForms();

    useEffect(() => {
      const valueInput = getValues(name);
      if (hasBranching && valueInput) {
        modifyStackStepForms(options, valueInput, stackStepForms, stepsList);
      }
    }, []);

    const handleChangeSkipStep = (e: SelectChangeEvent<unknown>) => {
      if (!hasBranching) return;
      modifyStackStepForms(options, e.target.value, stackStepForms, stepsList);
    };

    const answer = answerInput?.answer?.description;

    const isValidOption = options.some(
      (opt: { description?: string }) => opt.description === answer,
    );

    return (
      <FormSelect
        name={name}
        options={buildOptionsToDropDown(options)}
        rules={{ required: required && !formFilled }}
        defaultValue={isValidOption ? answer : null}
        disabled={formFilled}
        onChangeSkipStep={handleChangeSkipStep}
        {...props}
      />
    );
  },
  [InputType.CHECKBOX]: ({ multipleAnswer, name, ...props }: Input) =>
    multipleAnswer ? (
      <MultipleCheckBox
        name={name}
        {...props}
      />
    ) : (
      <MultipleRadioButton
        name={name}
        {...props}
      />
    ),
  [InputType.CHECKBOX_GRID]: ({
    name,
    answerInput,
    required,
    formFilled,
    ...props
  }: Input) => (
    <CheckboxGrid
      name={name}
      defaultValue={buildCheckboxGridAnswer(answerInput)}
      disabled={formFilled}
      rules={{ requiredCheckboxGrid: required && !formFilled }}
      {...props}
    />
  ),
};

export type GetFormInputComponentProps = Required<
  Pick<InputWrapperProps, 'attachments' | 'name'>
> & {
  inputType: InputType;
  required: boolean;
  answerInput?: any;
  formFilled?: boolean;
  multipleAnswer?: boolean;
  options?: any;
  label?: string;
  multiline?: boolean;
  InputProps?: any;
  onAddCurrentAmount?: () => void;
};

export const GetFormInputComponent: FC<GetFormInputComponentProps> = props => {
  const { inputType, name, attachments, onAddCurrentAmount } = props;
  const { user } = useAuth();

  const lang = user?.language ?? 'en';

  const {
    formState: { errors },
  } = useFormContext();

  const { t } = useLokaliseTranslation('forms');

  if (inputType === InputType.INFO) {
    return <InputWrapper attachments={attachments} />;
  }

  const inputKey = inputType as keyof typeof getInput;

  if (!(inputType && getInput[inputKey])) {
    return null;
  }

  const RenderInput = getInput[inputKey];

  return (
    <InputWrapper
      attachments={attachments}
      errors={errors}
      name={name}
      inputType={inputType}
      infoMessage={t('NUMBER_INPUT_REQUIREMENTS')}
    >
      {RenderInput({ ...props, lang, onAddCurrentAmount })}
    </InputWrapper>
  );
};

export default GetFormInputComponent;
