import React, { cloneElement, FC, isValidElement, useEffect, useRef, useState } from 'react'
import { AsyncMethodReturns, connectToChild } from 'penpal'
import {
  Popover,
  PopoverTrigger,
  PopoverContent,
  Box,
  useDisclosure,
  useOutsideClick,
  chakra,
  Button,
  Portal,
} from '@chakra-ui/react'
import maxSize from 'popper-max-size-modifier'
import { NotificationsMenuTrigger, NotificationsTriggerTheme } from './notifications-trigger'
import { HNS_HC_NOTIFICATIONS_LIST_URL } from '../constants'
import { ApolloError } from '@apollo/client'
import { apolloClient } from '../apollo-client'
import { useUnreadCountQueryQuery } from '../graphql'
import { useHNSContext } from '../provider'

const GUTTER = 16

const applyMaxSize = {
  name: 'applyMaxSize',
  enabled: true,
  phase: 'beforeWrite',
  requires: ['maxSize'],
  fn({ state }: any) {
    // The `maxSize` modifier provides this data
    const { height } = state.modifiersData.maxSize

    state.styles.popper = {
      ...state.styles.popper,
      height: `${height - GUTTER}px`,
      display: 'flex',
      flexDirection: 'column',
    }
  },
}

export interface NotificationsPopoverProps {
  buttonTheme?: NotificationsTriggerTheme
  unreadCount?: number
  onNotificationClick?: (id: string, clickUrl?: string) => void
  closeOnNotificationClick?: boolean
  buttonComponent?: React.ReactNode
}

interface ChildApi {
  setIsNotificationCenterVisible: (value: boolean) => void
  setIsWebPushSubscribed: (value: boolean) => void
}

export const NotificationsPopover: FC<NotificationsPopoverProps> = ({
  unreadCount,
  buttonTheme,
  onNotificationClick: onNotificationClickProp,
  closeOnNotificationClick = true,
  buttonComponent = <NotificationsMenuTrigger />,
}) => {
  const { getUserToken, subscribeToWebPush, isWebPushSubscribed } = useHNSContext()
  const [mounted, setMounted] = useState<boolean>(false)
  // we need this ref because inside the useEffect the state is not updated yet
  const refisOpened = useRef<boolean>(false)
  const iframeRef = useRef<HTMLIFrameElement>(null)
  const containerRef = useRef<HTMLDivElement | null>(null)
  const connection = useRef<AsyncMethodReturns<ChildApi>>()

  const { isOpen, onOpen, onClose } = useDisclosure({
    onClose() {
      refisOpened.current = false
      connection.current?.setIsNotificationCenterVisible?.(false)
    },

    onOpen() {
      console.log('onOpen', connection)
      refisOpened.current = true
      connection.current?.setIsNotificationCenterVisible?.(true)
    },
  })

  useEffect(() => {
    if (!mounted || !iframeRef.current || !isOpen) {
      return
    }

    const newConn = connectToChild<ChildApi>({
      // The iframe to which a connection should be made
      iframe: iframeRef.current,
      // Methods the parent is exposing to the child
      methods: {
        getUserToken,
        refetchUnreadCount: refetch,
        subscribeToWebPush,
        isWebPushSubscribed: async () => {
          // navigator.serviceWorker.getRegistration("/hns-webpush-sw.js")
          return isWebPushSubscribed
        },
        onNotificationClick: (id: string, clickUrl?: string) => {
          if (onNotificationClickProp) {
            onNotificationClickProp(id, clickUrl)
          }
          if (closeOnNotificationClick) {
            onClose()
          }
        },
      },
    })

    newConn.promise.then((child) => {
      connection.current = child
      if (child.setIsNotificationCenterVisible) {
        child.setIsNotificationCenterVisible?.(true)
      }
      child.setIsWebPushSubscribed?.(isWebPushSubscribed)
    })

    return () => {
      if (newConn) {
        newConn.promise.then((child) => {
          if (child.setIsNotificationCenterVisible) {
            child.setIsNotificationCenterVisible?.(false)
          }
        })
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [getUserToken, isOpen])

  useEffect(() => {
    if (connection.current) {
      console.log('setIsWebPushSubscribed', connection, isWebPushSubscribed)
      connection.current?.setIsWebPushSubscribed(isWebPushSubscribed)
    }
  }, [isWebPushSubscribed])

  const [jwt, setJwt] = React.useState<string | undefined>()

  const { loading, data, refetch } = useUnreadCountQueryQuery({
    client: apolloClient,
    skip: !jwt,
    ssr: false,
    context: {
      headers: {
        authorization: `Bearer ${jwt}`,
      },
    },
    notifyOnNetworkStatusChange: true,
  })

  useOutsideClick({
    ref: containerRef,
    enabled: isOpen,
    handler: (e) => {
      if (e.target instanceof HTMLElement && e.target.closest('[data-action="notifications-menu"]')) {
        return
      } else {
        onClose()
      }
    },
  })

  useEffect(() => {
    let ignore = false

    const getToken = async () => {
      const token = await getUserToken()

      if (!ignore) {
        setJwt(token)
      }
    }

    getToken()

    return () => {
      ignore = true
    }
  }, [getUserToken])

  useEffect(() => {
    setMounted(true)
    return () => {
      setMounted(false)
    }
  }, [])

  useEffect(() => {
    const interval = setInterval(async () => {
      try {
        await refetch()
      } catch (e) {
        if (e instanceof ApolloError && e.message === 'Not authorized') {
          const token = await getUserToken()
          setJwt(token)
        }
      }
    }, 10 * 1000)
    return () => clearInterval(interval)
  }, [])

  if (!mounted) {
    return (
      <>
        {isValidElement(buttonComponent) && typeof buttonComponent !== 'string' ? (
          cloneElement<HTMLButtonElement, any>(
            buttonComponent as any,
            {
              isActive: false,
              isLoading: true,
              unreadCount: data?.unreadCount || 0,
              'data-action': 'notifications-menu',
              isDisabled: true,
            } as any
          )
        ) : typeof buttonComponent === 'string' ? (
          <Button isDisabled>{buttonComponent}</Button>
        ) : (
          buttonComponent
        )}
      </>
    )
  }

  return (
    <Popover
      closeOnBlur={false}
      isOpen={isOpen}
      onClose={onClose}
      onOpen={onOpen}
      returnFocusOnClose
      closeOnEsc
      modifiers={[maxSize, applyMaxSize as any]}
      isLazy
      lazyBehavior="keepMounted"
    >
      <PopoverTrigger>
        {isValidElement(buttonComponent) && typeof buttonComponent !== 'string' ? (
          cloneElement<HTMLButtonElement, any>(
            buttonComponent as any,
            {
              isActive: isOpen,
              isLoading: loading,
              unreadCount: data?.unreadCount || 0,
              'data-action': 'notifications-menu',
            } as any
          )
        ) : typeof buttonComponent === 'string' ? (
          <Button>{buttonComponent}</Button>
        ) : (
          buttonComponent
        )}
      </PopoverTrigger>
      <Portal>
        <PopoverContent minW="360px" overflow="hidden" h="100%" boxShadow="2xl">
          <Box ref={containerRef} h="100%" overflowY="scroll" overflowX="hidden">
            <chakra.iframe w="100%" h="100%" overflowX="hidden" src={HNS_HC_NOTIFICATIONS_LIST_URL} ref={iframeRef} />
          </Box>
        </PopoverContent>
      </Portal>
    </Popover>
  )
}
