import { useRef, useLayoutEffect, useEffect } from 'preact/hooks'
import h from '../shared/h.js'
import { ARROW_BACK, ARROW_NEXT } from '../shared/icons.js'
import t from '../shared/t.js'
import { Button } from './button.js'
import { tw, LARGE, MEDIUM } from '../shared/twind.js'
import getSizeClass from '../shared/get-size-class.js'

// Sometimes navigating with a button does not cause the slider to scroll completely.
const RIGHT_BLUR_MARGIN_TOLERANCE = 2
const DEFAULT_COUNT = [3.25, 2.25, 1.25]

export function Scroller ({ _count, _tall, _index = 0, children, ...props }) {
  const ref = useRef(null)
  const {
    _autoplay = false,
    _interval = 3500,
    _infinite = false,
    _hideControls = false,
    ...restProps
  } = props
  const baseSlides = children ?? []
  const isInfinite = _infinite && baseSlides.length > 1
  const slides = _infinite && baseSlides.length > 1
    ? [...baseSlides, ...baseSlides, ...baseSlides]
    : baseSlides
  const interval = Math.max(1000, Number(_interval) || 3500)
  const visibleSlideCount = _count ?? DEFAULT_COUNT
  const [colsLarge, colsMedium, colsSmall] = visibleSlideCount
  const blurSides = !visibleSlideCount.every(Number.isInteger)
  const sizeClass = getSizeClass(_tall)

  // Scroll to selected slide.
  useLayoutEffect(() => {
    if (ref.current) {
      const container = ref.current
      const startIndex = isInfinite ? baseSlides.length + _index : _index
      const child = container.children[startIndex]
      if (child) {
        // Timeout fixes scroller in popup instances.
        // We need to make sure the scroll is visible.
        setTimeout(() => container.scrollTo({ left: child.offsetLeft }), 0)
      }
    }
  }, [_index, isInfinite, baseSlides.length])

  // Auto-scroll banners/slides and wrap seamlessly when infinite mode is enabled.
  useEffect(() => {
    if (!_autoplay || !ref.current || slides.length <= 1) {
      return
    }

    const slider = ref.current
    const timer = setInterval(() => {
      const slideWidth = slider?.firstChild?.offsetWidth ?? 0
      if (!slideWidth) {
        return
      }

      const maxScrollLeft = Math.max(0, slider.scrollWidth - slider.clientWidth)
      const nextLeft = slider.scrollLeft + slideWidth

      if (isInfinite) {
        const loopWidth = slider.children[baseSlides.length]?.offsetLeft ?? 0
        const thirdSetStart = slider.children[baseSlides.length * 2]?.offsetLeft ?? maxScrollLeft
        if (loopWidth > 0 && nextLeft >= thirdSetStart - RIGHT_BLUR_MARGIN_TOLERANCE) {
          slider.scrollTo({ left: Math.max(0, nextLeft - loopWidth), behavior: 'auto' })
        } else {
          slider.scroll({ left: nextLeft, behavior: 'smooth' })
        }
        return
      }

      if (nextLeft >= maxScrollLeft - RIGHT_BLUR_MARGIN_TOLERANCE) {
        slider.scrollTo({ left: 0, behavior: 'smooth' })
      } else {
        slider.scroll({ left: nextLeft, behavior: 'smooth' })
      }
    }, interval)

    return () => clearInterval(timer)
  }, [_autoplay, _infinite, interval, slides.length])

  return h('div', {
    ...restProps,
    className: `flex flex-col ${sizeClass}`,
    style: 'direction: ltr', // Do not mirror for RTL.
    children: [
      h('div', { className: 'relative grow' },
        blurSides && h('div', { // Left blur
          className: 'hidden absolute start-0 top-0 bottom-0 w-6 bg-bga rounded-tr-lg rounded-br-lg animate-fade',
          style: 'background: linear-gradient(90deg, hsl(var(--color-bga)), transparent); pointer-events: none'
        }),
        h('div', {
          ref,
          className: 'w-full h-full overflow-x-scroll snap-x overflow-y-hidden grid grid-flow-col gap-5 outline-offset-1',
          role: 'list',
          onScroll (event) {
            if (isInfinite) {
              const slider = event.target
              const loopWidth = slider.children[baseSlides.length]?.offsetLeft ?? 0
              const maxScrollLeft = Math.max(0, slider.scrollWidth - slider.clientWidth)
              const thirdSetStart = slider.children[baseSlides.length * 2]?.offsetLeft ?? maxScrollLeft

              if (loopWidth > 0 && slider.scrollLeft <= RIGHT_BLUR_MARGIN_TOLERANCE) {
                slider.scrollTo({ left: slider.scrollLeft + loopWidth, behavior: 'auto' })
              } else if (loopWidth > 0 && slider.scrollLeft >= thirdSetStart - RIGHT_BLUR_MARGIN_TOLERANCE) {
                slider.scrollTo({ left: Math.max(0, slider.scrollLeft - loopWidth), behavior: 'auto' })
              }
            }

            if (!blurSides) {
              return
            }

            const slider = event.target
            if (slider.scrollLeft === 0) {
              slider.parentElement.firstChild.classList.add(tw('hidden'))
            } else if (slider.scrollLeft > slider.scrollWidth - slider.offsetWidth - RIGHT_BLUR_MARGIN_TOLERANCE) {
              slider.parentElement.lastChild.classList.add(tw('hidden'))
            } else {
              slider.parentElement.firstChild.classList.remove(tw('hidden'))
              slider.parentElement.lastChild.classList.remove(tw('hidden'))
            }
          },
          _style: id => `
            /* Firefox scrollbar cannot be styled, so hide it. */
            @-moz-document url-prefix() ${id} {
              scrollbar-width: none;
            }

            ${id} {
              grid-auto-columns: ${getWidth(colsLarge)};
              scrollbar-color: auto;
            }

            @media screen and (min-width: ${MEDIUM}px) and (max-width: ${LARGE - 1}px) {
              ${id} { grid-auto-columns: ${getWidth(colsMedium)}; }
            }

            @media screen and (max-width: ${MEDIUM - 1}px) {
              ${id} { grid-auto-columns: ${getWidth(colsSmall)}; }
            }

            ${id}::-webkit-scrollbar {
              height: 24px;
              background: transparent;
            }

            ${id}::-webkit-scrollbar-track {
              background: transparent;
              border-bottom: solid 4px hsla(var(--color-txa), 0.1);
            }

            ${id}::-webkit-scrollbar-thumb {
              background: transparent;
              border-bottom: solid 4px hsl(var(--color-bgb));
            }

            ${id} > div > * { height: 100%; }

            /* Restore the RTL direction for slide content. */
            [dir="rtl"] ${id} > div { direction: rtl; }
          `,
          children: slides.map(child =>
            h('div', {
              style: 'outline-offset: -1px',
              role: 'listitem',
              className: 'snap-start h-full',
              children: child
            })
          )
        }),
        blurSides && h('div', { // Right blur
          className: 'absolute end-0 top-0 bottom-0 w-6 bg-bga rounded-tl-lg rounded-bl-lg animate-fade',
          style: 'background: linear-gradient(90deg, transparent, hsl(var(--color-bga))); pointer-events: none'
        })
      ),
      !_hideControls && h('div', { className: 'gap-5 flex justify-end mt-5' },
        h(Button, {
          _model: { name: t('Previous slide'), icon: ARROW_BACK },
          _textless: true,
          tabindex: -1,
          'aria-hidden': true,
          onClick: () => scroll(ref, -1, isInfinite, baseSlides.length)
        }),
        h(Button, {
          _model: { name: t('Next slide'), icon: ARROW_NEXT },
          _textless: true,
          tabindex: -1,
          'aria-hidden': true,
          onClick: () => scroll(ref, 1, isInfinite, baseSlides.length)
        })
      )
    ]
  })
}

function getWidth (visibleSlideCount) {
  const gapCount = Math.ceil(visibleSlideCount) - 1
  return `calc((100% - ${gapCount} * 20px) / ${visibleSlideCount})`
}

function scroll (ref, direction = 1, infinite = false, baseSlideCount = 0) {
  const slider = ref?.current
  const slideWidth = slider?.firstChild?.offsetWidth ?? 0
  const pos = slider?.scrollLeft ?? 0
  const nextPos = pos + direction * slideWidth

  if (infinite && baseSlideCount > 1) {
    const loopWidth = slider?.children?.[baseSlideCount]?.offsetLeft ?? 0
    const thirdSetStart = slider?.children?.[baseSlideCount * 2]?.offsetLeft ?? 0
    if (loopWidth > 0 && direction < 0 && nextPos <= RIGHT_BLUR_MARGIN_TOLERANCE) {
      slider?.scrollTo({ left: pos + loopWidth, behavior: 'auto' })
      return
    }
    if (loopWidth > 0 && thirdSetStart > 0 && direction > 0 && nextPos >= thirdSetStart - RIGHT_BLUR_MARGIN_TOLERANCE) {
      slider?.scrollTo({ left: nextPos - loopWidth, behavior: 'auto' })
      return
    }
  }

  slider?.scroll({ left: nextPos, behavior: 'smooth' })
}
