import * as React from 'react';
import { useClientSide, useFallbackId, useProxyInput } 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' | '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,
  /**
   * Should the component be enhanced?
   */
  enhanced?: boolean,
  /**
   * Is the label hidden?
   */
  hiddenLabel?: boolean,
  previewComponent?: React.ElementType<P>,
}

export const FileSelectBoxDefaultPreviewComponent: React.FC<CommonPreviewProps> = ({
  file,
  mini,
}) => (
  <>
    <div className="w-full whitespace-nowrap overflow-hidden text-ellipsis">
      {file?.name ?? (
        <span className="opacity-50">
          File
        </span>
      )}
    </div>
    {!mini && (
      <>
        {typeof file?.type === 'string' && (
          <div className="w-full whitespace-nowrap overflow-hidden text-ellipsis">{file?.type}</div>
        )}
        {typeof file?.size === 'number' && (
          <div className="w-full whitespace-nowrap overflow-hidden text-ellipsis">
            {new Intl.NumberFormat(undefined, {
              style: 'unit',
              unit: 'byte',
              unitDisplay: 'long',
            }).format(file.size ?? 0)}
          </div>
        )}
        {typeof file?.lastModified === 'number' && (
          <div className="w-full whitespace-nowrap overflow-hidden text-ellipsis">
            <time dateTime={new Date(file.lastModified).toISOString()}>
              {new Date(file.lastModified).toDateString()}
            </time>
          </div>
        )}
      </>
    )}
  </>
);

export const FileSelectBox = React.forwardRef<FileSelectBoxDerivedElement, FileSelectBoxProps>((
  {
    label = '',
    hint = '',
    border = false,
    block = false,
    enhanced: enhancedProp = false,
    hiddenLabel = false,
    multiple = false,
    disabled = false,
    className,
    onChange,
    id: idProp,
    placeholder,
    previewComponent: FilePreviewComponent = FileSelectBoxDefaultPreviewComponent,
    style,
    ...etcProps
  },
  forwardedRef,
) => {
  const { clientSide } = useClientSide({ clientSide: enhancedProp });
  const [fileList, setFileList] = React.useState<FileList>();
  const [lastUpdated, setLastUpdated] = React.useState<number>();
  const clearFileListRef = React.useRef(false);
  const { defaultRef, handleChange: doSetFileList } = useProxyInput<
    React.ChangeEvent<FileSelectBoxDerivedElement>
    | React.MouseEvent<HTMLButtonElement>
    | React.DragEvent<HTMLDivElement>
    | React.KeyboardEvent<FileSelectBoxDerivedElement>,
    FileSelectBoxDerivedElement
  >({
    forwardedRef,
    valueSetterFn: (e) => {
      if (e.type === 'click') {
        // delete
        const fileInput = defaultRef.current as FileSelectBoxDerivedElement;
        clearFileListRef.current = true;
        fileInput.value = '';
        setFileList(undefined);
        setLastUpdated(Date.now());
      } else if (e.type === 'drop') {
        // drop file
        const fileInput = defaultRef.current as FileSelectBoxDerivedElement;
        const { dataTransfer } = e as React.DragEvent<HTMLDivElement>;
        const { files } = dataTransfer;
        if (files && files.length > 0) {
          setFileList(fileInput.files = files);
          setLastUpdated(Date.now());
        }
      } else if (e.type === 'keyup') {
        const {
          currentTarget: fileInput,
          code,
        } = e as React.KeyboardEvent<FileSelectBoxDerivedElement>;
        if (code === 'Backspace' || code === 'Delete') {
          clearFileListRef.current = true;
          fileInput.value = '';
          setFileList(undefined);
          setLastUpdated(Date.now());
        }
      }
    },
  });
  const ref = forwardedRef ?? defaultRef;
  const labelId = React.useId();
  const id = useFallbackId(idProp);

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

  const filesCount = fileList?.length ?? 0;

  const handleFileChange: React.ChangeEventHandler<FileSelectBoxDerivedElement> = (e) => {
    const { currentTarget } = e;
    if (clientSide && currentTarget.files && currentTarget.files.length > 0) {
      setFileList(currentTarget.files);
      setLastUpdated(Date.now());
      onChange?.(e);
      return;
    }
    if (clientSide && clearFileListRef.current) {
      clearFileListRef.current = false;
      setFileList(undefined);
      setLastUpdated(Date.now());
      onChange?.(e);
      return;
    }
    e.preventDefault();
    e.currentTarget.files = fileList ?? null;
  };

  const handleKeyUp: React.KeyboardEventHandler<FileSelectBoxDerivedElement> = (e) => {
    const { code } = e;
    if (code === 'Backspace' || code === 'Delete') {
      doSetFileList(e);
    }
  };

  return (
    <div
      className={clsx(
        'relative rounded ring-secondary/50 group',
        'focus-within:ring-4',
        block && 'flex w-full',
        !block && 'inline-flex w-64 min-h-16 justify-center items-center',
        className,
        clientSide && 'resize-y',
      )}
      onDragEnter={clientSide ? cancelEvent : undefined}
      onDragOver={clientSide ? cancelEvent : undefined}
      onDrop={clientSide ? (e) => {
        cancelEvent(e);
        doSetFileList(e);
      } : undefined}
      data-testid="root"
      style={{
        height: clientSide ? 64 : undefined,
        ...(style ?? {}),
      }}
    >
      {clientSide && (
        <label
          className={clsx(
            'flex items-center justify-center absolute top-0 left-0 w-full h-full cursor-pointer',
            (fileList?.length ?? 0) > 0 && 'opacity-0',
          )}
          data-testid="clickArea"
          htmlFor={id}
        >
          {placeholder}
        </label>
      )}
      <input
        {...etcProps}
        id={id}
        disabled={disabled}
        ref={typeof ref === 'function' ? defaultRef : ref}
        type="file"
        onKeyUp={clientSide ? handleKeyUp : undefined}
        className={clsx(
          'peer box-border focus:outline-0 px-4 pt-2 block resize-y min-h-16 cursor-pointer disabled:cursor-not-allowed file:bg-transparent file:text-primary file:block file:font-bold file:font-semi-expanded file:uppercase file:p-0 file:border-0 group-focus-within:file:text-secondary',
          {
            'sr-only': clientSide,
            'h-full w-full': !clientSide,
          },
        )}
        onChange={clientSide ? handleFileChange : onChange}
        multiple={multiple}
        data-testid="input"
        aria-labelledby={label ? `${labelId}` : undefined}
        style={{
          height: clientSide ? undefined : 256,
        }}
      />
      {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) => (
                      <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) => (
                <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 cursor-pointer group-focus-within: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"
                name="action"
                value="clear"
                onClick={doSetFileList}
                tabIndex={-1}
                className="flex w-full h-full bg-negative text-primary disabled:text-primary group-focus-within:text-secondary group-focus-within: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';

FileSelectBox.defaultProps = {
  border: false,
  block: false,
  enhanced: false,
  hiddenLabel: false,
  label: undefined,
  hint: undefined,
  previewComponent: FileSelectBoxDefaultPreviewComponent,
};
