import clsx from 'clsx';
import React, { useCallback, useEffect, useState } from 'react';
import { noop } from '../../../helpers/utils';
import { IconName, Icons } from '../../Icons';
import { ProgressBar } from '../../ProgressBar';
import { BaseFormControl } from '../Form.models';
import { FormElementContainer } from '../FormElementContainer';
import classes from './FileUploadControl.scss';

export interface FileUpload {
  file: File;
  uploadStarted: () => void;
  uploadProgress: (percentage: number) => void;
  uploadCompleted: () => void;
}

export interface FileUploadProps extends BaseFormControl {
  /** Input placeholder */
  placeholder?: string;
  /** Current value of the file upload */
  value?: FileUpload;
  /** Input 'accept' attribute. MIME Types can be set here to limit which file types can be selected */
  accept?: string;
  /** Callback called when a file is dropped or selected
   * @param selectedFile File selected
   * @param uploadStarted Function to be called to show the upload progress bar
   * @param uploadProgress Function to be called to update the progress bar
   * @param uploadCompleted Function to be called to hide the upload progress bar
   */
  onFileSelected: (args: FileUpload) => void;
}

export const FileUploadControl: React.FC<FileUploadProps> = ({
  onFileSelected,
  error,
  id = 'file',
  placeholder = 'Browse or drag your file here',
  value,
  accept,
  className = '',
  disabled,
  ...rest
}) => {
  const [fileName, setFileName] = useState<string>(value?.file.name ?? '');
  const [progress, setProgress] = useState<number>(0);
  const [uploading, setUploading] = useState<boolean>(false);
  const [dragging, setDragging] = useState<boolean>(false);

  const handleDragIn = (e: DragEvent): void => {
    e.preventDefault();
    e.stopPropagation();
    if (!dragging && e.dataTransfer?.items && e.dataTransfer.items.length > 0) {
      setDragging(true);
    }
  };

  const handleDragOut = (e: MouseEvent): void => {
    e.preventDefault();
    e.stopPropagation();
    if (dragging) {
      setDragging(false);
    }
  };

  const handleDrop = (e: React.DragEvent<HTMLDivElement>): void => {
    e.preventDefault();
    e.stopPropagation();

    const file = e.dataTransfer?.files.item(0);

    // Checks MIME Types, if set. Exits if type is unsupported
    if (accept !== undefined && file?.type !== undefined) {
      if (!accept.includes(file.type)) {
        setDragging(false);
        return;
      }
    }

    file && fileSelected(file);

    setDragging(false);
  };

  useEffect(() => {
    document.addEventListener('dragover', handleDragIn);
    document.addEventListener('mouseover', handleDragOut);

    return () => {
      document.removeEventListener('dragover', handleDragIn);
      document.removeEventListener('mouseover', handleDragOut);
    };
  });

  const onUploadStarted = useCallback(() => {
    setUploading(true);
  }, []);

  const onUploadProgress = useCallback((progress: number) => {
    setUploading(true);
    setProgress(progress);
  }, []);

  const onUploadCompleted = useCallback(() => {
    setUploading(false);
    setProgress(0);
  }, []);

  const fileSelected = (file: File): void => {
    onFileSelected &&
      onFileSelected({
        file,
        uploadStarted: onUploadStarted,
        uploadProgress: onUploadProgress,
        uploadCompleted: onUploadCompleted,
      });
  };

  useEffect(() => {
    setFileName(value?.file.name ?? '');
    if (value) {
      value.uploadCompleted = onUploadCompleted;
      value.uploadProgress = onUploadProgress;
      value.uploadStarted = onUploadStarted;
    }
  }, [onUploadCompleted, onUploadProgress, onUploadStarted, value]);

  return (
    <FormElementContainer
      {...rest}
      className={clsx(
        classes.container,
        'file-upload-control-container',
        className,
      )}
      error={error}
      dataTestFieldType="FileUpload"
      htmlFor={id ?? name}
    >
      <div className={clsx(classes.content)}>
        {dragging && !disabled ? (
          <div className={clsx(classes.dropTarget)} onDrop={handleDrop}>
            Drag & Drop your file here
          </div>
        ) : (
          <>
            <input
              className={clsx({
                [classes.hasError]: error !== undefined,
                [classes.filename]: true,
              })}
              value={fileName}
              onChange={noop}
              placeholder={disabled ? undefined : placeholder}
              disabled={true}
            />
            <label
              className={clsx({
                [classes.fileuploadbutton]: true,
                [classes.disabled]: disabled,
              })}
              htmlFor={id ?? name}
            >
              <Icons icon={IconName.File} />
            </label>
            <input
              id={id ?? name}
              type="file"
              accept={accept}
              disabled={disabled}
              onChange={(event) => {
                const file = event.target.files?.item(0);
                file && fileSelected(file);
                // Reset the value of the input field to ensure onChange fires even with same file
                event.target.value = '';
              }}
            />
          </>
        )}
        <div
          className={clsx(classes.progressbar)}
          style={{ display: uploading ? 'block' : 'none' }}
        >
          <ProgressBar kind="simple" progress={progress} />
        </div>
      </div>
    </FormElementContainer>
  );
};
