import React from 'react'
import PropTypes from 'prop-types'
import {
  selectSystemProps,
  StackView,
  Typography,
  useHash,
  useInputValue,
  useResponsiveProp,
  withLinkRouter,
  variantProp,
  resolveContentMaxWidth,
  useTheme
} from '@telus-uds/components-base'
import styled from 'styled-components'
import { htmlAttrs, scrollToAnchor } from '../utils'
import NavigationItem from './NavigationItem'
import NavigationSubMenu from './NavigationSubMenu'
import collapseItems from './collapseItems'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])

const Heading = styled.div({
  alignItems: 'center',
  display: 'flex',
  flex: 1,
  justifyContent: 'flex-start',
  '> *': { display: 'contents', letterSpacing: 0 }
})

const ContentWrapper = styled.div(({ maxWidth }) => ({
  width: '100%',
  ...(maxWidth && {
    maxWidth,
    marginLeft: 'auto',
    marginRight: 'auto'
  })
}))

/**
 * NavigationBar can be used to allow customers to consistently navigate across
 * key pages within a specific product line
 */
const NavigationBar = React.forwardRef(
  (
    {
      accessibilityRole = 'navigation',
      heading,
      headingLevel = 'h1',
      items,
      onChange,
      selectedId,
      value,
      LinkRouter,
      linkRouterProps,
      tokens,
      variant,
      contentMaxWidth,
      ...rest
    },
    ref
  ) => {
    const { currentValue, setValue } = useInputValue({ value, initialValue: selectedId, onChange })
    const { themeOptions } = useTheme()

    const contentWidthValue = useResponsiveProp(contentMaxWidth)
    const responsiveWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
    const maxWidth = resolveContentMaxWidth(contentWidthValue, responsiveWidth)

    useHash(
      (hash, event) => {
        let hashItem = hash && items.find(({ href }) => hash === href)
        if (!hashItem) {
          const parentItem = items.find(({ items: parentItems }) =>
            parentItems?.some(({ href }) => hash === href)
          )
          hashItem = parentItem?.items.find(({ href }) => hash === href)
        }
        const hashId = hashItem && (hashItem.id || hashItem.label)
        if (hashId) setValue(hashId, event)
      },
      [items, setValue]
    )

    const direction = useResponsiveProp({ xs: 'column', sm: 'row' })
    const itemsForViewport = useResponsiveProp({
      xs: collapseItems(items, currentValue),
      lg: items
    })
    const openOverlayRef = React.useRef(null)
    const [openSubMenuId, setOpenSubMenuId] = React.useState(null)
    const handleSubMenuClose = (event) => {
      if (event.type === 'keydown') {
        if (event.key === 'Escape' || event.key === 27) {
          setOpenSubMenuId(null)
        }
      } else if (
        event.type === 'click' &&
        openOverlayRef?.current &&
        event.target &&
        !openOverlayRef?.current?.contains(event.target)
      ) {
        setOpenSubMenuId(null)
      } else if (
        event.type === 'touchstart' &&
        openOverlayRef?.current &&
        event.touches[0].target &&
        !openOverlayRef?.current?.contains(event.touches[0].target)
      ) {
        setOpenSubMenuId(null)
      }
    }

    const navRefDefault = React.useRef(null)
    const navRef = ref ?? navRefDefault
    const itemsRef = React.useRef(null)

    // Close the submenu when the user clicks outside the navigation bar
    const handleMouseDown = React.useCallback(
      (event) => {
        if (navRef.current && itemsRef.current) {
          // Get the bounding rectangles of the navigation bar and the items container
          const navRect = navRef.current.getBoundingClientRect()
          const itemsRect = itemsRef.current.getBoundingClientRect()

          // Check if the click was outside the navigation bar and the items container
          const isOutsideNav =
            event.clientX < navRect.left ||
            event.clientX > navRect.right ||
            event.clientY < navRect.top ||
            event.clientY > navRect.bottom

          const isOutsideItems =
            event.clientX < itemsRect.left ||
            event.clientX > itemsRect.right ||
            event.clientY < itemsRect.top ||
            event.clientY > itemsRect.bottom

          if (isOutsideNav && isOutsideItems) {
            setOpenSubMenuId(null)
          }
        }
      },
      [navRef, itemsRef, setOpenSubMenuId]
    )

    // TODO: create a custom hook for that and use here and in the Footnote
    React.useEffect(() => {
      // Add listeners for mouse clicks outside and for key presses
      document.addEventListener('mousedown', handleMouseDown)

      if (openSubMenuId !== null) {
        window.addEventListener('click', handleSubMenuClose)
        window.addEventListener('keydown', handleSubMenuClose)
        window.addEventListener('touchstart', handleSubMenuClose)
      }
      return () => {
        // Remove listeners when the component is unmounted
        document.removeEventListener('mousedown', handleMouseDown)

        if (openSubMenuId !== null) {
          window.removeEventListener('click', handleSubMenuClose)
          window.removeEventListener('keydown', handleSubMenuClose)
          window.removeEventListener('touchstart', handleSubMenuClose)
        }
      }
    }, [openSubMenuId, handleMouseDown])

    const stackView = (
      <StackView
        accessibilityRole={accessibilityRole}
        direction={direction}
        ref={navRef}
        space={2}
        tokens={{
          alignItems: direction === 'column' ? 'flex-start' : 'center',
          justifyContent: 'flex-end'
        }}
        {...selectProps(rest)}
      >
        {heading && (
          <Heading>
            <Typography variant={{ size: 'h5' }} heading={headingLevel}>
              {heading}
            </Typography>
          </Heading>
        )}
        {itemsForViewport?.map(
          (
            {
              href,
              label,
              id,
              onClick,
              ref: itemRef,
              LinkRouter: ItemLinkRouter = LinkRouter,
              linkRouterProps: itemLinkRouterProps,
              items: nestedItems,
              ...itemRest
            },
            index
          ) => {
            const itemId = id ?? label
            const handleClick = (event) => {
              if (nestedItems) {
                setOpenSubMenuId(openSubMenuId !== itemId ? itemId : null)
                return
              }
              if (href?.startsWith('#')) {
                scrollToAnchor(href, event, () => setValue(itemId, event))
              } else {
                setValue(itemId, event)
              }
              onClick?.(event)
            }

            const ItemComponent = nestedItems ? NavigationSubMenu : NavigationItem
            const isOpen = itemId === openSubMenuId

            const scrollableNestedItems =
              nestedItems?.map((item) => ({
                ...item,
                onPress: (event) => {
                  const nestedItemId = item.id ?? item.label
                  scrollToAnchor(item.href, event, () => setValue(nestedItemId, event))
                }
              })) ?? nestedItems

            return (
              <ItemComponent
                ref={itemRef}
                key={itemId}
                href={href}
                onClick={handleClick}
                selectedId={currentValue}
                index={index}
                LinkRouter={ItemLinkRouter}
                linkRouterProps={{ ...linkRouterProps, ...itemLinkRouterProps }}
                items={scrollableNestedItems}
                tokens={tokens}
                variant={variant}
                selected={itemId === currentValue}
                itemsContainerRef={itemsRef}
                {...itemRest}
                {...(scrollableNestedItems && { isOpen })}
                {...(scrollableNestedItems && isOpen && { openOverlayRef })}
              >
                {label}
              </ItemComponent>
            )
          }
        )}
      </StackView>
    )

    return maxWidth ? <ContentWrapper maxWidth={maxWidth}>{stackView}</ContentWrapper> : stackView
  }
)

NavigationBar.displayName = 'NavigationBar'

NavigationBar.propTypes = {
  ...selectedSystemPropTypes,
  ...withLinkRouter.propTypes,
  /**
   * NavigationBar pages
   *
   * Each `item` object must contain:
   *  - `heading` - user-facing text in the tab link
   *  - `href` - the URL of the page linked to.
   *  - `id` - a stable, unique identifier of the page within the set. Not written into the HTML.
   */
  items: PropTypes.arrayOf(
    PropTypes.shape({
      label: PropTypes.string.isRequired,
      href: PropTypes.string,
      id: PropTypes.string.isRequired,
      onClick: PropTypes.func,
      selected: PropTypes.bool,
      LinkRouter: withLinkRouter.propTypes?.LinkRouter,
      linkRouterProps: withLinkRouter.propTypes?.linkRouterProps,
      // One layer of nested links is allowed
      items: PropTypes.arrayOf(
        PropTypes.shape({
          label: PropTypes.string.isRequired,
          href: PropTypes.string,
          id: PropTypes.string.isRequired,
          onClick: PropTypes.func,
          selected: PropTypes.bool,
          LinkRouter: withLinkRouter.propTypes?.LinkRouter,
          linkRouterProps: withLinkRouter.propTypes?.linkRouterProps
        })
      )
    })
  ).isRequired,
  /**
   * Common navigation bar heading.
   */
  heading: PropTypes.string,
  /**
   * Sets the `headingLevel` of the `heading`
   */
  headingLevel: PropTypes.oneOf(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']),
  /**
   * Initial selected item ID
   */
  selectedId: PropTypes.string,
  /**
   * Optional function to be called on pressing a link
   */
  onChange: PropTypes.func,
  /**
   * Controlled value for selected item ID
   */
  value: PropTypes.string,
  /**
   * Accesibility role for stackview
   */
  accessibilityRole: PropTypes.string,
  /**
   * Variant configuration
   */
  variant: variantProp.propType,
  /**
   * The maximum width of the content in the NavigationBar.
   * This prop accepts responsive values for different viewports. If a number is provided,
   * it will be the max content width for the desired viewport.
   * - `xs`: 'max' | 'full' | <number>
   * - `sm`: 'max' | 'full' | <number>
   * - `md`: 'max' | 'full' | <number>
   * - `lg`: 'max' | 'full' | <number>
   * - `xl`: 'max' | 'full' | <number>
   */
  contentMaxWidth: PropTypes.shape({
    xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    md: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    sm: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
    xs: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number])
  })
}

export default NavigationBar
