<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { appAiChatStreamGet, appMerchantCartAddCartPost } from '@/service'
import { useUserStore } from '@/store'
import GoCartPopup from './components/go-cart-popup.vue'
const { t } = useI18n()
const userStore = useUserStore()

function goPreference() {
  const current = getCurrentPages?.().slice(-1)[0]
  if (current?.route === 'pages/ai/diet-preference/index') return
  uni.navigateTo({
    url: '/pages/ai/diet-preference/index',
  })
}

type ChatMessage = { from: 'ai' | 'me'; text: string }
type RecommendItem = {
  id?: string
  name?: string
  img?: string
  price?: string | number
  specId?: string
  specPrice?: string | number
  specMemberPrice?: string | number
  specActualSalePrice?: string | number
  merchantId?: string | number
  storeId?: string | number
  dishId?: string | number
}

const PREF_DATA_KEY = 'ai_diet_pref_data'
const inputText = ref('')
const defaultAiMessage = computed(() => t('pages.ai.chat.defaultMessage'))
const messages = ref<ChatMessage[]>([
  { from: 'ai', text: defaultAiMessage.value },
])
const recommendItems = ref<RecommendItem[]>([])
const pendingRecommendItems = ref<RecommendItem[]>([])
const addedRecommendMap = ref<Record<string, boolean>>({})
const addingAll = ref(false)
const addingItemKey = ref('')
const streamTask = ref<UniApp.RequestTask | null>(null)
const isStreaming = ref(false)
const conversationId = ref('')
const toolStatus = ref('')
const thinkingStatus = ref('')
const textQueue = ref('')
const goCartPopupVisible = ref(false)
const goCartPopupContent = ref('')
let goCartPopupResolver: ((value: boolean) => void) | null = null
let typingRafId: ReturnType<typeof setTimeout> | 0 = 0
const TYPING_CHARS_PER_FRAME = 1

function scheduleFrame(cb: () => void): ReturnType<typeof setTimeout> {
  const raf = (globalThis as any)?.requestAnimationFrame
  if (typeof raf === 'function') {
    return raf(cb)
  }
  // App 端部分运行环境没有 requestAnimationFrame，降级为 16ms 定时器
  return setTimeout(cb, 16)
}

function cancelFrame(id: ReturnType<typeof setTimeout> | 0) {
  if (!id) return
  const caf = (globalThis as any)?.cancelAnimationFrame
  if (typeof caf === 'function') {
    caf(id)
    return
  }
  clearTimeout(id)
}

const preferenceSummary = computed(() => {
  const data = uni.getStorageSync(PREF_DATA_KEY) || {}
  if (!data || typeof data !== 'object') return ''
  const keys = Object.keys(data).slice(0, 3)
  if (!keys.length) return ''
  return keys
    .map((k) => {
      const v = data[k]
      if (Array.isArray(v)) return `${k}: ${v.join(',') || '-'}`
      return `${k}: ${String(v ?? '-')}`
    })
    .join(' | ')
})

function tryParseProductLine(line: string): RecommendItem | null {
  const trimmed = line.trim()
  if (!trimmed.startsWith('- {')) return null
  const jsonRaw = trimmed.replace(/^-+\s*/, '')
  try {
    const parsed = JSON.parse(jsonRaw)
    if (!parsed || typeof parsed !== 'object') return null
    return parsed as RecommendItem
  } catch (e) {
    return null
  }
}

function isSummaryLine(line: string): boolean {
  const text = line.trim()
  return /找到\s*\d+\s*件商品.*?预估\s*[0-9.]+.*?实际\s*[0-9.]+.*?优惠\s*[0-9.]+/.test(text)
}

function splitAiTextForRender(text: string): Array<{ text: string; strong: boolean }> {
  if (!text) return []
  const summaryReg = /(找到\s*\d+\s*件商品.*?预估\s*[0-9.]+.*?实际\s*[0-9.]+.*?优惠\s*[0-9.]+(?:元)?)/g
  const result: Array<{ text: string; strong: boolean }> = []
  let lastIndex = 0
  let match: RegExpExecArray | null = null
  while ((match = summaryReg.exec(text)) !== null) {
    if (match.index > lastIndex) {
      result.push({ text: text.slice(lastIndex, match.index), strong: false })
    }
    result.push({ text: match[0], strong: true })
    lastIndex = summaryReg.lastIndex
  }
  if (lastIndex < text.length) {
    result.push({ text: text.slice(lastIndex), strong: false })
  }
  return result
}

function resolveDishId(item: RecommendItem): string {
  const raw = item.id ?? item.dishId ?? item.specId
  if (raw == null) return ''
  return String(raw).trim()
}

function resolveMerchantId(item: RecommendItem): string {
  const raw = item.merchantId ?? item.storeId
  if (raw == null) return ''
  return String(raw).trim()
}

function getRecommendKey(item: RecommendItem): string {
  return `${resolveMerchantId(item)}__${resolveDishId(item)}`
}

function isRecommendAdded(item: RecommendItem): boolean {
  return !!addedRecommendMap.value[getRecommendKey(item)]
}

function canRecommendAdd(item: RecommendItem): boolean {
  return !!resolveDishId(item) && !!resolveMerchantId(item)
}

function hasInCart(item: RecommendItem): boolean {
  const merchantId = resolveMerchantId(item)
  const dishId = resolveDishId(item)
  if (!merchantId || !dishId) return false
  const merchants = userStore.userCartAllData as Array<any> | null
  if (!Array.isArray(merchants)) return false
  return merchants.some((merchant) => {
    const mId = String(merchant?.id ?? merchant?.merchantId ?? '')
    if (mId !== merchantId) return false
    const list = merchant?.merchantCartVoList
    if (!Array.isArray(list)) return false
    return list.some((cartItem: any) => String(cartItem?.dishId ?? '') === dishId)
  })
}

async function confirmDuplicateAdd(content: string): Promise<boolean> {
  return new Promise((resolve) => {
    uni.showModal({
      title: t('pages.ai.chat.duplicateTitle'),
      content,
      confirmText: t('common.confirm'),
      cancelText: t('common.cancel'),
      success: (res) => {
        resolve(!!res.confirm)
      },
      fail: () => resolve(false),
    })
  })
}

function handleGoCartConfirm() {
  goCartPopupVisible.value = false
  uni.navigateTo({
    url: '/pages-user/pages/cart/index',
  })
  if (goCartPopupResolver) {
    goCartPopupResolver(true)
    goCartPopupResolver = null
  }
}

function handleGoCartCancel() {
  goCartPopupVisible.value = false
  if (goCartPopupResolver) {
    goCartPopupResolver(false)
    goCartPopupResolver = null
  }
}

async function confirmGoCart(content?: string): Promise<boolean> {
  if (goCartPopupResolver) {
    goCartPopupResolver(false)
    goCartPopupResolver = null
  }
  goCartPopupContent.value = content || t('pages.ai.chat.goCartTip')
  goCartPopupVisible.value = true
  return new Promise((resolve) => {
    goCartPopupResolver = resolve
  })
}

function openDishDetail(item: RecommendItem) {
  const dishId = resolveDishId(item)
  const merchantId = resolveMerchantId(item)
  if (!dishId || !merchantId) {
    uni.showToast({ title: t('pages.ai.chat.missingDishNav'), icon: 'none' })
    return
  }
  uni.navigateTo({
    url: `/pages-store/pages/store/dishes?id=${dishId}&storeId=${merchantId}`,
  })
}

async function addRecommendToCart(
  item: RecommendItem,
  options?: { silent?: boolean; forceDuplicate?: boolean; suppressDuplicatePrompt?: boolean }
): Promise<boolean> {
  if (!userStore.checkLogin()) return false
  const key = getRecommendKey(item)
  if (isRecommendAdded(item) && !options?.forceDuplicate) return true
  if (addingItemKey.value === key) return false
  const dishId = resolveDishId(item)
  const merchantId = resolveMerchantId(item)
  if (!dishId || !merchantId) {
    if (!options?.silent) {
      uni.showToast({ title: t('pages.ai.chat.missingCartArgs'), icon: 'none' })
    }
    return false
  }
  if (!options?.forceDuplicate && hasInCart(item)) {
    if (options?.suppressDuplicatePrompt) return false
    const ok = await confirmDuplicateAdd(t('pages.ai.chat.duplicateSingleTip'))
    if (!ok) return false
  }
  try {
    addingItemKey.value = key
    await appMerchantCartAddCartPost({
      body: {
        merchantId,
        dishId,
        count: 1,
        merchantCartSideDishBoList: [],
      } as any,
    })
    addedRecommendMap.value[key] = true
    if (!options?.silent) {
      await confirmGoCart()
    }
    userStore.getUserCartAllData()
    return true
  } catch (e) {
    return false
  } finally {
    if (addingItemKey.value === key) addingItemKey.value = ''
  }
}

const allRecommendAdded = computed(() => {
  if (!recommendItems.value.length) return false
  return recommendItems.value.every((item) => isRecommendAdded(item))
})

const recommendCountText = computed(() => {
  const count = recommendItems.value.length
  return `${t('pages.ai.chat.recommendCountPrefix')}${count}${t('pages.ai.chat.recommendCountSuffix')}`
})

function duplicateBatchTipText(count: number) {
  return `${t('pages.ai.chat.duplicateBatchTipPrefix')}${count}${t('pages.ai.chat.duplicateBatchTipSuffix')}`
}

function addAllResultText(success: number, failed: number, skipped: number) {
  return `${t('pages.ai.chat.addAllResultPrefix')}${success}${t('pages.ai.chat.addAllResultMiddle1')}${failed}${t('pages.ai.chat.addAllResultMiddle2')}${skipped}${t('pages.ai.chat.addAllResultSuffix')}`
}

async function addAllRecommendToCart() {
  if (!recommendItems.value.length || addingAll.value) return
  if (!userStore.checkLogin()) return
  const duplicateCount = recommendItems.value.filter((item) => hasInCart(item)).length
  let allowDuplicate = false
  if (duplicateCount > 0) {
    allowDuplicate = await confirmDuplicateAdd(
      duplicateBatchTipText(duplicateCount)
    )
  }
  addingAll.value = true
  let success = 0
  let failed = 0
  let skipped = 0
  for (const item of recommendItems.value) {
    if (isRecommendAdded(item)) {
      skipped += 1
      continue
    }
    if (!canRecommendAdd(item)) {
      failed += 1
      continue
    }
    const ok = await addRecommendToCart(item, {
      silent: true,
      forceDuplicate: allowDuplicate,
      suppressDuplicatePrompt: true,
    })
    if (ok) success += 1
    else failed += 1
  }
  addingAll.value = false
  if (success > 0) {
    await confirmGoCart(
      `${addAllResultText(success, failed, skipped)}\n${t('pages.ai.chat.goCartTip')}`
    )
  } else if (failed > 0) {
    uni.showToast({ title: t('pages.ai.chat.addAllFailed'), icon: 'none' })
  }
}

function appendAiText(aiMessageIndex: number, text: string) {
  if (!text) return
  messages.value[aiMessageIndex].text += text
}

function flushTypingQueue(aiMessageIndex: number) {
  if (typingRafId) return
  const tick = () => {
    if (!textQueue.value) {
      typingRafId = 0
      return
    }
    const chunk = textQueue.value.slice(0, TYPING_CHARS_PER_FRAME)
    textQueue.value = textQueue.value.slice(TYPING_CHARS_PER_FRAME)
    appendAiText(aiMessageIndex, chunk)
    typingRafId = scheduleFrame(tick)
  }
  typingRafId = scheduleFrame(tick)
}

function handleSend() {
  const text = inputText.value.trim()
  if (!text || isStreaming.value) return
  messages.value.push({ from: 'me', text })
  messages.value.push({ from: 'ai', text: '' })
  const aiMessageIndex = messages.value.length - 1
  inputText.value = ''
  isStreaming.value = true
  toolStatus.value = ''
  thinkingStatus.value = t('pages.ai.chat.thinking')
  recommendItems.value = []
  pendingRecommendItems.value = []
  addedRecommendMap.value = {}
  textQueue.value = ''
  if (typingRafId) {
    cancelFrame(typingRafId)
    typingRafId = 0
  }

  streamTask.value = appAiChatStreamGet({
    query: {
      content: text,
      conversationId: conversationId.value || undefined,
    },
    callbacks: {
      onStart(payload) {
        if (payload?.conversationId) {
          conversationId.value = String(payload.conversationId)
        }
      },
      onTool(payload) {
        const tool = String(payload?.tool || '')
        const size = payload?.size != null ? String(payload.size) : ''
        toolStatus.value = tool
          ? t('pages.ai.chat.toolStatusWithSize', { tool, size: size || '-' })
          : t('pages.ai.chat.searchingGoods')
        thinkingStatus.value = t('pages.ai.chat.thinkingSearching')
      },
      onChunk(payload) {
        if (payload?.conversationId) {
          conversationId.value = String(payload.conversationId)
        }
        const chunkText = String(payload?.text || '')
        if (!chunkText) return

        let plainText = ''
        chunkText.split('\n').forEach((line) => {
          if (!line.trim()) return
          if (isSummaryLine(line)) {
            plainText += (plainText ? '\n' : '') + line
            return
          }
          const lineProduct = tryParseProductLine(line)
          if (lineProduct) {
            pendingRecommendItems.value.push(lineProduct)
            return
          }
          plainText += (plainText ? '\n' : '') + line
        })
        if (plainText) textQueue.value += plainText
        flushTypingQueue(aiMessageIndex)
      },
      onEnd() {
        toolStatus.value = ''
        thinkingStatus.value = ''
        const finalize = () => {
          if (pendingRecommendItems.value.length > 0) {
            recommendItems.value = [...pendingRecommendItems.value]
            pendingRecommendItems.value = []
            addedRecommendMap.value = {}
          }
          if (!messages.value[aiMessageIndex].text.trim() && recommendItems.value.length === 0) {
            messages.value[aiMessageIndex].text = t('pages.ai.chat.emptyResult')
          }
          isStreaming.value = false
          streamTask.value = null
        }
        if (textQueue.value) {
          const waitDone = () => {
            if (textQueue.value) {
              scheduleFrame(waitDone)
              return
            }
            finalize()
          }
          scheduleFrame(waitDone)
        } else {
          finalize()
        }
      },
      onError() {
        toolStatus.value = ''
        thinkingStatus.value = ''
        textQueue.value = ''
        pendingRecommendItems.value = []
        if (typingRafId) {
          cancelFrame(typingRafId)
          typingRafId = 0
        }
        if (!messages.value[aiMessageIndex].text.trim()) {
          messages.value[aiMessageIndex].text = t('pages.ai.chat.requestFailed')
        }
        isStreaming.value = false
        streamTask.value = null
      },
      // fallback: non-chunk response still rendered
      onToken(token) {
        textQueue.value += token
        flushTypingQueue(aiMessageIndex)
      },
    },
  })
}

onUnmounted(() => {
  if (goCartPopupResolver) {
    goCartPopupResolver(false)
    goCartPopupResolver = null
  }
  if (streamTask.value && typeof streamTask.value.abort === 'function') {
    streamTask.value.abort()
  }
  textQueue.value = ''
  pendingRecommendItems.value = []
  if (typingRafId) {
    cancelFrame(typingRafId)
    typingRafId = 0
  }
})
</script>

<template>
  <view class="page">
    <navbar :title="t('navbar-ai-chat')">
      <template #right>
        <!-- <view class="nav-action" @click="goPreference">
          {{ t('common.go-to-settings') }}
        </view> -->
        <image src="/static/app/images/gengduo.png" class="w-44rpx h-44rpx ml-8rpx shrink-0"  @click="goPreference"></image>
      </template>
    </navbar>
    <!-- <view class="header">
      <text class="title">{{ t('pages.ai.chat.title') }}</text>
      <text v-if="preferenceSummary" class="pref">{{ preferenceSummary }}</text>
    </view> -->

    <view class="chat">
      <view v-for="(m, idx) in messages" :key="idx" class="bubble-row" :class="m.from === 'me' ? 'row-me' : 'row-ai'">
        <view class="bubble" :class="m.from === 'me' ? 'bubble-me' : 'bubble-ai'">
          <text v-if="m.from === 'me'" class="bubble-text">{{ m.text }}</text>
          <text v-else class="bubble-text">
            <text
              v-for="(seg, sIdx) in splitAiTextForRender(m.text)"
              :key="`${idx}-${sIdx}`"
              :class="seg.strong ? 'bubble-strong' : ''"
            >{{ seg.text }}</text>
          </text>
        </view>
      </view>

      <view v-if="thinkingStatus" class="thinking-status">
        <text class="thinking-dot"></text>
        <text class="thinking-text">{{ thinkingStatus }}</text>
      </view>
      <view v-if="toolStatus" class="tool-status">{{ toolStatus }}</view>

      <view v-if="recommendItems.length > 0" class="product-card">
        <view class="product-card__top">
          <text class="price">{{ recommendCountText }}</text>
          <text class="label">{{ t('pages.ai.chat.recommendSource') }}</text>
        </view>
        <view class="product-list">
          <view class="product-item" v-for="(item, i) in recommendItems" :key="item.specId || item.id || i" @click="openDishDetail(item)">
            <image class="thumb" mode="aspectFill" :src="item.img || ''" />
            <view class="meta">
              <text class="name">{{ item.name || t('pages.ai.chat.unnamed') }}</text>
              <text class="money">$ {{ item.specActualSalePrice || item.price || '-' }}</text>
            </view>
            <button
              class="add-one-btn"
              :class="{ 'add-one-btn--added': isRecommendAdded(item) }"
              :disabled="isRecommendAdded(item)"
              @click.stop="addRecommendToCart(item)"
            >
              {{ isRecommendAdded(item) ? t('pages.ai.chat.added') : t('pages.ai.chat.quickAdd') }}
            </button>
          </view>
        </view>
        <button class="btn" :disabled="addingAll || allRecommendAdded" @click="addAllRecommendToCart">
          {{ addingAll ? t('pages.ai.chat.addingAll') : (allRecommendAdded ? t('pages.ai.chat.allAdded') : t('pages.ai.chat.addAll')) }}
        </button>
      </view>
    </view>

    <view class="composer">
      <input
        v-model="inputText"
        class="input"
        :placeholder="t('pages.ai.chat.inputPlaceholder')"
      />
      <button class="send" :disabled="isStreaming" @click="handleSend">
        {{ isStreaming ? t('pages.ai.chat.generating') : t('common.send') }}
      </button>
    </view>
    <go-cart-popup
      v-model="goCartPopupVisible"
      :content="goCartPopupContent"
      :title="t('pages.ai.chat.goCartTitle')"
      :cancel-text="t('pages.ai.chat.goCartLater')"
      :confirm-text="t('pages.ai.chat.goCartNow')"
      @confirm="handleGoCartConfirm"
      @cancel="handleGoCartCancel"
    />
  </view>
</template>

<style scoped lang="scss">
.page {
  background: #f2f2f2;
  min-height: 100vh;
  // padding: 24rpx;
  padding-bottom: calc(180rpx + env(safe-area-inset-bottom, 0px));
}

.header {
  margin-bottom: 18rpx;
}

.title {
  font-size: 36rpx;
  font-weight: 700;
  color: #111;
  display: block;
}

.pref {
  margin-top: 10rpx;
  font-size: 24rpx;
  color: #888;
  display: block;
}

.chat {
  // background: #fff;
  border-radius: 20rpx;
  padding:24rpx;
  // padding: 18rpx;
  // box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}

.bubble-row {
  display: flex;
  margin-bottom: 16rpx;
}

.row-ai {
  justify-content: flex-start;
}

.row-me {
  justify-content: flex-end;
}

.bubble {
  max-width: 78%;
  padding: 14rpx 18rpx;
  border-radius: 16rpx;
}

.bubble-ai {
  background: #ffffff;
  color: #14181b;
  border-radius: 0 16rpx 16rpx 16rpx;
}

.bubble-me {
  background: #14181b;
  color: #ffffff;
  border-radius: 16rpx 0 16rpx 16rpx;
}

.bubble-text {
  font-size: 26rpx;
  line-height: 1.4;
  white-space: pre-wrap;
}

.bubble-strong {
  font-weight: 700;
  color: #b03d15;
}

.product-card {
  margin-top: 10rpx;
  border-radius: 16rpx;
  border: 1rpx solid #f1f1f1;
  padding: 16rpx;
  background: #fafafa;
}

.product-card__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 12rpx;
}

.price {
  color: #e23636;
  font-size: 32rpx;
  font-weight: 700;
}

.label {
  color: #14181b;
  font-size: 24rpx;
  background: #e9eef6;
  padding: 6rpx 14rpx;
  border-radius: 999rpx;
}

.product-list {
  display: flex;
  flex-direction: column;
  gap: 12rpx;
}

.product-item {
  display: flex;
  gap: 12rpx;
  align-items: center;
  justify-content: space-between;
}

.thumb {
  width: 88rpx;
  height: 72rpx;
  border-radius: 12rpx;
  background: #f0f0f0;
}

.meta {
  display: flex;
  flex-direction: column;
  gap: 6rpx;
  flex: 1;
  min-width: 0;
}

.name {
  font-size: 24rpx;
  color: #111;
  line-height: 1.35;
}

.money {
  font-size: 24rpx;
  color: #e23636;
  font-weight: 600;
}

.add-one-btn {
  width: 128rpx;
  height: 56rpx;
  line-height: 56rpx;
  border-radius: 999rpx;
  border: 1rpx solid #14181b;
  background: #fff;
  color: #14181b;
  font-size: 22rpx;
  padding: 0;
}

.add-one-btn--added {
  border-color: #9aa5b1;
  color: #9aa5b1;
  background: #f6f8fa;
}

.btn {
  margin-top: 14rpx;
  border-radius: 16rpx;
  background: #14181b;
  color: #fff;
  font-size: 26rpx;
}

.composer {
  position: fixed;
  // left: 24rpx;
  // right: 24rpx;
  bottom: 0;
  background: #fff;
  // border-radius: 20rpx;
  width: 100%;
  padding: 24rpx 24rpx calc(52rpx + env(safe-area-inset-bottom, 0px));
  display: flex;
  gap: 12rpx;
  align-items: center;
  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.06);
}

.input {
  flex: 1;
  font-size: 26rpx;
  background: #f7f7f7;
  padding: 12rpx 14rpx;
  border-radius: 14rpx;
}

.send {
  width: 160rpx;
  border-radius: 16rpx;
  background: #14181b;
  color: #fff;
  font-size: 26rpx;
  padding: 12rpx 0;
}

.tool-status {
  margin-top: 8rpx;
  margin-bottom: 8rpx;
  font-size: 22rpx;
  color: #4f667e;
  background: #eef3fb;
  border-radius: 999rpx;
  padding: 8rpx 14rpx;
  display: inline-flex;
}

.thinking-status {
  margin-top: 8rpx;
  margin-bottom: 8rpx;
  display: flex;
  align-items: center;
  gap: 10rpx;
  color: #5a6d85;
  font-size: 24rpx;
}

.thinking-dot {
  width: 14rpx;
  height: 14rpx;
  border-radius: 999rpx;
  background: #5a6d85;
  opacity: 0.75;
  animation: ai-thinking-pulse 1s ease-in-out infinite;
}

.thinking-text {
  color: #5a6d85;
}

.nav-action {
  font-size: 24rpx;
  color: #14181b;
  padding: 8rpx 0 8rpx 16rpx;
}

@keyframes ai-thinking-pulse {
  0% { transform: scale(0.85); opacity: 0.45; }
  50% { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(0.85); opacity: 0.45; }
}
</style>

