import { createRef, useMemo, useState } from 'react'
import Link from 'next/link'
import { m } from 'framer-motion'
import cx from 'classnames'

import {
  BREAKPOINTS,
  DEFAULT_DRAG_ELASTIC,
  DEFAULT_SPRING_TRANSITION,
  SWIPE_CONFIDENCE_THRESHOLD,
} from '@/lib/constants'
import { useBreakpoint, useMeasure } from '@/lib/hooks'
import { getSwipePower, transformHeadlessURLs } from '@/lib/utilities'
import { useOrb } from '@/lib/OrbContext'

import BlurImage from '@/components/shared/BlurImage'
import Container from '@/components/shared/Container'
import Icon from '@/components/shared/Icon'
import Callout from '@/components/blocks/Callout'

type Slide = {
  sliderImage?: {
    imageUrl: string
    alt: string
    placeholder: string
  }
  slideLink?: {
    url: string
    title: string
  }
}

type Props = {
  title?: string
  content?: string
  button?: {
    url: string
    title: string
  }
  slides: Slide[]
}

const SliderImage = ({ slide, width }: { slide: Slide; width: string }) => {
  const { orbPointerEvents, orbClasses } = useOrb()

  if (!slide.sliderImage) return null

  return (
    <li className="relative group" {...orbPointerEvents}>
      <Link
        href={transformHeadlessURLs(slide.slideLink?.url ?? '')}
        className={cx('relative flex flex-shrink-0 aspect-square w-full z-1', orbClasses)}
        style={{ width }}
      >
        <BlurImage
          src={slide.sliderImage.imageUrl}
          alt=""
          placeholderSrc={slide.sliderImage.placeholder}
          fill
          className="object-cover"
          sizes={`(min-width: ${BREAKPOINTS.md}) 50vw, 80vw`}
        />
        <span className="sr-only">View {slide.slideLink?.title}</span>
      </Link>
      <div className="block absolute inset-0 cursor-none bg-black bg-opacity-0 group-hover:bg-opacity-10 transition-colors duration-300 z-1 pointer-events-none" />
    </li>
  )
}

export default function CalloutWithSlider({ title, content, button, slides }: Props) {
  const [page, setPage] = useState(0)
  const [containerRef, { width: containerWidth }] = useMeasure<HTMLDivElement>()
  const { isDesktop } = useBreakpoint()
  const captionRefs = useMemo(() => {
    return slides.map(() => createRef<HTMLLIElement>())
  }, [slides])
  const maxPage = slides.length - 1

  const sliderImageWidth = isDesktop ? containerWidth / 2 : containerWidth * 0.8
  // 8px/page to account for the `gap-2` spacing between images
  const trackOffset = `${-sliderImageWidth * page - page * 8}px`

  const handleUpdatePage = (newPage: number) => {
    if (newPage < 0 || newPage > maxPage) return
    setPage(newPage)
  }

  const captionTrackOffset = useMemo(() => {
    const activeEls = captionRefs.slice(0, page)
    return -activeEls.reduce((results, el) => results + (el.current?.getBoundingClientRect().width || 0), 0)
  }, [captionRefs, page])

  return (
    <div className="overflow-hidden">
      <Container>
        <div ref={containerRef} className="md:flex items-center">
          <div className="flex-shrink-0 md:w-1/2 max-md:pb-12 md:pr-24">
            <Callout heading={title} content={content} button={button} />
          </div>

          <div className="overflow-hidden flex-shrink-0 max-md:w-vw">
            <m.div animate={{ x: trackOffset }} transition={DEFAULT_SPRING_TRANSITION} className="flex w-full">
              <m.ul
                className="flex w-full gap-2"
                drag="x"
                dragDirectionLock
                dragSnapToOrigin
                dragConstraints={{ left: 0, right: 0 }}
                dragElastic={DEFAULT_DRAG_ELASTIC}
                onDragEnd={(e, { offset, velocity }) => {
                  const swipe = getSwipePower(offset.x, velocity.x)

                  if (swipe < -SWIPE_CONFIDENCE_THRESHOLD) {
                    handleUpdatePage(page + 1)
                  } else if (swipe > SWIPE_CONFIDENCE_THRESHOLD) {
                    handleUpdatePage(page - 1)
                  }
                }}
              >
                {slides.map((slide, index: number) => (
                  <SliderImage key={index} slide={slide} width={`${sliderImageWidth}px`} />
                ))}
              </m.ul>
            </m.div>

            <div className="flex gap-4 pt-6">
              <div className="flex gap-4">
                <button
                  disabled={page === 0}
                  className="disabled:opacity-50"
                  onClick={() => handleUpdatePage(page - 1)}
                >
                  <Icon name="arrow-down" className="w-5 rotate-90" aria-hidden />
                  <span className="sr-only">Previous slide</span>
                </button>

                <button
                  disabled={page === slides.length - 1}
                  className="disabled:opacity-50"
                  onClick={() => handleUpdatePage(page + 1)}
                >
                  <Icon name="arrow-down" className="w-5 -rotate-90" aria-hidden />
                  <span className="sr-only">Next slide</span>
                </button>
              </div>

              <div className="relative flex flex-1 overflow-hidden">
                <m.ul
                  animate={{ x: captionTrackOffset }}
                  transition={DEFAULT_SPRING_TRANSITION}
                  className="flex w-full"
                >
                  {slides.map((slide, index: number) =>
                    slide.slideLink ? (
                      <li
                        key={index}
                        ref={captionRefs[index]}
                        className={cx(
                          'text-photo-slider transition-opacity flex-shrink-0 pt-1',
                          index === page ? 'opacity-100' : 'opacity-0'
                        )}
                      >
                        <Link href={transformHeadlessURLs(slide.slideLink?.url ?? '')} className="block pb-1">
                          {slide.slideLink?.title}
                        </Link>
                      </li>
                    ) : null
                  )}
                </m.ul>
              </div>
            </div>
          </div>
        </div>
      </Container>
    </div>
  )
}
