<script setup lang="ts">
import { useSocket, XMessage } from '@/hooks'
import dayjs from 'dayjs'
import isToday from 'dayjs/plugin/isToday'
import isYesterday from 'dayjs/plugin/isYesterday'
import selfAvatar from '../../assets/learning/avatar-self.png'
import teacherAvatar from '../../assets/learning/avatar-teacher.png'
import urgeApi from '@/api/urge.ts'
import { storeToRefs } from 'pinia'
import { userStore } from '@/stores'

const props = defineProps<{ readonly: boolean; plan: anyObj }>()

const { userInfo } = storeToRefs(userStore())

dayjs.extend(isToday)
dayjs.extend(isYesterday)

const { sendMsg, inspectorMessage } = useSocket()

const FIVE_MINUTES = 5 * 60 * 1000

const clientIdCounter = ref(0)
const messages: Ref<anyObj[]> = ref([])
const isMessagesLoading = ref(false)
const isMessagesLoadFinished = ref(false)
const isNewMessage = ref(false)
const scrollContainerRef = ref<HTMLElement | null>(null)
const input = ref('')

let scrollHeightBeforeLoad = 0
let lastScrollTop = 0

const nextClientId = () => `c-${clientIdCounter.value++}`

const formatDate = (timestamp: number) => {
  const date = dayjs(timestamp)
  if (date.isToday()) return date.format('HH:mm')
  if (date.isYesterday()) return `昨天 ${date.format('HH:mm')}`
  if (date.isSame(dayjs(), 'week') && (date.day() !== 0 || dayjs().day() === 0)) {
    const daysOfWeek = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
    return `${daysOfWeek[date.day()]} ${date.format('HH:mm')}`
  }
  return date.format('YYYY年M月D日 HH:mm')
}

const insertDividers = (messages: anyObj[]) => {
  const result = []
  if (messages.length > 0) {
    result.push({ text: formatDate(messages[0].ts), type: 'divider', id: nextClientId() })
  }
  return messages.reduce((acc, currentMessage, index) => {
    acc.push(currentMessage)
    if (index < messages.length - 1) {
      const nextMessage = messages[index + 1]
      if (nextMessage.ts - currentMessage.ts >= FIVE_MINUTES) {
        acc.push({ text: formatDate(nextMessage.ts), type: 'divider', id: nextClientId() })
      }
    }
    return acc
  }, result) as anyObj[]
}

const scrollToBottom = () => {
  if (scrollContainerRef.value) {
    scrollContainerRef.value.scrollTop = scrollContainerRef.value.scrollHeight
  }
}

const loadMoreMessages = async () => {
  if (isMessagesLoading.value || isMessagesLoadFinished.value) return

  isMessagesLoading.value = true
  try {
    const earliestMessageId = messages.value.find(message => message.type !== 'divider')?.id
    const {
      data: { data: res },
    } = await urgeApi.getInspectorTalkRecordPage({
      inspectorTeacherUserId: props.plan.inspectorTeacherInfo.userId,
      inspectorTalkRecordId: earliestMessageId,
    })

    if (res.length === 0) {
      isMessagesLoadFinished.value = true
      return
    }

    const newMessages = insertDividers(
      res.map((row: anyObj) => ({
        id: row.id,
        text: row.messageContent,
        avatar:
          row.sender === 2
            ? (userInfo.value.avatarUrl ?? selfAvatar)
            : (props.plan.inspectorTeacherInfo.avatarUrl ?? teacherAvatar),
        self: row.sender === 2,
        ts: dayjs(row.createTime).valueOf(),
        status: 'success',
      })),
    )

    messages.value = [...newMessages, ...messages.value]

    await nextTick(() => {
      if (scrollContainerRef.value) {
        scrollContainerRef.value.scrollTop =
          scrollContainerRef.value.scrollHeight - scrollHeightBeforeLoad
      }
    })
  } finally {
    isMessagesLoading.value = false
  }
}

const handleScroll = () => {
  if (isMessagesLoading.value || isMessagesLoadFinished.value) return

  if (scrollContainerRef.value) {
    const scrollTop = scrollContainerRef.value.scrollTop
    if (scrollTop < lastScrollTop && scrollTop < 250) {
      scrollHeightBeforeLoad = scrollContainerRef.value.scrollHeight
      loadMoreMessages()
    }
    lastScrollTop = scrollTop
  }
}

onMounted(async () => {
  await loadMoreMessages()
  await urgeApi.readTalkRecord({
    receiverId: props.plan.userId,
    senderId: props.plan.inspectorTeacherInfo.userId,
  })
  scrollToBottom()
})

const updateMessageStatus = (messageId: string, status: 'success' | 'failed') => {
  const message = messages.value.find(message => message.id === messageId)
  if (message) message.status = status
}

const lastMessageTs = computed(() => messages.value.at(-1)?.ts ?? 0)

const handleSendMessage = async () => {
  if (!input.value) return

  const clientId = nextClientId()
  const newMessage = {
    id: clientId,
    text: input.value,
    avatar: userInfo.value.avatarUrl ?? selfAvatar,
    self: true,
    ts: Date.now(),
    status: 'sending',
  }

  input.value = ''
  isNewMessage.value = true
  messages.value = [
    ...messages.value,
    lastMessageTs.value === 0 || newMessage.ts - lastMessageTs.value > FIVE_MINUTES
      ? { text: formatDate(newMessage.ts), type: 'divider', id: nextClientId() }
      : null,
    newMessage,
  ].filter(Boolean) as anyObj[]

  await nextTick(() => {
    scrollToBottom()
    isNewMessage.value = false
  })

  const msg = {
    inspectorPlanId: props.plan.id,
    sender: 2,
    senderId: props.plan.userId,
    receiverId: props.plan.inspectorTeacherInfo.userId,
    messageType: 1,
    messageContent: newMessage.text,
  }

  try {
    const {
      data: { data: res },
    } = await urgeApi.sendInspectorTalkRecord(msg)
    sendMsg({
      type: 'inspectorTalkMessage',
      msg: {
        id: res.id,
        ...msg,
      },
    })
    updateMessageStatus(clientId, 'success')
  } catch (error) {
    updateMessageStatus(clientId, 'failed')
    XMessage.error('发送失败，稍后请重新发送')
  }
}

watch(inspectorMessage, message => {
  if (!message) {
    return
  }
  if (message.senderId === props.plan.inspectorTeacherInfo.userId) {
    const newMessage = {
      id: message.id,
      text: message.messageContent,
      avatar: props.plan.inspectorTeacherInfo.avatarUrl ?? teacherAvatar,
      self: false,
      ts: dayjs(message.createTime).valueOf(),
      status: 'success',
    }
    isNewMessage.value = true
    messages.value = [
      ...messages.value,
      lastMessageTs.value === 0 || newMessage.ts - lastMessageTs.value > FIVE_MINUTES
        ? { text: formatDate(newMessage.ts), type: 'divider', id: nextClientId() }
        : null,
      newMessage,
    ].filter(Boolean) as anyObj[]

    nextTick(() => {
      scrollToBottom()
      isNewMessage.value = false
    })

    urgeApi.readTalkRecord({
      receiverId: props.plan.userId,
      senderId: props.plan.inspectorTeacherInfo.userId,
    })
  }
})
</script>

<template>
  <div :class="['chat', { readonly }]">
    <div ref="scrollContainerRef" class="chat-messages" @scroll="handleScroll">
      <transition-group :name="isNewMessage ? 'message-tg' : ''" tag="div">
        <div
          v-for="message in messages"
          :key="message.id"
          :class="{
            message: message.type !== 'divider',
            'time-divider': message.type === 'divider',
            self: message.self,
            sending: message.status === 'sending',
            failed: message.status === 'failed',
          }"
        >
          <template v-if="message.type === 'divider'">{{ message.text }}</template>
          <template v-else>
            <div class="avatar">
              <img :src="message.avatar" alt="头像" />
            </div>
            <div class="text">{{ message.text }}</div>
          </template>
        </div>
      </transition-group>
    </div>
    <div class="chat-send">
      <div class="voice-button"></div>
      <form class="message-input" @submit.prevent="handleSendMessage">
        <div class="img-button"></div>
        <input v-model.trim="input" enterkeyhint="send" placeholder="来说点什么吧..." />
        <button type="submit" class="send-button"></button>
      </form>
    </div>
  </div>
</template>

<style scoped lang="scss">
.time-divider {
  padding: 2.67vh;
  text-align: center;
  color: #999;
  font-size: 2.33vh;
  line-height: 3.5vh;
}

.voice-button {
  background: url(../../assets/learning/icon-voice.svg) center no-repeat;
  background-size: cover;
  width: 64px;
  height: 5.33vh;
  flex-shrink: 0;
  display: none;
}

.img-button {
  background: url(../../assets/learning/icon-image.svg) center no-repeat;
  background-size: cover;
  width: 64px;
  height: 5.33vh;
  position: absolute;
  left: 8px;
  top: 0.67vh;
  display: none;
}

.send-button {
  background: url(../../assets/learning/icon-send.svg) center no-repeat;
  background-size: cover;
  width: 64px;
  height: 5.33vh;
  position: absolute;
  right: 8px;
  top: 0.67vh;
}

.message-input {
  position: relative;
  border-radius: 4vh;
  background: #ebfdff;
  height: 6.67vh;
  padding: 0 104px 0 48px;
  width: 100%;
  display: flex;

  input {
    color: $font-color;
    font-size: 2.33vh;
    line-height: 3.5vh;
    background: transparent;
    width: 100%;
  }
}

.chat-send {
  position: relative;
  border-radius: 0 0 4.17vh 4.17vh;
  height: 11.67vh;
  flex-shrink: 0;
  background: linear-gradient(
    198deg,
    #8ffdf6 11.75%,
    #8fd3fd 41.28%,
    #7eacfe 70.5%,
    #8ffdf6 96.89%
  );
  display: flex;
  align-items: center;
  padding: 0 128px;
  column-gap: 16px;
}

.text {
  padding: 1.33vh 24px;
  border-radius: 0.67vh 1.67vh;
  background: #fff;
  font-size: 2.33vh;
  line-height: 3.5vh;
  display: inline-block;

  .message.self & {
    background: #fffcd9;
    text-align: left;
  }
}

.avatar {
  position: absolute;
  left: 0;
  top: 0;
  width: 80px;
  height: 6.67vh;
  flex-shrink: 0;
  border-radius: 4.17vh;

  .message.self & {
    right: 0;
    left: auto;
  }

  img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: 50%;
  }
}

@keyframes rotate {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}

.message {
  position: relative;
  padding-left: 96px;
  padding-top: 0.25vh;

  + .message {
    margin-top: 1.83vh;
  }

  &.sending .text {
    position: relative;

    &:before {
      content: '';
      position: absolute;
      top: 50%;
      right: 100%;
      margin-top: -2vh;
      margin-right: 8px;
      width: 48px;
      height: 4vh;
      background: url(../../assets/learning/icon-loading.svg) center no-repeat;
      background-size: cover;
      animation: rotate 1s linear infinite;
    }
  }

  &.failed .text {
    position: relative;

    &:before {
      content: '';
      position: absolute;
      top: 50%;
      transform: translateY(-50%);
      right: 100%;
      margin-right: 8px;
      width: 48px;
      height: 4vh;
      background: url(../../assets/learning/icon-failed.svg) center no-repeat;
      background-size: cover;
    }
  }

  &.self {
    padding-left: 0;
    padding-right: 96px;
    text-align: right;
  }
}

.chat-messages {
  padding: 3.33vh;
  border-radius: 4.67vh 4.67vh 0 0;
  flex-grow: 1;
  background: url(../../assets/learning/bg-im-content.svg) center no-repeat #ebfdff;
  overflow: auto;
}

.chat {
  margin-top: -0.33vh;
  height: calc(100vh - 128px - 16px);
  display: flex;
  flex-direction: column;

  /* &.readonly {
     .message-input {
       background: #D4E4E5;

       input {
         display: none;
       }
     }
   }*/
}

@keyframes slide-up {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.message-tg-enter-active {
  animation: slide-up 0.3s ease-out;
}
</style>
