/**
 * 文件库领域基础:角色秩、命名规则(D14)、错误类型。
 *
 * 语义锚定:仓库根《文件库-接口契约.md》(2.2 权限等级 / D8 可见性 / D14 命名)
 * 与 .omo/文件库-开工计划.md。本模块是独立文件库,不复用 hub 的
 * Folder/Project/PermissionGrant 体系。
 */

export const FILE_LIB_ROLES = ["VIEW", "EDIT", "MANAGE"] as const;
export type FileLibRole = (typeof FILE_LIB_ROLES)[number];

/** 契约 2.2:MANAGE > EDIT > VIEW,严格全序。 */
export const ROLE_RANK: Record<FileLibRole, number> = { VIEW: 1, EDIT: 2, MANAGE: 3 };

export function roleAtLeast(role: FileLibRole, min: FileLibRole): boolean {
  return ROLE_RANK[role] >= ROLE_RANK[min];
}

/** 业务错误。statusCode 由路由层映射为 HTTP 响应(D8 语义在此层只表达为 code)。 */
export class FileLibError extends Error {
  constructor(
    readonly statusCode: number,
    readonly code: string,
    message: string,
    /** 结构化附加信息(如 409 时的 currentVersion),路由层并入错误响应。 */
    readonly details?: Record<string, unknown>,
  ) {
    super(message);
    this.name = "FileLibError";
  }
}

export const NODE_NAME_MAX_LENGTH = 128;

/** D14:禁 `/`;反斜杠同样禁止(它会变成存储路径的分隔符,且易用于伪装)。控制字符禁。 */
const FORBIDDEN_NAME_CHARS = /[/\\\p{C}]/u;

/**
 * D14:NFC 归一化 + trim,然后校验长度与字符集。
 * 违规抛 FileLibError(400, "invalid_name"),路由层原样透传。
 */
export function normalizeNodeName(raw: string): string {
  const name = raw.normalize("NFC").trim();
  if (name.length === 0) {
    throw new FileLibError(400, "invalid_name", "name must not be empty");
  }
  if (name.length > NODE_NAME_MAX_LENGTH) {
    throw new FileLibError(400, "invalid_name", `name exceeds ${NODE_NAME_MAX_LENGTH} characters`);
  }
  if (FORBIDDEN_NAME_CHARS.test(name)) {
    throw new FileLibError(400, "invalid_name", "name must not contain '/', '\\' or control characters");
  }
  return name;
}

/** D14:活跃兄弟节点大小写不敏感唯一的比较键(DB 层另有部分唯一索引兜底)。 */
export function nameKey(normalizedName: string): string {
  return normalizedName.toLowerCase();
}
