import React from 'react'
import {
  StyleSheet,
  TouchableWithoutFeedback,
  View,
  ScrollView,
  Modal as NativeModal,
  Platform
} from 'react-native'

import PropTypes from 'prop-types'
import { applyShadowToken } from '../ThemeProvider/utils/styles'
import { useThemeTokens } from '../ThemeProvider/useThemeTokens'
import { a11yProps } from '../utils/props/a11yProps'
import { copyPropTypes } from '../utils/props/copyPropTypes'
import { getTokensPropType } from '../utils/props/tokens'
import { selectSystemProps } from '../utils/props/selectSystemProps'
import { useCopy } from '../utils/useCopy'
import { variantProp } from '../utils/props/variantProp'
import { viewProps } from '../utils/props/viewProps'
import { useViewport } from '../ViewportProvider/useViewport'
import { IconButton } from '../IconButton/IconButton'
import { dictionary } from './dictionary'
import { useScrollBlocking } from '../utils/useScrollBlocking'
import { ModalContent } from './ModalContent'
import { WebModal } from './WebModal'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])

const selectContainerStyles = ({
  containerPaddingLeft,
  containerPaddingRight,
  containerPaddingTop,
  containerPaddingBottom,
  maxWidth,
  height
}) => ({
  height,
  maxWidth,
  paddingLeft: containerPaddingLeft,
  paddingRight: containerPaddingRight,
  paddingTop: containerPaddingTop,
  paddingBottom: containerPaddingBottom
})

const selectModalStyles = ({
  backgroundColor,
  borderColor,
  borderRadius,
  borderWidth,
  height,
  paddingLeft,
  paddingRight,
  paddingTop,
  paddingBottom,
  shadow
}) => ({
  backgroundColor,
  borderColor,
  borderRadius,
  borderWidth,
  height,
  paddingLeft,
  paddingRight,
  paddingTop: Platform.OS !== 'web' ? 16 + paddingTop : paddingTop, // the 16 additions are due to the border that is present in the mobile version
  paddingBottom,
  ...applyShadowToken(shadow)
})

const selectBackdropStyles = ({ backdropColor, backdropOpacity, backdropCursor }) => ({
  backgroundColor: backdropColor,
  opacity: backdropOpacity,
  ...(Platform.OS === 'web' && backdropCursor ? { cursor: backdropCursor } : {})
})

const selectCloseButtonContainerStyles = ({ paddingRight, paddingTop }) => ({
  paddingRight,
  paddingTop: Platform.OS !== 'web' ? 24 + paddingTop : paddingTop // The 24 additions are due to the border that is present in the mobile version for the close button
})

const selectScrollViewStyles = () => ({
  flex: 1
})

/**
 * A modal window is a secondary window that opens on top of the main one.
 * Users have to interact with it before they can carry out their task and return to the main window.
 * Use to reveal additional information to a user after they have performed an explicit interaction.
 * They are a strongly discouraged pattern; it's preferred to have all relevant information within a page,
 * and irrelevant information either linked externally or omitted.
 *
 * - Must only appear after a customer interaction, not on page load or any other circumstance
 * - Open a modal based on explicit customer action e.g. clicking on a button/link/form field
 * - Only one modal should be "current" at any time
 * - Read [WebAIM's documentation](https://www.w3.org/TR/wai-aria-practices/examples/dialog-modal/dialog.html) to create accessible modals
 * - Don’t use modals to reinforce or repeat information already available in the parent page or view
 * - Don’t use modals consecutively
 */
export const Modal = React.forwardRef(
  (
    {
      children,
      isOpen,
      onClose,
      maxWidth,
      tokens,
      copy,
      closeButton,
      onCancel = onClose,
      onConfirm,
      heading,
      headingLevel,
      subHeading,
      subHeadingSize,
      bodyText,
      variant = {},
      confirmButtonText,
      confirmButtonVariant,
      cancelButtonText,
      cancelButtonType,
      footer,
      backgroundDismissible = true,
      ...rest
    },
    ref
  ) => {
    const viewport = useViewport()

    const isBackdropClickable = onClose && backgroundDismissible

    const themeTokens = useThemeTokens('Modal', tokens, variant, {
      viewport,
      maxWidth,
      backdropCursor: isBackdropClickable ? 'pointer' : 'default'
    })
    const modalRef = useScrollBlocking(isOpen)
    const modalBodyRef = React.useRef(ref)
    const modalContentRef = React.useRef(null)

    const { closeIcon: CloseIconComponent } = themeTokens

    const getCopy = useCopy({ dictionary, copy })
    const closeLabel = getCopy('closeButton')

    const handleClose = () => {
      if (typeof onClose === 'function') onClose()
    }

    const handleKeyUp = (event) => {
      if (event.key === 'Escape') onClose()
    }

    const manageFocus = React.useCallback(
      (event) => {
        if (event.key === 'Tab') {
          const focusableElements = Array.from(
            modalBodyRef?.current?.querySelectorAll(`
      a[href], button, textarea, input, select, 
      [tabindex]:not([tabindex="-1"]),
      [contenteditable="true"]
    `) || []
          )

          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()
          }
        }
      },
      [modalBodyRef]
    )

    // Show the custom react node passed to `closedButton` or the default close button if `closeButton` is `undefined`.
    // Hide the close button if `closeButton` is `null`.
    const showCloseButton = closeButton !== null

    // These refs are used to manage focus in the web modal container
    const focusTrapRef = React.useRef(null)
    const closeButtonRef = React.useRef(null)

    React.useEffect(() => {
      if (Platform.OS === 'web') {
        // Add an event listener to manage keydown and focus in the web modal container
        document.addEventListener('keydown', manageFocus)

        // Clean up the event listener
        return () => document.removeEventListener('keydown', manageFocus)
      }
      return undefined
    }, [manageFocus])

    React.useEffect(() => {
      if (isOpen) {
        modalContentRef?.current?.focus()
      }
    }, [isOpen, modalContentRef?.current?.focus])

    if (!isOpen) {
      return null
    }

    const content = (
      <>
        <ScrollView contentContainerStyle={[staticStyles.positioningContainer]} ref={modalRef}>
          <View
            style={[staticStyles.sizingContainer, selectContainerStyles(themeTokens)]}
            pointerEvents="box-none" // don't capture backdrop press events
          >
            <View
              ref={modalBodyRef}
              style={[staticStyles.modal, selectModalStyles(themeTokens)]}
              onKeyUp={handleKeyUp}
            >
              {showCloseButton && (
                <View
                  style={[
                    staticStyles.closeButtonContainer,
                    selectCloseButtonContainerStyles(themeTokens)
                  ]}
                >
                  {closeButton || (
                    <IconButton
                      onPress={handleClose}
                      icon={CloseIconComponent}
                      accessibilityRole="button"
                      accessibilityLabel={closeLabel}
                      ref={closeButtonRef}
                    />
                  )}
                </View>
              )}
              <ModalContent
                tokens={tokens}
                variant={variant}
                onCancel={onCancel}
                onConfirm={onConfirm}
                heading={heading}
                headingLevel={headingLevel}
                subHeading={subHeading}
                subHeadingSize={subHeadingSize}
                bodyText={bodyText}
                confirmButtonText={confirmButtonText}
                confirmButtonVariant={confirmButtonVariant}
                cancelButtonText={cancelButtonText}
                cancelButtonType={cancelButtonType}
                footer={footer}
              >
                {Platform.OS !== 'web' ? (
                  <ScrollView style={selectScrollViewStyles}>{children}</ScrollView>
                ) : (
                  children
                )}
              </ModalContent>
            </View>
          </View>
          {/* when a modal becomes open its first focusable element is being automatically focused */}
          {/* and we prefer the close button over backdrop */}
          <TouchableWithoutFeedback onPress={isBackdropClickable ? handleClose : undefined}>
            <View style={[staticStyles.backdrop, selectBackdropStyles(themeTokens)]} />
          </TouchableWithoutFeedback>
        </ScrollView>
      </>
    )

    if (Platform.OS === 'web') {
      return (
        <WebModal {...selectProps(rest)} ref={modalContentRef}>
          {content}
          <View accessibilityRole="button" ref={focusTrapRef} />
        </WebModal>
      )
    }
    return (
      <NativeModal transparent {...selectProps(rest)}>
        {content}
      </NativeModal>
    )
  }
)
Modal.displayName = 'Modal'

Modal.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * Content rendered inside the modal.
   */
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
  /**
   * To define the locale of the copy
   */
  copy: copyPropTypes,
  /**
   * Setting this to `true` will open the `Modal`
   */
  isOpen: PropTypes.bool,
  /**
   * This function is triggered when the `Modal` is closed
   */
  onClose: PropTypes.func,
  /**
   * `max-width` is applied to the `Modal` container
   */
  maxWidth: PropTypes.bool,
  tokens: getTokensPropType('Modal'),
  variant: variantProp.propType,
  /**
   * Pass a react node to override the default close button or pass `null` to hide the close button.
   */
  closeButton: PropTypes.node,
  /**
   * Receive a string to set the heading of the modal.
   */
  heading: PropTypes.string,
  /**
   * Receive h3 or h4 to define the level of the heading.
   */
  headingLevel: PropTypes.oneOf(['h3', 'h4']),
  /**
   * Receive a string to set the sub heading of the modal.
   */
  subHeading: PropTypes.string,
  /**
   * Receive small, medium or large to set the size of the sub heading.
   */
  subHeadingSize: PropTypes.oneOf(['small', 'medium', 'large']),
  /**
   * Receive a string to set the bodyText of the modal.
   */
  bodyText: PropTypes.string,
  /**
   * Receive a string text for the confirm button.
   */
  confirmButtonText: PropTypes.string,
  /**
   * Receives a variant of button for the confirm button.
   */
  confirmButtonVariant: PropTypes.object,
  /**
   * Receive a function for the onConfirm event in the confirm button.
   */
  onConfirm: PropTypes.func,
  /**
   * Receive a string text for the cancel button.
   */
  cancelButtonText: PropTypes.string,
  /**
   * Receives a variant of button for the cancel button.
   */
  cancelButtonType: PropTypes.elementType, // TODO: figure out a way of passing an icon to the TextButton
  /**
   * Receive a function for the onCancel event in the cancel button.
   */
  onCancel: PropTypes.func,
  /**
   * Receive a react node or an array of nodes to render at the bottom of the modal, above the action buttons.
   */
  footer: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
  /**
   * Controls whether the modal can be dismissed by clicking on the backdrop.
   * When set to `false`, clicking the backdrop will not close the modal.
   * The backdrop cursor automatically changes to 'default' to indicate it's not clickable.
   * Note: Backdrop dismissal requires `onClose` to be defined.
   * @default true
   */
  backgroundDismissible: PropTypes.bool
}

const staticStyles = StyleSheet.create({
  backdrop: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    zIndex: -1
  },
  positioningContainer: {
    flexBasis: '100%',
    alignItems: 'center',
    justifyContent: 'center'
  },
  sizingContainer: {
    maxHeight: '100%', // so that the container can expand up to the full viewport height
    width: '100%' // ensure that the modal actually expands to the set maxWidth
  },
  modal: {
    maxHeight: '100%', // so that the modal can expand vertically up to the  sizing container's height (exclusive of its vertical padding)
    ...Platform.select({
      web: {
        overflow: 'auto'
      }
    })
  },
  closeButtonContainer: {
    position: 'absolute',
    top: 0,
    right: 0,
    zIndex: 1
  }
})
