<script setup >
import {uploadFormData, uploadImg} from "@/api/api";
import dayjs from "dayjs";
import { computed, onMounted, onUnmounted, ref,watch } from 'vue'
import { useRoute } from 'vue-router'
import { NAutoComplete, NButton, NInput, useDialog, useMessage } from 'naive-ui'
import { AreaChartOutlined ,PlusOutlined } from '@ant-design/icons-vue';
import html2canvas from 'html2canvas'
import { Message } from './components'
import { useScroll } from './hooks/useScroll'
import { useChat } from './hooks/useChat'
import { useUsingContext } from './hooks/useUsingContext'
import HeaderComponent from './components/Header/index.vue'
import { HoverButton, SvgIcon } from '@/components/common'
import { useBasicLayout } from '@/hooks/useBasicLayout'
import { useChatStore, usePromptStore } from '@/store'
import { fetchChatAPIProcess } from '@/api'
import { t } from '@/locales'
import { UploadOutlined } from '@ant-design/icons-vue';
import {storeToRefs} from 'pinia'
import { sessionDetailForSetup } from '@/store'
const sessionDetailData=sessionDetailForSetup()
let controller = new AbortController()
const { sessionDetail:dataSources ,currentListUuid,gptMode,isStop} = storeToRefs(sessionDetailData)

const dialog = useDialog()
const ms = useMessage()
const chatStore = useChatStore()
const { isMobile } = useBasicLayout()
const { addChat, updateChat, updateChatSome, getChatByUuidAndIndex } = useChat()
const { scrollRef, scrollToBottom, scrollToBottomIfAtBottom } = useScroll()
const { usingContext, toggleUsingContext } = useUsingContext()
const prompt = ref('')
const loading = ref(false)
const inputRef = ref(null)

// 添加PromptStore
const promptStore = usePromptStore()

// 使用storeToRefs，保证store修改后，联想部分能够重新渲染
const { promptList: promptTemplate } = storeToRefs(promptStore)

// 未知原因刷新页面，loading 状态不会重置，手动重置
dataSources.value.forEach((item, index) => {
  if (item.loading)
    updateChatSome(+uuid, index, { loading: false })
})

function handleSubmit() {
  dataSources.value.push({
    dateTime: dayjs().format('YYYY/MM/DD HH:mm:ss'),
    text: prompt.value,
    inversion: true,
    error: false,
    fileList:fileList.value.map(x=>x.url),
    loading:false
  })
  sendDataStream()
}
const API_URL = `${import.meta.env.VITE_APP_API_BASE_URL}/chat/completion`;

const createParams = () => {
  const messages = dataSources.value.map((x) => {
    return {
      content:(()=>{
        if (gptMode.value==='gpt-4-vision-preview'){
          return [{type: "text", text: x.text},...x.fileList.map((y)=>{
            return {type: "image_url", image_url:y}
          })]
        }else {
          return x.text
        }
      })(),
      role: x.inversion ? 'user' : 'assistant'
    }
  });
  return {
    type:isFile.value?'custom':'',
    listUuid: currentListUuid.value,
    messages,
    frequency_penalty: 0,
    max_tokens: 1000,
    model: gptMode.value,
    presence_penalty: 0,
    stream: true,
    temperature: 1,
    top_p: 1
  };
};
const handleResponseStream = async (reader) => {
  const { done, value } = await reader.read();
  if (!done) {
    let decoded = new TextDecoder().decode(value);
    let decodedArray = decoded.split("data: ");
    decodedArray.forEach((decoded) => {
      if (decoded !== "") {
        if (decoded.trim() === "[DONE]") {
          dataSources.value[dataSources.value.length - 1].loading=false
          loading.value=false
          return;
        } else {
          if (isStop.value){
            loading.value=false
            return;
          }
          loading.value=true
          dataSources.value[dataSources.value.length - 1].loading=true
          const response = JSON.parse(decoded).choices[0].delta.content
              ? JSON.parse(decoded).choices[0].delta.content
              : "";
          dataSources.value[dataSources.value.length - 1].text += response;
        }
      }
    });

    await handleResponseStream(reader);
  }
};
const sendDataStream = async () => {
    const params = createParams();
  fileList.value=[]
  prompt.value=''
  isStop.value=false
  dataSources.value.push({
    dateTime: dayjs().format('YYYY/MM/DD HH:mm:ss'),
    text: '思考中..',
    inversion: false,
    error: false,
    loading:true
  });
  visible.value=false
  try {
    loading.value=true
    const response = await fetch(API_URL, {
      method: "POST",
      timeout: 10000,
      body: JSON.stringify(params),
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
        Authorization: localStorage.getItem('token'),
      },
    });

    const contentType = response.headers.get('Content-Type');
    if (!contentType || !contentType.includes('application/json')) {
      const reader = response.body.getReader();
      dataSources.value[dataSources.value.length-1].text=''
      await handleResponseStream(reader);
    }
  } catch (error) {
    console.error('发生错误:', error);
  }
};
function handleExport() {
  if (loading.value)
    return

  const d = dialog.warning({
    title: t('chat.exportImage'),
    content: t('chat.exportImageConfirm'),
    positiveText: t('common.yes'),
    negativeText: t('common.no'),
    onPositiveClick: async () => {
      try {
        d.loading = true
        const ele = document.getElementById('image-wrapper')
        const canvas = await html2canvas(ele, {
          useCORS: true,
        })
        const imgUrl = canvas.toDataURL('image/png')
        const tempLink = document.createElement('a')
        tempLink.style.display = 'none'
        tempLink.href = imgUrl
        tempLink.setAttribute('download', 'chat-shot.png')
        if (typeof tempLink.download === 'undefined')
          tempLink.setAttribute('target', '_blank')

        document.body.appendChild(tempLink)
        tempLink.click()
        document.body.removeChild(tempLink)
        window.URL.revokeObjectURL(imgUrl)
        d.loading = false
        ms.success(t('chat.exportSuccess'))
        Promise.resolve()
      }
      catch (error) {
        ms.error(t('chat.exportFailed'))
      }
      finally {
        d.loading = false
      }
    },
  })
}

function handleDelete(index) {
  if (loading.value)
    return

  dialog.warning({
    title: t('chat.deleteMessage'),
    content: t('chat.deleteMessageConfirm'),
    positiveText: t('common.yes'),
    negativeText: t('common.no'),
    onPositiveClick: () => {
      chatStore.deleteChatByUuid(+uuid, index)
    },
  })
}

function handleClear() {
  if (loading.value)
    return

  dialog.warning({
    title: t('chat.clearChat'),
    content: t('chat.clearChatConfirm'),
    positiveText: t('common.yes'),
    negativeText: t('common.no'),
    onPositiveClick: () => {
      chatStore.clearChatByUuid(+uuid)
    },
  })
}

function handleEnter(event) {
  if (!isMobile.value) {
    if (event.key === 'Enter' && !event.shiftKey) {
      event.preventDefault()
      handleSubmit()
    }
  }
  else {
    if (event.key === 'Enter' && event.ctrlKey) {
      event.preventDefault()
      handleSubmit()
    }
  }
}
function handleStop() {
  if (loading.value) {
    loading.value = false
    isStop.value=true
  }
}

// 可优化部分
// 搜索选项计算，这里使用value作为索引项，所以当出现重复value时渲染异常(多项同时出现选中效果)
// 理想状态下其实应该是key作为索引项,但官方的renderOption会出现问题，所以就需要value反renderLabel实现
const searchOptions = computed(() => {
  if (prompt.value.startsWith('/')) {
    return promptTemplate.value.filter((item) => item.key.toLowerCase().includes(prompt.value.substring(1).toLowerCase())).map((obj) => {
      return {
        label: obj.value,
        value: obj.value,
      }
    })
  }
  else {
    return []
  }
})

// value反渲染key
const renderOption = (option) => {
  for (const i of promptTemplate.value) {
    if (i.value === option.label)
      return [i.key]
  }
  return []
}

const placeholder = computed(() => {
  if (isMobile.value)
    return t('chat.placeholderMobile')
  return t('chat.placeholder')
})

const buttonDisabled = computed(() => {
  return loading.value || !prompt.value || prompt.value.trim() === ''
})

const footerClass = computed(() => {
  let classes = ['p-4']
  if (isMobile.value)
    classes = ['sticky', 'left-0', 'bottom-0', 'right-0', 'p-2', 'pr-3', 'overflow-hidden']
  return classes
})

onMounted(() => {

  scrollToBottom()
  if (inputRef.value && !isMobile.value)
    inputRef.value?.focus()
})
const fileList = ref([
]);
onUnmounted(() => {
  if (loading.value)
    controller.abort()
})
function getBase64(file) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onload = () => resolve(reader.result);
    reader.onerror = error => reject(error);
  });
}
const previewVisible = ref(false);
const previewImage = ref('');
const previewTitle = ref('');
const handleCancel = () => {
  previewVisible.value = false;
  previewTitle.value = '';
};
const handlePreview = async (file) => {
  if (!file.url && !file.preview) {
    file.preview = (await getBase64(file.originFileObj))
  }
  previewImage.value = file.url || file.preview;
  previewVisible.value = true;
  previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1);
};
const value = ref('gpt-3.5-turbo');
const visible=ref(false)
const removeImg=(data)=>{
  fileList.value.splice(fileList.value.findIndex(x=>x.url===data.url),1)
}
watch(gptMode,()=>{
  currentListUuid.value=''
  dataSources.value=[]
})
const visible1=ref(false)
const fileList1=ref([])
const isFile=ref(false)
const upItemImage1= async (file)=>{
  const data = {
    file: file.file,
  }
const res=await  uploadFormData(data)
if (res.code===0){
  file.onSuccess()
  dataSources.value=[...dataSources.value,...res.data.paragraph.flatMap(n=>[n,'']).map((x)=>{
    return {
      dateTime: dayjs().format('YYYY/MM/DD HH:mm:ss'),
      text: x,
      inversion: Boolean(x),
      error: false,
    }
  })]
  isFile.value=true
  fileList1.value=[]
  visible1.value=false
  sendDataStream()
}
}

const customRequest=async (file)=>{
 const res=await uploadImg({file:file.file,source:'approval'})
  if (res.code===0){
    file.onSuccess()
    fileList.value.push({
        url:res.data.ori_url
    })
  }
}
</script>
<template>
  <div class="flex flex-col w-full h-full">
    <HeaderComponent
      v-if="isMobile"
      :using-context="usingContext"
      @export="handleExport"
      @handle-clear="handleClear"
    />
    <main class="flex-1 overflow-hidden">
      <div id="scrollRef"   ref="scrollRef" class="h-full overflow-hidden overflow-y-auto" >
        <div
          id="image-wrapper"
          class="w-full max-w-screen-xl m-auto dark:bg-[#101014]"

          :class="[isMobile ? 'p-2' : 'p-4']"
        >
          <template v-if="!dataSources.length">
            <div class="flex items-center justify-center mt-4 text-center text-neutral-300">
              <SvgIcon icon="ri:bubble-chart-fill" class="mr-2 text-3xl" />
              <span>当前没有会话哦</span>
            </div>
          </template>
          <template v-else>
            <div>
              <Message
                v-for="(item, index) of dataSources.filter(x=>x.text||x.fileList?.length>0)"
                :key="index"
                :date-time="item.dateTime"
                :text="item.text"
                :fileList="item.fileList"
                :inversion="item.inversion"
                :error="item.error"
                :loading="item.loading"
                @delete="handleDelete(index)"
              />
              <div class="sticky bottom-0 left-0 flex justify-center">
                <NButton v-if="loading" type="warning" @click="handleStop">
                  <template #icon>
                    <SvgIcon icon="ri:stop-circle-line" />
                  </template>
                  停止响应
                </NButton>
              </div>
            </div>
          </template>
        </div>
      </div>
    </main>
    <footer :class="footerClass">
      <div class="w-full max-w-screen-xl m-auto">
        <div class="flex items-center justify-between space-x-2" style="flex-wrap: initial">
          <a-popover  :open="visible1" trigger="click">
            <template #content>
              <div class="clearfix">
                <a-upload
                    :max-count="1"
                    v-model:file-list="fileList1"
                    name="file"
                    :customRequest="upItemImage1"
                >
                  <a-button>
                    <upload-outlined></upload-outlined>
                    上传文件
                  </a-button>
                </a-upload>
              </div>
            </template>
            <HoverButton @click="visible1=!visible1">
            <span  class="text-xl text-[#4f555e] dark:text-white" style="display: flex;justify-content: center;align-items: center">
            <SvgIcon icon="ri:upload-2-line" />
            </span>
            </HoverButton>
          </a-popover>
          <a-popover v-if="gptMode==='gpt-4-vision-preview'" :open="visible" trigger="click">
            <template #content>
              <div class="clearfix">
                <a-upload
                    :file-list="fileList"
                    :customRequest="customRequest"
                    list-type="picture-card"
                    @preview="handlePreview"
                    @remove="removeImg"
                >
                  <div>
                    <plus-outlined />
                    <div style="margin-top: 8px">上传</div>
                  </div>
                </a-upload>
                <a-modal :open="previewVisible" :title="previewTitle" :footer="null" @cancel="handleCancel">
                  <img alt="example" style="width: 100%" :src="previewImage" />
                </a-modal>
              </div>
            </template>
            <HoverButton @click="visible=!visible">
            <span  class="text-xl text-[#4f555e] dark:text-white" style="display: flex;justify-content: center;align-items: center">
             <AreaChartOutlined />
            </span>
            </HoverButton>
          </a-popover>
<!--          <HoverButton v-if="!isMobile" @click="handleClear">
            <span class="text-xl text-[#4f555e] dark:text-white">
              <SvgIcon icon="ri:delete-bin-line" />
            </span>
          </HoverButton>-->
          <HoverButton v-if="!isMobile" @click="handleExport">
            <span class="text-xl text-[#4f555e] dark:text-white">
              <SvgIcon icon="ri:download-2-line" />
            </span>
          </HoverButton>
<!--          <HoverButton @click="toggleUsingContext">
            <span class="text-xl" :class="{ 'text-[#4b9e5f]': usingContext, 'text-[#a8071a]': !usingContext }">
              <SvgIcon icon="ri:chat-history-line" />
            </span>
          </HoverButton>-->
          <NAutoComplete v-model:value="prompt" :options="searchOptions" :render-label="renderOption">
            <template #default="{ handleInput, handleBlur, handleFocus }">
              <NInput
                ref="inputRef"
                v-model:value="prompt"
                type="textarea"
                :placeholder="placeholder"
                :autosize="{ minRows: 1, maxRows: isMobile ? 4 : 8 }"
                @input="handleInput"
                @focus="handleFocus"
                @blur="handleBlur"
                @keypress="handleEnter"
              />
            </template>
          </NAutoComplete>
          <NButton color="#8a2be2" type="primary"  :disabled="buttonDisabled" @click="handleSubmit">
            <template #icon>
              <span class="dark:text-black">
                <SvgIcon icon="ri:send-plane-fill" />
              </span>
            </template>
          </NButton>
        </div>
      </div>
    </footer>
  </div>
</template>
