import * as React from 'react';
import {ContentType, FileWithResolvedContentType, getContentType, getMimeTypeDescription} from '@/utils/blob';
import {formatFileSize} from '@/utils/numeral';
import {AudioFilePreview} from '../AudioFilePreview';
import {BinaryFilePreview} from '../BinaryFilePreview';
import {ImageFilePreview} from '../ImageFilePreview';
import {VideoFilePreview} from '../VideoFilePreview';
import {TextFilePreview} from '../TextFilePreview';
import {AudioMiniFilePreview} from '../AudioMiniFilePreview';
import {delegateTriggerEvent} from '@/utils/event';
import clsx from 'clsx';
import {useEnhanced} from '@modal-soft/react-utils';
import {FilePreviewComponent} from '@/categories/blob/react/common';

const FILE_PREVIEW_COMPONENTS: Record<ContentType, FilePreviewComponent> = {
  [ContentType.IMAGE]: ImageFilePreview,
  [ContentType.AUDIO]: AudioFilePreview,
  [ContentType.VIDEO]: VideoFilePreview,
  [ContentType.BINARY]: BinaryFilePreview,
  [ContentType.TEXT]: TextFilePreview,
};

export type FileSelectBoxDerivedElement = HTMLInputElement;

export interface FileSelectBoxProps extends Omit<React.HTMLProps<FileSelectBoxDerivedElement>, '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<FileSelectBoxDerivedElement, FileSelectBoxProps>(
  (
    {
      label = '',
      hint = '',
      border = false,
      block = false,
      enhanced: enhancedProp = false,
      hiddenLabel = false,
      multiple = false,
      onChange,
      disabled = false,
      className,
      id: idProp,
      ...etcProps
    }: FileSelectBoxProps,
    forwardedRef,
  ) => {
    const { enhanced } = useEnhanced({ enhanced: enhancedProp });
    const [fileList, setFileList] = React.useState<FileList>();
    const [lastUpdated, setLastUpdated] = React.useState<number>();
    const defaultRef = React.useRef<HTMLInputElement>(null);
    const ref = forwardedRef ?? defaultRef;
    const labelId = React.useId();
    const defaultId = React.useId();
    const id = idProp ?? defaultId;

    const doSetFileList: React.ChangeEventHandler<HTMLInputElement> = (e) => {
      if (enhancedProp) {
        setFileList(e.currentTarget.files as FileList);
        setLastUpdated(Date.now());
      }
      onChange?.(e);
    };

    const doClearFileList: React.MouseEventHandler<HTMLButtonElement> = (e) => {
      e.preventDefault();
      if (!(typeof ref === 'object' && ref)) {
        return;
      }
      const { current } = ref;
      if (!current) {
        return;
      }

      current.value = '';
      setFileList(undefined);
      setLastUpdated(Date.now());
      setTimeout(() => {
        delegateTriggerEvent('change', current);
      });
    };

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

    const handleDropZone: React.DragEventHandler<HTMLDivElement> = async (e) => {
      cancelEvent(e);
      if (!(typeof ref === 'object' && ref)) {
        return;
      }
      const { current } = ref;
      if (!current) {
        return;
      }
      const { dataTransfer } = e;
      const { files } = dataTransfer;
      if (!(files && files.length > 0)) {
        return;
      }
      setFileList(current.files = files);
      setLastUpdated(Date.now());
      setTimeout(() => {
        delegateTriggerEvent('change', current);
      });
    }

    const filesCount = fileList?.length ?? 0;

    return (
      <div
        className={clsx(
          'relative rounded ring-secondary/50 group',
          'focus-within:ring-4',
          className,
        )}
        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"
          htmlFor={id}
        />
        <input
          {...etcProps}
          id={id}
          disabled={disabled}
          ref={ref}
          type="file"
          className={clsx(
            'peer',
            {
              'sr-only': enhanced,
            }
          )}
          onChange={doSetFileList}
          multiple={multiple}
          data-testid="input"
          aria-labelledby={label ? `${labelId}` : undefined}
        />
        {
          label && (
            <div
              data-testid="label"
              id={labelId}
              className={clsx(
                'absolute z-[1] w-full top-0.5 left-0 pointer-events-none pl-1 text-xxs font-bold peer-disabled:opacity-50 group-focus-within:text-secondary text-primary leading-none bg-negative',
                {
                  'sr-only': hiddenLabel,
                },
              )}
            >
              <div className="w-full whitespace-nowrap h-[1.1em] overflow-hidden text-ellipsis">
                {label}
              </div>
            </div>
          )
        }
        {
          filesCount < 1
          && enhanced
          && 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>
          )
        }
        {
          filesCount > 0
          && enhanced
          && (
            <React.Fragment key={lastUpdated}>
              <div className={`sm:absolute top-0 left-0 w-full h-full pointer-events-none pb-12 box-border overflow-hidden pt-8`}>
                <div
                  className={`pointer-events-auto w-full h-full px-4 pb-4 box-border`}
                >
                  {
                    multiple
                    && (
                      <div className="w-full h-full overflow-auto -mx-4 px-4">
                        <div className="w-full grid gap-4 grid-cols-3">
                          {Array.from(fileList ?? []).map((file: File) => {
                            const f = file as unknown as FileWithResolvedContentType;
                            const fileContentType = getContentType(file.type, file.name);
                            return (
                              <div
                                data-testid="selectedFileItem"
                                key={f?.url ?? f?.name}
                                className={`w-full aspect-square rounded overflow-hidden relative before:absolute before:content-[''] before:bg-current before:top-0 before:left-0 before:w-full before:h-full before:opacity-10`}
                                title={[f.name, getMimeTypeDescription(f.type), formatFileSize(f.size)].join(', ')}
                              >
                                {
                                  fileContentType === ContentType.IMAGE
                                  && typeof f?.url === 'string'
                                  && (
                                    <img
                                      className="block w-full h-full object-center object-cover"
                                      src={f.url}
                                      alt={f.name}
                                      data-testid="preview"
                                    />
                                  )
                                }
                                {
                                  fileContentType === ContentType.AUDIO
                                  && (
                                    <AudioMiniFilePreview file={f} />
                                  )
                                }
                              </div>
                            );
                          })}
                        </div>
                      </div>
                    )
                  }
                  {
                    !multiple
                    && Array.from(fileList ?? []).map((file) => {
                      const f = file as unknown as FileWithResolvedContentType;
                      const fileContentType = getContentType(file.type, file.name);
                      const { [fileContentType]: FilePreviewComponent = BinaryFilePreview } = FILE_PREVIEW_COMPONENTS;
                      return (
                        <div
                          key={f?.url ?? f?.name}
                          className="w-full h-full"
                        >
                          <div
                            data-testid="selectedFileItem"
                            className="h-full w-full p-4 box-border rounded overflow-hidden relative before:absolute before:content-[''] before:bg-current before:top-0 before:left-0 before:w-full before:h-full before:opacity-10"
                          >
                            <FilePreviewComponent
                              className="w-full h-full relative"
                              file={f}
                              enhanced={enhanced}
                              disabled={disabled}
                            />
                          </div>
                        </div>
                      )
                    })
                  }
                </div>
              </div>
              <div className="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">
                  <label
                    data-testid="reselect"
                    htmlFor={id}
                    className="flex w-full h-full bg-negative text-primary disabled:text-primary focus:text-secondary active:text-tertiary items-center justify-center leading-none gap-4 select-none"
                  >
                    <span
                      className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
                    >
                      Reselect
                    </span>
                  </label>
                </div>
                <div className="w-0 flex-auto flex flex-col items-center justify-center h-full">
                  <button
                    data-testid="clear"
                    type="button"
                    onClick={doClearFileList}
                    className="flex w-full h-full bg-negative text-primary disabled:text-primary focus:text-secondary active:text-tertiary items-center justify-center leading-none gap-4 select-none focus:outline-0"
                  >
                    <span
                      className="block uppercase font-bold h-[1.1em] w-full whitespace-nowrap overflow-hidden text-ellipsis font-semi-expanded"
                    >
                      Clear
                    </span>
                  </button>
                </div>
              </div>
            </React.Fragment>
          )
        }
        {
          border && (
            <span
              data-testid="border"
              className="absolute z-[1] peer-disabled:opacity-50 inset-0 rounded-inherit border-2 border-primary pointer-events-none group-focus-within:border-secondary"
            />
          )
        }
      </div>
    );
  }
);

FileSelectBox.displayName = 'FileSelectBox';
