/**
 * 画像标签筛选字典 — 列表/选人侧"按画像圈人"的可筛维度(单一真理源)。
 *
 * 与 PERSONA_FEATURE_SPECS(标签卡全集)的关系:这里是**精选的可筛子集**,
 * 每项声明它在 persona_features.data 里的结构化取值位置(dataPath)+ code↔中文。
 * 未收录:acquisition_channel(取值随宿主,非稳定枚举)、discount_anchor(数值型非标签)。
 * code 必须与对应 feature extractor 写入 data 的值一致(rfm.feature.ts 等);
 * 改 extractor 枚举时同步改这里(CI 不强校验,靠 PR review)。
 *
 * 筛选语义(plan.service 实现):同一维度多选 = OR;跨维度 = AND;
 * 匹配的是患者**当前版**画像(personas.supersededAt IS NULL)。
 *
 * ⚠️ **加维度必须同步补库索引** —— 每个维度的 (key, dataPath) 在
 * `prisma/migrations/20260728020000_persona_features_tag_filter_indexes` 里各有一条偏索引
 * (数组维度走 GIN、标量维度走 btree 且第二列盖 persona_id)。漏建的维度会退化成
 * 「按 key 捞全量特征行 + 回堆过滤 jsonb」:生产实测禁忌维度 190,210 行里只中 2,156 行,
 * 读了 1.3GB 堆、单次筛人 20+ 秒(rfm 在测试服 57 秒)。这不是能忍的慢,是不可用。
 */
export interface PersonaTagOption {
  /** data 里的结构化取值(英文 code) */
  value: string;
  zh: string;
  /**
   * 一句话说明 —— 筛选面板上 hover 该选项时显示。
   *
   * 「重要保持」「待激活」「重要发展」这类分群名是算法口径的产物,光看名字猜不出边界
   * (重要保持 vs 重要挽留差在哪?待激活 vs 沉睡客几个月分界?),客服只能凭感觉勾。
   * 这里用大白话给出**判定条件**,不解释算法。省略 = 名字本身够自明(性别之类)。
   */
  hint?: string;
}

export interface PersonaTagFilterDim {
  /** persona_features.key */
  key: string;
  nameZh: string;
  /** data 里取值的字段名(单层) */
  dataPath: string;
  /** true = data[dataPath] 是 string[](用 array_contains);false = 标量 equals */
  isArray?: boolean;
  options: PersonaTagOption[];
}

export const PERSONA_TAG_FILTER_DIMS: PersonaTagFilterDim[] = [
  {
    key: 'rfm',
    nameZh: '价值分群',
    dataPath: 'segment',
    options: [
      { value: 'important_value', zh: '重要价值', hint: '消费高、来得勤,最近还在来' },
      { value: 'important_retain', zh: '重要保持', hint: '消费高、来得勤,但有段时间没来了' },
      { value: 'important_develop', zh: '重要发展', hint: '消费高、最近来过,但来的次数少' },
      { value: 'important_winback', zh: '重要挽留', hint: '消费高,但很久没来了' },
      { value: 'general_value', zh: '一般价值', hint: '来得勤、最近还在来,消费一般' },
      { value: 'general_retain', zh: '一般保持', hint: '来得勤但有段时间没来,消费一般' },
      { value: 'general_develop', zh: '一般发展', hint: '最近来过但次数少,消费一般' },
      { value: 'low_active', zh: '低活跃', hint: '很久没来,或三项都偏低' },
    ],
  },
  {
    key: 'lifecycle_stage',
    nameZh: '生命周期',
    dataPath: 'stage',
    options: [
      { value: 'prospect', zh: '潜客', hint: '还没到过诊,只约过或咨询过' },
      { value: 'new', zh: '新客', hint: '首诊半年内,就诊不超过 3 次' },
      { value: 'growth', zh: '成长客', hint: '还在来,近一年消费明显上涨' },
      { value: 'mature', zh: '成熟客', hint: '还在来,消费平稳' },
      { value: 'reactivate', zh: '待激活', hint: '半年到一年半没来' },
      { value: 'dormant', zh: '沉睡客', hint: '一年半到两年没来' },
      { value: 'churned', zh: '流失客', hint: '两年以上没来' },
    ],
  },
  {
    key: 'urgency_level',
    nameZh: '紧迫度',
    dataPath: 'level',
    options: [
      { value: 'urgent', zh: '紧急', hint: '有待做的项目,且 90 天以上没来' },
      { value: 'high', zh: '高', hint: '有待做的项目,30 到 90 天没来' },
      { value: 'mid', zh: '中', hint: '有待做的项目,30 天内来过或刚发现' },
    ],
  },
  {
    key: 'age_bracket',
    nameZh: '年龄段',
    dataPath: 'bracket',
    options: [
      { value: 'infant', zh: '婴幼儿', hint: '0-2 岁' },
      { value: 'preschool', zh: '学龄前', hint: '3-6 岁' },
      { value: 'mixed_dentition', zh: '替牙期', hint: '7-11 岁,换牙阶段' },
      { value: 'adolescent', zh: '青少年', hint: '12-17 岁' },
      { value: 'youth', zh: '青年', hint: '18-25 岁' },
      { value: 'young_adult', zh: '中青年', hint: '26-30 岁' },
      { value: 'middle_aged', zh: '中年', hint: '31-45 岁' },
      { value: 'pre_senior', zh: '中老年', hint: '46-54 岁' },
      { value: 'senior', zh: '老年', hint: '55 岁以上' },
    ],
  },
  {
    key: 'gender',
    nameZh: '性别',
    dataPath: 'gender',
    options: [
      { value: 'male', zh: '男' },
      { value: 'female', zh: '女' },
    ],
  },
  {
    key: 'family_structure',
    nameZh: '家庭构成',
    dataPath: 'structure',
    options: [
      { value: 'multigen', zh: '多代之家', hint: '院内登记有长辈(父母 / 祖辈)' },
      { value: 'nuclear', zh: '多口之家', hint: '院内登记有子女' },
      { value: 'couple', zh: '两口之家', hint: '院内登记有配偶' },
      { value: 'single', zh: '单身家庭', hint: '只有旁系或朋友关系' },
    ],
  },
  {
    key: 'entitlement_status',
    nameZh: '权益身份',
    dataPath: 'types',
    isArray: true,
    options: [
      { value: 'high_insurance', zh: '高端保险直付', hint: '用过 Bupa、万欣和等商业保险直付' },
      { value: 'bank_vip', zh: '银行私行权益', hint: '用过私人银行 / 白金卡等银行权益' },
      { value: 'stored_value', zh: '储值会员', hint: '有储值或预存记录' },
      { value: 'pedo_member', zh: '儿牙会员', hint: '买过儿牙会员卡 / 涂氟年卡' },
      { value: 'medical', zh: '医保客户', hint: '用过医保结算' },
    ],
  },
  {
    key: 'treatment_history',
    nameZh: '治疗史',
    dataPath: 'types',
    isArray: true,
    options: [
      { value: 'implant_history', zh: '种植史', hint: '种过牙' },
      { value: 'ortho_history', zh: '正畸史', hint: '做过矫正' },
      { value: 'prostho_history', zh: '修复史', hint: '做过冠桥 / 贴面 / 嵌体' },
      { value: 'perio_history', zh: '牙周治疗史', hint: '做过洁牙 / 刮治 / 牙周序列治疗' },
    ],
  },
  {
    key: 'treatment_sensitivity',
    nameZh: '治疗敏感',
    dataPath: 'types',
    isArray: true,
    options: [
      { value: 'dental_fear', zh: '看牙恐惧', hint: '病历里提过害怕看牙' },
      { value: 'needle_faint', zh: '晕针', hint: '病历里提过晕针 / 怕打针' },
      { value: 'blood_faint', zh: '晕血', hint: '病历里提过晕血' },
      { value: 'claustrophobia', zh: '密闭恐惧', hint: '病历里提过幽闭 / 长时间张口不适' },
    ],
  },
  {
    key: 'special_attention',
    nameZh: '特殊关注',
    dataPath: 'types',
    isArray: true,
    options: [
      { value: 'frequent_no_show', zh: '屡次爽约', hint: '近一年至少 3 次预约,履约不到一半' },
      { value: 'often_late', zh: '经常迟到', hint: '近一年多次晚到 15 分钟以上' },
      { value: 'do_not_disturb', zh: '免打扰', hint: '已标记不接受主动联系,不要外呼' },
      { value: 'cannot_wait', zh: '不可等候', hint: '病历或备注提过赶时间 / 不能等' },
    ],
  },
  {
    key: 'time_preference',
    nameZh: '时间偏好',
    dataPath: 'types',
    isArray: true,
    options: [
      { value: 'weekday', zh: '工作日', hint: '近两年预约多在工作日' },
      { value: 'weekend', zh: '周末', hint: '近两年预约多在周末' },
      { value: 'morning', zh: '上午', hint: '多约在 8-12 点' },
      { value: 'afternoon', zh: '下午', hint: '多约在 12-18 点' },
      { value: 'evening', zh: '晚间', hint: '多约在 18-21 点' },
    ],
  },
  {
    key: 'referral_champion',
    nameZh: '转介绍达人',
    dataPath: 'type',
    options: [
      { value: 'family', zh: '家庭型', hint: '推荐 3 人以上并带来成交,带的是家里人' },
      { value: 'social', zh: '社交型', hint: '推荐 3 人以上并带来成交,带的是家人以外的人' },
    ],
  },
  {
    key: 'contraindication',
    nameZh: '禁忌',
    /**
     * 按【治疗域】筛,不按病因 —— data.domains 而非 data.types。
     *
     * 客服圈人时想的是「哪些人不能做种植」,不是「哪些人在吃抗骨吸收药」;
     * 病因(抗凝药/放疗史/双膦酸盐…)是算法口径,列成筛选项等于把内部实现摊给业务看。
     * 病因照常在关键事实卡和画像详情里露出(带医生原话),要查"所有放疗史患者"是另一个
     * 场景(风险排查),需要时再加一个独立维度,别混进这里。
     *
     * 不列「拍片禁忌」:唯一来源是妊娠,而妊娠同时禁种植和手术 —— 勾那两项就已经覆盖,
     * 单列只会让面板变长。data.domains 里仍有 xray,详情页照常显示。
     *
     * 核查项(糖尿病/高血压/骨质疏松/吸烟)不在这里 —— 它们落 data.checks 而非 data.domains,
     * 按定义就不是禁忌,放进来会让客服把「问一句」当成「不能治」。
     */
    dataPath: 'domains',
    isArray: true,
    options: [
      { value: 'surgery', zh: '手术禁忌', hint: '拔牙 / 翻瓣 / 植骨 —— 抗凝药、凝血障碍、抗骨吸收药、放疗史、妊娠期' },
      { value: 'implant', zh: '种植禁忌', hint: '未满 19 岁,或放疗史 / 抗骨吸收药 / 抗凝药 / 心血管手术史 / 妊娠哺乳' },
      { value: 'anesthesia', zh: '麻醉禁忌', hint: '局麻药物过敏;哮喘为相对禁忌' },
      { value: 'orthodontic', zh: '正畸禁忌', hint: '诊断为重度牙周炎 —— 牙周控制后可矫治,属相对禁忌' },
    ],
  },
  {
    key: 'potential_treatment',
    nameZh: '潜在治疗',
    dataPath: 'types',
    isArray: true,
    options: [
      { value: 'implant', zh: '种植', hint: '诊断缺牙,还没种' },
      { value: 'ortho', zh: '正畸', hint: '建议矫正,还没做(13-40 岁)' },
      { value: 'early_ortho', zh: '早矫', hint: '建议儿童早期矫治(3-12 岁)' },
      { value: 'endo', zh: '根管', hint: '诊断牙髓问题,还没做根管' },
      { value: 'perio', zh: '牙周', hint: '诊断牙周问题,还没治' },
      { value: 'filling', zh: '补牙', hint: '诊断龋齿,还没补' },
      { value: 'restoration', zh: '修复', hint: '需要冠桥 / 贴面,还没做' },
      { value: 'extraction', zh: '拔牙', hint: '残根残冠等需拔,还没拔' },
    ],
  },
];

/** "key:value" 串(query 上线格式)解析;非法项丢弃。 */
export function parsePersonaTags(raw: string): Array<{ key: string; value: string }> {
  return raw
    .split(',')
    .map((s) => s.trim())
    .filter(Boolean)
    .map((s) => {
      const i = s.indexOf(':');
      if (i <= 0) return null;
      return { key: s.slice(0, i), value: s.slice(i + 1) };
    })
    .filter((x): x is { key: string; value: string } => x !== null);
}
