import { encryptImageToken, getClassNames, IImageResize, ImageStrategy, isObject } from '@websolutespa/bom-core';
import { getRemoteApiUrl } from '@websolutespa/bom-mixer-store';
import { CSSProperties, memo, useContext, useEffect, useRef } from 'react';
import styled, { ThemeContext } from 'styled-components';
import { UIStyledComponentProps } from '../../components/types';
import { IThemeResponsive } from '../../theme';
import { SizeVariant } from '../../variants';
import { IMediaItemProps } from './media-items';

export type ImageSizeMap = Partial<Record<SizeVariant, number>>;

const IMAGE_SIZE_VARIANTS = ['xs', 'sm', 'md', 'lg', 'xl'];

const IMAGE_SIZES = process.env.IMAGE_SIZES ?
  process.env.IMAGE_SIZES.split(',').map(x => parseInt(x)) :
  [750, 960, 1440, 1600, 1920];

const IMAGE_SIZEMAP: ImageSizeMap = Object.fromEntries(IMAGE_SIZES.map((x, i) => (
  [IMAGE_SIZE_VARIANTS[i], x]
)));

const IMAGE_FOCAL_X = 50;
const IMAGE_FOCAL_Y = 50;

const IMAGE_QUALITY = process.env.IMAGE_QUALITY ?
  parseInt(process.env.IMAGE_QUALITY) :
  90;

const IMAGE_STRATEGY: ImageStrategy = process.env.IMAGE_STRATEGY ?
  process.env.IMAGE_STRATEGY as ImageStrategy :
  ImageStrategy.Mixer;

export type MediaSizeVariant = SizeVariant;

export type MediaSizeVariants = ImageSizeMap;

export type MediaSize = MediaSizeVariants | MediaSizeVariant | number;

export type MediaRatio = Record<SizeVariant, number>;

export function isMediaSizeVariant(item: unknown): item is MediaSizeVariant {
  return typeof item === 'string';
  // return typeof item === 'string' && IMAGE_SIZE_VARIANTS.includes(item);
}

export function isMediaSizeVariants(item: unknown): item is MediaSizeVariants {
  return isObject(item) && Object.entries(item).reduce((p, [k, v]) => p && typeof v === 'number', true);
  // return Array.isArray(item) && item.length > 0 && item.reduce((p, c) => p && typeof c === 'number', true);
}

const StyledMediaImage = styled.img`
  max-width: 100%;
  user-select: none;
  color: transparent;
`;

function getMediaSizes(
  mediaQuery: IThemeResponsive<CSSProperties['width']>,
  ratio: MediaRatio,
  size?: MediaSize,
  maxWidth: number = Number.POSITIVE_INFINITY,
  maxHeight: number = Number.POSITIVE_INFINITY
): [number, number, number][] {
  // console.log('getMediaSizes', mediaQuery, ratio, size, maxWidth, maxHeight);
  let sizes: [number, number, number][] = [];
  if (size) {
    if (typeof size === 'number') {
      const s: number = size;
      // console.log('typeof size === \'number\'', size);
      sizes = Object.entries(ratio).map(([k, v]) => {
        const w = IMAGE_SIZEMAP[k as keyof typeof IMAGE_SIZEMAP];
        return [
          (w && s > w) ? w : s,
          parseInt(String(mediaQuery[k])),
          v,
        ];
      }).filter(x => x[0] >= x[1]) as [number, number, number][];
      // console.log(size, 'sizes', sizes);
    } else if (isMediaSizeVariant(size)) {
      // console.log('isMediaSizeVariant');
      const sizeLimit = IMAGE_SIZEMAP[size] || 0;
      // console.log(size, ratio[size], sizeLimit);
      sizes = Object.entries(IMAGE_SIZEMAP).filter(([k, v]) => (
        v <= sizeLimit
      )).map(([k, v]) => [
        v,
        parseInt(String(mediaQuery[k])),
        ratio[k as SizeVariant],
      ]);
    }
  } else {
    size = size || IMAGE_SIZEMAP;
    // console.log('size || IMAGE_SIZEMAP', size, IMAGE_SIZEMAP);
    sizes = Object.entries(size).map(([k, v]) => [
      v,
      parseInt(String(mediaQuery[k])),
      ratio[k as SizeVariant],
    ]) as [number, number, number][];
  }
  // console.log(size, 'sizes', sizes, ratio);
  sizes = sizes.filter((size, i) =>
    i === 0 ||
    (
      size[0] <= maxWidth &&
      (
        size[2] === 0 ||
        size[0] / size[2] <= maxHeight
      )
    ));
  // console.log(sizes[0][2] ? sizes[0][0] / sizes[0][2] : 0, maxHeight);
  return sizes;
}

function getImageProps(
  { src, ...props }: Omit<MediaImageProps, 'mimeType' | 'sizes'>,
  mediaQuery: IThemeResponsive<CSSProperties['width']>
) {
  if (IMAGE_STRATEGY === ImageStrategy.None || !isValidSrc(src)) {
    return {
      src,
      ...props,
    };
  }
  const { width, height, size, ...otherProps } = props;
  let useSize = size;
  let useWidth: number | undefined = undefined;
  if (typeof width === 'string' && width.indexOf('px') !== -1) {
    useWidth = parseInt(width);
    useSize = useWidth;
  } else if (typeof width === 'number') {
    useWidth = width;
  }
  let useHeight: number | undefined = undefined;
  if (typeof height === 'string' && height.indexOf('px') !== -1) {
    useHeight = parseInt(height);
  } else if (typeof height === 'number') {
    useHeight = height;
  }
  const ratio = getMediaRatio(useWidth, useHeight, props.aspectRatio, props.aspectRatioSm, props.aspectRatioMd, props.aspectRatioLg, props.aspectRatioXl);
  const sizes = getMediaSizes(mediaQuery, ratio, useSize, props.mediaWidth, props.mediaHeight);
  const imageSrc = getImageSrc(src, sizes[0][0], sizes[0][2], props.mediaFocalX, props.mediaFocalY);
  const imageSrcSet = getImageSrcset(src, sizes, props.mediaFocalX, props.mediaFocalY);
  const imageSizes = getImageSizes(sizes);
  // console.log('useSize', useSize, 'width', width, useWidth, 'height', height, useHeight);
  return {
    src: imageSrc,
    srcSet: imageSrcSet,
    sizes: imageSizes,
    width: useWidth ? `${useWidth}px` : undefined,
    height: useHeight ? `${useHeight}px` : undefined,
    ...otherProps,
  };
}

function getMediaRatio(
  width: number | undefined,
  height: number | undefined,
  aspectRatio: CSSProperties['aspectRatio'],
  aspectRatioSm: CSSProperties['aspectRatio'],
  aspectRatioMd: CSSProperties['aspectRatio'],
  aspectRatioLg: CSSProperties['aspectRatio'],
  aspectRatioXl: CSSProperties['aspectRatio']
): MediaRatio {
  // console.log('getMediaRatio', width, height, aspectRatio);
  const defaultRatio = width && height ? (width / height) : 0;
  const xs = getRatio(aspectRatio, defaultRatio);
  const sm = getRatio(aspectRatioSm, xs);
  const md = getRatio(aspectRatioMd, sm);
  const lg = getRatio(aspectRatioLg, md);
  const xl = getRatio(aspectRatioXl, lg);
  const ratio: MediaRatio = {
    xs,
    sm,
    md,
    lg,
    xl,
  };
  return ratio;
}

function getRatio(aspectRatio?: CSSProperties['aspectRatio'], defaultRatio = 0): number {
  return typeof aspectRatio === 'string' ? parseFloat(aspectRatio) : (aspectRatio || defaultRatio);
}

const BASE_URLS = {
  [ImageStrategy.Mixer]: '/api/_image',
  [ImageStrategy.Token]: '/api/_image',
  [ImageStrategy.Next]: '/_next/image',
  [ImageStrategy.Oven]: getRemoteApiUrl('/_image'),
};

const BASE_URL = BASE_URLS[IMAGE_STRATEGY as keyof typeof BASE_URLS];

function parseUrl(src: string) {
  if (src.indexOf('http') === 0) {
    const url = new URL(src);
    return url;
  }
  const pathname = src.split('/').map(x => encodeURIComponent(x)).join('/');
  const url = { origin: '', pathname, search: '', href: pathname };
  return url;
}

function getImageSrc(src: string, width: number, ratio: number, focalX: number = IMAGE_FOCAL_X, focalY: number = IMAGE_FOCAL_Y, quality: number = IMAGE_QUALITY): string {
  if (!isValidSrc(src)) {
    return src;
  }
  switch (IMAGE_STRATEGY) {
    case ImageStrategy.Token: {
      const { origin, pathname, search, href } = parseUrl(src);
      const imageResize: IImageResize = ratio !== 0 ? {
        w: width,
        h: Math.round(width / ratio),
        x: focalX,
        y: focalY,
        q: quality,
        src: href,
      } : {
        w: width,
        q: quality,
        src: href,
      };
      const token = encryptImageToken(imageResize);
      return `${BASE_URL}${pathname}?origin=${encodeURIComponent(origin)}&search=${encodeURIComponent(search)}&t=${encodeURIComponent(token)}`;
    }
    case ImageStrategy.Mixer:
    case ImageStrategy.Oven: {
      const { origin, pathname, search } = parseUrl(src);
      return ratio !== 0 ?
        `${BASE_URL}${pathname}?origin=${encodeURIComponent(origin)}&search=${encodeURIComponent(search)}&w=${width}&h=${Math.round(width / ratio)}&x=${focalX}&y=${focalY}&q=${quality}` :
        `${BASE_URL}${pathname}?origin=${encodeURIComponent(origin)}&search=${encodeURIComponent(search)}&w=${width}&x=${focalX}&y=${focalY}&q=${quality}`;
    }
    default:
      return ratio !== 0 ?
        `${BASE_URL}?url=${encodeURIComponent(src)}&w=${width}&h=${Math.round(width / ratio)}&q=${quality}` :
        `${BASE_URL}?url=${encodeURIComponent(src)}&w=${width}&q=${quality}`;
  }
}

function getImageSrcset(src: string, sizes: [number, number, number][], focalX: number = IMAGE_FOCAL_X, focalY: number = IMAGE_FOCAL_Y, quality: number = IMAGE_QUALITY): string | undefined {
  if (!isValidSrc(src)) {
    return undefined;
  }
  return sizes.length > 1 ?
    sizes.map(size => getImageSrc(src, size[0], size[2], focalX, focalY, quality) + ` ${size[0]}w`).join(', ') :
    undefined;
}

function getImageSizes(
  sizes: [number, number, number][]
): string | undefined {
  return sizes.length > 1 ?
    sizes.map((size, i) => `${i < sizes.length - 1 ? `(max-width: ${sizes[i + 1][1] - 1}px) ` : ''}${size[0]}px`).join(', ') :
    undefined;
}

function isValidSrc(src?: string): src is string {
  // undefined
  if (!src) {
    return false;
  }
  // svg
  if (isSVG(src)) {
    return false;
  }
  // gif
  if (isGIF(src)) {
    return false;
  }
  // data image
  if (src.indexOf('data:') === 0) {
    return false;
  }
  // valid
  return true;
}

function isSVG(src: string): boolean {
  return src.indexOf('.svg') !== -1;
}

function isGIF(src: string): boolean {
  return src.indexOf('.gif') !== -1;
}

export type MediaImageProps = UIStyledComponentProps<IMediaItemProps, 'img'>;

const MediaImage_ = ({ className, loading = 'lazy', onLoaded, canLoad = true, mimeType, ...props }: MediaImageProps) => {
  const theme = useContext(ThemeContext);
  if (!theme) {
    throw new Error('MediaImage must be used within ThemeProvider');
  }
  const imageRef = useRef<HTMLImageElement>(null);
  const classNames = getClassNames(className, 'image', props.src && { 'image-svg': isSVG(props.src) });
  const imageProps = getImageProps(props, theme.mediaQuery);
  useEffect(() => {
    if (imageRef.current && canLoad) {
      const image = imageRef.current;
      if (image.complete) {
        if (typeof onLoaded === 'function') {
          onLoaded(imageRef as React.RefObject<HTMLImageElement>);
        }
      } else {
        const onLoad = () => {
          if (typeof onLoaded === 'function') {
            onLoaded(imageRef as React.RefObject<HTMLImageElement>);
          }
        };
        image.addEventListener('load', onLoad);
        return () => {
          if (image) {
            image.removeEventListener('load', onLoad);
          }
        };
      }
    }
    return () => { };
  }, [canLoad, onLoaded]);
  return (
    <StyledMediaImage
      ref={imageRef}
      {...(canLoad ? imageProps : undefined)}
      className={classNames}
      loading={loading}
      draggable={false}
      aria-hidden={!props.informative}
    />
  );
};

export const MediaImage = memo(MediaImage_);
