import * as React from 'react';
import {AugmentedFile, augmentFile, ContentType, getMimeTypeDescription} from '@/utils/blob';
import { FilePreview as FilePreviewComponent} from '../FilePreview';
import {formatFileSize} from '@/utils/numeral';
import {AudioMiniFilePreview} from '@tesseract-design/web-blob-react';
import {delegateTriggerChangeEvent} from '@/utils/event';
import clsx from 'clsx';

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,
}

const useFilePreviews = (fileList?: FileList) => {
  const [selectedFiles, setSelectedFiles] = React.useState([] as AugmentedFile[]);
  React.useEffect(() => {
    const loadFilePreviews = async (fileList: FileList) => {
      const files = Array.from(fileList);
      return Promise.all(
        files.map(async (f) => {
          const augmentedFile = await augmentFile(f);
          if (augmentedFile.resolvedType === ContentType.TEXT && augmentedFile.metadata?.scheme) {
            await import(`prismjs/components/prism-${augmentedFile.metadata.scheme}`);
          }
          return augmentedFile;
        })
      );
    }

    if (fileList) {
      loadFilePreviews(fileList).then((fileResult) => {
        setSelectedFiles(fileResult);
      });
    }
  }, [fileList]);

  return React.useMemo(() => ({
    files: selectedFiles,
  }), [selectedFiles]);
}

const FilePreviewGrid = ({
  fileList,
}: { fileList?: FileList }) => {
  const { files } = useFilePreviews(fileList);

  return (
    <div className="w-full h-full overflow-auto -mx-4 px-4">
      <div className="w-full grid gap-4 grid-cols-3">
        {files.map((f, i) => (
          <div
            data-testid="selectedFileItem"
            key={i}
            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(', ')}
          >
            {
              f.resolvedType === ContentType.IMAGE
              && typeof f.metadata?.previewUrl === 'string'
              && (
                <img
                  className="block w-full h-full object-center object-cover"
                  src={f.metadata.previewUrl}
                  alt={f.name}
                  data-testid="preview"
                />
              )
            }
            {
              f.resolvedType === ContentType.AUDIO
              && (
                <AudioMiniFilePreview file={f} />
              )
            }
          </div>
        ))}
      </div>
    </div>
  )
}

export const FileSelectBox = React.forwardRef<HTMLInputElement, FileButtonProps>(
  (
    {
      label = '',
      hint = '',
      border = false,
      block = false,
      enhanced = false,
      hiddenLabel = false,
      multiple = false,
      onChange,
      disabled = false,
      className,
      id: idProp,
      ...etcProps
    }: FileButtonProps,
    forwardedRef,
  ) => {
    const [renderEnhanced, setRenderEnhanced] = React.useState(false);
    const [fileList, setFileList] = React.useState<FileList>();
    const [lastSelectedFileAt, setLastSelectedFileAt] = 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 addFile: React.ChangeEventHandler<HTMLInputElement> = (e) => {
      if (!enhanced) {
        onChange?.(e);
        return;
      }

      setFileList(e.currentTarget.files as FileList);
      setLastSelectedFileAt(Date.now());
      onChange?.(e);
    };

    const deleteFiles: React.MouseEventHandler<HTMLButtonElement> = () => {
      if (typeof ref === 'object' && ref.current) {
        ref.current.value = '';
        setFileList(undefined);
        setTimeout(() => {
          delegateTriggerChangeEvent(ref.current as HTMLInputElement);
        })
      }
    };

    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;
        if (!(files && files.length > 0)) {
          return;
        }
        setFileList(ref.current.files = files);
        delegateTriggerChangeEvent(ref.current);
      }
    }

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

    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': renderEnhanced,
            }
          )}
          onChange={addFile}
          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>
          )
        }
        {
          (fileList?.length ?? 0) < 1
          && renderEnhanced
          && 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>
          )
        }
        {
          (fileList?.length ?? 0) > 0
          && renderEnhanced
          && (
            <>
              <div className={`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`}
                  key={lastSelectedFileAt}
                >
                  {
                    multiple
                    && (
                      <FilePreviewGrid fileList={fileList} />
                    )
                  }
                  {
                    !multiple
                    && (
                      <div
                        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`}>
                          <div className="relative">
                            <FilePreviewComponent
                              fileList={fileList}
                            />
                          </div>
                        </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={deleteFiles}
                    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"
                    >
                      {multiple ? 'Clear' : 'Delete'}
                    </span>
                  </button>
                </div>
              </div>
            </>
          )
        }
        {
          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';
