//To know when scroll event was triggered by scrollIntoView, it needs to polyfill scrollIntoView method
import '../../../extensions/scrollIntoView'
import React, { useState, useEffect, useRef } from 'react'
import { throttle } from './utils'

/**
 * BackToTopButton component
 * @param {string} startSelector - user need to scroll over this element to make the button visible
 * @param {Record<string, string>} attributes - attributes to be added to the button
 * @param {Window | HTMLElement} scrollableElement - element that will be listened to scroll events
 * @returns {JSX.Element}
 * @description Component that renders a button that scrolls users to the top of the page when clicked
 * and is only visible when the user has scrolled down at least to the startSelector element and then scrolls up
 */

type BackToTopProps = {
  startSelector?: string
  attributes?: Record<string, string>
  scrollableElement?: Window | HTMLElement
}

const BackToTop: React.FC<BackToTopProps> = ({
  startSelector,
  attributes = {},
  scrollableElement = window,
}) => {
  const [visible, setVisible] = useState(false)
  const [scrolledDownFirstTime, setScrolledDownFirstTime] = useState(false)
  const lastScroll = useRef(0)
  const element = useRef<HTMLButtonElement>(null)
  // receives e as parameter that is a scroll event
  const scrollMargin = 10
  const handleScroll = () => {
    setScrolledDownFirstTime((scrolledDownFirstTime) => {
      if (!scrolledDownFirstTime && getScrollY() > lastScroll.current) {
        return true
      }
      return scrolledDownFirstTime
    })

    setVisible((visible) => {
      let result = visible
      if (
        // Scrolling direction is up
        getScrollY() < lastScroll.current &&
        // Button is not visible
        !visible &&
        // The user scrolled down at least once
        scrolledDownFirstTime
      ) {
        const startElement = startSelector
          ? document.querySelector(startSelector)
          : null

        // has passed the element that selector selects or the top of the parent container
        const hasPassedTriggerPosition = startElement
          ? startElement?.getBoundingClientRect()?.bottom <= 0
          : (element?.current?.getBoundingClientRect()?.top ?? 0) <= 0 // Add a fallback value of 0 if element is undefined
        // if the user has scrolled past the trigger element and the scroll is not programatic
        const window =
          ((scrollableElement as HTMLElement)?.ownerDocument
            ?.defaultView as Window & { programmaticScroll?: boolean }) ||
          (scrollableElement as Window & { programmaticScroll?: boolean })
        if (hasPassedTriggerPosition && !window?.programmaticScroll) {
          result = true
        }
      }
      //if scroll to top and component is visible, it will be hidden
      else if (getScrollY() < scrollMargin && visible) {
        result = false
      }
      // if scroll down and component is visible, it will be hidden
      else if (getScrollY() > lastScroll.current && visible) {
        result = false
      }
      // update last scroll position
      lastScroll.current = getScrollY()
      return result
    })
  }

  useEffect(() => {
    const handler = throttle(handleScroll, 250)
    if (element.current && scrollableElement) {
      lastScroll.current = getScrollY()
      // Add a throttle to the scroll event to prevent performance issues
      scrollableElement.addEventListener('scroll', handler)
    }
    return () =>
      scrollableElement &&
      scrollableElement.removeEventListener('scroll', handler)
  }, [element, scrollableElement, scrolledDownFirstTime])

  const handleClick = () => {
    scrollableElement?.scrollTo({
      top: 0,
      left: 0,
      behavior: 'smooth',
    })
    setVisible(false)
  }

  const getScrollY = () => {
    if (scrollableElement && scrollableElement instanceof Window) {
      return scrollableElement?.scrollY
    } else if (scrollableElement && scrollableElement instanceof HTMLElement) {
      return scrollableElement?.scrollTop
    } else {
      //fallback in case scrollableElement is null or undefined
      return window.scrollY
    }
  }

  return (
    <div
      className="back-to-top__container"
      style={{ display: visible ? 'block' : 'none' }}
    >
      <button
        ref={element}
        type="button"
        onClick={handleClick}
        className="o3-button o-visual-effects-shadow-high o3-button--big o3-button--primary o3-button-icon o3-button-icon--chevron-up"
        aria-live="polite"
        data-o3-theme="mono"
        {...attributes}
      >
        Back to top
      </button>
    </div>
  )
}

export default BackToTop
