import {
  ABANDON_REASON_META,
  AbandonReason,
  EXECUTION_OUTCOME_META,
  ExecutionOutcome,
  BREAKER_SUPPRESS_DAYS,
} from '@pac/types';

const DAY_MS = 86_400_000;

/**
 * 计算 execution 回写后的 snoozedUntil(召回冷静期 / 终态抑制窗)—— 纯函数,便于单测。
 *
 * 优先级(高 → 低):
 *   1. 熔断(breakerTripped):连续未接通累计达上限强制 abandoned → 固定短冷静期(30d),
 *      不是"拒绝",换天再试 / 后续可接换渠道。优先于 outcome 自身策略。
 *   2. 放弃原因细分(abandonReasons → ABANDON_REASON_META.suppressDays):
 *      同一个 `abandoned` 结果,原因不同该压的时长差很远 —— 「机会识别不准确」是**我们**的
 *      信号有问题(14d,等 DW 同步 + 排除闸接手,不该罚患者);「迁居外地 / 投诉」则该永久。
 *      **多选取 max**:每个勾中的原因都独立成立,取最严的那个才不会把该压死的放出来。
 *      没配 suppressDays 的原因(如「明确不需要治疗」)沿用下面 outcome 的默认档,一起参与 max。
 *   3. outcome 固定抑制窗(EXECUTION_OUTCOME_META.suppressDays):
 *      明确拒绝/近期不考虑=90d、成功转化/客服放弃=60d、外院/无效=永久。
 *   4. keep 类无固定抑制窗但带了 scheduledNextAt(约定回访/考虑中等)→ 到该时间前 snooze。
 *   5. 其余(no_answer / sms_sent 等)→ null,不 snooze,维持现状很快再试。
 *
 * 注:宿主回访模式的「关闭机会」不是独立分支 —— 它就是 outcome='abandoned' + abandonReasons,
 *    走上面第 2 条。PAC 侧不为它另立概念,分叉只在展示层(ABANDON_REASON_META.shownIn)。
 *
 * @returns snoozedUntil Date,或 null(不抑制)
 */
export function resolveSnoozedUntil(params: {
  outcome: string;
  breakerTripped: boolean;
  scheduledNextAt?: string | null;
  now: Date;
  /** 放弃原因(多选);仅 outcome 自带抑制窗时参与 —— 见上方第 2 条 */
  abandonReasons?: string[] | null;
}): Date | null {
  const { outcome, breakerTripped, scheduledNextAt, now, abandonReasons } = params;

  if (breakerTripped) {
    return new Date(now.getTime() + BREAKER_SUPPRESS_DAYS * DAY_MS);
  }

  const meta = EXECUTION_OUTCOME_META[outcome as ExecutionOutcome];

  if (meta?.suppressDays != null) {
    // 原因级细分:每项 effective = 自己的 suppressDays ?? outcome 默认档,多选取 max。
    // ⚠️ max 的种子**不能**是 outcome 默认档 —— 否则比默认档短的原因(识别不准 14d)
    //    单独勾选时会被 60d 盖掉,短档形同虚设。没有已知原因时才退回默认档。
    const known = (abandonReasons ?? []).filter((r) => ABANDON_REASON_META[r as AbandonReason]);
    const days = known.length
      ? Math.max(
          ...known.map(
            (r) => ABANDON_REASON_META[r as AbandonReason].suppressDays ?? meta.suppressDays!,
          ),
        )
      : meta.suppressDays;
    return new Date(now.getTime() + days * DAY_MS);
  }

  if (scheduledNextAt) {
    return new Date(scheduledNextAt);
  }

  return null;
}
