import {
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
} from 'react';
import classNames from 'classnames';

import type { FC, ReactNode, RefObject } from 'react';
import type { CardProps } from '../Card';
import type { FilePickerApi, FilePickerProps } from '../FilePicker';

import { useI18n } from '~/src/lib/i18n';
import {
  createImage,
  getImageSize,
  testImageHasTransparency,
} from '~/src/lib/utils/image';
import Box from '../Box';
import Card from '../Card';
import CardAction from '../Card/CardAction';
import FadeOnMount from '../FadeOnMount';
import FilePicker from '../FilePicker';
import Gradient from '../Gradient';
import CrossIcon from '../Icon/CrossIcon';
import ImageIcon from '../Icon/ImageIcon';
import Image from '../Image';
import Loading from '../Loading';
import Text from '../Text';

export interface ImagePickerApi {
  clear: () => void;
  isEmpty: () => boolean;
}

export type ImagePickerProps = {
  apiRef?: RefObject<ImagePickerApi | null>;
  defaultImage?: string;
  image?: string;
  label?: string;
  imageSize?: {
    width: number;
    height: number;
  };
  renderPreview?(): ReactNode;
  onInit?(): void;
  onClear?(): void;
  isLoading?: boolean;
  onChange?(params: {
    file: File;
    imageSrc: string;
    hasTransparency: boolean;
    size: {
      width: number;
      height: number;
    };
  }): Promise<boolean | void> | boolean | void;
  accept: ('jpeg' | 'png')[];
  validationMessage?: string;
  isRequired?: boolean;
  name?: string;
  imageOpacity?: number;

  /**
   * If defined the image will define the height of the picker
   */
  sizeToImageAspect?: boolean;
} & CardProps;

const ImagePicker: FC<ImagePickerProps> = ({
  apiRef,
  defaultImage,
  image,
  imageSize: initialImageSize,
  accept,
  renderPreview,
  isLoading,
  isRequired = true,
  validationMessage,
  name,
  onInit,
  onChange,
  onClear,
  sizeToImageAspect,
  testId,
  imageOpacity = 1,
  label,
  height = '12rem',
  ...cardProps
}) => {
  const filePickerApiRef = useRef<FilePickerApi>(null);
  const hiddenInputRef = useRef<HTMLInputElement>(null);
  const { t } = useI18n();

  const [imageSrc, setImageSrc] = useState(image || defaultImage);
  const [imageSize, setImageSize] = useState<
    { width: number; height: number } | undefined
  >(initialImageSize);

  const customPreview = renderPreview?.();
  const hasImage = !!(imageSrc || customPreview);

  useEffect(() => {
    if (image) {
      setImageSrc(image);
    } else if (!imageSrc) {
      setImageSrc(defaultImage);
    }
  }, [image, defaultImage]);

  const onFileChange = useCallback<FilePickerProps['onChange']>(
    async ({ file }) => {
      if (!file) return;

      const prevImageSrc = image;
      const prevImageSize = imageSize;
      const imageSrc = URL.createObjectURL(file);
      const newImageSize = await getImageSize(file);
      const imageEl = await createImage(imageSrc);
      const hasTransparency = testImageHasTransparency(imageEl);

      setImageSrc(imageSrc);
      setImageSize(newImageSize);

      const isOk =
        (await onChange?.({
          file,
          imageSrc,
          size: newImageSize,
          hasTransparency,
        })) ?? true;

      if (!isOk) {
        setImageSrc(prevImageSrc);
        setImageSize(prevImageSize);
      }
    },
    [onChange]
  );

  const clear = useCallback(() => {
    filePickerApiRef.current?.clear();

    // clear the currently rendered image so that if a `defaultImage` or `image`
    // prop is defined then the image will be rendered again
    setImageSrc(undefined);
  }, [defaultImage]);

  useImperativeHandle(apiRef, () => ({
    clear,
    isEmpty() {
      return !imageSrc;
    },
  }));

  useEffect(() => {
    if (!isRequired || !hiddenInputRef.current) return;

    hiddenInputRef.current.setCustomValidity(
      validationMessage ?? 'An image is required'
    );
  }, [isRequired, validationMessage]);

  useEffect(() => {
    onInit?.();
  }, []);

  return (
    <Card
      positionRelative
      testId={classNames('imagePicker', testId)}
      {...cardProps}
      pointerEvents={isLoading ? 'none' : undefined}
    >
      <FilePicker
        accept={accept.map((type) => `image/${type}`)}
        onChange={onFileChange}
        apiRef={filePickerApiRef}
        testId={testId}
        tabIndex={-1}
        name={name}
      >
        <Box
          centerContent
          height={sizeToImageAspect && imageSize ? undefined : height}
          positionRelative
        >
          {isLoading && (
            <FadeOnMount>
              <Loading
                coverParent
                height="100.33%"
                centerContent
                size="3.2rem"
                style={{ backgroundColor: 'rgba(0,0,0,0.4)' }}
                zIndex={2}
              />
            </FadeOnMount>
          )}
          {(() => {
            if (customPreview) return customPreview;

            if (imageSrc) {
              return (
                <div style={{ opacity: imageOpacity, width: '100%' }}>
                  <Image
                    alt=""
                    src={imageSrc}
                    zIndex={1}
                    // HACK: this seems to overflow the parent element and mess with clicks outside
                    // pointer-events none fixes this.
                    pointerEvents="none"
                    fillParent={!sizeToImageAspect}
                    aspect={
                      imageSize ? imageSize.height / imageSize.width : undefined
                    }
                    cover
                  />
                </div>
              );
            }

            return (
              <Box flexBox flexColumn gap="0.5rem" centerContent>
                <ImageIcon size="4rem" color="#555" isCentered />
                <Text size="1.4rem" isBold>
                  {label || t('app.actions.pickImage')}
                </Text>
                {!isRequired && (
                  <Text size="1.3rem" color="#555">
                    {t('app.labels.optional')}
                  </Text>
                )}
              </Box>
            );
          })()}
        </Box>
        {hasImage && (
          <CardAction text="Change image" tag="div" isDisabled={isLoading} />
        )}
      </FilePicker>
      {onClear && (
        <FadeOnMount>
          <Gradient
            from="rgba(0,0,0,0.4)"
            to="rgba(0,0,0,0)"
            positionAbsolute
            top={0}
            left={0}
            right={0}
            flexRow
          >
            <CrossIcon
              testId="clearImageButton"
              onClick={onClear}
              margin="0 0 0 auto"
              padding="1rem 1rem"
              withHoverOpacityFrom={0.7}
              size="2.9rem"
            />
          </Gradient>
        </FadeOnMount>
      )}
      {!hasImage && (
        <input
          type="checkbox"
          ref={hiddenInputRef}
          tabIndex={-1}
          required={isRequired}
          style={{
            position: 'absolute',
            bottom: 0,
            left: '50%',
            width: 1,
            height: 1,
            border: 0,
            outline: 0,
            margin: 0,
            pointerEvents: 'none',
          }}
        />
      )}
    </Card>
  );
};

export default ImagePicker;
