import React, { PropsWithChildren, useContext } from 'react'
import {
  FallbackImageOverrideWrapper,
  ImageSetOverrideWrapper,
} from '../../ImageOverrideWrappers'

import type { ContentTree } from '@financial-times/content-tree'

import { PresentationFlagsContext } from '../../../context'
import { ContentProps } from '../../types'
import { ContentTreeWorkarounds } from '@financial-times/cp-content-pipeline-schema'
import { SetRequired } from 'type-fest'
import type * as ComponentWorkarounds from '../Workarounds'

type ImageWithSourceSet = SetRequired<ContentTree.Image, 'sourceSet'>

type ImageWithAliases = ContentTree.Image & {
  [alias: string]: ImageWithSourceSet['sourceSet']
}

const formatSourceSet = (sourceSet: ContentTree.ImageSource[]) =>
  sourceSet.map((src) => `${src.url} ${src.dpr}x`).join(',')

const figureClassNameMap: Record<
  ContentTree.ImageSetPicture['layoutWidth'],
  string
> = {
  'full-grid':
    'n-content-picture n-content-picture--wide n-content-layout__container',
  'inset-left': 'n-content-image n-content-image--inline',
  standard: 'n-content-image n-content-image--full',
}

type Breakpoint = {
  minWidth?: number
  maxWidth?: number
  orientation?: string
}

export type AliasedBreakpoints = {
  [alias: string]: Breakpoint
}

const defaultGetBreakpoints = (
  image: ComponentWorkarounds.Image
): Breakpoint | Array<Breakpoint> | AliasedBreakpoints | undefined => {
  switch (image.format) {
    case 'mobile':
      return { maxWidth: 490 }
    case 'desktop':
      return { minWidth: 980 }
    case 'standard':
    case 'standard-inline':
      return { minWidth: 700 }
  }
}

export type BreakpointGetter = typeof defaultGetBreakpoints

const breakpointToMedia = (breakpoint: Breakpoint): string =>
  [
    breakpoint.minWidth && `(min-width: ${breakpoint.minWidth}px)`,
    breakpoint.maxWidth && `(max-width: ${breakpoint.maxWidth}px)`,
    breakpoint.orientation && `(orientation: ${breakpoint.orientation})`,
  ]
    .filter(Boolean)
    .join(' and ')

function hasSourceSet(
  image: ComponentWorkarounds.Image | ContentTreeWorkarounds.RawImage
): image is ImageWithSourceSet {
  return image && 'sourceSet' in image
}

function isAliasedBreakpoints(
  breakpoints: ReturnType<BreakpointGetter>
): breakpoints is AliasedBreakpoints {
  return Boolean(
    breakpoints &&
      Object.values(breakpoints)[0] instanceof Object &&
      !Array.isArray(breakpoints)
  )
}

function figureWidth(picture: ComponentWorkarounds.ImageSetPicture) {
  if (picture.layoutWidth === 'inset-left') {
    const DEFAULT_WIDTH = 700
    const width = picture.images[0]?.width ?? DEFAULT_WIDTH
    const height = picture.images[0]?.height

    const isWidthWithinBoundary =
      height && width < height && width > 350 && width < 580
    const calculatedWidth = isWidthWithinBoundary
      ? 350
      : Math.floor(Math.min(width, DEFAULT_WIDTH))

    return { width: calculatedWidth, maxWidth: '100%' }
  }
}

type SourceProps = {
  image: ComponentWorkarounds.Image
  getBreakpoints?: BreakpointGetter
}

const Source: React.FC<SourceProps> = ({
  image,
  getBreakpoints = defaultGetBreakpoints,
}) => {
  const breakpoints = getBreakpoints(image)
  if (!breakpoints) {
    return null
  }
  const breakpointsDict = isAliasedBreakpoints(breakpoints)
    ? breakpoints
    : { sourceSet: breakpoints }
  return (
    <>
      {Object.entries(breakpointsDict)
        .map(([key, breakpoint]) => {
          const sourceSet = (image as ImageWithAliases)[key]
          const sourceSetMap = Array.isArray(breakpoint)
            ? breakpoint
            : [breakpoint]
          return (
            sourceSet &&
            sourceSetMap.map((breakpoint) => {
              return (
                <source
                  key={key}
                  media={breakpointToMedia(breakpoint)}
                  srcSet={formatSourceSet(sourceSet)}
                  width={image.width ?? undefined}
                  height={image.height ?? undefined}
                />
              )
            })
          )
        })
        .filter((elem) => elem)}
    </>
  )
}

type SourcesProps = {
  images: readonly ComponentWorkarounds.Image[]
  getBreakpoints?: BreakpointGetter
}

export const Sources: React.FC<SourcesProps> = ({ images, getBreakpoints }) => {
  return (
    <>
      {images.map((image, index) => (
        <Source key={index} image={image} getBreakpoints={getBreakpoints} />
      ))}
    </>
  )
}

// HACK:20240809:IM This props type is quite a mess as the FallbackImage
// component can be used both directly in a RichTextChild with a Content Tree
// node, but also from the Image component above which the Image type. Image is
// defined in Content Tree but is _not_ a node so this is a bit icky :(
export type FallbackImageProps = (
  | ContentProps<ComponentWorkarounds.RawImage>
  | {
      content: { image: ComponentWorkarounds.Image }
    }
) & {
  imageType?: string
  picture?: ComponentWorkarounds.ImageSetPicture
  className?: string
  inSourceSet?: boolean
  isMainImage?: boolean
}

// TODO should `fallback` be added to content-tree imageset?
export const FallbackImage: React.FC<FallbackImageProps> = (props) => {
  const {
    content: { image },
    imageType = 'image',
    picture,
    className,
    inSourceSet = false,
    isMainImage = false,
  } = props

  const Wrapper = inSourceSet
    ? React.Fragment
    : ({ children }: PropsWithChildren) => (
        <figure className="n-content-image">{children}</figure>
      )

  return (
    <Wrapper>
      <FallbackImageOverrideWrapper {...props}>
        <img
          src={
            hasSourceSet(image)
              ? image.sourceSet[0]?.url
              : image?.url ?? undefined
          }
          srcSet={
            hasSourceSet(image) ? formatSourceSet(image.sourceSet) : undefined
          }
          alt={picture?.alt ?? ''}
          data-image-type={imageType}
          width={image.width ?? undefined}
          height={image.height ?? undefined}
          className={className}
          loading={isMainImage ? 'eager' : 'lazy'}
        />
      </FallbackImageOverrideWrapper>
    </Wrapper>
  )
}

export interface ImageSetProps
  extends ContentProps<
    ComponentWorkarounds.ImageSet & { fragmentIdentifier?: string }
  > {
  isMainImage?: boolean
}

const ImageSet: React.FC<ImageSetProps> = (props) => {
  const { content: imageSet, isMainImage } = props

  if (!imageSet.picture?.images) {
    return null
  }

  const { reduceFullBleedImages } = useContext(PresentationFlagsContext)

  const { fragmentIdentifier } = imageSet

  const Wrapper =
    // HACK:20240618:IM don't stretch out the main image to the full grid in
    // the app as it can clash with the right hand rail
    !reduceFullBleedImages && imageSet.picture!.layoutWidth === 'full-grid'
      ? ({ children }: PropsWithChildren) => (
          <div className="n-content-layout" data-layout-width="full-grid">
            {children}
          </div>
        )
      : React.Fragment

  return (
    <Wrapper>
      <figure
        id={fragmentIdentifier}
        className={
          (imageSet.picture.layoutWidth &&
            figureClassNameMap[imageSet.picture.layoutWidth]) ??
          'n-content-image'
        }
        style={figureWidth(imageSet.picture)}
        data-component="image-set"
      >
        <ImageSetOverrideWrapper {...props}>
          <picture>
            <Sources images={imageSet.picture.images} />
            <FallbackImage
              picture={imageSet.picture}
              content={{ image: imageSet.picture.fallbackImage }}
              imageType={imageSet.picture.imageType || 'image'}
              inSourceSet={true}
              isMainImage={isMainImage}
            />
          </picture>
        </ImageSetOverrideWrapper>
        {(imageSet.picture.caption || imageSet.picture.credit) && (
          <figcaption className="n-content-picture__caption o3-editorial-typography-caption">
            <span>{imageSet.picture.caption}</span>
            <span>
              {imageSet.picture.caption ? ' ' : ''}
              {imageSet.picture.credit}
            </span>
          </figcaption>
        )}
      </figure>
    </Wrapper>
  )
}

export default ImageSet
