import { memo, useEffect, useRef, useState } from 'react';
import Head from 'next/head';
import NextImage from 'next/image';
import Debug from 'debug';

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

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

const debug = Debug('songwhip/BackgroundImage');

interface BackgroundImageProps extends ImageProps, WithSpacingProps {
  gradient?: string;
  withPreload?: boolean;
  backgroundColor?: string;
  backgroundSize?: string;
  imageOpacity?: number;
}

export const BackgroundImage = ({
  src,
  alt,
  size,
  className,
  style,
  objectPosition = '50% 50%',
  fadeInDuration = 1000,
  backgroundColor = '#000',
  backgroundSize = 'cover',
  borderRadius,
  gradient,
  withPreload,
  imageOpacity = 1,
  testId,
  imgStyle,
}: BackgroundImageProps) => {
  const dynamicStyleRef = useRef<CSSProperties>({ opacity: 0 });
  const nodeRef = useRef<HTMLDivElement>(null);
  const [activeSrc, setActiveSrc] = useState(src);
  const aspect = getAspect(size);

  debug('render', src);

  const rootStyle: CSSProperties = {
    backgroundColor,
    ...style,
  };

  const innerStyle: CSSProperties = {
    ...imgStyle,
    position: 'relative',
    width: '100%',
    height: '100%',
    paddingBottom: aspect ? `${aspect * 100}%` : '',
    backgroundPosition: objectPosition,
    backgroundSize,
    transitionProperty: 'opacity',
    transitionDuration: `${fadeInDuration}ms`,
    borderRadius,
    ...dynamicStyleRef.current,
  };

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

    if (!srcChanged || !style) {
      debug('no changed');
      return;
    }

    debug('src changed', src);

    // fade out the current image
    style.opacity = '0';

    // set the new src as 'active' to trigger fade-in
    const timeout = setTimeout(() => {
      setActiveSrc(src);
    }, fadeInDuration);

    return () => {
      clearTimeout(timeout);
    };
  }, [src]);

  return (
    <div
      className={className}
      style={rootStyle}
      title={alt}
      data-testid={testId}
    >
      <div
        ref={nodeRef}
        style={{
          ...innerStyle,
          backgroundImage: toBackgroundImage({ src: activeSrc, gradient }),
        }}
      />
      <NextImage
        src={activeSrc || ''}
        loader={() => activeSrc || ''}
        width={0}
        height={0}
        alt=""
        onLoad={() => {
          if (nodeRef.current)
            nodeRef.current.style.opacity = `${imageOpacity}`;
        }}
      />
      {withPreload && (
        <Head>
          <link rel="preload" href={activeSrc} as="image" />
        </Head>
      )}
    </div>
  );
};

const toBackgroundImage = ({
  src,
  gradient,
}: {
  src: string | undefined;
  gradient?: string;
}) => {
  if (!src) return;

  if (gradient) return `${gradient}, url(${src})`;

  // hack to trick LCP into thinking it's a background image
  return `url(${src}), linear-gradient(0deg,rgba(0,0,0,.01) 0%,rgba(0,0,0,.01) 100%)`;
};

const BackgroundImageContainer = memo(
  withSpacing<BackgroundImageProps>(BackgroundImage)
);

export default BackgroundImageContainer;
