import type { AbilityDefinition, AbilityEffect } from "./abilityCatalog"; export type PartyBuffScope = "party" | "tank" | "healer"; export interface PartyBuffRule { readonly abilityId: string; readonly scope: PartyBuffScope } export interface PartyBuffPolicy { readonly schemaVersion: 1; readonly rules: readonly PartyBuffRule[] } export const DEFAULT_PARTY_BUFF_POLICY: PartyBuffPolicy = { schemaVersion: 1, rules: [] }; export function normalizePartyBuffPolicy(value: unknown): PartyBuffPolicy { if (!value || typeof value !== "object") return DEFAULT_PARTY_BUFF_POLICY; const policy = value as Partial; if (policy.schemaVersion !== 1 || !Array.isArray(policy.rules)) return DEFAULT_PARTY_BUFF_POLICY; const seen = new Set(); return { schemaVersion: 1, rules: policy.rules.flatMap((rule) => { if (!rule || typeof rule.abilityId !== "string" || !/^[a-z0-9][a-z0-9:._-]{0,127}$/i.test(rule.abilityId) || !["party", "tank", "healer"].includes(rule.scope) || seen.has(rule.abilityId)) return []; seen.add(rule.abilityId); return [{ abilityId: rule.abilityId, scope: rule.scope }]; }).slice(0, 500), }; } export function permanentBuffEffects(ability: AbilityDefinition): readonly Extract[] { if (ability.passive || ability.target === "hostile" || ability.talentNodeId || ability.talentEntryId !== undefined) return []; if (ability.effects.some((effect) => effect.kind !== "apply-aura" && effect.kind !== "unsupported")) return []; return ability.effects.filter((effect): effect is Extract => ( effect.kind === "apply-aura" && effect.aura.disposition === "buff" && !effect.aura.procs?.length && !effect.aura.controlImmunities?.length && Boolean(effect.aura.modifiers?.length) )); }