import { ApiCode } from '@pac/types';
import { BizError } from '../../common/errors/biz-error';

/**
 * 认领闸 —— 「未认领只能看,不能操作」的服务端硬约束。
 *
 * 为什么必须在服务端拦(不能只做前端禁用):
 *   1. **执行必须有归属**。execution.service 特意保留 assigneeUserId 作业绩归属
 *      (见那里的注释:清了会让「我的已完成」/ 转化率永远为 0)。未认领就提交执行 =
 *      这条执行没有任何归属人,统计里直接蒸发 —— 前端提示拦不住这个数据问题。
 *   2. **防撞单**。两个客服同时打同一个患者,只有服务端能仲裁。
 *   3. 生产实证(2026-07):缺牙召回有两条 plan 在 `assignee IS NULL` 状态下被直接关闭,
 *      当时 submitExecution 只校验了 status、完全没看 assignee。
 *
 * ⚠️ 适用边界 —— 不是所有写操作都该过这道闸:
 *   过闸:提交通话结果 / 关闭机会、重生成话术(含 SSE)、话术反馈、召回反馈
 *   不过闸:
 *     · `assign`(认领本身就是入口,过闸会变成死锁)
 *     · `recycle`(PLAN_RECYCLE 是 **leader 独占**,语义就是"回收 staff 已认领的单",
 *        按"必须自己认领"拦会把组长回收功能整个废掉)
 *     · `recompute`(ops/dev 工具,走 PLAN_ASSIGN 权限)
 *
 * 两种拒绝态分开报,文案不同:没人认领 → 引导去认领;别人认领了 → 说清是谁,
 * 否则组员会以为是自己没点对。
 */
export interface ClaimablePlan {
  assigneeUserId: string | null;
}

export function assertPlanClaimedBy(plan: ClaimablePlan, userId: string): void {
  if (!plan.assigneeUserId) {
    throw new BizError(ApiCode.PLAN_NOT_CLAIMED);
  }
  if (plan.assigneeUserId !== userId) {
    // details 带上占用人,前端可查字典换成姓名展示("已被 张三 认领")
    throw new BizError(ApiCode.PLAN_CLAIMED_BY_OTHER, undefined, {
      assigneeUserId: plan.assigneeUserId,
    });
  }
}
