import * as React from 'react';
import * as ButtonBase from '@tesseract-design/web-base-button';

interface FileWithPreview extends File {
  previewUrl?: string;
}

export interface FileButtonProps extends Omit<React.HTMLProps<HTMLInputElement>, 'size' | 'type' | 'style' | 'label' | 'list'> {
  /**
   * Should the component display a border?
   */
  border?: boolean,
  /**
   * Should the component occupy the whole width of its parent?
   */
  block?: boolean,
  /**
   * Short textual description indicating the nature of the component's value.
   */
  label?: React.ReactNode,
  /**
   * Short textual description as guidelines for valid input values.
   */
  hint?: React.ReactNode,
  enhanced?: boolean,
  /**
   * Is the label hidden?
   */
  hiddenLabel?: boolean,
}

export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>(
  (
    {
      label = '',
      hint = '',
      border = false,
      block = false,
      enhanced = false,
      hiddenLabel = false,
      multiple = false,
      onChange,
      disabled = false,
      className: _className,
      placeholder: _placeholder,
      as: _as,
      ...etcProps
    }: FileButtonProps,
    forwardedRef,
  ) => {
    const [isEnhanced, setIsEnhanced] = React.useState(false);
    const [selectedFiles, setSelectedFiles] = React.useState([] as Partial<FileWithPreview>[]);
    const defaultRef = React.useRef<HTMLInputElement>(null);
    const ref = forwardedRef ?? defaultRef;

    const handleAddFile = async (fileList: FileList) => {
      const files = Array.from(fileList);
      const fileResult = await Promise.all(
        files.map((f) => new Promise<Partial<FileWithPreview>>((resolve, reject) => {
          if (f.type.startsWith('image/')) {
            const reader = new FileReader();
            reader.addEventListener('error', () => {
              reject();
            });

            reader.addEventListener('load', (loadEvent) => {
              const target = loadEvent.target as FileReader;
              resolve({
                ...f,
                name: f.name,
                type: f.type,
                size: f.size,
                lastModified: f.lastModified,
                previewUrl: target.result as string,
              });
            });

            reader.readAsDataURL(f);
            return;
          }

          resolve(f);
        }))
      );

      setSelectedFiles(fileResult);
    }

    const addFile: React.ChangeEventHandler<HTMLInputElement> = async (e) => {
      if (!enhanced) {
        onChange?.(e);
        return;
      }

      const currentTarget = e.currentTarget;
      const fileList = currentTarget.files as FileList;
      await handleAddFile(fileList);
      onChange?.(e);
    };

    const deleteFiles: React.MouseEventHandler<HTMLButtonElement> = () => {
      if (typeof ref === 'object' && ref.current) {
        ref.current.value = '';
        setSelectedFiles([]);
      }
    };

    const cancelEvent = (e: React.DragEvent) => {
      e.stopPropagation();
      e.preventDefault();
    }

    const handleDropZone: React.DragEventHandler<HTMLDivElement> = async (e) => {
      cancelEvent(e);
      const { dataTransfer } = e;
      if (typeof ref === 'object' && ref.current) {
        const { files } = dataTransfer;
        ref.current.files = files;
        await handleAddFile(files);
        ref.current.dispatchEvent(new Event('change'));
      }
    }

    React.useEffect(() => {
      setIsEnhanced(enhanced);
    }, [enhanced]);

    return (
      <div
        className="rounded relative min-h-[300px] overflow-hidden"
        onDragEnter={cancelEvent}
        onDragOver={cancelEvent}
        onDrop={handleDropZone}
        data-testid="root"
      >
        <label
          className="block absolute top-0 left-0 w-full h-full cursor-pointer"
          data-testid="clickArea"
        >
          <input
            {...etcProps}
            disabled={disabled}
            ref={ref}
            type="file"
            className={`${enhanced ? 'sr-only' : ''}`}
            onChange={addFile}
            multiple={multiple}
            data-testid="input"
          />
        </label>
        {
          border && (
            <span
              data-testid="border"
              className={
                ButtonBase.Border({
                  size: ButtonBase.ButtonSize.MEDIUM,
                  border: true,
                  variant: ButtonBase.ButtonVariant.OUTLINE,
                  disabled,
                  compact: false,
                  menuItem: false,
                  block: true,
                })
              }
            />
          )
        }
        {
          label
          && !hiddenLabel
          && (
            <div
              data-testid="label"
              className="absolute top-0 left-0"
            >
              {label}
            </div>
          )
        }
        {
          selectedFiles.length < 1
          && isEnhanced
          && hint
          && (
            <div
              data-testid="hint"
              className="absolute top-0 left-0 w-full h-full pointer-events-none box-border overflow-hidden pt-4"
            >
              <div className="flex items-center justify-center w-full h-full">
                {hint}
              </div>
            </div>
          )
        }
        {
          selectedFiles.length > 0
          && isEnhanced
          && (
            <>
              <div className={`absolute top-0 left-0 w-full h-full pointer-events-none pb-12 box-border overflow-hidden ${multiple ? 'pt-8' : 'pt-4'}`}>
                <div className={`pointer-events-auto w-full h-full overflow-auto ${multiple ? '' : 'pt-4 box-border'}`}>
                  <div
                    className={`w-full px-4 box-border ${multiple ? 'grid gap-4 grid-cols-3 pb-4' : 'h-full'}`}
                  >
                    {selectedFiles.map((f, i) => (
                      <div data-testid="selectedFileItem" key={i} className={`${!multiple ? 'h-full flex gap-4 w-full' : 'w-full aspect-square'}`}>
                        <div className={`h-full ${multiple ? 'w-full' : 'w-1/3 flex-shrink-0'}`}>
                          {
                            f.previewUrl
                            && (
                              <img
                                className="block w-full h-full object-center object-cover"
                                src={f.previewUrl}
                                alt={f.name}
                                data-testid="preview"
                              />
                            )
                          }
                        </div>
                        {
                          !multiple
                          && (
                            <dl className="w-2/3 flex-shrink-0 m-0" data-testid="infoBox">
                              <div className="w-full">
                                <dt className="sr-only">
                                  Name
                                </dt>
                                <dd className="m-0 w-full text-ellipsis overflow-hidden">
                                  {f.name}
                                </dd>
                              </div>
                              <div className="w-full">
                                <dt className="sr-only">
                                  Size
                                </dt>
                                <dd className="m-0 w-full text-ellipsis overflow-hidden">
                                  {f.size}
                                </dd>
                              </div>
                              <div className="w-full">
                                <dt className="sr-only">
                                  Type
                                </dt>
                                <dd className="m-0 w-full text-ellipsis overflow-hidden">
                                  {f.type}
                                </dd>
                              </div>
                            </dl>
                          )
                        }
                      </div>
                    ))}
                  </div>
                </div>
              </div>
              <div className="pointer-events-none absolute bottom-0 left-0 w-full text-center h-12 box-border flex">
                <div className="w-0 flex-auto flex flex-col items-center justify-center h-full">
                  <span
                    className={ButtonBase.Button({
                      size: ButtonBase.ButtonSize.MEDIUM,
                      border: false,
                      block: true,
                      variant: ButtonBase.ButtonVariant.OUTLINE,
                      disabled,
                      compact: false,
                      menuItem: false,
                    })}
                  >
                    Reselect Files
                  </span>
                </div>
                <div className="pointer-events-auto w-0 flex-auto flex flex-col items-center justify-center h-full">
                  <button
                    data-testid="clear"
                    type="button"
                    onClick={deleteFiles}
                    className={ButtonBase.Button({
                      size: ButtonBase.ButtonSize.MEDIUM,
                      border: false,
                      block: true,
                      variant: ButtonBase.ButtonVariant.OUTLINE,
                      disabled,
                      compact: false,
                      menuItem: false,
                    })}
                  >
                    {multiple ? 'Clear Files' : 'Delete File'}
                  </button>
                </div>
              </div>
            </>
          )
        }
      </div>
    );
  }
);

FileSelectBox.displayName = 'FileSelectBox';
