import { clsx } from 'clsx';
import { useState, useRef, useLayoutEffect, useEffect } from 'react';
import { useIntl } from 'react-intl';
import Button from '../button/Button.mjs';
import { useInputAttributes } from '../inputs/contexts.mjs';
import Modal from '../modal/Modal.mjs';
import { isSizeValid } from '../upload/utils/isSizeValid/isSizeValid.mjs';
import { isTypeValid } from '../upload/utils/isTypeValid/isTypeValid.mjs';
import MESSAGES from './UploadInput.messages.mjs';
import UploadButton from './uploadButton/UploadButton.mjs';
import { imageFileTypes, DEFAULT_SIZE_LIMIT } from './uploadButton/defaults.mjs';
import UploadItem from './uploadItem/UploadItem.mjs';
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
import { Status } from '../common/propsValues/status.mjs';
import { Priority, ControlType } from '../common/propsValues/control.mjs';

function generateFileId(file) {
  const {
    name,
    size
  } = file;
  const uploadTimeStamp = new Date().getTime();
  return `${name}_${size}_${uploadTimeStamp}`;
}
const UploadInput = ({
  files = [],
  fileInputName = 'file',
  className,
  deleteConfirm,
  disabled,
  multiple = false,
  fileTypes = imageFileTypes,
  sizeLimit = DEFAULT_SIZE_LIMIT,
  description,
  onUploadFile,
  onDeleteFile,
  onValidationError,
  onFilesChange,
  onDownload,
  maxFiles,
  maxFilesErrorMessage,
  id,
  sizeLimitErrorMessage,
  uploadButtonTitle
}) => {
  const inputAttributes = useInputAttributes({
    nonLabelable: true
  });
  const [markedFileForDelete, setMarkedFileForDelete] = useState(null);
  const [fileToRemoveIndex, setFileToRemoveIndex] = useState(null);
  const [mounted, setMounted] = useState(false);
  const {
    formatMessage
  } = useIntl();
  const itemRefs = useRef([]);
  const uploadInputRef = useRef(null);
  const PROGRESS_STATUSES = new Set([Status.PENDING, Status.PROCESSING]);
  const [uploadedFiles, setUploadedFiles] = useState(multiple || files.length === 0 ? files : [files[0]]);
  const uploadedFilesListReference = useRef(multiple || files.length === 0 ? files : [files[0]]);
  function addFileToList(recentUploadedFile) {
    function addToList(listToAddTo) {
      return [...listToAddTo, recentUploadedFile];
    }
    setUploadedFiles(addToList);
    uploadedFilesListReference.current = addToList(uploadedFilesListReference.current);
  }
  const removeFileFromList = file => {
    function filterOutFrom(listToFilterFrom) {
      return listToFilterFrom.filter(fileInList => file !== fileInList && file.id !== fileInList.id);
    }
    setUploadedFiles(filterOutFrom);
    uploadedFilesListReference.current = filterOutFrom(uploadedFilesListReference.current);
  };
  const modifyFileInList = (file, updates) => {
    const updateListItem = listToUpdate => listToUpdate.map(fileInList => {
      return fileInList === file || fileInList.id === file.id ? {
        ...file,
        ...updates
      } : fileInList;
    });
    setUploadedFiles(updateListItem);
    uploadedFilesListReference.current = updateListItem(uploadedFilesListReference.current);
  };
  const [fileToRemove, setFileToRemove] = useState(null);
  const removeFile = file => {
    const {
      id,
      status
    } = file;
    const index = uploadedFiles.findIndex(f => f.id === file.id);
    setFileToRemoveIndex(index);
    if (status === Status.FAILED) {
      removeFileFromList(file);
      setFileToRemove(file);
    } else if (onDeleteFile && id) {
      modifyFileInList(file, {
        status: Status.PROCESSING,
        error: undefined
      });
      onDeleteFile(id).then(() => {
        removeFileFromList(file);
      }).catch(error => {
        modifyFileInList(file, {
          error: error
        });
      }).finally(() => {
        setFileToRemove(file);
      });
    }
  };
  function handleFileUploadFailure(file, failureMessage) {
    const {
      name
    } = file;
    const id = generateFileId(file);
    const failedUpload = {
      id,
      filename: name,
      status: Status.FAILED,
      error: failureMessage
    };
    addFileToList(failedUpload);
    if (onValidationError) {
      onValidationError(failedUpload);
    }
  }
  function getNumberOfFilesUploaded() {
    const uploadInitiatedStatus = new Set([Status.SUCCEEDED, Status.PENDING]);
    const validFiles = uploadedFilesListReference.current.filter(file => file.status && uploadInitiatedStatus.has(file.status));
    return validFiles.length;
  }
  function areMaximumFilesUploadedAlready() {
    if (!maxFiles) {
      return false;
    }
    const numberOfValidFiles = getNumberOfFilesUploaded();
    return numberOfValidFiles >= maxFiles;
  }
  // One or more files selected, create entries for them
  const addFiles = selectedFiles => {
    for (let i = 0; i < selectedFiles.length; i += 1) {
      const file = selectedFiles.item(i);
      // Returning a FormData[] array instead of FileList so we can filter out incorrect files
      const formData = new FormData();
      if (file) {
        generateFileId(file);
        const allowedFileTypes = typeof fileTypes === 'string' ? fileTypes : fileTypes.join(',');
        // Check if file type is valid
        if (!isTypeValid(file, allowedFileTypes)) {
          handleFileUploadFailure(file, formatMessage(MESSAGES.fileTypeNotSupported));
          continue;
        }
        // Check if the filesize is valid
        // Convert to rough bytes
        if (!isSizeValid(file, sizeLimit * 1000)) {
          const failureMessage = sizeLimitErrorMessage || formatMessage(MESSAGES.fileIsTooLarge);
          handleFileUploadFailure(file, failureMessage);
          continue;
        }
        if (areMaximumFilesUploadedAlready()) {
          const failureMessage = maxFilesErrorMessage || formatMessage(MESSAGES.maximumFilesAlreadyUploaded, {
            maxFilesAllowed: maxFiles
          });
          handleFileUploadFailure(file, failureMessage);
          continue;
        }
        // Check if the file is already in the list
        const existingFile = uploadedFiles.find(f => f.filename === file.name);
        if (existingFile) {
          // Remove the file from the list before adding it again
          removeFileFromList(existingFile);
        }
        // Add the file to the list
        formData.append(fileInputName, file);
        const pendingFile = {
          id: generateFileId(file),
          filename: file.name,
          status: Status.PENDING
        };
        addFileToList(pendingFile);
        // Start uploading the file
        onUploadFile(formData).then(({
          id,
          url,
          error
        }) => {
          // Replace the temporary id with the final one received from the API, and also set any errors
          modifyFileInList(pendingFile, {
            id,
            url,
            error,
            status: Status.SUCCEEDED
          });
        }).catch(error => {
          modifyFileInList(pendingFile, {
            error: error,
            status: Status.FAILED
          });
        });
        if (!multiple) {
          // Only upload a single file
          break;
        }
      }
    }
  };
  useLayoutEffect(() => {
    if (fileToRemove && fileToRemoveIndex !== null) {
      requestAnimationFrame(() => {
        const nextFocusIndex = Math.min(fileToRemoveIndex, uploadedFiles.length - 1);
        if (itemRefs.current[nextFocusIndex]) {
          itemRefs.current[nextFocusIndex].focus(); // Focus the next UploadItem
        } else {
          // If there's only one item left, focus the UploadButton
          uploadInputRef.current?.focus();
        }
      });
      setFileToRemove(null); // Reset the state
      setFileToRemoveIndex(null); // Reset the index
    }
  }, [uploadedFiles, fileToRemove, fileToRemoveIndex, itemRefs, uploadInputRef]);
  useEffect(() => {
    setMounted(true);
  }, []);
  useEffect(() => {
    if (onFilesChange && mounted) {
      onFilesChange([...uploadedFiles]);
    }
  }, [onFilesChange, uploadedFiles]); // eslint-disable-line react-hooks/exhaustive-deps
  return /*#__PURE__*/jsxs(Fragment, {
    children: [/*#__PURE__*/jsxs("div", {
      role: "group",
      className: clsx('np-upload-input', className, {
        disabled
      }),
      ...inputAttributes,
      children: [/*#__PURE__*/jsx("div", {
        className: "np-upload-input__section",
        "aria-live": "polite",
        "aria-relevant": "all",
        role: "region",
        children: uploadedFiles.map((file, index) => /*#__PURE__*/jsx(UploadItem, {
          ref: el => {
            itemRefs.current[index] = el;
          },
          file: file,
          singleFileUpload: !multiple,
          canDelete: (!!onDeleteFile || file.status === Status.FAILED) && (!file.status || !PROGRESS_STATUSES.has(file.status)),
          onDelete: file.status === Status.FAILED ? () => removeFile(file) : () => setMarkedFileForDelete(file),
          onDownload: onDownload
        }, file.id))
      }), (multiple || !multiple && !uploadedFiles.length) && /*#__PURE__*/jsx("div", {
        className: "np-upload-input__section np-upload-input__section--uploader",
        children: /*#__PURE__*/jsx(UploadButton, {
          ref: uploadInputRef,
          id: id,
          uploadButtonTitle: uploadButtonTitle,
          disabled: areMaximumFilesUploadedAlready() || disabled,
          multiple: multiple,
          fileTypes: fileTypes,
          sizeLimit: sizeLimit,
          description: description,
          maxFiles: maxFiles,
          withEntries: Boolean(uploadedFiles.length),
          onChange: addFiles
        })
      })]
    }), /*#__PURE__*/jsx(Modal, {
      title: deleteConfirm?.title !== undefined ? deleteConfirm.title : formatMessage(MESSAGES.deleteModalTitle),
      body: deleteConfirm?.body !== undefined ? deleteConfirm.body : formatMessage(MESSAGES.deleteModalBody),
      open: !!markedFileForDelete,
      footer: /*#__PURE__*/jsxs(Fragment, {
        children: [/*#__PURE__*/jsx(Button, {
          block: true,
          onClick: () => {
            setMarkedFileForDelete(null);
          },
          children: deleteConfirm?.cancelText || formatMessage(MESSAGES.deleteModalCancelButtonText)
        }), /*#__PURE__*/jsx(Button, {
          block: true,
          priority: Priority.SECONDARY,
          type: ControlType.NEGATIVE,
          onClick: () => {
            if (markedFileForDelete) {
              removeFile(markedFileForDelete);
            }
            setMarkedFileForDelete(null);
          },
          children: deleteConfirm?.confirmText || formatMessage(MESSAGES.deleteModalConfirmButtonText)
        })]
      }),
      onClose: () => {
        setMarkedFileForDelete(null);
      }
    })]
  });
};

export { UploadInput as default };
//# sourceMappingURL=UploadInput.mjs.map
