<template>
  <view class="deepseek-chat-container">
    <scroll-view class="chat-messages" scroll-y :scroll-into-view="scrollIntoView" v-show="pestMessage">
      <view class="message-content">
        <view class="message-input" style="padding:0 20rpx">{{ pestMessage }}</view>
        <view v-if="isloading" class="loading-hint">AI 正在分析中…</view>
        <view v-if="errorMsg" class="loading-hint error-hint">{{ errorMsg }}</view>
        <rich-text class="message-text markdown-output" :nodes="outputHtml"></rich-text>
        <view class="message-input footer">内容由大模型生成，仅供参考，相关风险需自行承担。</view>
      </view>
      <view id="chatBottomAnchor"></view>
    </scroll-view>
    <view v-if="!pestMessage" style="height:100%">

    </view>
    <!-- App 端 renderjs SSE 桥接：仅 App 渲染（H5/小程序走 sse.js，不触发 renderjs）。
         用 v-if="isApp" 运行时门控，避免 H5 上 change:prop 随组件重渲染被反复触发。 -->
    <view v-if="isApp" :sseSignal="sseSignal" :change:sseSignal="sse.onSignalChange" style="position:absolute;left:-9999px;width:1px;height:1px;opacity:0"></view>
  </view>
</template>

<script>
import { createSSEConnection } from './sse.js';
import { renderMarkdown } from './markdownRenderer.js';

export default {
  name: 'DeepSeekChat',
  props: {
    pestMessage: {
      type: String,
      default: ''
    },
  },
  data() {
    return {
      isApp: typeof plus !== 'undefined', // 运行时判断是否 App：H5/小程序走 sse.js，不启用 renderjs
      text: '',
      sseConnection: null,
      sseUrl: '',          // 当前请求地址（供 renderjs 失败时降级复用）
      sseSignal: null,     // 与 renderjs 通信的信号（App 端用）
      result: '',
      thinking: false,
      activeNames: ['1'],
      thinkHtml: '',
      isloading: true,
      errorMsg: '',    // 连接失败/超时时的提示文案
      mdParser: null,
      mdRawText: '',   // 累积的原始 markdown 文本
      outputHtml: '',  // renderMarkdown 渲染后的 HTML，交给 rich-text 展示
      scrollIntoView: '', // scroll-view 滚动到底部锚点用
      sseTimer: null,  // 连接“一条数据都没收到”的超时兜底定时器
    };
  },
  watch: {
    pestMessage(newVal) {
      if (newVal) {
        this.connectSSE(newVal);
      } else {
        this.isloading = false;
      }
    }
  },
  methods: {
    resetStreamState() {
      this.mdRawText = '';
      this.outputHtml = '';
      this.text = '';
      this.errorMsg = '';
      this._thinkContent = '';
      this._isCollecting = false;
      this._receivedAny = false;
    },
    connectSSE(message) {
      this.clearSseTimer();
      // 关闭上一次 sse.js 连接；App 的 renderjs 旧 XHR 由 start() 内部 stop() 清理，
      // 这里不再调 disconnectSSE()，避免在 App 上多发一个 stop 信号导致 onSignalChange 多触发一次
      if (this.sseConnection) {
        this.sseConnection.close();
        this.sseConnection = null;
      }
      this.resetStreamState();
      this.isloading = true; // 进入“分析中”

      // message 含中文/逗号/冒号，必须编码后拼到 URL
      const sseUrl = `https://ai.nyzhwlw.com/analysis?message=${encodeURIComponent(message)}`;
      this.sseUrl = sseUrl;
      this._fallbackUsed = false;

      // 超时兜底：30s 内一条数据都没收到（含 <think>），判定为连接异常
      this.startTimeout();

      // App 走 renderjs（WebView 原生 XHR，逐字流式）；H5/小程序走 sse.js
      if (this.isApp) {
        this.startViaRenderjs(sseUrl);
      } else {
        this.startViaSseJs(sseUrl);
      }
    },
    startTimeout() {
      this.sseTimer = setTimeout(() => {
        if (!this._receivedAny) {
          this.errorMsg = 'AI 分析超时，请稍后重试';
          this.isloading = false;
          this.disconnectSSE();
        }
      }, 30000);
    },
    // H5/小程序（及 App 降级）：用 sse.js 连接
    startViaSseJs(url) {
      this.sseConnection = createSSEConnection(url);
      this.sseConnection.onMessage((data) => this.handleChunk(data));
      this.sseConnection.onError((err) => this.onStreamError(err));
      this.sseConnection.onClose(() => this.onStreamClose());
    },
    // App：通过 renderjs 桥接，WebView 用原生 XHR 接收流式数据
    startViaRenderjs(url) {
      this.sseSignal = { action: 'start', url };
    },
    stopRenderjs() {
      this.sseSignal = { action: 'stop' };
    },
    // 三端共用的数据处理：think 块过滤 + markdown 累积渲染
    handleChunk(data) {
      this._receivedAny = true;
      this.isloading = false;
      if (data === 'yfkj@2025' || data === '[DONE]') {
        return;
      }
      // 处理 <think>...</think> 思考块：模板不展示思考内容，只保留 </think> 之后的正文。
      // （原实现里 think 用到的 mdParser 未初始化会抛错，导致后续正文也不渲染，这里整体丢弃思考块）
      if (this._isCollecting || data.indexOf('<think>') > -1) {
        if (data.indexOf('<think>') > -1) {
          this._isCollecting = true;
          this.thinking = true;
        }
        this._thinkContent += data;
        const endIdx = this._thinkContent.indexOf('</think>');
        if (endIdx === -1) {
          return; // 仍在思考块内，等待结束标记
        }
        data = this._thinkContent.slice(endIdx + '</think>'.length);
        this._thinkContent = '';
        this._isCollecting = false;
        this.thinking = false;
        if (!data) {
          return;
        }
      }
      // 累积正文并渲染为 markdown，用 rich-text 展示
      this.mdRawText += data;
      this.outputHtml = renderMarkdown(this.mdRawText);
      this.scrollToBottom();
    },
    onStreamError(err) {
      console.error('SSE Error:', err);
      this.clearSseTimer();
      // 已经渲染出内容时不弹错误，避免服务端正常关闭被误判为失败
      if (!this.mdRawText) {
        this.errorMsg = 'AI 分析失败，请稍后重试';
      }
      this.isloading = false;
      this.disconnectSSE();
    },
    onStreamClose() {
      // 流正常结束，仅清理超时定时器
      this.clearSseTimer();
    },
    // ===== renderjs → 逻辑层 回调（仅 App 会触发）=====
    onSseData(data) {
      this.handleChunk(data);
    },
    onSseError(msg) {
      // renderjs 流式失败（常见于跨域 CORS）：若还没收到任何数据，降级到 sse.js 缓冲通道，
      // 保证至少能出结果（只是没有逐字效果）
      if (!this._receivedAny && !this._fallbackUsed) {
        this._fallbackUsed = true;
        this.stopRenderjs();
        this.startViaSseJs(this.sseUrl);
        return;
      }
      this.onStreamError(new Error(msg || 'stream error'));
    },
    onSseClose() {
      this.onStreamClose();
    },
    disconnectSSE() {
      if (this.isApp) {
        this.stopRenderjs();
      }
      if (this.sseConnection) {
        this.sseConnection.close();
        this.sseConnection = null;
      }
    },
    clearSseTimer() {
      if (this.sseTimer) {
        clearTimeout(this.sseTimer);
        this.sseTimer = null;
      }
    },
    formatTime(date) {
      return date.toLocaleTimeString([], {
        hour: '2-digit',
        minute: '2-digit'
      });
    },
    scrollToBottom() {
      // 小程序没有 DOM，不能用 $refs.scrollTop；改用 scroll-view 的 scroll-into-view 滚到底部锚点
      this.scrollIntoView = '';
      this.$nextTick(() => {
        this.scrollIntoView = 'chatBottomAnchor';
      });
    }
  },
  mounted() {
    this.scrollToBottom();
    if (this.pestMessage) {
      this.connectSSE(this.pestMessage);
    } else {
      this.isloading = false;
    }
  },
  beforeDestroy() {
    // 组件卸载时断开连接并清理定时器（uni-app 默认 Vue2，用 beforeDestroy）
    this.clearSseTimer();
    this.disconnectSSE();
  }
};
</script>

<script module="sse" lang="renderjs">
// App 端：在 WebView 层用原生 XMLHttpRequest 接收 SSE 流式数据。
// 通过 readyState=3 实时读取 responseText 增量并回传逻辑层，实现逐字输出。
// （plus.net.XMLHttpRequest 会被缓冲、无法实时，故改用 renderjs。）
export default {
  data() {
    return {
      xhr: null,
      buffer: '',   // SSE 事件解析缓冲
      processed: 0  // responseText 已处理长度
    };
  },
  methods: {
    // 逻辑层改变 sseSignal 时触发（:change:sseSignal="sse.onSignalChange"）
    onSignalChange(signal) {
      if (!signal) return;
      if (signal.action === 'start' && signal.url) {
        this.start(signal.url);
      } else if (signal.action === 'stop') {
        this.stop();
      }
    },
    start(url) {
      this.stop();
      this.buffer = '';
      this.processed = 0;
      const xhr = new XMLHttpRequest();
      xhr.open('GET', url, true);
      try { xhr.setRequestHeader('Accept', 'text/event-stream'); } catch (e) {}
      const self = this;
      xhr.onreadystatechange = function () {
        // readyState 3(LOADING) / 4(DONE) 都读取增量，3 时实时推送即为逐字流式
        if (xhr.readyState === 3 || xhr.readyState === 4) {
          const full = xhr.responseText || '';
          if (full.length > self.processed) {
            const delta = full.substring(self.processed);
            self.processed = full.length;
            self.parseAndSend(delta);
          }
        }
        if (xhr.readyState === 4) {
          self.$ownerInstance.callMethod('onSseClose');
        }
      };
      xhr.onerror = function () {
        self.$ownerInstance.callMethod('onSseError', 'xhr error');
      };
      xhr.send();
      this.xhr = xhr;
    },
    // SSE 协议解析：按空行(\n\n)切事件，提取 data: 字段后回传逻辑层
    parseAndSend(text) {
      this.buffer += text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
      let idx;
      while ((idx = this.buffer.indexOf('\n\n')) !== -1) {
        const raw = this.buffer.slice(0, idx);
        this.buffer = this.buffer.slice(idx + 2);
        const lines = raw.split('\n');
        let dataStr = '';
        for (let i = 0; i < lines.length; i++) {
          if (lines[i].indexOf('data:') === 0) {
            dataStr += lines[i].slice(5).replace(/^ /, '');
          }
        }
        if (dataStr !== '') {
          this.$ownerInstance.callMethod('onSseData', dataStr.replace(/\\n/g, '\n'));
        }
      }
    },
    stop() {
      if (this.xhr) {
        try { this.xhr.abort(); } catch (e) {}
        this.xhr = null;
      }
    }
  },
  beforeDestroy() {
    this.stop();
  }
};
</script>

<style scoped lang="less">
.el-collapse {
  background: #f6f6f6;
  color: #a4aab2;
  border-radius: 8rpx;
}
::v-deep .el-collapse-item__header {
  padding: 0 16rpx;
  height: 72rpx;
  background: #f6f6f6 !important;
  border-radius: 8rpx;
}
::v-deep .el-collapse-item__wrap {
  background: #f6f6f6 !important;
  color: #a4aab2 !important;
  border-radius: 8rpx;
}
::v-deep .el-collapse-item__content {
  color: #a4aab2 !important;
  border-radius: 8rpx;
}
.deep-thinking {
  padding: 0 52rpx;
  border-radius: 8rpx;
}
.deepseek-chat-container {
  position: relative;
  display: flex;
  flex-direction: column;
  height: calc(100vh - 640rpx);
  // max-width: 1086px;
  margin: 0 auto;
  border-radius: 8rpx;
  // box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
  overflow: hidden;
  font-family: 'Segoe UI', Arial, sans-serif;
  // background-color: #f9f9f9;
}
.chat-messages {
  flex: 1;
  overflow-y: auto;
  height: 100%;
  // background-color: #ffffff;
}

.message {
  display: flex;
  margin-bottom: 24rpx;
}

.message-content {
  max-width: 100%;
}
.loading-hint {
  padding: 16rpx;
  color: #999;
  font-size: 26rpx;
}

.error-hint {
  color: #e64340;
}

.message-text {
  padding: 16rpx;
  border-radius: 18rpx;
  line-height: 1.4;
  word-wrap: break-word;
  padding-bottom: 60rpx;
}

.message.bot .message-text {
  background-color: #fff;
  border-top-left-radius: 8rpx;
  color: #5c5c5c;
}

.message.user .message-text {
  color: white;
  border-top-right-radius: 8rpx;
}

.message-time {
  font-size: 22rpx;
  color: #777;
  margin-top: 8rpx;
  text-align: right;
}

.message.user .message-time {
  text-align: left;
}

.typing-indicator {
  display: flex;
  padding: 12rpx 30rpx;
  background-color: #f6f6f6;
  border-radius: 18rpx;
  border-top-left-radius: 8rpx;
}

.typing-indicator span {
  width: 16rpx;
  height: 16rpx;
  margin: 0 2rpx;
  background-color: #999;
  border-radius: 50%;
  display: inline-block;
  animation: bounce 1.4s infinite ease-in-out both;
}

.typing-indicator span:nth-child(1) {
  animation-delay: -0.32s;
}

.typing-indicator span:nth-child(2) {
  animation-delay: -0.16s;
}
@keyframes bounce {
  0%,
  80%,
  100% {
    transform: scale(0);
  }
  40% {
    transform: scale(1);
  }
}

/* Markdown样式 - 使用深度选择器 */
::v-deep .markdown-output {
  /* 标题样式 */
  h1, h2, h3, h4, h5, h6 {
    margin-top: 32rpx;
    margin-bottom: 16rpx;
    font-weight: 600;
    line-height: 1.25;
  }
  h1 { font-size: 1.8em; }
  h2 { font-size: 1.5em; }
  h3 { font-size: 1.25em; }
  h4 { font-size: 1em; }
  h5 { font-size: 0.875em; }
  h6 { font-size: 0.85em; }

  /* 列表样式 */
  ul, ol {
    padding-left: 2em;
    margin: 16rpx 0;
    list-style-type: disc;
  }
  ol {
    list-style-type: decimal;
  }
  li {
    margin: 8rpx 0;
    line-height: 1.5;
  }

  /* 粗体斜体 */
  strong {
    font-weight: 600;
  }
  em {
    font-style: italic;
  }

  /* 代码块 */
  pre {
    background-color: #f6f8fa;
    padding: 32rex;
    border-radius: 12rpx;
    overflow-x: auto;
    margin: 16rpx 0;
  }
  code {
    background-color: #f6f8fa;
    padding: 4rpx 8rpx;
    border-radius: 6rpx;
    font-family: monospace;
  }

  /* 段落 */
  p {
    margin: 16rpx 0;
    line-height: 1.5;
  }
}

.message-input {
  padding: 20rpx 30rpx;
  border-radius: 8rpx;
  background-color: #f6f6f6;

  &.footer {
    position: fixed;
    bottom: 20rpx;
    left: 0;
    right: 0rpx;
    margin-top: 40rpx;
    font-size: 24rpx;
    color: #999;
    text-align: center;
  }
}
</style>
