/**
 * aam.ts — Adaptive Associative Memory (AAM)
 *
 * cc-soul 原创核心算法。纯 TypeScript，零依赖，不需要任何 embedding 模型。
 * 在个人记忆召回场景下达到甚至超越向量搜索的效果。
 *
 * 三层架构：
 *   Layer 1: 词关联网络 — 从用户数据自动学习语义关系
 *   Layer 2: 多键非线性召回 — Noisy-OR 多维度组合
 *   Layer 3: 赫布学习 — 从反馈中自动强化有效关联
 *
 * 认知科学基础：
 *   - Hebbian Learning: "neurons that fire together wire together"
 *   - Associative Memory: 记忆通过多维度关联访问，不是线性搜索
 *   - Noisy-OR: 多个独立线索的非线性组合
 */

import type { Memory } from './types.ts'
import { trigrams, trigramSimilarity, tokenize as _utilTokenize } from './memory-utils.ts'
import { DATA_DIR, loadJson, debouncedSave } from './persistence.ts'
import { resolve } from 'path'
import { existsSync, readFileSync } from 'fs'

// ═══════════════════════════════════════════════════════════════════════════════
// LAYER 1: WORD ASSOCIATION NETWORK
// 从用户记忆中自动学习词语之间的语义关联
// "吃"和"减肥"共现 3 次 → 关联强度 3.2
// ═══════════════════════════════════════════════════════════════════════════════

const ASSOC_PATH = resolve(DATA_DIR, 'aam_associations.json') // legacy path for migration

interface AssociationNetwork {
  // word → { related_word → co-occurrence count }
  cooccur: Record<string, Record<string, number>>
  // word → total document frequency
  df: Record<string, number>
  totalDocs: number
  lastRebuild: number
}

// ── Language detection utility ──
function detectLanguage(text: string): string {
  const cjk = (text.match(/[\u4e00-\u9fff]/g) || []).length
  const latin = (text.match(/[a-zA-Z]/g) || []).length
  if (cjk > latin * 0.3) return 'zh'
  return 'en'
}

// ── Per-language network storage ──
const networks = new Map<string, AssociationNetwork>()

function cooccurPath(lang: string): string {
  return resolve(DATA_DIR, `aam_cooccur_${lang}.json`)
}

function synonymsPath(lang: string): string {
  return resolve(DATA_DIR, `aam_synonyms_${lang}.json`)
}

function getNetwork(lang: string): AssociationNetwork {
  if (!networks.has(lang)) {
    const p = cooccurPath(lang)
    networks.set(lang, loadJson<AssociationNetwork>(p, {
      cooccur: {}, df: {}, totalDocs: 0, lastRebuild: 0,
    }))
    // 非中英文的新语言：自动触发 LLM 种子生成（异步，不阻塞）
    if (lang !== 'zh' && lang !== 'en') {
      const sp = synonymsPath(lang)
      if (!existsSync(sp)) {
        generateSynonymSeed(lang).catch(() => {})
      }
    }
  }
  return networks.get(lang)!
}

// ── Backward-compatible migration: old single file → per-language ──
function migrateOldAssociations(): void {
  if (!existsSync(ASSOC_PATH)) return
  const zhPath = cooccurPath('zh')
  if (existsSync(zhPath)) return // already migrated
  try {
    const old = loadJson<AssociationNetwork>(ASSOC_PATH, { cooccur: {}, df: {}, totalDocs: 0, lastRebuild: 0 })
    if (old.totalDocs > 0) {
      networks.set('zh', old)
      debouncedSave(zhPath, old)
      console.log(`[cc-soul][aam] migrated legacy aam_associations.json → aam_cooccur_zh.json (${old.totalDocs} docs)`)
    }
  } catch {}
}
migrateOldAssociations()

// Convenience: default network accessor for functions that need backward compat
// (internal use only — gradually being replaced by explicit lang parameter)
let _currentLang = 'zh'
function network(): AssociationNetwork { return getNetwork(_currentLang) }

// ═══════════════════════════════════════════════════════════════════════════════
// PAM: Temporal Directed Co-occurrence（有向时序共现）
// arXiv 2602.11322 启发：用户先说 A 再说 B → A→B 有向链接
// 下次说 A 时，含 B 的记忆获得激活 boost
// ═══════════════════════════════════════════════════════════════════════════════

const TEMPORAL_PATH = resolve(DATA_DIR, 'aam_temporal.json')
const TEMPORAL_MAX_ENTRIES = 1000

interface TemporalNetwork {
  // word → { nextWord → count }（有向：A→B ≠ B→A）
  directed: Record<string, Record<string, number>>
}

let _temporalNet: TemporalNetwork = loadJson<TemporalNetwork>(TEMPORAL_PATH, { directed: {} })

// 上一轮用户消息的关键词（用于跨消息有向链接）
let _prevMessageWords: string[] = []

/**
 * 学习有向时序共现：上一条消息的词 → 当前消息的词
 * 由外部在每条用户消息时调用（与 learnAssociation 并行）
 */
// 英文停用词（temporal 专用，不从 activation-field.ts 导入避免循环依赖）
const _TEMPORAL_EN_STOPS = new Set([
  'the','is','are','was','were','be','been','being','have','has','had',
  'do','does','did','will','would','could','should','can','may','might',
  'what','when','where','which','who','whom','how','why',
  'that','this','these','those','it','its',
  'and','but','or','not','no','yes',
  'to','of','in','on','at','by','for','with','from','about',
  'very','just','also','too','so','then','than','now',
])

export function learnTemporalLink(currentContent: string): void {
  const words = filterStopWords(tokenize(currentContent))
  const unique = [...new Set(words)].filter(w => !isJunkToken(w) && !_TEMPORAL_EN_STOPS.has(w.toLowerCase()))

  // 如果有上一轮消息，建立 prev → current 有向链接
  if (_prevMessageWords.length > 0 && unique.length > 0) {
    for (const prev of _prevMessageWords) {
      if (!_temporalNet.directed[prev]) _temporalNet.directed[prev] = {}
      for (const curr of unique) {
        if (prev === curr) continue  // 跳过自环
        _temporalNet.directed[prev][curr] = (_temporalNet.directed[prev][curr] || 0) + 1
      }
    }

    // LRU 驱逐：超过上限时删除最低频的词条
    const totalEntries = Object.keys(_temporalNet.directed).length
    if (totalEntries > TEMPORAL_MAX_ENTRIES) {
      const entries = Object.entries(_temporalNet.directed)
        .map(([w, succs]) => ({ word: w, total: Object.values(succs).reduce((a, b) => a + b, 0) }))
        .sort((a, b) => a.total - b.total)
      const toRemove = totalEntries - TEMPORAL_MAX_ENTRIES + 100  // 批量删 100 个余量
      for (let i = 0; i < toRemove && i < entries.length; i++) {
        delete _temporalNet.directed[entries[i].word]
      }
    }

    // 每 20 轮持久化
    if (network().totalDocs % 20 === 0) {
      debouncedSave(TEMPORAL_PATH, _temporalNet)
    }
  }

  // 更新上一轮词
  _prevMessageWords = unique.slice(0, 10)  // cap 保留最多 10 词
}

/**
 * 查询某词的时序后继词（按频次降序）
 * 供 activation-field.ts Signal 7 使用
 */
export function getTemporalSuccessors(word: string, topK = 5): { word: string; count: number }[] {
  const succs = _temporalNet.directed[word]
  if (!succs) return []
  return Object.entries(succs)
    .sort(([, a], [, b]) => b - a)
    .slice(0, topK)
    .map(([w, c]) => ({ word: w, count: c }))
}

/** 时序共现衰减（由 decayCooccurrence 顺带调用） */
export function decayTemporalLinks(): void {
  let pruned = 0
  for (const [w, succs] of Object.entries(_temporalNet.directed)) {
    for (const [w2, count] of Object.entries(succs)) {
      const newCount = count * 0.98
      if (newCount < 0.5) { delete succs[w2]; pruned++ }
      else succs[w2] = newCount
    }
    if (Object.keys(succs).length === 0) delete _temporalNet.directed[w]
  }
  if (pruned > 0) debouncedSave(TEMPORAL_PATH, _temporalNet)
}

// ── Built-in synonyms for cold start (before enough user data accumulates) ──
const SYNONYMS_PATH = resolve(DATA_DIR, 'aam_synonyms.json')
const _defaultSynonyms: Record<string, string[]> = {
  '开心': ['高兴', '快乐', '愉快', '心情好', 'happy'],
  '高兴': ['开心', '快乐', '愉快'],
  '难过': ['伤心', '难受', '不开心', '心情差', 'sad'],
  '生气': ['愤怒', '发火', '恼火', 'angry'],
  '害怕': ['恐惧', '担心', '焦虑', '紧张'],
  '喜欢': ['爱', '偏好', '中意', '喜爱'],
  '讨厌': ['不喜欢', '烦', '受不了', '反感'],
  '吃': ['饮食', '食物', '餐', '菜', '美食', '零食'],
  '减肥': ['瘦身', '健身', '体重', '卡路里', '节食'],
  '工作': ['上班', '办公', '职场', '公司', '加班'],
  '学习': ['读书', '学', '课程', '教程', '知识'],
  '代码': ['编程', '开发', '写代码', '程序'],
  '睡觉': ['睡眠', '失眠', '休息', '早睡', '熬夜'],
  '运动': ['锻炼', '健身', '跑步', '游泳', '篮球', '足球', '羽毛球', '乒乓球', '网球', '瑜伽'],
  '旅行': ['旅游', '出行', '出差', '度假'],
  '电影': ['影片', '看电影', '影院'],
  '音乐': ['歌', '听歌', '唱歌'],
  '买': ['购买', '选购', '入手', '下单'],
  '贵': ['价格高', '不便宜', '昂贵'],
  '便宜': ['实惠', '性价比', '划算'],
  '好': ['不错', '棒', '优秀', '牛'],
  '差': ['烂', '垃圾', '不行', '糟糕'],
  '快': ['迅速', '效率高', '速度快'],
  '慢': ['缓慢', '效率低', '卡顿'],
  '大': ['庞大', '巨大', '很多'],
  '小': ['微小', '很少', '不多'],
  'Python': ['python', 'py', 'pip'],
  'JavaScript': ['js', 'node', 'npm', 'typescript', 'ts'],
  'Docker': ['docker', '容器', 'container', 'k8s'],
  'Git': ['git', 'github', 'gitlab', '版本控制'],
  'Linux': ['linux', 'ubuntu', 'centos', 'shell', 'bash'],
  'API': ['api', '接口', '请求', 'http', 'rest'],
  '数据库': ['sql', 'mysql', 'postgres', 'redis', 'mongo', 'sqlite'],
  '面试': ['interview', '简历', '求职', 'offer'],
  '房子': ['租房', '买房', '房价', '装修'],
  '车': ['汽车', '轿车', 'SUV', '电动车', '买车', '开车'],
  '买车': ['购车', '提车', '选车', '试驾', '4S店'],
  '孩子': ['小孩', '宝宝', '育儿', '教育'],
  '老婆': ['老公', '对象', '伴侣', '爱人'],
  '钱': ['收入', '工资', '花费', '预算', '存款'],
  '身体': ['健康', '体检', '生病', '医院'],

  // ── 情感表达（50组）──────────────────────────────────────────────────
  '愉快': ['愉悦', '舒畅', '心旷神怡', 'pleasant'],
  '幸福': ['美满', '幸运', '满足', '甜蜜', 'happiness'],
  '兴奋': ['激动', '亢奋', '热血沸腾', 'excited'],
  '惊喜': ['意外', '惊讶', '惊艳', 'surprise'],
  '感动': ['动容', '触动', '暖心', '泪目'],
  '满足': ['满意', '知足', '心满意足', 'satisfied'],
  '自信': ['底气', '有把握', '信心十足', 'confident'],
  '骄傲': ['自豪', '得意', '荣耀', 'proud'],
  '期待': ['盼望', '憧憬', '翘首以盼', '期望'],
  '感恩': ['感谢', '谢谢', '感激', 'grateful'],
  '轻松': ['放松', '舒适', '自在', '无压力'],
  '平静': ['安宁', '宁静', '淡定', '心如止水'],
  '勇敢': ['胆大', '无畏', '有胆量', 'brave'],
  '温暖': ['暖', '暖心', '温馨', 'warm'],
  '善良': ['好心', '善意', '心地好', 'kind'],
  '伤心': ['悲伤', '心碎', '心痛', '难过', 'heartbroken'],
  '愤怒': ['暴怒', '气炸', '怒火', '火大', 'furious'],
  '恐惧': ['害怕', '胆怯', '惊恐', 'fear'],
  '焦虑': ['紧张', '不安', '担忧', '心慌', 'anxiety'],
  '无聊': ['没意思', '乏味', '枯燥', 'boring'],
  '疲惫': ['累', '精疲力尽', '筋疲力尽', '体力不支'],
  '烦躁': ['烦', '心烦', '不耐烦', '烦死了', 'annoyed'],
  '尴尬': ['难为情', '窘迫', '丢脸', 'embarrassed'],
  '孤独': ['寂寞', '形单影只', '一个人', 'lonely'],
  '失望': ['心灰意冷', '泄气', '灰心', 'disappointed'],
  '后悔': ['懊悔', '追悔', '悔恨', '不该', 'regret'],
  '嫉妒': ['眼红', '羡慕嫉妒恨', '妒忌', 'jealous'],
  '内疚': ['愧疚', '自责', '过意不去', 'guilty'],
  '不安': ['忐忑', '惶恐', '心神不宁', 'uneasy'],
  '悲观': ['消极', '灰心', '看不到希望', 'pessimistic'],
  '绝望': ['无望', '走投无路', '万念俱灰', 'hopeless'],
  '委屈': ['受委屈', '冤枉', '心酸', '憋屈'],
  '崩溃': ['受不了', '绷不住', '精神崩溃', 'breakdown'],
  '抑郁': ['郁闷', '消沉', '低落', 'depression'],
  '迷茫': ['困惑', '不知所措', '茫然', 'confused'],
  '纠结': ['犹豫', '举棋不定', '左右为难', '拿不定主意'],
  '羡慕': ['眼红', '向往', '真好', 'envy'],
  '释然': ['放下', '想开了', '看开了', '释怀'],
  '怀念': ['想念', '思念', '挂念', 'miss'],
  '担心': ['操心', '忧虑', '挂心', 'worry'],
  '惭愧': ['羞愧', '无地自容', '汗颜'],
  '沮丧': ['颓废', '消沉', '低落', 'dejected'],
  '厌倦': ['腻了', '受够了', '厌烦', 'tired of'],
  '震惊': ['吃惊', '目瞪口呆', '难以置信', 'shocked'],
  '羞耻': ['丢人', '可耻', '没脸', 'shame'],
  '恐慌': ['慌张', '六神无主', '手忙脚乱', 'panic'],
  '欣慰': ['安慰', '宽慰', '放心'],
  '激动': ['热泪盈眶', '心潮澎湃', '情绪高涨'],
  '心疼': ['心痛', '舍不得', '怜惜', '疼惜'],

  // ── 日常生活（80组）──────────────────────────────────────────────────
  '喝': ['饮', '喝水', '饮料', '喝酒'],
  '睡': ['睡觉', '入睡', '躺下', '休息'],
  '走': ['步行', '走路', '散步', '溜达'],
  '跑': ['跑步', '慢跑', '冲刺', 'run', 'jogging'],
  '坐': ['坐下', '就坐', '落座'],
  '站': ['站立', '站起来', '起立'],
  '看': ['观看', '瞧', '瞅', '注视', '盯'],
  '听': ['倾听', '听到', '聆听'],
  '说': ['讲', '告诉', '说话', '聊'],
  '写': ['书写', '撰写', '写作', '码字'],
  '读': ['阅读', '看书', '朗读', '读书'],
  '洗': ['清洗', '洗涤', '冲洗', '洗澡'],
  '穿': ['穿衣', '搭配', '穿搭', '衣服'],
  '做饭': ['烹饪', '下厨', '炒菜', '煮饭', 'cook'],
  '打扫': ['清洁', '扫地', '拖地', '搞卫生'],
  '购物': ['买东西', '逛街', '剁手', 'shopping'],
  '旅游': ['旅行', '出游', '游玩', 'travel'],
  '聚会': ['派对', '聚餐', '轰趴', 'party'],
  '约会': ['见面', '约见', '相亲', 'date'],
  '加班': ['熬夜干活', '赶工', '超时工作', 'overtime'],
  '请假': ['休假', '告假', '放假', '歇班'],
  '迟到': ['晚了', '来不及', '赶不上'],
  '出差': ['差旅', '公出', '外派'],
  '洗澡': ['淋浴', '泡澡', '冲凉', 'shower'],
  '刷牙': ['漱口', '口腔清洁'],
  '化妆': ['美妆', '上妆', '打扮', 'makeup'],
  '理发': ['剪头发', '理发店', '发型', 'haircut'],
  '遛狗': ['溜狗', '带狗散步'],
  '喂猫': ['铲屎', '撸猫', '养猫'],
  '下单': ['下单购买', '网购', '买买买'],
  '退货': ['退款', '售后', '换货'],
  '签收': ['取快递', '收包裹', '收货'],
  '快递': ['包裹', '物流', '顺丰', '菜鸟'],
  '外卖': ['点外卖', '美团', '饿了么', '叫外卖'],
  '叫车': ['打车', '叫滴滴', '网约车', '出租车'],
  '排队': ['等位', '等候', '排号'],
  '预约': ['挂号', '预定', '订位', '预订'],
  '充值': ['充钱', '缴费', '续费', '付费'],
  '提现': ['取钱', '转账', '汇款'],
  '扫码': ['二维码', '扫一扫', '付款码'],
  '搬家': ['搬迁', '乔迁', '换地方', '换房子', '新家'],
  '装修': ['翻新', '改造', '装潢', '施工'],
  '倒垃圾': ['扔垃圾', '垃圾分类'],
  '做家务': ['家务活', '干活', '打扫卫生'],
  '叫醒': ['闹钟', '起床', '早起', '赖床'],
  '熬夜': ['晚睡', '通宵', '夜猫子', '失眠'],
  '午休': ['午睡', '小憩', '打个盹'],
  '散步': ['溜达', '走走', '逛逛'],
  '晨跑': ['晨练', '早起运动', '跑步'],
  '锻炼': ['健身', '运动', '练', 'workout', '跑步', '锻炼习惯'],
  '做瑜伽': ['瑜伽', '拉伸', '冥想', 'yoga'],
  '游泳': ['泡泳池', '蛙泳', '自由泳', 'swim'],
  '骑车': ['骑自行车', '单车', '骑行', 'cycling'],
  '爬山': ['登山', '徒步', '远足', 'hiking'],
  '钓鱼': ['垂钓', '钓', '钓鱼佬', 'fishing'],
  '唱歌': ['K歌', '唱K', 'KTV', 'karaoke'],
  '跳舞': ['舞蹈', '蹦迪', '跳', 'dance'],
  '拍照': ['拍摄', '摄影', '照相', 'photo'],
  '刷手机': ['刷抖音', '刷微博', '刷视频', '摸鱼'],
  '看剧': ['追剧', '刷剧', '看电视剧', '煲剧'],
  '打游戏': ['玩游戏', '开黑', '上分', 'gaming'],
  '聊天': ['聊', '侃', '吹牛', '闲聊'],
  '吵架': ['争吵', '吵', '闹矛盾', '拌嘴'],
  '道歉': ['认错', '说对不起', '赔不是', 'apologize'],
  '搬砖': ['干活', '打工', '苦力'],
  '带娃': ['带孩子', '看孩子', '陪娃'],
  '遛弯': ['散步', '溜达', '走走'],
  '赶飞机': ['赶航班', '去机场', '登机'],
  '堵车': ['塞车', '交通拥堵', '路上堵'],
  '停车': ['找车位', '泊车', '停车场'],
  '加油': ['充电', '加满', '油费', '电费'],
  '修车': ['汽车维修', '保养', '4S店'],
  '看病': ['就医', '去医院', '看医生', '挂号'],
  '配眼镜': ['验光', '近视', '眼镜店'],
  '办证': ['办手续', '证件', '审批'],
  '缴税': ['报税', '个税', '税务'],


  // ── 冷启动种子扩展（自动生成，2000组目标）──────────────────────────
  '烤鸭': ['北京烤鸭', '片皮鸭', '烤鸭店'],
  '馒头': ['花卷', '发面', '面食'],
  '煎饼': ['煎饼果子', '鸡蛋饼', '薄饼'],
  '寿司': ['生鱼片', '刺身', '日料'],
  '披萨': ['pizza', '外卖', '必胜客'],
  '汉堡': ['麦当劳', '肯德基', '快餐'],
  '沙拉': ['蔬菜沙拉', '轻食', '健康餐'],
  '牛排': ['西餐', '五分熟', '七分熟', 'steak'],
  '意面': ['pasta', '意大利面', '通心粉'],
  '蒸菜': ['蒸鱼', '蒸排骨', '蒸蛋'],
  '炖汤': ['老火汤', '煲汤', '鸡汤'],
  '凉拌': ['拌菜', '凉菜', '冷菜'],
  '酱油': ['生抽', '老抽', '调味'],
  '辣椒': ['辣', '辣酱', '辣油', '剁椒'],
  '花椒': ['麻', '藤椒', '麻辣'],
  '八角': ['五香', '桂皮', '香料'],
  '冰淇淋': ['雪糕', '冰棍', '甜筒'],
  '蛋糕': ['生日蛋糕', '甜品', '烘焙'],
  '巧克力': ['可可', '甜食', '黑巧'],
  '果汁': ['鲜榨', '橙汁', '西瓜汁'],
  '可乐': ['碳酸饮料', '雪碧', '汽水'],
  '豆腐': ['豆干', '豆皮', '豆制品'],
  '鸡肉': ['鸡胸', '鸡腿', '鸡翅'],
  '猪肉': ['五花肉', '排骨', '肉丝'],
  '牛肉': ['牛腩', '牛腱', '牛肉面'],
  '羊肉': ['涮羊肉', '烤全羊', '手抓'],
  '海鲜': ['虾', '螃蟹', '鱼', '贝类'],
  '素食': ['素菜', '吃素', '斋饭'],
  '辣味': ['麻辣', '香辣', '微辣', '变态辣'],
  '甜味': ['甜点', '甜品', '糖'],
  '酸味': ['酸菜', '柠檬', '醋'],
  '苦味': ['苦瓜', '黑咖啡', '苦'],
  '香菜': ['芫荽', '不吃香菜', '香菜党'],
  '葱': ['大葱', '小葱', '葱花'],
  '姜': ['老姜', '生姜', '姜片'],
  '蒜': ['大蒜', '蒜末', '蒜蓉'],
  '味精': ['鸡精', '调味料', '提鲜'],
  '蘑菇': ['香菇', '金针菇', '平菇'],
  '土豆': ['马铃薯', '薯条', '土豆丝'],
  '西红柿': ['番茄', '圣女果', '番茄炒蛋'],
  '黄瓜': ['青瓜', '凉拌黄瓜'],
  '茄子': ['红烧茄子', '鱼香茄子'],
  '白菜': ['大白菜', '小白菜', '娃娃菜'],
  '菠菜': ['波菜', '绿叶菜'],
  '西兰花': ['花菜', '花椰菜'],
  '玉米': ['甜玉米', '爆米花', '玉米棒'],
  '苹果': ['红富士', '青苹果', '水果'],
  '香蕉': ['芭蕉', '热带水果'],
  '草莓': ['蓝莓', '覆盆子', '浆果'],
  '西瓜': ['哈密瓜', '甜瓜'],
  '葡萄': ['提子', '葡萄酒', '红酒'],
  '橘子': ['橙子', '柑橘', '柚子'],
  '芒果': ['热带水果', '芒果汁'],
  '榴莲': ['臭', '水果之王'],
  '菜谱': ['食谱', '做法', '教程'],
  '拿手菜': ['特长菜', '招牌菜', '绝活'],
  '口味': ['味道', '口感', '风味'],
  '忌口': ['不吃', '过敏食物', '禁忌', '不碰'],
  '素食主义': ['吃素', '纯素', 'vegan'],
  '小吃': ['零嘴', '路边摊', '夜市小吃'],
  '抖音': ['短视频', 'TikTok', '刷抖音', '抖音直播'],
  '微博': ['发微博', '热搜', '转发', '超话'],
  '微信': ['聊天', '朋友圈', '公众号', '微信群'],
  '小红书': ['种草', '笔记', '分享', '拔草'],
  'B站': ['哔哩哔哩', '弹幕', 'up主', '番剧'],
  '网红': ['博主', 'KOL', '达人', '网红店'],
  'WiFi': ['网络', '信号', '断网', '网速'],
  '公众号': ['订阅号', '推文', '文章'],
  '朋友圈': ['发圈', '晒', '点赞', '评论'],
  '表情包': ['斗图', 'emoji', '搞笑图'],
  '热搜': ['上热搜', '热门话题', '热议'],
  '转发': ['分享', '扩散', '安利'],
  '点赞': ['双击', '喜欢', 'like'],
  '评论': ['留言', '回复', '讨论'],
  '粉丝': ['关注者', '追随者', 'fan'],
  '黑粉': ['喷子', '键盘侠', '杠精'],
  '信息茧房': ['同温层', '推荐算法'],
  'vlog': ['视频日记', '拍vlog', '记录生活'],
  '弹幕': ['评论区', '互动', '吐槽'],
  '种草': ['推荐', '安利', '好物'],
  '拔草': ['买到手', '入手', '下单'],
  '代购': ['海淘', '国外买', '免税店'],
  '拼团': ['拼单', '团购', '拼多多'],
  '秒杀': ['限时抢购', '闪购', '秒杀价'],
  '客服': ['售后', '投诉', '退换'],
  '好评': ['五星', '评价', '推荐'],
  '差评': ['一星', '投诉', '不推荐'],
  '打卡': ['签到', '每日打卡', 'check-in'],
  '在线': ['线上', '远程', '云端'],
  '密码': ['口令', '验证码', '指纹'],
  '账号': ['注册', '登录', '用户名'],
  '隐私': ['个人信息', '数据保护', '泄露'],
  '广告': ['推广', '弹窗', '植入'],
  '纪录片': ['BBC', '舌尖', '人文'],
  '古典音乐': ['交响乐', '钢琴曲', '协奏曲'],
  '摇滚': ['乐队', '摇滚乐', '吉他solo'],
  '说唱': ['rap', '嘻哈', 'hip-hop', 'freestyle'],
  '民谣': ['民谣歌手', '木吉他', '文艺'],
  '电子音乐': ['EDM', 'DJ', '蹦迪'],
  '爵士': ['jazz', '蓝调', 'blues'],
  '歌单': ['播放列表', '收藏', '推荐歌曲'],
  '耳机': ['降噪', 'AirPods', '音质'],
  '吉他': ['弹吉他', 'guitar', '弹唱', '和弦'],
  '钢琴': ['弹钢琴', 'piano', '键盘', '琴键'],
  '小提琴': ['violin', '弦乐', '拉琴'],
  '架子鼓': ['打鼓', 'drums', '节奏'],
  '乐队': ['组乐队', 'band', '排练'],
  '歌手': ['唱歌', '歌曲', '出道'],
  '偶像': ['追星', '粉丝', '应援'],
  '喜剧': ['搞笑', '幽默', '笑话'],
  '恐怖片': ['惊悚', '吓人', '恐怖'],
  '爱情片': ['浪漫', '言情', '甜蜜'],
  '动作片': ['武打', '特效', '大片'],
  '科幻片': ['科幻', '未来', '太空'],
  '悬疑': ['推理', '烧脑', '反转'],
  '导演': ['拍电影', '影片', '作品'],
  '演员': ['明星', '角色', '表演'],
  '票房': ['上映', '首映', '院线'],
  '游戏主机': ['PS5', 'Switch', 'Xbox'],
  '手游': ['手机游戏', '王者', '原神'],
  'steam': ['PC游戏', '端游', '3A大作'],
  '密室逃脱': ['密室', '解谜', '恐怖密室'],
  '网购': ['淘宝', '京东', '拼多多', '天猫'],
  '双十一': ['双11', '打折', '大促', '满减'],
  '优惠券': ['折扣', '红包', '返利', '优惠码'],
  '盲盒': ['潮玩', '手办', '收藏', '泡泡玛特'],
  '打折': ['降价', '促销', '特价', '清仓'],
  '满减': ['满减券', '凑单', '优惠'],
  '预售': ['定金', '尾款', '预购'],
  '直播带货': ['直播间', '链接', '上架'],
  '好物': ['推荐', '必买', '值得买'],
  '性价比': ['划算', '便宜又好', '物美价廉'],
  '品牌': ['名牌', '大牌', '奢侈品'],
  '山寨': ['仿品', '假货', '高仿'],
  '正品': ['正版', '官方', '旗舰店'],
  '会员': ['VIP', '年费', '权益'],
  '积分': ['兑换', '攒积分', '折扣'],
  '物流': ['快递', '发货', '到货'],
  '售后': ['退货', '换货', '维修'],
  '包邮': ['免运费', '顺丰到付'],
  '批发': ['团购', '量大优惠'],
  '勇气': ['勇敢', '胆量', '敢', 'brave'],
  '坚持': ['毅力', '坚强', '不放弃', '持之以恒'],
  '放弃': ['半途而废', '算了', '不干了'],
  '成功': ['胜利', '达成', '成就', '赢'],
  '失败': ['输', '没做到', '落选', '失利'],
  '机会': ['机遇', '时机', '风口'],
  '挑战': ['困难', '考验', '难关'],
  '选择': ['决定', '抉择', '取舍'],
  '后果': ['结果', '代价', '影响'],
  '目标': ['目的', '方向', '追求'],
  '计划': ['安排', '规划', '打算'],
  '执行': ['落实', '做', '实施'],
  '青春': ['年轻', '青年', '年少'],
  '中年': ['中年危机', '上有老下有小'],
  '老年': ['退休', '养老', '晚年'],
  '怀旧': ['想念', '以前', '当年'],
  '信任': ['相信', '靠谱', '托付'],
  '背叛': ['出卖', '欺骗', '辜负'],
  '愧疚': ['对不起', '内疚', '歉意'],
  '宽容': ['原谅', '包容', '大度'],
  '责任': ['担当', '义务', '负责'],
  '自由': ['无拘无束', '随心', '解放'],
  '痛苦': ['折磨', '难受', '受苦'],
  '希望': ['期待', '盼望', '憧憬'],
  '努力': ['奋斗', '拼搏', '加油'],
  '懒惰': ['懒', '不想动', '摸鱼'],
  '好习惯': ['自律', '坚持', '规律'],
  '偏食': ['挑食', '不爱吃', '嫌弃'],
  '味道': ['口味', '口感', '风味'],
  '品味': ['审美', '眼光', '格调'],
  '氛围': ['气氛', '环境', '感觉', '调调'],
  '仪式感': ['仪式', '郑重', '特别'],
  '安全感': ['踏实', '稳定', '可靠'],
  '存在感': ['被关注', '被看见'],
  '归属感': ['家的感觉', '融入', '认同'],
  '副业': ['兼职', '外快', '斜杠'],
  '职业规划': ['发展方向', '职业路径', '晋升'],
  '攒钱': ['存钱', '储蓄', '积蓄'],
  '月光族': ['花光', '入不敷出', '超支'],
  '社交恐惧': ['社恐', '不善交际', '内向', '怕生'],
  '社交牛人': ['社牛', '自来熟', '外向'],
  '仪式': ['习惯', '日常', '固定流程'],
  '早起': ['早鸟', '晨型人', '早起的鸟'],
  '夜猫子': ['熬夜', '晚睡', '夜晚'],
  '拖延症': ['拖延', '明天再说', '拖拉'],
  '完美主义': ['强迫症', '追求完美', '不满意'],
  '选择困难': ['纠结', '选不好', '犹豫'],
  '分离焦虑': ['想念', '不舍', '依赖'],
  '财务焦虑': ['缺钱', '还不起', '压力大'],
  '春天': ['春季', '开春', '万物复苏', '花开'],
  '夏天': ['夏季', '酷暑', '三伏天', '空调'],
  '秋天': ['秋季', '金秋', '落叶', '凉爽'],
  '冬天': ['冬季', '寒冬', '下雪', '暖气'],
  '下雨': ['雨天', '暴雨', '梅雨', '打伞'],
  '下雪': ['雪天', '堆雪人', '暴雪'],
  '台风': ['暴风', '飓风', '强风'],
  '地震': ['震', '余震', '震感'],
  '晴天': ['大太阳', '蓝天', '好天气'],
  '阴天': ['多云', '灰蒙蒙'],
  '雾霾': ['空气质量', 'PM2.5', '口罩'],
  '高温': ['热浪', '中暑', '防暑'],
  '樱花': ['花开', '赏花', '花季'],
  '红叶': ['赏秋', '枫叶', '金黄'],
  '海边': ['沙滩', '海滩', '冲浪', '日落'],
  '山顶': ['登顶', '风景', '远眺'],
  '日出': ['朝阳', '清晨', '东方'],
  '日落': ['夕阳', '黄昏', '晚霞'],
  '星空': ['银河', '流星', '观星'],
  '月亮': ['满月', '月光', '赏月'],
  'PMP': ['项目管理', '项目经理认证', 'PMI'],
  'CPA': ['注册会计师', '会计考试'],
  '教师资格': ['教资', '教师证', '教编'],
  '律师资格': ['司法考试', '法考', '律师证'],
  '四六级': ['CET', '英语等级', '过四级'],
  '程序员': ['码农', '工程师', '开发者'],
  '产品经理': ['PM', '产品设计', '需求'],
  '设计师': ['UI', 'UX', '美工', '视觉'],
  '运营': ['内容运营', '用户运营', '活动'],
  '销售': ['业务', '客户', '业绩'],
  '人力资源': ['HR', '招聘', '面试官'],
  '市场营销': ['营销', '推广', '品牌'],
  '咨询': ['顾问', '咨询师', '方案'],
  '自由职业': ['freelance', '独立', '接单'],
  '远程办公': ['在家办公', 'WFH', '居家办公'],
  '共享办公': ['co-working', '众创空间'],
  '网约车': ['打车', '滴滴', '出租车'],
  '自驾': ['自驾游', '开车出去', '公路旅行'],
  '加油站': ['加油', '油价', '充电站'],
  '轻轨': ['城轨', '有轨电车'],
  '机票': ['订机票', '特价机票', '廉航'],
  '火车票': ['抢票', '12306', '硬座软卧'],
  '沙发': ['客厅', '布艺沙发', '真皮沙发'],
  '床': ['床垫', '床单', '枕头'],
  '窗帘': ['遮光帘', '百叶窗'],
  '空调': ['制冷', '暖风', '空调病'],
  '洗衣机': ['洗衣服', '滚筒', '烘干'],
  '冰箱': ['保鲜', '冷冻', '冰镇'],
  '微波炉': ['加热', '热饭'],
  '吸尘器': ['扫地机器人', '除尘'],
  '马桶': ['卫生间', '冲水', '智能马桶'],
  '浴室': ['淋浴', '浴缸', '洗澡'],
  '灯': ['台灯', '吊灯', '夜灯'],
  '插座': ['充电', '插头', '转换器'],
  '垃圾桶': ['垃圾分类', '倒垃圾'],
  '拖把': ['拖地', '扫地', '清洁'],
  '皮肤': ['护肤', '痘痘', '防晒', '保湿'],
  '头发': ['发型', '洗头', '掉发', '染发'],
  '眼睛': ['视力', '近视', '眼镜', '眼药水'],
  '耳朵': ['听力', '耳机', '耳鸣'],
  '牙齿': ['刷牙', '补牙', '智齿', '矫正'],
  '骨头': ['骨折', '关节', '腰椎'],
  '肌肉': ['增肌', '拉伤', '酸痛'],
  '免疫力': ['抵抗力', '体质', '不容易生病'],
  '新陈代谢': ['代谢', '基础代谢'],
  '激素': ['内分泌', '荷尔蒙'],
  '中暑': ['热射病', '防暑降温'],
  '晕车': ['晕船', '晕机', '运动病'],
  '抽筋': ['腿抽筋', '肌肉痉挛'],
  '打呼噜': ['呼噜', '睡眠呼吸', '鼾声'],
  '布偶猫': ['猫品种', '长毛猫'],
  '英短': ['英国短毛', '蓝猫'],
  '金毛': ['金毛猎犬', '大型犬'],
  '柯基': ['小短腿', '威尔士柯基'],
  '哈士奇': ['二哈', '拆家', '雪橇犬'],
  '柴犬': ['小柴', '日本犬'],
  '猫砂': ['猫厕所', '铲猫砂'],
  '狗粮': ['犬粮', '宠物食品'],
  '宠物医院': ['兽医', '看病', '疫苗'],
  '绝育': ['做手术', '绝育手术'],
  '仓鼠': ['龙猫', '兔子', '小动物'],
  '金鱼': ['观赏鱼', '鱼缸', '养鱼'],
  '乌龟': ['陆龟', '水龟', '龟壳'],
  '鹦鹉': ['鸟', '养鸟', '学说话'],
  '一会儿': ['待会', '等一下', '稍后'],
  '刚才': ['刚刚', '方才', '之前'],
  '经常': ['常常', '总是', '老是'],
  '偶尔': ['有时候', '时不时', '间或'],
  '从来': ['一直', '始终', '向来'],
  '突然': ['忽然', '猛然', '骤然'],
  '马上': ['立刻', '立马', '赶紧'],
  '慢慢': ['逐渐', '渐渐', '一点点'],
  '永远': ['一辈子', '始终', '永恒'],
  '暂时': ['临时', '先', '目前'],
  '大概': ['大约', '差不多', '左右'],
  '至少': ['起码', '最少', '不低于'],
  '最多': ['顶多', '不超过', '上限'],
  '几乎': ['差不多', '快要', '接近'],

  // ── 概念级近义（美食/人生事件/宠物/数码/节日）─────────────────────────
  '母校': ['大学', '学校', '高中', '校友'],
  '火锅': ['涮锅', '麻辣烫', '串串', '涮肉', '重庆火锅'],
  '烧烤': ['撸串', 'BBQ', '烤肉', '烧烤摊'],
  '奶茶': ['奶茶店', '珍珠奶茶', '蜜雪冰城', '茶饮'],
  '咖啡': ['拿铁', '美式', '星巴克', '咖啡因', 'coffee'],
  '酒': ['喝酒', '啤酒', '白酒', '红酒', '醉'],
  '零食': ['薯片', '饼干', '小食', '嘴馋'],
  '早餐': ['早饭', '早起吃', '早点', '吃早餐'],
  '晚餐': ['晚饭', '晚上吃', '夜宵', '宵夜'],
  '怀孕': ['孕期', '孕妇', '待产', '预产期', '产检'],
  '宠物': ['猫', '狗', '铲屎官', '宠物医院', '猫咪', '狗狗'],
  '猫': ['喵', '猫咪', '养猫', '铲屎', '猫粮'],
  '狗': ['汪', '狗子', '养狗', '遛狗', '狗粮'],
  '天气': ['下雨', '晴天', '冷', '热', '降温'],
  '手机': ['手机壳', '充电', '换手机', 'iPhone', '安卓'],
  '电脑': ['笔记本', '台式机', '修电脑', 'Mac', 'PC', 'MacBook', 'ThinkPad', 'iPad', '平板'],
  '家': ['回家', '家里', '在家', '老家', '家人'],
  '过年': ['春节', '除夕', '年三十', '拜年', '年夜饭'],
  '生日': ['生日快乐', '庆生', '蛋糕', '许愿'],

  // ── 工作职场（50组）──────────────────────────────────────────────────
  '简历': ['CV', '履历', '个人简介', 'resume'],
  '工资': ['薪资', '薪水', '月薪', '年薪', 'salary'],
  '加薪': ['涨薪', '调薪', '涨工资', '提薪'],
  '升职': ['晋升', '提拔', '升官', 'promotion'],
  '离职': ['辞职', '走人', '跑路', '不干了'],
  '跳槽': ['换工作', '换公司', '另谋高就'],
  '同事': ['同僚', '搭档', '队友', 'colleague'],
  '老板': ['领导', '上司', '主管', 'boss'],
  '项目': ['工程', '需求', '任务', 'project'],
  '会议': ['开会', '例会', '会', 'meeting'],
  '绩效': ['考核', '评估', 'KPI', 'performance'],
  'KPI': ['指标', '绩效', '目标', 'OKR'],
  '汇报': ['报告', '述职', '总结', 'report'],
  '培训': ['培训课', '内训', '学习', 'training'],
  '实习': ['实习生', '实习期', 'intern', 'internship'],
  '裁员': ['裁人', '优化', '毕业', 'layoff'],
  '入职': ['报到', '入职日', '上班第一天', 'onboarding'],
  '试用期': ['考察期', '转正', '试用'],
  '转正': ['正式员工', '过了试用期', '通过考核'],
  '甲方': ['客户', '客户方', '需求方'],
  '乙方': ['供应商', '外包', '服务方'],
  '需求': ['需求文档', 'PRD', '产品需求', '功能需求'],
  '排期': ['计划', '时间表', '里程碑', 'schedule'],
  '上线': ['发布', '上线部署', '推送', 'release'],
  '周报': ['日报', '月报', '进度汇报'],
  '团队': ['小组', '部门', '组', 'team'],
  'HR': ['人事', '人力资源', '招聘'],
  '福利': ['五险一金', '年终奖', '奖金', '补贴'],
  '年假': ['带薪假', '假期', '休假', '放假'],
  '出勤': ['打卡', '签到', '考勤'],
  '早退': ['提前走', '先走了'],
  '加班费': ['加班工资', '补贴', '调休'],
  '调休': ['补休', '换休', '倒休'],
  '年终奖': ['年终', '十三薪', '双薪'],
  '股权': ['期权', '股票期权', 'RSU', 'option'],
  '创业': ['创业公司', '初创', 'startup', '自己干'],
  '融资': ['风投', 'VC', '投资', 'funding'],
  '上市': ['IPO', '敲钟', '市值'],
  'PPT': ['幻灯片', '演示文稿', 'slide', '做PPT'],
  '方案': ['提案', '计划书', '策划'],
  '复盘': ['回顾', '反思', '事后总结', 'retrospective'],
  '协作': ['合作', '配合', '协同', 'collaboration'],
  '沟通': ['交流', '对接', '拉通', 'communication'],
  '摸鱼': ['划水', '偷懒', '磨洋工', '带薪拉屎'],
  '内卷': ['卷', '过度竞争', '996'],
  '躺平': ['佛系', '随缘', '不卷了'],
  '背锅': ['背黑锅', '替罪羊', '甩锅'],
  '跑路': ['溜了', '闪人', '开溜'],

  // ── 技术编程（60组）──────────────────────────────────────────────────
  'Go': ['golang', 'goroutine', 'channel', 'go语言'],
  'Rust': ['rust', 'cargo', 'crate', 'borrow checker'],
  'Java': ['java', 'jvm', 'spring', 'maven', 'gradle'],
  'C++': ['cpp', 'c plus plus', '指针', 'stl'],
  'Swift': ['swift', 'swiftui', 'xcode', 'ios开发'],
  'PHP': ['php', 'laravel', 'composer', 'wordpress'],
  'Ruby': ['ruby', 'rails', 'gem', 'rake'],
  'React': ['react', 'jsx', 'hooks', 'redux', '前端框架'],
  'Vue': ['vue', 'vuex', 'nuxt', 'vue3'],
  'Angular': ['angular', 'rxjs', 'ngrx', 'ng'],
  'TypeScript': ['ts', 'typescript', '类型', '泛型'],
  'SQL': ['sql', '查询', 'SELECT', 'JOIN', '数据库查询'],
  'Redis': ['redis', '缓存', 'cache', 'key-value'],
  'MongoDB': ['mongo', 'nosql', '文档数据库', 'mongoose'],
  'PostgreSQL': ['pg', 'postgres', '关系型数据库', 'psql'],
  'MySQL': ['mysql', 'mariadb', '关系型数据库'],
  'SQLite': ['sqlite', 'sqlite3', '嵌入式数据库', '轻量数据库'],
  'Kubernetes': ['k8s', 'kubectl', 'pod', 'helm', '容器编排'],
  'HTTP': ['http', 'https', '请求', '响应', 'status code'],
  'REST': ['restful', 'rest api', '接口设计'],
  'GraphQL': ['graphql', 'query', 'mutation', 'schema'],
  'gRPC': ['grpc', 'protobuf', 'rpc'],
  '前端': ['frontend', '页面', 'HTML', 'CSS', 'UI'],
  '后端': ['backend', '服务端', '接口', 'server'],
  '全栈': ['fullstack', '前后端', '全栈开发'],
  '算法': ['algorithm', '数据结构', '刷题', 'leetcode'],
  '数据结构': ['链表', '树', '图', '哈希表', '栈', '队列'],
  '部署': ['deploy', '上线', '发布', '运维', 'CI/CD'],
  '调试': ['debug', '排查', '定位', '断点'],
  'bug': ['缺陷', '问题', '故障', '报错', 'issue'],
  '代码审查': ['code review', 'CR', '审代码', 'review'],
  '重构': ['refactor', '优化代码', '重写', '改善'],
  '框架': ['framework', '库', '中间件', 'library'],
  '微服务': ['microservice', '服务拆分', '分布式', 'SOA'],
  '消息队列': ['MQ', 'Kafka', 'RabbitMQ', '队列'],
  '负载均衡': ['load balancer', 'nginx', '反向代理', 'LB'],
  '监控': ['monitoring', '告警', 'Prometheus', 'Grafana'],
  '日志': ['log', '日志系统', 'ELK', '日志分析'],
  '测试': ['test', '单元测试', '集成测试', 'QA', '自动化测试'],
  '性能': ['performance', '优化', '并发', '响应时间'],
  '安全': ['security', '漏洞', '加密', '认证', '鉴权'],
  '架构': ['architecture', '设计模式', '系统设计'],
  '云服务': ['AWS', '阿里云', '腾讯云', 'Azure', 'GCP'],
  '服务器': ['server', '主机', 'VPS', '云主机'],
  'Shell': ['bash', 'zsh', '终端', 'terminal', '命令行'],
  '正则': ['regex', '正则表达式', '匹配', 'pattern'],
  '爬虫': ['spider', 'crawler', '抓取', 'scrape'],
  '机器学习': ['ML', '深度学习', 'AI', '模型', '训练'],
  '大模型': ['LLM', 'GPT', 'Claude', 'transformer', '大语言模型'],
  '向量': ['embedding', '向量数据库', 'vector', 'FAISS'],
  'Nginx': ['nginx', '反向代理', '负载均衡', 'web服务器'],
  '版本控制': ['git', 'svn', '分支', 'merge', 'commit'],
  'CI/CD': ['持续集成', '持续部署', 'pipeline', 'Jenkins', 'GitHub Actions'],
  '容器': ['docker', '镜像', 'image', 'dockerfile'],
  'ORM': ['orm', 'ActiveRecord', 'SQLAlchemy', 'Prisma'],
  '缓存': ['cache', 'Redis', 'CDN', '浏览器缓存'],
  '并发': ['多线程', '多进程', '异步', 'async', 'goroutine'],
  '网络': ['TCP', 'UDP', 'DNS', 'IP', 'socket', '网络协议'],
  '加密': ['encryption', 'RSA', 'AES', 'HTTPS', '签名'],
  '开源': ['open source', 'GitHub', '社区', 'MIT', 'Apache'],

  // ── 健康医疗（40组）──────────────────────────────────────────────────
  '头疼': ['头痛', '偏头痛', '脑袋疼', 'headache'],
  '感冒': ['着凉', '流感', '鼻塞', '打喷嚏', 'cold'],
  '发烧': ['发热', '高烧', '低烧', '体温高', 'fever'],
  '咳嗽': ['咳', '干咳', '嗓子痒', 'cough'],
  '过敏': ['皮肤痒', '过敏反应', '花粉过敏', 'allergy'],
  '失眠': ['睡不着', '难以入睡', '翻来覆去', 'insomnia', '睡眠不好', '睡眠质量'],
  '体检': ['检查', '检查身体', '年度体检', '健康检查'],
  '手术': ['开刀', '做手术', '微创', 'surgery'],
  '药': ['吃药', '用药', '处方药', '药物', 'medicine'],
  '减肥药': ['减肥产品', '瘦身药', '代餐'],
  '蛋白质': ['protein', '鸡胸肉', '蛋白'],
  '碳水': ['碳水化合物', '主食', '糖', 'carb'],
  '脂肪': ['油脂', '肥肉', '卡路里', 'fat'],
  '维生素': ['维C', '维D', 'vitamin', '营养素'],
  '血压': ['高血压', '低血压', '量血压', '血压计', '血压高'],
  '血糖': ['高血糖', '低血糖', '糖尿病', '测血糖'],
  '胃疼': ['胃痛', '胃病', '消化不良', '胃炎'],
  '拉肚子': ['腹泻', '肠胃炎', '水土不服', 'diarrhea'],
  '便秘': ['排便困难', '肠道不通', '通便'],
  '腰疼': ['腰痛', '腰酸', '腰椎', '久坐'],
  '颈椎': ['颈椎病', '脖子疼', '肩颈', '落枕'],
  '近视': ['视力', '眼镜', '散光', '矫正'],
  '牙疼': ['牙痛', '蛀牙', '智齿', '看牙'],
  '打针': ['注射', '打点滴', '输液', '疫苗'],
  '中药': ['中医', '汤药', '针灸', '草药'],
  '西药': ['西医', '抗生素', '消炎药'],
  '核酸': ['核酸检测', '抗原', '检测'],
  '健身房': ['gym', '撸铁', '器械', '私教'],
  '跑步机': ['椭圆机', '动感单车', '有氧器械'],
  '营养': ['营养搭配', '膳食', '饮食均衡', 'nutrition'],
  '热量': ['卡路里', 'kcal', '热量摄入', '基础代谢'],
  '体重': ['称重', '秤', '体脂', 'BMI'],
  '塑形': ['身材', '增肌', '线条', '马甲线'],
  '拉伸': ['柔韧性', '筋膜', '泡沫轴', 'stretching'],
  '心率': ['心跳', '脉搏', '有氧心率', '运动手表'],
  '保健品': ['补品', '保健', '膳食补充剂'],
  '康复': ['复健', '理疗', '恢复', 'rehab'],
  '挂号': ['预约挂号', '网上挂号', '排号'],
  '医保': ['医疗保险', '报销', '社保卡'],
  '处方': ['处方药', '医嘱', '开药'],

  // ── 教育学习（30组）──────────────────────────────────────────────────
  '考试': ['考', '测试', '笔试', 'exam'],
  '英语': ['English', '口语', '听力', '四六级', '雅思'],
  '数学': ['math', '高数', '微积分', '线性代数'],
  '论文': ['paper', '毕业论文', '学术', '期刊'],
  '课程': ['课', '网课', '公开课', 'course'],
  '大学': ['本科', '高校', '学校', '高中', '校友', 'university'],
  '研究生': ['硕士', '读研', '考研', '研一'],
  '留学': ['出国', '留学申请', '海外', 'study abroad'],
  '证书': ['认证', '考证', '资格证', '证'],
  '刷题': ['做题', '练习', '题库', 'leetcode'],
  '考研': ['研究生考试', '备考', '考研党'],
  '考公': ['公务员考试', '事业编', '国考', '省考'],
  '雅思': ['IELTS', '出国考试', '语言考试'],
  '托福': ['TOEFL', '留学考试', '英语考试'],
  'GPA': ['绩点', '成绩', '学分'],
  '毕业': ['毕业典礼', '答辩', '毕业季'],
  '学费': ['tuition', '教育费用', '书费'],
  '奖学金': ['scholarship', '助学金', '补助'],
  '导师': ['mentor', '老师', '教授', '指导'],
  '实验室': ['lab', '实验', '科研'],
  '图书馆': ['library', '自习室', '阅览室'],
  '网课': ['在线课程', '直播课', '录播课', 'MOOC'],
  '编程课': ['编程培训', '编程学习', '代码训练营', 'bootcamp'],
  '学历': ['文凭', '学位', '本科', '硕士'],
  '辅导': ['补课', '家教', '辅导班', '培优'],
  '笔记': ['课堂笔记', '学习笔记', '记录', 'note'],
  '复习': ['温习', '回顾', '备考', 'review'],
  '预习': ['提前学', '自学'],
  '作业': ['homework', '练习', '作业本', '写作业'],
  '阅读': ['精读', '泛读', '看书', '读'],

  // ── 财务理财（30组）──────────────────────────────────────────────────
  '收入': ['进账', '入账', '到手', 'income'],
  '花费': ['开销', '支出', '消费', 'expense'],
  '房贷': ['月供', '按揭', '贷款', 'mortgage'],
  '信用卡': ['刷卡', '还款', '账单', '信用额度'],
  '基金': ['定投', '指数基金', '理财产品', 'fund'],
  '股票': ['炒股', '股市', 'A股', '美股', 'stock'],
  '保险': ['投保', '保单', '理赔', '车险', '医疗险', '寿险', 'insurance'],
  '养老': ['养老金', '退休金', '退休', '社保', '老年', 'pension'],
  '公积金': ['住房公积金', '提取公积金', '公积金贷款'],
  '存款': ['储蓄', '定期', '活期', '存钱'],
  '投资': ['理财', '增值', '资产配置', '基金', '股票', '炒股', '定投', 'invest'],
  '理财': ['财务管理', '资产管理', '钱生钱'],
  '利息': ['利率', '年化', '收益率', 'interest'],
  '贷款': ['借钱', '借贷', '分期', '房贷', '车贷', '还款', '月供', 'loan'],
  '还款': ['还钱', '还贷', '月供'],
  '账单': ['对账', '流水', '明细', 'bill'],
  '预算': ['花费计划', '开支预算', 'budget'],
  '省钱': ['节省', '节约', '省着花'],
  '负债': ['欠钱', '欠款', '负资产', 'debt'],
  '记账': ['账本', '记录支出', '账目'],
  '税': ['个税', '所得税', '税率', 'tax'],
  '汇率': ['外汇', '换汇', '美元', '汇兑'],
  '比特币': ['BTC', '加密货币', 'crypto', '币圈'],
  '数字货币': ['虚拟货币', 'USDT', '区块链'],
  '定投': ['基金定投', '定期投资', '长期持有'],
  '分红': ['股息', '红利', '派息', 'dividend'],
  '亏损': ['亏钱', '赔钱', '浮亏', 'loss'],
  '盈利': ['赚钱', '盈余', '回报', 'profit'],
  '通胀': ['通货膨胀', '物价上涨', '贬值', 'inflation'],
  '财务自由': ['FIRE', '经济自由', '退休自由'],

  // ── 家庭关系（30组）──────────────────────────────────────────────────
  '爸爸': ['父亲', '爹', '老爸', '老爹', '准爸爸', '爸', '当爸爸', 'dad'],
  '妈妈': ['母亲', '娘', '老妈', 'mom'],
  '老公': ['丈夫', '先生', '另一半', 'husband'],
  '儿子': ['男孩', '小子', '崽', 'son'],
  '女儿': ['女孩', '闺女', '丫头', 'daughter'],
  '哥哥': ['兄', '大哥', '老哥', 'brother'],
  '姐姐': ['姐', '大姐', '老姐', 'sister'],
  '弟弟': ['弟', '小弟', '老弟'],
  '妹妹': ['妹', '小妹', '妹子'],
  '爷爷': ['祖父', '外公', '姥爷', 'grandfather'],
  '奶奶': ['祖母', '外婆', '姥姥', 'grandmother'],
  '朋友': ['好友', '哥们', '闺蜜', '伙伴', '兄弟', '死党', 'friend'],
  '同学': ['同窗', '校友', '学长', '学姐', 'classmate'],
  '邻居': ['隔壁', '邻里', '楼上楼下', 'neighbor'],
  '亲戚': ['亲属', '家人', '家族'],
  '叔叔': ['大叔', '舅舅', '伯伯', 'uncle'],
  '阿姨': ['大姨', '姑姑', '婶婶', 'aunt'],
  '宝宝': ['小婴儿', '娃娃', '宝贝', '婴儿', '新生儿', '娃', 'baby'],
  '父母': ['爸妈', '双亲', '家长', '爸爸', '妈妈', '父亲', '母亲', 'parents'],
  '家人': ['家庭', '家族', '家里人', 'family'],
  '女朋友': ['女友', '对象', '媳妇'],
  '男朋友': ['男友', '对象', '老公'],
  '对象': ['女朋友', '男朋友', '女友', '男友', '伴侣', '另一半'],
  '情侣': ['男女朋友', '对象', '恋人', 'couple'],
  '前任': ['前男友', '前女友', '前对象', 'ex'],
  '暗恋': ['单恋', '暗恋对象', '悄悄喜欢'],
  '表白': ['告白', '说喜欢', '追', 'confess'],
  '分手': ['分了', '散了', '掰了', 'break up'],
  '结婚': ['婚礼', '婚姻', '领证', '嫁', '娶', 'marry'],
  '离婚': ['离了', '分开', '离异', '散了', '不过了', 'divorce'],
  '婆媳': ['婆婆', '儿媳', '婆媳关系'],
  '育儿': ['养娃', '带孩子', '教育孩子', 'parenting'],
  '陪伴': ['在一起', '陪', '相伴', 'companion'],

  // ── 住房出行（30组）──────────────────────────────────────────────────
  '租房': ['租', '房租', '合租', '整租', '押金', '中介', 'rent'],
  '买房': ['购房', '首付', '房产', '新房'],
  '房价': ['房价走势', '均价', '楼盘', '地段'],
  '地铁': ['地铁站', '换乘', '早高峰', 'subway'],
  '公交': ['公交车', '坐公交', '公交站', 'bus'],
  '打车': ['叫车', '出租车', '网约车', '滴滴'],
  '开车': ['自驾', '驾驶', '驾车', 'drive'],
  '高铁': ['火车', '动车', '铁路', '12306'],
  '飞机': ['航班', '机票', '登机', '航空', 'flight'],
  '酒店': ['宾馆', '民宿', '住宿', 'hotel'],
  '车位': ['停车位', '停车场', '地下车库'],
  '物业': ['物业费', '物业管理', '业主委员会'],
  '二手房': ['二手', '存量房', '挂牌'],
  '学区房': ['学区', '划片', '对口学校'],
  '新房': ['期房', '现房', '样板间'],
  '首付': ['首付款', '付首付', '首付比例'],
  '贷款利率': ['房贷利率', 'LPR', '利率下调'],
  '通勤': ['上下班', '通勤时间', '距离'],
  '导航': ['地图', '百度地图', '高德', 'GPS'],
  '违章': ['罚单', '扣分', '超速', '闯红灯'],
  '驾照': ['驾驶证', '考驾照', '科目', '学车'],
  '电动车': ['电瓶车', '电动自行车', '充电'],
  '共享单车': ['哈啰', '美团单车', '青桔'],
  '限号': ['尾号限行', '单双号', '限行'],
  '路况': ['堵不堵', '路况信息', '拥堵'],
  '高速': ['高速公路', '收费站', 'ETC'],
  '签证': ['visa', '办签证', '签证申请'],
  '护照': ['passport', '办护照', '出境'],
  '行李': ['行李箱', '拉杆箱', '收拾行李', 'luggage'],
  '民宿': ['airbnb', '短租', '客栈'],

  // ── 饮食烹饪（30组）──────────────────────────────────────────────────
  '米饭': ['白饭', '大米', '蒸饭', 'rice'],
  '面条': ['面', '拉面', '意面', '挂面', 'noodle'],
  '火锅': ['涮锅', '自助火锅', '麻辣锅', 'hotpot'],
  '烧烤': ['撸串', 'BBQ', '烤串', '烤肉'],
  '咖啡': ['coffee', '拿铁', '美式', '星巴克', '瑞幸'],
  '奶茶': ['茶饮', '喜茶', '蜜雪冰城', '珍珠奶茶'],
  '啤酒': ['beer', '精酿', '扎啤', '冰啤'],
  '水果': ['fruit', '苹果', '香蕉', '橘子', '葡萄'],
  '蔬菜': ['青菜', '蔬', '菜', 'vegetable'],
  '肉': ['猪肉', '牛肉', '鸡肉', '羊肉', 'meat'],
  '鱼': ['鱼肉', '鱼类', '海鱼', '淡水鱼', 'fish'],
  '鸡蛋': ['蛋', '鸡蛋', '蛋类', 'egg'],
  '饺子': ['水饺', '包饺子', '蒸饺', 'dumpling'],
  '包子': ['馒头', '肉包', '蒸包'],
  '粥': ['稀饭', '八宝粥', '白粥'],
  '炒菜': ['炒', '爆炒', '清炒', '小炒'],
  '红烧': ['红烧肉', '红烧鱼', '焖煮'],
  '煲汤': ['炖汤', '汤', '老火汤', '靓汤'],
  '凉菜': ['凉拌', '冷盘', '拍黄瓜'],
  '甜点': ['dessert', '蛋糕', '甜品', '冰淇淋'],
  '零食': ['snack', '薯片', '饼干', '坚果'],
  '调料': ['酱油', '盐', '醋', '辣椒', 'seasoning'],
  '辣': ['辣椒', '麻辣', '微辣', '变态辣', 'spicy'],
  '酸': ['醋', '酸味', '柠檬', 'sour'],
  '甜': ['糖', '甜味', '蜂蜜', 'sweet'],
  '苦': ['苦味', '苦瓜', '苦口', 'bitter'],
  '点餐': ['点外卖', '外送', '配送'],
  '食堂': ['饭堂', '食堂饭', '工作餐'],
  '下厨': ['做饭', '自己做', '在家做'],
  '食谱': ['菜谱', '做法', '配方', 'recipe'],

  // ── 娱乐休闲（30组）──────────────────────────────────────────────────
  '电视剧': ['剧', '连续剧', '网剧', 'TV series'],
  '综艺': ['综艺节目', '真人秀', '选秀', 'variety show'],
  '动漫': ['动画', '番剧', '二次元', 'anime'],
  '小说': ['网文', '小说书', '看小说', 'novel'],
  '游戏': ['game', '手游', '端游', '主机游戏'],
  '摄影': ['拍照', '单反', '相机', 'photography'],
  '健身': ['gym', '力量训练', '有氧', '撸铁'],
  '瑜伽': ['yoga', '冥想', '拉伸', '正念'],
  '画画': ['绘画', '素描', '水彩', 'drawing'],
  '书法': ['毛笔', '练字', '字帖', 'calligraphy'],
  '乐器': ['吉他', '钢琴', '贝斯', '架子鼓'],
  '直播': ['live', '主播', '直播间', '带货'],
  '短视频': ['抖音', '快手', 'vlog', 'B站'],
  '播客': ['podcast', '播客节目', '有声'],
  '桌游': ['剧本杀', '狼人杀', '三国杀', 'board game'],
  '密室': ['密室逃脱', '沉浸式', '体验馆'],
  '露营': ['camping', '帐篷', '野餐', '户外'],
  '滑雪': ['skiing', '雪场', '滑板'],
  '潜水': ['diving', '浮潜', '深潜'],
  '冲浪': ['surfing', '浪', '水上运动'],
  '攀岩': ['climbing', '抱石', '岩壁'],
  '跑团': ['TRPG', '跑团游戏', 'DND'],
  '手办': ['模型', '盲盒', '潮玩', 'figure'],
  '追星': ['粉丝', '偶像', '饭圈', 'fan'],
  '演唱会': ['concert', '音乐节', '看演出', '现场'],
  '展览': ['展', '美术馆', '博物馆', 'exhibition'],
  'KTV': ['唱歌', 'K歌', '麦霸'],
  '麻将': ['棋牌', '打牌', '打麻将', '搓麻'],
  '剧本杀': ['剧本', '推理', '本格'],
  '宠物': ['猫', '狗', '宠物店', 'pet', '猫咪', '狗狗'],

  // ── 通用形容（40组）──────────────────────────────────────────────────
  '不错': ['挺好', '还行', '可以', '行'],
  '棒': ['赞', '厉害', '牛逼', '给力', 'awesome'],
  '糟糕': ['完蛋', '惨了', '坏了', '糟了'],
  '多': ['很多', '一大堆', '不少', '大量'],
  '少': ['不多', '很少', '稀少', '仅有'],
  '远': ['遥远', '很远', '路程长', 'far'],
  '近': ['很近', '附近', '不远', 'near'],
  '难': ['困难', '不容易', '费劲', 'difficult'],
  '简单': ['容易', '不难', '轻松', 'easy'],
  '复杂': ['麻烦', '繁琐', '复杂度高', 'complex'],
  '重要': ['关键', '要紧', '核心', 'important'],
  '有趣': ['好玩', '有意思', '有梗', 'interesting'],
  '热': ['炎热', '闷热', '高温', 'hot'],
  '冷': ['寒冷', '冰冷', '零下', 'cold'],
  '新': ['崭新', '最新', '全新', 'new'],
  '旧': ['老旧', '过时', '老的', 'old'],
  '高': ['很高', '偏高', '居高不下', 'high'],
  '低': ['偏低', '很低', '不高', 'low'],
  '长': ['很长', '冗长', '时间长', 'long'],
  '短': ['很短', '简短', '时间短', 'short'],
  '干净': ['整洁', '清爽', '一尘不染', 'clean'],
  '脏': ['肮脏', '不干净', '邋遢', 'dirty'],
  '漂亮': ['好看', '美丽', '颜值高', '养眼', 'beautiful'],
  '丑': ['难看', '丑陋', '不好看', 'ugly'],
  '聪明': ['智慧', '机灵', '脑子好使', 'smart'],
  '笨': ['蠢', '迟钝', '脑子不好使', 'stupid'],
  '安静': ['静', '清静', '安静一下', 'quiet'],
  '吵': ['嘈杂', '吵闹', '闹腾', 'noisy'],
  '忙': ['繁忙', '没空', '事情多', 'busy'],
  '闲': ['空闲', '没事', '有空', 'free'],
  '厚': ['厚实', '很厚', '厚重'],
  '薄': ['很薄', '薄薄的', '单薄'],
  '硬': ['坚硬', '很硬', '硬邦邦'],
  '软': ['柔软', '很软', '软绵绵'],
  '亮': ['明亮', '光亮', '发光', 'bright'],
  '暗': ['黑暗', '昏暗', '灰暗', 'dark'],
  '香': ['好闻', '香味', '芳香', 'fragrant'],
  '臭': ['难闻', '臭味', '恶臭', 'stink'],
  '正常': ['没毛病', '正常的', '一切正常', 'normal'],
  '奇怪': ['怪', '诡异', '不正常', '离谱', 'weird'],

  // ── English synonym groups ──────────────────────────────────────────────
  'happy': ['glad', 'cheerful', 'joyful', 'pleased', 'delighted'],
  'sad': ['unhappy', 'depressed', 'down', 'miserable', 'upset'],
  'angry': ['mad', 'furious', 'annoyed', 'irritated', 'pissed'],
  'work': ['job', 'office', 'career', 'employment', 'workplace'],
  'home': ['house', 'apartment', 'residence', 'place'],
  'food': ['meal', 'eat', 'dinner', 'lunch', 'breakfast', 'snack'],
  'health': ['fitness', 'exercise', 'workout', 'medical', 'wellness'],
  'money': ['salary', 'income', 'budget', 'savings', 'pay', 'wage'],
  'family': ['parents', 'children', 'kids', 'relatives', 'siblings'],
  'friend': ['buddy', 'mate', 'pal', 'colleague', 'peer'],
  'learn': ['study', 'practice', 'training', 'course', 'tutorial'],
  'travel': ['trip', 'vacation', 'holiday', 'journey', 'flight'],
  'code': ['programming', 'coding', 'development', 'software', 'dev'],
  'bug': ['error', 'issue', 'problem', 'crash', 'failure', 'glitch'],
  'deploy': ['release', 'ship', 'publish', 'launch', 'rollout'],
  'database': ['db', 'sql', 'postgres', 'mysql', 'mongo', 'redis'],
  'frontend': ['ui', 'react', 'vue', 'css', 'html', 'web'],
  'backend': ['server', 'api', 'rest', 'graphql', 'microservice'],
  'tired': ['exhausted', 'drained', 'burnt', 'burnout', 'fatigue'],
  'stressed': ['anxious', 'overwhelmed', 'pressure', 'tense'],
  'excited': ['thrilled', 'pumped', 'stoked', 'hyped', 'eager'],
  'boring': ['dull', 'tedious', 'monotonous', 'uninteresting'],
  'difficult': ['hard', 'tough', 'challenging', 'tricky', 'complex'],
  'easy': ['simple', 'straightforward', 'effortless', 'trivial'],
  'expensive': ['costly', 'pricey', 'overpriced'],
  'cheap': ['affordable', 'budget', 'bargain', 'inexpensive'],

  // ── English cold-start synonyms (200+ groups) ──────────────────────────

  // Emotions & feelings
  'happy': ['glad', 'joyful', 'cheerful', 'pleased', 'delighted', 'content'],
  'sad': ['unhappy', 'depressed', 'down', 'miserable', 'heartbroken', 'upset'],
  'angry': ['mad', 'furious', 'annoyed', 'irritated', 'pissed', 'outraged'],
  'afraid': ['scared', 'frightened', 'terrified', 'fearful', 'phobia', 'phobias'],
  'worried': ['concerned', 'anxious', 'nervous', 'uneasy', 'apprehensive'],
  'lonely': ['alone', 'isolated', 'solitary', 'lonesome'],
  'proud': ['accomplished', 'fulfilled', 'satisfied', 'achieved'],
  'confused': ['puzzled', 'bewildered', 'lost', 'unsure'],
  'grateful': ['thankful', 'appreciative', 'blessed'],
  'frustrated': ['stuck', 'blocked', 'hitting a wall', 'can\'t figure out'],

  // Health & body
  'sick': ['ill', 'unwell', 'not feeling well', 'under the weather'],
  'pain': ['ache', 'hurt', 'sore', 'discomfort', 'chronic'],
  'allergy': ['allergic', 'allergies', 'intolerance', 'sensitive', 'reaction'],
  'exercise': ['workout', 'training', 'gym', 'fitness', 'running', 'jogging'],
  'sleep': ['rest', 'nap', 'insomnia', 'bedtime', 'wake up', 'sleeping'],
  'diet': ['eating', 'nutrition', 'fasting', 'calories', 'weight loss', 'meal plan'],
  'surgery': ['operation', 'procedure', 'medical procedure'],
  'medication': ['medicine', 'pills', 'prescription', 'drugs', 'treatment'],
  'doctor': ['physician', 'specialist', 'appointment', 'checkup', 'clinic'],
  'phobia': ['fear', 'afraid', 'phobias', 'terrified', 'scared of'],
  'wellness': ['wellbeing', 'health', 'self-care', 'mindfulness', 'meditation'],

  // Work & career
  'job': ['work', 'career', 'employment', 'position', 'role', 'occupation'],
  'boss': ['manager', 'supervisor', 'lead', 'director', 'superior'],
  'colleague': ['coworker', 'teammate', 'peer', 'workmate'],
  'promotion': ['promoted', 'advancement', 'raise', 'title change', 'career progress'],
  'salary': ['pay', 'wage', 'income', 'compensation', 'earnings'],
  'interview': ['job interview', 'hiring', 'recruiter', 'application'],
  'resign': ['quit', 'leave', 'resign', 'hand in notice', 'stepping down'],
  'meeting': ['conference', 'standup', 'sync', 'call', 'huddle'],
  'deadline': ['due date', 'timeline', 'crunch', 'rush'],
  'overtime': ['extra hours', 'working late', 'staying late', 'long hours'],
  'review': ['evaluation', 'assessment', 'appraisal', 'feedback', 'annual review'],
  'mentor': ['mentoring', 'coaching', 'guiding', 'teaching', 'training'],

  // Family & relationships
  'wife': ['spouse', 'partner', 'significant other', 'better half'],
  'husband': ['spouse', 'partner', 'significant other'],
  'child': ['kid', 'son', 'daughter', 'children', 'kids'],
  'parent': ['mom', 'dad', 'mother', 'father', 'parents'],
  'friend': ['buddy', 'mate', 'pal', 'bestie', 'best friend', 'close friend'],
  'pet': ['cat', 'dog', 'pets', 'animal', 'fur baby'],
  'dating': ['relationship', 'seeing someone', 'going out', 'girlfriend', 'boyfriend'],
  'marriage': ['married', 'wedding', 'engaged', 'engagement'],
  'divorce': ['separated', 'split up', 'broke up', 'ex'],
  'baby': ['infant', 'newborn', 'toddler', 'little one'],
  'birthday': ['bday', 'born', 'turning', 'anniversary'],

  // Housing & living
  'home': ['house', 'apartment', 'flat', 'condo', 'place', 'residence'],
  'rent': ['renting', 'tenant', 'landlord', 'lease'],
  'mortgage': ['home loan', 'house payment', 'monthly payment'],
  'move': ['moving', 'relocate', 'relocation', 'new place'],
  'renovation': ['remodel', 'remodeling', 'fixing up', 'home improvement'],
  'commute': ['commuting', 'travel to work', 'daily drive', 'train ride'],

  // Finance & money
  'money': ['cash', 'funds', 'finance', 'finances', 'financial'],
  'invest': ['investment', 'investing', 'portfolio', 'stocks', 'crypto'],
  'save': ['savings', 'saving up', 'piggy bank', 'emergency fund'],
  'debt': ['loan', 'owe', 'borrowed', 'credit card debt'],
  'budget': ['budgeting', 'spending', 'expenses', 'cost'],
  'insurance': ['coverage', 'policy', 'premium', 'insured'],

  // Education
  'school': ['university', 'college', 'campus', 'alma mater', 'class'],
  'degree': ['diploma', 'bachelor', 'master', 'MBA', 'PhD', 'graduate'],
  'study': ['studying', 'learning', 'coursework', 'homework', 'exam'],
  'teacher': ['professor', 'instructor', 'tutor', 'lecturer'],
  'roommate': ['flatmate', 'housemate', 'dorm mate'],
  'graduation': ['graduated', 'commencement', 'finishing school'],

  // Transportation
  'car': ['vehicle', 'automobile', 'ride', 'drive', 'driving'],
  'flight': ['airplane', 'plane', 'flying', 'airline', 'airport'],
  'train': ['railway', 'rail', 'metro', 'subway'],
  'trip': ['travel', 'vacation', 'holiday', 'journey', 'getaway'],
  'commute': ['daily travel', 'getting to work', 'ride to work'],

  // Food & dining
  'restaurant': ['dining', 'eat out', 'eatery', 'place to eat', 'dine'],
  'cook': ['cooking', 'prepare', 'make food', 'kitchen', 'homemade'],
  'breakfast': ['morning meal', 'brunch'],
  'lunch': ['midday meal', 'lunchtime'],
  'dinner': ['supper', 'evening meal', 'dinnertime'],
  'vegetarian': ['vegan', 'plant-based', 'meatless'],
  'recipe': ['dish', 'meal prep', 'ingredients', 'how to make'],

  // Hobbies & leisure
  'movie': ['film', 'cinema', 'theater', 'watching', 'streaming'],
  'book': ['reading', 'novel', 'read', 'literature', 'author'],
  'game': ['gaming', 'video game', 'playing', 'gamer'],
  'music': ['song', 'listen', 'playlist', 'concert', 'band'],
  'sport': ['sports', 'athletic', 'playing', 'team'],
  'running': ['jogging', 'jog', 'marathon', 'half marathon', 'run'],
  'hiking': ['hike', 'trail', 'trekking', 'outdoor'],
  'photography': ['photo', 'camera', 'pictures', 'shooting'],
  'volunteer': ['volunteering', 'charity', 'community service', 'giving back'],
  'hobby': ['hobbies', 'pastime', 'side project', 'interest', 'passion'],

  // Technology
  'laptop': ['computer', 'notebook', 'PC', 'MacBook', 'ThinkPad'],
  'phone': ['mobile', 'smartphone', 'iPhone', 'Android', 'cell phone'],
  'app': ['application', 'software', 'program', 'tool'],
  'update': ['upgrade', 'new version', 'patch', 'release'],
  'password': ['credential', 'login', 'authentication', 'passphrase'],
  'wifi': ['internet', 'network', 'connection', 'online'],

  // Time expressions
  'morning': ['AM', 'sunrise', 'early', 'dawn', 'wake up'],
  'evening': ['PM', 'night', 'sunset', 'dusk', 'nighttime'],
  'routine': ['habit', 'daily', 'regular', 'schedule', 'ritual'],
  'weekend': ['Saturday', 'Sunday', 'day off', 'off day'],

  // General adjectives
  'big': ['large', 'huge', 'massive', 'enormous'],
  'small': ['tiny', 'little', 'mini', 'compact'],
  'fast': ['quick', 'rapid', 'speedy', 'swift'],
  'slow': ['sluggish', 'lagging', 'delayed'],
  'good': ['great', 'excellent', 'awesome', 'fantastic', 'wonderful'],
  'bad': ['terrible', 'awful', 'horrible', 'poor', 'worst'],
  'old': ['ancient', 'vintage', 'previous', 'former', 'used to'],
  'new': ['latest', 'recent', 'fresh', 'brand new', 'modern'],
  'important': ['critical', 'crucial', 'essential', 'vital', 'key'],

  // ── 美食烹饪扩展（100组）──────────────────────────────────────────────
  '馄饨': ['云吞', '抄手', '扁食', '馄饨汤'],
  '烧饼': ['芝麻烧饼', '油酥烧饼', '肉夹馍'],
  '麻辣烫': ['串串香', '冒菜', '关东煮', '麻辣拌'],
  '寿喜烧': ['sukiyaki', '日式火锅', '寿喜锅'],
  '咖喱': ['curry', '咖喱饭', '日式咖喱', '印度咖喱'],
  '泡面': ['方便面', '速食面', '杯面', '泡面吃'],
  '三明治': ['sandwich', '夹心面包', '帕尼尼'],
  '蒸': ['蒸菜', '蒸鱼', '蒸蛋', '清蒸', '上锅蒸'],
  '煮': ['煮菜', '水煮', '白煮', '煮饭'],
  '炸': ['油炸', '炸鸡', '炸薯条', '深炸', '煎炸'],
  '烤': ['烘烤', '烧烤', '炭烤', '焗烤', '烤箱'],
  '炖': ['炖肉', '慢炖', '小火炖', '煨', '焖'],
  '醋': ['陈醋', '米醋', '白醋', '香醋', 'vinegar'],
  '盐': ['食盐', '粗盐', '海盐', '加盐', 'salt'],
  '糖': ['白糖', '冰糖', '红糖', '蔗糖', 'sugar'],
  '饼干': ['曲奇', 'cookie', '苏打饼', '威化'],
  '白酒': ['baijiu', '茅台', '二锅头', '白的'],
  '红酒': ['wine', '葡萄酒', '干红', '红的'],
  '茶': ['tea', '绿茶', '红茶', '乌龙茶', '普洱'],
  '红薯': ['地瓜', '番薯', '烤红薯', 'sweet potato'],
  '豆浆': ['soy milk', '豆奶', '早餐豆浆'],
  '油条': ['炸油条', '豆浆油条', '早点'],
  '月饼': ['mooncake', '中秋', '五仁月饼', '广式月饼'],
  '粽子': ['端午', '肉粽', '甜粽', '包粽子'],
  '汤圆': ['元宵', '芝麻汤圆', '吃汤圆'],
  '春卷': ['spring roll', '炸春卷', '春卷皮'],
  '年糕': ['糍粑', '炒年糕', '年糕片'],
  '螺蛳粉': ['螺蛳', '柳州螺蛳粉', '臭味粉'],
  '酸辣粉': ['红薯粉', '粉丝', '重庆酸辣粉'],
  '臭豆腐': ['长沙臭豆腐', '炸臭豆腐', '臭干子'],
  '煎蛋': ['荷包蛋', '煎鸡蛋', '太阳蛋', '溏心蛋'],
  '炒饭': ['蛋炒饭', '扬州炒饭', '炒米饭'],
  '盖浇饭': ['盖饭', '浇头饭', '快餐'],
  '黄焖鸡': ['黄焖鸡米饭', '焖锅', '鸡煲'],
  '麻婆豆腐': ['麻辣豆腐', '川菜豆腐'],
  '宫保鸡丁': ['宫爆鸡丁', '川菜鸡丁'],
  '糖醋排骨': ['糖醋里脊', '糖醋鱼', '糖醋'],
  '回锅肉': ['川菜', '回锅', '蒜苗回锅肉'],
  '水煮鱼': ['水煮肉片', '麻辣鱼', '沸腾鱼'],
  '小龙虾': ['龙虾', '蒜蓉小龙虾', '十三香龙虾', '虾'],
  '烤串': ['羊肉串', '烤韭菜', '烤面筋', '烤生蚝'],
  '卤味': ['卤肉', '卤鸡爪', '卤鸭脖', '绝味'],
  '腊肉': ['腊肠', '咸肉', '腌肉', '熏肉'],
  '螃蟹': ['大闸蟹', '帝王蟹', '蟹', '吃蟹'],
  '龙虾': ['波士顿龙虾', '小龙虾', '龙虾尾'],
  '牛腩': ['炖牛腩', '牛腩面', '红烧牛腩'],
  '鸡翅': ['可乐鸡翅', '烤鸡翅', '炸鸡翅', '翅中'],
  '排骨': ['红烧排骨', '糖醋排骨', '蒸排骨', '炖排骨'],
  '豆角': ['四季豆', '长豆角', '炒豆角'],
  '椰子': ['coconut', '椰汁', '椰奶', '椰子鸡'],
  '牛奶': ['milk', '鲜奶', '酸奶', '纯牛奶'],
  '酸奶': ['yogurt', '希腊酸奶', '益生菌'],
  '面包': ['bread', '吐司', '全麦面包', '法棍'],
  '黄油': ['butter', '奶油', '芝士', '芝士片'],
  '番茄酱': ['ketchup', '辣酱', '蒜蓉酱', '沙拉酱'],
  '葱姜蒜': ['葱', '姜', '蒜', '爆香', '炝锅'],
  '腐乳': ['豆腐乳', '臭腐乳', '下饭菜'],
  '泡菜': ['kimchi', '韩国泡菜', '酸菜', '腌菜'],
  '干锅': ['干锅花菜', '干锅鸡', '干锅虾'],
  '拌面': ['热干面', '凉面', '炸酱面', '担担面'],
  '刀削面': ['山西面', '手擀面', '拉条子'],
  '肉夹馍': ['凉皮', '陕西小吃', '腊汁肉'],
  '凉皮': ['麻酱凉皮', '秦镇凉皮', '擀面皮'],
  '煎饺': ['锅贴', '生煎', '煎包', '锅贴饺'],
  '豆花': ['豆腐花', '甜豆花', '咸豆花'],
  '炸酱面': ['老北京炸酱面', '杂酱面', '打卤面'],
  '鱼香肉丝': ['鱼香茄子', '鱼香味', '川菜经典'],
  '东坡肉': ['红烧肉', '五花肉', '慢炖肉'],
  '白切鸡': ['白斩鸡', '粤菜', '鸡肉'],
  '叉烧': ['蜜汁叉烧', '港式', '叉烧饭'],
  '烤鱼': ['纸包鱼', '万州烤鱼', '重庆烤鱼'],
  '酸菜鱼': ['酸汤鱼', '鱼片', '酸菜'],
  '蛋挞': ['葡式蛋挞', '港式蛋挞', '烘焙'],
  '提拉米苏': ['tiramisu', '意式甜点', '咖啡蛋糕'],
  '抹茶': ['matcha', '抹茶拿铁', '抹茶蛋糕', '抹茶味'],
  '椰奶': ['coconut milk', '椰浆', '椰子水'],
  '柠檬水': ['柠檬茶', '柠檬汁', '蜂蜜柠檬'],
  '姜茶': ['红糖姜茶', '暖胃', '驱寒'],
  '养生粥': ['红豆粥', '小米粥', '皮蛋瘦肉粥', '杂粮粥'],
  '卤鸡爪': ['泡椒凤爪', '鸡爪', '柠檬鸡爪'],
  '鸡排': ['炸鸡排', '大鸡排', '鸡排饭'],
  '热狗': ['hotdog', '香肠', '烤肠'],
  '薯条': ['fries', '炸薯条', '粗薯条', '细薯条'],
  '关东煮': ['日式关东煮', '便利店', '煮物'],

  // ── 社交媒体/互联网/数码（100组）──────────────────────────────────────
  '快手': ['快手直播', '老铁', '双击', '短视频平台'],
  '知乎': ['知乎问答', '刷知乎', '知乎回答', '大V'],
  '豆瓣': ['豆瓣评分', '影评', '书评', '豆瓣小组'],
  '贴吧': ['百度贴吧', '吧友', '贴吧帖子'],
  '4G': ['5G', '流量', '信号', '移动网络', '蜂窝'],
  '流量': ['数据流量', '流量套餐', '超流量', '流量不够'],
  '收藏': ['mark', '书签', '收藏夹', '收起来'],
  '私信': ['DM', '私聊', '发消息', '站内信'],
  '拉黑': ['屏蔽', '黑名单', '不看他', '删好友'],
  '取关': ['取消关注', '掉粉', '脱粉'],
  '头像': ['avatar', '换头像', '照片', '形象'],
  '昵称': ['网名', '名字', '备注名', '用户名'],
  '群聊': ['微信群', '群消息', '群公告', '拉群'],
  '话题': ['hashtag', '标签', '讨论', '热门话题'],
  '直播间': ['直播房间', '直播', '看直播', '进直播间'],
  '打赏': ['送礼物', '刷礼物', '打钱', '投币'],
  '网暴': ['网络暴力', '键盘侠', '喷子', '人肉'],
  '杠精': ['抬杠', '找茬', '杠', '挑刺'],
  '水军': ['刷评论', '带节奏', '控评', '营销号'],
  '营销号': ['自媒体', '蹭热度', '标题党', '洗稿'],
  '视频号': ['微信视频号', '发视频', '短视频'],
  'AI': ['人工智能', '智能', 'ChatGPT', 'AI助手'],
  'ChatGPT': ['GPT', 'AI聊天', '智能助手', 'OpenAI'],
  'VPN': ['翻墙', '梯子', '科学上网', '代理'],
  '翻墙': ['VPN', '梯子', '科学上网', '出墙'],
  '云盘': ['网盘', '百度网盘', '阿里云盘', '存储'],
  '蓝牙': ['bluetooth', '蓝牙耳机', '配对', '无线'],
  '充电宝': ['移动电源', '充电', '共享充电宝'],
  '智能手表': ['Apple Watch', '手环', '可穿戴', '运动手表'],
  '平板': ['iPad', '平板电脑', 'tablet', '安卓平板'],
  '投影仪': ['投影', '幕布', '家庭影院', '便携投影'],
  '路由器': ['router', '信号', '网速', '千兆'],
  '摄像头': ['监控', '摄像', '安防', '门铃摄像头'],
  '无人机': ['drone', '航拍', '大疆', '飞行器'],
  'VR': ['虚拟现实', 'VR眼镜', '元宇宙', 'AR'],
  '元宇宙': ['metaverse', '虚拟世界', '数字人', 'Web3'],
  '区块链': ['blockchain', 'Web3', 'NFT', '去中心化'],
  'NFT': ['数字藏品', '加密艺术', '数字资产'],
  '人脸识别': ['刷脸', '面部识别', '生物识别'],
  '指纹': ['指纹解锁', '指纹识别', '生物识别'],
  '截图': ['screenshot', '屏幕截图', '录屏', '截屏'],
  '下载': ['download', '下载安装', '离线下载', '缓存'],
  '上传': ['upload', '传文件', '分享文件', '发送文件'],
  '压缩': ['zip', '解压', '压缩包', '打包'],
  '格式': ['文件格式', 'PDF', 'JPG', 'PNG', '格式转换'],
  '存储': ['storage', '硬盘', 'SSD', '内存卡', '扩容'],
  '备份': ['backup', '云备份', '数据备份', '恢复'],
  '杀毒': ['杀毒软件', '病毒', '木马', '安全软件'],
  '弹窗': ['广告弹窗', '推送', '通知', '烦人广告'],
  '卡顿': ['卡', '不流畅', '延迟', '掉帧'],
  '死机': ['蓝屏', '黑屏', '重启', '卡死'],
  '闪退': ['crash', '崩了', '退出', '应用闪退'],
  '验证码': ['短信验证', '图形验证', '滑块验证', '人机验证'],
  '二维码': ['QR code', '扫码', '二维码扫描'],
  '小程序': ['微信小程序', '支付宝小程序', '轻应用'],
  'APP': ['应用', '手机应用', '软件', '客户端'],
  '支付宝': ['Alipay', '花呗', '余额宝', '蚂蚁'],
  '微信支付': ['扫码付', '零钱', '微信转账'],
  '网速': ['带宽', '下载速度', '上传速度', '测速'],
  '服务器崩了': ['宕机', '服务器挂了', '502', '503'],
  '数据泄露': ['信息泄露', '隐私泄露', '被盗号', '信息安全'],
  '钓鱼网站': ['诈骗', '钓鱼链接', '假网站', '诈骗短信'],
  '网络诈骗': ['电信诈骗', '杀猪盘', '骗局', '被骗'],
  'emoji': ['表情', '符号', '颜文字', '特殊符号'],
  '算法推荐': ['信息茧房', '推荐算法', '猜你喜欢', '千人千面'],
  '大数据': ['数据分析', '数据挖掘', '数据驱动', 'big data'],
  '云计算': ['cloud', '云服务', 'SaaS', 'PaaS'],
  '物联网': ['IoT', '智能家居', '智能设备', '万物互联'],
  '自动驾驶': ['无人驾驶', '智能驾驶', 'L4', '自动泊车'],
  '机器人': ['robot', '智能机器人', 'bot', '机械臂'],
  '芯片': ['chip', '处理器', 'CPU', 'GPU', '半导体'],
  '显卡': ['GPU', '显存', 'N卡', 'A卡', '独显'],
  'SSD': ['固态硬盘', '硬盘', '机械硬盘', '存储盘'],
  '内存': ['RAM', '运行内存', '8G', '16G', '32G'],
  '屏幕': ['display', '显示屏', 'OLED', 'LCD', '分辨率'],
  '浏览器': ['browser', 'Chrome', 'Safari', 'Firefox', '上网'],
  '搜索引擎': ['百度', 'Google', '搜索', '搜一下'],
  '论坛': ['forum', '社区', '板块', '灌水'],
  '博客': ['blog', '个人博客', '写博客', '技术博客'],
  '播客节目': ['podcast', '音频节目', '电台'],
  '订阅': ['subscribe', '关注', '订阅号', 'RSS'],
  '推送通知': ['push', '消息推送', '通知', '提醒'],
  '人工客服': ['在线客服', '智能客服', '机器人客服', '转人工'],
  '语音助手': ['Siri', '小爱同学', '天猫精灵', '语音控制'],
  '智能音箱': ['HomePod', '小爱', '天猫精灵', '小度'],
  '扫地机器人': ['扫地机', '石头扫地机', '自动清扫'],
  '智能门锁': ['指纹锁', '密码锁', '电子锁', '智能锁'],
  '空气净化器': ['净化器', '滤芯', 'PM2.5', '新风'],
  '电动牙刷': ['声波牙刷', '牙刷头', 'Oral-B'],
  '数据线': ['充电线', 'Type-C', 'Lightning', 'USB'],
  '转接头': ['适配器', '扩展坞', 'hub', '拓展'],
  '贴膜': ['手机膜', '钢化膜', '屏幕保护'],
  '手机壳': ['保护壳', '手机套', '透明壳'],
  '二手手机': ['二手机', '翻新机', '官翻'],
  '以旧换新': ['折抵', '旧机回收', '换新'],
  '科技新闻': ['数码圈', '发布会', '新品', '科技媒体'],
  '开发者': ['developer', '程序员', '码农', '工程师'],
  '用户体验': ['UX', '交互设计', '用户研究', '体验优化'],
  '暗黑模式': ['dark mode', '夜间模式', '深色模式'],

  // ── 影视音乐娱乐（100组）──────────────────────────────────────────────
  '综艺节目': ['选秀', '脱口秀', '喜剧', '真人秀节目'],
  '流行音乐': ['pop', '流行歌', '流行曲', '热门歌'],
  'R&B': ['节奏布鲁斯', '灵魂乐', 'soul'],
  '金属': ['metal', '重金属', '死亡金属', '硬核'],
  '朋克': ['punk', '朋克摇滚', '叛逆', '独立朋克'],
  '嘻哈': ['hip-hop', '说唱', 'rapper', '嘻哈文化'],
  '贝斯': ['bass', '贝斯手', '低音吉他'],
  '萨克斯': ['saxophone', '吹萨克斯', '管乐'],
  '音乐节': ['festival', '草莓音乐节', '迷笛', '户外音乐'],
  '网易云': ['网易云音乐', '云村', 'QQ音乐', '听歌App'],
  'QQ音乐': ['听歌', '音乐App', '酷狗', '酷我'],
  '翻唱': ['cover', '翻唱歌曲', '改编', '重新演绎'],
  '原创': ['原创音乐', '原创歌曲', '自己写的'],
  '歌词': ['lyrics', '歌词本', '写歌词'],
  '喜剧片': ['comedy', '搞笑电影', '喜剧电影', '笑死'],
  '悬疑片': ['thriller', '悬疑电影', '推理', '悬念'],
  '战争片': ['war film', '军事', '战争电影', '历史战争'],
  '武侠': ['武侠片', '江湖', '功夫', '仙侠'],
  '动画电影': ['animated', '皮克斯', '迪士尼', '宫崎骏'],
  '漫画': ['manga', '漫画书', '连载', '看漫画'],
  '番剧': ['anime', '新番', '追番', '日番'],
  '二次元': ['ACG', '动漫文化', '宅', '二次元文化'],
  'cosplay': ['cos', '角色扮演', 'coser', '扮装'],
  '漫展': ['动漫展', 'CJ', 'CP', '漫展活动'],
  '轻小说': ['light novel', '轻小说改编', '日轻'],
  '网文': ['网络小说', '网文小说', '起点', '阅文'],
  '玄幻': ['仙侠', '修真', '奇幻', '异世界'],
  '穿越': ['穿越剧', '穿越小说', '重生', '时空穿越'],
  '脱口秀': ['stand-up', '单口喜剧', '脱口秀演员', '段子'],
  '相声': ['说相声', '捧哏', '逗哏', '郭德纲'],
  '小品': ['春晚小品', '喜剧小品', '赵本山'],
  '话剧': ['舞台剧', 'theater', '演话剧', '看话剧'],
  '歌剧': ['opera', '歌唱', '声乐', '歌剧院'],
  '芭蕾': ['ballet', '芭蕾舞', '舞者', '跳芭蕾'],
  '音乐剧': ['musical', '百老汇', '舞台', '剧场'],
  '影评': ['movie review', '豆瓣影评', '电影评论', '影评人'],
  '明星': ['star', '偶像', '艺人', '明星八卦'],
  '粉丝经济': ['饭圈', '应援', '打投', '追星'],
  '选秀': ['选秀节目', '出道', '偶像练习生', '创造营'],
  '颁奖': ['颁奖典礼', '奥斯卡', '金马', '金鸡'],
  '奥斯卡': ['Oscar', '学院奖', '奥斯卡金像奖'],
  '金曲': ['经典歌曲', '热门歌曲', '排行榜', '榜单'],
  '排行榜': ['榜单', '热歌榜', '新歌榜', 'chart'],
  '彩蛋': ['easter egg', '隐藏剧情', '片尾彩蛋', '惊喜'],
  '剧透': ['spoiler', '透剧', '剧透警告', '泄露剧情'],
  '弃剧': ['不看了', '烂尾', '看不下去', '太烂了'],
  '催更': ['更新', '连载', '等更新', '什么时候更'],
  '完结': ['大结局', '完结撒花', '全剧终', '结局'],
  '经典': ['classic', '神作', '巅峰', '必看'],
  '烂片': ['烂剧', '垃圾片', '浪费时间', '雷剧'],
  '预告': ['trailer', '预告片', '先导片', '花絮'],
  '首映': ['premiere', '首映式', '上映', '公映'],
  '院线': ['电影院', '影院', '大银幕', '排片'],
  '流媒体': ['streaming', '在线观看', '点播', '视频平台'],
  '爱奇艺': ['优酷', '腾讯视频', '芒果TV', '视频网站'],
  'Netflix': ['网飞', '奈飞', '流媒体平台', 'Disney+'],
  'Spotify': ['Apple Music', '音乐平台', '听歌软件'],
  '乐评': ['music review', '乐评人', '音乐评论'],
  'MV': ['music video', '音乐视频', '拍MV', 'PV'],
  '专辑': ['album', '新专辑', '发专辑', '出碟'],
  '单曲': ['single', '新单曲', '主打歌'],
  '巡演': ['tour', '世界巡演', '全国巡演', '巡回演出'],
  '安可': ['encore', '加场', '返场', '再来一首'],
  '应援': ['应援棒', '灯海', '粉丝应援', '打call'],
  '混音': ['remix', '重混', 'DJ混音', '重制'],
  '采样': ['sample', '取样', '音乐采样'],
  '编曲': ['arrangement', '作曲', '配乐', '制作人'],
  '调音': ['mixing', '混音', '音效', '音频处理'],
  '后期': ['post-production', '剪辑', '特效', '调色'],
  '剪辑': ['editing', '视频剪辑', '剪片', '后期剪辑'],
  '特效': ['VFX', '视觉特效', 'CGI', '绿幕'],
  '配音': ['dubbing', '配音演员', '声优', '中配'],
  '声优': ['CV', '日本声优', '配音', '角色声音'],
  '字幕': ['subtitle', '中文字幕', '外挂字幕', '翻译'],
  '弹幕文化': ['弹幕梗', '满屏弹幕', '弹幕互动'],
  '独立电影': ['indie', '文艺片', '小众电影', '独立制作'],
  '黑色电影': ['film noir', '犯罪片', '黑暗风格'],
  '公路片': ['road movie', '自驾旅行', '在路上'],
  '默片': ['silent film', '无声电影', '卓别林'],
  '短片': ['short film', '微电影', '短片电影'],
  '奥特曼': ['ultraman', '特摄', '怪兽', '变身'],
  '高达': ['gundam', '机甲', '机器人动画'],
  '海贼王': ['one piece', '路飞', '航海王'],
  '火影忍者': ['naruto', '忍者', '鸣人'],
  '进击的巨人': ['巨人', '立体机动', '调查兵团'],
  '鬼灭之刃': ['鬼灭', '炭治郎', '呼吸法'],
  '间谍过家家': ['SPY×FAMILY', '阿尼亚', '间谍家家酒'],
  '新海诚': ['你的名字', '天气之子', '铃芽之旅'],
  '吉卜力': ['宫崎骏', '龙猫', '千与千寻', '哈尔'],
  '韩剧': ['K-drama', '韩国电视剧', '追韩剧'],
  '美剧': ['美国电视剧', '追美剧', 'HBO', 'Netflix剧'],
  '日剧': ['日本电视剧', '追日剧', '日剧跑'],
  '国产剧': ['国剧', '内地剧', '古装剧', '都市剧'],
  '偶像剧': ['台剧', '甜宠剧', '恋爱剧'],
  '悬疑剧': ['烧脑剧', '推理剧', '犯罪剧'],
  '古装剧': ['仙侠剧', '宫斗', '权谋'],
  '真人秀': ['reality show', '素人', '明星真人秀'],
  '旅行综艺': ['旅游节目', '花少', '向往的生活'],
  '音乐综艺': ['歌手', '好声音', '我是歌手', '音乐节目'],
  '喜剧综艺': ['一年一度喜剧大赛', '喜剧节目', '脱口秀大会'],

  // ── 购物消费（100组）──────────────────────────────────────────────────
  '淘宝': ['taobao', '淘宝网', '天猫', '逛淘宝'],
  '京东': ['JD', '京东商城', '京东自营', '京东快递'],
  '拼多多': ['拼团', '砍价', '拼单', '百亿补贴'],
  '天猫': ['Tmall', '天猫旗舰店', '天猫超市'],
  '双十二': ['双12', '年终大促', '购物节'],
  '618': ['京东618', '年中大促', '618大促'],
  '尾款': ['付尾款', '尾款人', '补差价'],
  '购物车': ['加购', '购物车清空', '凑满减'],
  '清空购物车': ['下单', '全买了', '付款'],
  '运费': ['快递费', '运费险', '到付'],
  '海淘': ['海外直邮', '转运', '跨境电商', '亚马逊'],
  '免税': ['免税店', '机场免税', '日上'],
  '退款': ['退钱', '退回', '仅退款', '退货退款'],
  '保修': ['warranty', '质保', '保修期', '延保'],
  '评价': ['评论', '买家秀', '晒单', '评分'],
  '买家秀': ['晒图', '实物图', '真实评价'],
  '旗舰店': ['官方店', '专卖店', '品牌店'],
  '发货': ['已发货', '待发货', '催发货', '发快递'],
  '潮牌': ['潮流品牌', '街头品牌', '联名', '限量'],
  '联名': ['联名款', '合作款', '联名商品', '跨界'],
  '限量': ['限量版', '稀有', '限定', '绝版'],
  '折扣店': ['奥特莱斯', 'outlets', '工厂店', '特卖'],
  '返利': ['返现', '佣金', '推广', '返利网'],
  '分期': ['分期付款', '花呗分期', '白条', '免息'],
  '花呗': ['蚂蚁花呗', '花呗分期', '先买后付'],
  '白条': ['京东白条', '白条分期', '信用购'],
  '信用购': ['先享后付', '免息', '赊账'],
  '比价': ['价格对比', '历史价格', '全网比价'],
  '囤货': ['囤', '备货', '屯货', '多买几个'],
  '凑单': ['凑满减', '凑包邮', '加购凑'],
  '剁手': ['买买买', '忍不住', '钱包哭了', '吃土'],
  '吃土': ['月底吃土', '穷了', '花光了', '没钱了'],
  '省钱攻略': ['薅羊毛', '省钱秘籍', '低价攻略'],
  '薅羊毛': ['羊毛党', '优惠', '白嫖', '便宜'],
  '品质': ['质量', '做工', '用料', '品控'],
  '尺码': ['size', '偏大', '偏小', '尺寸'],
  '颜色': ['color', '色号', '配色', '色系'],
  '款式': ['style', '样式', '版型', '型号'],
  '库存': ['stock', '有货', '缺货', '补货'],
  '预约购买': ['抢购', '预约', '定闹钟', '开抢'],
  '发票': ['开发票', '电子发票', '报销'],
  '支付': ['付款', '付钱', '结账', '支付方式'],
  '货到付款': ['到付', 'COD', '先验后付'],
  '拆包裹': ['开箱', '拆快递', '开包裹', 'unboxing'],
  '开箱': ['unboxing', '开箱视频', '拆箱', '晒开箱'],
  '种草清单': ['购物清单', '必买清单', '推荐清单'],
  '黑五': ['Black Friday', '黑色星期五', '海外促销'],
  'Prime Day': ['亚马逊会员日', '海外大促'],
  '直播购物': ['直播买', '直播间买', '主播推荐'],
  '样品': ['试用装', '小样', '赠品', '体验装'],
  '赠品': ['买赠', '附赠', '送赠品', '满赠'],
  '抽奖': ['抽签', '中奖', '幸运抽奖', '抽奖活动'],
  '团购': ['拼团', '组团', '团购价', '开团'],
  '砍价': ['讲价', '还价', '砍一刀', '便宜点'],
  '试穿': ['试衣', '试戴', '试用', '体验'],
  '定制': ['个性化', '私人定制', '专属', '手工'],
  '二手': ['二手闲置', '闲鱼', '旧货', '转让'],
  '闲鱼': ['二手平台', '转转', '闲置', '二手交易'],
  '奢侈品': ['luxury', '大牌', '名牌', '奢侈'],
  '化妆品': ['美妆', '护肤品', '彩妆', 'cosmetics'],
  '护肤品': ['skincare', '面霜', '精华', '水乳'],
  '口红': ['lipstick', '唇膏', '色号', '涂口红'],
  '香水': ['perfume', '香氛', '喷香水', '香味'],
  '服装': ['衣服', '服饰', '穿搭', 'fashion'],
  '鞋子': ['shoes', '球鞋', '运动鞋', '拖鞋'],
  '球鞋': ['sneaker', 'AJ', '椰子', '运动鞋', '限量鞋'],
  '手表': ['watch', '腕表', '智能手表', '机械表'],
  '数码产品': ['电子产品', '数码', '3C', '科技产品'],
  '家电': ['电器', '家用电器', '小家电', '大家电'],
  '家具': ['furniture', '沙发', '桌椅', '柜子'],
  '生鲜': ['蔬果', '新鲜食材', '冷链', '生鲜配送'],
  '童装': ['儿童服装', '宝宝衣服', '童鞋'],
  '内衣': ['文胸', '内裤', '贴身衣物', 'underwear'],
  '防晒': ['防晒霜', '遮阳', '防晒衣', 'SPF'],
  '眼霜': ['眼部护理', '去黑眼圈', '抗皱'],
  '面膜': ['贴片面膜', '泥膜', '睡眠面膜', '补水面膜'],
  '洗面奶': ['洁面乳', '洗脸', '清洁', '氨基酸洗面奶'],
  '沐浴露': ['body wash', '沐浴', '身体清洁'],
  '洗发水': ['shampoo', '护发素', '洗护'],
  '牙膏': ['刷牙', '牙刷', '口腔护理', 'toothpaste'],
  '纸巾': ['抽纸', '湿巾', '卫生纸', '手帕纸'],
  '垃圾袋': ['垃圾桶', '保鲜袋', '收纳袋'],
  '充电器': ['充电头', '快充', '无线充电', '充电套装'],
  '数据线': ['充电线', 'Type-C', 'Lightning', '数据传输'],
  '行李箱': ['拉杆箱', '旅行箱', '登机箱', '托运箱'],
  '背包': ['双肩包', '书包', '电脑包', 'backpack'],
  '钱包': ['wallet', '卡包', '零钱包', '长款钱包'],
  '眼镜': ['glasses', '墨镜', '太阳镜', '镜框'],
  '帽子': ['hat', '棒球帽', '渔夫帽', '遮阳帽'],
  '围巾': ['scarf', '丝巾', '披肩', '围脖'],
  '手套': ['gloves', '保暖手套', '触屏手套'],
  '袜子': ['socks', '棉袜', '长袜', '船袜'],
  '保温杯': ['水杯', '保温壶', '随行杯', '杯子'],
  '雨伞': ['umbrella', '折叠伞', '太阳伞', '自动伞'],
  '文具': ['笔', '本子', '文具盒', '办公用品'],
  '玩具': ['toy', '积木', '乐高', '毛绒玩具'],

  // ── 日常生活/习惯（100组）──────────────────────────────────────────────
  '起床': ['醒来', '起来', '闹钟响', '睁眼'],
  '赖床': ['不想起', '贪睡', '再睡会', '起不来'],
  '洗脸': ['洁面', '卸妆', '洗面奶', '清洁面部'],
  '洗头': ['洗发', '洗头发', '洗发水', '护发素'],
  '护肤': ['涂面霜', '涂防晒', '敷面膜', '保养'],
  '敷面膜': ['面膜', '贴面膜', '护理', '补水'],
  '吹头发': ['吹风机', '吹干', '造型'],
  '换衣服': ['穿衣服', '搭配', '选衣服', '换装'],
  '挤地铁': ['挤公交', '早高峰', '人太多', '沙丁鱼'],
  '刷卡': ['打卡', '刷交通卡', '扫码乘车'],
  '等电梯': ['坐电梯', '挤电梯', '电梯间'],
  '午饭': ['中饭', '午餐', '中午吃什么', '工作餐'],
  '下午茶': ['teatime', '点心', '喝下午茶', '摸鱼吃'],
  '洗衣服': ['洗衣机', '晾衣服', '熨烫', '洗涤'],
  '晾衣服': ['晒衣服', '晾晒', '挂衣服', '收衣服'],
  '叠衣服': ['整理衣服', '收纳', '叠被子'],
  '拖地': ['擦地', '拖把', '湿拖', '干拖'],
  '擦窗': ['擦玻璃', '擦窗户', '清洁窗户'],
  '洗碗': ['刷碗', '洗碗机', '洗盘子', '清洗餐具'],
  '收拾房间': ['整理房间', '打扫房间', '收拾屋子'],
  '换床单': ['换被套', '洗床单', '换床品'],
  '浇花': ['养花', '浇水', '花草', '绿植'],
  '养鱼': ['鱼缸', '换水', '喂鱼', '观赏鱼'],
  '喂宠物': ['喂猫', '喂狗', '宠物粮', '宠物零食'],
  '煮咖啡': ['冲咖啡', '磨豆', '咖啡机', '手冲'],
  '泡茶': ['茶道', '喝茶', '泡功夫茶', '煮茶'],
  '看新闻': ['刷新闻', '今日头条', '看报', '早报'],
  '看天气': ['天气预报', '查天气', '明天天气'],
  '带伞': ['带雨伞', '下雨了', '忘带伞'],
  '化妆出门': ['打扮', '画妆', '出门前', '照镜子'],
  '接孩子': ['接放学', '幼儿园接', '学校门口等'],
  '辅导作业': ['陪写作业', '检查作业', '家庭作业'],
  '哄睡': ['哄宝宝', '讲故事', '睡前故事', '摇篮曲'],
  '做早操': ['晨操', '广播体操', '晨间运动'],
  '逛超市': ['超市购物', '买菜', '逛商场'],
  '买菜': ['菜市场', '买食材', '挑菜', '选菜'],
  '下班': ['收工', '下班了', '准时下班', '到点走'],
  '回家': ['回去', '到家', '到家了', '回家路上'],
  '换拖鞋': ['进门换鞋', '室内鞋', '穿拖鞋'],
  '洗手': ['清洁双手', '洗手液', '消毒'],
  '换睡衣': ['穿睡衣', '换舒服的', '家居服'],
  '看电视': ['看节目', '遥控器', '换台', '看频道'],
  '玩手机': ['刷手机', '看手机', '手机不离手', '屏幕时间'],
  '夜宵': ['吃夜宵', '宵夜', '半夜吃', '深夜食堂'],
  '敷热水袋': ['暖水袋', '暖宝宝', '取暖'],
  '关灯': ['开灯', '灯光', '台灯', '夜灯'],
  '设闹钟': ['定闹钟', '闹钟几点', '提醒'],
  '充手机': ['充电', '手机快没电', '充电器', '充电线'],
  '锁门': ['关门', '反锁', '带钥匙', '门禁'],
  '穿外套': ['加衣服', '穿多点', '冷了加衣'],
  '戴口罩': ['口罩', '防护', '出门戴口罩'],
  '擦桌子': ['擦台面', '抹布', '清洁桌面'],
  '收快递': ['取包裹', '快递柜', '菜鸟驿站', '丰巢'],
  '寄快递': ['寄东西', '发货', '叫快递员'],
  '交水电费': ['交房租', '缴费', '水电煤', '物业费'],
  '修东西': ['修理', '坏了修', '维修', '找师傅'],
  '换灯泡': ['灯坏了', '换灯', '修灯'],
  '通马桶': ['马桶堵了', '厕所堵了', '下水道'],
  '倒水': ['接水', '喝水', '热水', '凉白开'],
  '午觉': ['午睡', '小憩', '打个盹', '眯一会'],
  '伸懒腰': ['舒展', '活动筋骨', '起来动动'],
  '眼保健操': ['护眼', '休息眼睛', '看远处'],
  '称体重': ['上秤', '体重计', '每日称重'],
  '记日记': ['写日记', '日记本', '每日记录', '手账'],
  '手账': ['手帐', '记录', 'bullet journal', '手账本'],
  '冥想': ['meditation', '正念', '打坐', '静心'],
  '泡脚': ['热水泡脚', '足浴', '养生'],
  '喝牛奶': ['睡前奶', '热牛奶', '一杯牛奶'],
  '刷剧': ['追剧', '看剧', '煲剧', '一口气看完'],
  '刷视频': ['短视频', '刷抖音', '刷快手', '刷B站'],
  '叫外卖': ['点餐', '外卖到了', '等外卖', '外卖小哥'],
  '等公交': ['等车', '公交站', '车还没来'],
  '赶路': ['赶时间', '快迟到了', '加快脚步'],
  '雨天': ['下雨', '下雨了', '阴天', '暴雨'],
  '降温': ['变冷', '冷空气', '降温了', '入冬'],
  '升温': ['变热', '热了', '回暖', '升温了'],
  '换季': ['季节交替', '换季衣服', '春秋换季'],
  '过周末': ['周末安排', '休息日', '双休', '放松'],
  '宅家': ['窝家', '在家待着', '哪都不去', '家里蹲'],
  '逛公园': ['去公园', '公园散步', '户外活动'],
  '逛商场': ['逛街', '商场购物', '橱窗', '血拼'],
  '约朋友': ['约人', '叫上朋友', '一起出去玩'],
  '聚餐': ['吃饭', '饭局', '约饭', '团建吃饭'],
  '烤肉': ['韩式烤肉', '日式烤肉', '自助烤肉', '烤五花肉'],
  '自助餐': ['buffet', '自助', '不限量', '吃回本'],
  '吃火锅': ['涮火锅', '约火锅', '火锅局'],
  '拍全家福': ['家庭照', '合影', '全家合照'],
  '大扫除': ['年终大扫除', '全屋清洁', '彻底打扫'],
  '换季收纳': ['收纳整理', '衣柜整理', '换季衣物'],
  '网上办事': ['线上办理', '电子政务', '网上预约'],
  '排号等位': ['等叫号', '取号', '排队等'],
  '剪指甲': ['指甲钳', '修指甲', '美甲'],
  '理发预约': ['预约理发', '剪头', '约Tony'],
  '晒太阳': ['日光浴', '晒', '晒暖'],
  '打哈欠': ['困了', '犯困', '瞌睡'],
  '翻身': ['辗转', '翻来覆去', '睡不着翻身'],
  '开窗通风': ['通风', '换气', '开窗'],
  '关窗': ['关窗户', '窗户关了', '怕冷关窗'],
  '开空调': ['空调', '制冷', '制暖', '调温度'],
  '开暖气': ['暖气', '暖风', '供暖', '地暖'],
  '喂奶': ['母乳', '奶瓶', '冲奶粉', '喂宝宝'],
  '换尿布': ['尿不湿', '纸尿裤', '换尿裤'],
  '背单词': ['记单词', '单词本', '背英语', '词汇'],
  '弹琴': ['练琴', '弹钢琴', '弹吉他', '练习'],
  '做手工': ['手工', 'DIY', '手作', '编织'],
  '种菜': ['种地', '菜园', '阳台种菜', '蔬菜种植'],
  '喂鸟': ['养鸟', '鸟笼', '鸟食'],
  '遛小孩': ['带小孩', '推婴儿车', '户外玩'],
  '接电话': ['打电话', '通话', '来电话了'],
  '回消息': ['回微信', '回复', '看消息'],
  '订外卖': ['点外卖', '下单外卖', '叫餐'],
  '找钥匙': ['钥匙丢了', '钥匙在哪', '忘带钥匙'],
  '修眉': ['修眉毛', '画眉', '眉笔'],
  '擦鞋': ['刷鞋', '洗鞋', '鞋油'],
  '补觉': ['补眠', '多睡会', '睡回笼觉'],
  '做梦': ['梦', '噩梦', '梦境', '做了个梦'],
  '打嗝': ['嗝', '止嗝', '打了个嗝'],
  '打喷嚏': ['喷嚏', '鼻子痒', 'achoo'],
  '伸展': ['拉伸', '做拉伸', '伸展运动'],
  '跳绳': ['跳绳运动', '花式跳绳', '跳绳减肥'],
}
// ── Per-language synonym storage ──
const _synonymsByLang = new Map<string, Record<string, string[]>>()
// 反向索引：word → [key1, key2, ...]（加速 expandQuery 反向查找）
const _reverseIndex = new Map<string, Map<string, string[]>>()  // lang → (word → keys[])

function buildReverseIndex(lang: string, syns: Record<string, string[]>): void {
  const idx = new Map<string, string[]>()
  for (const [key, words] of Object.entries(syns)) {
    for (const w of words) {
      if (!idx.has(w)) idx.set(w, [])
      idx.get(w)!.push(key)
    }
  }
  _reverseIndex.set(lang, idx)
}

/** Classify a synonym key as 'zh' or 'en' */
function classifySynonymLang(key: string): string {
  return detectLanguage(key)
}

/** Split _defaultSynonyms by language */
function splitDefaultSynonyms(): { zh: Record<string, string[]>; en: Record<string, string[]> } {
  const zh: Record<string, string[]> = {}
  const en: Record<string, string[]> = {}
  for (const [key, syns] of Object.entries(_defaultSynonyms)) {
    const lang = classifySynonymLang(key)
    if (lang === 'zh') zh[key] = syns
    else en[key] = syns
  }
  return { zh, en }
}

function getSynonyms(lang: string): Record<string, string[]> {
  if (!_synonymsByLang.has(lang)) {
    const p = synonymsPath(lang)
    const defaults = splitDefaultSynonyms()
    const seed = (defaults as any)[lang] || {}
    const loaded = loadJson<Record<string, string[]>>(p, seed)
    // Merge code defaults into loaded (ensure new code synonyms aren't lost)
    let dirty = false
    for (const [key, syns] of Object.entries(seed)) {
      if (!loaded[key]) { loaded[key] = syns; dirty = true }
      else {
        for (const syn of syns) {
          if (!loaded[key].includes(syn)) { loaded[key].push(syn); dirty = true }
        }
      }
    }
    // 自动加载 data/seeds-{lang}.json 扩展种子（零代码添加新语言/新种子）
    try {
      const seedPath = resolve(DATA_DIR, `seeds-${lang}.json`)
      if (existsSync(seedPath)) {
        const seeds = JSON.parse(readFileSync(seedPath, 'utf-8'))
        let seedCount = 0
        for (const [key, syns] of Object.entries(seeds)) {
          if (key.startsWith('_')) continue  // 跳过 _meta 等元数据
          if (!loaded[key]) { loaded[key] = syns as string[]; dirty = true; seedCount++ }
          else {
            for (const syn of (syns as string[])) {
              if (!loaded[key].includes(syn)) { loaded[key].push(syn); dirty = true }
            }
          }
        }
        if (seedCount > 0) console.log(`[cc-soul][aam] loaded ${seedCount} seed groups from seeds-${lang}.json`)
      }
    } catch {}
    if (dirty || !existsSync(p)) debouncedSave(p, loaded)
    _synonymsByLang.set(lang, loaded)
    buildReverseIndex(lang, loaded)  // 构建反向索引（10000 组 ~5ms）
  }
  return _synonymsByLang.get(lang)!
}

// Backward compat: migrate old single aam_synonyms.json → per-language
function migrateOldSynonyms(): void {
  const oldPath = resolve(DATA_DIR, 'aam_synonyms.json')
  if (!existsSync(oldPath)) return
  if (existsSync(synonymsPath('zh'))) return // already migrated
  try {
    const old = loadJson<Record<string, string[]>>(oldPath, {})
    const zh: Record<string, string[]> = {}
    const en: Record<string, string[]> = {}
    for (const [key, syns] of Object.entries(old)) {
      // 清洗：key 是 junk → 跳过，syns 里的 junk → 过滤掉
      if (/^[\u4e00-\u9fff]{2}$/.test(key) && isJunkToken(key)) continue
      const cleanSyns = syns.filter(s => {
        if (typeof s !== 'string' || s.length < 2) return false
        if (/^[\u4e00-\u9fff]{2}$/.test(s) && isJunkToken(s)) return false
        return true
      })
      if (cleanSyns.length === 0) continue
      if (classifySynonymLang(key) === 'zh') zh[key] = cleanSyns
      else en[key] = cleanSyns
    }
    if (Object.keys(zh).length > 0) debouncedSave(synonymsPath('zh'), zh)
    if (Object.keys(en).length > 0) debouncedSave(synonymsPath('en'), en)
    console.log(`[cc-soul][aam] migrated legacy aam_synonyms.json → per-language (zh: ${Object.keys(zh).length}, en: ${Object.keys(en).length})`)
  } catch {}
}
migrateOldSynonyms()

// Eagerly initialize zh and en synonym caches
let COLD_START_SYNONYMS = getSynonyms('zh')
const _enSynonyms = getSynonyms('en')

// ── 首次启动 LLM 种子翻译（语言自适应冷启动）──
// 检测用户语言 → 有 LLM 时一次性翻译种子词 → 缓存永不再调
// 没有 LLM → 跳过，靠 emoji + AAM 自学习 + 中英双语种子兜底
let _seedTranslationDone = false
export function maybeTranslateSeedsForLanguage(userMsg: string): void {
  if (_seedTranslationDone) return
  _seedTranslationDone = true  // 只尝试一次

  // 检测语言：非中非英才需要翻译
  const hasCJK = /[\u4e00-\u9fff]/.test(userMsg)
  const hasLatin = /[a-zA-Z]{3,}/.test(userMsg)
  if (hasCJK || hasLatin) return  // 中文或英文 → 种子已覆盖，不需要翻译

  // 检测 LLM 可用性
  try {
    const { hasLLM, spawnCLI } = require('./cli.ts')
    if (!hasLLM()) return  // 没 LLM → 降级到 emoji + AAM 自学习

    // 提取少量种子词（不翻译全部，只翻译高频情绪词）
    const coreSeedWords = ['开心', '难过', '生气', '害怕', '喜欢', '讨厌', '工作', '家人', '朋友', '健康']
    spawnCLI(
      `Translate these Chinese words to the SAME language as this message: "${userMsg.slice(0, 50)}"\n\nWords: ${coreSeedWords.join(', ')}\n\nOutput format: one translation per line, same order. Just the word, no explanation.`,
      (output: string) => {
        if (!output || output.length < 10) return
        const translations = output.split('\n').map(l => l.trim()).filter(l => l.length >= 1)
        if (translations.length < 5) return  // 翻译结果太少，不可靠

        // 把翻译结果加入同义词表
        for (let i = 0; i < Math.min(coreSeedWords.length, translations.length); i++) {
          const key = coreSeedWords[i]
          const translated = translations[i].toLowerCase()
          if (!COLD_START_SYNONYMS[key]) COLD_START_SYNONYMS[key] = []
          if (!COLD_START_SYNONYMS[key].includes(translated)) {
            COLD_START_SYNONYMS[key].push(translated)
          }
        }
        debouncedSave(synonymsPath('zh'), COLD_START_SYNONYMS)
        try { require('./decision-log.ts').logDecision('seed_translation', userMsg.slice(0, 20), `translated ${translations.length} seed words`) } catch {}
      },
      15000
    )
  } catch {}
}

/**
 * 种子注入：把 COLD_START_SYNONYMS 写入 cooccur，初始 count=2
 * 关键：种子词必须通过 tokenizer 分词后再注入，保证粒度匹配
 * 例如 "心情好" 会被拆成 ["心情", "情好"]，每个片段都跟对应的同义词建关联
 */
// Seeds 注入策略：df 全量注入 + cooccur 只注入 key↔synonym 直接对（不做笛卡尔积）
// 这样 PMI 路径仍然可用，但 cooccur 规模从 500K → ~50K（可控）
function injectSeedsLite(lang: string) {
  const net = getNetwork(lang)
  const syns = getSynonyms(lang)
  let dfInjected = 0, pairInjected = 0

  for (const [word, synonyms] of Object.entries(syns)) {
    // 1. df 全量注入
    const allWords = [word, ...synonyms]
    for (const w of allWords) {
      if (w.length >= 2 && !net.df[w]) { net.df[w] = 2; dfInjected++ }
      const cjk = w.match(/[\u4e00-\u9fff]+/g) || []
      for (const seg of cjk) {
        for (let i = 0; i <= seg.length - 2; i++) {
          const sub = seg.slice(i, i + 2)
          if (!net.df[sub]) { net.df[sub] = 2; dfInjected++ }
        }
      }
    }

    // 2. cooccur 只注入 key↔每个 synonym 的直接对（不做 synonym×synonym 笛卡尔积）
    const keyToken = word.length >= 2 ? word.toLowerCase() : null
    if (!keyToken) continue
    for (const syn of synonyms) {
      const synToken = syn.length >= 2 ? syn.toLowerCase() : null
      if (!synToken || synToken === keyToken) continue
      const existing = net.cooccur[keyToken]?.[synToken] ?? 0
      if (existing < 2) {
        if (!net.cooccur[keyToken]) net.cooccur[keyToken] = {}
        net.cooccur[keyToken][synToken] = 2
        if (!net.cooccur[synToken]) net.cooccur[synToken] = {}
        net.cooccur[synToken][keyToken] = 2
        pairInjected++
      }
    }
  }
  if (dfInjected > 0 || pairInjected > 0) {
    console.log(`[cc-soul][aam] seed injection [${lang}]: ${dfInjected} df + ${pairInjected} cooccur pairs`)
  }
}
injectSeedsLite('zh')
injectSeedsLite('en')

/**
 * Tokenize text into words for association network.
 * CJK: 2-3 char segments. English: 3+ letter words.
 */
/** tokenize → 使用统一的 tokenize('standard') from memory-utils.ts */
function tokenize(text: string): string[] {
  return _utilTokenize(text, 'standard')
}

// Stop words to filter out noise (CJK 2-grams that are grammatical, not semantic)
const STOPWORDS_PATH = resolve(DATA_DIR, 'aam_stopwords.json')
const _defaultStopWords = [
  '的', '了', '是', '在', '我', '你', '他', '她', '它', '们',
  '不', '有', '这', '那', '就', '也', '和', '但', '还', '都',
  '会', '能', '可以', '什么', '怎么', '为什么', '吗', '呢', '吧',
  '很', '太', '最', '比较', '非常', '一个', '一些', '一下',
  '被问', '问了', '到了', '试完', '完拿', '拿到', '期间', '多了',
  '二天', '太多', '班太', '第二', '上吃', '想学', '写了',
  '试被', '天称', '称重', '重涨', '涨了', '少吃', '点疼',
  'the', 'and', 'for', 'that', 'this', 'with', 'from', 'are', 'was',
  'not', 'but', 'have', 'has', 'had', 'will', 'can', 'you', 'your',
]
const STOP_WORDS = new Set(loadJson<string[]>(STOPWORDS_PATH, _defaultStopWords))

function filterStopWords(words: string[]): string[] {
  return words.filter(w => !STOP_WORDS.has(w) && w.length >= 2)
}

// ── 两阶段 junk-token 过滤器 ──
// Phase 1 (always): CJK 虚词表，O(1) 预过滤
// Phase 2 (≥50 memories): 数据驱动统计（fanOut / avgPMI / dfRatio）
const CJK_FUNCTION_CHARS = new Set(
  '的了是在很不也都就有我你他她它和与被把让给会能要想到过着吗呢吧啊么'
)

export function isJunkToken(word: string): boolean {
  // 只对 CJK 2-gram 做判断
  if (word.length !== 2 || !/^[\u4e00-\u9fff]{2}$/.test(word)) return false

  // Phase 1: 虚词表——两个字都是虚词才算 junk（"的了"、"是在"）
  // 单字虚词不过滤，因为 "什么"、"不好"、"很好" 等包含虚词字但本身是合法词
  const chars = [...word]
  if (chars.length === 2 && CJK_FUNCTION_CHARS.has(chars[0]) && CJK_FUNCTION_CHARS.has(chars[1])) return true

  // Phase 2: 数据驱动（需要足够样本）
  if (network().totalDocs >= 50) {
    const cooc = network().cooccur[word]
    const fanOut = cooc ? Object.keys(cooc).length : 0
    const df = network().df[word] || 0
    const dfRatio = df / Math.max(1, network().totalDocs)

    // 计算 avgPMI
    let avgPMI = 0
    if (cooc && fanOut > 0) {
      let sum = 0
      for (const partner of Object.keys(cooc)) {
        sum += pmi(word, partner)
      }
      avgPMI = sum / fanOut
    }

    // Junk if ANY:
    if (fanOut > 20 && avgPMI < 1.0) return true   // 弱连接太多词
    if (dfRatio > 0.3) return true                   // 太常见
    if (fanOut <= 1 && df <= 1) return true           // 太罕见，偶然碎片
  }

  return false
}

// ── 概念层级（模块级，供 isKnownWord 和 expandQuery 共用）──
const CONCEPT_HIERARCHY: Record<string, string[]> = {
  // ── 健康 & 身体 & 健康隐患 ──
  '健康': ['血压','血糖','体重','睡眠','失眠','运动','体检','感冒','过敏','头疼','腰疼','视力','近视','手术','减肥','疫苗','中暑','哮喘','胃炎','牙疼','膝盖','腰椎','颈椎'],
  '身体': ['血压','血糖','体重','睡眠','运动','体检','疲劳','腰疼','身高','体温','心率'],
  '健康隐患': ['过敏','近视','血压','血糖','失眠','胃炎','腰疼','颈椎','焦虑','肥胖'],
  '健康问题': ['血压','血糖','过敏','失眠','近视','胃炎','头疼','腰疼','手术','减肥','哮喘','焦虑'],

  // ── 经济 & 财务 & 理财 & 经济压力 ──
  '经济': ['房贷','工资','薪资','收入','股票','理财','储蓄','花销','信用卡','贷款','税','亏钱','欠款'],
  '财务': ['房贷','工资','收入','股票','理财','储蓄','信用卡','贷款','预算'],
  '理财': ['投资','存款','基金','股票','房贷','车贷','保险','预算','收入','花销','定投'],
  '经济压力': ['房贷','车贷','贷款','欠款','亏钱','花销','信用卡','工资','涨薪','开销'],
  '收入变化': ['涨薪','降薪','奖金','跳槽','加薪','升职','绩效'],

  // ── 学校 & 教育 & 考试 ──
  '学校': ['大学','母校','高中','专业','导师','论文','考试','学位','毕业','同学'],
  '教育': ['大学','母校','高中','专业','导师','论文','考试','培训','学位','认证','考证','自学','网课'],
  '考试': ['考研','考公','雅思','托福','PMP','驾照','教资','认证','考证'],

  // ── 工作 & 职业 & 职业规划 ──
  '工作': ['加班','薪资','同事','老板','项目','晋升','跳槽','面试','绩效','裸辞','简历','远程','出差','创业','自由职业','认证','PMP'],
  '职业': ['加班','薪资','晋升','跳槽','面试','绩效','转行'],
  '职业规划': ['跳槽','面试','转行','晋升','简历','考证','培训','换工作','创业','目标'],
  '工作累': ['加班','疲惫','压力','996','熬夜','休息','年假','倦怠'],

  // ── 家庭 & 家人 ──
  '家庭': ['父母','爸妈','孩子','老婆','老公','宠物','猫','狗','家务','怀孕','预产期','生孩子','婚礼','生日','搬家','装修'],
  '家人': ['父母','爸妈','孩子','老婆','老公','兄弟','姐妹','女儿','儿子','爷爷','奶奶','外婆','舅舅'],

  // ── 住房 & 居住 ──
  '住房': ['房贷','租房','装修','搬家','小区','物业','房东','房价'],
  '居住': ['房贷','租房','装修','搬家','小区','房东'],

  // ── 饮食 & 吃 & 饮品 & 美食 ──
  '饮食': ['减肥','外卖','做饭','食谱','早餐','午餐','晚餐','零食','营养','忌口','香菜','过敏食物','素食','辣','甜','厨艺','拿手菜'],
  '吃': ['外卖','做饭','早餐','午餐','晚餐','零食','火锅','奶茶'],
  '饮品': ['咖啡','茶','奶茶','果汁','可乐','啤酒','红酒'],
  '美食': ['火锅','烤肉','拉面','寿司','烧烤','甜品','小吃','海鲜','川菜','粤菜','日料','西餐','拿手菜','外卖'],

  // ── 厨艺 ──
  '厨艺': ['做饭','烹饪','炒菜','蒸','煮','烤','拿手菜','菜谱','下厨房'],

  // ── 出行 & 交通 ──
  '出行': ['开车','地铁','航班','出差','高铁','打车','堵车','机票','通勤','共享单车','驾照','自驾','骑行'],
  '交通': ['开车','地铁','高铁','打车','堵车','公交'],

  // ── 情绪 & 心理 & 烦恼 & 恐惧 ──
  '情绪': ['焦虑','压力','抑郁','失眠','开心','烦躁','疲惫','孤独','崩溃','恐惧','社恐','紧张','害怕','恐惧症'],
  '心理': ['焦虑','压力','抑郁','失眠','烦躁','孤独','咨询','心理医生','冥想'],
  '烦恼': ['压力','焦虑','亏钱','加班','失眠','吵架','纠结'],
  '恐惧': ['害怕','怕','恐惧症','紧张','蛇','高处','打针','黑暗','蜘蛛','密闭','深水'],
  '担心': ['焦虑','压力','怀孕','晋升','健康','钱','加班','失业','考试','孩子'],

  // ── 社交 & 人际 & 社交媒体 ──
  '社交': ['朋友','同事','邻居','相亲','聚会','吵架','分手','约会','微信','朋友圈','小红书','社交媒体','八卦'],
  '人际': ['朋友','同事','邻居','吵架','沟通','冲突'],
  '社交媒体': ['抖音','微博','微信','小红书','B站','朋友圈','短视频'],

  // ── 技术 & 编程 ──
  '技术': ['服务器','数据库','部署','前端','后端','bug','框架','API','运维','AI','大模型','ChatGPT','区块链','云计算'],
  '编程': ['服务器','数据库','前端','后端','bug','框架','API','算法'],

  // ── 娱乐 & 休闲 & 影视 & 音乐 ──
  '娱乐': ['电影','游戏','音乐','跑步','旅游','看书','刷剧','综艺','抖音','短视频','动漫','B站','直播','网红'],
  '休闲': ['电影','游戏','音乐','旅游','看书','刷剧'],
  '影视': ['电影','电视剧','综艺','纪录片','动漫','番剧','院线'],
  '音乐': ['演唱会','摇滚','说唱','rap','古典','民谣','乐器','吉他','钢琴'],

  // ── 生育 ──
  '生育': ['怀孕','预产期','产检','当爸爸','当妈妈','生孩子','月子','爸爸','妈妈'],

  // ── 购物 & 数码 ──
  '购物': ['买车','买房','网购','下单','退货','特斯拉','车','手机','淘宝','京东','拼多多','双十一','打折','快递'],
  '数码': ['手机','电脑','平板','耳机','相机','手表','充电宝'],

  // ── 童年 & 回忆 ──
  '童年': ['小时候','儿时','回忆','老家','农村','校园','玩伴','玩具'],
  '回忆': ['童年','大学','第一次','难忘','以前','当年'],

  // ── 梦想 & 自我提升 ──
  '梦想': ['目标','理想','愿望','计划','创业','开店','攒钱','未来'],
  '自我提升': ['学习','读书','考证','培训','语言','技能','认证','日语','英语','健身'],

  // ── 坏习惯 ──
  '坏习惯': ['戒','改掉','克服','上瘾','熬夜','抽烟','喝酒','拖延','戒烟','戒酒'],

  // ── 宠物护理 ──
  '宠物护理': ['猫粮','狗粮','宠物医院','洗澡','遛狗','铲屎'],

  // ── 季节 ──
  '季节': ['春天','夏天','秋天','冬天','花开','下雪','落叶'],

  // ── 个人特质 & 性格 ──
  '个人特质': ['性格','怕','喜欢','讨厌','习惯','特长','缺点'],
  '性格': ['急躁','内向','外向','正义','纠结','独立','敏感','乐观','悲观','执着','随和','倔强'],

  // ── 日常 & 晨练 ──
  '日常': ['起床','通勤','午休','下班','散步','洗澡','睡觉'],
  '晨练': ['跑步','晨跑','早起','锻炼','运动','瑜伽','太极'],

  // ── 运动 & 锻炼 ──
  '运动爱好': ['跑步','篮球','羽毛球','游泳','健身','瑜伽','骑行','登山','足球','乒乓球','网球','滑雪'],
  '锻炼': ['跑步','健身','游泳','瑜伽','篮球','羽毛球','登山','骑行','散步'],

  // ── 课外活动 ──
  '课外活动': ['钢琴','舞蹈','画画','书法','围棋','跆拳道','游泳','篮球','足球','编程','英语'],

  // ── 矛盾 & 碎片时间 & 仪式感 ──
  '矛盾': ['纠结','犹豫','两难','取舍','冲突','明知故犯','忍不住'],
  '碎片时间': ['抖音','微博','B站','播客','小红书','刷手机','短视频','朋友圈'],
  '仪式感': ['咖啡','早起','散步','冥想','日记','整理','打扫','洗漱','视频通话'],

  // ── English concept hierarchy（expanded for LOCOMO coverage）──
  'health': ['blood pressure','weight','sleep','insomnia','exercise','checkup','allergy','headache','vision','surgery','medication','therapy','diet','symptoms','diagnosis','prescription','hospital','doctor','mental health','chronic','asthma','inhaler','cholesterol','knee','back pain','concussion','heart rate'],
  'health problems': ['allergy','asthma','blood pressure','cholesterol','knee','back pain','concussion','insomnia','anxiety','peanut','shellfish','vision','contacts','prescription','inhaler','surgery'],
  'breathing': ['asthma','inhaler','allergy','lung','respiratory'],
  'medical': ['doctor','surgery','prescription','hospital','checkup','therapy','chiropractor','dental','EpiPen','inhaler','cholesterol','blood type','diagnosis'],
  'finance': ['mortgage','salary','income','stocks','savings','budget','credit card','loan','tax','investment','retirement','insurance','debt','expenses','bonus','raise','rent','bills','financial','401k','Roth IRA','index funds','emergency fund','crypto'],
  'retirement': ['401k','Roth IRA','index funds','pension','savings','investment','retirement fund','social security'],
  'credit': ['credit score','credit card','loan','debt','mortgage','interest rate','FICO'],
  'income': ['salary','bonus','raise','wages','paycheck','compensation','earnings','promotion'],
  'career': ['overtime','colleague','boss','project','promotion','interview','resume','performance','coworker','manager','meeting','deadline','startup','business','company','hired','fired','layoff','remote','office','mentor','mentoring','review'],
  'career progress': ['promoted','promotion','raise','bonus','review','performance','title','senior','lead','manager'],
  'employer': ['company','work at','Microsoft','Google','startup','office','job','hired','position'],
  'work setup': ['laptop','desk','standing desk','monitor','keyboard','office','remote','ThinkPad','chair','screen'],
  'office culture': ['coworker','manager','standup','meeting','Jira','team','donuts','coffee','lunch'],
  'family': ['parents','children','kids','spouse','partner','pets','cat','dog','mother','father','sister','brother','son','daughter','wife','husband','wedding','divorce','pregnant','baby','grandma','uncle','niece','nephew','cousin','in-law'],
  'children': ['son','daughter','kid','school','soccer','birthday','grade','coaching','homework','recital','vet dream','daycare'],
  'extended family': ['uncle','aunt','cousin','niece','nephew','grandma','grandfather','in-law','father-in-law','mother-in-law'],
  'housing': ['mortgage','rent','renovation','moving','apartment','landlord','house','condo','neighborhood','lease','furniture','kitchen','bedroom','backyard','property'],
  'real estate': ['mortgage','house','condo','rent','buy','sell','property','renovation','kitchen','sold','bedroom','HOA','closing'],
  'food': ['takeout','cooking','recipe','breakfast','lunch','dinner','snack','restaurant','cuisine','diet','vegetarian','baking','groceries','meal prep','favorite food','allergic','pasta','sourdough','brew','hot sauce'],
  'food restrictions': ['allergy','allergic','shellfish','peanut','vegetarian','vegan','lactose','gluten','intolerance','EpiPen','celiac'],
  'beverages': ['coffee','tea','beer','wine','oat milk','brew','Chemex','latte','espresso','cocktail','juice','smoothie'],
  'transport': ['driving','subway','flight','business trip','train','taxi','traffic','commute','bus','car','parking','road trip','uber','bicycle','walk'],
  'emotion': ['anxiety','stress','depression','insomnia','happiness','frustration','loneliness','burnout','excited','worried','angry','sad','grateful','confident','overwhelmed','jealous','cry','nightmares','panic','therapy'],
  'weakness': ['overthink','procrastinate','road rage','impatient','anxious','stubborn','perfectionist','indecisive'],
  'fear': ['afraid','phobia','heights','spiders','public speaking','dark','claustrophobic','needles','snakes','deep water'],
  'social': ['friend','colleague','neighbor','dating','party','argument','breakup','relationship','wedding','reunion','group','community','club','gathering','social media','poker','game night','book club','BBQ'],
  'gatherings': ['poker','game night','book club','BBQ','party','reunion','dinner party','brunch','meetup','potluck'],
  'tech': ['server','database','deploy','frontend','backend','bug','framework','api','devops','cloud','machine learning','software','hardware','app','website','coding','React','TypeScript','Python','JavaScript'],
  'entertainment': ['movie','game','music','running','travel','reading','streaming','concert','show','podcast','book','series','hobby','sport','painting','photography'],
  'education': ['school','college','university','degree','professor','thesis','exam','grade','scholarship','class','major','student','homework','graduation','campus','bootcamp','MBA','hackathon','TA'],
  'academic': ['thesis','hackathon','scholarship','TA','dean','GPA','honors','research','published','peer review'],
  'personality': ['introvert','extrovert','shy','confident','patient','impatient','organized','messy','punctual','procrastinate','competitive','easygoing','stubborn','generous','ambitious','left-handed','morning person'],
  'travel': ['vacation','trip','flight','hotel','destination','passport','sightseeing','backpacking','beach','mountain','abroad','tourist','itinerary','luggage','adventure','Tokyo','Bali','Italy','London'],
  'travel documents': ['passport','visa','ID','driver license','TSA','boarding pass','vaccination card'],
  'hobby': ['running','hiking','yoga','painting','gardening','cooking','photography','chess','reading','gaming','cycling','fishing','woodworking','singing','dancing','brewing','vinyl','guitar','puzzle','sourdough'],
  'crafts': ['woodworking','painting','photography','knitting','pottery','brewing','sourdough','vinyl','puzzle','scrapbook'],
  'exercise': ['running','marathon','gym','basketball','yoga','cycling','hiking','swimming','5K','half marathon','CrossFit','stretching','weights','cardio','heart rate'],
  'running': ['marathon','half marathon','5K','10K','pace','time','PR','race','training','treadmill'],
  'routine': ['morning','commute','lunch break','afternoon','evening','bedtime','wake up','coffee','workout','dinner','walk','shower','sleep','alarm','schedule'],
  'morning routine': ['wake up','alarm','coffee','meditate','gym','run','shower','breakfast','commute','journal'],
  'evening routine': ['dinner','walk','shower','journal','read','bedtime','white noise','melatonin','TV','stretch'],
  'self improvement': ['learning','Spanish','guitar','mentor','mentoring','reading','meditation','fasting','journal','screen time','therapy','bootcamp','MBA'],
  'life event': ['wedding','graduation','promotion','moving','birth','death','accident','surgery','retirement','engagement','breakup','new job','bought house','lost job'],
  'addiction': ['quit','drinking','smoking','screen time','gambling','caffeine','social media','gaming'],
  'appearance': ['tattoo','height','weight','gray hair','birthmark','glasses','contacts','left-handed','shoe size','haircut'],
  'body measurements': ['height','weight','shoe size','waist','BMI','blood type','vision','prescription'],
  'outdoor': ['garden','backyard','grill','fire pit','patio','deck','lawn','hiking','camping','picnic','BBQ'],
  'green energy': ['Tesla','solar panels','EV','electric car','hybrid','recycling','compost','carbon footprint'],
  'sleep': ['insomnia','white noise','melatonin','nightmare','snore','nap','bedtime','alarm','sleep schedule','mattress'],
  // ── T2 补充：具体→抽象上位概念（填补 vocabulary gap）──
  'instrument': ['guitar','piano','violin','drums','ukulele','bass','flute','saxophone','cello','trumpet','harmonica','keyboard','banjo','harp','clarinet','trombone','accordion','oboe'],
  'pet': ['dog','cat','fish','hamster','rabbit','bird','turtle','parrot','guinea pig','snake','lizard','ferret','goldfish','kitten','puppy'],
  'vehicle': ['car','truck','motorcycle','bicycle','scooter','van','suv','sedan','convertible','pickup','minivan','jeep','tesla'],
  'sport': ['basketball','football','soccer','baseball','tennis','golf','swimming','running','hiking','yoga','boxing','volleyball','cricket','surfing','skiing','badminton','wrestling'],
  'language': ['english','spanish','french','german','chinese','japanese','korean','portuguese','italian','arabic','hindi','russian','dutch','swedish','polish'],
  'cuisine type': ['italian','mexican','chinese','japanese','thai','indian','french','korean','mediterranean','american','vietnamese','greek','turkish','brazilian'],
  'color': ['red','blue','green','yellow','purple','orange','pink','black','white','brown','gray','gold','silver','turquoise','maroon'],
  'furniture': ['table','chair','sofa','bed','desk','shelf','cabinet','dresser','couch','bookcase','nightstand','recliner','ottoman'],
  'clothing': ['shirt','pants','dress','jacket','coat','sweater','jeans','skirt','shoes','boots','hat','scarf','gloves','socks','hoodie'],
  'fruit': ['apple','banana','orange','grape','strawberry','mango','pineapple','watermelon','peach','blueberry','cherry','kiwi','pear','plum'],
  'illness': ['cold','flu','fever','headache','stomachache','allergy','asthma','diabetes','arthritis','migraine','infection','pneumonia','bronchitis','anemia'],
  'weather condition': ['rain','snow','sunshine','wind','storm','fog','hail','thunder','lightning','drizzle','hurricane','tornado','heat wave','frost'],
  'room': ['bedroom','kitchen','bathroom','living room','dining room','garage','attic','basement','office','hallway','closet','nursery','den'],
  'tree': ['oak','maple','pine','birch','willow','cedar','palm','cherry blossom','redwood','elm','cypress','spruce'],
  'flower': ['rose','tulip','daisy','sunflower','lily','orchid','lavender','chrysanthemum','peony','violet','jasmine','hibiscus'],
  'board game': ['chess','checkers','monopoly','scrabble','risk','settlers','catan','ticket to ride','clue','trivial pursuit'],
  'dance': ['ballet','salsa','tango','waltz','hip hop','contemporary','jazz','swing','breakdance','tap','ballroom','flamenco'],
  'art form': ['painting','sculpture','photography','pottery','drawing','calligraphy','ceramics','mosaic','watercolor','oil painting','sketching'],
  'gemstone': ['diamond','ruby','emerald','sapphire','amethyst','topaz','opal','jade','pearl','turquoise','garnet'],
  // ── LOCOMO 失败模式补充：抽象查询词→具体活动/物品 ──
  'activity': ['hiking','painting','pottery','camping','reading','cooking','gardening','dancing','swimming','running','yoga','fishing','knitting','singing','playing','volunteering','traveling','shopping'],
  'destress': ['paint','read','hike','yoga','meditate','garden','cook','walk','swim','exercise','music','sleep','bath','journal'],
  'relax': ['paint','read','hike','yoga','meditate','garden','cook','walk','swim','exercise','music','sleep','bath','nap','unwind'],
  'partake': ['participate','join','attend','do','engage','take part','involved','active'],
  'identity': ['transgender','gender','orientation','sexuality','queer','nonbinary','pronouns','transition','coming out','LGBTQ'],
  'book': ['novel','story','read','reading','author','title','fiction','nonfiction','memoir','biography','literature'],
  'camp': ['camping','tent','campfire','outdoor','campsite','campground','trail','backpack','wilderness','nature trip'],
  'event': ['conference','parade','rally','meeting','workshop','seminar','festival','gathering','celebration','ceremony','fundraiser','race','charity','support group'],
  'participate': ['attend','join','volunteer','contribute','engage','take part','involved','active','signed up','registered'],
}

// ── T2: CONCEPT_HIERARCHY 反向索引（child → parent keys）──
const _conceptReverseIndex = new Map<string, string[]>()
function buildConceptReverseIndex(): void {
  _conceptReverseIndex.clear()
  for (const [parent, children] of Object.entries(CONCEPT_HIERARCHY)) {
    const pl = parent.toLowerCase()
    for (const child of children) {
      const cl = child.toLowerCase()
      if (!_conceptReverseIndex.has(cl)) _conceptReverseIndex.set(cl, [])
      const arr = _conceptReverseIndex.get(cl)!
      if (!arr.includes(pl)) arr.push(pl)
    }
  }
}
buildConceptReverseIndex()


/** SSH: 查询一个词属于哪些语义维度（seeds 组 + concept parent）*/
export function getSemanticDimensions(word: string): string[] {
  const dims: string[] = []
  const wl = word.toLowerCase()
  const lang = detectLanguage(word)
  const seedKeys = _reverseIndex.get(lang)?.get(wl) || []
  for (const k of seedKeys) dims.push('s:' + k)
  const conceptKeys = _conceptReverseIndex.get(wl) || []
  for (const k of conceptKeys) dims.push('c:' + k)
  return dims
}

/** 判断一个 CJK 2-gram 是否是"已知词"（在同义词表或概念层级中出现过） */
let _knownWordCache = new Set<string>()
// 每次进程启动时重建
let _knownWordBuilt = false
export function isKnownWord(word: string): boolean {
  if (!_knownWordBuilt) {
    // 只信任人工维护的 _defaultSynonyms，不用 COLD_START_SYNONYMS（含 PMI 毕业垃圾如"你有"）
    for (const k of Object.keys(_defaultSynonyms)) _knownWordCache.add(k)
    for (const syns of Object.values(_defaultSynonyms)) {
      for (const s of syns) _knownWordCache.add(s)
    }
    for (const k of Object.keys(CONCEPT_HIERARCHY)) _knownWordCache.add(k)
    for (const children of Object.values(CONCEPT_HIERARCHY)) {
      for (const c of children) _knownWordCache.add(c)
    }
    _knownWordBuilt = true
  }
  return _knownWordCache.has(word)
}

/**
 * 清空 per-conversation 状态（benchmark per-conv 隔离用）
 * 保留：cooccur / df / totalDocs（长期学习到的关联知识）
 * 清空：activation damping / max-hop 负反馈 / prevMessageWords / temporal net
 */
export function resetLearnedData(): void {
  // 只清 per-conversation 短期状态，不碰 cooccur/df/totalDocs（长期知识）
  _activationDamping.clear()
  _maxHopByPattern.clear()
  _prevMessageWords = []
  // 清 temporal 网络（session 级别的时序关系）
  _temporalNet.directed = {}
}

/**
 * Feed a new memory into the association network.
 * Updates word co-occurrence counts.
 */
export function learnAssociation(content: string, emotionIntensity: number = 0, weight: number = 1.0, fullPair: boolean = false) {
  const lang = detectLanguage(content)
  _currentLang = lang
  const emotionMultiplier = emotionIntensity >= 0.7 ? 3 : emotionIntensity >= 0.5 ? 2 : 1
  const words = filterStopWords(tokenize(content))
  const unique = [...new Set(words)].filter(w => !isJunkToken(w))
  if (unique.length < 2) return

  if (emotionMultiplier > 1) {
    console.log(`[AAM] emotion-weighted learning: intensity=${emotionIntensity.toFixed(2)} multiplier=x${emotionMultiplier}`)
  }

  network().totalDocs++

  // Update document frequency (always uses unique — "how many docs contain this word")
  for (const w of unique) {
    network().df[w] = (network().df[w] || 0) + 1
  }

  const inc = emotionMultiplier * weight
  const _addPair = (w1: string, w2: string) => {
    if (!network().cooccur[w1]) network().cooccur[w1] = {}
    network().cooccur[w1][w2] = Math.min(50, (network().cooccur[w1][w2] || 0) + inc)
    if (!network().cooccur[w2]) network().cooccur[w2] = {}
    network().cooccur[w2][w1] = Math.min(50, (network().cooccur[w2][w1] || 0) + inc)
  }

  if (fullPair) {
    // 全配对模式：跨消息语义桥梁等场景使用
    for (let i = 0; i < unique.length; i++)
      for (let j = i + 1; j < unique.length; j++)
        _addPair(unique[i], unique[j])
  } else {
    // 滑动窗口模式（默认）：只有距离 ≤ 5 的词算共现，减少噪音 ~80%
    const WINDOW = 5
    const seen = new Set<string>()
    for (let i = 0; i < words.length; i++) {
      if (isJunkToken(words[i])) continue
      for (let j = i + 1; j < Math.min(i + WINDOW, words.length); j++) {
        if (isJunkToken(words[j])) continue
        if (words[i] === words[j]) continue
        const pair = words[i] < words[j] ? `${words[i]}\0${words[j]}` : `${words[j]}\0${words[i]}`
        if (seen.has(pair)) continue
        seen.add(pair)
        _addPair(words[i], words[j])
      }
    }
  }

  // Periodic save (every 10 documents) — benchmark 跳过（4.5MB JSON 写入慢）
  if (!process.env.CC_SOUL_BENCHMARK && network().totalDocs % 10 === 0) {
    debouncedSave(cooccurPath(lang), network())
  }

  // PMI 强关联自动毕业到同义词表（全动态，不需要人工维护）
  // 每 50 个文档检查一次，发现 PMI > 2.5 的词对自动写入 aam_synonyms_{lang}.json
  // benchmark 环境跳过（231K cooccur entries 遍历太慢）
  if (!process.env.CC_SOUL_BENCHMARK && network().totalDocs >= 30 && network().totalDocs % 50 === 0) {
    graduateStrongAssociations()
  }
}

/**
 * PMI 强关联自动毕业：从共现网络中发现的强关联自动写入同义词表
 * 不需要人工维护 500 组同义词——系统自己从用户数据中学到
 * 同义词表是"活的"，会随着用户聊天自动增长
 */
function graduateStrongAssociations() {
  const lang = _currentLang
  const syns = getSynonyms(lang)
  let graduated = 0
  for (const [w1, related] of Object.entries(network().cooccur)) {
    if (w1.length < 2) continue
    if (isJunkToken(w1)) continue
    for (const [w2, count] of Object.entries(related)) {
      if (w2.length < 2 || count < 5) continue  // 提高：count>=3 → count>=5
      if (isJunkToken(w2)) continue
      const p = pmi(w1, w2)
      if (p > 3.0) {  // 提高：PMI>2.5 → PMI>3.0
        // dfRatio 护栏：高频词间的关联大概率是噪音
        const totalDocs = Math.max(1, network().totalDocs)
        if ((network().df[w1] || 0) / totalDocs > 0.3 || (network().df[w2] || 0) / totalDocs > 0.3) continue
        // 双向 fanOut 护栏：两个都是 hub 才拒绝（单 hub+低频词的 PMI 仍有意义）
        const fanOut1 = Object.keys(network().cooccur[w1] || {}).length
        const fanOut2 = Object.keys(network().cooccur[w2] || {}).length
        if (fanOut1 > 50 && fanOut2 > 50) continue
        const existing = syns[w1]
        if (existing && existing.includes(w2)) continue
        if (!syns[w1]) syns[w1] = []
        syns[w1].push(w2)
        graduated++
      }
    }
  }
  if (graduated > 0) {
    debouncedSave(synonymsPath(lang), syns)
    console.log(`[cc-soul][aam] graduated ${graduated} strong associations to synonym table [${lang}] (total groups: ${Object.keys(syns).length})`)
  }
}

/**
 * Get PMI (Pointwise Mutual Information) between two words.
 * PMI > 0 means they co-occur more than expected by chance.
 */
export function pmi(w1: string, w2: string): number {
  const N = Math.max(1, network().totalDocs)
  const cooccurCount = network().cooccur[w1]?.[w2] || 0
  if (cooccurCount === 0) return 0
  const df1 = network().df[w1] || 1
  const df2 = network().df[w2] || 1
  // PMI = log2(P(w1,w2) / (P(w1) × P(w2)))
  const pmiVal = Math.log2((cooccurCount * N) / (df1 * df2))
  return Math.max(0, pmiVal) // Positive PMI only
}

// ── Union-Find PMI 聚类：自动从共现数据生成同义词簇 ──
const _ufParent = new Map<string, string>()
const _ufRank = new Map<string, number>()

function ufFind(x: string): string {
  if (!_ufParent.has(x)) { _ufParent.set(x, x); _ufRank.set(x, 0) }
  let root = x
  while (_ufParent.get(root) !== root) root = _ufParent.get(root)!
  // Path compression
  let curr = x
  while (curr !== root) {
    const next = _ufParent.get(curr)!
    _ufParent.set(curr, root)
    curr = next
  }
  return root
}

function ufUnion(a: string, b: string): void {
  const ra = ufFind(a), rb = ufFind(b)
  if (ra === rb) return
  const rankA = _ufRank.get(ra) || 0
  const rankB = _ufRank.get(rb) || 0
  if (rankA < rankB) _ufParent.set(ra, rb)
  else if (rankA > rankB) _ufParent.set(rb, ra)
  else { _ufParent.set(rb, ra); _ufRank.set(ra, rankA + 1) }
}

/** 缓存的 PMI 聚类结果（由 buildPMIClusters 填充）*/
let _pmiClusters = new Map<string, string[]>()
/** 反向索引：word → cluster root */
let _pmiClusterIndex = new Map<string, string>()

/**
 * Build synonym clusters from high-PMI pairs using Union-Find.
 * Called periodically (e.g., hourly from heartbeat).
 * Words with PMI > threshold get clustered into automatic synonym groups.
 */
export function buildPMIClusters(minPMI: number = 3.0): Map<string, string[]> {
  // Reset UF
  _ufParent.clear()
  _ufRank.clear()

  const net = network()
  // Union all pairs with PMI > threshold
  for (const [w1, related] of Object.entries(net.cooccur)) {
    if (w1.length < 2) continue
    if (isJunkToken(w1)) continue
    for (const [w2, count] of Object.entries(related)) {
      if (w2.length < 2 || count < 2) continue
      if (isJunkToken(w2)) continue
      const p = pmi(w1, w2)
      if (p < minPMI) continue
      ufUnion(w1, w2)
    }
  }

  // Extract clusters
  const clusters = new Map<string, string[]>()
  for (const word of _ufParent.keys()) {
    const root = ufFind(word)
    if (!clusters.has(root)) clusters.set(root, [])
    clusters.get(root)!.push(word)
  }

  // Filter: only keep clusters with 2-20 members
  const result = new Map<string, string[]>()
  const index = new Map<string, string>()
  for (const [root, members] of clusters) {
    if (members.length >= 2 && members.length <= 20) {
      result.set(root, members)
      for (const m of members) index.set(m, root)
    }
  }

  _pmiClusters = result
  _pmiClusterIndex = index
  if (result.size > 0) {
    console.log(`[cc-soul][aam] PMI clusters: ${result.size} groups, ${index.size} words`)
  }
  return result
}

/** Get PMI cluster peers for a word (empty if not in any cluster) */
export function getPMIClusterPeers(word: string): string[] {
  const root = _pmiClusterIndex.get(word)
  if (!root) return []
  return (_pmiClusters.get(root) || []).filter(w => w !== word)
}

/**
 * Expand a set of query words with semantically related words.
 * Uses learned co-occurrence + cold-start synonyms.
 */
/** 查询两个词的共现次数（供 hybridSimilarity 的 AAM 同义词融合用）*/
export function getCooccurrence(wordA: string, wordB: string): number {
  return network().cooccur[wordA]?.[wordB] ?? network().cooccur[wordB]?.[wordA] ?? 0
}

/** 获取词的 AAM 共现邻居（1-hop），返回 { word, pmiScore, fanOut } */
export function getAAMNeighbors(word: string, topK = 5): { word: string; pmiScore: number; fanOut: number }[] {
  const cooc = network().cooccur[word]
  if (!cooc) return []
  const results: { word: string; pmiScore: number; fanOut: number }[] = []
  for (const [other, _count] of Object.entries(cooc)) {
    const p = pmi(word, other)
    if (p > 0.5) {
      const fanOut = Object.keys(network().cooccur[other] || {}).length
      results.push({ word: other, pmiScore: p, fanOut })
    }
  }
  results.sort((a, b) => b.pmiScore - a.pmiScore)
  return results.slice(0, topK)
}

export function expandQuery(queryWords: string[], maxExpansion = 10): { word: string; weight: number }[] {
  // Detect language from query words and switch context
  const lang = detectLanguage(queryWords.join(' '))
  _currentLang = lang
  const langSynonyms = getSynonyms(lang)

  const expanded: Map<string, number> = new Map()

  // Original words get weight 1.0
  for (const w of queryWords) expanded.set(w, 1.0)

  // PMI-based associations (种子已注入 cooccur，始终可用)
  for (const w of queryWords) {
    const cooc = network().cooccur[w]
    if (!cooc) continue
    // Find top associated words by PMI
    const candidates: { word: string; pmiScore: number }[] = []
    for (const [other, _count] of Object.entries(cooc)) {
      if (expanded.has(other)) continue
      // Filter: skip 2-char CJK fragments that aren't real words
      // Real words: in synonym table (key or value via _reverseIndex), or df >= 1
      if (other.length === 2 && /^[\u4e00-\u9fff]+$/.test(other)) {
        const isKnown = !!langSynonyms[other] || !!(_reverseIndex.get(lang)?.has(other))
        if (!isKnown && (network().df[other] || 0) < 1) continue
      }
      const p = pmi(w, other)
      // 动态 PMI 阈值：小语料更宽松（冷启动），大语料保持不变
      const _totalDocs = network().totalDocs || 100
      const _pmiThr = _totalDocs < 100 ? 0.7 : 1.0
      const _dfThr = _totalDocs < 100 ? 1 : 2
      if (p > _pmiThr && (network().df[other] || 0) >= _dfThr) candidates.push({ word: other, pmiScore: p })
    }
    candidates.sort((a, b) => b.pmiScore - a.pmiScore)
    for (const c of candidates.slice(0, 3)) {
      // Weight proportional to PMI, capped at 0.8
      const baseWeight = Math.min(0.8, c.pmiScore / 5)
      // 应用 activationDamping：话题切换后旧话题词权重降低
      const damping = getDamping(c.word)
      const weight = baseWeight * damping
      expanded.set(c.word, Math.max(expanded.get(c.word) || 0, weight))
    }
  }

  // ── Synonym table: 权重高于 PMI 碎片，确保真正的同义词不被挤掉 ──
  // 同义词是人工/毕业验证过的高质量关联，权重应该高于 PMI 碎片
  for (const w of queryWords) {
    // 正向查：w 是同义词表的 key
    const directSyns = langSynonyms[w]
    if (directSyns) {
      for (const syn of directSyns) {
        if (syn.length >= 2) {
          // 覆盖已有的低权重 PMI 结果（同义词 > PMI 碎片）
          const existing = expanded.get(syn) || 0
          if (existing < 0.9) expanded.set(syn, 0.9)
        }
      }
    }
    // 反向查：w 是某个 key 的同义词（用反向索引 O(1) 替代暴力遍历 O(N)）
    const _revIdx = _reverseIndex.get(lang)
    const _matchedKeys = _revIdx?.get(w)
    if (_matchedKeys) {
      for (const key of _matchedKeys) {
        if (key.length >= 2) {
          const existing = expanded.get(key) || 0
          if (existing < 0.9) expanded.set(key, 0.9)
        }
        // 同组的其他词也加入（同义传递）
        const peers = langSynonyms[key]
        if (peers) {
          for (const peer of peers) {
            if (peer !== w && peer.length >= 2) {
              const existing = expanded.get(peer) || 0
              if (existing < 0.7) expanded.set(peer, 0.7)
            }
          }
        }
      }
    }
  }

  // ── PMI 聚类：Union-Find 自动发现的同义词簇（权重介于 PMI 碎片和人工同义词之间）──
  // 只对同义词表没覆盖到的词做 fallback
  for (const w of queryWords) {
    const peers = getPMIClusterPeers(w)
    for (const peer of peers) {
      if (peer.length < 2) continue
      const existing = expanded.get(peer) || 0
      if (existing < 0.85) expanded.set(peer, 0.85) // 高于 PMI 碎片(0.8), 略低于同义词(0.9)
    }
  }

  // ── 概念层级：上位→下位 双向扩展（权重 0.3，低于同义词）──
  // 按语言过滤：中文查询只用中文概念，英文只用英文概念
  const isConceptCJK = (s: string) => /[\u4e00-\u9fff]/.test(s)
  const conceptLangMatch = lang === 'zh' ? isConceptCJK : (s: string) => !isConceptCJK(s)
  // ── 动态概念层级：从 entity graph 关系自动生成 ──
  try {
    const graph = require('./graph.ts')
    if (graph.graphState?.relations) {
      const rels = graph.graphState.relations.filter((r: any) => !r.invalid_at)
      const groups = new Map<string, Set<string>>()
      for (const r of rels) {
        const type = r.type || 'related'
        if (!groups.has(type)) groups.set(type, new Set())
        if (r.source?.length >= 2) groups.get(type)!.add(r.source)
        if (r.target?.length >= 2) groups.get(type)!.add(r.target)
      }
      for (const [type, members] of groups) {
        if (members.size >= 2 && !(CONCEPT_HIERARCHY as any)[type]) {
          (CONCEPT_HIERARCHY as any)[type] = [...members].slice(0, 15)
        }
      }
    }
  } catch {}

  const _conceptReverse = new Map<string, string[]>()
  for (const [parent, children] of Object.entries(CONCEPT_HIERARCHY)) {
    if (!conceptLangMatch(parent)) continue
    for (const child of children) {
      if (!_conceptReverse.has(child)) _conceptReverse.set(child, [])
      _conceptReverse.get(child)!.push(parent)
    }
  }
  for (const w of queryWords) {
    // 上位→下位：用户说"健康"→扩展出"血压/血糖/体重..."
    // 英文 stemming：instruments→instrument, books→book, activities→activity
    let children = CONCEPT_HIERARCHY[w]
    if (!children && w.length >= 4 && /^[a-z]+$/i.test(w)) {
      const stems = [
        w.replace(/ies$/i, 'y'), w.replace(/ves$/i, 'f'), w.replace(/ses$/i, 'se'),
        w.replace(/s$/i, ''), w.replace(/ing$/i, ''), w.replace(/ed$/i, ''),
      ]
      for (const stem of stems) {
        if (stem !== w && CONCEPT_HIERARCHY[stem]) { children = CONCEPT_HIERARCHY[stem]; break }
      }
    }
    if (children) {
      for (const c of children) {
        if (c.length >= 2) {
          const existing = expanded.get(c) || 0
          if (existing < 0.85) expanded.set(c, 0.85)  // 概念层级 ≈ 同义词级别
        }
      }
    }
    // 下位→上位：用户说"血压"→关联"健康"相关记忆
    // 同样做 stemming 查找反向索引
    let parents = _conceptReverse.get(w)
    if (!parents && w.length >= 4 && /^[a-z]+$/i.test(w)) {
      const stems = [w.replace(/ies$/i, 'y'), w.replace(/s$/i, ''), w.replace(/ing$/i, ''), w.replace(/ed$/i, '')]
      for (const stem of stems) {
        if (stem !== w && _conceptReverse.has(stem)) { parents = _conceptReverse.get(stem); break }
      }
    }
    if (parents) {
      for (const p of parents) {
        if (!expanded.has(p)) expanded.set(p, 0.3)
        // 同级兄弟词也弱关联（血压→血糖/体重，但权重更低）
        const siblings = CONCEPT_HIERARCHY[p]
        if (siblings) {
          for (const s of siblings) {
            if (s !== w && s.length >= 2 && !expanded.has(s)) expanded.set(s, 0.3)
          }
        }
      }
    }
  }

  // ── 二次展开：对高权重扩展词做概念层级展开（解决 美食→吃→火锅 的两跳问题）──
  const secondPassWords = [...expanded.entries()]
    .filter(([w, wt]) => (wt as number) >= 0.9 && !queryWords.includes(w) && w.length >= 2)
    .map(([w]) => w)
  for (const w of secondPassWords.slice(0, 5)) {  // 最多 5 个词做二次展开
    const children2 = CONCEPT_HIERARCHY[w]
    if (children2) {
      for (const c of children2) {
        if (c.length >= 2) {
          const existing = expanded.get(c) || 0
          if (existing < 0.5) expanded.set(c, 0.5)  // 二次展开权重低于一次（0.85）
        }
      }
    }
  }

  // ── 过滤 junk token：用两阶段过滤器替代旧的白名单方案 ──
  for (const [word] of [...expanded.entries()]) {
    if (queryWords.includes(word)) continue
    if (isJunkToken(word)) expanded.delete(word)
  }

  // ── 共字关联：汉字结构特性，共享字 = 语义关联 ──
  // "睡眠"和"失眠"共享"眠"字，"睡眠"和"睡觉"共享"睡"字
  // 这是中文特有的零维护语义关联能力
  for (const w of queryWords) {
    if (!/^[\u4e00-\u9fff]{2,}$/.test(w)) continue  // 只对中文词做
    const chars = [...w]
    // 在同义词表中找共享字的词
    for (const [key, syns] of Object.entries(langSynonyms)) {
      if (expanded.has(key)) continue
      // key 和查询词共享至少一个字
      const keyChars = [...key]
      const shared = chars.filter(c => keyChars.includes(c))
      if (shared.length > 0 && key.length >= 2) {
        // 共字权重：共享字越多越高，1 字共享 0.4，2 字 0.7
        const sharedWeight = Math.min(0.7, 0.4 * shared.length)
        if ((expanded.get(key) || 0) < sharedWeight) {
          expanded.set(key, sharedWeight)
        }
      }
    }
  }

  // 每次 expandQuery 调用后恢复 damping（逐步回到 1.0）
  recoverDamping()

  // 最终清理：移除不是已知词的 CJK 2-gram 碎片
  // isKnownWord 只信任 _defaultSynonyms + CONCEPT_HIERARCHY，PMI 产出的碎片（"的问","术问","作压"）被删除
  for (const [word] of [...expanded.entries()]) {
    if (queryWords.includes(word)) continue
    if (word.length === 2 && /^[\u4e00-\u9fff]{2}$/.test(word) && !isKnownWord(word)) {
      expanded.delete(word)
    }
  }

  // Sort by weight, take top N
  return [...expanded.entries()]
    .map(([word, weight]) => ({ word, weight }))
    .sort((a, b) => b.weight - a.weight)
    .slice(0, queryWords.length + maxExpansion)
}

// LAYER 2 (旧 Noisy-OR 多钥匙召回) 已删除 — 被 activation-field.ts 的 7 信号激活场完全替代

// [DELETED] RECALL_KEYS / noisyOR / computeAdaptiveThreshold (旧 Noisy-OR 召回键)
// 被 activation-field.ts 的 7 信号激活场完全替代，零外部调用
// 删除时间：2026-04-06

// ═══════════════════════════════════════════════════════════════════════════════
// LAYER 3: HEBBIAN LEARNING
// "Neurons that fire together wire together"
// 有用的钥匙变强，没用的变弱
// ═══════════════════════════════════════════════════════════════════════════════

const KEY_WEIGHTS_PATH = resolve(DATA_DIR, 'aam_key_weights.json')

interface KeyWeights {
  // type → weight multiplier (default 1.0, range 0.3-3.0)
  weights: Record<string, number>
  feedbackCount: number
  lastUpdated: number  // 赫布时间衰减用
}

let keyWeights: KeyWeights = loadJson<KeyWeights>(KEY_WEIGHTS_PATH, {
  weights: {
    lexical: 1.0, temporal: 1.0, emotional: 1.0, entity: 1.0,
    behavioral: 1.0, factual: 1.0, causal: 1.0, sequence: 1.0, cognitive: 1.0,
  },
  feedbackCount: 0,
  lastUpdated: Date.now(),
})

function saveKeyWeights() { debouncedSave(KEY_WEIGHTS_PATH, keyWeights) }

/**
 * Hebbian update: strengthen keys that contributed to useful recall.
 */
export function hebbianUpdate(keyScores: Record<string, number>, wasUseful: boolean) {
  // ── 时间衰减：旧反馈影响逐渐减弱，向中性值 1.0 衰减 ──
  const now = Date.now()
  const hoursSinceLastUpdate = (now - (keyWeights.lastUpdated || now)) / 3600000
  if (hoursSinceLastUpdate > 1) {  // 至少过 1 小时才衰减
    const decayFactor = Math.exp(-hoursSinceLastUpdate / 168)  // 一周半衰期
    for (const key of Object.keys(keyWeights.weights)) {
      keyWeights.weights[key] = 1.0 + (keyWeights.weights[key] - 1.0) * decayFactor
    }
  }
  keyWeights.lastUpdated = now

  const lr = 0.05 / (1 + keyWeights.feedbackCount * 0.001)  // annealing learning rate
  keyWeights.feedbackCount++

  for (const [keyType, score] of Object.entries(keyScores)) {
    if (score < 0.2) continue  // key wasn't active, don't update
    const current = keyWeights.weights[keyType] || 1.0
    if (wasUseful) {
      // Hebbian 强化
      keyWeights.weights[keyType] = Math.min(3.0, current + lr * score)
    } else {
      // Anti-Hebbian 抑制：无用的高激活 key 惩罚更重
      // score 高但没用 = 假阳性 → 惩罚大
      // score 低且没用 = 本来就没参与 → 惩罚小
      const penalty = lr * score * (score > 0.5 ? 1.5 : 0.5)  // 高激活假阳性额外惩罚 1.5x
      keyWeights.weights[keyType] = Math.max(0.3, current - penalty)
    }
  }

  saveKeyWeights()
  antiHebbianDecay()
}

/**
 * Anti-Hebbian 关联抑制：削弱总是共现但从未有用的词关联
 * 人脑原理：竞争性学习——无用的连接被主动削弱，不只是被动衰减
 */
export function antiHebbianDecay() {
  // 每 100 次反馈后执行一次
  if (keyWeights.feedbackCount % 100 !== 0 || keyWeights.feedbackCount === 0) return

  // 找出关联网络中高共现但低 PMI 的词对（噪音关联）
  let pruned = 0
  for (const [w1, related] of Object.entries(network().cooccur)) {
    for (const [w2, count] of Object.entries(related)) {
      if (count < 3) continue  // 样本不够，不判定
      const pmiVal = pmi(w1, w2)
      if (pmiVal < 0.5 && count > 5) {
        // 高共现但低 PMI = 纯粹是因为都是常见词而共现，不是有意义的关联
        // 分层衰减（审核结论）：强关联慢衰减，弱关联快清理
        const decayRate = count > 20 ? 0.9 : count > 10 ? 0.8 : 0.6
        related[w2] = Math.max(1, Math.floor(count * decayRate))
        pruned++
      }
    }
  }

  if (pruned > 0) {
    debouncedSave(cooccurPath(_currentLang), network())
    console.log(`[cc-soul][aam] anti-Hebbian [${_currentLang}]: pruned ${pruned} noise associations`)
  }
}

// [DELETED] AAM RECALL / aamRecall / buildAAMContext / AAMResult
// 被 activation-field.ts 的 7 信号激活场完全替代，零外部调用
// 删除时间：2026-04-06

// ═══════════════════════════════════════════════════════════════════════════════
// STATS
// ═══════════════════════════════════════════════════════════════════════════════

/** 时间衰减：分层衰减共现权重，由 heartbeat 触发 */
export function decayCooccurrence() {
  // Decay all loaded language networks
  for (const [lang, net] of networks) {
    _decayNetwork(lang, net)
  }
  // 顺带衰减时序共现
  decayTemporalLinks()
}

function _decayNetwork(lang: string, net: AssociationNetwork) {
  _currentLang = lang  // for pmi() etc.
  let decayed = 0, pruned = 0
  for (const [w1, related] of Object.entries(net.cooccur)) {
    for (const [w2, count] of Object.entries(related)) {
      const baseDRate = count > 10 ? 0.995 : 0.98
      let momentum = 0
      try {
        const { getMomentumBoost } = require('./activation-field.ts')
        momentum = (getMomentumBoost(w1) || 0) + (getMomentumBoost(w2) || 0)
      } catch {}
      let factor = baseDRate * (1 + Math.min(0.1, momentum * 0.05))
      const newCount = count * Math.min(factor, 1.0)
      if (newCount < 0.5) {
        // 衰减到 <0.5 时删边（pruning）
        delete related[w2]
        pruned++
      } else {
        related[w2] = newCount
        decayed++
      }
    }
    // 如果节点没有任何边了，删节点
    if (Object.keys(related).length === 0) {
      delete net.cooccur[w1]
    }
  }
  if (pruned > 0) {
    console.log(`[cc-soul][aam] decay [${lang}]: ${decayed} edges decayed, ${pruned} pruned`)
    debouncedSave(cooccurPath(lang), net)
  }
}

export function getAAMStats() {
  // Aggregate stats across all loaded language networks
  const langStats: Record<string, { vocabularySize: number; totalDocs: number; associationPairs: number; synonyms: number }> = {}
  for (const [lang, net] of networks) {
    const syns = _synonymsByLang.get(lang)
    langStats[lang] = {
      vocabularySize: Object.keys(net.df).length,
      totalDocs: net.totalDocs,
      associationPairs: Object.values(net.cooccur).reduce((s, v) => s + Object.keys(v).length, 0),
      synonyms: syns ? Object.keys(syns).length : 0,
    }
  }
  return {
    languages: Object.keys(langStats),
    perLanguage: langStats,
    vocabularySize: Object.keys(network().df).length,
    totalDocs: network().totalDocs,
    associationPairs: Object.values(network().cooccur).reduce((s, v) => s + Object.keys(v).length, 0),
    coldStartSynonyms: Object.keys(getSynonyms(_currentLang)).length,
    keyWeights: { ...keyWeights.weights },
    feedbackCount: keyWeights.feedbackCount,
  }
}

// ═══════════════════════════════════════════════════════════════════════════════
// ACTIVATION DAMPING — 话题切换时短期压制旧话题节点
// 不碰 cooccur（长期知识），只碰激活阈值（短期状态）
// ═══════════════════════════════════════════════════════════════════════════════

const _activationDamping = new Map<string, number>()

/** 获取某个词的 damping factor（默认 1.0） */
export function getDamping(word: string): number {
  return _activationDamping.get(word) ?? 1.0
}

/**
 * 话题切换时调用：旧话题词 damping 降低，新话题词 damping 恢复
 * 由 cognition.ts 的 intentMomentum 话题切换信号触发
 */
export function onTopicSwitch(oldTopicWords: string[], newTopicWords: string[]): void {
  for (const w of oldTopicWords) {
    _activationDamping.set(w, 0.3)  // 旧话题压制
  }
  for (const w of newTopicWords) {
    _activationDamping.set(w, 1.0)  // 新话题满激活
  }
  if (oldTopicWords.length > 0) {
    console.log(`[cc-soul][aam] topic switch: damped ${oldTopicWords.length} old words, activated ${newTopicWords.length} new words`)
  }
}

/**
 * 每轮对话后恢复 damping（时间衰减回 1.0）
 * 由 expandQuery 或 heartbeat 调用
 */
export function recoverDamping(): void {
  for (const [word, factor] of _activationDamping) {
    const recovered = factor * 0.9 + 1.0 * 0.1  // 逐步恢复到 1.0
    if (recovered > 0.95) {
      _activationDamping.delete(word)  // 接近 1.0 就删掉
    } else {
      _activationDamping.set(word, recovered)
    }
  }
}

// ═══════════════════════════════════════════════════════════════════════════════
// 力5：正反馈 — 强化好的扩展路径
// 力6：负反馈 — 抑制坏的扩展（减跳数不减边权重）
// ═══════════════════════════════════════════════════════════════════════════════

// 负反馈跳数状态：query pattern → max hop（默认 2，miss 时降级）
const _maxHopByPattern = new Map<string, number>()


/**
 * 正反馈：用户 engaged 的记忆，强化其扩展路径上的 AAM 边
 * 由 feedbackMemoryEngagement 调用
 */
/**
 * 正反馈：强化查询词 ↔ 被 engaged 记忆内容词的关联
 * 不依赖 step.word（trace 构建时未写入该字段，导致旧版空转）
 * 改为直接从 memory content + 调用方传入的 queryWords 建立关联
 */
export function reinforceTrace(trace: { memory?: { content?: string }; path?: any[] }, queryWords?: string[]): void {
  if (!queryWords || queryWords.length === 0 || !trace.memory?.content) return
  const lang = detectLanguage(trace.memory.content)
  _currentLang = lang
  const memWords = filterStopWords(tokenize(trace.memory.content))
    .filter(w => !isJunkToken(w) && w.length >= 2).slice(0, 10)
  if (memWords.length === 0) return
  let reinforced = 0
  for (const qw of queryWords) {
    if (qw.length < 2 || isJunkToken(qw)) continue
    for (const mw of memWords) {
      if (qw === mw) continue
      if (!network().cooccur[qw]) network().cooccur[qw] = {}
      network().cooccur[qw][mw] = Math.min(50, (network().cooccur[qw][mw] || 0) + 1.5)
      if (!network().cooccur[mw]) network().cooccur[mw] = {}
      network().cooccur[mw][qw] = Math.min(50, (network().cooccur[mw][qw] || 0) + 1.5)
      reinforced++
    }
  }
  if (reinforced > 0) console.log(`[cc-soul][aam] reinforced ${reinforced} pairs from ${queryWords.length} qw × ${memWords.length} mw`)
}

/**
 * 负反馈：用户 ignored 的记忆，降低扩展跳数
 * 不减边权重（信用分配问题），只减跳数
 */
export function suppressExpansion(queryPattern: string, missedVia: string): void {
  // OOM protection: clear cache when exceeding cap (safe to rebuild)
  if (_maxHopByPattern.size > 1000) _maxHopByPattern.clear()

  const currentMax = _maxHopByPattern.get(queryPattern) ?? 2

  if (missedVia === 'aam_hop2' && currentMax > 1) {
    _maxHopByPattern.set(queryPattern, 1)
    console.log(`[cc-soul][aam] negative feedback: ${queryPattern} hop2→hop1`)
  } else if (missedVia === 'aam_hop1' && currentMax > 0) {
    _maxHopByPattern.set(queryPattern, 0)
    console.log(`[cc-soul][aam] negative feedback: ${queryPattern} hop1→no expansion`)
  }
}

/**
 * 正反馈恢复：engaged 后恢复跳数
 */
export function restoreExpansion(queryPattern: string): void {
  const currentMax = _maxHopByPattern.get(queryPattern)
  if (currentMax !== undefined && currentMax < 2) {
    _maxHopByPattern.set(queryPattern, Math.min(2, currentMax + 1))
    console.log(`[cc-soul][aam] expansion restored: ${queryPattern} → hop${currentMax + 1}`)
  }
}

// ═══════════════════════════════════════════════════════════════════════════════
// AAM 情绪词自学习（语言无关）
// 从 mood 反馈学习哪些词是正面/负面的，不依赖任何预定义词表
// ═══════════════════════════════════════════════════════════════════════════════

const _emotionWordCandidates = new Map<string, { posCount: number; negCount: number }>()

/**
 * Record that a word appeared in a message where mood changed.
 * Over time, words that consistently appear with positive mood → positive emotion word.
 */
export function learnEmotionWord(word: string, moodDelta: number): void {
  if (Math.abs(moodDelta) < 0.15) return
  if (word.length < 2) return

  // OOM protection: evict lowest-count half when exceeding cap
  if (_emotionWordCandidates.size > 10000) {
    const sorted = [..._emotionWordCandidates.entries()]
      .sort((a, b) => (a[1].posCount + a[1].negCount) - (b[1].posCount + b[1].negCount))
    const toDelete = Math.floor(sorted.length / 2)
    for (let i = 0; i < toDelete; i++) _emotionWordCandidates.delete(sorted[i][0])
  }

  let entry = _emotionWordCandidates.get(word)
  if (!entry) { entry = { posCount: 0, negCount: 0 }; _emotionWordCandidates.set(word, entry) }

  if (moodDelta > 0.15) entry.posCount++
  else if (moodDelta < -0.15) entry.negCount++
}

/**
 * Get learned emotion words (graduated: 3+ consistent mood associations)
 */
export function getLearnedEmotionWords(): { positive: string[]; negative: string[] } {
  const positive: string[] = []
  const negative: string[] = []
  for (const [word, counts] of _emotionWordCandidates) {
    const total = counts.posCount + counts.negCount
    if (total < 3) continue
    if (counts.posCount > counts.negCount * 2) positive.push(word)
    if (counts.negCount > counts.posCount * 2) negative.push(word)
  }
  return { positive, negative }
}

// ═══════════════════════════════════════════════════════════════════════════════
// 启动 bootstrap：从已有记忆补课 AAM
// ═══════════════════════════════════════════════════════════════════════════════

function bootstrapFromMemories(): void {
  if (network().totalDocs > 100) return  // 已有足够数据
  try {
    const { memoryState, ensureMemoriesLoaded } = require('./memory.ts')
    ensureMemoriesLoaded()
    // 只学高价值 scope（过滤掉 WAL/insight/consolidated 等格式化文本）
    const LEARN_SCOPES = new Set(['fact', 'preference', 'event', 'correction', 'opinion', 'episode'])
    const candidates = memoryState.memories
      .filter((m: any) => m.content && LEARN_SCOPES.has(m.scope))
      .slice(-200)
    for (const m of candidates) {
      learnAssociation(m.content)
    }
    if (candidates.length > 0) console.log(`[cc-soul][aam] bootstrapped from ${candidates.length} memories`)

    // topic nodes → AAM 领域关联：蒸馏产物喂入 AAM，领域词自动互联
    try {
      const { getDistillStats, getMentalModel } = require('./distill.ts')
      const stats = getDistillStats()
      if (stats?.topicNodeCount > 0) {
        const model = getMentalModel()
        if (model?.traits) {
          for (const trait of Object.values(model.traits) as any[]) {
            if (trait?.examples?.length >= 2) {
              learnAssociation(trait.examples.join(' '))  // 同一 trait 的例子互相关联
            }
          }
        }
        console.log(`[cc-soul][aam] learned from ${stats.topicNodeCount} topic nodes`)
      }
    } catch {}
  } catch {}
}

// 延迟 3 秒执行（避免阻塞启动）
setTimeout(() => bootstrapFromMemories(), 3000)

// ═══════════════════════════════════════════════════════════════════════════════
// LLM SEED GENERATION — auto-generate synonyms for new languages
// ═══════════════════════════════════════════════════════════════════════════════

/** Generate synonym seed for a new language via LLM. Called once when a new language is detected. */
export async function generateSynonymSeed(lang: string): Promise<void> {
  const path = synonymsPath(lang)
  if (existsSync(path)) {
    const existing = loadJson<Record<string, string[]>>(path, {})
    if (Object.keys(existing).length > 20) return  // 已有足够种子
  }

  console.log(`[AAM] New language "${lang}" detected. Generating synonym seed via LLM...`)

  try {
    const { spawnCLI } = await import('./cli.ts')
    const prompt = `Generate exactly 200 synonym groups for the "${lang}" language. Each group is a common word and its synonyms/related words that a personal AI assistant would need.

Cover these categories: emotions (30 groups), health (20), work/career (20), family (20), food (15), housing (15), finance (15), transport (10), education (10), hobbies (15), tech (15), time expressions (10), general adjectives (15).

Output as JSON object. Keys are the main word, values are arrays of synonyms. Example for English:
{"happy":["glad","joyful","cheerful"],"sad":["unhappy","depressed","down"]}

Output ONLY valid JSON, no explanation, no markdown.`

    await new Promise<void>((resolve) => {
      const timeout = setTimeout(() => {
        console.log(`[AAM] Seed generation timed out for "${lang}"`)
        resolve()
      }, 60000)

      spawnCLI(prompt, (output: string) => {
        clearTimeout(timeout)
        try {
          // 提取 JSON（可能包含 markdown 包裹）
          const jsonMatch = output.match(/\{[\s\S]*\}/)
          if (!jsonMatch) { console.log(`[AAM] No valid JSON in LLM response`); resolve(); return }

          const synonyms = JSON.parse(jsonMatch[0])
          if (typeof synonyms !== 'object' || Object.keys(synonyms).length < 10) {
            console.log(`[AAM] LLM returned too few synonyms: ${Object.keys(synonyms).length}`)
            resolve(); return
          }

          // 存到对应语言文件
          debouncedSave(path, synonyms)

          // 注入到当前网络
          _synonymsByLang.set(lang, synonyms)

          // 种子注入到共现网络
          const net = getNetwork(lang)
          for (const [word, syns] of Object.entries(synonyms)) {
            if (!Array.isArray(syns)) continue
            for (const syn of syns) {
              if (typeof syn !== 'string' || syn.length < 2) continue
              if (!net.cooccur[word]) net.cooccur[word] = {}
              net.cooccur[word][syn] = (net.cooccur[word][syn] || 0) + 2
              if (!net.cooccur[syn]) net.cooccur[syn] = {}
              net.cooccur[syn][word] = (net.cooccur[syn][word] || 0) + 2
              net.df[word] = (net.df[word] || 0) + 1
              net.df[syn] = (net.df[syn] || 0) + 1
            }
          }
          debouncedSave(cooccurPath(lang), net)

          console.log(`[AAM] Generated ${Object.keys(synonyms).length} synonym groups for "${lang}"`)
        } catch (e: any) {
          console.log(`[AAM] Seed parse error: ${e.message}`)
        }
        resolve()
      }, 60000, `aam-seed-${lang}`)
    })
  } catch (e: any) {
    console.log(`[AAM] Seed generation failed: ${e.message}`)
  }
}

// ── Export language detection for external use ──
export { detectLanguage }
