import * as React from 'react';
import { delegateTriggerEvent, useClientSide } from '@modal-sh/react-utils';
import clsx from 'clsx';

export interface CommonPreviewProps<F extends Partial<File> = Partial<File>> {
  file?: F;
  disabled?: boolean;
  enhanced?: boolean;
  mini?: boolean;
}

export type FileSelectBoxDerivedElement = HTMLInputElement;

export interface FileSelectBoxProps<
  F extends Partial<File> = Partial<File>,
  P extends CommonPreviewProps<F> = CommonPreviewProps<F>
> 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,
  previewComponent: React.ElementType<P>,
}

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,
      previewComponent: FilePreviewComponent,
      ...etcProps
    }: FileSelectBoxProps,
    forwardedRef,
  ) => {
    const { clientSide } = useClientSide({ clientSide: 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',
          block && 'w-full',
          !block && 'inline-block min-w-64',
          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': clientSide,
            }
          )}
          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
          && clientSide
          && 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
          && clientSide
          && (
            <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, i) => {
                            return (
                              <div
                                key={`${file.name}:${i}`}
                                data-testid="selectedFileItem"
                                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`}
                              >
                                <FilePreviewComponent
                                  file={file}
                                  enhanced={clientSide}
                                  disabled={disabled}
                                  mini
                                />
                              </div>
                            );
                          })}
                        </div>
                      </div>
                    )
                  }
                  {
                    !multiple
                    && Array.from(fileList ?? []).map((file, i) => {
                      return (
                        <div
                          key={`${file.name}:${i}`}
                          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="w-full h-full relative"
                            >
                              <FilePreviewComponent
                                file={file}
                                enhanced={clientSide}
                                disabled={disabled}
                              />
                            </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={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';
