'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var clsx = require('clsx');
var React = require('react');
var reactIntl = require('react-intl');
var Button_resolver = require('../button/Button.resolver.js');
require('../common/theme.js');
require('../common/direction.js');
require('@transferwise/neptune-tokens');
var control = require('../common/propsValues/control.js');
require('../common/propsValues/size.js');
require('../common/propsValues/typography.js');
require('../common/propsValues/width.js');
require('../common/propsValues/type.js');
require('../common/propsValues/dateMode.js');
require('../common/propsValues/monthFormat.js');
require('../common/propsValues/position.js');
require('../common/propsValues/layouts.js');
var status = require('../common/propsValues/status.js');
require('../common/propsValues/sentiment.js');
require('../common/propsValues/profileType.js');
require('../common/propsValues/variant.js');
require('../common/propsValues/scroll.js');
require('../common/propsValues/markdownNodeType.js');
require('../common/fileType.js');
require('@transferwise/formatting');
require('@transferwise/icons');
require('../common/closeButton/CloseButton.messages.js');
var jsxRuntime = require('react/jsx-runtime');
var contexts = require('../inputs/contexts.js');
var Modal = require('../modal/Modal.js');
var isSizeValid = require('../upload/utils/isSizeValid/isSizeValid.js');
var isTypeValid = require('../upload/utils/isTypeValid/isTypeValid.js');
var UploadInput_messages = require('./UploadInput.messages.js');
var UploadButton = require('./uploadButton/UploadButton.js');
var defaults = require('./uploadButton/defaults.js');
var UploadItem = require('./uploadItem/UploadItem.js');

function generateFileId(file) {
  const {
    name,
    size
  } = file;
  const uploadTimeStamp = new Date().getTime();
  return `${name}_${size}_${uploadTimeStamp}`;
}
/**
 * The component allows users to upload files, manage the list of uploaded files,
 * and handle file validation and deletion.
 *
 * @param {UploadInputProps} props - The properties for the UploadInput component.
 *
 * @see {@link UploadInput} for further information.
 * @see {@link https://storybook.wise.design/?path=/docs/forms-uploadinput--docs|Storybook Wise Design}
 */
const UploadInput = ({
  files = [],
  fileInputName = 'file',
  className,
  deleteConfirm,
  disabled,
  multiple = false,
  fileTypes = defaults.imageFileTypes,
  sizeLimit = defaults.DEFAULT_SIZE_LIMIT,
  description,
  onUploadFile,
  onDeleteFile,
  onValidationError,
  onFilesChange,
  onDownload,
  maxFiles,
  maxFilesErrorMessage,
  id,
  sizeLimitErrorMessage,
  uploadButtonTitle
}) => {
  const inputAttributes = contexts.useInputAttributes({
    nonLabelable: true
  });
  const [markedFileForDelete, setMarkedFileForDelete] = React.useState(null);
  const [lastAttemptedDeleteId, setLastAttemptedDeleteId] = React.useState(null);
  const mountedRef = React.useRef(false);
  const {
    formatMessage
  } = reactIntl.useIntl();
  const uploadInputRef = React.useRef(null);
  const fileRefs = React.useRef([]);
  const PROGRESS_STATUSES = new Set([status.Status.PENDING, status.Status.PROCESSING]);
  const [uploadedFiles, setUploadedFiles] = React.useState(multiple || files.length === 0 ? files : [files[0]]);
  const uploadedFilesListReference = React.useRef(multiple || files.length === 0 ? files : [files[0]]);
  function updateFileList(updateFn) {
    setUploadedFiles(updateFn);
    uploadedFilesListReference.current = updateFn(uploadedFilesListReference.current);
  }
  function addFileToList(recentUploadedFile) {
    updateFileList(list => [...list, recentUploadedFile]);
  }
  function removeFileFromList(file) {
    updateFileList(list => list.filter(fileInList => file !== fileInList && file.id !== fileInList.id));
    fileRefs.current = fileRefs.current.filter(ref => ref && ref.id !== file.id);
  }
  function modifyFileInList(file, updates) {
    updateFileList(list => list.map(fileInList => fileInList === file || fileInList.id === file.id ? {
      ...file,
      ...updates
    } : fileInList));
  }
  const removeFile = async file => {
    const {
      id: fileId,
      status: status$1
    } = file;
    fileRefs.current = fileRefs.current.filter(item => item && item.id !== file.id);
    if (status$1 === status.Status.FAILED) {
      removeFileFromList(file);
      return Promise.resolve();
    }
    if (onDeleteFile && fileId) {
      modifyFileInList(file, {
        status: status.Status.PROCESSING,
        error: undefined
      });
      return onDeleteFile(fileId).then(() => {
        removeFileFromList(file);
      }).catch(error => {
        modifyFileInList(file, {
          error: error
        });
      });
    }
  };
  function handleFileUploadFailure(file, failureMessage) {
    const {
      name
    } = file;
    const failedUpload = {
      id: generateFileId(file),
      filename: name,
      status: status.Status.FAILED,
      error: failureMessage
    };
    addFileToList(failedUpload);
    if (onValidationError) {
      onValidationError(failedUpload);
    }
  }
  function getNumberOfFilesUploaded() {
    const uploadInitiatedStatus = new Set([status.Status.SUCCEEDED, status.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;
  }
  const addFiles = selectedFiles => {
    for (let i = 0; i < selectedFiles.length; i += 1) {
      const file = selectedFiles.item(i);
      const formData = new FormData();
      if (file) {
        const allowedFileTypes = typeof fileTypes === 'string' ? fileTypes : fileTypes.join(',');
        if (!isTypeValid.isTypeValid(file, allowedFileTypes)) {
          handleFileUploadFailure(file, formatMessage(UploadInput_messages.default.fileTypeNotSupported));
          continue;
        }
        if (typeof sizeLimit === 'number' && !isSizeValid.isSizeValid(file, sizeLimit * 1000)) {
          const failureMessage = sizeLimitErrorMessage || formatMessage(UploadInput_messages.default.fileIsTooLarge);
          handleFileUploadFailure(file, failureMessage);
          continue;
        }
        if (areMaximumFilesUploadedAlready()) {
          const failureMessage = maxFilesErrorMessage || formatMessage(UploadInput_messages.default.maximumFilesAlreadyUploaded, {
            maxFilesAllowed: maxFiles
          });
          handleFileUploadFailure(file, failureMessage);
          continue;
        }
        const existingFile = uploadedFiles.find(f => f.filename === file.name);
        if (existingFile) {
          removeFileFromList(existingFile);
        }
        formData.append(fileInputName, file);
        const pendingFile = {
          id: generateFileId(file),
          filename: file.name,
          status: status.Status.PENDING
        };
        addFileToList(pendingFile);
        onUploadFile(formData).then(({
          id,
          url,
          error
        }) => {
          modifyFileInList(pendingFile, {
            id,
            url,
            error,
            status: status.Status.SUCCEEDED
          });
        }).catch(error => {
          modifyFileInList(pendingFile, {
            error: error,
            status: status.Status.FAILED
          });
        });
        if (!multiple) {
          break;
        }
      }
    }
  };
  React.useEffect(() => {
    // Skip onFilesChange call on initial mount, only call on updates
    if (!mountedRef.current) {
      mountedRef.current = true;
      return;
    }
    if (onFilesChange) {
      onFilesChange([...uploadedFiles]);
    }
  }, [onFilesChange, uploadedFiles]);
  const [nextFocusable, setNextFocusable] = React.useState(null);
  const handleFocus = fileId => {
    fileRefs.current = fileRefs.current.filter(ref => {
      return ref && ref.id !== markedFileForDelete?.id;
    });
    const filesCount = fileRefs.current.length;
    let next = uploadInputRef.current;
    let focusTarget = 'button';
    // If there will be no files left after deletion, focus the upload button
    if (filesCount === 1) {
      next = uploadInputRef.current;
      setNextFocusable(next);
      return;
    }
    if (filesCount > 1) {
      const currentFileIndex = fileRefs.current.findIndex(file => file?.id === fileId);
      const currentFileId = fileRefs.current?.[currentFileIndex]?.id;
      const lastFileId = fileRefs.current?.[filesCount - 1]?.id;
      // if last file, select a previous one
      if (currentFileId === lastFileId) {
        next = fileRefs.current[filesCount - 2];
      } else {
        next = fileRefs.current[currentFileIndex + 1];
      }
      // If next is an UploadItemRef, check if it has a URL (succeeded)
      if (next && 'status' in next) {
        // Find the file object for this ref
        const fileObj = uploadedFiles.find(f => f.id === next?.id);
        if (fileObj && (fileObj.status === status.Status.SUCCEEDED || fileObj.status === status.Status.DONE) && fileObj.url) {
          focusTarget = 'link';
        }
      }
      setNextFocusable(() => {
        if (next && typeof next.focus === 'function') {
          return {
            ref: next,
            target: focusTarget
          };
        }
        return next;
      });
    }
  };
  const handleRefocus = () => {
    const focusTarget = nextFocusable;
    if (lastAttemptedDeleteId) {
      setLastAttemptedDeleteId(null);
      return;
    }
    if (focusTarget) {
      // If there are no files left, focus the upload button
      if (uploadedFiles.length === 0 && uploadInputRef.current && typeof uploadInputRef.current.focus === 'function') {
        setTimeout(() => {
          uploadInputRef.current?.focus();
        }, 0);
      } else if (typeof focusTarget === 'object' && 'ref' in focusTarget && focusTarget.ref && typeof focusTarget.ref.focus === 'function') {
        setTimeout(() => {
          if (focusTarget.ref && typeof focusTarget.ref.focus === 'function') {
            // @ts-expect-error: focus may not exist on all possible ref types, but is safe here
            focusTarget.ref.focus(focusTarget.target);
          }
        }, 0);
      } else if (focusTarget && typeof focusTarget.focus === 'function') {
        setTimeout(() => {
          focusTarget.focus();
        }, 0);
      }
    }
  };
  return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
    children: [/*#__PURE__*/jsxRuntime.jsxs("div", {
      role: "group",
      className: clsx.clsx('np-upload-input', className, {
        disabled
      }),
      ...inputAttributes,
      children: [/*#__PURE__*/jsxRuntime.jsx("div", {
        className: "np-upload-input__section",
        "aria-live": "polite",
        "aria-relevant": "all",
        role: "region",
        children: uploadedFiles.map(file => /*#__PURE__*/jsxRuntime.jsx(UploadItem.default, {
          ref: el => {
            if (el && el.id !== markedFileForDelete?.id && !fileRefs.current.some(ref => ref?.id === el.id) && el.status !== 'processing') {
              fileRefs.current.push(el);
            }
          },
          file: file,
          singleFileUpload: !multiple,
          canDelete: (!!onDeleteFile || file.status === status.Status.FAILED) && (!file.status || !PROGRESS_STATUSES.has(file.status)),
          onDelete: file.status === status.Status.FAILED ? async () => {
            setLastAttemptedDeleteId(file.id);
            await removeFile(file);
            handleRefocus();
          } : () => {
            setLastAttemptedDeleteId(file.id);
            setMarkedFileForDelete(file);
          },
          onDownload: onDownload,
          onFocus: () => handleFocus(file.id)
        }, file.id))
      }), (multiple || !multiple && !uploadedFiles.length) && /*#__PURE__*/jsxRuntime.jsx("div", {
        className: "np-upload-input__section np-upload-input__section--uploader",
        children: /*#__PURE__*/jsxRuntime.jsx(UploadButton.default, {
          ref: uploadInputRef,
          id: id,
          uploadButtonTitle: uploadButtonTitle
          // eslint-disable-next-line react-hooks/refs -- Function reads ref for file count check
          ,
          disabled: areMaximumFilesUploadedAlready() || disabled,
          multiple: multiple,
          fileTypes: fileTypes,
          sizeLimit: sizeLimit,
          description: description,
          maxFiles: maxFiles,
          withEntries: Boolean(uploadedFiles.length),
          onChange: addFiles
        })
      })]
    }), /*#__PURE__*/jsxRuntime.jsx(Modal.default, {
      title: deleteConfirm?.title !== undefined ? deleteConfirm.title : formatMessage(UploadInput_messages.default.deleteModalTitle),
      body: deleteConfirm?.body !== undefined ? deleteConfirm.body : formatMessage(UploadInput_messages.default.deleteModalBody),
      open: !!markedFileForDelete,
      footer: /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, {
        children: [/*#__PURE__*/jsxRuntime.jsx(Button_resolver.default, {
          block: true,
          onClick: () => {
            setMarkedFileForDelete(null);
          },
          children: deleteConfirm?.cancelText || formatMessage(UploadInput_messages.default.deleteModalCancelButtonText)
        }), /*#__PURE__*/jsxRuntime.jsx(Button_resolver.default, {
          block: true,
          priority: control.Priority.SECONDARY,
          type: control.ControlType.NEGATIVE,
          tabIndex: markedFileForDelete ? 0 : -1,
          onClick: () => {
            if (markedFileForDelete) {
              void removeFile(markedFileForDelete);
            }
            setMarkedFileForDelete(null);
            setLastAttemptedDeleteId(null);
          },
          children: deleteConfirm?.confirmText || formatMessage(UploadInput_messages.default.deleteModalConfirmButtonText)
        })]
      }),
      onUnmount: handleRefocus,
      onClose: () => {
        setMarkedFileForDelete(null);
      }
    })]
  });
};

exports.default = UploadInput;
//# sourceMappingURL=UploadInput.js.map
