import React from 'react'
import PropTypes from 'prop-types'
import {
  Icon,
  Portal,
  selectSystemProps,
  Typography,
  useCopy,
  useTheme,
  useResponsiveProp,
  useThemeTokens,
  useViewport,
  getTokensPropType
} from '@telus-uds/components-base'
import styled, { createGlobalStyle } from 'styled-components'
import OrderedListBase from '../OrderedList/OrderedListBase'
import { htmlAttrs, media, renderStructuredContent, isElementFocusable } from '../utils'
import defaultDictionary from './dictionary'

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

const GlobalBodyScrollLock = createGlobalStyle({
  'html, body': media().until('md').css({ overflow: 'hidden' })
})

const StyledFootnote = styled.div(
  ({ footnoteBackground, isVisible, footnoteBorderTop, isScrollable, isMobileFullScreen }) => ({
    position: 'fixed',
    overflowY: isVisible && isScrollable ? 'scroll' : 'hidden',
    top: 0,
    left: window.innerWidth >= viewportBreakpoint ? '50%' : 0,
    height: '100vh',
    width: '100vw',
    backgroundColor: footnoteBackground,
    display: 'block',
    transform: 'translateY(100%)',
    translate: window.innerWidth >= viewportBreakpoint ? '-50%' : '',
    transition: 'transform 500ms ease-out',
    '@media() (prefers-reduced-motion: reduce)': {
      transition: 'none'
    },
    zIndex: 99999,
    visibility: isVisible ? 'visible' : 'hidden',
    ...media()
      .from(isMobileFullScreen ? 'md' : 'xs')
      .css({
        top: 'auto',
        bottom: 0,
        height: 'auto',
        maxHeight: '50vh',
        borderTop: footnoteBorderTop
      })
  }),
  ({ isOpen }) => {
    if (isOpen) {
      return {
        transform: 'translateY(0)'
      }
    }
    return {}
  }
)

const StyledFootnoteHeader = styled.div(() => ({
  position: 'relative',
  width: '100%'
}))

const StyledHeader = styled.div(
  ({
    footnoteHeaderPaddingLeft,
    footnoteHeaderPaddingRight,
    footnoteHeaderPaddingTop,
    footnoteHeaderPaddingBottom
  }) => ({
    alignItems: 'center',
    display: 'flex',
    flexDirection: 'row',
    justifyContent: 'space-between',
    paddingTop: footnoteHeaderPaddingTop,
    paddingBottom: footnoteHeaderPaddingBottom,
    paddingRight: footnoteHeaderPaddingRight,
    paddingLeft: footnoteHeaderPaddingLeft
  })
)

const StyledFootnoteBody = styled.div(
  ({ footnoteBodyBackground, footnoteBodyPadding, headerHeight, bodyHeight, isTextVisible }) => ({
    overflow: 'auto',
    transition: 'height 300ms ease-out, opacity 200ms ease-out',
    transform: 'translateZ(0)',
    '@media() (prefers-reduced-motion: reduce)': {
      transition: 'height 1ms ease-out, opacity 1ms ease-out'
    },
    backgroundColor: footnoteBodyBackground,
    padding: footnoteBodyPadding,
    maxHeight: `calc(100vh - ${headerHeight}px)`,
    ...media()
      .from('md')
      .css({
        maxHeight: `calc(50vh - ${headerHeight}px)`
      }),
    height: bodyHeight,
    opacity: isTextVisible ? 1 : 0
  })
)

const List = styled(OrderedListBase)(({ listPaddingLeft }) => ({
  listStylePosition: 'outside',
  paddingLeft: listPaddingLeft
}))

const ListItem = styled(OrderedListBase.Item)(
  ({
    listItemMarkerFontSize,
    listItemMarkerLineHeight,
    listItemColor,
    listItemFontSize,
    listItemLineHeight,
    listItemPaddingLeft
  }) => ({
    display: 'list-item',
    '&::marker': {
      fontFamily: 'HNforTELUSSA400normal',
      fontSize: listItemMarkerFontSize,
      lineHeight: listItemMarkerLineHeight,
      textAlign: 'end !important'
    },
    color: listItemColor,
    fontFamily: 'HNforTELUSSA400normal',
    fontSize: listItemFontSize,
    lineHeight: listItemLineHeight,
    paddingLeft: listItemPaddingLeft
  })
)

const CloseButton = styled.button(
  ({
    closeButtonBorder,
    closeButtonHeight,
    closeButtonBackgroundColor,
    closeButtonMargin,
    closeButtonWidth
  }) => ({
    alignItems: 'center',
    borderRadius: '50%',
    cursor: 'pointer',
    display: 'flex',
    justifyContent: 'center',
    backgroundColor: closeButtonBackgroundColor,
    border: closeButtonBorder,
    height: closeButtonHeight,
    margin: closeButtonMargin,
    width: closeButtonWidth
  })
)

const ContentContainer = styled.div(
  {
    'margin-left': 'auto',
    'margin-right': 'auto',
    left: 0,
    right: 0,
    maxWidth: '1200px'
  },
  ({ maxWidth }) => ({
    width: maxWidth
  })
)

const StyledCustomContentContainer = styled.div(
  ({ listItemColor, listItemFontSize, listItemLineHeight, listItemPaddingLeft }) => ({
    fontSize: listItemFontSize,
    lineHeight: listItemLineHeight,
    paddingLeft: listItemPaddingLeft,
    color: listItemColor,
    fontFamily: 'HNforTELUSSA400normal'
  })
)

const usePrevious = (value) => {
  const ref = React.useRef()
  React.useEffect(() => {
    ref.current = value
  })
  if (ref.current) {
    return ref.current
  }
  return {}
}

/**
 * Use `Footnote` to display a single legal content.
 *
 * ## Usage Criteria
 *
 * - Use `Footnote` to display a single legal statement
 * - Display on top of all UI, including other sticky elements such as Cart Summary
 * - Dismiss by clicking on the close button, clicking anywhere outside of the `Footnote`, or by pressing the ESC key
 * - Responsive display based on breakpoints
 * - Use copy to set language, ‘en’ for English or ‘fr’ for French
 *
 * ## Accessibility requirements
 *
 * - Only one instance of `Footnote` should display at a time
 * - Place `Footnote` as the last element in the body or main
 * - When `Footnote` is open, the inert prop must be set on all children of body excluding the Footnote
 * - When `Footnote` is closed, focus must return to the initiating element
 */
const Footnote = React.forwardRef((props, ref) => {
  const {
    copy = 'en',
    number,
    content,
    onClose,
    isOpen = false,
    tokens,
    variant = {},
    isMobileFullScreen = true,
    dictionary = defaultDictionary,
    ...rest
  } = props
  const viewport = useViewport()
  const {
    footnoteBackground,
    footnoteBorderTopSizeMd,
    footnoteBorderColorMd,
    headerMargin,
    footnoteBodyBackground,
    footnoteBodyPaddingLeft,
    footnoteBodyPaddingRight,
    footnoteBodyPaddingTop,
    footnoteBodyPaddingBottom,
    footnoteHeaderPaddingLeft,
    footnoteHeaderPaddingRight,
    footnoteHeaderPaddingTop,
    footnoteHeaderPaddingBottom,
    headerLineHeight,
    headerFontSize,
    listPaddingLeft,
    listItemMarkerFontSize,
    listItemMarkerLineHeight,
    listItemColor,
    listItemFontSize,
    listItemLineHeight,
    listItemPaddingLeft,
    closeButtonBorderSize,
    closeButtonBorderColor,
    closeButtonBackgroundColor,
    closeButtonHeight,
    closeButtonMarginTop,
    closeButtonMarginLeft,
    closeButtonMarginRight,
    closeButtonMarginBottom,
    closeButtonWidth,
    closeButtonIconSize,
    closeIcon
  } = useThemeTokens('Footnote', tokens, variant, { viewport })

  const footnoteRef = React.useRef(null)
  const headerRef = React.useRef(null)
  const bodyRef = React.useRef(null)
  const contentRef = React.useRef(null)
  const closeButtonRef = React.useRef(null)
  const [data, setData] = React.useState({ content: null, number: null })
  const [headerHeight, setHeaderHeight] = React.useState('auto')
  const [bodyHeight, setBodyHeight] = React.useState('auto')
  const [isVisible, setIsVisible] = React.useState(false)
  const [isTextVisible, setIsTextVisible] = React.useState(true)
  const getCopy = useCopy({ dictionary, copy })

  const prevProps = usePrevious(props)
  const theme = useTheme()
  const maxWidth = useResponsiveProp(theme.themeOptions?.contentMaxWidth)
  const [isScrollable, setIsScrollable] = React.useState(false)

  const closeFootnote = React.useCallback(
    (event, options) => {
      onClose(event, options)
    },
    [onClose]
  )

  /**
   * When listen for ESCAPE, close button clicks, and clicks outside of the Footnote. Call onClose.
   * When the event type is a 'keydonw' and the event key is a 'Tab', using a 'querySelectorAll we obtain all
   * the interactive elements within the footnote, we order and save the first and the last,
   * if the footnote is active the focus will be inside the footnote until it is closed,
   * if there are no interactive elements the focus will remain inside the close button.
   */
  const manageFootnoteFocusAndClose = React.useCallback(
    (event) => {
      if (!isVisible) {
        return
      }

      if (event.type === 'keydown') {
        if (event.key === 'Escape' || event.key === 27) {
          closeFootnote(event, { returnFocus: true })
        } else if (event.key === 'Tab') {
          const focusableElements = Array.from(footnoteRef.current.querySelectorAll('*')).filter(
            isElementFocusable
          )
          const firstElement = focusableElements[0]
          const lastElement = focusableElements[focusableElements.length - 1]

          if (event.shiftKey && document.activeElement === firstElement) {
            event.preventDefault()
            lastElement.focus()
          } else if (!event.shiftKey && document.activeElement === lastElement) {
            event.preventDefault()
            firstElement.focus()
          }
        }
      } else if (
        (event.type === 'click' || event.type === 'mousedown') &&
        footnoteRef?.current &&
        event.target &&
        !footnoteRef?.current?.contains(event.target) &&
        event.target.getAttribute('data-tds-id') !== 'footnote-link'
      ) {
        closeFootnote(event, { returnFocus: false })
      } else if (
        event.type === 'touchstart' &&
        footnoteRef?.current &&
        event.touches[0].target &&
        !footnoteRef?.current?.contains(event.touches[0].target) &&
        event.touches[0].target.getAttribute('data-tds-id') !== 'footnote-link'
      ) {
        closeFootnote(event, { returnFocus: false })
      }
    },
    [closeFootnote, isVisible]
  )

  const saveCurrentHeight = () => {
    const oldHeight = contentRef.current.offsetHeight
    setBodyHeight(oldHeight)
  }

  const focusHeading = () => {
    if (Boolean(content) && isVisible && closeButtonRef && closeButtonRef.current !== null) {
      closeButtonRef.current.focus()
    }
  }

  const handleStyledFootnoteTransitionEnd = (event) => {
    if (event.propertyName === 'transform' && !isOpen) {
      setIsVisible(false)
    } else {
      focusHeading()
    }
  }

  const handleTransitionEnd = (event) => {
    event.persist()
    if (event.propertyName === 'opacity' && !isTextVisible) {
      setData({ content, number })
      if (bodyHeight !== contentRef.current.offsetHeight) {
        // Set new height
        setBodyHeight(contentRef.current.offsetHeight)
      } else {
        setIsTextVisible(true)
      }
    } else {
      setBodyHeight(contentRef.current.offsetHeight)
    }

    if (event.propertyName === 'height' && !isTextVisible) {
      setIsTextVisible(true)
    }
  }

  const resetFootnote = () => {
    // Reset footnote state if closed
    if (!isOpen) {
      setBodyHeight('auto')
      setIsTextVisible(true)
    }
  }

  // Set height of header on mount
  React.useEffect(() => {
    setHeaderHeight(headerRef.current?.offsetHeight)
  }, [])

  const preventDefault = (event) => {
    if (!bodyRef.current.contains(event.touches[0].target)) {
      event.preventDefault()
    }
  }

  // Add listeners for mouse clicks outside of Footnote and for ESCAPE key presses
  React.useEffect(() => {
    if (isOpen) {
      setIsVisible(true)
      document.addEventListener('mousedown', manageFootnoteFocusAndClose)
      window.addEventListener('click', manageFootnoteFocusAndClose)
      window.addEventListener('keydown', manageFootnoteFocusAndClose)
      window.addEventListener('touchstart', manageFootnoteFocusAndClose)
      window.addEventListener('touchmove', preventDefault, { passive: false })
    }
    return () => {
      if (isOpen) {
        document.removeEventListener('mousedown', manageFootnoteFocusAndClose)
        window.removeEventListener('click', manageFootnoteFocusAndClose)
        window.removeEventListener('keydown', manageFootnoteFocusAndClose)
        window.removeEventListener('touchstart', manageFootnoteFocusAndClose)
        window.removeEventListener('touchmove', preventDefault)
      }
    }
  }, [manageFootnoteFocusAndClose, isOpen])

  // Set data if opening a new footnote
  React.useEffect(() => {
    if (isOpen && !prevProps.isOpen) {
      setData({ content, number })
    }
  }, [isOpen, prevProps.isOpen, content, number])

  React.useEffect(() => {
    if (isOpen && prevProps.isOpen && number !== prevProps.number) {
      saveCurrentHeight()
      setIsTextVisible(false)
    }
  }, [number, isOpen, prevProps.isOpen, prevProps.number])

  // Reset footnote on close
  React.useEffect(resetFootnote, [isOpen])

  const getFootnoteBodyContent = React.useCallback(() => {
    if (!data.number || !data.content) {
      return null
    }
    if (React.isValidElement(data.content)) {
      return (
        <StyledCustomContentContainer
          listItemColor={listItemColor}
          listItemFontSize={listItemFontSize}
          listItemLineHeight={listItemLineHeight}
          listItemPaddingLeft={listItemPaddingLeft}
          ref={contentRef}
        >
          {data.content}
        </StyledCustomContentContainer>
      )
    }
    return (
      <List start={data.number} ref={contentRef} listPaddingLeft={listPaddingLeft}>
        <ListItem
          listItemMarkerFontSize={listItemMarkerFontSize}
          listItemMarkerLineHeight={listItemMarkerLineHeight}
          listItemColor={listItemColor}
          listItemFontSize={listItemFontSize}
          listItemLineHeight={listItemLineHeight}
          listItemPaddingLeft={listItemPaddingLeft}
        >
          <Typography
            tokens={{
              fontSize: listItemFontSize,
              lineHeight: listItemLineHeight
            }}
          >
            {renderStructuredContent(data.content)}
          </Typography>
        </ListItem>
      </List>
    )
  }, [
    data.content,
    data.number,
    listItemColor,
    listItemFontSize,
    listItemLineHeight,
    listItemMarkerFontSize,
    listItemMarkerLineHeight,
    listItemPaddingLeft,
    listPaddingLeft
  ])

  const checkIfScrollable = React.useCallback(() => {
    const footnoteElement = footnoteRef.current
    if (footnoteElement) {
      const footnoteViewportHeight = footnoteElement.clientHeight ? footnoteElement.clientHeight : 0
      const contentHeight = contentRef.current ? contentRef.current.offsetHeight : 0

      setIsScrollable(contentHeight > footnoteViewportHeight * 0.5)
    }
  }, [contentRef, setIsScrollable])

  React.useEffect(() => {
    if (isOpen) {
      setTimeout(() => {
        checkIfScrollable()
      }, 100)
    }
  }, [isOpen, checkIfScrollable])

  return (
    <Portal>
      <div {...selectProps(rest)} ref={ref}>
        {isOpen && <GlobalBodyScrollLock />}
        <StyledFootnote
          ref={footnoteRef}
          isOpen={isOpen}
          isVisible={isVisible}
          onTransitionEnd={handleStyledFootnoteTransitionEnd}
          tabIndex={0}
          footnoteBackground={footnoteBackground}
          footnoteBorderTop={`${footnoteBorderTopSizeMd}px solid ${footnoteBorderColorMd}`}
          isScrollable={isScrollable}
          isMobileFullScreen={isMobileFullScreen}
        >
          <ContentContainer maxWidth={maxWidth}>
            <StyledFootnoteHeader ref={headerRef} viewport={viewport}>
              <StyledHeader
                footnoteHeaderPaddingLeft={footnoteHeaderPaddingLeft}
                footnoteHeaderPaddingRight={footnoteHeaderPaddingRight}
                footnoteHeaderPaddingTop={footnoteHeaderPaddingTop}
                footnoteHeaderPaddingBottom={footnoteHeaderPaddingBottom}
                headerMargin={headerMargin}
              >
                <Typography
                  tokens={{
                    fontSize: headerFontSize,
                    lineHeight: headerLineHeight
                  }}
                  variant={{ size: 'h4' }}
                >
                  {getCopy('heading')}
                </Typography>
                <CloseButton
                  ref={closeButtonRef}
                  closeButtonBorder={`${closeButtonBorderSize} solid ${closeButtonBorderColor}`}
                  closeButtonWidth={`${closeButtonWidth}px`}
                  closeButtonHeight={`${closeButtonHeight}px`}
                  closeButtonBackgroundColor={closeButtonBackgroundColor}
                  closeButtonMargin={`${closeButtonMarginTop}px ${closeButtonMarginRight}px ${closeButtonMarginBottom}px ${closeButtonMarginLeft}px`}
                  onClick={(event) => {
                    closeFootnote(event, { returnFocus: true })
                  }}
                  aria-label={getCopy('close')}
                >
                  <Icon icon={closeIcon} tokens={{ size: `${closeButtonIconSize}px` }} />
                </CloseButton>
              </StyledHeader>
            </StyledFootnoteHeader>
            <StyledFootnoteBody
              ref={bodyRef}
              bodyHeight={bodyHeight}
              headerHeight={headerHeight}
              isTextVisible={isTextVisible}
              onTransitionEnd={handleTransitionEnd}
              maxWidth={theme.contentMaxWidth}
              footnoteBodyBackground={footnoteBodyBackground}
              footnoteBodyPadding={`${footnoteBodyPaddingTop}px ${footnoteBodyPaddingRight}px ${footnoteBodyPaddingBottom}px ${footnoteBodyPaddingLeft}px`}
            >
              {getFootnoteBodyContent()}
            </StyledFootnoteBody>
          </ContentContainer>
        </StyledFootnote>
      </div>
    </Portal>
  )
})

Footnote.displayName = 'Footnote'

const copyShape = PropTypes.shape({
  close: PropTypes.string.isRequired,
  heading: PropTypes.string.isRequired
})

// If a language dictionary entry is provided, it must contain every key
const dictionaryContentShape = PropTypes.shape({
  a11yLabel: PropTypes.string.isRequired,
  close: PropTypes.string.isRequired,
  heading: PropTypes.string.isRequired
})

Footnote.propTypes = {
  ...selectedSystemPropTypes,
  tokens: getTokensPropType('Footnote'),
  /**
   * The content.
   */
  content: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /**
   * Use the `copy` prop to either select provided English or French copy by passing 'en' or 'fr' respectively.
   * To provide your own, pass a JSON object with the keys `heading` and `close`.
   */
  copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), copyShape]),
  /**
   * A boolean flag used hide or show the `Footnote`. Set to `true` to open the `Footnote`.
   */
  isOpen: PropTypes.bool,
  /**
   * The number, must match the number of the `FootnoteLink` that initiated the `Footnote`.
   */
  number: PropTypes.number,
  /**
   * A callback function to handle the closing of the footnote.
   *
   * @param {SyntheticEvent} event The React `SyntheticEvent`
   * @param {Object} options Custom options
   * @param {boolean} options.returnFocus Should the `Footnote` return focus on close
   */
  onClose: PropTypes.func.isRequired,
  /**
   * Override the default dictionary, by passing the complete dictionary object for `en` and `fr`
   */
  dictionary: PropTypes.shape({
    en: dictionaryContentShape,
    fr: dictionaryContentShape
  }),
  /**
   * A boolean flag used to disable isMobileFullScreen of Footnote for mobile view
   */
  isMobileFullScreen: PropTypes.bool
}

export default Footnote
