import type { PersonaFeatureKey } from '@pac/types';
import type { Prisma } from '@prisma/client';
import type { PotentialGap } from '../../clinical-gap/potential-treatment.selector';

/**
 * Feature Extractor 接口
 *
 * 输入:patient 主档 + profile + 所有 active facts
 * 输出:0 / 1 个 PersonaFeatureDraft(规则路径下通常每个 extractor 1 个 feature)
 *
 * 设计纪律:
 *   - description 是**自包含自然语言**,客服 / 主管能直接看懂,不依赖 score / evidence
 *   - score 是可选数值排序辅助,**不能替代 description 的语义**
 *   - evidence.factIds 必填 — 反查 fact → transaction → rawPayload 的证据链
 *   - 单个 extractor 失败不影响其他;Persona 整体 status=partial
 */
export interface FeatureExtractorContext {
  patient: {
    id: string;
    externalId: string;
    name: string | null;
    phone: string | null;
    gender: string | null;
    birthDate: Date | null;
    active: boolean;
  };
  profile: {
    doNotContact: boolean;
    doNotContactReason: string | null;
    deceased: boolean;
    tags: string[];
    notes: string | null;
    /// 获客渠道(A.2.1):一级 PAC 标准枚举 / 二级 host 原值
    acquisitionChannel: string | null;
    acquisitionSub: string | null;
    /// 转介绍(B.1.2):推荐人数 / 带来转化总额(分)
    referralCount: number | null;
    referralAmountCents: number | null;
  } | null;
  /// 该 patient 的所有 active facts(按 type 索引方便查)
  factsByType: Map<string, ActiveFact[]>;
  /// 该 patient 的关系边(PatientRelation;家庭构成等用)。relationship 已归一
  /// (spouse/father/mother/grandparent/child/grandchild/sibling/friend/other)。
  relations: Array<{ relationship: string }>;
  /// 全部 appointment_record(各 patient_facts.status,排 superseded)— 特别关注的爽约/迟到用。
  /// factsByType 只含 active/fulfilled,no_show/cancelled 预约不在其中,故单独加载。
  appointmentsAll: ActiveFact[];
  /// 潜在治疗 gap(诊断了/建议了但没启动对应治疗;牙位级)— 复用召回 gap 核心(共享单一源),
  /// 去掉召回的时间门/合规门(常态属性)。potential_treatment 特征消费。
  potentialGaps: PotentialGap[];
  /// today 锚点(测试时可注入固定时间)
  now: Date;
  /// 群体统计(租户级,统计层特征用 — 如 RFM 的 M 分位阈值 [p20,p40,p60,p80])。
  /// 单一真理源由 PersonaService 计算 + 缓存后注入;缺失时统计层特征降级到绝对阈值。
  populationStats?: {
    /// 累计净消费(cents)分位阈值 [p20,p40,p60,p80](租户内,M 打分用)
    monetaryQuantiles: number[];
  } | null;
}

export interface ActiveFact {
  id: string;
  subjectId: string;
  type: string;
  kind: string;
  status: string;
  occurredAt: Date | null;
  plannedFor: Date | null;
  clinicId: string | null;
  content: Prisma.JsonValue;
}

export interface PersonaFeatureDraft {
  key: PersonaFeatureKey;
  description: string;
  score?: number | null;
  /// 结构化特征值(可空)→ 落 persona_features.data 列。description 人读、score 排序、
  /// data 给 scorer / UI 结构化消费(如权益身份 insurers / lastInsuranceAt / monthsSinceLast)。
  data?: Record<string, unknown> | null;
  /// 证据反查 fact + AI 调用;**不存 ruleIds 字符串常量**(版本追溯靠 git + persona.createdAt)
  evidence: {
    factIds: string[];
    agentInvocationIds?: string[];
  };
}

export interface FeatureExtractor {
  readonly key: PersonaFeatureKey;
  extract(ctx: FeatureExtractorContext): PersonaFeatureDraft | null;
  /**
   * 可选:**纯时间流逝**会让本特征改档的最近时刻(未来时刻;不会再变 → null)。
   *
   * 水位闸只在事实变了时放行重算,所以"只是时间过去了"导致的改档(急迫跨 90 天线、
   * 年龄跨段、滑窗把最老记录挤出去…)本来永远不会被触发。各特征在这里报出自己的到期时刻,
   * PersonaService 取全体最小值存 personas.next_boundary_at,每晚只捞到期的那一小撮重算。
   *
   * 不实现 = 该特征不随时间自行改变(性别 / 获客渠道 / 治疗史 …)。
   * 用 time-boundary.ts 里的原语拼,别手搓日期算术。
   */
  nextBoundaryAt?(ctx: FeatureExtractorContext): Date | null;
}
