import { buffsFromKeyword } from "../cruncher/from-keyword.js";
import { effectToBuffs, } from "../cruncher/from-dsl.js";
/** A unit, linked to its faction, weapons, and abilities. */
export class UnitView {
    raw;
    ds;
    constructor(
    /** The full generated `Unit` record. */
    raw, ds) {
        this.raw = raw;
        this.ds = ds;
    }
    get id() {
        return this.raw.id;
    }
    get name() {
        return this.raw.name;
    }
    /** The unit's faction, or `undefined` if its `faction_id` is unknown. */
    get faction() {
        return this.ds.factions.get(this.raw.faction_id);
    }
    /** Weapons referenced by `weapon_ids`; unresolved ids are skipped. */
    get weapons() {
        return resolveAll(this.raw.weapon_ids, (id) => this.ds.weapons.get(id));
    }
    /** Abilities referenced by `ability_ids`; unresolved ids are skipped. */
    get abilities() {
        return resolveAll(this.raw.ability_ids, (id) => this.ds.abilities.get(id));
    }
    /** Wargear options (weapon swaps, add-ons, choices) authored for this unit. */
    get wargearOptions() {
        return this.ds.wargearOptionsOf(this.raw);
    }
    /**
     * The stat profile at index `i` (default 0). Returns the schema-generated
     * profile object directly so callers can feed it straight to the engine
     * without an intermediate wrapper.
     */
    profileAt(i = 0) {
        const profile = this.raw.profiles[i];
        if (profile === undefined) {
            throw new RangeError(`UnitView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`);
        }
        return profile;
    }
}
/**
 * An ability, linked to the phases it acts in and the units that have it.
 *
 * Phases are not stored on the ability — they live in `phase-mappings` records.
 *
 * @example
 * units.find("Kharn")!.abilities
 *   .filter(a => a.phases.includes("shooting"));
 */
export class AbilityView {
    raw;
    ds;
    constructor(
    /** The full generated ability record. */
    raw, ds) {
        this.raw = raw;
        this.ds = ds;
    }
    /** The ability's id (`ability_id` in the raw record). */
    get id() {
        return this.raw.ability_id;
    }
    get name() {
        return this.raw.name;
    }
    /** Game phases this ability acts in, unioned across its phase-mappings. */
    get phases() {
        return this.ds.phasesFor("ability", this.raw.ability_id);
    }
    /** Units that list this ability in their `ability_ids`. */
    get units() {
        return this.ds.unitsWithAbility(this.raw.ability_id);
    }
    /**
     * Buff stack this ability contributes against `context`, with provenance
     * tagged via `source` (the caller knows whether this ability is being read
     * as army, detachment, unit, leader, etc.). DSL branches the buff layer
     * can't auto-apply are dropped here; call {@link describeBuffs} if you
     * also want the diagnostics. `perspective` defaults to `"attacker"`; pass
     * `"target"` to translate the ability as a defensive buff (FNP, T/Sv
     * stat-mods, save rerolls, incoming hit penalties).
     */
    getBuffs(source, context, perspective = "attacker") {
        return this.describeBuffs(source, context, perspective).applied;
    }
    /**
     * Full DSL→Buff translation, including the `unsupported` list of effect
     * fragments the buff layer can't model. The SPA renders these as warnings
     * so users see which abilities have effects that need a manual toggle.
     */
    describeBuffs(source, context, perspective = "attacker") {
        const ctx = context ?? { phase: "shooting" };
        return effectToBuffs(this.raw.effect, source, ctx, perspective);
    }
}
/** A weapon, linked to the units that carry it. */
export class WeaponView {
    raw;
    ds;
    constructor(
    /** The full generated `Weapon` record. */
    raw, ds) {
        this.raw = raw;
        this.ds = ds;
    }
    get id() {
        return this.raw.id;
    }
    get name() {
        return this.raw.name;
    }
    /** Units that list this weapon in their `weapon_ids`. */
    get units() {
        return this.ds.unitsWithWeapon(this.raw.id);
    }
    /** The stat profile at index `i` (default 0). */
    profileAt(i = 0) {
        const profile = this.raw.profiles[i];
        if (profile === undefined) {
            throw new RangeError(`WeaponView(${this.raw.id}).profileAt(${i}): only ${this.raw.profiles.length} profile(s) defined`);
        }
        return profile;
    }
    /**
     * Catalog views for each keyword referenced by profile `i`, paired with the
     * reference-site parameters. Unresolved keyword ids are skipped.
     */
    keywordsAt(i = 0) {
        const profile = this.profileAt(i);
        const refs = profile.keywords ?? [];
        const out = [];
        for (const ref of refs) {
            const view = this.ds.weaponKeywords.get(ref.keyword_id);
            if (!view)
                continue;
            out.push({
                keyword: view,
                parameters: ref.parameters,
            });
        }
        return out;
    }
    /**
     * Buffs contributed by profile `i`'s intrinsic keywords against `context` —
     * the natural "what does this profile bring on its own?" call the engine
     * makes automatically before adding ability/manual buffs.
     */
    profileBuffs(i, context) {
        const index = i ?? 0;
        const out = [];
        for (const { keyword, parameters } of this.keywordsAt(index)) {
            out.push(...buffsFromKeyword({
                keywordId: keyword.id,
                weaponId: this.raw.id,
                effect: keyword.raw.effect,
                ...(parameters !== undefined ? { parameters } : {}),
                context,
            }));
        }
        return out;
    }
}
/**
 * A weapon-keyword catalog entry, linked to the weapons whose profiles
 * reference it. Exposes the keyword's mechanical effect as a buff stack
 * via {@link getBuffs}.
 */
export class WeaponKeywordView {
    raw;
    ds;
    constructor(
    /** The full generated `WeaponKeyword` record. */
    raw, ds) {
        this.raw = raw;
        this.ds = ds;
    }
    get id() {
        return this.raw.id;
    }
    get name() {
        return this.raw.name;
    }
    /** Weapons whose profiles reference this keyword id. */
    get weapons() {
        return this.ds.weaponsWithKeyword(this.raw.id);
    }
    /**
     * Buff contributions from this catalog entry, for one reference site:
     * pass the keyword's `parameters` (e.g. `{ value: 1 }` for Sustained Hits 1)
     * along with the `weaponId` that's carrying it (used as the buff source)
     * and the engine `context` (e.g. attacker stationary?).
     */
    getBuffs(parameters, weaponId, context) {
        return buffsFromKeyword({
            keywordId: this.raw.id,
            weaponId,
            effect: this.raw.effect,
            ...(parameters !== undefined ? { parameters } : {}),
            context,
        });
    }
}
/** A faction, linked to its units and the records scoped to it. */
export class FactionView {
    raw;
    ds;
    constructor(
    /** The full generated `Faction` record. */
    raw, ds) {
        this.raw = raw;
        this.ds = ds;
    }
    get id() {
        return this.raw.id;
    }
    get name() {
        return this.raw.name;
    }
    /** Units whose `faction_id` is this faction (may be empty for successors). */
    get units() {
        return this.ds.units.byFaction(this.raw.id);
    }
    /** Faction-scoped abilities (abilities whose `faction_id` is this faction). */
    get abilities() {
        return this.ds.abilities.byFaction(this.raw.id);
    }
    /** Distinct weapons carried by this faction's units. */
    get weapons() {
        const seen = new Set();
        const out = [];
        for (const unit of this.units) {
            for (const weapon of unit.weapons) {
                if (seen.has(weapon.id))
                    continue;
                seen.add(weapon.id);
                out.push(weapon);
            }
        }
        return out;
    }
}
/** Resolve a list of ids, dropping any that don't resolve. */
function resolveAll(ids, get) {
    const out = [];
    for (const id of ids ?? []) {
        const v = get(id);
        if (v !== undefined)
            out.push(v);
    }
    return out;
}
//# sourceMappingURL=entities.js.map