import { clsx } from 'clsx';
import { useState, useRef, useEffect } from 'react';
import { useIntl } from 'react-intl';
import Button from '../button/Button.resolver.mjs';
import '../common/theme.mjs';
import '../common/direction.mjs';
import '@transferwise/neptune-tokens';
import { ControlType, Priority } from '../common/propsValues/control.mjs';
import '../common/propsValues/size.mjs';
import '../common/propsValues/typography.mjs';
import '../common/propsValues/width.mjs';
import '../common/propsValues/type.mjs';
import '../common/propsValues/dateMode.mjs';
import '../common/propsValues/monthFormat.mjs';
import '../common/propsValues/position.mjs';
import '../common/propsValues/layouts.mjs';
import { Status } from '../common/propsValues/status.mjs';
import '../common/propsValues/sentiment.mjs';
import '../common/propsValues/profileType.mjs';
import '../common/propsValues/variant.mjs';
import '../common/propsValues/scroll.mjs';
import '../common/propsValues/markdownNodeType.mjs';
import '../common/fileType.mjs';
import '@transferwise/formatting';
import '@transferwise/icons';
import '../common/closeButton/CloseButton.messages.mjs';
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
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 { DEFAULT_SIZE_LIMIT, imageFileTypes } from './uploadButton/defaults.mjs';
import UploadItem from './uploadItem/UploadItem.mjs';

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 = 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 [lastAttemptedDeleteId, setLastAttemptedDeleteId] = useState(null);
  const mountedRef = useRef(false);
  const {
    formatMessage
  } = useIntl();
  const uploadInputRef = useRef(null);
  const fileRefs = useRef([]);
  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 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
    } = file;
    fileRefs.current = fileRefs.current.filter(item => item && item.id !== file.id);
    if (status === Status.FAILED) {
      removeFileFromList(file);
      return Promise.resolve();
    }
    if (onDeleteFile && fileId) {
      modifyFileInList(file, {
        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.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;
  }
  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(file, allowedFileTypes)) {
          handleFileUploadFailure(file, formatMessage(MESSAGES.fileTypeNotSupported));
          continue;
        }
        if (typeof sizeLimit === 'number' && !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;
        }
        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.PENDING
        };
        addFileToList(pendingFile);
        onUploadFile(formData).then(({
          id,
          url,
          error
        }) => {
          modifyFileInList(pendingFile, {
            id,
            url,
            error,
            status: Status.SUCCEEDED
          });
        }).catch(error => {
          modifyFileInList(pendingFile, {
            error: error,
            status: Status.FAILED
          });
        });
        if (!multiple) {
          break;
        }
      }
    }
  };
  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] = 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.SUCCEEDED || fileObj.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__*/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 => /*#__PURE__*/jsx(UploadItem, {
          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.FAILED) && (!file.status || !PROGRESS_STATUSES.has(file.status)),
          onDelete: file.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__*/jsx("div", {
        className: "np-upload-input__section np-upload-input__section--uploader",
        children: /*#__PURE__*/jsx(UploadButton, {
          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__*/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,
          tabIndex: markedFileForDelete ? 0 : -1,
          onClick: () => {
            if (markedFileForDelete) {
              void removeFile(markedFileForDelete);
            }
            setMarkedFileForDelete(null);
            setLastAttemptedDeleteId(null);
          },
          children: deleteConfirm?.confirmText || formatMessage(MESSAGES.deleteModalConfirmButtonText)
        })]
      }),
      onUnmount: handleRefocus,
      onClose: () => {
        setMarkedFileForDelete(null);
      }
    })]
  });
};

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