import { z } from 'zod';

// =============================================================
// User Role + Permission
// =============================================================

export const UserRole = {
  STAFF: 'staff',
  LEADER: 'leader',
  ADMIN: 'admin',
} as const;
export type UserRole = (typeof UserRole)[keyof typeof UserRole];
export const UserRoleSchema = z.enum(['staff', 'leader', 'admin']);

export const Permission = {
  // Plan / recall pool
  PLAN_VIEW_OWN: 'plan:view_own',
  PLAN_VIEW_ALL: 'plan:view_all',
  PLAN_ASSIGN: 'plan:assign',
  PLAN_RECYCLE: 'plan:recycle',
  PLAN_EXECUTE: 'plan:execute',
  // Patient / persona
  PATIENT_VIEW: 'patient:view',
  PERSONA_VIEW: 'persona:view',
  // Agent
  AGENT_INVOKE: 'agent:invoke',
  // Facts(Pool 1)
  FACT_INGEST: 'fact:ingest',
  // Stats
  STATS_VIEW: 'stats:view',
  // Platform management — admin only
  PLATFORM_MANAGE: 'platform:manage',
} as const;
export type Permission = (typeof Permission)[keyof typeof Permission];

export const ROLE_PERMISSIONS: Record<UserRole, Permission[]> = {
  [UserRole.STAFF]: [
    Permission.PLAN_VIEW_OWN,
    /// W3 末:staff 自助认领池里的 plan(分配给自己);分配给他人/回收仍是 leader 权限。
    /// 前端 plansApi.assign(planId, user.sub) 在 pool 视图按钮上调用,即"认领"语义。
    Permission.PLAN_ASSIGN,
    Permission.PLAN_EXECUTE,
    Permission.PATIENT_VIEW,
    Permission.PERSONA_VIEW,
    Permission.AGENT_INVOKE,
  ],
  [UserRole.LEADER]: [
    Permission.PLAN_VIEW_OWN,
    Permission.PLAN_VIEW_ALL,
    Permission.PLAN_ASSIGN,    // leader 可分配给任意 staff
    Permission.PLAN_RECYCLE,   // leader 独占:回收 staff 已认领的 plan
    Permission.PLAN_EXECUTE,
    Permission.PATIENT_VIEW,
    Permission.PERSONA_VIEW,
    Permission.AGENT_INVOKE,
    Permission.STATS_VIEW,
  ],
  [UserRole.ADMIN]: Object.values(Permission),
};

// =============================================================
// Patient(主档状态)
// =============================================================

// =============================================================
// Host action URL keys — 前端跳宿主 deep-link 的 known action 集合。
//
// 用法:Host.actionUrls 是 Record<HostActionKey | string, string>(允许 + 自定义 custom_xxx)。
// admin 配置页用 enum 作下拉,自定义需带 `custom_` 前缀(避免跟未来新增官方 key 撞名)。
// =============================================================

export const HostActionKey = {
  VIEW_PATIENT: 'VIEW_PATIENT',                 // 看患者档案(单 patient)
  EDIT_PATIENT: 'EDIT_PATIENT',                 // 编辑患者基本信息
  CREATE_APPOINTMENT: 'CREATE_APPOINTMENT',     // 新建预约
  VIEW_APPOINTMENT: 'VIEW_APPOINTMENT',         // 看某次预约
  VIEW_ENCOUNTER: 'VIEW_ENCOUNTER',             // 看某次接诊
  VIEW_MEDICAL_RECORD: 'VIEW_MEDICAL_RECORD',   // 看病历
  VIEW_IMAGE: 'VIEW_IMAGE',                     // 看影像
  VIEW_PAYMENT: 'VIEW_PAYMENT',                 // 看收款/账单
  ADD_NOTE: 'ADD_NOTE',                         // 加备注
  OPEN_WECOM_CHAT: 'OPEN_WECOM_CHAT',           // 打开企微会话
  CALL_PATIENT: 'CALL_PATIENT',                 // 拨号
} as const;
export type HostActionKey = (typeof HostActionKey)[keyof typeof HostActionKey];
export const HostActionKeySchema = z.enum([
  'VIEW_PATIENT',
  'EDIT_PATIENT',
  'CREATE_APPOINTMENT',
  'VIEW_APPOINTMENT',
  'VIEW_ENCOUNTER',
  'VIEW_MEDICAL_RECORD',
  'VIEW_IMAGE',
  'VIEW_PAYMENT',
  'ADD_NOTE',
  'OPEN_WECOM_CHAT',
  'CALL_PATIENT',
]);

/// 标签(中文)+ 占位变量提示(给 admin 配置页显示)
export const HOST_ACTION_META: Record<HostActionKey, { label: string; placeholders: string[] }> = {
  VIEW_PATIENT:        { label: '查看患者档案',    placeholders: ['{patientId}'] },
  EDIT_PATIENT:        { label: '编辑患者信息',    placeholders: ['{patientId}'] },
  CREATE_APPOINTMENT:  { label: '新建预约',        placeholders: ['{patientId}', '{doctorId}'] },
  VIEW_APPOINTMENT:    { label: '查看预约',        placeholders: ['{appointmentId}'] },
  VIEW_ENCOUNTER:      { label: '查看接诊',        placeholders: ['{encounterId}'] },
  VIEW_MEDICAL_RECORD: { label: '查看病历',        placeholders: ['{emrId}', '{patientId}'] },
  VIEW_IMAGE:          { label: '查看影像',        placeholders: ['{imageId}'] },
  VIEW_PAYMENT:        { label: '查看账单/收款',   placeholders: ['{paymentId}', '{orderId}'] },
  ADD_NOTE:            { label: '添加备注',        placeholders: ['{patientId}'] },
  OPEN_WECOM_CHAT:     { label: '打开企微会话',    placeholders: ['{patientId}', '{wecomExternalUserId}'] },
  CALL_PATIENT:        { label: '拨号',            placeholders: ['{phone}'] },
};

export const PatientStatus = {
  ACTIVE: 'active',
  ARCHIVED: 'archived',
  MERGED: 'merged',
} as const;
export type PatientStatus = (typeof PatientStatus)[keyof typeof PatientStatus];
export const PatientStatusSchema = z.enum(['active', 'archived', 'merged']);

export const DoNotContactReason = {
  COMPLAINT: 'complaint',
  OPT_OUT: 'opt_out',
  TREATED_ELSEWHERE: 'treated_elsewhere',
  LEGAL_HOLD: 'legal_hold',
  OTHER: 'other',
} as const;
export type DoNotContactReason = (typeof DoNotContactReason)[keyof typeof DoNotContactReason];
export const DoNotContactReasonSchema = z.enum([
  'complaint',
  'opt_out',
  'treated_elsewhere',
  'legal_hold',
  'other',
]);

// =============================================================
// PatientTransaction(操作账本) — 三类 type 严格区分(db-design-v2 §13 D2)
//   - Action(动作事实,push 主导)
//   - SubjectType(被操作主体类型,PAC 归一化封闭集)
//   - FactType(parser 下游统一,事实单元;见下方 PatientFact 段)
//
// v2.1 重塑(W2 末):
//   - 拆 SubjectType:diagnosis / treatment / recommendation(对齐 fact_type 拆分)
//   - Action 加 diagnosis_recorded / treatment_planned / treatment_started /
//     treatment_completed / recommendation_extracted
// =============================================================

/// patient_transactions.action — PAC 归一化封闭集
/// 形式:`英文_key`(中文含义)
export const Action = {
  // ── 主流程动作 ──
  CONSULTATION_CREATED: 'consultation_created', // 咨询创建
  APPOINTMENT_CREATED: 'appointment_created', // 预约创建
  APPOINTMENT_CHANGED: 'appointment_changed', // 预约变更
  APPOINTMENT_CANCELLED: 'appointment_cancelled', // 预约取消
  APPOINTMENT_FULFILLED: 'appointment_fulfilled', // 患者到诊(预约履约)
  VISIT_REGISTERED: 'visit_registered', // 挂号
  RECEPTION_COMPLETED: 'reception_completed', // 前台接待完成
  DOCTOR_ENCOUNTER_STARTED: 'doctor_encounter_started', // 医生接诊开始(只元数据)

  // ── v2.1 拆出的临床动作 ──
  DIAGNOSIS_RECORDED: 'diagnosis_recorded', // 诊断记录(独立 fact)
  TREATMENT_PLANNED: 'treatment_planned', // 治疗计划录入(kind=planned)
  TREATMENT_STARTED: 'treatment_started', // 治疗开始(kind=actual,status=in_progress)
  TREATMENT_COMPLETED: 'treatment_completed', // 治疗完成(kind=actual,status=completed)
  RECOMMENDATION_EXTRACTED: 'recommendation_extracted', // 医生建议(Layer C 抽出/规则识别)

  // ── 单据 / 病历 / 影像 ──
  ORDER_SUBMITTED: 'order_submitted', // 收费单提交
  PAYMENT_RECEIVED: 'payment_received', // 收款
  REFUND_CREATED: 'refund_created', // 退费
  EMR_SUBMITTED: 'emr_submitted', // 病历提交
  EMR_REJECTED: 'emr_rejected', // 病历打回
  IMAGE_UPLOADED: 'image_uploaded', // 影像上传

  // ── 产品收集 ──
  RECHARGE_RECEIVED: 'recharge_received', // 充值入账
  COMPLAINT_FILED: 'complaint_filed', // 投诉提交
  COMPLAINT_RESOLVED: 'complaint_resolved', // 投诉结案
  REFERRAL_RECORDED: 'referral_recorded', // 转介绍记录
} as const;
export type Action = (typeof Action)[keyof typeof Action];
export const ActionSchema = z.enum([
  'consultation_created',
  'appointment_created',
  'appointment_changed',
  'appointment_cancelled',
  'appointment_fulfilled',
  'visit_registered',
  'reception_completed',
  'doctor_encounter_started',
  // v2.1 拆出
  'diagnosis_recorded',
  'treatment_planned',
  'treatment_started',
  'treatment_completed',
  'recommendation_extracted',
  // 单据/病历/影像
  'order_submitted',
  'payment_received',
  'refund_created',
  'emr_submitted',
  'emr_rejected',
  'image_uploaded',
  // 产品收集
  'recharge_received',
  'complaint_filed',
  'complaint_resolved',
  'referral_recorded',
]);

/// patient_transactions.subject_type — PAC 归一化封闭集
/// **纪律**:宿主侧 resource 不允许通过造新名进来,必须归一化到此集合
/// 形式:`英文_key`(中文含义)
export const SubjectType = {
  CONSULTATION: 'consultation', // 咨询
  APPOINTMENT: 'appointment', // 预约
  VISIT_REGISTRATION: 'visit_registration', // 挂号
  VISIT_RECEPTION: 'visit_reception', // 接待登记
  CLINICAL_ENCOUNTER: 'encounter', // 医生接诊(只元数据)— W3 末统一命名,对齐 FactType.ENCOUNTER_RECORD
  // v2.1 拆出 ─
  DIAGNOSIS: 'diagnosis', // 诊断(独立)
  TREATMENT: 'treatment', // 治疗(actual/planned 由 fact.kind 区分)
  RECOMMENDATION: 'recommendation', // 医生建议(LLM/规则抽出)
  // ─────────────
  EMR_DOCUMENT: 'emr', // 病历文档 — W3 末统一命名,对齐 FactType.EMR_RECORD
  ORDER: 'order', // 医嘱(治疗/耗材/药品)
  PAYMENT: 'payment', // 收款
  REFUND: 'refund', // 退费
  IMAGE_STUDY: 'image', // 影像资料 — W3 末统一命名,对齐 FactType.IMAGE_RECORD
  // ── 产品收集 ──
  RECHARGE: 'recharge', // 充值/储值/福利卡办理
  COMPLAINT: 'complaint', // 投诉
  REFERRAL: 'referral', // 转介绍记录
} as const;
export type SubjectType = (typeof SubjectType)[keyof typeof SubjectType];
export const SubjectTypeSchema = z.enum([
  'consultation',
  'appointment',
  'visit_registration',
  'visit_reception',
  'encounter',
  // v2.1 拆出
  'diagnosis',
  'treatment',
  'recommendation',
  'emr',
  'order',
  'payment',
  'refund',
  'image',
  // 产品收集
  'recharge',
  'complaint',
  'referral',
]);

// =============================================================
// PatientFact(事实单元版本流)
// =============================================================

/// patient_facts.kind — actual / planned
export const FactKind = {
  ACTUAL: 'actual',
  PLANNED: 'planned',
} as const;
export type FactKind = (typeof FactKind)[keyof typeof FactKind];
export const FactKindSchema = z.enum(['actual', 'planned']);

/// patient_facts.status — 6 个生命周期状态
export const FactStatus = {
  ACTIVE: 'active',
  SUPERSEDED: 'superseded',
  CANCELLED: 'cancelled',
  FULFILLED: 'fulfilled',
  EXPIRED: 'expired',
  INVALIDATED: 'invalidated',
} as const;
export type FactStatus = (typeof FactStatus)[keyof typeof FactStatus];
export const FactStatusSchema = z.enum([
  'active',
  'superseded',
  'cancelled',
  'fulfilled',
  'expired',
  'invalidated',
]);

/// patient_facts.type — v2.1 重塑(W2 末)
/// 形式:`英文_key`(中文含义)
///
/// **v2.1 关键变更**:
///   - encounter_record 退化为**只装元数据**(doctor_id / notes / 时间)
///   - 拆出独立 diagnosis_record / treatment_record / recommendation_record
///   - treatment_record 合并旧 treatment_plan / review_plan(用 kind=planned + content.category 区分)
///
/// **设计纪律**:
///   - fact 只装"源事件",不做衍生 fact 派生 — 衍生信息留在源 fact 的 content JSONB 里
///   - 算法 / Persona / 前端 / AI 永远只读 fact.content,不读 raw_payload
export const FactType = {
  CONSULTATION_RECORD: 'consultation_record', // 咨询记录
  VISIT_REGISTRATION_RECORD: 'visit_registration_record', // 挂号记录
  VISIT_RECEPTION_RECORD: 'visit_reception_record', // 接待记录
  ENCOUNTER_RECORD: 'encounter_record', // 接诊事件(**只装元数据**)

  // ── v2.1 新独立 fact_type ──
  DIAGNOSIS_RECORD: 'diagnosis_record', // 诊断(content: code / tooth_position / severity)
  TREATMENT_RECORD: 'treatment_record', // 治疗(actual/planned 由 kind 区分,合并旧 treatment_plan/review_plan)
  RECOMMENDATION_RECORD: 'recommendation_record', // 医生建议(Layer C 抽取产物)

  // ── 单据 / 病历 / 影像 / 预约 ──
  ORDER_RECORD: 'order_record', // 医嘱记录
  PAYMENT_RECORD: 'payment_record', // 收款记录
  REFUND_RECORD: 'refund_record', // 退费记录
  EMR_RECORD: 'emr_record', // 病历记录(自由文本)
  IMAGE_RECORD: 'image_record', // 影像记录
  APPOINTMENT_RECORD: 'appointment_record', // 预约(kind=planned/actual 区分)

  // ── 产品收集 ──
  RECHARGE_RECORD: 'recharge_record', // 充值记录
  COMPLAINT_RECORD: 'complaint_record', // 投诉记录
  REFERRAL_RECORD: 'referral_record', // 转介绍记录
} as const;
export type FactType = (typeof FactType)[keyof typeof FactType];
export const FactTypeSchema = z.enum([
  'consultation_record',
  'visit_registration_record',
  'visit_reception_record',
  'encounter_record',
  // v2.1 新
  'diagnosis_record',
  'treatment_record',
  'recommendation_record',
  // 单据/病历/影像/预约
  'order_record',
  'payment_record',
  'refund_record',
  'emr_record',
  'image_record',
  'appointment_record',
  // 产品收集
  'recharge_record',
  'complaint_record',
  'referral_record',
]);

// =============================================================
// 治疗类别(content.treatment_category)
// =============================================================

export const TreatmentCategory = {
  ENDODONTIC: 'endodontic', // 根管 / 牙髓
  ORTHODONTIC: 'orthodontic', // 正畸
  IMPLANT: 'implant', // 种植
  PERIODONTIC: 'periodontic', // 牙周
  RESTORATIVE: 'restorative', // 修复 / 充填 / 冠
  PROSTHODONTIC: 'prosthodontic', // 修复(义齿 / 桥)
  PEDODONTIC: 'pedodontic', // 儿牙
  ORAL_SURGERY: 'oral_surgery', // 口腔外科 / 拔牙
  COSMETIC: 'cosmetic', // 美白 / 美容
  PREVENTIVE: 'preventive', // 预防 / 洁牙
  CONSULTATION: 'consultation', // 咨询
} as const;
export type TreatmentCategory = (typeof TreatmentCategory)[keyof typeof TreatmentCategory];

// =============================================================
// Persona Feature
// =============================================================

/// persona_features.key — 13 起步 + 5 产品收集 = 18 候选(v1 起步实施 4 个)
/// 形式:`英文_key`(中文含义)
export const PersonaFeatureKey = {
  // v1 起步(规则路径)
  VALUE: 'value', // 患者价值(LTV / VIP 等级)
  TREATMENT_CHAIN_STATUS: 'treatment_chain_status', // 治疗链状态(进行中 / 已闭环 / 有缺口)
  RECALL_RISK: 'recall_risk', // 流失/复发风险
  DO_NOT_CONTACT_STATUS: 'do_not_contact_status', // 不打扰状态(合规硬约束)

  // v1 候选(规则路径,业务方反馈后逐步上)
  ENTITLEMENT_STATUS: 'entitlement_status', // 权益身份(商保直付 / 医保 / 储值 / 私行;事实投影型,史+最近日期)
  INCOMPLETE_TREATMENT: 'incomplete_treatment', // 未完成治疗(治疗链有缺口)
  RECOMMENDED_ROLE: 'recommended_role', // 推荐执行角色(staff/leader/...)
  REFERRAL_RELATION: 'referral_relation', // 转介绍关系(老带新链)
  DOCTOR_RELATION: 'doctor_relation', // 医患关系(主治医生绑定)

  // W5+ 候选(agent 路径)
  COMMUNICATION_PREFERENCE: 'communication_preference', // 沟通偏好(微信 / 电话 / 拒接)
  CUSTOMER_EXPERIENCE_RISK: 'customer_experience_risk', // 客户体验风险(投诉前兆)
  TREATMENT_INTENT: 'treatment_intent', // 治疗意向(咨询转化等)
  FAMILY_SOCIAL_RELATION: 'family_social_relation', // 家庭/社交关系

  // v1 不实现(无 touchpoint 数据源,Phase 5+ 接入)
  ENGAGEMENT: 'engagement', // 触达活跃度

  // ── 产品收集(回访中心信息字段.docx)──
  PATIENT_SEGMENT: 'patient_segment', // 患者基础人群细分(儿童/中年男/老年女...)
  COMPLAINT_HISTORY: 'complaint_history', // 投诉历史(合规营销避雷)
  POTENTIAL_TREATMENT: 'potential_treatment', // 待治疗(AI 影像识别 / 病历推断)
  COOPERATION_ATTITUDE: 'cooperation_attitude', // 配合态度(好说话 / 难沟通 / 拒接)
  PRIMARY_CONTACT_RELATION: 'primary_contact_relation', // 主要联系人身份(本人/配偶/父母/子女)
} as const;
export type PersonaFeatureKey =
  (typeof PersonaFeatureKey)[keyof typeof PersonaFeatureKey];

/// Persona 触发来源
export const PersonaTriggerSource = {
  TXN: 'txn', // event-driven(主路径)
  MANUAL: 'manual', // admin / API 调试
  SCHEDULED: 'scheduled', // 每日 cron 兜底
} as const;
export type PersonaTriggerSource =
  (typeof PersonaTriggerSource)[keyof typeof PersonaTriggerSource];

// =============================================================
// FollowupPlan
// =============================================================

/// 业务场景 — PAC v1 的核心定位:
///   "治疗链已完成患者的疗效保障召回服务" + "新链召回"
///
/// 两条主战场(独立 scenario,同 patient 可同时命中,合并到一个 plan 的多个 reason):
///   - treatment_aftercare_recall  疗效保障召回(治疗链已完成,周期保养未做)
///   - treatment_initiation_recall 启治召回(漏治新链 — 诊断了 X 病但没启动 X 治疗)
///
/// 不在 v1:
///   - 链内中断(根管→未戴冠 / 正畸→保持期 / 等)— 业务拍板后再决定是否做
///   - 漏诊(影像有医生没看)— W6+,依赖 AI 影像分析
export const PlanScenario = {
  /// 治疗链已完成患者的疗效保障召回 — 链结束后周期性维护漏掉
  /// 例:种植冠装完 12 个月,医嘱年度复查,患者没回来 → 召回
  TREATMENT_AFTERCARE_RECALL: 'treatment_aftercare_recall',
  /// 启治召回 — 诊断了 X 病,患者从未启动 X 治疗
  /// 例:CT 显示 36 缺失,医生诊断"缺失牙",但无任何 implant/桥/义齿治疗 → 召回
  /// 状态:enum 占位,需 host 真实诊断数据丰富后启用(W3+)
  TREATMENT_INITIATION_RECALL: 'treatment_initiation_recall',
} as const;
export type PlanScenario = (typeof PlanScenario)[keyof typeof PlanScenario];
export const PlanScenarioSchema = z.enum([
  'treatment_aftercare_recall',
  'treatment_initiation_recall',
]);

/// Plan 生命周期状态(5 个)
export const PlanStatus = {
  ACTIVE: 'active', // 在召回池等待分配
  ASSIGNED: 'assigned', // 已分配给某客服
  COMPLETED: 'completed', // 完成
  ABANDONED: 'abandoned', // 客服放弃
  SUPERSEDED: 'superseded', // 被新版本取代
} as const;
export type PlanStatus = (typeof PlanStatus)[keyof typeof PlanStatus];
export const PlanStatusSchema = z.enum([
  'active',
  'assigned',
  'completed',
  'abandoned',
  'superseded',
]);

// =============================================================
// Plan Execution(接口 2 客服回写)
// =============================================================

/// plan_executions.outcome — 4 起步 + 7 产品收集 = 11 类
/// 形式:`英文_key`(中文含义)
///
/// W4 末调整(2026-05):
///   - 删 PROACTIVE_SMS:前端无对应按钮,生产用不到 → 清理
///   - MARKED_INVALID 状态机映射改 → abandoned(原 completed 语义错;标记无效不该算"成功完成")
export const ExecutionOutcome = {
  // 4 起步(W2 设计)
  ABANDONED: 'abandoned', // 已联系,客服放弃 → Plan abandoned
  NO_ANSWER: 'no_answer', // 未接通(打不通)→ Plan 仍 active 等下次
  QUICK_HANGUP: 'quick_hangup', // 接通秒挂(没说上话)→ 算"保持",Plan 仍 active
  SCHEDULED_NEXT: 'scheduled_next', // 已联系,约定下次 → Plan 仍 assigned
  SUCCESS_APPOINTED: 'success_appointed', // 已联系,成功新预约 → Plan completed(新预约跳到宿主创建,本表不冗余存)

  // ── 产品收集(回访中心信息字段.docx — 执行结果细化)──
  RESCHEDULED: 'rescheduled', // 已联系,改约
  CONSIDERING: 'considering', // 已联系,考虑中(近期再跟进)
  DECLINED_RECENT: 'declined_recent', // 已联系,近期不考虑
  NEEDS_DOCTOR: 'needs_doctor', // 已联系,需要找医生
  EXTERNAL_TREATMENT: 'external_treatment', // 已联系,已在外院治疗
  REFUSED: 'refused', // 已联系,明确拒绝,不需要治疗
  PENDING_INFO: 'pending_info', // 已联系,需进一步确认信息后回复
  SMS_SENT: 'sms_sent', // 电话未接,已发短信跟进
  MARKED_INVALID: 'marked_invalid', // 标记为无效(数据错 / 不该召回)→ abandoned
} as const;
export type ExecutionOutcome = (typeof ExecutionOutcome)[keyof typeof ExecutionOutcome];
export const ExecutionOutcomeSchema = z.enum([
  'abandoned',
  'no_answer',
  'quick_hangup',
  'scheduled_next',
  'success_appointed',
  // 产品收集
  'rescheduled',
  'considering',
  'declined_recent',
  'needs_doctor',
  'external_treatment',
  'refused',
  'pending_info',
  'sms_sent',
  'marked_invalid',
]);

/// outcome 提交后 Plan.status 怎么变(状态机决策)
///   completed — 任务结案(成功 / 终态客户决策)
///   abandoned — 任务关闭(放弃 / 无效)
///   keep      — Plan 状态不变(active 还是 active / assigned 还是 assigned),等下次跟进
///                注:keep 模式下熔断器仍可能介入 — contactAttempts 达上限自动 abandoned
export type ExecutionOutcomeDrives = 'completed' | 'abandoned' | 'keep';

/// outcome 的 UI tone(对应 shadcn 调色板),前端按按钮渲染
export type ExecutionOutcomeTone = 'emerald' | 'amber' | 'sky' | 'slate' | 'rose';

/// "永久"抑制的天数哨兵(患者级粒度下 = 该患者长期不再自动召回,除非人工重激活)。
/// 用大值而非 Infinity,方便落库 / 算 cutoff;100 年足够覆盖业务生命周期。
export const SUPPRESS_PERMANENT_DAYS = 36500;

/// 熔断(连续未接通累计达上限强制 abandoned)的抑制天数 —— 不是"拒绝",是"暂时联系不上",
/// 短冷静期后允许重新进池(后续可接换渠道策略)。
export const BREAKER_SUPPRESS_DAYS = 30;

/// 通话结果分组(UI 归类,group key 不变,仅 label 演进)。大体对应状态机:
///   成功 close → completed →「已完成」 / 保持 keep → keep →「进行中」 / 不成功 give_up → 多为 abandoned
///   ⚠️ 例外:不成功组里的「再考虑(冷静期7天)」drivesStatus='keep'(非终态)— group 只管 UI 归类,
///      真实状态看 drivesStatus(7天后自动浮现,plan 仍在「进行中」)。group≠status 是允许的。
export type ExecutionOutcomeGroup = 'close' | 'keep' | 'give_up';

/// 分组元数据(label / 显示顺序 / tone)。outcome-form 据此渲染分组标题。
export const EXECUTION_OUTCOME_GROUP_META: Record<
  ExecutionOutcomeGroup,
  { labelZh: string; order: number; tone: ExecutionOutcomeTone }
> = {
  close:   { labelZh: '成功',   order: 1, tone: 'emerald' },
  give_up: { labelZh: '不成功', order: 2, tone: 'rose' },
  keep:    { labelZh: '保持',   order: 3, tone: 'amber' },
};

/// ExecutionOutcome 单一真理源(group / label / tone / 状态机 / 抑制窗)
///
/// 用法:
///   - 前端 outcome-form 按 group 2 级渲染(大类 → 具体项)+ state hint
///   - 后端 execution.service.ts OUTCOME_TO_STATUS 派生自 drivesStatus
///   - 后端 execution.service.ts 用 suppressDays 算 snoozedUntil(回访冷静期 / 终态抑制窗)
///   → 前后端口径完全对齐,改 enum / group / drivesStatus / suppressDays 一处改完处处生效
///
/// suppressDays 语义(召回闭环核心):提交该 outcome 后,患者多久内不再自动进召回池
///   - 数字 N        — 写 snoozedUntil = now + N 天;到期自愈(重新可召回 / 重进池)
///   - null          — 不设固定冷静期(若执行带 scheduledNextAt,则按该时间 snooze;否则维持现状)
///   - PERMANENT     — 真闭环(外院治疗 / 无效),患者级长期不再召回
/// 标准档默认值(后续可接 tenant.rules_config 覆盖):
///   明确拒绝/近期不考虑=90d;成功转化/客服放弃=60d;外院/无效=永久;(熔断另算=30d)
///
/// hiddenInForm:历史 enum 值,新建执行不再展示(如 rescheduled 已并入"约定下次回访"),
///   但保留在 map 里让历史 plan_executions 仍能翻译 label。
export const EXECUTION_OUTCOME_META: Record<
  ExecutionOutcome,
  {
    group: ExecutionOutcomeGroup;
    labelZh: string;
    tone: ExecutionOutcomeTone;
    drivesStatus: ExecutionOutcomeDrives;
    /// 抑制天数:number=固定冷静期 / null=不固定(看 scheduledNextAt)
    suppressDays: number | null;
    /// 新建执行表单是否隐藏(历史值兼容用)
    hiddenInForm?: boolean;
  }
> = {
  // ── 成功(close → completed,「已完成」tab)──
  success_appointed:  { group: 'close',   labelZh: '转化新预约',       tone: 'emerald', drivesStatus: 'completed', suppressDays: 60 },
  // ── 不成功(give_up;明确拒绝/外院 = 终态 abandoned,再考虑 = keep+7天非终态)──
  refused:            { group: 'give_up', labelZh: '明确拒绝',         tone: 'rose',    drivesStatus: 'abandoned', suppressDays: 90 },
  external_treatment: { group: 'give_up', labelZh: '已在外院治疗',     tone: 'rose',    drivesStatus: 'abandoned', suppressDays: SUPPRESS_PERMANENT_DAYS },
  declined_recent:    { group: 'give_up', labelZh: '再考虑', tone: 'rose',  drivesStatus: 'abandoned', suppressDays: 7 }, // 非终态:7天冷静期后自动浮现(plan 仍「进行中」)
  // ── 保持(keep,留工单「进行中」)──
  no_answer:          { group: 'keep',    labelZh: '未接通',           tone: 'slate',   drivesStatus: 'keep', suppressDays: null },
  quick_hangup:       { group: 'keep',    labelZh: '秒挂',             tone: 'slate',   drivesStatus: 'keep', suppressDays: null }, // 接通秒挂,算保持,很快再试
  scheduled_next:     { group: 'keep',    labelZh: '约定下次回访',     tone: 'amber',   drivesStatus: 'keep', suppressDays: null }, // 带 scheduledNextAt → snooze 到回访日
  // ── 历史值(hiddenInForm):新建不展示,仅供历史 plan_executions 翻译 label ──
  considering:        { group: 'keep',    labelZh: '待跟进',           tone: 'sky',     drivesStatus: 'keep', suppressDays: null, hiddenInForm: true },
  marked_invalid:     { group: 'give_up', labelZh: '无效',             tone: 'rose',    drivesStatus: 'abandoned', suppressDays: SUPPRESS_PERMANENT_DAYS, hiddenInForm: true },
  rescheduled:        { group: 'keep',    labelZh: '改约',             tone: 'amber',   drivesStatus: 'keep', suppressDays: null, hiddenInForm: true }, // 已并入"约定下次回访"
  sms_sent:           { group: 'keep',    labelZh: '未接通·已发短信',  tone: 'slate',   drivesStatus: 'keep', suppressDays: null, hiddenInForm: true },
  needs_doctor:       { group: 'keep',    labelZh: '需要找医生',       tone: 'sky',     drivesStatus: 'keep', suppressDays: null, hiddenInForm: true },
  pending_info:       { group: 'keep',    labelZh: '需进一步确认信息', tone: 'sky',     drivesStatus: 'keep', suppressDays: null, hiddenInForm: true },
  abandoned:          { group: 'give_up', labelZh: '客服放弃',         tone: 'rose',    drivesStatus: 'abandoned', suppressDays: 60, hiddenInForm: true },
};

/// 召回反馈(选填·多选)— 一线客服对"这条召回本身准不准"的反馈,**正交于通话结果**。
/// 目的:把一线经验变成算法改进数据 —— 每个 tag 对应一个可改的召回算法维度,可聚合定位系统性问题。
///   info_wrong        → 信号抽取 / 数据质量(反查 extraction 规则 / 反馈 DW)
///   already_handled   → 排除闸 ⑤a/⑤b 漏(同步延迟 / 补排除)
///   duplicate_contact → 冷静期 / 去重(调 cooldown / 抑制窗)
///   bad_timing        → 黄金窗 / 入池窗(调 windowDays)
///   not_worth         → 打分 / 场景纳入(调 base 分 / scenario)
export const RECALL_FEEDBACK_OPTIONS = [
  { value: 'info_wrong',        labelZh: '信息有误',   hint: '诊断/牙位/姓名/电话不对' },
  { value: 'already_handled',   labelZh: '其实已处理', hint: '本院/外院做过·已有预约' },
  { value: 'duplicate_contact', labelZh: '重复联系',   hint: '最近已联系过' },
  { value: 'bad_timing',        labelZh: '时机不对',   hint: '太早 / 太晚' },
  { value: 'not_worth',         labelZh: '不值得召',   hint: '价值低 / 不是真机会' },
] as const;
export type RecallFeedbackTag = (typeof RECALL_FEEDBACK_OPTIONS)[number]['value'];

export const ExecutionChannel = {
  PHONE: 'phone',
  WECOM: 'wecom',
  SMS: 'sms',
  OTHER: 'other',
} as const;
export type ExecutionChannel = (typeof ExecutionChannel)[keyof typeof ExecutionChannel];
export const ExecutionChannelSchema = z.enum(['phone', 'wecom', 'sms', 'other']);

export const AbandonReason = {
  WRONG_NUMBER: 'wrong_number',
  PATIENT_REFUSED: 'patient_refused',
  OUT_OF_SERVICE_AREA: 'out_of_service_area',
  TREATED_ELSEWHERE: 'treated_elsewhere',
  NOT_INTERESTED: 'not_interested',
  DO_NOT_CONTACT_REQUEST: 'do_not_contact_request', // 自动加 DNC
  OTHER: 'other',
} as const;
export type AbandonReason = (typeof AbandonReason)[keyof typeof AbandonReason];
export const AbandonReasonSchema = z.enum([
  'wrong_number',
  'patient_refused',
  'out_of_service_area',
  'treated_elsewhere',
  'not_interested',
  'do_not_contact_request',
  'other',
]);

// =============================================================
// Plan Scripts / Summaries(异步生成,状态机)
// =============================================================

/// 资产生成状态(plan_scripts / plan_summaries 共用)
export const AssetStatus = {
  PENDING: 'pending', // 已 enqueue,正在跑或排队
  READY: 'ready', // content 已就绪
  FAILED: 'failed', // agent + 模板兜底都失败(罕见)
} as const;
export type AssetStatus = (typeof AssetStatus)[keyof typeof AssetStatus];
export const AssetStatusSchema = z.enum(['pending', 'ready', 'failed']);

/// 资产来源(content 怎么产出的;status=ready/failed 时有意义)
export const AssetSource = {
  AGENT: 'agent', // LLM 成功生成(agentInvocationId 非空)
  TEMPLATE_FALLBACK: 'template_fallback', // agent 失败,模板兜底
  MANUAL: 'manual', // 运维 / 调试手填
} as const;
export type AssetSource = (typeof AssetSource)[keyof typeof AssetSource];
export const AssetSourceSchema = z.enum(['agent', 'template_fallback', 'manual']);

/// PlanSummary 类型(per-plan × 3 type;PlanScript 单行无 type)
export const SummaryType = {
  ONE_PAGE: 'one_page',
  MEDICAL_RECORD: 'medical_record',
  TREATMENT_CHAIN: 'treatment_chain',
} as const;
export type SummaryType = (typeof SummaryType)[keyof typeof SummaryType];
export const SummaryTypeSchema = z.enum(['one_page', 'medical_record', 'treatment_chain']);

// =============================================================
// Sync(同步运行账本)
// =============================================================

/// sync_logs.direction — pull / push(替代旧 AdapterKind)
export const SyncDirection = {
  PULL: 'pull', // PAC 主动拉
  PUSH: 'push', // 宿主主动推
} as const;
export type SyncDirection = (typeof SyncDirection)[keyof typeof SyncDirection];
export const SyncDirectionSchema = z.enum(['pull', 'push']);

/// sync_logs.status — 4 个(跟 PersonaRecomputeLog / PlanGenerationLog 对齐)
export const SyncStatus = {
  RUNNING: 'running',
  SUCCESS: 'success',
  PARTIAL: 'partial',
  FAILED: 'failed',
} as const;
export type SyncStatus = (typeof SyncStatus)[keyof typeof SyncStatus];
export const SyncStatusSchema = z.enum(['running', 'success', 'partial', 'failed']);

/// Pull / Push 资源 — 跟 SubjectType 对齐 + patients + users(host /users:resolve)
export const SyncResource = {
  PATIENTS: 'patients',
  CONSULTATIONS: 'consultations',
  APPOINTMENTS: 'appointments',
  VISIT_REGISTRATIONS: 'visit_registrations',
  VISIT_RECEPTIONS: 'visit_receptions',
  CLINICAL_ENCOUNTERS: 'clinical_encounters',
  EMR_DOCUMENTS: 'emr_documents',
  CHARGE_ORDERS: 'charge_orders',
  PAYMENTS: 'payments',
  REFUNDS: 'refunds',
  IMAGE_STUDIES: 'image_studies',
  USERS: 'users', // host /users:resolve(用于 Plan 分配查客服)
} as const;
export type SyncResource = (typeof SyncResource)[keyof typeof SyncResource];

// =============================================================
// AgentInvocation
// =============================================================

/// Agent 类型(全部小写;每个 agent 一次输出可含多种子类型)
export const AgentKind = {
  SCRIPT: 'script', // 产出 PlanScript(每个 plan 1 次调用,Markdown 多子段)
  SUMMARY: 'summary', // 产出 PlanSummary(每个 plan 1 次调用,输出 JSON 含 3 type)
  PERSONA_FEATURE: 'persona_feature', // 产出 PersonaFeature(W5+ 接入)
  RANKING: 'ranking', // 将来,排序辅助
  PLAN_ANALYSIS: 'plan_analysis', // 将来,Plan 解释
} as const;
export type AgentKind = (typeof AgentKind)[keyof typeof AgentKind];
export const AgentKindSchema = z.enum([
  'script',
  'summary',
  'persona_feature',
  'ranking',
  'plan_analysis',
]);

/// Agent 调用状态(3 个;流式过程也归 running,流式完成也归 succeeded)
export const AgentStatus = {
  RUNNING: 'running',
  SUCCEEDED: 'succeeded',
  FAILED: 'failed',
} as const;
export type AgentStatus = (typeof AgentStatus)[keyof typeof AgentStatus];
export const AgentStatusSchema = z.enum(['running', 'succeeded', 'failed']);
