/**
 * TEAM: frontend_infra
 * @flow
 */

/* eslint-disable flexport/no-unused-aphrodite-styles */

import * as React from "react";
import {StyleSheet, css} from "aphrodite";
import Dropzone from "react-dropzone";

import {documentsT as t} from "../config/I18n";

import {whitespaceSizeConstants} from "../styles/whitespace";
import latitudeColors from "../colors";
import Flex from "../Flex";
import Loader from "../Loader";
import Button from "../button/Button";
import Text from "../Text";
import AttachmentList from "./AttachmentList";
import type {AttachmentType} from "./AttachmentTypes";

import createAttachmentsFromFiles from "../tools/documents/createAttachmentsFromFiles";
import getScaledImageForAttachment from "../tools/documents/getScaledImageForAttachment";
import validateFileOfAttachment from "../tools/documents/validateFileOfAttachment";
import removeAttachmentsWithError from "../tools/documents/removeAttachmentsWithError";

export type DocumentOption = {|
  +label: string,
  +value: string,
|};

type Props = {|
  /** List of files uploaded */
  +attachments?: $ReadOnlyArray<AttachmentType>,
  /** Displays a Dropzone around the FileUploader Button */
  +showDropzone?: boolean,
  /** Displays description editor for each file */
  +showDescriptionEditor?: boolean,
  /**
   * Displays thumbnail preview for uploaded files
   * Currently, preview for IMAGE and PDF formats are supported
   */
  +showPreview?: boolean,
  /** FileUploader saves only 1 file (the latest) from uploaded files */
  +singleFileUpload?: boolean,
  /** Allows user to edit file names */
  +fileNameEditable?: boolean,
  /**
   * This list populates the document type selector for each file.
   * When documentTypeList contains only 1 option, it is set as the default document
   * type for files and document type selector will not be displayed.
   */
  +documentTypeList: $ReadOnlyArray<DocumentOption>,
  /** Called when files are uploaded from the dropzone or the file selector */
  +onChange: ($ReadOnlyArray<AttachmentType>) => void,
  /** whether the uploader is disabled or not */
  +disabled?: boolean,
  /** Add a margin below the FileUploader */
  +deprecatedShowBottomMargin?: boolean,
  /** File types that can be accepted */
  +accept?: string,
  /** You can select multiple files with CTRL holding down while multiple is set to be true */
  +multiple?: boolean,
  /** You can remove the display of uploading part while showFileUploader is set to be false */
  +showFileUploader?: boolean,
  /** This is an extension slot for adding additional rendering elements to each attachment list item */
  +extraItemRender?: ?(AttachmentType) => React.Node,
|};

/**
 * @short Uncontrolled component for bulk uploading and previewing files
 * @brandStatus V3
 * @status Beta
 * @category Documents
 *
 * The fileUploader provides a list of `Attachment` objects to `onChange` handler when a user uploads
 * files. This component wraps an `Attachment` object around each `File` object that is uploaded. It
 * also renders thumbnail preview, document type selector and description selector for each file.
 * Consumer is responsible for setting `attachments` prop to the list of `Attachments` objects
 * returned from `onChange`.
 */
export default function FileUploader({
  attachments = [],
  showDropzone = false,
  showPreview = false,
  showDescriptionEditor = false,
  singleFileUpload = false,
  fileNameEditable = false,
  documentTypeList,
  onChange,
  disabled = false,
  deprecatedShowBottomMargin = false,
  accept,
  multiple = true,
  showFileUploader = true,
  extraItemRender,
}: Props): React.Element<"div"> {
  const handleFileInput = (event: SyntheticInputEvent<EventTarget>): void => {
    if (!event.target || !event.target.files) return;
    handleFileUpload(Array.from(event.target.files));
  };

  const handleFileUpload = (files: $ReadOnlyArray<File>) => {
    const uploadedAttachments = parseFilesToAttachment(files, documentTypeList);

    // Remove files with errors from last upload
    let attachmentList = removeAttachmentsWithError(attachments);

    if (singleFileUpload) {
      // For single file upload, only save the last file uploaded
      attachmentList = [uploadedAttachments[uploadedAttachments.length - 1]];
    } else {
      // Concat newly-uploaded files to previously validated files
      attachmentList = attachmentList.concat(uploadedAttachments);
    }

    onChange(attachmentList);
  };

  const uploadingCount = removeAttachmentsWithError(attachments).filter(
    a => a.uploaded === false
  ).length;
  const isUploading = uploadingCount > 0;

  // Disable dropzone while uploading, and display loader
  const dropzoneProps = {
    className: css(
      styles.dropzone,
      deprecatedShowBottomMargin && styles.dropzoneBottomMargin
    ),
    accept,
    multiple,
    disabled: isUploading ? true : disabled,
  };

  const dropzone = isUploading ? (
    <Dropzone {...dropzoneProps}>
      <Flex alignItems="center" justifyContent="center">
        <Loader loaded={false} size={30} />
        <Text>
          {t("Uploading {{count}} files", {count: uploadingCount.toString()})}
        </Text>
      </Flex>
    </Dropzone>
  ) : (
    <Dropzone
      {...dropzoneProps}
      onDrop={handleFileUpload}
      data-qa-id="Dropzone"
    >
      <Flex alignItems="center" justifyContent="center">
        <Text>{t("Drag and drop a file or")}</Text>
        <Button
          label={t("Click to upload")}
          intent="basic"
          type="submit"
          onClick={() => {}}
          disabled={disabled}
        />
      </Flex>
    </Dropzone>
  );

  const uploadingComponent = showDropzone ? (
    dropzone
  ) : (
    <FileInputButton
      accept={accept}
      multiple={multiple}
      onClick={handleFileInput}
    />
  );

  return (
    <div className={css(styles.uploaderContainer)}>
      {showFileUploader ? uploadingComponent : null}
      <AttachmentList
        attachments={attachments}
        showPreview={showPreview}
        showDescriptionEditor={showDescriptionEditor}
        showDocumentTypeSelector={documentTypeList.length > 1}
        fileNameEditable={fileNameEditable}
        documentTypeList={documentTypeList}
        onAttachmentsChange={attachments =>
          onChange(removeAttachmentsWithError(attachments))
        }
        extraItemRender={extraItemRender}
      />
    </div>
  );
}

export function parseFilesToAttachment(
  files: $ReadOnlyArray<File>,
  documentTypeList: $ReadOnlyArray<DocumentOption>
): Array<AttachmentType> {
  return createAttachmentsFromFiles(files, documentTypeList)
    .map(attachment =>
      // Scale Image Files
      getScaledImageForAttachment(attachment)
    )
    .map(attachment =>
      // Validate uploaded files
      validateFileOfAttachment(attachment)
    );
}

type FileInputButtonProps = {|
  +accept?: string,
  +multiple?: boolean,
  +onClick?: (event: SyntheticInputEvent<EventTarget>) => mixed,
|};

function FileInputButton({onClick, multiple, accept}: FileInputButtonProps) {
  const fileInput = React.useRef();
  return (
    <div className={css(styles.fileInputButton)}>
      <input
        type="file"
        ref={fileInput}
        accept={accept}
        onChange={onClick}
        multiple={multiple}
        style={{display: "none"}}
      />
      <Button
        label={t("Click to upload")}
        intent="basic"
        type="submit"
        onClick={_ => fileInput.current && fileInput.current.click()}
      />
    </div>
  );
}

const styles = StyleSheet.create({
  uploaderContainer: {
    width: "100%",
  },
  dropzone: {
    display: "flex",
    flexWrap: "wrap",
    justifyContent: "center",
    padding: whitespaceSizeConstants.l,
    width: "100%",
    minHeight: 72,
    boxSizing: "border-box",
    backgroundColor: latitudeColors.grey10,
  },
  dropzoneBottomMargin: {
    marginBottom: whitespaceSizeConstants.l,
  },
  fileInputButton: {
    marginBottom: whitespaceSizeConstants.m,
  },
});
