/**
 * 简化消息解析器
 */

import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import { RawMessage, MessageElement, NTMsgType } from 'NapCatQQ/src/core/types.js';

/* ------------------------------ 内部高性能工具 ------------------------------ */

/** 并发限流 map（保持顺序） */
async function mapLimit<T, R>(
  arr: readonly T[],
  limit: number,
  mapper: (item: T, index: number) => Promise<R>
): Promise<R[]> {
  const len = arr.length;
  const out = new Array<R>(len);
  if (len === 0) return out;

  const workers = Math.min((limit >>> 0) || 1, len);
  let next = 0;

  async function worker() {
    while (true) {
      const i = next++;
      if (i >= len) break;
      out[i] = await mapper(arr[i]!, i);
    }
  }
  const tasks = new Array(workers);
  for (let i = 0; i < workers; i++) tasks[i] = worker();
  await Promise.all(tasks);
  return out;
}

function resolveConcurrency(): number {
  try {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const os = require('os');
    const cores = (os?.cpus?.() || []).length || 4;
    return Math.max(4, Math.min(32, cores * 2));
  } catch {
    return 8;
  }
}

/** 让出事件循环 */
function yieldToEventLoop(): Promise<void> {
  return new Promise((resolve) => {
    if (typeof setImmediate === 'function') setImmediate(resolve);
    else setTimeout(resolve, 0);
  });
}

/** Chunked 字符串构建器 */
class ChunkedBuilder {
  private chunks: string[] = [];
  push(s: string | undefined | null) {
    if (s) this.chunks.push(s);
  }
  toString() {
    return this.chunks.join('');
  }
  clear() {
    this.chunks.length = 0;
  }
}

const NEED_ESCAPE_RE = /[&<>"']/;
function escapeHtmlFast(text: string): string {
  if (!text) return '';
  if (!NEED_ESCAPE_RE.test(text)) return text;
  const len = text.length;
  let out = '';
  let last = 0;
  for (let i = 0; i < len; i++) {
    const c = text.charCodeAt(i);
    let rep: string | null = null;
    if (c === 38) rep = '&amp;';
    else if (c === 60) rep = '&lt;';
    else if (c === 62) rep = '&gt;';
    else if (c === 34) rep = '&quot;';
    else if (c === 39) rep = '&#39;';
    if (rep) {
      if (i > last) out += text.slice(last, i);
      out += rep;
      last = i + 1;
    }
  }
  if (last < len) out += text.slice(last);
  return out;
}

/** RFC3339（UTC）格式化工具 */
function pad2(n: number) {
  return n < 10 ? '0' + n : '' + n;
}
function pad3(n: number) {
  if (n >= 100) return '' + n;
  if (n >= 10) return '0' + n;
  return '00' + n;
}
function pad4(n: number) {
  if (n >= 1000) return '' + n;
  if (n >= 100) return '0' + n;
  if (n >= 10) return '00' + n;
  return '000' + n;
}
function rfc3339FromMillis(ms: number): string {
  const d = new Date(ms);
  return `${pad4(d.getUTCFullYear())}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}T${pad2(
    d.getUTCHours()
  )}:${pad2(d.getUTCMinutes())}:${pad2(d.getUTCSeconds())}.${pad3(d.getUTCMilliseconds())}Z`;
}
function rfc3339FromUnixSeconds(sec: number | string | bigint): string {
  try {
    if (typeof sec === 'bigint') {
      const n = Number(sec * 1000n);
      return Number.isFinite(n) ? rfc3339FromMillis(n) : '1970-01-01T00:00:00.000Z';
    }
    const n = typeof sec === 'string' ? parseInt(sec, 10) : sec;
    if (!Number.isFinite(n)) return '1970-01-01T00:00:00.000Z';
    return rfc3339FromMillis(Math.trunc(n * 1000));
  } catch {
    return '1970-01-01T00:00:00.000Z';
  }
}
function millisFromUnixSeconds(sec: number | string | bigint): number {
  try {
    if (typeof sec === 'bigint') {
      const n = Number(sec * 1000n);
      return Number.isFinite(n) ? n : 0;
    }
    const n = typeof sec === 'string' ? parseInt(sec, 10) : sec;
    return Number.isFinite(n) ? Math.trunc(n * 1000) : 0;
  } catch {
    return 0;
  }
}

/** 高性能 JSON 解析（SIMD 优先） */
type FastJsonParser = (s: string) => any;
let fastJsonParse: FastJsonParser = (s) => JSON.parse(s);
(function tryLoadSimdJson() {
  try {
    // eslint-disable-next-line @typescript-eslint/no-var-requires
    const mod = typeof require !== 'undefined' ? require('simdjson') : null;
    if (mod && typeof mod.parse === 'function') {
      fastJsonParse = (s) => mod.parse(s);
    }
  } catch {
    // 静默降级
  }
})();

/* ------------------------------ 导出类型（保持不变） ------------------------------ */

export interface CleanMessage {
  id: string;
  seq: string;
  timestamp: number;
  time: string;
  sender: {
    uid: string;
    uin?: string;
    name: string;          // 优先显示的名称（群昵称 > 备注 > QQ昵称）
    nickname?: string;     // QQ昵称（原始昵称）
    groupCard?: string;    // 群名片/群昵称
    remark?: string;       // 好友备注
  };
  type: string;
  content: MessageContent;
  recalled: boolean;
  system: boolean;
}

export interface MessageContent {
  text: string;
  html: string;
  elements: MessageElementData[];
  resources: ResourceData[];
  mentions: MentionData[];
}

export interface MentionData {
  uid: string;
  uin?: string;
  name: string;
  type: 'user' | 'all';
}

export interface MessageElementData {
  type: string;
  data: any;
}

export interface ResourceData {
  type: string;
  filename: string;
  size: number;
  url?: string;
  localPath?: string;
  width?: number;
  height?: number;
  duration?: number;
}

export interface MessageStatistics {
  total: number;
  byType: Record<string, number>;
  bySender: Record<string, { uid: string; count: number }>;
  resources: {
    total: number;
    byType: Record<string, number>;
    totalSize: number;
  };
  timeRange: {
    start: string;
    end: string;
    durationDays: number;
  };
}

/** 轻量解析器配置 */
export interface SimpleParserOptions {
  concurrency?: number;
  progressEvery?: number;
  yieldEvery?: number;
  html?: 'full' | 'none';
  onProgress?: (processed: number, total: number) => void;
}

const DEFAULT_SIMPLE_OPTIONS: Required<Omit<SimpleParserOptions, 'onProgress'>> = {
  concurrency: resolveConcurrency(),
  progressEvery: 100,
  yieldEvery: 1000,
  html: 'full'
};

/* ---------------------------------- 主类 ---------------------------------- */

export class SimpleMessageParser {
  private readonly options: Required<Omit<SimpleParserOptions, 'onProgress'>>;
  private readonly onProgress?: (processed: number, total: number) => void;

  private readonly concurrency: number;

  // 全局消息映射，用于查找被引用的消息
  private messageMap: Map<string, RawMessage> = new Map();
  
  // QQ表情映射表
  private faceMap: Map<string, string> = new Map();

  constructor(opts: SimpleParserOptions = {}) {
    this.options = { ...DEFAULT_SIMPLE_OPTIONS, ...opts };
    this.onProgress = opts.onProgress;
    this.concurrency = this.options.concurrency ?? resolveConcurrency();
    this.initializeFaceMap();
  }

  /**
   * 解析消息列表（高并发 + 有序输出）
   */
  async parseMessages(messages: RawMessage[]): Promise<CleanMessage[]> {
    const total = messages.length;
    let processed = 0;

    // 先建立全局消息映射
    this.messageMap.clear();
    for (const msg of messages) {
      if (msg && msg.msgId) {
        this.messageMap.set(msg.msgId, msg);
        // 同时将 records 数组中的消息也添加到映射中
        if (msg.records && msg.records.length > 0) {
          for (const record of msg.records) {
            if (record && record.msgId) {
              this.messageMap.set(record.msgId, record);
            }
          }
        }
      }
    }

    const results = await mapLimit(messages, this.concurrency, async (message, idx) => {
      try {
        const cm = await this.parseMessage(message);

        processed++;
        if (this.onProgress) {
          this.onProgress(processed, total);
        } else if (processed % this.options.progressEvery === 0) {
          console.log(`[SimpleMessageParser] 已解析 ${processed}/${total}`);
        }

        if (this.options.yieldEvery > 0 && (idx + 1) % this.options.yieldEvery === 0) {
          await yieldToEventLoop();
        }

        return cm;
      } catch (error) {
        console.error('解析消息失败:', error, message?.msgId);
        return this.createErrorMessage(message, error);
      }
    });
    
    // 清理映射
    this.messageMap.clear();
    
    return results;
  }

  /**
   * 【流式版本】解析消息生成器 - 逐条解析并yield，实现低内存占用
   * 适用于大量消息的场景，配合流式导出可实现全程低内存
   */
  async *parseMessagesStream(
    messages: RawMessage[],
    resourceMap?: Map<string, any>
  ): AsyncGenerator<CleanMessage, void, undefined> {
    const total = messages.length;
    let processed = 0;

    for (let i = 0; i < messages.length; i++) {
      const message = messages[i];
      if (!message) continue; // 跳过undefined元素
      
      try {
        const cleanMessage = await this.parseMessage(message);
        
        // 如果提供了resourceMap，立即更新这条消息的资源路径
        if (resourceMap && resourceMap.has(message.msgId)) {
          const resources = resourceMap.get(message.msgId);
          if (resources && cleanMessage.content.elements) {
            this.updateSingleMessageResourcePaths(cleanMessage, resources);
          }
        }

        processed++;
        if (this.onProgress) {
          this.onProgress(processed, total);
        } else if (processed % this.options.progressEvery === 0) {
          console.log(`[SimpleMessageParser] 已解析 ${processed}/${total}`);
        }

        if (this.options.yieldEvery > 0 && (i + 1) % this.options.yieldEvery === 0) {
          await yieldToEventLoop();
        }

        yield cleanMessage;
      } catch (error) {
        console.error('解析消息失败:', error, message.msgId);
        yield this.createErrorMessage(message, error);
      }
    }
  }

  /**
   * 解析单条消息（公开）
   */
  async parseSingleMessage(message: RawMessage): Promise<CleanMessage> {
    return this.parseMessage(message);
  }

  /**
   * 解析单条消息（内部）
   */
  private async parseMessage(message: RawMessage): Promise<CleanMessage> {
    const tsMs = millisFromUnixSeconds(message.msgTime as any);
    const timestamp = tsMs > 0 ? tsMs : Date.now();

    // 提取所有可用的名称信息
    const groupCard = message.sendMemberName && message.sendMemberName.trim() || undefined;
    const remark = message.sendRemarkName && message.sendRemarkName.trim() || undefined;
    const nickname = message.sendNickName && message.sendNickName.trim() || undefined;
    
    // 群名片 > 好友备注 > 昵称 > QQ号 > UID
    const senderName = (
      groupCard ||
      remark ||
      nickname ||
      (message.senderUin && String(message.senderUin)) ||
      (message.senderUid && String(message.senderUid)) ||
      '未知用户'
    ).trim();

    const content = await this.parseMessageContent(message);

    const cleanMessage: CleanMessage = {
      id: message.msgId,
      seq: message.msgSeq,
      timestamp,
      // RFC3339（UTC）
      time: rfc3339FromMillis(timestamp),
      sender: {
        uid: message.senderUid || '未知',
        uin: message.senderUin,
        name: senderName,
        nickname: nickname,
        groupCard: groupCard,
        remark: remark
      },
      type: this.getMessageTypeString(message.msgType),
      content,
      recalled: message.recallTime !== '0',
      system: this.isSystemMessage(message)
    };

    return cleanMessage;
  }

  private getMessageTypeString(msgType: NTMsgType | number): string {
    // NTMsgType 枚举值（兼容两种定义方式）
    // 原始枚举: KMSGTYPENULL=1, KMSGTYPEMIX=2, KMSGTYPEFILE=3, KMSGTYPESTRUCT=4, KMSGTYPEGRAYTIPS=5, KMSGTYPEPTT=6, KMSGTYPEVIDEO=7, KMSGTYPEMULTIMSGFORWARD=8, KMSGTYPEREPLY=9, KMSGTYPEARKSTRUCT=11
    // 简化版: Text=1, Picture=2, File=3, Video=4, Voice=5, Reply=7
    const typeNum = typeof msgType === 'number' ? msgType : Number(msgType);
    switch (typeNum) {
      case 1: // KMSGTYPENULL / Text
      case 2: // KMSGTYPEMIX / Picture (混合消息，通常包含文本)
        return 'text';
      case 3: // KMSGTYPEFILE / File
        return 'file';
      case 4: // KMSGTYPESTRUCT / Video (简化版)
      case 7: // KMSGTYPEVIDEO (原始枚举)
        return 'video';
      case 5: // KMSGTYPEGRAYTIPS / Voice (简化版)
        return 'system';
      case 6: // KMSGTYPEPTT
        return 'audio';
      case 8: // KMSGTYPEMULTIMSGFORWARD
        return 'forward';
      case 9: // KMSGTYPEREPLY
        return 'reply';
      case 11: // KMSGTYPEARKSTRUCT
        return 'json';
      default:
        return `type_${typeNum}`;
    }
  }

  /**
   * 单趟解析消息内容
   */
  private async parseMessageContent(message: RawMessage): Promise<MessageContent> {
    const elements = message.elements || [];
    const parsedElements: MessageElementData[] = new Array(elements.length);
    const resources: ResourceData[] = [];
    const mentions: MentionData[] = [];

    const textB = new ChunkedBuilder();
    const htmlB = new ChunkedBuilder();
    const htmlEnabled = this.options.html !== 'none';

    let count = 0;
    for (let i = 0; i < elements.length; i++) {
      const element = elements[i]!;
      const parsed = await this.parseElement(element, message);
      if (!parsed) continue;
      parsedElements[count++] = parsed;

      // 资源抽取
      const resource = this.extractResource(parsed);
      if (resource) resources.push(resource);

      // 提取 @ 提及信息
      if (parsed.type === 'at') {
        mentions.push({
          uid: parsed.data.uid || 'unknown',
          uin: parsed.data.uin,
          name: parsed.data.name || '某人',
          type: parsed.data.uid === 'all' ? 'all' : 'user'
        });
      }

      // 文本/HTML
      const { text, html } = this.elementToText(parsed, htmlEnabled);
      textB.push(text);
      if (htmlEnabled) htmlB.push(html);
    }
    // 压缩 parsedElements 实际长度
    parsedElements.length = count;

    return {
      text: textB.toString().trim(),
      html: htmlEnabled ? htmlB.toString().trim() : '',
      elements: parsedElements,
      resources,
      mentions
    };
  }

  /**
   * 元素解析（尽量同步，无额外中间对象）
   */
  private async parseElement(element: MessageElement, message: RawMessage): Promise<MessageElementData | null> {
    // 文本 / @ 提及
    if (element.textElement) {
      const te = element.textElement;
      // atType: 0=普通文本, 1=@全体成员, 2=@某人
      if (te.atType === 1) {
        return {
          type: 'at',
          data: {
            uid: 'all',
            uin: '0',
            name: '全体成员',
            atType: 1
          }
        };
      } else if (te.atType === 2) {
        return {
          type: 'at',
          data: {
            uid: te.atNtUid || te.atUid || 'unknown',
            uin: te.atUid || '0',
            name: (te.content || '').replace(/^@/, ''),
            atType: 2
          }
        };
      }
      // 普通文本
      return {
        type: 'text',
        data: { text: te.content || '' }
      };
    }

    // 表情
    if (element.faceElement) {
      const faceId = element.faceElement.faceIndex?.toString() || '';
      const faceName = element.faceElement.faceText || this.faceMap.get(faceId) || `表情${faceId}`;
      return {
        type: 'face',
        data: {
          id: faceId,
          name: faceName
        }
      };
    }

    // 商城表情
    if (element.marketFaceElement) {
      const emojiId = element.marketFaceElement.emojiId || '';
      const key = element.marketFaceElement.key || '';
      const url = emojiId ? this.generateMarketFaceUrl(emojiId) : '';

      return {
        type: 'market_face',
        data: {
          name: element.marketFaceElement.faceName || '商城表情',
          tabName: (element.marketFaceElement as any).tabName || '',
          key,
          emojiId,
          emojiPackageId: element.marketFaceElement.emojiPackageId,
          url
        }
      };
    }

    // 图片
    if (element.picElement) {
      return {
        type: 'image',
        data: {
          filename: element.picElement.fileName || '图片',
          size: this.parseSizeString(element.picElement.fileSize),
          width: element.picElement.picWidth,
          height: element.picElement.picHeight,
          md5: element.picElement.md5HexStr,
          url: element.picElement.originImageUrl || ''
        }
      };
    }

    // 文件
    if (element.fileElement) {
      return {
        type: 'file',
        data: {
          filename: element.fileElement.fileName || '文件',
          size: this.parseSizeString(element.fileElement.fileSize),
          md5: element.fileElement.fileMd5
        }
      };
    }

    // 视频
    if (element.videoElement) {
      return {
        type: 'video',
        data: {
          filename: element.videoElement.fileName || '视频',
          size: this.parseSizeString(element.videoElement.fileSize),
          duration: (element.videoElement as any).duration || 0,
          thumbSize: this.parseSizeString(element.videoElement.thumbSize)
        }
      };
    }

    // 语音
    if (element.pttElement) {
      return {
        type: 'audio',
        data: {
          filename: element.pttElement.fileName || '语音',
          size: this.parseSizeString(element.pttElement.fileSize),
          duration: element.pttElement.duration || 0
        }
      };
    }

    // 回复
    if (element.replyElement) {
      const replyData = this.extractReplyContent(element.replyElement, message);
      return {
        type: 'reply',
        data: {
          messageId: replyData.messageId,
          referencedMessageId: replyData.referencedMessageId,  // 被引用消息的实际messageId
          senderUin: replyData.senderUin,
          senderName: replyData.senderName,
          content: replyData.content,
          timestamp: replyData.timestamp
        }
      };
    }

    // 转发
    if (element.multiForwardMsgElement) {
      return {
        type: 'forward',
        data: {
          title: '转发消息',
          resId: element.multiForwardMsgElement.resId || '',
          summary: element.multiForwardMsgElement.xmlContent || ''
        }
      };
    }

    // JSON 卡片
    if (element.arkElement) {
      const jsonContent = element.arkElement.bytesData || '{}';
      const parsedJson = this.parseJsonContent(jsonContent);
      return {
        type: 'json',
        data: {
          content: jsonContent,
          title: parsedJson.title || 'JSON消息',
          description: parsedJson.description,
          url: parsedJson.url,
          preview: parsedJson.preview,
          appName: parsedJson.appName,
          summary: parsedJson.title || parsedJson.description || 'JSON消息'
        }
      };
    }

    // 位置
    if (element.shareLocationElement) {
      return {
        type: 'location',
        data: {
          title: '位置消息',
          summary: '分享了位置'
        }
      };
    }

    // 小灰条（系统提示）
    if (element.grayTipElement) {
      return this.parseGrayTipElement(element.grayTipElement);
    }

    // 未知类型
    console.warn(`[SimpleMessageParser] 未知消息元素类型: ${element.elementType}`, element);
    return {
      type: 'system',
      data: {
        elementType: element.elementType,
        summary: this.getSystemMessageSummary(element),
        text: this.getSystemMessageSummary(element)
      }
    };
  }

  private extractResource(element: MessageElementData): ResourceData | null {
    if (!['image', 'file', 'video', 'audio'].includes(element.type)) return null;
    const d = element.data || {};
    return {
      type: element.type,
      filename: d.filename || '未知',
      size: d.size || 0,
      url: d.url,
      localPath: d.localPath, // 包含本地路径信息
      width: d.width,
      height: d.height,
      duration: d.duration
    };
  }

  private elementToText(element: MessageElementData, htmlEnabled: boolean): { text: string; html: string } {
    switch (element.type) {
      case 'text': {
        const t = element.data.text || '';
        return { text: t, html: htmlEnabled ? escapeHtmlFast(t) : '' };
      }
      case 'face': {
        const t = `[表情${element.data.id}]`;
        return { text: t, html: htmlEnabled ? t : '' };
      }
      case 'market_face': {
        const t = `[${element.data.name || '表情'}]`;
        return { text: t, html: htmlEnabled ? t : '' };
      }
      case 'image': {
        const t = `[图片:${element.data.filename}]`;
        return { text: t, html: htmlEnabled ? `<img alt="${escapeHtmlFast(element.data.filename)}" class="image">` : '' };
      }
      case 'file': {
        const t = `[文件:${element.data.filename}]`;
        return { text: t, html: htmlEnabled ? `<span class="file">${escapeHtmlFast(t)}</span>` : '' };
      }
      case 'video': {
        const t = `[视频:${element.data.filename}]`;
        return { text: t, html: htmlEnabled ? `<span class="video">${escapeHtmlFast(t)}</span>` : '' };
      }
      case 'audio': {
        const t = `[语音:${element.data.duration}秒]`;
        return { text: t, html: htmlEnabled ? `<span class="audio">${escapeHtmlFast(t)}</span>` : '' };
      }
      case 'at': {
        const name = element.data.name || '某人';
        const t = `@${name}`;
        const uid = element.data.uid || 'unknown';
        if (uid === 'all') {
          return { text: t, html: htmlEnabled ? `<span class="mention mention-all">${escapeHtmlFast(t)}</span>` : '' };
        }
        return { text: t, html: htmlEnabled ? `<span class="mention" data-uid="${uid}">${escapeHtmlFast(t)}</span>` : '' };
      }
      case 'reply': {
        const t = `[回复消息]`;
        return { text: t, html: htmlEnabled ? `<div class="reply">${t}</div>` : '' };
      }
      case 'forward': {
        const t = `[转发消息]`;
        return { text: t, html: htmlEnabled ? `<div class="forward">${t}</div>` : '' };
      }
      case 'location': {
        const t = `[位置消息]`;
        return { text: t, html: htmlEnabled ? `<div class="location">${t}</div>` : '' };
      }
      case 'json': {
        const t = `[JSON消息]`;
        return { text: t, html: htmlEnabled ? `<div class="json">${t}</div>` : '' };
      }
      case 'system': {
        const t = element.data.text || element.data.summary || '系统消息';
        return { text: t, html: htmlEnabled ? `<div class="system">${escapeHtmlFast(t)}</div>` : '' };
      }
      default: {
        const rawText = element.data.text || element.data.summary || element.data.content || '';
        return { text: rawText, html: htmlEnabled ? (rawText ? `<span>${escapeHtmlFast(rawText)}</span>` : '') : '' };
      }
    }
  }

  private parseSizeString(size: string | number | undefined): number {
    if (typeof size === 'number') return size;
    if (typeof size === 'string') {
      const n = parseInt(size, 10);
      return Number.isFinite(n) ? n : 0;
    }
    return 0;
  }

  private isSystemMessage(message: RawMessage): boolean {
    return message.msgType === NTMsgType.KMSGTYPEGRAYTIPS;
  }

  private createErrorMessage(message: RawMessage, error: any): CleanMessage {
    const tsMs = millisFromUnixSeconds(message.msgTime as any);
    const timestamp = tsMs > 0 ? tsMs : Date.now();

    // 提取所有可用的名称信息
    const groupCard = message.sendMemberName && message.sendMemberName.trim() || undefined;
    const remark = message.sendRemarkName && message.sendRemarkName.trim() || undefined;
    const nickname = message.sendNickName && message.sendNickName.trim() || undefined;
    
    // 群名片 > 好友备注 > 昵称 > QQ号 > UID
    const senderName = (
      groupCard ||
      remark ||
      nickname ||
      (message.senderUin && String(message.senderUin)) ||
      (message.senderUid && String(message.senderUid)) ||
      '未知用户'
    ).trim();

    const errMsg = (error && (error.message || error.toString?.())) || 'Unknown';
    return {
      id: message.msgId,
      seq: message.msgSeq,
      timestamp,
      time: rfc3339FromMillis(timestamp),
      sender: {
        uid: message.senderUid || '未知',
        uin: message.senderUin,
        name: senderName,
        nickname: nickname,
        groupCard: groupCard,
        remark: remark
      },
      type: 'error',
      content: {
        text: `[解析失败: ${errMsg}]`,
        html: `<span class="error">[解析失败: ${escapeHtmlFast(errMsg)}]</span>`,
        elements: [],
        resources: [],
        mentions: []
      },
      recalled: false,
      system: false
    };
  }

  /** @deprecated 使用 isPureMediaMessage 代替 */
  isPureImageMessage(message: CleanMessage): boolean {
    return this.isPureMediaMessage(message);
  }

  isPureMediaMessage(message: CleanMessage): boolean {
    const els = message.content.elements || [];
    
    // 必须包含媒体元素
    const hasMedia = els.some((e) => ['image', 'video', 'audio', 'file', 'face'].includes(e.type));
    if (!hasMedia) return false;

    // 最可靠的判断：检查实际的文本内容
    // content.text已经是所有元素解析后的纯文本内容
    const actualText = (message.content.text || '').trim();
    
    // 如果有实际的文本内容，则不是纯媒体消息
    if (actualText.length > 0) {
      // 进一步检查是否只包含CQ码
      const withoutCQ = actualText.replace(/\[CQ:[^\]]+\]/g, '').trim();
      if (withoutCQ.length > 0) {
        return false; // 有实际文字内容，不过滤
      }
    }

    // 没有实际文字内容，判定为纯媒体消息
    return true;
  }

  private hasRealTextContent(message: CleanMessage): boolean {
    const textEls = message.content.elements.filter((e) => e.type === 'text');
    for (let i = 0; i < textEls.length; i++) {
      const t = textEls[i]!.data?.text || '';
      if (t.trim().length > 0 && !this.isOnlyCQCode(t)) return true;
    }
    return false;
  }

  private isOnlyCQCode(text: string): boolean {
    if (!text || text.trim().length === 0) return true;
    // 移除所有 CQ 码，检测是否还有实际文字
    const without = text.replace(/\[CQ:[^\]]+\]/g, '').trim();
    return without.length === 0;
  }

  filterMessages(messages: CleanMessage[], includePureImages: boolean = true): CleanMessage[] {
    if (includePureImages) return messages;
    return messages.filter((m) => !this.isPureMediaMessage(m));
  }

  calculateStatistics(messages: CleanMessage[]): MessageStatistics {
    const stats: MessageStatistics = {
      total: messages.length,
      byType: {},
      bySender: {},
      resources: {
        total: 0,
        byType: {},
        totalSize: 0
      },
      timeRange: {
        start: '',
        end: '',
        durationDays: 0
      }
    };

    if (messages.length === 0) return stats;

    // 时间范围
    const ts = messages.map((m) => m.timestamp).filter((t) => t > 0).sort((a, b) => a - b);
    if (ts.length > 0) {
      const start = new Date(ts[0]!);
      const end = new Date(ts[ts.length - 1]!);
      stats.timeRange = {
        start: start.toISOString(),
        end: end.toISOString(),
        durationDays: Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24))
      };
    }

    // 统计
    for (let i = 0; i < messages.length; i++) {
      const m = messages[i]!;
      if (!m || !m.content) continue;

      // 类型
      stats.byType[m.type] = (stats.byType[m.type] || 0) + 1;

      // 发送者
      const senderKey = m.sender?.name || m.sender?.uid || '未知用户';
      if (!stats.bySender[senderKey]) {
        stats.bySender[senderKey] = {
          uid: m.sender?.uid || 'unknown',
          count: 0
        };
      }
      stats.bySender[senderKey]!.count++;

      // 资源
      const res = m.content.resources || [];
      for (let j = 0; j < res.length; j++) {
        const r = res[j]!;
        stats.resources.total++;
        const t = r.type || 'unknown';
        stats.resources.byType[t] = (stats.resources.byType[t] || 0) + 1;
        stats.resources.totalSize += r.size || 0;
      }
    }

    return stats;
  }

  async updateResourcePaths(messages: CleanMessage[], resourceMap: Map<string, any[]>): Promise<void> {
    for (let mi = 0; mi < messages.length; mi++) {
      const message = messages[mi]!;
      const resources = resourceMap.get(message.id);
      if (resources && resources.length > 0) {
        this.updateSingleMessageResourcePaths(message, resources);
      }
    }
  }

  /**
   * 更新单条消息的资源路径（私有方法，供批量和流式使用）
   */
  private updateSingleMessageResourcePaths(message: CleanMessage, resources: any[]): void {
    console.log(`[SimpleMessageParser] 更新消息 ${message.id} 的资源路径，资源数量: ${resources.length}`);
    
    // 更新 message.content.resources
    const resArr = message.content.resources;
    const n = Math.min(resArr.length, resources.length);
    for (let i = 0; i < n; i++) {
      const info = resources[i];
      if (info && info.localPath) {
        const fileName = path.basename(info.localPath);
        const typeDir = info.type + 's';  // image -> images, video -> videos
        // 修复 Issue #30: 保留类型子目录，让导出器能正确找到文件
        resArr[i]!.localPath = `${typeDir}/${fileName}`;
        resArr[i]!.url = `resources/${typeDir}/${fileName}`;
        resArr[i]!.type = info.type;
        console.log(`[SimpleMessageParser] 资源 ${i}: type=${info.type}, path=${typeDir}/${fileName}`);
      } else {
        console.warn(`[SimpleMessageParser] 资源 ${i} 无localPath:`, info);
      }
    }

    // 更新 elements 中的 URL
    // 按类型和顺序匹配，而不是按文件名（因为下载后文件名可能改变）
    const els = message.content.elements;
    let resourceIndex = 0;
    
    console.log(`[SimpleMessageParser] 消息有 ${els.length} 个元素`);
    
    for (let i = 0; i < els.length; i++) {
      const el = els[i]!;
      if (!el.data || typeof el.data !== 'object') continue;
      
      // 只处理媒体类型元素
      if (el.type === 'image' || el.type === 'video' || el.type === 'audio' || el.type === 'file') {
        console.log(`[SimpleMessageParser] 元素 ${i}: type=${el.type}, filename=${(el.data as any).filename}`);
        
        // 按顺序匹配对应类型的资源
        const matchingResource = resources.find((r, idx) => 
          idx >= resourceIndex && r.type === el.type
        );
        
        if (matchingResource && matchingResource.localPath) {
          const fileName = path.basename(matchingResource.localPath);
          const typeDir = matchingResource.type + 's';
          // 修复 Issue #30: 保留类型子目录，让导出器能正确找到文件
          (el.data as any).localPath = `${typeDir}/${fileName}`;
          (el.data as any).url = `resources/${typeDir}/${fileName}`;
          
          console.log(`[SimpleMessageParser] ✓ 元素 ${i} 匹配到资源: ${typeDir}/${fileName}`);
          
          // 更新资源索引
          resourceIndex = resources.indexOf(matchingResource) + 1;
        } else {
          console.warn(`[SimpleMessageParser] ✗ 元素 ${i} (type=${el.type}) 未找到匹配资源`);
        }
      }
    }
  }

  private parseJsonContent(jsonString: string): any {
    try {
      const json = fastJsonParse(jsonString);
      const result: any = {};

      // 标题
      if (json.prompt) result.title = json.prompt;
      else if (json.meta?.detail_1?.title) result.title = json.meta.detail_1.title;
      else if (json.meta?.news?.title) result.title = json.meta.news.title;

      // 描述
      if (json.meta?.detail_1?.desc) result.description = json.meta.detail_1.desc;
      else if (json.meta?.news?.desc) result.description = json.meta.news.desc;

      // URL
      if (json.meta?.detail_1?.qqdocurl) result.url = json.meta.detail_1.qqdocurl;
      else if (json.meta?.detail_1?.url) result.url = json.meta.detail_1.url;
      else if (json.meta?.news?.jumpUrl) result.url = json.meta.news.jumpUrl;

      // 预览图
      if (json.meta?.detail_1?.preview) result.preview = json.meta.detail_1.preview;
      else if (json.meta?.news?.preview) result.preview = json.meta.news.preview;

      // 应用名称
      if (json.meta?.detail_1?.title && json.app) result.appName = json.meta.detail_1.title;
      else if (json.app === 'com.tencent.miniapp_01') result.appName = '小程序';

      return result;
    } catch (error) {
      console.warn('[SimpleMessageParser] JSON解析失败:', error);
      return {};
    }
  }

  private extractReplyContent(replyElement: any, message: RawMessage): any {
    // 使用 replayMsgId 作为被引用消息的真实ID（但要排除 "0" 的情况）
    const replayMsgId = replyElement.replayMsgId;
    let referencedMessageId: string | undefined = (replayMsgId && replayMsgId !== '0') ? replayMsgId : undefined;
    
    // sourceMsgIdInRecords 用于内部查找（在 records 数组中）
    const sourceMsgId = replyElement.sourceMsgIdInRecords;
    let referencedMessage: RawMessage | undefined;
    let source: 'messageMap' | 'records' | 'sourceMsgText' | 'sourceMsgTextElems' | 'referencedMsg' | 'seq' | 'none' = 'none';
    
    // 1. 尝试用 replayMsgId 从全局消息映射中查找（replayMsgId才是真实被引用消息ID）
    if (referencedMessageId && this.messageMap.has(referencedMessageId)) {
      referencedMessage = this.messageMap.get(referencedMessageId);
      source = 'messageMap';
    }
    
    // 2. 如果全局映射中找不到，再从当前消息的 records 数组中查找
    if (!referencedMessage && sourceMsgId && sourceMsgId !== '0' && message.records && message.records.length > 0) {
      referencedMessage = message.records.find((record: RawMessage) => record.msgId === sourceMsgId);
      if (referencedMessage) {
        referencedMessageId = referencedMessage.msgId;
        source = 'records';
      }
    }
    
    // 3. 如果还是找不到，尝试用 replayMsgSeq 匹配 msgSeq
    if (!referencedMessage && replyElement.replayMsgSeq) {
      for (const [msgId, msg] of this.messageMap.entries()) {
        if (msg.msgSeq === replyElement.replayMsgSeq) {
          referencedMessage = msg;
          referencedMessageId = msg.msgId;
          source = 'seq';
          break;
        }
      }
    }
    
    // 4. 如果还找不到，尝试用 replyMsgClientSeq 匹配
    if (!referencedMessage && replyElement.replyMsgClientSeq) {
      for (const [msgId, msg] of this.messageMap.entries()) {
        if (msg.clientSeq === replyElement.replyMsgClientSeq) {
          referencedMessage = msg;
          referencedMessageId = msg.msgId;
          source = 'seq';
          break;
        }
      }
    }

    const result = {
      messageId: sourceMsgId || replyElement.replayMsgId || replyElement.replayMsgSeq || '0',
      referencedMessageId: referencedMessageId || undefined,  // 确保不会是 "0"
      senderUin: replyElement.senderUin || '',
      senderName: replyElement.senderUidStr || '',
      content: '原消息',
      timestamp: 0
    };

    // 如果找到了被引用的消息，从中提取内容
    if (referencedMessage) {
      // 保持messageId为sourceMsgId，referencedMessageId已经在前面设置
      result.senderUin = referencedMessage.senderUin;
      result.senderName = referencedMessage.sendNickName || referencedMessage.senderUin;
      
      // 提取被引用消息的文本内容
      if (referencedMessage.elements && referencedMessage.elements.length > 0) {
        const parts = [];
        for (const element of referencedMessage.elements) {
          if (element.textElement?.content) {
            parts.push(element.textElement.content);
          } else if (element.picElement) {
            parts.push('[图片]');
          } else if (element.videoElement) {
            parts.push('[视频]');
          } else if (element.pttElement) {
            parts.push('[语音]');
          } else if (element.fileElement) {
            parts.push('[文件]');
          } else if (element.faceElement) {
            parts.push(`[表情${element.faceElement.faceIndex}]`);
          } else if (element.marketFaceElement) {
            const faceName = element.marketFaceElement.faceName || '超级表情';
            parts.push(`[${faceName}]`);
          }
        }
        if (parts.length > 0) {
          result.content = parts.join('');
        }
      }
      
      if (referencedMessage.msgTime) {
        result.timestamp = parseInt(referencedMessage.msgTime) || 0;
      }
    } else {
      // 如果没有找到被引用的消息，尝试从 replyElement 中提取内容（备用方案）
    if (replyElement.sourceMsgText) {
      result.content = replyElement.sourceMsgText;
        source = 'sourceMsgText';
    } else if (replyElement.sourceMsgTextElems && replyElement.sourceMsgTextElems.length > 0) {
      const parts = [];
      for (let i = 0; i < replyElement.sourceMsgTextElems.length; i++) {
        const e = replyElement.sourceMsgTextElems[i];
        if (e?.textElement?.content) parts.push(e.textElement.content);
      }
        if (parts.length > 0) {
          result.content = parts.join('');
          source = 'sourceMsgTextElems';
        }
    } else if (replyElement.referencedMsg && replyElement.referencedMsg.msgBody) {
      result.content = replyElement.referencedMsg.msgBody;
        source = 'referencedMsg';
      }
    }

    if (replyElement.senderNick) result.senderName = replyElement.senderNick;
    if (replyElement.replayMsgTime) result.timestamp = replyElement.replayMsgTime;

    return result;
  }

  private generateMarketFaceUrl(emojiId: string): string {
    if (emojiId.length < 2) return '';
    const prefix = emojiId.substring(0, 2);
    return `https://gxh.vip.qq.com/club/item/parcel/item/${prefix}/${emojiId}/raw300.gif`;
  }

  private parseGrayTipElement(grayTip: any): MessageElementData {
    const subType = grayTip.subElementType;
    let summary = '系统消息';
    let text = '';

    try {
      if (subType === 1 && grayTip.revokeElement) {
        const revokeInfo = grayTip.revokeElement;
        const operatorName = revokeInfo.operatorName || '用户';
        const originalSenderName = revokeInfo.origMsgSenderName || '用户';

        if (revokeInfo.isSelfOperate) {
          text = `${operatorName} 撤回了一条消息`;
        } else if (operatorName === originalSenderName) {
          text = `${operatorName} 撤回了一条消息`;
        } else {
          text = `${operatorName} 撤回了 ${originalSenderName} 的消息`;
        }
        if (revokeInfo.wording) text = revokeInfo.wording;
        summary = text;
      } else if (subType === 4 && grayTip.groupElement) {
        text = grayTip.groupElement.content || '群聊更新';
        summary = text;
      } else if (subType === 17 && grayTip.jsonGrayTipElement) {
        const jsonContent = grayTip.jsonGrayTipElement.jsonStr || '{}';
        try {
          const parsed = fastJsonParse(jsonContent);
          text = parsed.prompt || parsed.content || '系统提示';
        } catch {
          text = '系统提示';
        }
        summary = text;
      } else if (grayTip.aioOpGrayTipElement) {
        const aioOp = grayTip.aioOpGrayTipElement;
        if (aioOp.operateType === 1) {
          const fromUser = aioOp.peerName || '用户';
          const toUser = aioOp.targetName || '用户';
          text = `${fromUser} 拍了拍 ${toUser}`;
          if (aioOp.suffix) text += ` ${aioOp.suffix}`;
        } else {
          text = aioOp.content || '互动消息';
        }
        summary = text;
      } else {
        const content = grayTip.content || grayTip.text || grayTip.wording;
        if (content) {
          text = content;
          summary = content;
        } else {
          text = `系统提示 (类型: ${subType})`;
          summary = text;
        }
      }
    } catch (error) {
      console.warn('[SimpleMessageParser] 解析灰条消息失败:', error, grayTip);
      text = '系统消息';
      summary = text;
    }

    return {
      type: 'system',
      data: {
        subType,
        text,
        summary,
        originalData: grayTip
      }
    };
  }

  private getSystemMessageSummary(element: any): string {
    const t = element.elementType;
    switch (t) {
      case 8:
        return '系统提示消息';
      case 9:
        return '文件传输消息';
      case 10:
        return '语音通话消息';
      case 11:
        return '视频通话消息';
      case 12:
        return '红包消息';
      case 13:
        return '转账消息';
      default:
        return `系统消息 (类型: ${t})`;
    }
  }

  /**
   * 初始化QQ表情映射表
   */
  private initializeFaceMap(): void {
    try {
      // ES模块中获取当前文件目录
      const __filename = fileURLToPath(import.meta.url);
      const __dirname = path.dirname(__filename);
      
      const faceConfigPath = path.join(__dirname, 'face_config.json');
      if (fs.existsSync(faceConfigPath)) {
        const faceConfig = JSON.parse(fs.readFileSync(faceConfigPath, 'utf-8'));
        if (faceConfig.sysface && Array.isArray(faceConfig.sysface)) {
          for (const face of faceConfig.sysface) {
            if (face.QSid && face.QDes) {
              this.faceMap.set(face.QSid.toString(), face.QDes);
            }
          }
        }
      }
    } catch (error) {
      // 加载失败时静默失败，使用默认的"表情{ID}"格式
      console.warn('[SimpleMessageParser] 加载表情映射失败:', error);
    }
  }
}
