<script setup lang="ts">
import { useRoute } from 'vue-router'
import { onMounted, reactive, ref, useTemplateRef, watch } from 'vue'
import { api } from '@/tools/web.ts'
import type { ReturnDto } from '@/types/response.ts'
import type { Chatroom } from '@/types/chatroom.ts'
import { useMessage } from 'naive-ui'
import ChatroomCard from '@/components/chatroom/ChatroomCard.vue'
import ChatTable from '@/components/chatroom/ChatTable.vue'
import ChatControlPanel from '@/components/chatroom/ChatControlPanel.vue'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import type { AiiTokenInfo } from '@/types/aii.ts'
import { SEE_YOU_TOMORROW } from '@/types/syt.ts'
import { useHead } from '@unhead/vue'

const pageHead = reactive({
  title: '正在加载聊天室...',
  titleTemplate: '%s | 聊天室 | NyaHome',
})

useHead(pageHead)

const ROUTE = useRoute()
const MESSAGE = useMessage()

const crName = ref('')
const crDescription = ref('')
const crFeatureImage = ref('')
const crContent = ref('')
const crScript = ref('')

const selectedModel = ref<number | null>(null)
const quickerPrompt = ref('')
const inputMessage = ref<string>('')
const inputMode = ref<'continue' | 'expand'>('expand')

// aiiMessage 是 AI **正在** 输出时的存储 AI 输出的容器。在 AI 完成输出后、开始输出前以及错误被处理后，应该为 null。
const aiiMessage = ref<string | null>(null)
// aiiThinking 是思维链/思考过程，有的模型不提供
const aiiThinking = ref('')
const aiiTokenInfo = ref(SEE_YOU_TOMORROW)

function load() {
  const id = Number(ROUTE.params.id)
  api
    .get(`/chatroom/${id}/`)
    .then((res) => res.data as ReturnDto)
    .then((data) => {
      if (data.success) {
        return data.result as Chatroom
      } else {
        throw TypeError('聊天室不存在，请检查。')
      }
    })
    .then((cr) => {
      pageHead.title = cr.name
      crName.value = cr.name
      crDescription.value = cr.description
      crFeatureImage.value = cr.feature_image
      crContent.value = cr.content
      crScript.value = cr.script
    })
    .catch((e) => {
      MESSAGE.error(`访问聊天室失败：${e}`)
    })
}

watch(
  () => ROUTE.params.id,
  (newId, oldId) => {
    console.log(`聊天室跳转，从 ${oldId} 来到 ${newId}。`)
    load()
  },
)

function chat() {
  if (!selectedModel.value) {
    MESSAGE.warning('未选择模型，无法开始创作喵！')
    return
  }
  if (inputMessage.value === '') {
    MESSAGE.warning('未输入任何内容，无法开始创作喵！')
    return
  }

  const id = Number(ROUTE.params.id)
  aiiThinking.value = ''
  aiiMessage.value = ''
  aiiTokenInfo.value = SEE_YOU_TOMORROW
  // /chatroom/${id}/chat 接口返回的是 SSE 流式输出
  fetchEventSource(`/api/chatroom/${id}/chat/`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${localStorage.getItem('access-token')}`,
    },
    body: JSON.stringify({
      message: inputMessage.value,
      prefix: quickerPrompt.value,
      mode: inputMode.value,
      model_id: selectedModel.value,
    }),
    openWhenHidden: true, // 此开关控制在浏览器失去焦点时是否保持连接开启。默认为 false 会导致焦点转移时流式传输中断然后重连，很怪

    onmessage(msg) {
      if (msg.data === '[DONE]') {
        MESSAGE.success('AI 似乎已经完成创作，请检查一下喵！')
        console.log('SSE 流式输出结束。')
        // aiiMessage.value = null  // 即使 AI 完成输出，也等待用户确认后再保存喵！
        return
      }

      const data: { type: 'output' | 'thinking' | 'usage'; text?: string } = JSON.parse(msg.data)
      if (data.type === 'output') {
        aiiMessage.value += data.text as string
      } else if (data.type === 'thinking') {
        aiiThinking.value += data.text as string
      } else {
        const usage = data as AiiTokenInfo
        aiiTokenInfo.value = `总计：${usage.total_tokens} | 输入：${usage.prompt_tokens} | 输出：${usage.completion_tokens}`
        if (usage.prompt_cache_hit_tokens && usage.prompt_cache_miss_tokens) {
          aiiTokenInfo.value += `<br />[ 输入（缓存）：${usage.prompt_cache_hit_tokens} | 输入（未缓存）：${usage.prompt_cache_miss_tokens} ]`
        }
      }
    },

    onerror(err) {
      console.error(`SSE 错误：${err}`)
      // aiiMessage.value = null  // 等待用户主动确认 AI 输出错误之后，再主动重置为 null
      throw err
    },
  })
}

function accept() {
  const id = Number(ROUTE.params.id)
  api
    .post(
      `/chatroom/${id}/chat/accept/`,
      JSON.stringify({
        aii_message: aiiMessage.value,
        user_message: inputMessage.value,
        mode: inputMode.value,
      }),
    )
    .then((res) => res.data as ReturnDto)
    .then((data) => {
      if (data.success) {
        MESSAGE.success('保存成功，正在刷新创作视图喵~')
        aiiMessage.value = null
        inputMessage.value = ''
        return data.result as Chatroom
      } else {
        throw TypeError('未知原因，后端业务错误')
      }
    })
    .then((result) => {
      crContent.value = result.content
    })
    .catch((err) => {
      MESSAGE.error(`保存失败：${err}`)
    })
}

function rewrite() {
  chat()
}

function cancel() {
  aiiMessage.value = null
}

function messageEdit(oldMessage: string, newMessage: string, change: 'aii' | 'user') {
  const id = Number(ROUTE.params.id)
  api
    .post(
      `/chatroom/${id}/chat/edit/`,
      JSON.stringify({
        old_message: oldMessage,
        new_message: newMessage,
        change,
      }),
    )
    .then((res) => res.data as ReturnDto)
    .then((data) => {
      if (data.success) {
        return data.result as Chatroom
      } else {
        throw TypeError('未知原因，后端聊天记录修改失败')
      }
    })
    .then((result) => {
      crContent.value = result.content
      MESSAGE.success('聊天记录已删除，页面已更新~')
    })
    .catch((err) => {
      MESSAGE.error(`修改聊天消息失败：${err}`)
    })
}

function messageDelete(message: string, change: 'aii' | 'user') {
  const id = Number(ROUTE.params.id)
  api
    .post(`/chatroom/${id}/chat/delete/`, JSON.stringify({ message, change }))
    .then((res) => res.data as ReturnDto)
    .then((data) => {
      if (data.success) {
        return data.result as Chatroom
      } else {
        throw TypeError('未知原因，后端聊天记录删除失败')
      }
    })
    .then((result) => {
      crContent.value = result.content
      MESSAGE.success('聊天记录已删除，页面已更新~')
    })
    .catch((err) => {
      MESSAGE.error(`删除聊天消息失败：${err}`)
    })
}

onMounted(() => {
  enableSidebar()
  load()
})

const mainToggle = useTemplateRef('main-toggle')
const sidebar = useTemplateRef('sidebar')

function disableSidebar() {
  mainToggle.value?.style.setProperty('--opacity', '1')
  sidebar.value?.style.setProperty('--width', '0')
  sidebar.value?.style.setProperty('--opacity', '0')
  sidebar.value?.style.setProperty('--transform-x', '100%')
}

function enableSidebar() {
  mainToggle.value?.style.setProperty('--opacity', '0')
  sidebar.value?.style.setProperty('--width', '400px')
  sidebar.value?.style.setProperty('--opacity', '1')
  sidebar.value?.style.setProperty('--transform-x', '0')
}
</script>

<template>
  <div class="page-container">
    <div class="main-column">
      <chatroom-card
        :id="Number(ROUTE.params.id)"
        :name="crName"
        :description="crDescription"
        :feature_image="crFeatureImage"
      />
      <chat-table
        :content="crContent"
        :aii-thinking
        :aii-message
        :aii-token-info
        v-model:message="inputMessage"
        v-model:mode="inputMode"
        :on-send-message="chat"
        :on-accept="accept"
        :on-rewrite="rewrite"
        :on-cancel="cancel"
        :on-message-edit="messageEdit"
        :on-message-delete="messageDelete"
      />
      <div id="main-toggle" ref="main-toggle" @click="enableSidebar" />
    </div>
    <div class="sidebar-column" ref="sidebar">
      <chat-control-panel
        :script="crScript"
        v-model:quicker-prompt="quickerPrompt"
        v-model:select-model="selectedModel"
      />
      <div id="sidebar-toggle" @click="disableSidebar" />
    </div>
  </div>
</template>

<style scoped lang="scss">
div.page-container {
  width: min(1200px, 90vw);
  display: flex;
  flex-direction: row;
  height: 100%;
  overflow: hidden;

  div.main-column {
    flex: 1;
    min-height: 0;
    overflow: hidden;
    padding: 4px;

    display: flex;
    flex-direction: column;
    gap: 4px;

    position: relative;

    div#main-toggle {
      --opacity: 1;

      position: absolute;
      top: 40%;
      right: 12px;
      height: 20%;
      width: 10px;

      background: rgb(0 0 0 / 0.1);
      border-radius: 5px;
      opacity: var(--opacity);

      transition:
        background-color 0.8s,
        transform 0.5s,
        opacity 1s,
        height 0.5s,
        width 0.5s,
        top 0.5s,
        border-radius 0.5s;

      &:hover {
        background: rgb(0 0 0 / 0.6);
        transform: translateX(-4px);
        top: calc(50% - 15px);
        height: 30px;
        width: 30px;
        border-radius: 15px;
      }
    }
  }

  div.sidebar-column {
    --transition-x: 100%;
    --opacity: 1;
    --width: 400px;

    flex: 0;
    overflow: auto;

    position: relative;

    transition:
      transform 1s,
      opacity 1s,
      flex-basis 1s;
    transform: translateX(var(--transform-x));
    flex-basis: var(--width);
    opacity: var(--opacity);

    div#sidebar-toggle {
      position: absolute;
      top: 40%;
      left: 0;
      height: 20%;
      width: 10px;

      background: rgb(0 0 0 / 0.1);
      border-radius: 5px;

      transition:
        background-color 0.8s,
        transform 0.5s,
        height 0.5s,
        width 0.5s,
        top 0.5s,
        border-radius 0.5s;

      &:hover {
        background: rgb(0 0 0 / 0.6);
        transform: translateX(4px);
        top: calc(50% - 15px);
        height: 30px;
        width: 30px;
        border-radius: 15px;
      }
    }
  }
}
</style>
