import { getBlurHashAverageColor } from 'fast-blurhash';
import { Fragment } from 'preact';
import {
  useCallback,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'preact/hooks';
import QuickPinchZoom, { make3dTransformValue } from 'react-quick-pinch-zoom';

import formatDuration from '../utils/format-duration';
import mem from '../utils/mem';
import states from '../utils/states';

import Icon from './icon';
import Link from './link';

const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); // https://stackoverflow.com/a/23522755

/*
Media type
===
unknown = unsupported or unrecognized file type
image = Static image
gifv = Looping, soundless animation
video = Video clip
audio = Audio track
*/

const dataAltLabel = 'ALT';
const AltBadge = (props) => {
  const { alt, lang, index, ...rest } = props;
  if (!alt || !alt.trim()) return null;
  return (
    <button
      type="button"
      class="alt-badge clickable"
      {...rest}
      onClick={(e) => {
        e.stopPropagation();
        e.preventDefault();
        states.showMediaAlt = {
          alt,
          lang,
        };
      }}
      title="Media description"
    >
      {dataAltLabel}
      {!!index && <sup>{index}</sup>}
    </button>
  );
};

const MEDIA_CAPTION_LIMIT = 140;
const MEDIA_CAPTION_LIMIT_LONGER = 280;
export const isMediaCaptionLong = mem((caption) =>
  caption?.length
    ? caption.length > MEDIA_CAPTION_LIMIT ||
      /[\n\r].*[\n\r]/.test(caption.trim())
    : false,
);

function Media({
  class: className = '',
  media,
  to,
  lang,
  showOriginal,
  autoAnimate,
  showCaption,
  allowLongerCaption,
  altIndex,
  onClick = () => {},
}) {
  let {
    blurhash,
    description,
    meta,
    previewRemoteUrl,
    previewUrl,
    remoteUrl,
    url,
    type,
  } = media;
  if (/no\-preview\./i.test(previewUrl)) {
    previewUrl = null;
  }
  const { original = {}, small, focus } = meta || {};

  const width = showOriginal
    ? original?.width
    : small?.width || original?.width;
  const height = showOriginal
    ? original?.height
    : small?.height || original?.height;
  const mediaURL = showOriginal ? url : previewUrl || url;
  const remoteMediaURL = showOriginal
    ? remoteUrl
    : previewRemoteUrl || remoteUrl;
  const hasDimensions = width && height;
  const orientation = hasDimensions
    ? width > height
      ? 'landscape'
      : 'portrait'
    : null;

  const rgbAverageColor = blurhash ? getBlurHashAverageColor(blurhash) : null;

  const videoRef = useRef();

  let focalPosition;
  if (focus) {
    // Convert focal point to CSS background position
    // Formula from jquery-focuspoint
    // x = -1, y = 1 => 0% 0%
    // x = 0, y = 0 => 50% 50%
    // x = 1, y = -1 => 100% 100%
    const x = ((focus.x + 1) / 2) * 100;
    const y = ((1 - focus.y) / 2) * 100;
    focalPosition = `${x.toFixed(0)}% ${y.toFixed(0)}%`;
  }

  const mediaRef = useRef();
  const onUpdate = useCallback(({ x, y, scale }) => {
    const { current: media } = mediaRef;

    if (media) {
      const value = make3dTransformValue({ x, y, scale });

      if (scale === 1) {
        media.style.removeProperty('transform');
      } else {
        media.style.setProperty('transform', value);
      }

      media.closest('.media-zoom').style.touchAction =
        scale <= 1.01 ? 'pan-x' : '';
    }
  }, []);

  const [pinchZoomEnabled, setPinchZoomEnabled] = useState(false);
  const quickPinchZoomProps = {
    enabled: pinchZoomEnabled,
    draggableUnZoomed: false,
    inertiaFriction: 0.9,
    tapZoomFactor: 2,
    doubleTapToggleZoom: true,
    containerProps: {
      className: 'media-zoom',
      style: {
        overflow: 'visible',
        //   width: 'inherit',
        //   height: 'inherit',
        //   justifyContent: 'inherit',
        //   alignItems: 'inherit',
        //   display: 'inherit',
      },
    },
    onUpdate,
  };

  const Parent = useMemo(
    () => (to ? (props) => <Link to={to} {...props} /> : 'div'),
    [to],
  );

  const remoteMediaURLObj = remoteMediaURL ? new URL(remoteMediaURL) : null;
  const isVideoMaybe =
    type === 'unknown' &&
    remoteMediaURLObj &&
    /\.(mp4|m4r|m4v|mov|webm)$/i.test(remoteMediaURLObj.pathname);
  const isAudioMaybe =
    type === 'unknown' &&
    remoteMediaURLObj &&
    /\.(mp3|ogg|wav|m4a|m4p|m4b)$/i.test(remoteMediaURLObj.pathname);
  const isImage =
    type === 'image' ||
    (type === 'unknown' && previewUrl && !isVideoMaybe && !isAudioMaybe);

  const parentRef = useRef();
  const [imageSmallerThanParent, setImageSmallerThanParent] = useState(false);
  useLayoutEffect(() => {
    if (!isImage) return;
    if (!showOriginal) return;
    if (!parentRef.current) return;
    const { offsetWidth, offsetHeight } = parentRef.current;
    const smaller = width < offsetWidth && height < offsetHeight;
    if (smaller) setImageSmallerThanParent(smaller);
  }, [width, height]);

  const maxAspectHeight =
    window.innerHeight * (orientation === 'portrait' ? 0.45 : 0.33);
  const maxHeight = orientation === 'portrait' ? 0 : 160;
  const averageColorStyle = {
    '--average-color': rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
  };
  const mediaStyles =
    width && height
      ? {
          '--width': `${width}px`,
          '--height': `${height}px`,
          // Calculate '--aspectWidth' based on aspect ratio calculated from '--width' and '--height', max height has to be 160px
          '--aspectWidth': `${
            (width / height) * Math.max(maxHeight, maxAspectHeight)
          }px`,
          aspectRatio: `${width} / ${height}`,
          ...averageColorStyle,
        }
      : {
          ...averageColorStyle,
        };

  const longDesc = isMediaCaptionLong(description);
  let showInlineDesc =
    !!showCaption && !showOriginal && !!description && !longDesc;
  if (
    allowLongerCaption &&
    !showInlineDesc &&
    description?.length <= MEDIA_CAPTION_LIMIT_LONGER
  ) {
    showInlineDesc = true;
  }
  const Figure = !showInlineDesc
    ? Fragment
    : (props) => {
        const { children, ...restProps } = props;
        return (
          <figure {...restProps}>
            {children}
            <figcaption
              class="media-caption"
              lang={lang}
              dir="auto"
              onClick={(e) => {
                e.preventDefault();
                e.stopPropagation();
                states.showMediaAlt = {
                  alt: description,
                  lang,
                };
              }}
            >
              {description}
            </figcaption>
          </figure>
        );
      };

  if (isImage) {
    // Note: type: unknown might not have width/height
    quickPinchZoomProps.containerProps.style.display = 'inherit';

    useLayoutEffect(() => {
      if (!isSafari) return;
      if (!showOriginal) return;
      (async () => {
        try {
          await fetch(mediaURL, { mode: 'no-cors' });
          mediaRef.current.src = mediaURL;
        } catch (e) {
          // Ignore
        }
      })();
    }, [mediaURL]);

    return (
      <Figure>
        <Parent
          ref={parentRef}
          class={`media media-image ${className}`}
          onClick={onClick}
          data-orientation={orientation}
          data-has-alt={!showInlineDesc}
          style={
            showOriginal
              ? {
                  backgroundImage: `url(${previewUrl})`,
                  backgroundSize: imageSmallerThanParent
                    ? `${width}px ${height}px`
                    : undefined,
                  ...averageColorStyle,
                }
              : mediaStyles
          }
        >
          {showOriginal ? (
            <QuickPinchZoom {...quickPinchZoomProps}>
              <img
                ref={mediaRef}
                src={mediaURL}
                alt={description}
                width={width}
                height={height}
                data-orientation={orientation}
                loading="eager"
                decoding="sync"
                onLoad={(e) => {
                  e.target.closest('.media-image').style.backgroundImage = '';
                  e.target.closest('.media-zoom').style.display = '';
                  setPinchZoomEnabled(true);
                }}
                onError={(e) => {
                  const { src } = e.target;
                  if (
                    src === mediaURL &&
                    remoteMediaURL &&
                    mediaURL !== remoteMediaURL
                  ) {
                    e.target.src = remoteMediaURL;
                  }
                }}
              />
            </QuickPinchZoom>
          ) : (
            <>
              <img
                src={mediaURL}
                alt={showInlineDesc ? '' : description}
                width={width}
                height={height}
                data-orientation={orientation}
                loading="lazy"
                style={{
                  // backgroundColor:
                  //   rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
                  // backgroundPosition: focalBackgroundPosition || 'center',
                  // Duration based on width or height in pixels
                  objectPosition: focalPosition || 'center',
                  // 100px per second (rough estimate)
                  // Clamp between 5s and 120s
                  '--anim-duration': `${Math.min(
                    Math.max(Math.max(width, height) / 100, 5),
                    120,
                  )}s`,
                }}
                onLoad={(e) => {
                  // e.target.closest('.media-image').style.backgroundImage = '';
                  e.target.dataset.loaded = true;
                  if (!hasDimensions) {
                    const $media = e.target.closest('.media');
                    if ($media) {
                      const { naturalWidth, naturalHeight } = e.target;
                      $media.dataset.orientation =
                        naturalWidth > naturalHeight ? 'landscape' : 'portrait';
                      $media.style.setProperty('--width', `${naturalWidth}px`);
                      $media.style.setProperty(
                        '--height',
                        `${naturalHeight}px`,
                      );
                      $media.style.aspectRatio = `${naturalWidth}/${naturalHeight}`;
                    }
                  }
                }}
                onError={(e) => {
                  const { src } = e.target;
                  if (src === mediaURL && mediaURL !== remoteMediaURL) {
                    e.target.src = remoteMediaURL;
                  }
                }}
              />
              {!showInlineDesc && (
                <AltBadge alt={description} lang={lang} index={altIndex} />
              )}
            </>
          )}
        </Parent>
      </Figure>
    );
  } else if (type === 'gifv' || type === 'video' || isVideoMaybe) {
    const hasDuration = original.duration > 0;
    const shortDuration = original.duration < 31;
    const isGIF = type === 'gifv' && shortDuration;
    // If GIF is too long, treat it as a video
    const loopable = original.duration < 61;
    const formattedDuration = formatDuration(original.duration);
    const hoverAnimate = !showOriginal && !autoAnimate && isGIF;
    const autoGIFAnimate = !showOriginal && autoAnimate && isGIF;
    const showProgress = original.duration > 5;

    const videoHTML = `
    <video
      src="${url}"
      poster="${previewUrl}"
      width="${width}"
      height="${height}"
      data-orientation="${orientation}"
      preload="auto"
      autoplay
      ${isGIF ? 'muted' : ''}
      ${isGIF ? '' : 'controls'}
      playsinline
      loop="${loopable}"
      ${isGIF ? 'ondblclick="this.paused ? this.play() : this.pause()"' : ''}
      ${
        isGIF && showProgress
          ? "ontimeupdate=\"this.closest('.media-gif') && this.closest('.media-gif').style.setProperty('--progress', `${~~((this.currentTime / this.duration) * 100)}%`)\""
          : ''
      }
    ></video>
  `;

    return (
      <Figure>
        <Parent
          class={`media ${className} media-${isGIF ? 'gif' : 'video'} ${
            autoGIFAnimate ? 'media-contain' : ''
          } ${hoverAnimate ? 'media-hover-animate' : ''}`}
          data-orientation={orientation}
          data-formatted-duration={
            !showOriginal ? formattedDuration : undefined
          }
          data-label={isGIF && !showOriginal && !autoGIFAnimate ? 'GIF' : ''}
          data-has-alt={!showInlineDesc}
          // style={{
          //   backgroundColor:
          //     rgbAverageColor && `rgb(${rgbAverageColor.join(',')})`,
          // }}
          style={!showOriginal && mediaStyles}
          onClick={(e) => {
            if (hoverAnimate) {
              try {
                videoRef.current.pause();
              } catch (e) {}
            }
            onClick(e);
          }}
          onMouseEnter={() => {
            if (hoverAnimate) {
              try {
                videoRef.current.play();
              } catch (e) {}
            }
          }}
          onMouseLeave={() => {
            if (hoverAnimate) {
              try {
                videoRef.current.pause();
              } catch (e) {}
            }
          }}
          onFocus={() => {
            if (hoverAnimate) {
              try {
                videoRef.current.play();
              } catch (e) {}
            }
          }}
          onBlur={() => {
            if (hoverAnimate) {
              try {
                videoRef.current.pause();
              } catch (e) {}
            }
          }}
        >
          {showOriginal || autoGIFAnimate ? (
            isGIF && showOriginal ? (
              <QuickPinchZoom {...quickPinchZoomProps} enabled>
                <div
                  ref={mediaRef}
                  dangerouslySetInnerHTML={{
                    __html: videoHTML,
                  }}
                />
              </QuickPinchZoom>
            ) : (
              <div
                class="video-container"
                dangerouslySetInnerHTML={{
                  __html: videoHTML,
                }}
              />
            )
          ) : isGIF ? (
            <video
              ref={videoRef}
              src={url}
              poster={previewUrl}
              width={width}
              height={height}
              data-orientation={orientation}
              preload="auto"
              // controls
              playsinline
              loop
              muted
              onTimeUpdate={
                showProgress
                  ? (e) => {
                      const { target } = e;
                      const container = target?.closest('.media-gif');
                      if (container) {
                        const percentage =
                          (target.currentTime / target.duration) * 100;
                        container.style.setProperty(
                          '--progress',
                          `${percentage}%`,
                        );
                      }
                    }
                  : undefined
              }
            />
          ) : (
            <>
              {previewUrl ? (
                <img
                  src={previewUrl}
                  alt={showInlineDesc ? '' : description}
                  width={width}
                  height={height}
                  data-orientation={orientation}
                  loading="lazy"
                  decoding="async"
                  onLoad={(e) => {
                    if (!hasDimensions) {
                      const $media = e.target.closest('.media');
                      if ($media) {
                        const { naturalHeight, naturalWidth } = e.target;
                        $media.dataset.orientation =
                          naturalWidth > naturalHeight
                            ? 'landscape'
                            : 'portrait';
                        $media.style.setProperty(
                          '--width',
                          `${naturalWidth}px`,
                        );
                        $media.style.setProperty(
                          '--height',
                          `${naturalHeight}px`,
                        );
                        $media.style.aspectRatio = `${naturalWidth}/${naturalHeight}`;
                      }
                    }
                  }}
                />
              ) : (
                <video
                  src={url + '#t=0.1'} // Make Safari show 1st-frame preview
                  width={width}
                  height={height}
                  data-orientation={orientation}
                  preload="metadata"
                  muted
                  disablePictureInPicture
                  onLoadedMetadata={(e) => {
                    if (!hasDuration) {
                      const { duration } = e.target;
                      if (duration) {
                        const formattedDuration = formatDuration(duration);
                        const container = e.target.closest('.media-video');
                        if (container) {
                          container.dataset.formattedDuration =
                            formattedDuration;
                        }
                      }
                    }
                  }}
                />
              )}
              <div class="media-play">
                <Icon icon="play" size="xl" />
              </div>
            </>
          )}
          {!showOriginal && !showInlineDesc && (
            <AltBadge alt={description} lang={lang} index={altIndex} />
          )}
        </Parent>
      </Figure>
    );
  } else if (type === 'audio' || isAudioMaybe) {
    const formattedDuration = formatDuration(original.duration);
    return (
      <Figure>
        <Parent
          class={`media media-audio ${className}`}
          data-formatted-duration={
            !showOriginal ? formattedDuration : undefined
          }
          data-has-alt={!showInlineDesc}
          onClick={onClick}
          style={!showOriginal && mediaStyles}
        >
          {showOriginal ? (
            <audio src={remoteUrl || url} preload="none" controls autoplay />
          ) : previewUrl ? (
            <img
              src={previewUrl}
              alt={showInlineDesc ? '' : description}
              width={width}
              height={height}
              data-orientation={orientation}
              loading="lazy"
              onError={(e) => {
                try {
                  // Remove self if broken
                  e.target?.remove?.();
                } catch (e) {}
              }}
            />
          ) : null}
          {!showOriginal && (
            <>
              <div class="media-play">
                <Icon icon="play" size="xl" />
              </div>
              {!showInlineDesc && (
                <AltBadge alt={description} lang={lang} index={altIndex} />
              )}
            </>
          )}
        </Parent>
      </Figure>
    );
  }
}

export default Media;