import { useMemo } from 'react';

import { useTestIdAttribute } from '../../hooks/useTestIdAttribute';
import { assertEmptyObject } from '../../utils/assertEmptyObject';
import { makeTestId } from '../../utils/makeTestId';
import { GeneralFormControlProps } from '../types';

import { DropArea } from './components/DropArea/DropArea';
import { StatusList } from './components/StatusList/StatusList';
import { FileUploadInputErrorCode } from './constants';
import { FileUploadContextProvider, FileUploadContextValue } from './contexts/FileUploadContext';
import { FileUploadFileMeta } from './types';

export interface FileUploadProps
  extends Omit<GeneralFormControlProps<unknown>, 'value' | 'onChange' | 'onBlur'> {
  /** The additional text below the control button can be used, for example, to describe file formats. */
  caption?: string;
  /** Allowed file formats as an array of [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) */
  mimeTypes: string[];
  /** A function that will be invoked when user select some files */
  onSelect(files: File[]): void;
  /** A function that will be invoked when the user clicks the remove button on an item from the file list */
  onCancel(id: string): void;
  /**
   * Input error callback
   *
   * The component ignores incorrect input, but it can't prevent it.
   * It can read file meta information during drag & drop and show the appropriate warning.
   * Or it can use `mimeTypes` and `multiple` props.
   *
   * But users can bypass these checks:
   * - they can drag & drop multiple files or file with the wrong format ignoring the warning;
   * - they can change file picker dialog options and choose a file with the wrong format, ignoring the `mimeTypes` option.
   * - they can use a browser that can't read information about files during drag & drop (e.g., Safari),
   * and the component can't show warnings.
   *
   * Such cases can be handled using this callback. For example, it is possible to show a notification.
   */
  onInputError?(code: FileUploadInputErrorCode, files: File[]): void;
  /** File list in special format {@link FileUploadFileMeta}  */
  fileList: FileUploadFileMeta[];
  /** The input allows the user to select more than one file at a time */
  multiple?: boolean;
}

/**
 * The form field allows uploading files using the file picker dialog or drag & drop.
 *
 * ## Enabled
 * <Story id="form-fileupload--default" />
 *
 * ## Disabled
 * <Story id="form-fileupload--disabled" />
 *
 * ## With selected files in different statuses
 * <Story id="form-fileupload--filled" />
 *
 * ## Disabled with selected files in different statuses
 * <Story id="form-fileupload--filled-disabled" />
 */
export function FileUpload(props: FileUploadProps) {
  const {
    disabled = false,
    multiple = false,
    caption,
    mimeTypes,
    onSelect,
    onCancel,
    onInputError,
    fileList,
    className,
    id,
    ariaInvalid,
    ariaErrorMessage,
    ariaDescribedBy,
    ariaLabel,
    testId,
    required,
    ariaRequired,
    ...rest
  } = props;
  assertEmptyObject(rest);

  const testIdAttribute = useTestIdAttribute();

  const inputProps = useMemo<FileUploadContextValue['inputProps']>(
    () => ({
      id,
      ariaInvalid,
      ariaErrorMessage,
      ariaDescribedBy,
      ariaLabel,
      required,
      ariaRequired,
    }),
    [id, ariaInvalid, ariaErrorMessage, ariaDescribedBy, ariaLabel, required, ariaRequired],
  );

  return (
    <FileUploadContextProvider
      caption={caption}
      disabled={disabled}
      fileList={fileList}
      inputProps={inputProps}
      mimeTypes={mimeTypes}
      multiple={multiple}
      onCancel={onCancel}
      onInputError={onInputError}
      onSelect={onSelect}
    >
      <div className={className} {...{ [testIdAttribute]: testId }}>
        <DropArea testId={makeTestId(testId, 'drop-area')} />
        <StatusList />
      </div>
    </FileUploadContextProvider>
  );
}
