/** Compute the sorted-by-source eligible-ability list for one (unit, phase). */
export function resolveEligibleAbilities(dataset, input, phase) {
    // Resolve units within the faction when one is known. Unit ids are shared
    // across factions (a shared chassis like `chaos-land-raider` lives under
    // several Chaos factions), so a faction-blind `get` can return the wrong
    // faction's copy — and with it the wrong intrinsic abilities/keywords.
    const resolveUnit = (id, fid) => (fid ? dataset.units.getInFaction(id, fid) : undefined) ?? dataset.units.get(id);
    const unit = resolveUnit(input.unitId, input.factionId);
    if (!unit)
        return [];
    const factionId = input.factionId ?? unit.raw.faction_id;
    const seen = new Set();
    const out = [];
    // 1. Army — faction-scoped abilities (faction rule + any other faction-typed).
    for (const ability of dataset.abilities.byFaction(factionId)) {
        if (ability.raw.ability_type !== "faction")
            continue;
        if (!phaseMatches(ability, phase))
            continue;
        pushUnique(out, seen, { ability, source: { kind: "army" }, phases: intersect(ability.phases, phase) });
    }
    // 2. Detachment abilities — abilities whose detachment_id matches.
    if (input.detachmentId) {
        for (const ability of dataset.abilities) {
            if (ability.raw.ability_type !== "detachment")
                continue;
            if (ability.raw.detachment_id !== input.detachmentId)
                continue;
            if (!phaseMatches(ability, phase))
                continue;
            pushUnique(out, seen, {
                ability,
                source: { kind: "detachment", detachmentId: input.detachmentId },
                phases: intersect(ability.phases, phase),
            });
        }
        // 3. Detachment stratagems.
        const detachment = dataset.detachments.get(input.detachmentId);
        if (detachment) {
            for (const stratId of detachment.stratagem_ids ?? []) {
                const stratagem = dataset.stratagems.get(stratId);
                if (!stratagem)
                    continue;
                if (!stratagemPhaseMatches(stratagem, phase))
                    continue;
                const ability = stratagem.ability_id !== null && stratagem.ability_id !== undefined
                    ? dataset.abilities.get(stratagem.ability_id)
                    : undefined;
                if (!ability)
                    continue;
                pushUnique(out, seen, {
                    ability,
                    source: {
                        kind: "detachment-stratagem",
                        stratagemId: stratagem.id,
                        cpCost: stratagem.cp_cost,
                    },
                    phases: [phase], // the stratagem's printed phase governs eligibility.
                });
            }
        }
    }
    // 4. Unit's own abilities.
    for (const ability of unit.abilities) {
        if (!phaseMatches(ability, phase))
            continue;
        pushUnique(out, seen, {
            ability,
            source: { kind: "unit", unitId: input.unitId },
            phases: intersect(ability.phases, phase),
        });
    }
    // 5. Attached members — the combined unit pools every member's abilities,
    // pulled in full (not aura-filtered like step 6), regardless of which member
    // is the selected/firing unit.
    for (const memberId of input.attachedUnitIds ?? []) {
        const member = resolveUnit(memberId, factionId);
        if (!member)
            continue;
        for (const ability of member.abilities) {
            if (!phaseMatches(ability, phase))
                continue;
            pushUnique(out, seen, {
                ability,
                source: { kind: "attached", unitId: memberId },
                phases: intersect(ability.phases, phase),
            });
        }
    }
    // 6. Supporting units — only aura-scoped abilities (otherwise the buff
    // would describe a self-target effect that doesn't reach the input unit).
    for (const supportId of input.supportingUnitIds ?? []) {
        const supporter = resolveUnit(supportId, factionId);
        if (!supporter)
            continue;
        for (const ability of supporter.abilities) {
            if (!phaseMatches(ability, phase))
                continue;
            if (!isAuraScope(ability.raw.scope?.range))
                continue;
            pushUnique(out, seen, {
                ability,
                source: { kind: "support", sourceUnitId: supportId },
                phases: intersect(ability.phases, phase),
            });
        }
    }
    return out;
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function phaseMatches(ability, phase) {
    const phases = ability.phases;
    // An ability with no phase-mapping is permissive — surface it everywhere so
    // the SPA can decide. M2's translator already gates conditional-on-phase
    // effects internally, so this stays generous on purpose.
    if (phases.length === 0)
        return true;
    return phases.includes(phase);
}
function stratagemPhaseMatches(stratagem, phase) {
    if (!stratagem.phases || stratagem.phases.length === 0)
        return false;
    return stratagem.phases.includes(phase);
}
function intersect(phases, phase) {
    return phases.includes(phase) ? [phase] : phases;
}
function isAuraScope(range) {
    if (typeof range !== "string")
        return false;
    return range.startsWith("aura-") || range === "any-on-battlefield" || range === "any-visible";
}
function pushUnique(out, seen, entry) {
    const key = `${entry.source.kind}::${entry.ability.id}`;
    if (seen.has(key))
        return;
    seen.add(key);
    out.push(entry);
}
//# sourceMappingURL=resolver.js.map