<template>
  <view class="qa-page">
    <!-- 顶部导航（参考示例：左侧菜单，标题，新对话，右侧加号） -->
    <view class="qa-header" :style="{ paddingTop: statusBarHeight + 'px' }">
      <view class="qa-header-bar">
        <view class="qa-header-left" @click="openMainList">
          <view class="qa-header-menu-line"></view>
          <view class="qa-header-menu-line qa-header-menu-line-short"></view>
        </view>
        <text class="qa-header-title">新对话</text>
        <view class="qa-header-right" @click="startNewSession">
          <text class="qa-header-plus">＋</text>
        </view>
      </view>
    </view>

    <!-- AiQaMain 列表面板 -->
    <view v-if="showMainList" class="main-list-mask" @click="closeMainList"></view>
    <view v-if="showMainList" class="main-list-drawer">
      <view class="main-list-drawer-header">
        <text class="main-list-drawer-title">对话列表</text>
        <view class="main-list-drawer-close" @click.stop="closeMainList">
          <text>关闭</text>
        </view>
      </view>

      <scroll-view class="main-list-scroll" scroll-y="true">
        <view v-if="mainListLoading" class="main-list-loading">加载中...</view>

        <view v-else>
          <view
            v-for="g in groupedAiQaMainList"
            :key="g.key"
            class="main-list-group"
          >
            <view class="main-list-group-title">{{ g.label }}</view>

            <view
              v-for="item in g.items"
              :key="getMainItemKey(item)"
              class="main-list-item"
              @click.stop="selectMainItem(item)"
            >
              <text class="main-list-item-text">{{ getMainItemTitle(item) }}</text>
            </view>
          </view>

          <view v-if="!aiQaMainList || aiQaMainList.length === 0" class="main-list-empty">
            暂无数据
          </view>
        </view>
      </scroll-view>
    </view>

    <!-- 中间主体区域：图标 + 提示文案 -->
    <view class="qa-main" v-if="chatMessages.length === 0">
      <view class="qa-main-icon-wrapper">
        <text class="qa-main-icon">Q</text>
      </view>
      <text class="qa-main-text" selectable="true">今天有什么可以帮到你？</text>
    </view>

    <!-- 对话内容 -->
    <scroll-view v-else class="qa-chat" scroll-y="true">
      <view class="qa-chat-inner">
        <view
          v-for="(m, i) in chatMessages"
          :key="i"
          class="qa-msg"
          :class="m.role === 'user' ? 'qa-msg--user' : 'qa-msg--ai'"
        >
          <view class="qa-msg-actions" :class="m.role === 'ai' ? 'qa-msg-actions--left' : 'qa-msg-actions--right'">
            <view class="qa-copy-btn" @click.stop="copyText(m.text)">
              <text class="qa-copy-btn-icon">⧉</text>
            </view>
          </view>
          <!-- 用户消息仍按纯文本展示 -->
          <text
            v-if="m.role === 'user'"
            class="qa-msg-text"
            selectable="true"
          >{{ m.text }}</text>
          <!-- AI 消息支持 Markdown 渲染，保留纯文本兜底 -->
          <view
            v-else
            class="qa-msg-text qa-msg-text-markdown"
          >
            <rich-text
              :nodes="renderMarkdownNodes(m.text)"
            ></rich-text>
          </view>
        </view>
      </view>
    </scroll-view>

    <!-- 底部输入区域 -->
    <view class="qa-input-wrapper" :style="{ paddingBottom: safeBottom + 'px' }">
      <view class="qa-input-row">
        <view class="qa-input-box">
          <textarea
            class="qa-textarea"
            v-model="message"
            placeholder="发送消息或按住说话"
            placeholder-class="qa-input-placeholder"
            :auto-height="true"
            :maxlength="-1"
          ></textarea>
        </view>
        <view class="qa-input-send" @click="onSend">
          <uni-icons type="top" size="18" color="#fff"></uni-icons>
        </view>
      </view>
      <view class="qa-input-actions">
        <view class="qa-chip qa-chip-stream" :class="useStreamOutput ? 'qa-chip-stream--active' : ''" @click="toggleStreamOutput">
          <text class="qa-chip-stream-icon">∿</text>
          <text class="qa-chip-stream-text">流式</text>
        </view>
        <view class="qa-chip" @click="goBack">
          <uni-icons type="left" size="18" color="#666"></uni-icons>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
import { post, get } from '@/common/request.js'
import { getApiUrl } from '@/common/config.js'
import { store } from '@/common/store.js'

function firstNonEmptyTrimmed(...parts) {
  for (const p of parts) {
    if (p === undefined || p === null) continue;
    const s = String(p).trim();
    if (s) return s;
  }
  return '';
}

function getSubmitUserContext() {
  try {
    const login = uni.getStorageSync('backend-login-response') || {};
    const ui = store.userInfo || {};
    const loginAccount = firstNonEmptyTrimmed(login.userName, ui.username);
    const userName = firstNonEmptyTrimmed(
      login.realName,
      login.name,
      login.nickName,
      ui.nickname,
      login.email,
      ui.email
    );
    return { loginAccount, userName };
  } catch (e) {
    return { loginAccount: '', userName: '' };
  }
}

function getSystemInfo() {
  try {
    const info = uni.getSystemInfoSync();
    return {
      statusBarHeight: info.statusBarHeight || 20,
      safeAreaInsets: info.safeAreaInsets || { bottom: 0 }
    };
  } catch (e) {
    return {
      statusBarHeight: 20,
      safeAreaInsets: { bottom: 0 }
    };
  }
}

/**
 * AiQaItem.answerText 可能是 JsonNode（对象）或字符串形式的 JSON。
 * 这里尽量兼容后端返回的字段名，并返回“可展示”的答案内容（对象/字符串都允许，展示时再 normalizeText）。
 */
function extractAnswerFromAnswerText(answerText) {
  if (answerText === undefined || answerText === null) return '';

  // 字符串：尝试解析 JSON，否则当作普通文本
  if (typeof answerText === 'string') {
    const s = answerText.trim();
    if (!s) return '';
    if (
      (s.startsWith('{') && s.endsWith('}')) ||
      (s.startsWith('[') && s.endsWith(']'))
    ) {
      try {
        const parsed = JSON.parse(s);
        return extractAnswerFromAnswerText(parsed);
      } catch (e) {
        // ignore parse error
      }
    }
    return s;
  }

  // 对象：优先取常见答案字段，否则原样返回
  if (typeof answerText === 'object') {
    return (
      answerText.answer ??
      answerText.content ??
      answerText.response ??
      answerText.result ??
      answerText.message ??
      answerText
    );
  }

  return String(answerText);
}

/**
 * 历史记录里有时会把“问题类型: xxx / 用户问题: xxx”拼在一起展示。
 * 这里仅做展示清洗：去掉这些标签，保留最终问题内容。
 */
function stripQuestionTypePrefix(text) {
  const raw = text === undefined || text === null ? '' : String(text);
  let s = raw.trim();
  if (!s) return '';

  // 去掉“问题类型：xxx”（支持中英文冒号、空格/换行容忍）
  s = s.replace(/^\s*问题类型\s*[:：]\s*[^\r\n]*\s*/g, '');
  // 去掉“用户问题：xxx”中的“用户问题”标签（如果存在）
  s = s.replace(/^\s*用户问题\s*[:：]\s*/g, '');

  // 有些内容可能是两行：问题类型 + 用户问题，处理完后再次 trim
  return s.trim();
}

export default {
  data() {
    const systemInfo = getSystemInfo();
    return {
      statusBarHeight: systemInfo.statusBarHeight,
      safeBottom: systemInfo.safeAreaInsets.bottom || 0,
      message: '',
      sending: false,
      useStreamOutput: true,
      chatMessages: [],
      sessionParentId: '',
      streamRequestTask: null,
      streamAiMsgIndex: -1,
      streamBuffer: '',
      streamTextDecoder: null,
      streamPendingBytes: [],

      // AiQaMain 列表面板状态
      showMainList: false,
      mainListLoading: false,
      aiQaMainList: []
    };
  },
  computed: {
    groupedAiQaMainList() {
      const groups = [
        { key: 'today', label: '今天', items: [] },
        { key: 'yesterday', label: '昨天', items: [] },
        { key: 'seven', label: '7天内', items: [] },
        { key: 'thirty', label: '30天内', items: [] },
        { key: 'older', label: '更早', items: [] }
      ];

      const MS_DAY = 24 * 60 * 60 * 1000;
      const now = new Date();
      const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();

      const parseDateToMs = (v) => {
        if (v === undefined || v === null) return null;
        const raw = String(v).trim();
        if (!raw) return null;

        // 数字时间戳（秒/毫秒兜底）
        if (/^\d+$/.test(raw)) {
          const n = Number(raw);
          if (!n) return null;
          // 10 位通常是秒
          const ms = raw.length === 10 ? n * 1000 : n;
          if (ms > 0) return ms;
        }

        // yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss
        const m = raw.match(/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2})(?::(\d{2}))?)?/);
        if (m) {
          const y = Number(m[1]);
          const mo = Number(m[2]) - 1;
          const d = Number(m[3]);
          const hh = m[4] ? Number(m[4]) : 0;
          const mi = m[5] ? Number(m[5]) : 0;
          const ss = m[6] ? Number(m[6]) : 0;
          return new Date(y, mo, d, hh, mi, ss).getTime();
        }

        const dt = new Date(raw);
        const t = dt.getTime();
        return Number.isNaN(t) ? null : t;
      };

      const list = Array.isArray(this.aiQaMainList) ? this.aiQaMainList : [];
      list.forEach((item) => {
        const timeMs =
          parseDateToMs(item.createTime) ||
          parseDateToMs(item.gmtCreate) ||
          parseDateToMs(item.create_time) ||
          parseDateToMs(item.time) ||
          null;
        if (!timeMs) return;

        const diffDays = Math.floor((todayStart - timeMs) / MS_DAY);
        const idx = diffDays === 0 ? 0 : diffDays === 1 ? 1 : diffDays <= 6 ? 2 : diffDays <= 29 ? 3 : 4;
        groups[idx].items.push(item);
      });

      // 过滤空组，并保持顺序
      return groups.filter((g) => g.items.length > 0);
    }
  },
  methods: {
    goBack() {
      uni.navigateBack();
    },
    openMainList() {
      this.showMainList = true;
      this.fetchAiQaMainList(true);
    },
    closeMainList() {
      this.showMainList = false;
    },
    async fetchAiQaMainList(force = false) {
      if (this.mainListLoading) return;
      if (!force && Array.isArray(this.aiQaMainList) && this.aiQaMainList.length > 0) return;

      this.mainListLoading = true;
      try {
        const login = uni.getStorageSync('backend-login-response') || {};
        const salesId = login.userId ? String(login.userId).trim() : '';
        // 后端要求：登录账号传到 salesPhone（用于匹配 AiQaMain.salesPhone）
        // 这里优先取 login.userName；userName 为空再用手机号兜底。
        const salesPhone = firstNonEmptyTrimmed(login.userName, login.phone);

        // salesName 暂不参与过滤（避免误筛导致列表为空）
        const salesName = '';

        const url = getApiUrl('/api/aiQaMain/list');
        const res = await get(url, {
          current: 1,
          size: 50,
          salesId,
          salesPhone,
          salesName
        });

        const body = res && res.data ? res.data : null;
        const success = body && body.success === true;
        if (!success) {
          const msg = body?.message || '请求失败';
          uni.showToast({ title: msg, icon: 'none' });
          this.aiQaMainList = [];
          return;
        }

        this.aiQaMainList = body.data || [];
      } catch (e) {
        console.error('[general_qa] fetchAiQaMainList error:', e);
        uni.showToast({ title: '加载失败', icon: 'none' });
        this.aiQaMainList = [];
      } finally {
        this.mainListLoading = false;
      }
    },
    getMainItemKey(item) {
      const key = firstNonEmptyTrimmed(
        item.id,
        item.mainId,
        item.parentId,
        item.qaMainId,
        item.questionId
      );
      return key || JSON.stringify(item);
    },
    getMainItemTitle(item) {
      return firstNonEmptyTrimmed(
        item.questionTitle,
        item.title,
        item.questionContent,
        item.question,
        item.content,
        item.summary,
        item.name,
        item.id
      );
    },
    async selectMainItem(item) {
      const parentId = firstNonEmptyTrimmed(
        item.id, // AiQaMain.id（优先）
        item.parentId, // 兜底：如果后端把字段直接展开了
        item.mainId,
        item.qaMainId,
        item.sessionParentId
      );

      uni.hideLoading();
      this.mainListLoading = false;
      this.showMainList = false;
      this.chatMessages = [];
      this.message = '';
      this.sending = false;
      this.sessionParentId = parentId ? String(parentId) : '';

      if (!this.sessionParentId) return;

      // 拉取该会话下的历史对话明细，并恢复到聊天框
      try {
        uni.showLoading({ title: '加载历史...', mask: true });
        const url = getApiUrl('/api/aiQaItem/listByParentId');
        const res = await get(url, { parentId: this.sessionParentId });

        uni.hideLoading();
        const body = res && res.data ? res.data : null;
        const success = body && body.success === true;
        if (!success) {
          const msg = body?.message || '查询失败';
          uni.showToast({ title: msg, icon: 'none' });
          return;
        }

        const list = Array.isArray(body.data) ? body.data : [];

        // 后端按 createTime desc 返回，聊天展示一般按时间正序
        const ordered = list.slice().reverse();
        const restored = [];

        ordered.forEach((qItem) => {
          const questionText = firstNonEmptyTrimmed(
            qItem.questSrc,
            qItem.quest_src,
            qItem.questionContent,
            qItem.question,
            qItem.title
          );

          if (questionText) {
            const cleaned = stripQuestionTypePrefix(this.normalizeText(questionText));
            if (cleaned) {
              restored.push({ role: 'user', text: cleaned });
            }
          }

          const answerText = extractAnswerFromAnswerText(
            qItem.answerText ?? qItem.answer_text
          );

          if (answerText !== undefined && answerText !== null && String(answerText).trim()) {
            restored.push({ role: 'ai', text: this.normalizeText(answerText) });
          }
        });

        this.chatMessages = restored;
      } catch (e) {
        uni.hideLoading();
        console.error('[general_qa] listByParentId error:', e);
        uni.showToast({ title: '加载失败，请重试', icon: 'none' });
      }
    },
    startNewSession() {
      this.abortStreamRequest();
      uni.hideLoading();
      this.chatMessages = [];
      this.sessionParentId = '';
      this.message = '';
      this.sending = false;
    },
    toggleStreamOutput() {
      this.useStreamOutput = !this.useStreamOutput;
      uni.showToast({
        title: this.useStreamOutput ? '已开启流式输出' : '已关闭流式输出',
        icon: 'none'
      });
    },
    getRequestHeaders() {
      const header = {
        'Content-Type': 'application/json'
      };
      const token = uni.getStorageSync('backend-token') || '';
      if (token) {
        header.Authorization = `Bearer ${token}`;
      }
      const tenantId = uni.getStorageSync('backend-tenant-id') || '';
      if (tenantId) {
        header['X-Tenant-Id'] = tenantId;
      }
      const roleName = uni.getStorageSync('backend-role-name') || '';
      if (roleName) {
        header['X-Role-Name'] = roleName;
      }
      const scenario = uni.getStorageSync('backend-scenario') || '';
      if (scenario) {
        header['X-Scenario'] = scenario;
      }
      return header;
    },
    abortStreamRequest() {
      if (this.streamRequestTask && typeof this.streamRequestTask.abort === 'function') {
        try {
          this.streamRequestTask.abort();
        } catch (e) {
          // ignore abort error
        }
      }
      this.streamRequestTask = null;
      this.streamAiMsgIndex = -1;
      this.streamBuffer = '';
      this.streamTextDecoder = null;
      this.streamPendingBytes = [];
    },
    decodeUtf8Bytes(bytes) {
      if (!bytes || !bytes.length) return '';

      // 优先使用 TextDecoder，支持分片流式解码（自动处理半包字符）
      if (typeof TextDecoder !== 'undefined') {
        if (!this.streamTextDecoder) {
          this.streamTextDecoder = new TextDecoder('utf-8');
        }
        return this.streamTextDecoder.decode(bytes, { stream: true });
      }

      // 兜底：无 TextDecoder 时尝试按 UTF-8 手动解码
      const pending = Array.isArray(this.streamPendingBytes) ? this.streamPendingBytes : [];
      const merged = pending.concat(Array.from(bytes));
      this.streamPendingBytes = [];

      let i = 0;
      let out = '';
      while (i < merged.length) {
        const b1 = merged[i];
        if ((b1 & 0x80) === 0) {
          out += String.fromCharCode(b1);
          i += 1;
          continue;
        }
        if ((b1 & 0xe0) === 0xc0) {
          if (i + 1 >= merged.length) break;
          const b2 = merged[i + 1];
          out += String.fromCharCode(((b1 & 0x1f) << 6) | (b2 & 0x3f));
          i += 2;
          continue;
        }
        if ((b1 & 0xf0) === 0xe0) {
          if (i + 2 >= merged.length) break;
          const b2 = merged[i + 1];
          const b3 = merged[i + 2];
          out += String.fromCharCode(
            ((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f)
          );
          i += 3;
          continue;
        }
        if ((b1 & 0xf8) === 0xf0) {
          if (i + 3 >= merged.length) break;
          const b2 = merged[i + 1];
          const b3 = merged[i + 2];
          const b4 = merged[i + 3];
          const codePoint =
            ((b1 & 0x07) << 18) |
            ((b2 & 0x3f) << 12) |
            ((b3 & 0x3f) << 6) |
            (b4 & 0x3f);
          out += String.fromCodePoint(codePoint);
          i += 4;
          continue;
        }
        // 非法字节，跳过
        i += 1;
      }

      if (i < merged.length) {
        this.streamPendingBytes = merged.slice(i);
      }
      return out;
    },
    decodeChunkData(raw) {
      if (raw === undefined || raw === null) return '';
      if (typeof raw === 'string') return raw;
      try {
        const u8 = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
        return this.decodeUtf8Bytes(u8);
      } catch (e) {
        return String(raw);
      }
    },
    appendStreamTextChunk(text) {
      if (text === undefined || text === null) return;
      const chunk = String(text);
      if (!chunk) return;
      const idx = this.streamAiMsgIndex;
      if (idx < 0 || !this.chatMessages[idx] || this.chatMessages[idx].role !== 'ai') return;
      this.chatMessages[idx].text += chunk;
    },
    handleStreamDataLine(line) {
      const raw = String(line || '').trim();
      if (!raw) return;
      if (raw === '[DONE]') return;

      let textToAppend = raw;
      try {
        const parsed = JSON.parse(raw);
        if (parsed && typeof parsed === 'object') {
          const parentId = firstNonEmptyTrimmed(parsed.parentId, parsed.parent_id);
          if (parentId) {
            this.sessionParentId = String(parentId);
          }
          textToAppend =
            parsed.delta ??
            parsed.content ??
            parsed.answer ??
            parsed.text ??
            parsed.data ??
            parsed.result ??
            parsed.message ??
            parsed.output ??
            parsed.token ??
            '';
          // 若对象结构未知，至少回退到原始行，避免“有返回但前端无内容”
          if ((textToAppend === undefined || textToAppend === null || String(textToAppend) === '') && raw) {
            textToAppend = raw;
          }
        }
      } catch (e) {
        // 非 JSON 分片按纯文本处理
      }

      if (textToAppend) {
        this.appendStreamTextChunk(this.normalizeText(textToAppend));
      }
    },
    flushStreamBuffer(force = false) {
      if (!this.streamBuffer) return;
      let data = this.streamBuffer;
      if (!force) {
        const lastLf = data.lastIndexOf('\n');
        if (lastLf < 0) return;
        this.streamBuffer = data.slice(lastLf + 1);
        data = data.slice(0, lastLf);
      } else {
        this.streamBuffer = '';
      }
      if (!data) return;

      const lines = data.split(/\r?\n/);
      lines.forEach((line) => {
        const s = String(line || '').trim();
        if (!s) return;
        if (s.startsWith('data:')) {
          this.handleStreamDataLine(s.slice(5));
          return;
        }
        if (s.startsWith('event:') || s.startsWith('id:') || s.startsWith(':')) {
          return;
        }
        this.handleStreamDataLine(s);
      });
    },
    sendByNormal(payload) {
      const url = getApiUrl('/api/aiQaItem/askAI');
      // 非流式回答耗时更长，放宽超时避免频繁超时失败
      post(url, payload, { timeout: 120000 })
        .then((res) => {
          uni.hideLoading();
          const body = res && res.data ? res.data : null;
          const success = body && body.success === true;

          if (!success) {
            const msg = body?.message || '请求失败';
            this.chatMessages.push({ role: 'ai', text: msg });
            return;
          }

          const aiData = body.data || {};
          const returnedParentId = aiData.parentId;
          if (returnedParentId !== undefined && returnedParentId !== null && String(returnedParentId).trim()) {
            this.sessionParentId = String(returnedParentId).trim();
          }

          const rawAnswer =
            aiData.answer ??
            aiData.content ??
            aiData.response ??
            aiData.result ??
            aiData.message ??
            aiData;

          this.chatMessages.push({ role: 'ai', text: this.normalizeText(rawAnswer) });
        })
        .catch((err) => {
          uni.hideLoading();
          console.error('[general_qa] askAI error:', err);
          this.chatMessages.push({ role: 'ai', text: err?.errMsg || '调用失败，请重试' });
        })
        .finally(() => {
          this.sending = false;
        });
    },
    async sendByStream(payload) {
      const url = getApiUrl('/api/aiQaItem/askAIStream');
      this.chatMessages.push({ role: 'ai', text: '' });
      this.streamAiMsgIndex = this.chatMessages.length - 1;
      this.streamBuffer = '';
      this.streamTextDecoder = null;
      this.streamPendingBytes = [];

      // H5 环境：使用 fetch + ReadableStream 实现流式
      // #ifdef H5
      try {
        const headerObj = this.getRequestHeaders();
        const headers = new Headers();
        Object.keys(headerObj || {}).forEach((k) => {
          if (headerObj[k] !== undefined && headerObj[k] !== null) {
            headers.append(k, headerObj[k]);
          }
        });

        const res = await fetch(url, {
          method: 'POST',
          headers,
          body: JSON.stringify(payload)
        });

        if (!res.ok) {
          throw new Error(`HTTP ${res.status}`);
        }

        const reader = res.body && res.body.getReader ? res.body.getReader() : null;
        if (!reader) {
          throw new Error('当前浏览器不支持流式读取');
        }

        const pump = async () => {
          while (true) {
            const { done, value } = await reader.read();
            if (done) {
              this.flushStreamBuffer(true);
              break;
            }
            const chunk = this.decodeUtf8Bytes(value);
            this.streamBuffer += chunk;
            this.flushStreamBuffer(false);
          }
        };

        await pump();

        const idx = this.streamAiMsgIndex;
        if (idx >= 0 && this.chatMessages[idx] && !String(this.chatMessages[idx].text || '').trim()) {
          this.chatMessages[idx].text = '未获取到流式内容';
        }
      } catch (err) {
        console.error('[general_qa] askAIStream H5 error:', err);
        const idx = this.streamAiMsgIndex;
        if (idx >= 0 && this.chatMessages[idx]) {
          const raw = String(this.chatMessages[idx].text || '').trim();
          this.chatMessages[idx].text = raw || (err?.message || '调用失败，请重试');
        } else {
          this.chatMessages.push({ role: 'ai', text: err?.message || '调用失败，请重试' });
        }
      } finally {
        uni.hideLoading();
        this.sending = false;
        this.streamRequestTask = null;
        this.streamAiMsgIndex = -1;
        this.streamBuffer = '';
        this.streamTextDecoder = null;
        this.streamPendingBytes = [];
      }
      return;
      // #endif

      // 非 H5：仍然使用 uni.request / wx.request + onChunkReceived
      const requestOptions = {
        url,
        method: 'POST',
        data: payload,
        header: this.getRequestHeaders(),
        enableChunked: true,
        responseType: 'text',
        success: () => {
          this.flushStreamBuffer(true);
          const idx = this.streamAiMsgIndex;
          if (idx >= 0 && this.chatMessages[idx] && !String(this.chatMessages[idx].text || '').trim()) {
            this.chatMessages[idx].text = '未获取到流式内容';
          }
        },
        fail: (err) => {
          console.error('[general_qa] askAIStream error:', err);
          const idx = this.streamAiMsgIndex;
          if (idx >= 0 && this.chatMessages[idx]) {
            const raw = String(this.chatMessages[idx].text || '').trim();
            this.chatMessages[idx].text = raw || (err?.errMsg || '调用失败，请重试');
          } else {
            this.chatMessages.push({ role: 'ai', text: err?.errMsg || '调用失败，请重试' });
          }
        },
        complete: () => {
          uni.hideLoading();
          this.sending = false;
          this.streamRequestTask = null;
          this.streamAiMsgIndex = -1;
          this.streamBuffer = '';
          this.streamTextDecoder = null;
          this.streamPendingBytes = [];
        }
      };

      let task = null;
      // 微信小程序端优先使用原生 wx.request，确保 onChunkReceived 可用
      // #ifdef MP-WEIXIN
      if (typeof wx !== 'undefined' && wx && typeof wx.request === 'function') {
        task = wx.request(requestOptions);
      } else {
        task = uni.request(requestOptions);
      }
      // #endif
      // #ifndef MP-WEIXIN
      task = uni.request(requestOptions);
      // #endif

      this.streamRequestTask = task;
      if (task && typeof task.onChunkReceived === 'function') {
        task.onChunkReceived((res) => {
          try {
            const chunk = this.decodeChunkData(res.data);
            this.streamBuffer += chunk;
            this.flushStreamBuffer(false);
          } catch (e) {
            console.error('[general_qa] stream chunk parse error:', e);
          }
        });
      } else {
        const idx = this.streamAiMsgIndex;
        uni.hideLoading();
        this.sending = false;
        this.streamRequestTask = null;
        this.streamAiMsgIndex = -1;
        this.streamBuffer = '';
        if (idx >= 0 && this.chatMessages[idx]) {
          this.chatMessages[idx].text = '当前运行环境不支持流式输出，请关闭流式后重试';
        } else {
          this.chatMessages.push({ role: 'ai', text: '当前运行环境不支持流式输出，请关闭流式后重试' });
        }
      }
    },
    copyText(text) {
      const content = (text || '').toString();
      if (!content.trim()) return;
      uni.setClipboardData({
        data: content,
        success: () => {
          uni.showToast({ title: '已复制', icon: 'success' });
        },
        fail: () => {
          uni.showToast({ title: '复制失败', icon: 'none' });
        }
      });
    },
    normalizeText(value) {
      if (value === undefined || value === null) return '';
      if (typeof value === 'string') {
        // 兼容后端把换行转义成 \\n 的情况
        return value.replace(/\\n/g, '\n');
      }
      try {
        return JSON.stringify(value, null, 2);
      } catch (e) {
        return String(value);
      }
    },
    /**
     * 非常轻量的 Markdown 转换，仅支持：
     * - 换行 => <br/>
     * - **加粗** => <strong>
     * - *斜体*  => <em>
     * - 无序列表行 "- " => <ul><li>
     * - 代码块 ``` => <pre><code>
     * 复杂 Markdown 语法暂不完全覆盖，但足够保证结构清晰。
     */
    parseInlineMarkdownToNodes(line) {
      const text = String(line || '');
      const nodes = [];
      const tokenRe = /(\*\*[^*]+\*\*|`[^`\n]+`|\*[^*\n]+\*)/g;
      let lastIndex = 0;
      let match = null;

      while ((match = tokenRe.exec(text)) !== null) {
        const idx = match.index;
        if (idx > lastIndex) {
          nodes.push({ type: 'text', text: text.slice(lastIndex, idx) });
        }
        const token = match[0];
        if (token.startsWith('**') && token.endsWith('**')) {
          nodes.push({
            name: 'strong',
            attrs: { style: 'font-weight:600;' },
            children: [{ type: 'text', text: token.slice(2, -2) }]
          });
        } else if (token.startsWith('`') && token.endsWith('`')) {
          nodes.push({
            name: 'span',
            attrs: {
              style: 'font-family:monospace;background:#f2f2f4;padding:1px 4px;border-radius:3px;'
            },
            children: [{ type: 'text', text: token.slice(1, -1) }]
          });
        } else if (token.startsWith('*') && token.endsWith('*')) {
          nodes.push({
            name: 'em',
            attrs: { style: 'font-style:italic;' },
            children: [{ type: 'text', text: token.slice(1, -1) }]
          });
        } else {
          nodes.push({ type: 'text', text: token });
        }
        lastIndex = idx + token.length;
      }

      if (lastIndex < text.length) {
        nodes.push({ type: 'text', text: text.slice(lastIndex) });
      }
      if (nodes.length === 0) {
        nodes.push({ type: 'text', text: '' });
      }
      return nodes;
    },
    renderMarkdownNodes(text) {
      try {
        const raw = String(text || '');
        const lines = raw.replace(/\r\n/g, '\n').split('\n');
        const nodes = [];
        let i = 0;
        let inCode = false;
        let codeLines = [];

        while (i < lines.length) {
          const line = lines[i];

          // 代码块围栏
          if (line.trim().startsWith('```')) {
            if (!inCode) {
              inCode = true;
              codeLines = [];
            } else {
              inCode = false;
              nodes.push({
                name: 'div',
                attrs: {
                  style: 'background:#f7f7f9;border-radius:6px;padding:8px 10px;font-family:monospace;white-space:pre-wrap;word-break:break-word;'
                },
                children: [{ type: 'text', text: codeLines.join('\n') }]
              });
            }
            i += 1;
            continue;
          }

          if (inCode) {
            codeLines.push(line);
            i += 1;
            continue;
          }

          // 无序列表
          if (/^\s*-\s+/.test(line)) {
            const ulChildren = [];
            while (i < lines.length && /^\s*-\s+/.test(lines[i])) {
              const content = lines[i].replace(/^\s*-\s+/, '');
              ulChildren.push({
                name: 'li',
                attrs: { style: 'margin:4px 0;' },
                children: this.parseInlineMarkdownToNodes(content)
              });
              i += 1;
            }
            nodes.push({
              name: 'ul',
              attrs: { style: 'padding-left:18px;margin:6px 0;' },
              children: ulChildren
            });
            continue;
          }

          // 空行
          if (!line.trim()) {
            nodes.push({ name: 'div', children: [{ type: 'text', text: ' ' }] });
            i += 1;
            continue;
          }

          // 普通段落
          nodes.push({
            name: 'div',
            attrs: { style: 'line-height:1.6;margin:2px 0;word-break:break-word;' },
            children: this.parseInlineMarkdownToNodes(line)
          });
          i += 1;
        }

        // 兜底：如果代码块未闭合，也照样展示
        if (inCode && codeLines.length > 0) {
          nodes.push({
            name: 'div',
            attrs: {
              style: 'background:#f7f7f9;border-radius:6px;padding:8px 10px;font-family:monospace;white-space:pre-wrap;word-break:break-word;'
            },
            children: [{ type: 'text', text: codeLines.join('\n') }]
          });
        }

        return nodes;
      } catch (e) {
        return [{ type: 'text', text: String(text || '') }];
      }
    },
    onSend() {
      const text = (this.message || '').trim();
      if (!text) {
        uni.showToast({ title: '请输入消息', icon: 'none' });
        return;
      }
      if (this.sending) return;

      this.sending = true;

      // 角色消息先入栈，减少等待感
      const userMsg = { role: 'user', text };
      this.chatMessages.push(userMsg);
      this.message = '';

      uni.showLoading({ title: '生成中...', mask: true });

      // 该页面对应“通用问答”
      const questionType = 'general_qa';

      const { loginAccount, userName } = getSubmitUserContext();
      const payload = {
        questionType,
        questionContent: text,
        loginAccount,
        userName
      };
      // parentId：第一次请求不传；后端返回后再带上
      if (this.sessionParentId) {
        payload.parentId = this.sessionParentId;
      }

      if (this.useStreamOutput) {
        this.sendByStream(payload);
      } else {
        this.sendByNormal(payload);
      }
    }
  }
};
</script>

<style scoped>
.qa-page {
  flex: 1;
  min-height: 100vh;
  background-color: #F5F5F5;
  display: flex;
  flex-direction: column;
}

.qa-header {
  background-color: #FFFFFF;
  border-bottom: 1rpx solid #EDEDED;
}

.qa-header-bar {
  height: 88rpx;
  padding: 0 32rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.qa-header-left {
  width: 60rpx;
  height: 60rpx;
  justify-content: center;
  align-items: flex-start;
  display: flex;
  flex-direction: column;
}

.qa-header-menu-line {
  width: 26rpx;
  height: 4rpx;
  border-radius: 4rpx;
  background-color: #333333;
  margin-bottom: 6rpx;
}

.qa-header-menu-line-short {
  width: 16rpx;
}

.qa-header-title {
  flex: 1;
  text-align: center;
  font-size: 30rpx;
  font-weight: 500;
  color: #333333;
}

.qa-header-right {
  width: 60rpx;
  height: 60rpx;
  align-items: center;
  justify-content: center;
  display: flex;
}

.qa-header-plus {
  font-size: 38rpx;
  color: #333333;
}

.qa-main {
  flex: 1;
  align-items: center;
  justify-content: center;
  display: flex;
  flex-direction: column;
  padding-bottom: 200rpx; /* 给底部固定输入框留空间 */
}

.qa-main-icon-wrapper {
  width: 120rpx;
  height: 120rpx;
  border-radius: 60rpx;
  background-color: #FFFFFF;
  align-items: center;
  justify-content: center;
  display: flex;
  margin-bottom: 32rpx;
  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.06);
}

.qa-main-icon {
  font-size: 60rpx;
  color: #2A68FF;
}

.qa-main-text {
  font-size: 30rpx;
  color: #333333;
}

.qa-chat {
  flex: 1;
  width: 100%;
  padding-bottom: 200rpx; /* 给底部固定输入框留空间 */
}

.qa-chat-inner {
  padding: 24rpx 24rpx 12rpx;
}

.qa-msg {
  margin-bottom: 20rpx;
  max-width: 88%;
  position: relative;
}

.qa-msg-actions {
  position: absolute;
  top: 8rpx;
  z-index: 2;
}

.qa-msg-actions--left {
  left: 8rpx;
}

.qa-msg-actions--right {
  right: 8rpx;
}

.qa-copy-btn {
  width: 44rpx;
  height: 44rpx;
  border-radius: 22rpx;
  background-color: #F4F6FA;
  border: 1rpx solid #E5EAF3;
  display: flex;
  align-items: center;
  justify-content: center;
}

.qa-copy-btn-icon {
  font-size: 22rpx;
  color: #8a8a8a;
  line-height: 1;
}

.qa-msg--user {
  margin-left: auto;
  background-color: #EAF2FF;
  border: 1rpx solid #D7E6FF;
}

.qa-msg--ai {
  margin-right: auto;
  background-color: #FFFFFF;
  border: 1rpx solid #F0F0F0;
}

.qa-msg--user,
.qa-msg--ai {
  padding: 18rpx 20rpx;
  border-radius: 16rpx;
}

.qa-msg-text {
  font-size: 26rpx;
  color: #333333;
  line-height: 1.6;
  word-break: break-word;
  user-select: text;
}

.qa-msg-text-markdown {
  padding-top: 6rpx;
}

.md-root {
  font-size: 26rpx;
  line-height: 1.6;
  color: #333333;
}

.md-root strong {
  font-weight: 600;
}

.md-root em {
  font-style: italic;
}

.md-root ul {
  padding-left: 32rpx;
  margin: 8rpx 0;
}

.md-root li {
  margin: 4rpx 0;
}

.md-code-block {
  background-color: #f7f7f9;
  border-radius: 8rpx;
  padding: 10rpx 14rpx;
  font-family: monospace;
  overflow-x: auto;
}

.md-inline-code {
  font-family: monospace;
  background-color: #f2f2f4;
  padding: 2rpx 6rpx;
  border-radius: 4rpx;
}

/* 复制按钮在角上绝对定位；气泡已有左右 padding，再给正文加一点边距避免与 44rpx 按钮重叠 */
.qa-msg--ai .qa-msg-text {
  padding-left: 40rpx;
}

.qa-msg--user .qa-msg-text {
  padding-right: 40rpx;
}

.qa-input-wrapper {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1000;
  padding: 12rpx 20rpx 12rpx;
  background-color: #FFFFFF;
  box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.04);
  box-sizing: border-box;
}

/* AiQaMain 列表面板 */
.main-list-mask {
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.35);
  z-index: 1999;
}

.main-list-drawer {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: 560rpx;
  background-color: #FFFFFF;
  z-index: 2000;
  border-right: 1rpx solid #F0F0F0;
  display: flex;
  flex-direction: column;
}

.main-list-drawer-header {
  padding: 22rpx 24rpx;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1rpx solid #F0F0F0;
}

.main-list-drawer-title {
  font-size: 32rpx;
  font-weight: 600;
  color: #333333;
}

.main-list-drawer-close {
  font-size: 28rpx;
  color: #666666;
}

.main-list-scroll {
  flex: 1;
}

.main-list-loading,
.main-list-empty {
  padding: 30rpx 24rpx;
  font-size: 28rpx;
  color: #888888;
}

.main-list-group {
  padding: 18rpx 24rpx;
}

.main-list-group-title {
  font-size: 28rpx;
  color: #333333;
  font-weight: 600;
  margin-bottom: 10rpx;
}

.main-list-item {
  background-color: #F7F7FA;
  border: 1rpx solid #EFEFF4;
  border-radius: 12rpx;
  padding: 18rpx 16rpx;
  margin-bottom: 14rpx;
}

.main-list-item-text {
  font-size: 26rpx;
  color: #333333;
  line-height: 1.4;
  word-break: break-word;
}

.qa-input-row {
  flex-direction: row;
  display: flex;
  align-items: center;
}

.qa-input-box {
  flex: 1;
  min-height: 80rpx;
  border-radius: 40rpx;
  background-color: #F5F5F5;
  padding: 0 32rpx;
  display: flex;
  align-items: center;
}

.qa-textarea {
  width: 100%;
  min-height: 88rpx; /* 默认两行：2*44rpx */
  max-height: 176rpx; /* 最大四行：4*44rpx */
  line-height: 44rpx;
  font-size: 26rpx;
  color: #333333;
  padding: 18rpx 0;
  box-sizing: border-box;
  overflow-y: auto;
}

.qa-input-placeholder {
  font-size: 26rpx;
  color: #999999;
}

.qa-input-send {
  width: 80rpx;
  height: 80rpx;
  border-radius: 40rpx;
  margin-left: 16rpx;
  background-color: #2A68FF;
  align-items: center;
  justify-content: center;
  display: flex;
}

.qa-input-actions {
  margin-top: 12rpx;
  flex-direction: row;
  display: flex;
}

.qa-chip {
  padding: 8rpx 20rpx;
  border-radius: 999rpx;
  background-color: #F5F5F5;
  margin-right: 16rpx;
  display: flex;
  align-items: center;
  justify-content: center;
}

.qa-chip-stream {
  display: flex;
  flex-direction: row;
  align-items: center;
  background-color: #F1F4FA;
}

.qa-chip-stream--active {
  background-color: #E7F0FF;
}

.qa-chip-stream-icon {
  font-size: 24rpx;
  color: #2A68FF;
  line-height: 1;
  margin-right: 8rpx;
}

.qa-chip-stream-text {
  font-size: 24rpx;
  color: #2A68FF;
  line-height: 1;
}

</style>
