import { useEffect, useRef } from 'react';
import NextImage from 'next/image';

import type { WithSpacingProps } from '~/src/lib/hocs/withSpacing';
import type { CSSProperties } from 'react';
import type { TransitionInOut2Api } from '../TransitionInOut2';

import withSpacing from '~/src/lib/hocs/withSpacing';
import TransitionInOut2 from '../TransitionInOut2';
import onceImageLoaded from './onceImageLoaded';
import { getAspect } from './utils';

export interface ImageProps extends WithSpacingProps {
  src: string | undefined;
  alt?: string;
  load?: boolean;
  fillParent?: boolean;
  cover?: boolean;
  objectPosition?: string;
  isLazy?: boolean;
  size?: [number, number];
  aspect?: number;
  style?: CSSProperties;
  imgStyle?: CSSProperties;
  fadeInDuration?: number;
  borderRadius?: string;
  testId?: string;
}

const Image = withSpacing<ImageProps>(
  ({
    src,
    alt,
    isLazy = true,
    size,
    className,
    style,
    imgStyle,
    aspect,
    fillParent,
    objectPosition = '50% 50%',
    fadeInDuration = 600,
    borderRadius,
    cover,
    testId,
  }) => {
    const imageRef = useRef<HTMLImageElement>(null);
    const transitionRef = useRef<TransitionInOut2Api>(null);
    const imageSrcRef = useRef(src);
    const aspectResolved = aspect || getAspect(size);

    // fix fading image in within parent w/ border-radius needs
    // z-index higher than child to prevent corners bleeding out of border
    const rootStyle: CSSProperties = {
      position: 'relative',
      zIndex: 1,
      overflow: 'hidden',
      borderRadius,
      ...style,
    };

    if (fillParent) {
      rootStyle.position = 'absolute';
      rootStyle.width = '100%';
      rootStyle.height = '100%';
      rootStyle.left = rootStyle.top = 0;
    }

    const innerStyle: CSSProperties = {
      width: '100%',
      paddingBottom: aspectResolved ? `${aspectResolved * 100}%` : '',
    };

    if (cover) {
      innerStyle.width = '100%';
      innerStyle.height = '100%';
      innerStyle.boxSizing = 'border-box';
    }

    const setActiveImageSrc = (src: string | undefined) => {
      if (!imageRef.current) {
        return;
      }

      imageSrcRef.current = src;
      imageRef.current.src = src ?? '';
    };

    // first mount
    useEffect(() => {
      if (!src) {
        transitionRef.current?.setVisible(false);
        return;
      }

      // fade the image in once it's loaded
      onceImageLoaded(imageRef.current).then(() => {
        transitionRef.current?.setVisible(true);
      });
    }, []);

    // when the src changes gracefully fade-out then back in
    useEffect(() => {
      const srcChanged = src !== imageSrcRef.current;

      if (!srcChanged) {
        return;
      }

      // fade out the current image
      transitionRef.current?.setVisible(false).then(() => {
        setActiveImageSrc(src);

        // fade the image in once it's loaded
        onceImageLoaded(imageRef.current).then(() => {
          transitionRef.current?.setVisible(true);
        });
      });
    }, [src]);

    return (
      <div className={className} style={rootStyle}>
        <TransitionInOut2
          apiRef={transitionRef}
          duration={fadeInDuration}
          style={innerStyle}
          // When caller requests no fade-in duration we render the image
          // whether loaded or not, otherwise we have to wait for raf before image
          // is shown. When image is likely to be cached or doesn't have undesirable
          // partially loaded state (eg. svg) then we can render straight away.
          isVisibleInitial={!fadeInDuration}
        >
          {(() => {
            const style: CSSProperties = {
              display: 'block',
              width: '100%',
              height: '100%',
              objectFit: 'cover',
              objectPosition,
            };

            if (aspectResolved) {
              style.position = 'absolute';
              style.top = style.bottom = style.left = style.right = 0;
              style.zIndex = 0;
            }

            return (
              <NextImage
                ref={imageRef}
                src={imageSrcRef.current ?? ''}
                loader={() => imageSrcRef.current ?? ''}
                style={{ ...style, ...imgStyle }}
                width={0}
                height={0}
                loading={isLazy ? 'lazy' : undefined}
                draggable={false}
                alt={alt ?? ''}
                // Avoids being rate-limited when loading third-party images
                // we experienced this when loading users' google profile images
                // https://stackoverflow.com/a/61042200/516629
                referrerPolicy="no-referrer"
                data-testid={testId}
              />
            );
          })()}
        </TransitionInOut2>
      </div>
    );
  }
);

export default Image;
