<template>
  <AppShell>
    <div class="flex h-[calc(100vh-8rem)] -m-6">
      <!-- 左侧会话列表 -->
      <div class="w-72 border-r overflow-y-auto p-2 flex flex-col gap-1">
        <NButton block class="mb-2" @click="showCreate = true"> + 新建对话 </NButton>
        <div
          v-for="c in chat.conversations"
          :key="c.id"
          :class="[
            'group p-2 rounded cursor-pointer hover:bg-neutral-100 flex items-center justify-between gap-2',
            chat.currentConversation?.id === c.id ? 'bg-blue-50' : '',
          ]"
          @click="open(c.id)"
        >
          <div class="flex-1 min-w-0">
            <div class="truncate text-sm">
              {{ c.title || '(未命名)' }}
            </div>
            <div class="text-xs text-neutral-400">
              {{ formatTime(c.updated_at) }}
            </div>
          </div>
          <NPopconfirm :show-icon="false" @positive-click="chat.deleteConversation(c.id)">
            <template #trigger>
              <NButton
                text
                size="tiny"
                class="opacity-0 group-hover:opacity-100 transition-opacity"
                @click.stop
              >
                <template #icon>
                  <NIcon><TrashOutline /></NIcon>
                </template>
              </NButton>
            </template>
            <template #default> 删除后无法恢复，确定删除此会话？ </template>
          </NPopconfirm>
        </div>
        <div v-if="chat.conversations.length === 0" class="text-xs text-neutral-400 px-2">
          暂无对话
        </div>
      </div>

      <!-- 右侧消息流 -->
      <div class="flex-1 flex flex-col">
        <div
          v-if="!chat.currentConversation"
          class="flex-1 flex items-center justify-center text-neutral-400"
        >
          选择或新建会话
        </div>
        <template v-else>
          <!-- 头部操作栏 -->
          <div class="border-b px-4 py-2 flex items-center justify-between">
            <div class="text-sm font-medium truncate flex-1">
              {{ chat.currentConversation.title || '(未命名)' }}
            </div>
            <NPopconfirm :show-icon="false" @positive-click="deleteCurrentConversation">
              <template #trigger>
                <NButton text size="small">
                  <template #icon>
                    <NIcon><TrashOutline /></NIcon>
                  </template>
                  删除
                </NButton>
              </template>
              <template #default> 删除后无法恢复，确定删除此会话？ </template>
            </NPopconfirm>
          </div>
          <div ref="scrollEl" class="flex-1 overflow-y-auto p-4 space-y-3">
            <MessageBubble
              v-for="m in chat.messages"
              :key="m.id"
              v-memo="[m.id, m.content, m.status]"
              :message="m"
              @retry="onRetry"
            />
          </div>
          <div class="border-t p-3 space-y-2">
            <AttachmentUploader
              ref="uploaderRef"
              :model-capabilities="currentModelCaps"
              @change="onAttachmentChange"
            />
            <div class="flex gap-2">
              <NInput
                v-model:value="draft"
                type="textarea"
                :autosize="{ minRows: 1, maxRows: 6 }"
                placeholder="发送消息（Enter 发送，Shift+Enter 换行）"
                :disabled="chat.isStreaming"
                @keydown.enter.exact.prevent="send"
              />
              <NButton
                v-if="!chat.isStreaming"
                type="primary"
                :disabled="!draft.trim()"
                @click="send"
              >
                发送
              </NButton>
              <NButton v-else type="warning" @click="chat.cancelMessage"> 中断 </NButton>
            </div>
          </div>
        </template>
      </div>

      <ConversationCreateModal
        v-model:show="showCreate"
        :project-id="projectId"
        @created="onCreated"
      />
    </div>
  </AppShell>
</template>

<script setup lang="ts">
import { computed, defineAsyncComponent, nextTick, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { NButton, NInput, NPopconfirm, NIcon } from 'naive-ui'
import { TrashOutline } from '@vicons/ionicons5'

import AppShell from '@/layouts/AppShell.vue'
import { useChatStore } from '@/stores/chat'
import { formatDateTime } from '@/utils/date'
const MessageBubble = defineAsyncComponent(() => import('./components/MessageBubble.vue'))
import AttachmentUploader from './components/AttachmentUploader.vue'
const ConversationCreateModal = defineAsyncComponent(() => import('./ConversationCreateModal.vue'))
import { modelsApi } from '@/api/models'
import type { LLMModel, ModelCapabilities } from '@/api/llmAdmin'

const chat = useChatStore()
const route = useRoute()
const router = useRouter()

const draft = ref('')
const attachmentIds = ref<string[]>([])
const showCreate = ref(false)
const uploaderRef = ref<InstanceType<typeof AttachmentUploader> | null>(null)
const scrollEl = ref<HTMLElement | null>(null)
const allModels = ref<LLMModel[]>([])

const projectId = computed(() => {
  const v = route.params.pid
  return typeof v === 'string' && v !== '' ? v : undefined
})

const defaultCaps: ModelCapabilities = {
  image: false,
  pdf: false,
  audio: false,
  reasoning: false,
  tools: false,
}

const currentModelCaps = computed<ModelCapabilities>(() => {
  const id = chat.currentConversation?.model_id
  if (!id) return defaultCaps
  const m = allModels.value.find((x) => x.id === id)
  return m?.capabilities ?? defaultCaps
})

onMounted(async () => {
  allModels.value = await modelsApi.list(true)
  await chat.loadConversations({ project_id: projectId.value })
  const cid = route.params.conversationId
  if (typeof cid === 'string' && cid !== '') {
    await open(cid)
  }
})

// 路由 conversationId 切换：在 /chat ↔ /chat/:id 同组件复用时同步状态。
watch(
  () => route.params.conversationId,
  async (cid) => {
    if (typeof cid === 'string' && cid !== '') {
      if (chat.currentConversation?.id !== cid) {
        await chat.openConversation(cid)
      }
    } else {
      chat.closeCurrent()
    }
  },
)

// 流式输出时 messages.length 不变，只 content 增长 —— 同时观察末消息内容长度。
let scrollPending = false
watch(
  () => [chat.messages.length, chat.messages.at(-1)?.content?.length ?? 0],
  () => {
    if (scrollPending) return
    scrollPending = true
    requestAnimationFrame(() => {
      scrollPending = false
      nextTick(() => {
        const el = scrollEl.value
        if (el) el.scrollTo(0, el.scrollHeight)
      })
    })
  },
)

async function open(id: string) {
  await chat.openConversation(id)
  await router.replace({
    path: projectId.value ? `/projects/${projectId.value}/chat/${id}` : `/chat/${id}`,
  })
}

async function send() {
  if (!draft.value.trim() || chat.isStreaming) return
  const body = draft.value
  const ids = [...attachmentIds.value]
  draft.value = ''
  uploaderRef.value?.clear()
  attachmentIds.value = []
  await chat.sendMessage(body, ids)
}

function onAttachmentChange(ids: string[]) {
  attachmentIds.value = ids
}

function onRetry(id: number) {
  chat.retryMessage(id)
}

async function onCreated(id: string) {
  await chat.loadConversations({ project_id: projectId.value })
  await open(id)
}

async function deleteCurrentConversation() {
  const id = chat.currentConversation?.id
  if (!id) return
  await chat.deleteConversation(id)
  await router.replace({
    path: projectId.value ? `/projects/${projectId.value}/chat` : `/chat`,
  })
}

function formatTime(iso: string) {
  return formatDateTime(iso)
}
</script>
