<route lang="json5" type="page">
{
  layout: 'default',
  style: {
    navigationBarHidden: true,
  },
}
</route>

<template>
  <div class="flex flex-col h-screen bg-gray-50">
    <!-- Navigation Bar -->
    <div class="flex-none flex items-center justify-between px-5 py-3 bg-white shadow-md h-10">
      <image src="/static/aichat/back.png" class="w-2 h-4" @click="goBack" />
      <div class="text-lg font-medium">小墨</div>
      <div class="flex items-center space-x-3">
        <image src="/static/aichat/time.png" class="w-5 h-5" @click="viewHistory" />
        <image src="/static/aichat/new.png" class="w-5 h-5" @click="newChat" />
      </div>
    </div>

    <!-- 消息区 -->
    <div :class="['flex relative', showActions ? 'h-105' : 'h-130']">
      <!-- 背景层 -->
      <div class="absolute inset-0 flex flex-col items-center justify-center pointer-events-none">
        <image src="/static/aichat/logo.png" class="w-20 h-24 mb-4" @click="newChat" />
        <view class="text-xl font-medium mb-1">嗨！ 我是小墨</view>
        <view class="text-gray-400">开启新的聊天吧</view>
      </div>
      z
      <div
        ref="scrollEl"
        class="flex-1 overflow-y-auto bg-gray-50"
        :class="showActions ? 'pb-44' : 'pb-16'"
      >
        <div :class="['relative z-10 px-4 py-6', showActions ? 'mb--11 h-105' : 'mb--21 h-135']">
          <template v-for="(msg, idx) in messages" :key="idx">
            <view v-if="shouldShowTimestamp(idx)" class="text-center text-xs text-gray-500 my-2">
              {{ formatDayGroup(msg.timestamp) }}
            </view>
            <view
              class="flex items-start"
              :class="msg.role === 'assistant' ? 'justify-start' : 'justify-end'"
            >
              <image
                v-if="msg.role === 'assistant'"
                :src="assistantAvatar"
                class="w-8 h-8 rounded-full mr-2 mt-1"
              />
              <view class="relative max-w-[70%] mt-4 mb-3">
                <view
                  :class="[
                    'absolute -top-4 text-xs text-gray-400',
                    msg.role === 'assistant' ? 'left-0' : 'right-0',
                  ]"
                >
                  {{ formatTimeShort(msg.timestamp) }}
                </view>
                <view
                  :class="[
                    'py-2 px-3 rounded-lg break-words mt-1',
                    msg.role === 'assistant'
                      ? 'bg-[#f9f8fd] text-black shadow'
                      : 'bg-[#45299e] text-white',
                  ]"
                >
                  {{ msg.content }}
                </view>
                <view
                  v-if="msg.role === 'assistant' && msg.type === 'text'"
                  class="absolute bottom-0 flex space-x-3"
                >
                  <image src="/static/aichat/copy.png" class="w-4 h-4" @click="copyText(msg)" />
                  <image
                    src="/static/aichat/resect.png"
                    class="w-4 h-4"
                    @click="refreshText(msg)"
                  />
                </view>
              </view>
              <image
                v-if="msg.role === 'user'"
                :src="userAvatar"
                class="w-8 h-8 rounded-full ml-2 mt-1"
              />
            </view>
          </template>
        </div>
      </div>
    </div>

    <!-- 底部上传预览 + 输入区 -->
    <div
      :class="[
        'fixed bottom-0 left-0 right-0 bg-white z-[80] overflow-hidden transition-all duration-300',
        showActions ? 'h-45' : 'h-20',
      ]"
    >
      <!-- 上传列表 -->
      <div v-if="uploadList.length" class="flex px-4 py-2 overflow-x-auto space-x-3 bg-white">
        <div
          v-for="item in uploadList"
          :key="item.id"
          class="relative w-16 h-16 rounded overflow-hidden"
        >
          <!-- 预览图，成功后用后端返回的 URL；上传中可以先用本地预览 -->
          <img
            :src="item.url || item.localPath"
            class="w-full h-full object-cover"
            @click="previewImage(item.url || item.localPath)"
          />

          <!-- 关闭按钮 -->
          <div
            class="absolute top-1 right-1 w-4 h-4 rounded-full bg-black bg-opacity-50 flex items-center justify-center cursor-pointer text-white text-xs"
            @click="removeImage(item.id)"
          >
            ×
          </div>

          <!-- 进度 / 成功 / 失败 -->
          <div
            class="absolute bottom-0 left-0 w-full text-xs text-center text-white py-1"
            :class="{
              'bg-black bg-opacity-50': item.status === 'uploading',
              'bg-green-600 bg-opacity-50': item.status === 'success',
              'bg-red-600 bg-opacity-50': item.status === 'fail',
            }"
          >
            <template v-if="item.status === 'uploading'">{{ item.progress }}%</template>
            <template v-else-if="item.status === 'success'">✔ 成功</template>
            <template v-else>
              ✖ 失败
              <span class="cursor-pointer" @click.stop="retry(item)">↻</span>
            </template>
          </div>
        </div>
      </div>

      <!-- 输入 + 切换 -->
      <view class="flex items-center px-4 py-2.5 border-t border-solid border-[#E7E7E7]">
        <input
          v-model="inputText"
          @keyup.enter="sendText"
          placeholder="想对我说点什么～"
          class="flex-1 h-10 px-3 border border-gray-100 bg-[#f9f9f9] rounded-full focus:outline-none"
        />
        <image src="/static/aichat/add-circle.png" class="w-7 h-7 mx-3" @click="toggleActions" />
        <image
          src="/static/aichat/enter.png"
          class="w-7 h-7"
          @click="sendText"
          :disabled="loading"
        />
      </view>

      <!-- 操作面板 -->
      <transition name="slide-up">
        <view
          v-show="showActions"
          class="flex justify-around items-center h-20 border-t border-solid border-[#E7E7E7] bg-white"
        >
          <view class="flex flex-col items-center">
            <image src="/static/aichat/phone-img.png" class="w-13 h-13" @click="onPickImage" />
            <span class="text-xs mt-1 text-gray-500">照片</span>
          </view>
          <view class="flex flex-col items-center">
            <image src="/static/aichat/photo.png" class="w-13 h-13" @click="onTakePhoto" />
            <span class="text-xs mt-1 text-gray-500">拍摄</span>
          </view>
          <view class="flex flex-col items-center">
            <image src="/static/aichat/files.png" class="w-13 h-13" @click="onPickFile" />
            <span class="text-xs mt-1 text-gray-500">文件</span>
          </view>
        </view>
      </transition>
    </div>
  </div>
  1
</template>

<script lang="ts" setup>
import { ref, reactive, nextTick } from 'vue'
import dayjs from 'dayjs'
import { useUserStore } from '@/store'
import { getEnvBaseUrl } from '@/utils'
import type { IGptRequestBody } from '@/service/index/foo'
interface IUpload {
  id: number
  url: string
  filePath: string
  status: 'uploading' | 'success' | 'fail'
  progress: number
  detail: string
  mask: string
}
interface IMessage {
  role: 'user' | 'assistant'
  type: 'text' | 'images'
  content: string | string[]
  timestamp: Date
}
interface UploadItem {
  id: string
  localPath: string // 本地临时路径，用于预览
  url: string // 后端返回的在线 URL
  status: 'uploading' | 'success' | 'fail'
  progress: number // 上传进度 %
}
const assistantAvatar =
  'https://dci-file-new.bj.bcebos.com/fonchain-main/test/runtime/image/avatar/40/b8ed6fea-6662-416d-8bb3-1fd8a8197061.jpg'
const userAvatar = assistantAvatar
const baseUrl = getEnvBaseUrl()
const token = useUserStore().userInfo.token || import.meta.env.VITE_DEV_TOKEN || ''
const messages = reactive<IMessage[]>([])
const inputText = ref('')
const loading = ref(false)
const showActions = ref(false)
const scrollEl = ref<HTMLElement>()
const uploadList = reactive<IUpload[]>([])
const uploadId = ref(0)

function scrollToBottom() {
  const el = scrollEl.value!
  nextTick(() => {
    el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' })
  })
}
function addMessage(msg: IMessage) {
  messages.push(msg)
  scrollToBottom()
}

const shouldShowTimestamp = (i: number) => {
  if (i === 0) return true
  return !dayjs(messages[i].timestamp).isSame(messages[i - 1].timestamp, 'day')
}
const formatDayGroup = (d: Date) => dayjs(d).format('YYYY/MM/DD HH:mm')
const formatTimeShort = (d: Date) => dayjs(d).format('MM/DD HH:mm')

function goBack() {
  window.history.back()
}
function viewHistory() {
  uni.navigateTo({ url: '/pages/history/history' })
}
function newChat() {
  messages.splice(0)
  inputText.value = ''
}

function toggleActions() {
  showActions.value = !showActions.value
  scrollToBottom()
}

// 相册
function onPickImage() {
  uni.chooseImage({
    count: 10,
    success: (res: any) => {
      res.tempFilePaths.forEach((path) => {
        uploadId.value += 1
        const id = uploadId.value
        const upload: IUpload = { id, url: path, filePath: path, status: 'uploading', progress: 0 }
        uploadList.push(upload)
        uploadFile(path, upload)
      })
    },
  })
}
// 拍照
function onTakePhoto() {
  uni.chooseImage({
    sourceType: ['camera'],
    count: 1,
    success: (res: any) => {
      const path = res.tempFilePaths[0]
      uploadId.value += 1
      const id = uploadId.value
      const upload: IUpload = { id, url: path, filePath: path, status: 'uploading', progress: 0 }
      uploadList.push(upload)
      uploadFile(path, upload)
    },
  })
}
// 文件
// 触发文件选择后，或拍照后，或其它入口都调用这个
function onPickFile(path: string, detail = '') {
  const id = Date.now().toString()
  const item: UploadItem = {
    id,
    localPath: path,
    url: '',
    status: 'uploading',
    progress: 0,
  }
  uploadList.push(item)
  startUpload(item, detail)
}

interface UploadFile {
  uid: string
  name: string
  size: number
  progress: number // 上传进度 0-100
  status: 'uploading' | 'success' | 'error'
  file: File
  url?: string // 本地预览URL或服务器返回的URL
}
const filesList = ref<UploadFile[]>([])
async function startUpload(item: UploadItem, detail: string) {
  const userStore = useUserStore()

  // 标记开始上传
  item.status = 'uploading'
  item.progress = 0

  // 发起上传 (不带 success/fail 回调)
  // @ts-ignore: uni.uploadFile 返回 UploadTask 兼具 Promise 接口
  const uploadTask: UniApp.UploadTask & Promise<UniApp.UploadFileRes> = uni.uploadFile({
    // url: 'http://114.218.158.24:9020/upload/multi',
    url: 'https://ukw0y1.laf.run/upload',
    filePath: item.localPath,
    name: 'k1', // 这里的 name 依然是 formData 里的字段名，后端会以此为 key
    formData: {
      source: 'chat',
      mask: '2076',
      detail,
      type: 'image',
    },
  })

  // 监听进度
  uploadTask.onProgressUpdate((res: { progress: number }) => {
    item.progress = res.progress
  })

  // 3s 超时自动 abort
  const timeoutId = setTimeout(() => {
    if (item.status === 'uploading') {
      uploadTask.abort()
      item.status = 'fail'
    }
  }, 3000)

  try {
    // 等待上传完成
    const res = await uploadTask
    clearTimeout(timeoutId)

    // 解析后端 JSON
    const resp = JSON.parse(res.data) as {
      code: number
      data: Record<string, string>
    }
    console.log(resp, 'resp')
    if (resp.code === 0 && resp.data) {
      // 遍历 data 对象，找第一个非空值
      const urls = Object.values(resp.data).filter((u) => !!u)
      if (urls.length > 0) {
        item.url = urls[0]
        item.status = 'success'
      } else {
        console.warn('没有拿到任何上传后的 URL')
        item.status = 'fail'
      }
    } else {
      console.warn('后端返回异常 code=', resp.code)
      item.status = 'fail'
    }
  } catch (err) {
    clearTimeout(timeoutId)
    console.error('uploadFile 出错：', err)
    item.status = 'fail'
  }
}
// 点击重试
function retry(item: UploadItem) {
  item.status = 'uploading'
  item.progress = 0
  startUpload(item, '') // 如果需要 detail，可缓存后传入
}
// 删除
function removeImage(id: string) {
  const idx = uploadList.findIndex((i) => i.id === id)
  if (idx >= 0) uploadList.splice(idx, 1)
}
// 预览（可自行实现 uni.previewImage 等）
function previewImage(url: string) {
  uni.previewImage({ urls: [url] })
}
// 上传使用 postUpload
async function uploadFile(path: string, upload: IUpload) {
  // 标记开始
  upload.status = 'uploading'

  try {
    // @ts-ignore uni.uploadFile 返回 Promise
    const res: UniApp.UploadFileRes = await uni.uploadFile({
      // url: 'http://114.218.158.24:9020/upload/multi',
      url: 'https://ukw0y1.laf.run/upload',
      filePath: path,
      name: 'file', // 这个 name 依然是 formData key，后端会把它用在 data.data 对象里
      formData: {
        source: 'chat',
        mask: '2076',
        type: 'image',
        k1: 'xxxx.png',
        k2: 'xxxx.png',
      },
    })

    // 解析后端 JSON
    const resp = JSON.parse(res.data) as {
      code: number
      data: Record<string, string>
    }

    if (resp.code === 0 && resp.data) {
      // 找到 data 对象里第一个有值的字段并赋给 upload.url
      let found = false
      for (const key in resp.data) {
        const url = resp.data[key]
        if (url) {
          upload.url = url
          found = true
          break
        }
      }

      if (found) {
        upload.status = 'success'
      } else {
        console.warn('没有取到任何上传后的 URL')
        upload.status = 'fail'
      }
    } else {
      console.warn('后端返回异常 code=', resp.code)
      upload.status = 'fail'
    }
  } catch (err) {
    console.error('uploadFile 出错：', err)
    upload.status = 'fail'
  }
}
async function sendText() {
  const text = inputText.value.trim()
  if (!text || loading.value) return

  addMessage({ role: 'user', type: 'text', content: text, timestamp: new Date() })
  inputText.value = ''
  loading.value = true

  const aiMsg: IMessage = {
    role: 'assistant',
    type: 'text',
    content: '',
    timestamp: new Date(),
  }
  addMessage(aiMsg)

  const body: IGptRequestBody = {
    model: 'gpt-4-vision-preview',
    max_tokens: 1000,
    temperature: 1,
    top_p: 1,
    presence_penalty: 0,
    frequency_penalty: 0,
    messages: [{ role: 'user', content: [{ type: 'text', text }] }],
    stream: true,
  }

  try {
    const resp = await fetch(baseUrl + '/chat/completion', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Authorization: token },
      body: JSON.stringify(body),
    })
    const reader = resp.body!.getReader()
    const decoder = new TextDecoder()
    let buffer = ''
    let done = false

    while (!done) {
      const { value, done: streamDone } = await reader.read()
      done = streamDone
      if (value) {
        buffer += decoder.decode(value, { stream: true })
        const parts = buffer.split('data: ')
        buffer = parts.pop()!
        for (const part of parts) {
          scrollToBottom()
          console.log('1')
          const chunk = part.trim()
          if (chunk === '[DONE]') {
            done = true
            break
          }
          try {
            const json = JSON.parse(chunk)
            const delta = json.choices?.[0]?.delta?.content
            if (delta) {
              aiMsg.content += delta
              scrollToBottom()
              console.log('2')
            }
          } catch {}
        }
      }
    }
    scrollToBottom()
  } catch (err) {
    console.error(err)
  } finally {
    loading.value = false
    showActions.value = false
  }
}

function copyText(msg: IMessage) {
  if (typeof msg.content === 'string') {
    navigator.clipboard.writeText(msg.content)
    alert('已复制')
  }
}
function refreshText(msg: IMessage) {
  if (typeof msg.content === 'string') {
    inputText.value = msg.content
    const idx = messages.indexOf(msg)
    messages.splice(idx, 1)
    sendText()
  }
}
</script>

<style lang="scss" scoped>
.slide-up-enter-active,
.slide-up-leave-active {
  transition: transform 0.3s ease-out;
}
.slide-up-enter-from,
.slide-up-leave-to {
  transform: translateY(100%);
}
.slide-up-enter-to,
.slide-up-leave-from {
  transform: translateY(0%);
}
.font-pf {
  font-family: PingFangSC-Medium, sans-serif;
}
</style>
