import { Bin, CheckCircleFill, CrossCircleFill } from '@transferwise/icons';
import { clsx } from 'clsx';
import { forwardRef, useImperativeHandle, useRef } from 'react';
import { useIntl } from 'react-intl';

import Body from '../../body';
import { Size, Status, Typography } from '../../common';
import ProcessIndicator from '../../processIndicator/ProcessIndicator';
import { UploadedFile, UploadError } from '../types';

import MESSAGES from './UploadItem.messages';
import { UploadItemLink } from './UploadItemLink';

export type UploadItemProps = React.JSX.IntrinsicAttributes & {
  file: UploadedFile;
  /**
   * Is this Item part of a multiple- or single-file UploadInput
   */
  singleFileUpload: boolean;
  canDelete: boolean;
  onDelete: () => void;
  onFocus: (target?: 'button' | 'link') => void;

  /**
   * Callback to be called when the file link is clicked.
   * When provided, you need to manually trigger actions to load/download the file.
   *
   * @param file
   */
  onDownload?: (file: UploadedFile) => void;
  ref?: React.Ref<UploadItemRef>;
};
interface UploadItemRef {
  /**
   * A method to set focus on the upload item.
   * @returns {void}
   */
  focus: () => void;

  /**
   * A required unique identifier for the upload item.
   */
  id: string | number;

  /**
   * An optional status of the upload item.
   */
  status?: string;
}

export enum TEST_IDS {
  uploadItem = 'uploadItem',
  mediaBody = 'mediaBody',
  link = 'link',
  action = 'action',
}

const UploadItem = forwardRef<UploadItemRef, UploadItemProps>(
  ({ file, canDelete, onDelete, onDownload, singleFileUpload, onFocus: handleFocus }, ref) => {
    const { formatMessage } = useIntl();
    const { status, filename, error, errors, url } = file;
    const linkRef = useRef<HTMLAnchorElement>(null);
    const buttonRef = useRef<HTMLButtonElement>(null);

    useImperativeHandle<UploadItemRef, UploadItemRef>(ref, () => ({
      focus: (target?: 'button' | 'link'): void => {
        if (target === 'button' && buttonRef.current) {
          buttonRef.current.focus();
        } else if (target === 'link' && linkRef.current) {
          linkRef.current.focus();
        } else if (buttonRef.current) {
          buttonRef.current.focus();
        } else if (linkRef.current) {
          linkRef.current.focus();
        }
      },
      id: file.id,
      status: file.status,
    }));

    const isSucceeded = [Status.SUCCEEDED, undefined].includes(status) && !!url;

    /**
     * We're temporarily reverting to the regular icon components,
     * until the StatusIcon receives 24px sizing. Some misalignment
     * to be expected.
     */
    const getIcon = () => {
      if (error || errors?.length || status === Status.FAILED) {
        return <CrossCircleFill size={24} className="emphasis--negative" />;
      }

      let processIndicator: React.ReactNode;

      switch (status) {
        case Status.PROCESSING:
        case Status.PENDING:
          processIndicator = (
            <ProcessIndicator size={Size.EXTRA_SMALL} status={Status.PROCESSING} />
          );
          break;
        case Status.SUCCEEDED:
        case Status.DONE:
        default:
          processIndicator = <CheckCircleFill size={24} className="emphasis--positive" />;
      }

      return processIndicator;
    };

    const getErrorMessage = (error?: UploadError) =>
      typeof error === 'object' ? error.message : error || formatMessage(MESSAGES.uploadingFailed);

    const getMultipleErrors = (errors?: UploadError[]) => {
      if (!errors?.length) {
        return null;
      }

      if (errors?.length === 1) {
        return getErrorMessage(errors[0]);
      }

      return (
        <ul className="np-upload-input-errors m-b-0">
          {errors.map((error, index) => {
            // eslint-disable-next-line react/no-array-index-key
            return <li key={index}>{getErrorMessage(error)}</li>;
          })}
        </ul>
      );
    };

    const getDescription = () => {
      if (error || errors?.length || status === Status.FAILED) {
        return (
          <Body type={Typography.BODY_DEFAULT_BOLD} className="np-upload-input__text text-negative">
            {errors?.length ? getMultipleErrors(errors) : getErrorMessage(error)}
          </Body>
        );
      }

      switch (status) {
        case Status.PENDING:
          return (
            <Body type={Typography.BODY_DEFAULT} className="np-upload-input__text">
              {formatMessage(MESSAGES.uploading)}
            </Body>
          );
        case Status.PROCESSING:
          return <Body className="np-upload-input__text">{formatMessage(MESSAGES.deleting)}</Body>;
        case Status.SUCCEEDED:
        case Status.DONE:
        default:
          return (
            <Body type={Typography.BODY_DEFAULT_BOLD} className="np-upload-input__text">
              {formatMessage(MESSAGES.uploaded)}
            </Body>
          );
      }
    };

    const getTitle = () => {
      return filename || formatMessage(MESSAGES.uploadedFile);
    };

    const onDownloadFile = (event: React.MouseEvent): void => {
      if (onDownload) {
        event.preventDefault();
        onDownload(file);
      }
    };

    return (
      <div
        className={clsx('np-upload-input__item', { 'is-interactive': isSucceeded && url })}
        data-testid={`${file.id}-${TEST_IDS.uploadItem}`}
      >
        <UploadItemLink
          ref={linkRef}
          url={isSucceeded ? url : undefined}
          singleFileUpload={singleFileUpload}
          data-testid={`${file.id}-${TEST_IDS.link}`}
          onDownload={onDownloadFile}
        >
          <span className="np-upload-input__icon">{getIcon()}</span>
          <div
            className="np-upload-input__item-content"
            data-testid={`${file.id}-${TEST_IDS.mediaBody}`}
          >
            <Body type={Typography.BODY_LARGE} className="np-upload-input__title text-word-break">
              {getTitle()}
            </Body>
            {getDescription()}
          </div>
        </UploadItemLink>
        {canDelete && (
          <div className="np-upload-input__item-action">
            <button
              ref={buttonRef}
              aria-label={formatMessage(MESSAGES.removeFile, { filename })}
              className="np-upload-input__item-button"
              type="button"
              tabIndex={0}
              data-testid={`${file.id}-${TEST_IDS.action}`}
              onClick={() => onDelete()}
              // @ts-expect-error: handleFocus is not a standard FocusEventHandler, but required for parent logic
              onFocus={handleFocus}
            >
              <Bin size={16} />
            </button>
          </div>
        )}
      </div>
    );
  },
);

UploadItem.displayName = 'UploadItem';

export default UploadItem;
