import { DragEventHandler, useCallback, useMemo, useState } from 'react';

import { useTranslation } from '../../../../core/hooks/useTranslation';
import { useTestIdAttribute } from '../../../../hooks/useTestIdAttribute';
import { TestIdProps } from '../../../../types';
import { assertEmptyObject } from '../../../../utils/assertEmptyObject';
import { assertUnreachable } from '../../../../utils/assertUnreachable';
import { makeTestId } from '../../../../utils/makeTestId';
import { FileUploadInputErrorCode } from '../../constants';
import { useFileUploadContext } from '../../contexts/FileUploadContext';

import { ActiveMode } from './components/ActiveMode/ActiveMode';
import { DefaultMode } from './components/DefaultMode/DefaultMode';
import { ErrorMode } from './components/ErrorMode/ErrorMode';
import { Mode } from './constants';
import { StyledDropArea, StyledDropElement } from './styled';

export interface DropAreaProps extends TestIdProps {}

export function DropArea(props: DropAreaProps) {
  const { testId, ...rest } = props;
  assertEmptyObject(rest);

  const { t } = useTranslation();
  const testIdAttribute = useTestIdAttribute();

  const [mode, setMode] = useState<Mode>(Mode.Default);

  const { disabled, multiple, mimeTypes, onSelect, onInputError } = useFileUploadContext();

  const checkFiles = useCallback(
    (files: File[]) => {
      if (!multiple && files.length > 1) {
        onInputError?.(FileUploadInputErrorCode.TooManyFiles, files);
        return false;
      }

      if (!files.every((file) => mimeTypes.includes(file.type))) {
        onInputError?.(FileUploadInputErrorCode.WrongFileFormat, files);
        return false;
      }

      return true;
    },
    [onInputError, mimeTypes, multiple],
  );

  const handleSelect = useCallback(
    (files: File[]) => {
      if (checkFiles(files)) {
        onSelect(files);
      }
    },
    [checkFiles, onSelect],
  );

  const handleDragLeave: DragEventHandler = useCallback(() => {
    setMode(Mode.Default);
  }, []);

  const handleDragEnter: DragEventHandler = useCallback(
    (e) => {
      if (disabled) {
        return;
      }
      if (e.dataTransfer.items.length > 1 && !multiple) {
        setMode(Mode.TooManyFiles);
      } else if (Array.from(e.dataTransfer.items).some((item) => !mimeTypes.includes(item.type))) {
        setMode(Mode.WrongFileFormat);
      } else {
        setMode(Mode.Active);
      }
    },
    [disabled, mimeTypes, multiple],
  );

  const handleDragOver: DragEventHandler = useCallback((e) => {
    e.preventDefault();
    e.stopPropagation();
  }, []);

  const handleDrop: DragEventHandler = useCallback(
    (e) => {
      e.preventDefault();
      e.stopPropagation();

      setMode(Mode.Default);

      handleSelect(Array.from(e.dataTransfer.files));
    },
    [handleSelect],
  );

  const Content = useMemo(() => {
    switch (mode) {
      case Mode.Default:
        return () => <DefaultMode onSelect={handleSelect} testId={makeTestId(testId, 'default-mode')} />;
      case Mode.Active:
        return () => <ActiveMode testId={makeTestId(testId, 'active-mode')} />;
      case Mode.WrongFileFormat:
        return () => (
          <ErrorMode
            firstLine={t('ui.fileUpload.wrongFormatMessage.firstLine')}
            secondLine={t('ui.fileUpload.wrongFormatMessage.secondLine')}
            testId={makeTestId(testId, 'error-mode')}
          />
        );
      case Mode.TooManyFiles:
        return () => (
          <ErrorMode
            firstLine={t('ui.fileUpload.tooManyMessage.firstLine')}
            secondLine={t('ui.fileUpload.tooManyMessage.secondLine')}
            testId={makeTestId(testId, 'error-mode')}
          />
        );
      /* istanbul ignore next */
      default:
        assertUnreachable(mode);
    }
  }, [mode, handleSelect, testId, t]);

  return (
    <StyledDropArea $mode={mode} onDragEnter={handleDragEnter} {...{ [testIdAttribute]: testId }}>
      <Content />
      {mode !== Mode.Default ? (
        <StyledDropElement
          {...{ [testIdAttribute]: makeTestId(testId, 'drop-element') }}
          onDragEnter={handleDragEnter}
          onDragLeave={handleDragLeave}
          onDragOver={handleDragOver}
          onDrop={handleDrop}
        />
      ) : null}
    </StyledDropArea>
  );
}
