import { Box, Button, Flex, Group, Stack, Textarea, Typography } from '@tidbcloud/uikit'
import { IconSend03 } from '@tidbcloud/uikit/icons'
import { throttle } from 'lodash-es'
import React, { useCallback, useEffect, useRef, useState } from 'react'

import { eventTracking } from 'dbaas/utils/tracking'

import { useFeedback } from '../Feedback'

import { GeneratingTips } from './GeneratingTips'
import { ActionMessage, AssistantMessage, TipsMessage, UserMessage } from './Messages'
import { PrivacyMessages, useAIPrivacy } from './PrivacyMessages'
import { useMessages } from './useMessages'

const PRESET_MESSAGE = ['What is TiDB?', 'Does TiDB support FOREIGN KEY?', 'Does TiDB support vector search?']
const INPUT_LIMIT = 600

export const Chats: React.FC<React.PropsWithChildren<unknown>> = () => {
  const { showFeedback, like, dislike } = useFeedback()
  const [wasCleared, setWasCleared] = useState(false)
  const [isSendingMessage, setIsSendingMessage] = useState(false)
  const [isStreamLoading, setIsStreamLoading] = useState(false)
  const [error, setError] = useState<any>(null)
  const messagesRef = useRef<HTMLDivElement>(null)
  const [scrollToBottom, setScrollToBottom] = useState(true)
  const releaseScroll = () => {
    const canReleaseScroll =
      messagesRef.current?.scrollHeight! - messagesRef.current?.offsetHeight! - messagesRef.current?.scrollTop! > 10
    setScrollToBottom(!canReleaseScroll)
  }
  // for throttle
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const throttleReleaseScroll = useCallback(throttle(releaseScroll, 100), [])
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const scrollToLastMessage = useCallback(
    throttle(
      ({
        scrollToBottom,
        force = false,
        delay = 100
      }: {
        scrollToBottom: boolean
        force?: boolean
        delay?: number
      }) => {
        if (!scrollToBottom && !force) {
          return
        }
        setTimeout(
          () =>
            messagesRef.current?.scroll({
              top: Number.MAX_SAFE_INTEGER,
              behavior: (force ? 'instant' : 'smooth') as ScrollBehavior
            }),
          delay
        )
      },
      100
    ),
    []
  )
  const {
    messages,
    clearMessages,
    sendMessage: _sendMessage,
    isMessagesLoading
  } = useMessages({
    onMessagesChange: () => scrollToLastMessage({ scrollToBottom, force: true }),
    onError: (e) => {
      setIsSendingMessage(false)
      setError(e)
    }
  })
  const sendMessage = (m: string) => {
    if (m === '' || isSendingMessage || isStreamLoading || !!error) {
      return false
    }

    setIsSendingMessage(true)
    _sendMessage(m)
    return true
  }
  const newSession = () => {
    clearMessages()
    setError(null)
  }

  const { isPrivacyAllowed, isPrivacyLoading } = useAIPrivacy()
  // display privacy statement if privacy is set to false on other pages.
  useEffect(() => {
    if (isPrivacyAllowed) {
      return
    }
    setWasCleared(false)
  }, [isPrivacyAllowed])

  const scrollWhenInit = useRef(() => scrollToLastMessage({ scrollToBottom, force: true, delay: 0 }))
  useEffect(() => {
    scrollWhenInit.current()
  }, [])

  const isSendMessageReady = !isMessagesLoading && !isPrivacyLoading
  const isSendMessageDisabled = !!error || !isPrivacyAllowed

  const PresetMessages = (
    <Stack gap="xs">
      {PRESET_MESSAGE.map((m) => (
        <ActionMessage
          key={m}
          onClick={() => {
            if (!isSendMessageReady || isSendMessageDisabled) {
              return
            }
            sendMessage(m)
            eventTracking('Clicked TiDB Bot preset question', { question: m })
          }}
        >
          {m}
        </ActionMessage>
      ))}
    </Stack>
  )

  return (
    <Stack h="100%" gap={0} pb="md" mx="-xl" px="md">
      <Flex
        ref={messagesRef}
        direction="column"
        gap={8}
        // for y-scroll bar offset
        mx="-md"
        px="md"
        pt="xl"
        pb={34}
        sx={{ flex: '1 1 0%', overflowY: 'auto', display: showFeedback ? 'none' : 'flex' }}
        onScroll={throttleReleaseScroll}
      >
        <Box p="xs" mt={!messages.length ? 80 : 0} mb="auto" ta="center">
          <Typography variant="body-md" c="carbon.7">
            Your TiDB Cloud assistant bot
          </Typography>
          <Typography variant="body-md" c="carbon.7">
            Trained on knowledge from documentation
          </Typography>
          <Typography variant="body-md" c="carbon.7">
            Powered by OpenAI
          </Typography>
        </Box>
        {!wasCleared && !isPrivacyAllowed && (
          <PrivacyMessages
            onPrivacyChange={(allowed) => {
              if (!!messages.length && allowed) {
                newSession()
              }
              scrollToLastMessage({ scrollToBottom, force: true })
              eventTracking('Clicked TiDB Bot privacy policy', { allowed })
            }}
          />
        )}
        {isPrivacyAllowed && (
          <>
            <TipsMessage>Welcome back! What questions do you have?</TipsMessage>
            {messages.map((msg, i) =>
              msg.displayed.role === 'assistant' ? (
                <AssistantMessage
                  key={i}
                  msg={msg}
                  messages={messages}
                  generating={isStreamLoading && i === messages.length - 1 && !error}
                  error={i === messages.length - 1 && error}
                  alwaysShowFeedback={!isStreamLoading && i === messages.length - 1}
                  onStreamChange={() => {
                    setIsSendingMessage(false)
                    setIsStreamLoading(true)
                    scrollToLastMessage({ scrollToBottom })
                  }}
                  onStreamFinished={() => {
                    setIsStreamLoading(false)
                    scrollToLastMessage({ scrollToBottom })
                  }}
                  onStreamError={(e) => {
                    setIsStreamLoading(false)
                    setError(e)
                  }}
                  onLike={() => {
                    like(msg.currentId!)
                    eventTracking('Clicked TiDB Bot like button')
                  }}
                  onDislike={() => {
                    dislike(msg.currentId!)
                    eventTracking('Clicked TiDB Bot dislike button')
                  }}
                />
              ) : (
                <UserMessage key={i} msg={msg} />
              )
            )}
            {isSendingMessage && !isStreamLoading && (
              <TipsMessage>
                <GeneratingTips />
              </TipsMessage>
            )}
          </>
        )}
      </Flex>

      {isPrivacyAllowed && !messages?.length && PresetMessages}

      <Stack
        mx="-md"
        px="md"
        pt={16}
        sx={{
          flex: 'none',
          zIndex: 99,
          boxShadow: scrollToBottom ? '' : '0 -4px 8px -6px rgba(0, 0, 0, 0.2)',
          display: showFeedback ? 'none' : 'flex'
        }}
      >
        <TextInput sendMessage={sendMessage} isLoading={!isSendMessageReady} disabled={isSendMessageDisabled} />
        <Group w="100%" justify="space-between">
          <Button
            variant="default"
            c="carbon.7"
            fw="normal"
            size="xs"
            disabled={
              isSendingMessage || isStreamLoading || !isPrivacyAllowed || !messages.length || !isSendMessageReady
            }
            onClick={() => {
              newSession()
              setScrollToBottom(true)
              setWasCleared(true)
              eventTracking('Clicked TiDB Bot clear session button')
            }}
            data-mp-event="mp-cleartopic-1691461015818"
          >
            Clear Topic
          </Button>
        </Group>
      </Stack>
    </Stack>
  )
}

const TextInput: React.FC<
  React.PropsWithChildren<{ sendMessage: (v: string) => boolean; isLoading: boolean; disabled: boolean }>
> = ({ sendMessage, isLoading, disabled }) => {
  const [inputValue, setInputValue] = useState('')
  const [focused, setFocused] = useState(false)
  const handleSendMessage = () => {
    const startSend = sendMessage(inputValue.trim())
    startSend && setInputValue('')
    eventTracking('Send message to TiDB Bot')
  }

  return (
    <Box w="100%" sx={{ position: 'relative' }}>
      <Textarea
        disabled={isLoading || disabled}
        autosize
        maxLength={INPUT_LIMIT}
        minRows={2}
        maxRows={10}
        rightSection={
          <Stack h="100%" gap={8} py={8} justify="flex-start">
            <Box
              p={8}
              pb={0}
              m={-8}
              ta="center"
              sx={(theme) => ({
                cursor: 'pointer',
                color: focused ? theme.colors.carbon[8] : theme.colors.carbon[6]
              })}
              onClick={handleSendMessage}
            >
              <IconSend03 size={16} />
            </Box>
            {!!inputValue.length && (
              <Typography variant="label-sm" color="carbon.6" ta="center">
                {INPUT_LIMIT - inputValue.length}
              </Typography>
            )}
          </Stack>
        }
        rightSectionWidth="32px"
        styles={{
          input: { lineHeight: 1.4, paddingLeft: '8px', paddingTop: '8px!important', paddingBottom: '8px!important' }
        }}
        placeholder={!isLoading ? 'Ask me anything about TiDB Cloud' : 'Loading...'}
        value={inputValue}
        onFocus={() => setFocused(true)}
        onBlur={() => setFocused(false)}
        onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setInputValue(e.target.value)}
        onKeyDown={(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
          // see ignore IME composition events
          // https://developer.mozilla.org/en-US/docs/Web/API/Element/keydown_event
          if ((e.key === 'Enter' && !e.currentTarget.value) || e.keyCode === 229) {
            e.preventDefault()
            return
          }
          if (e.key === 'Enter' && !e.shiftKey && !e.metaKey) {
            e.preventDefault()
            handleSendMessage()
          }
        }}
      />
    </Box>
  )
}
