/**
 * 纯权限 reducer(契约 P6 / D11 / D8)。
 *
 * 设计约束(Metis 评审):本文件是纯函数层 —— 输入是"已解析好的" grant、祖先链
 * 与用户组集合,不碰 DB / 网络。数据获取在 treeService。这样权限代数可以脱离
 * 存储做密集单测与随机化不变量测试。
 */

import type { FileLibRole } from "./model.js";
import { ROLE_RANK } from "./model.js";

export interface FileLibGrantFact {
  readonly nodeId: string;
  readonly principalType: "USER" | "GROUP";
  readonly principalId: string;
  readonly role: FileLibRole;
  readonly isCreatorGrant: boolean;
}

export interface EffectiveRoleInput {
  /** 目标节点(self)。 */
  readonly nodeId: string;
  readonly nodeKind: "FOLDER" | "PROJECT";
  /** 目标的全部祖先 id(不含 self,顺序无关)。 */
  readonly ancestorIds: readonly string[];
  /** 项目独立权限开关(D11/P5);文件夹忽略此值。 */
  readonly independentPermissionsEnabled: boolean;
  readonly userId: string;
  /** C2 resolve 结果:用户直接所属 + 全部祖先 group 的 id 集合。 */
  readonly groupIds: readonly string[];
  /** self ∪ ancestors 上的全部活跃 grant(revokedAt 已由获取层过滤)。 */
  readonly grants: readonly FileLibGrantFact[];
}

/**
 * 契约 P6:effective(user, R) = max { grant.role | s ∈ {user} ∪ groups*(user),
 * r ∈ {R} ∪ ancestors(R) };无匹配 → null(无任何权限)。
 * "个人权限不能降权"在 max 语义下天然成立 —— 只取最高,不做减法。
 *
 * D11:目标为 PROJECT 且独立权限关闭时,项目级(挂在 self 上)非创建者 grant
 * 冻结不参与计算;创建者的自动 grant(isCreatorGrant)始终生效。祖先链上的
 * grant 不受开关影响。
 */
export function effectiveRole(input: EffectiveRoleInput): FileLibRole | null {
  const onChain = new Set<string>([input.nodeId, ...input.ancestorIds]);
  const groups = new Set(input.groupIds);
  const freezeProjectGrants =
    input.nodeKind === "PROJECT" && !input.independentPermissionsEnabled;

  let best: FileLibRole | null = null;
  for (const grant of input.grants) {
    if (!onChain.has(grant.nodeId)) continue;
    if (freezeProjectGrants && grant.nodeId === input.nodeId && !grant.isCreatorGrant) continue;
    if (grant.principalType === "USER" && grant.principalId !== input.userId) continue;
    if (grant.principalType === "GROUP" && !groups.has(grant.principalId)) continue;
    if (best === null || ROLE_RANK[grant.role] > ROLE_RANK[best]) best = grant.role;
  }
  return best;
}

/**
 * D8 可见性语义:
 *  - 完全无权限(effective === null)→ "not_found"(路由层映射 404,不泄露存在性);
 *  - 有权限但不足 → "forbidden"(路由层映射 403)。
 */
export type AccessVerdict =
  | { readonly allowed: true; readonly role: FileLibRole }
  | { readonly allowed: false; readonly reason: "not_found" | "forbidden" };

export function checkAccess(effective: FileLibRole | null, min: FileLibRole): AccessVerdict {
  if (effective === null) return { allowed: false, reason: "not_found" };
  if (ROLE_RANK[effective] < ROLE_RANK[min]) return { allowed: false, reason: "forbidden" };
  return { allowed: true, role: effective };
}
