models.js

/**
 * @module
 */
import constants from './constants.js';
import { PokemonBase } from './pokemonbase.js';
function deaccent(text) {
    const norm = text.normalize('NFD');
    const result = Array.from(norm)
        .filter(ch => !/[\u0300-\u036f]/.test(ch))
        .join('');
    return result.normalize('NFKC');
}
class UnregisteredError extends Error {
}
/**
 * Represents the effect of a Pokemon move.
 *
 * @class MoveEffect
 * @property {number} id - The ID of the move effect.
 * @property {string} description - The description of the move effect.
 * @property {DataManagerBase} instance - The DataManagerBase instance.
 */
class MoveEffect {
    id;
    description;
    instance;
    constructor(id, description, instance) {
        this.id = id;
        this.description = description;
        this.instance = instance;
    }
    format() {
        return this.description;
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
}
/**
 * Represents a change to a Pokemon's stat stages.
 *
 * @class StatChange
 * @property {number} stat_id - The ID of the stat that is being changed.
 * @property {number} change - The amount by which the stat stage is being changed.
 */
class StatChange {
    stat_id;
    change;
    constructor(stat_id, change) {
        this.stat_id = stat_id;
        this.change = change;
    }
    get stat() {
        return ['hp', 'atk', 'defn', 'satk', 'sdef', 'spd', 'evasion', 'accuracy'][this.stat_id - 1];
    }
}
/**
 * Represents the stat stages of a Pokemon.
 *
 * @class StatStages
 * @property {number} hp - The HP stat stage.
 * @property {number} atk - The Attack stat stage.
 * @property {number} defn - The Defense stat stage.
 * @property {number} satk - The Special Attack stat stage.
 * @property {number} sdef - The Special Defense stat stage.
 * @property {number} spd - The Speed stat stage.
 * @property {number} evasion - The evasion stat stage.
 * @property {number} accuracy - The accuracy stat stage.
 * @property {number} crit - The critical hit rate stat stage.
 */
class StatStages {
    hp;
    atk;
    defn;
    satk;
    sdef;
    spd;
    evasion;
    accuracy;
    crit;
    constructor(hp = 0, atk = 0, defn = 0, satk = 0, sdef = 0, spd = 0, evasion = 0, accuracy = 0, crit = 0) {
        this.hp = hp;
        this.atk = atk;
        this.defn = defn;
        this.satk = satk;
        this.sdef = sdef;
        this.spd = spd;
        this.evasion = evasion;
        this.accuracy = accuracy;
        this.crit = crit;
    }
    update(stages) {
        this.hp += stages.hp;
        this.atk += stages.atk;
        this.defn += stages.defn;
        this.satk += stages.satk;
        this.sdef += stages.sdef;
        this.spd += stages.spd;
        this.evasion += stages.evasion;
        this.accuracy += stages.accuracy;
        this.crit += stages.crit;
    }
}
/**
 * Represents the results of a Pokemon move being used.
 *
 * @class MoveResult
 * @property {boolean} success - Whether the move was successful.
 * @property {number} damage - The amount of damage dealt by the move.
 * @property {number} healing - The amount of HP healed by the move.
 * @property {string|null} ailment - The ailment inflicted by the move, or null if none.
 * @property {string[]} messages - Additional messages about the move's effect.
 * @property {StatChange[]} stat_changes - A list of stat changes caused by the move.
 */
class MoveResult {
    success;
    damage;
    healing;
    ailment;
    messages;
    stat_changes;
    constructor(success, damage, healing, ailment, messages, stat_changes) {
        this.success = success;
        this.damage = damage;
        this.healing = healing;
        this.ailment = ailment || null;
        this.messages = messages;
        this.stat_changes = stat_changes;
    }
}
/**
 * Additional information about the mechanics of a Pokemon move.
 *
 * @class MoveMeta
 * @property {number} meta_category_id - The ID of the move's meta category (e.g., damage dealing, status inflicting).
 * @property {string} meta_ailment_id - The ID of the ailment that the move can inflict.
 * @property {number} drain - The damage dealt that is healed back to the user.
 * @property {number} healing - Thethe user's max HP that is healed.
 * @property {number} crit_rate - The additional critical hit rate of the move.
 * @property {number} ailment_chance - The chance that the move will inflict an ailment.
 * @property {number} flinch_chance - The chance that the move will cause the target to flinch.
 * @property {number} stat_chance - The chance that the move will cause a stat change.
 * @property {number|null} min_hits - The minimum number of times the move can hit, or null if it always hits once.
 * @property {number|null} max_hits - The maximum number of times the move can hit, or null if it always hits once.
 * @property {number|null} min_turns - The minimum number of turns the move's effect lasts, or null if it lasts one turn.
 * @property {number|null} max_turns - The maximum number of turns the move's effect lasts, or null if it lasts one turn.
 * @property {StatChange[]} stat_changes - A list of stat changes that the move can cause.
 */
class MoveMeta {
    meta_category_id;
    meta_ailment_id;
    drain;
    healing;
    crit_rate;
    ailment_chance;
    flinch_chance;
    stat_chance;
    min_hits;
    max_hits;
    min_turns;
    max_turns;
    stat_changes;
    constructor(meta_category_id, meta_ailment_id, drain, healing, crit_rate, ailment_chance, flinch_chance, stat_chance, min_hits = null, max_hits = null, min_turns = null, max_turns = null, stat_changes = []) {
        this.meta_category_id = meta_category_id;
        this.meta_ailment_id = meta_ailment_id;
        this.drain = drain;
        this.healing = healing;
        this.crit_rate = crit_rate;
        this.ailment_chance = ailment_chance;
        this.flinch_chance = flinch_chance;
        this.stat_chance = stat_chance;
        this.min_hits = min_hits;
        this.max_hits = max_hits;
        this.min_turns = min_turns;
        this.max_turns = max_turns;
        this.stat_changes = stat_changes || [];
    }
    get meta_category() {
        return constants.MOVE_META_CATEGORIES[this.meta_category_id];
    }
    get meta_ailment() {
        return constants.MOVE_AILMENTS[this.meta_ailment_id];
    }
}
/**
 * Represents a Pokemon move.
 *
 * @class Move
 * @property {number} id - The ID of the move.
 * @property {string} slug - The slug for the move
 * @property {string} name - The name of the move.
 * @property {number|null} power - The power of the move, or null if it doesn't deal damage.
 * @property {number} pp - The number of Power Points (PP) the move has.
 * @property {number|null} accuracy - The accuracy of the move, or null if it never misses.
 * @property {number} priority - The priority of the move (higher values go first).
 * @property {number} target_id - The ID of the move's target (e.g., one opponent, all Pokemon).
 * @property {number} type_id - The ID of the move's type (e.g., Electric, Fire).
 * @property {number} damage_class_id - The ID of the move's damage class (e.g., Physical, Special, Status).
 * @property {number} effect_id - The ID of the move's effect.
 * @property {number|null} effect_chance - The chance that the move's effect will occur, or null if it always occurs.
 * @property {MoveMeta} meta - Additional information about the move's mechanics.
 * @property {DataManagerBase} instance - The DataManagerBase instance
 */
class Move {
    id;
    slug;
    name;
    power;
    pp;
    accuracy;
    priority;
    target_id;
    type_id;
    damage_class_id;
    effect_id;
    effect_chance;
    meta;
    instance;
    constructor(id, slug, name, power, pp, accuracy, priority, target_id, type_id, damage_class_id, effect_id, effect_chance, meta, instance) {
        this.id = id;
        this.slug = slug;
        this.name = name;
        this.power = power;
        this.pp = pp;
        this.accuracy = accuracy;
        this.priority = priority;
        this.target_id = target_id;
        this.type_id = type_id;
        this.damage_class_id = damage_class_id;
        this.effect_id = effect_id;
        this.effect_chance = effect_chance;
        this.meta = meta;
        this.instance = instance;
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
    get type() {
        return constants.TYPES[this.type_id];
    }
    get target_text() {
        return constants.MOVE_TARGETS[this.target_id];
    }
    get damage_class() {
        return constants.DAMAGE_CLASSES[this.damage_class_id];
    }
    get effect() {
        return this.instance.effects[this.effect_id];
    }
    get description() {
        return this.effect.description;
    }
    /**
    * Simulate a fight between two pokemon instances
    * @param {PokemonBase} pokemon - The pokemon performing the move
    * @param {PokemonBase} opponent - Opponent pokemon
    * @returns {MoveResult} The result of a move.
    */
    calculate_turn(pokemon, opponent) {
        let success;
        let damage = 0;
        let hits = 0;
        if (this.damage_class_id == 1 || this.power === null) {
            success = true;
            damage = 0;
            hits = 0;
        }
        else {
            success =
                Math.random() <
                    (Number(this.accuracy || 0) *
                        (constants.STAT_STAGE_MULTIPLIERS[String(pokemon.stages.accuracy)] *
                            2 +
                            1)) /
                        (Number(constants.STAT_STAGE_MULTIPLIERS[String(opponent.stages.evasion)]) *
                            2 +
                            1);
            hits =
                Math.floor(Math.random() *
                    (Number(this.meta.max_hits || 1) -
                        Number(this.meta.min_hits || 1) +
                        1)) + Number(this.meta.min_hits || 1);
            let atk = 0;
            let defn = 0;
            if (this.damage_class_id == 2) {
                atk =
                    Number(pokemon.atk) *
                        Number(constants.STAT_STAGE_MULTIPLIERS[String(pokemon.stages.atk)]);
                defn =
                    Number(opponent.defn) *
                        Number(constants.STAT_STAGE_MULTIPLIERS[String(opponent.stages.defn)]);
            }
            else {
                atk =
                    Number(pokemon.satk) *
                        Number(constants.STAT_STAGE_MULTIPLIERS[String(pokemon.stages.satk)]);
                defn =
                    Number(opponent.sdef) *
                        Number(constants.STAT_STAGE_MULTIPLIERS[String(opponent.stages.sdef)]);
            }
            damage = Math.floor((((2 * Number(pokemon.level)) / 5 + 2) * Number(this.power) * atk) /
                defn /
                50 +
                2);
        }
        let healing = (Number(damage) * Number(this.meta.drain)) / 100;
        healing += (Number(pokemon.max_hp) * Number(this.meta.healing)) / 100;
        for (const ailment of pokemon.ailments) {
            if (ailment === 'Paralysis') {
                if (Math.random() < 0.25) {
                    success = false;
                }
            }
            else if (ailment === 'Sleep') {
                if (![173, 214].includes(this.id)) {
                    success = false;
                }
            }
            else if (ailment === 'Freeze') {
                if (![588, 172, 221, 293, 503, 592].includes(this.id)) {
                    success = false;
                }
            }
            else if (ailment === 'Burn') {
                if (this.damage_class_id === 2) {
                    damage /= 2;
                }
            }
        }
        const ailment = Math.random() < this.meta.ailment_chance ? this.meta.meta_ailment : null;
        let typ_mult = 1;
        for (const typ of opponent.species.types) {
            try {
                const mult = constants.TYPE_EFFICACY[this.type_id];
                if (mult) {
                    const multi = mult[constants.TYPES.indexOf(typ)];
                    typ_mult *= multi || 1;
                }
            }
            catch (error) { }
        }
        damage *= Number(typ_mult);
        const messages = [];
        if (typ_mult == 0) {
            messages.push('It\'s not effective...');
        }
        else if (typ_mult > 1) {
            messages.push('It\'s super effective!');
        }
        else if (typ_mult < 1) {
            messages.push('It\'s not very effective...');
        }
        if (hits > 1) {
            messages.push(`It hit ${hits} times!`);
        }
        const changes = [];
        for (const change of this.meta.stat_changes) {
            if (Math.random() < this.meta.stat_chance) {
                changes.push(change);
            }
        }
        if (this.type && this.type in pokemon.species.types) {
            damage *= 1.5;
        }
        return new MoveResult(success, Number(damage), Number(healing), ailment, messages, changes);
    }
}
/**
 * Represents an item.
 *
 * @class Item
 * @property {number} id - The ID of the item.
 * @property {string} name - The name of the item.
 * @property {string|null} description - The description of the item, or null if none.
 * @property {number} cost - The cost of the item in PokeDollars.
 * @property {number} page - The page number in the bag where the item is found.
 * @property {string} action - The action that is performed when the item is used.
 * @property {boolean} inline - Whether the item is used inline (without opening the bag).
 * @property {string|null} emote - The emote associated with using the item, or null if none.
 * @property {boolean} shard - Whether the item is a shard.
 * @property {DataManagerBase} instance - The DataManagerBase instance associated with this item.
 */
class Item {
    id;
    name;
    description;
    cost;
    page;
    action;
    inline;
    emote;
    shard;
    instance;
    constructor(id, name, description, cost, page, action, inline, emote = null, shard = false, instance) {
        this.id = id;
        this.name = name;
        this.description = description;
        this.cost = cost;
        this.page = page;
        this.action = action;
        this.inline = inline;
        this.emote = emote;
        this.shard = shard;
        this.instance = instance;
    }
    toString() {
        return this.name;
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
}
/**
 * Represents a method by which a Pokemon can learn a move.
 *
 * @class MoveMethod
 */
class MoveMethod {
}
/**
 * Represents the method of learning a move by leveling up.
 *
 * @class LevelMethod
 * @extends MoveMethod
 * @property {number} level - The level at which the Pokemon learns the move.
 * @property {DataManagerBase} instance - The DataManagerBase instance associated with this move method.
 */
class LevelMethod extends MoveMethod {
    level;
    instance;
    constructor(level, instance) {
        super();
        this.level = level;
        this.instance = instance;
    }
    get text() {
        return `Level ${this.level}`;
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
}
/**
 * Represents a move that a Pokemon can learn, along with the method for learning it.
 *
 * @class PokemonMove
 * @property {number} move_id - The ID of the move that the Pokemon can learn.
 * @property {MoveMethod} method - The method by which the Pokemon learns the move.
 * @property {DataManagerBase} instance - The DataManagerBase instance associated with this Pokemon move.
 */
class PokemonMove {
    constructor(move_id, method, instance) {
        this.move_id = move_id;
        this.method = method;
        this.instance = instance;
    }
    move_id;
    method;
    instance;
    get move() {
        return this.instance.moves[this.move_id];
    }
    get text() {
        return `Move ${this.method}`;
    }
    /**
    * Simulate a fight between two pokemon instances
    * @param {PokemonBase} pokemon - The pokemon performing the move
    * @param {PokemonBase} opponent - Opponent pokemon
    * @returns {MoveResult} The result of a move.
    */
    calc_turn(pokemon, opponent) {
        return this.move.calculate_turn(pokemon, opponent);
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
}
/**
 * Represents the trigger for an evolution.
 *
 * @class EvolutionTrigger
 */
class EvolutionTrigger {
}
/**
 * Represents the trigger for an evolution that occurs when a Pokemon levels up.
 *
 * @class LevelTrigger
 * @extends EvolutionTrigger
 * @property {number|null} level - The level at which the Pokemon evolves, or null if it evolves upon level up regardless of level.
 * @property {number|null} item_id - The ID of the item that the Pokemon must be holding to evolve, or null if no item is required.
 * @property {number|null} move_id - The ID of the move that the Pokemon must know to evolve, or null if no move is required.
 * @property {number|null} move_type_id - The ID of the type of move that the Pokemon must know to evolve, or null if no specific type is required.
 * @property {number|null} time - The time of day when the Pokemon evolves (e.g., "day", "night"), or null if time of day doesn't matter.
 * @property {number|null} relative_stats - The relative stat requirement for evolution (1 for Attack > Defense, -1 for Defense > Attack, 0 for Attack = Defense), or null if no stat requirement.
 * @property {DataManagerBase} instance - The DataManagerBase instance associated with this evolution trigger.
 */
class LevelTrigger extends EvolutionTrigger {
    level;
    item_id;
    move_id;
    move_type_id;
    time;
    relative_stats;
    instance;
    constructor(level, item_id, move_id, move_type_id, time, relative_stats, instance) {
        super();
        this.level = level;
        this.item_id = item_id;
        this.move_id = move_id;
        this.move_type_id = move_type_id;
        this.time = time;
        this.relative_stats = relative_stats;
        this.instance = instance;
    }
    get item() {
        if (this.item_id === null) {
            return null;
        }
        return this.instance.items[this.item_id];
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
    get move() {
        if (this.move_id === null) {
            return null;
        }
        return this.instance.moves[this.move_id];
    }
    get move_type() {
        if (this.move_type_id === null) {
            return null;
        }
        return constants.TYPES[this.move_type_id];
    }
    get text() {
        let text;
        if (this.level === null) {
            text = 'when leveled up';
        }
        else {
            text = `starting from level ${this.level}`;
        }
        if (this.item !== null) {
            text += ` while holding a ${this.item}`;
        }
        if (this.move !== null) {
            text += ` while knowing ${this.move}`;
        }
        if (this.move_type !== null) {
            text += ` while knowing a ${this.move_type}-type move`;
        }
        if (this.relative_stats === 1) {
            text += ' when its Attack is higher than its Defense';
        }
        else if (this.relative_stats === -1) {
            text += ' when its Defense is higher than its Attack';
        }
        else if (this.relative_stats === 0) {
            text += ' when its Attack is equal to its Defense';
        }
        if (this.time !== null) {
            text += ` in the ${this.time} time`;
        }
        return text;
    }
}
/**
 * Represents the trigger for an evolution that occurs when an item is used on a Pokemon.
 *
 * @class ItemTrigger
 * @extends EvolutionTrigger
 * @property {number} item_id - The ID of the item that must be used to trigger the evolution.
 * @property {DataManagerBase} instance - The DataManagerBase instance associated with this evolution trigger.
 */
class ItemTrigger extends EvolutionTrigger {
    constructor(item_id, instance) {
        super();
        this.item_id = item_id;
        this.instance = instance;
    }
    item_id;
    instance;
    get item() {
        return this.instance.items[this.item_id];
    }
    get text() {
        return `using a ${this.item}`;
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
}
/**
 * Represents the trigger for an evolution that occurs when a Pokemon is traded.
 *
 * @class TradeTrigger
 * @extends EvolutionTrigger
 * @property {number|null} item_id - The ID of the item that the Pokemon must be holding to evolve when traded, or null if no item is required.
 * @property {DataManagerBase} instance - The DataManagerBase instance associated with this evolution trigger.
 */
class TradeTrigger extends EvolutionTrigger {
    constructor(item_id = null, instance) {
        super();
        this.item_id = item_id;
        this.instance = instance;
    }
    item_id;
    instance;
    get item() {
        if (this.item_id === null) {
            return null;
        }
        return this.instance.items[this.item_id];
    }
    get text() {
        if (this.item_id === null) {
            return 'when traded';
        }
        return `when traded while holding a ${this.item}`;
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
}
/**
 * Represents the trigger for an evolution that occurs under other, less common conditions.
 *
 * @class OtherTrigger
 * @extends EvolutionTrigger
 * @property {DataManagerBase} instance - The DataManagerBase instance associated with this evolution trigger.
 */
class OtherTrigger extends EvolutionTrigger {
    instance;
    constructor(instance) {
        super();
        this.instance = instance;
    }
    get text() {
        return 'somehow';
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
}
/**
 * Represents a pokemon evolution.
 *
 * @class Evolution
 * @property {number} target_id - The ID of the Pokemon species that this evolution evolves to or from.
 * @property {EvolutionTrigger} trigger - The trigger for this evolution (e.g., level up, trade, item).
 * @property {boolean} type - The direction of the evolution (true for evolving to, false for evolving from).
 * @property {DataManagerBase} instance - The DataManagerBase instance.
 */
class Evolution {
    constructor(target_id, trigger, type, instance) {
        this.target_id = target_id;
        this.trigger = trigger;
        this.type = type;
        this.instance = instance;
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
    async evolve_from(target, trigger, instance) {
        throw new Error('Method not implemented.');
    }
    async evolve_to(target, trigger, instance) {
        throw new Error('Method not implemented.');
    }
    target_id;
    trigger;
    type;
    instance;
    static async evolve_from(target, trigger, instance) {
        return new Evolution(target, trigger, false, instance);
    }
    static async evolve_to(target, trigger, instance) {
        return new Evolution(target, trigger, true, instance);
    }
    get dir() {
        return this.type ? 'to' : !this.type ? 'from' : 'to';
    }
    get target() {
        return this.instance.pokemon[this.target_id];
    }
    get text() {
        if (this.target[`evolution_${this.dir}`] !== null) {
            const pevo = this.target[`evolution_${this.dir}`];
            return `evolves ${this.dir} ${this.target} ${this.trigger.text}, which ${pevo.text} `;
        }
        return `evolves ${this.dir} ${this.target} ${this.trigger.text}`;
    }
}
/**
 * Represents a list of evolutions.
 *
 * @class EvolutionList
 * @property {Evolution[]} items - A list of Evolution objects representing the evolutions in the list.
 */
class EvolutionList {
    constructor(evolutions) {
        if (evolutions instanceof Evolution) {
            evolutions = [evolutions];
        }
        this.items = evolutions;
    }
    items;
    get text() {
        let txt = this.items.map(e => e.text).join(' and ');
        txt = txt.replace(' and ', ', ');
        return txt;
    }
}
/**
 * Represents the base stats of a Pokemon.
 *
 * @class Stats
 * @property {number} hp - The base HP stat.
 * @property {number} atk - The base Attack stat.
 * @property {number} defn - The base Defense stat.
 * @property {number} satk - The base Special Attack stat.
 * @property {number} sdef - The base Special Defense stat.
 * @property {number} spd - The base Speed stat.
 */
class Stats {
    constructor(hp, atk, defn, satk, sdef, spd) {
        this.hp = hp;
        this.atk = atk;
        this.defn = defn;
        this.satk = satk;
        this.sdef = sdef;
        this.spd = spd;
    }
    hp;
    atk;
    defn;
    satk;
    sdef;
    spd;
}
/**
 * Represents a Pokemon species.
 *
 * @class Species
 * @property {number} id - The ID of the species.
 * @property {string[][]} names - A list of names for the species in different languages. Each sub-array contains a language code and the name in that language.
 * @property {string} slug - The slug for the species (e.g., "pikachu").
 * @property {Stats} base_stats - The base stats for the species.
 * @property {number} height - The height of the species in meters.
 * @property {number} weight - The weight of the species in kilograms.
 * @property {number} dex_number - The Pokedex number of the species.
 * @property {boolean} catchable - Whether the species can be caught in the wild.
 * @property {string[]} types - A list of types for the species (e.g., ["Electric"]).
 * @property {number} abundance - The abundance of the species in the wild (higher values mean more common).
 * @property {number} gender_rate - The gender rate of the species (-1 for genderless, 0-8 for female ratio).
 * @property {boolean} has_gender_differences - Whether the species has gender differences.
 * @property {string|null} description - The description of the species, or null if none.
 * @property {number|null} mega_id - The ID of the species' Mega Evolution, or null if none.
 * @property {number|null} mega_x_id - The ID of the species' Mega X Evolution, or null if none.
 * @property {number|null} mega_y_id - The ID of the species' Mega Y Evolution, or null if none.
 * @property {EvolutionList|null} evolution_from - The EvolutionList representing how this species evolves from others, or null if none.
 * @property {EvolutionList|null} evolution_to - The EvolutionList representing how this species evolves into others, or null if none.
 * @property {boolean} mythical - Whether the species is a Mythical Pokemon.
 * @property {boolean} legendary - Whether the species is a Legendary Pokemon.
 * @property {boolean} ultra_beast - Whether the species is an Ultra Beast.
 * @property {boolean} event - Whether the species is an event-exclusive Pokemon.
 * @property {boolean} is_form - Whether the species is a form of another Pokemon.
 * @property {number|null} form_item - The ID of the item used to change into this form, or null if none.
 * @property {string} region - The region where the species was introduced.
 * @property {string|null} art_credit - The artist who created the artwork for the species, or null if unknown.
 * @property {DataManagerBase} instance - The DataManagerBase instance associated with this species.
 * @property {PokemonMove[]} moves - A list of PokemonMove objects representing the moves that the species can learn.
 */
class Species {
    constructor(id, names, slug, base_stats, height, weight, dex_number, catchable, types, abundance, gender_rate, has_gender_differences, description = null, mega_id = null, mega_x_id = null, mega_y_id = null, evolution_from = null, evolution_to = null, mythical = false, legendary = false, ultra_beast = false, event = false, is_form = false, form_item = null, region, art_credit = null, instance, moves = []) {
        this.id = id;
        this.names = names;
        this.slug = slug;
        this.base_stats = base_stats;
        this.height = height;
        this.weight = weight;
        this.dex_number = dex_number;
        this.catchable = catchable;
        this.types = types;
        this.abundance = abundance;
        this.gender_rate = gender_rate;
        this.has_gender_differences = has_gender_differences;
        this.description = description;
        this.mega_id = mega_id;
        this.mega_x_id = mega_x_id;
        this.mega_y_id = mega_y_id;
        this.evolution_from = evolution_from;
        this.evolution_to = evolution_to;
        this.mythical = mythical;
        this.legendary = legendary;
        this.ultra_beast = ultra_beast;
        this.event = event;
        this.is_form = is_form;
        this.form_item = form_item;
        this.region = region;
        this.art_credit = art_credit;
        this.instance = instance;
        this.moves = moves;
    }
    id;
    names;
    slug;
    base_stats;
    height;
    weight;
    dex_number;
    catchable;
    types;
    abundance;
    gender_rate;
    has_gender_differences;
    description;
    mega_id;
    mega_x_id;
    mega_y_id;
    evolution_from;
    evolution_to;
    mythical;
    legendary;
    ultra_beast;
    event;
    is_form;
    form_item;
    region;
    art_credit;
    instance;
    moves;
    get name() {
        const found = this.names.find(x => (x[0] ? x[0] === '🇬🇧' : null));
        if (found == null) {
            return null;
        }
        return found[1];
    }
    toString() {
        return this.name;
    }
    toJSON() {
        const copy = { ...this };
        delete copy.instance;
        return copy;
    }
    get moveset() {
        return this.moves.map(x => this.instance.moves[x.move_id]);
    }
    get mega() {
        if (this.mega_id === null) {
            return null;
        }
        return this.instance.pokemon[this.mega_id];
    }
    get mega_x() {
        if (this.mega_x_id === null) {
            return null;
        }
        return this.instance.pokemon[this.mega_x_id];
    }
    get mega_y() {
        if (this.mega_y_id === null) {
            return null;
        }
        return this.instance.pokemon[this.mega_y_id];
    }
    get correct_guesses() {
        return [this.names.map(x => deaccent(x[1].toLowerCase())), this.slug];
    }
    get trade_evolutions() {
        if (this.evolution_to === null) {
            return [];
        }
        const evos = [];
        for (const e of this.evolution_to.items) {
            if (e.trigger instanceof TradeTrigger) {
                evos.push(e);
            }
        }
        return evos;
    }
    get evolution_text() {
        if (this.is_form && this.form_item !== null) {
            const species = this.instance.pokemon[this.dex_number];
            const item = this.instance.items[this.form_item];
            return `${this.name} transforms from ${species} when given a ${item.name}.`;
        }
        if (this.evolution_from !== null && this.evolution_to !== null) {
            return `${this.name} ${this.evolution_from.text} and ${this.evolution_to.text}.`;
        }
        if (this.evolution_from !== null) {
            return `${this.name} ${this.evolution_from.text}.`;
        }
        if (this.evolution_to !== null) {
            return `${this.name} ${this.evolution_to.text}.`;
        }
        return null;
    }
}
/**
 * Represents a Pokedex.
 *
 * @class DataManagerBase
 */
class DataManagerBase {
    /**
     * Checks if the DataManager has been initialized.
     * @throws {Error} If the DataManager is not initialized.
     */
    checkinitialized() {
        throw new Error('Method not implemented.');
    }
    /**
     * A dictionary of Pokemon species, indexed by ID.
     * @type {Object.<number, Species>}
     */
    pokemon;
    /**
     * A dictionary of items, indexed by ID.
     * @type {Object.<number, Item>}
     */
    items;
    /**
     * A dictionary of move effects, indexed by ID.
     * @type {Object.<number, MoveEffect>}
     */
    effects;
    /**
     * A dictionary of moves, indexed by ID.
     * @type {Object.<number, Move>}
     */
    moves;
    toJSON() {
        return {
            pokemonCount: Object.keys(this.pokemon).length,
            itemCount: Object.keys(this.items).length,
            effectCount: Object.keys(this.effects).length,
            moveCount: Object.keys(this.moves).length,
        };
    }
    /**
     * Gets a list of all Pokemon species.
     * @returns {Species[]} A list of all Pokemon species.
     */
    allPokemon() {
        this.checkinitialized();
        return Object.values(this.pokemon);
    }
    /**
     * Gets a list of Alolan Pokemon species IDs.
     * @type {number[]}
     */
    get list_alolan() {
        return [
            10091, 10092, 10093, 10100, 10101, 10102, 10103, 10104, 10105, 10106,
            10107, 10108, 10109, 10110, 10111, 10112, 10113, 10114, 10115, 50076,
        ];
    }
    /**
     * Gets a list of Galarian Pokemon species IDs.
     * @type {number[]}
     */
    get list_galarian() {
        return [
            10158, 10159, 10160, 10161, 10162, 10163, 10164, 10165, 10166, 10167,
            10168, 10169, 10170, 10171, 10172, 10173, 10174, 10175, 10176, 10177,
            50053,
        ];
    }
    /**
     * Gets a list of Hisuian Pokemon species IDs.
     * @type {number[]}
     */
    get list_hisuian() {
        return [
            10221, 10222, 10223, 10224, 10225, 10226, 10227, 10228, 10229, 10230,
            10231, 10232, 10233, 10234, 10235, 10236, 10237, 10238, 10239, 50145,
        ];
    }
    /**
     * Gets a list of Paradox Pokemon species IDs.
     * @type {number[]}
     */
    get list_paradox() {
        return [
            984, 985, 986, 987, 988, 989, 990, 991, 992, 993, 994, 995, 1005, 1006,
            1007, 1008, 1009, 1010,
        ];
    }
    /**
     * Gets a list of Mythical Pokemon species IDs.
     * @type {number[]}
     */
    get list_mythical() {
        this.checkinitialized();
        return Object.values(this.pokemon)
            .filter(v => v.mythical)
            .map(v => v.id);
    }
    /**
     * Gets a list of Legendary Pokemon species IDs.
     * @type {number[]}
     */
    get list_legendary() {
        this.checkinitialized();
        return Object.values(this.pokemon)
            .filter(v => v.legendary)
            .map(v => v.id);
    }
    /**
     * Gets a list of Ultra Beast Pokemon species IDs.
     * @type {number[]}
     */
    get list_ub() {
        this.checkinitialized();
        return Object.values(this.pokemon)
            .filter(v => v.ultra_beast)
            .map(v => v.id);
    }
    /**
     * Gets a list of Event Pokemon species IDs.
     * @type {number[]}
     */
    get list_event() {
        this.checkinitialized();
        return Object.values(this.pokemon)
            .filter(v => v.event)
            .map(v => v.id);
    }
    /**
     * Gets a list of Mega Evolution Pokemon species IDs.
     * @type {number[]}
     */
    get list_mega() {
        this.checkinitialized();
        return [
            ...Object.values(this.pokemon)
                .filter(v => v.mega_id !== null)
                .map(v => v.mega_id),
            ...Object.values(this.pokemon)
                .filter(v => v.mega_x_id !== null)
                .map(v => v.mega_x_id),
            ...Object.values(this.pokemon)
                .filter(v => v.mega_y_id !== null)
                .map(v => v.mega_y_id),
        ];
    }
    /**
     * Gets a dictionary of species IDs indexed by Pokemon type.
     * @type {Record<string, number[]>}
     */
    get species_id_by_type_index() {
        this.checkinitialized();
        const ret = {};
        for (const pokemon of Object.values(this.pokemon)) {
            for (const typ of pokemon.types) {
                if (!ret[typ]) {
                    ret[typ] = [];
                }
                ret[typ].push(pokemon.id);
            }
        }
        return ret;
    }
    /**
     * Gets a Map of species IDs indexed by Pokemon region.
     * @type {Map<string, number[]>}
     */
    get speciesIdByRegionIndex() {
        this.checkinitialized();
        const ret = new Map();
        for (const pokemon of Object.values(this.pokemon)) {
            const region = pokemon.region.toLowerCase();
            if (!ret.has(region)) {
                ret.set(region, []);
            }
            ret.get(region).push(pokemon.id);
        }
        return ret;
    }
    /**
     * Gets a list of Pokemon species IDs for a given region.
     * @param {string} region - The name of the region.
     * @returns {number[]} A list of Pokemon species IDs.
     */
    listRegion(region) {
        this.checkinitialized();
        return this.speciesIdByRegionIndex.get(region.toLowerCase()) || [];
    }
    /**
     * Gets a Map of species IDs indexed by move ID.
     * @type {Map<number, number[]>}
     */
    get speciesIdByMoveIndex() {
        this.checkinitialized();
        const ret = new Map();
        for (const pokemon of this.allPokemon()) {
            for (const pmove of pokemon.moves) {
                if (!ret.has(pmove.move_id)) {
                    ret.set(pmove.move_id, []);
                }
                const ls = ret.get(pmove.move_id);
                if (!ls.includes(pokemon.id)) {
                    ls.push(pokemon.id);
                }
            }
        }
        return ret;
    }
    /**
     * Gets a list of Pokemon species IDs that can learn a given move.
     * @param {string} moveName - The name of the move.
     * @returns {number[]} A list of Pokemon species IDs.
     */
    listMove(moveName) {
        this.checkinitialized();
        if (!moveName) {
            return this.allPokemon()
                .filter(s => s.moves)
                .map(s => s.id);
        }
        const move = this.moveByName(moveName);
        if (!move) {
            return [];
        }
        return this.speciesIdByMoveIndex.get(move.id) || [];
    }
    /**
     * Gets a list of all items.
     * @returns {Item[]} A list of all items.
     */
    allItems() {
        this.checkinitialized();
        return Object.values(this.items);
    }
    /**
     * Gets a Map of species indexed by their dex number.
     * @type {Map<number, Species[]>}
     */
    get speciesByDexNumberIndex() {
        this.checkinitialized();
        const ret = new Map();
        for (const pokemon of Object.values(this.pokemon)) {
            if (!ret.has(pokemon.id)) {
                ret.set(pokemon.id, []);
            }
            ret.get(pokemon.id).push(pokemon);
            if (pokemon.id !== pokemon.dex_number) {
                if (!ret.has(pokemon.dex_number)) {
                    ret.set(pokemon.dex_number, []);
                }
                ret.get(pokemon.dex_number).push(pokemon);
            }
        }
        return ret;
    }
    /**
     * Gets a list of all species with the given dex number.
     * @param {number} number - The dex number.
     * @returns {Species[]} A list of Pokemon species.
     */
    allSpeciesByNumber(number) {
        this.checkinitialized();
        return this.speciesByDexNumberIndex.get(number) || [];
    }
    /**
    * Gets a list of all species with the given name.
    * @param {string} name - The name of the Pokemon species.
    * @returns {Species[]} A list of Pokemon species.
    */
    allSpeciesByName(name) {
        this.checkinitialized();
        const st = deaccent(name.toLowerCase().replace('′', '\''));
        return this.speciesByNameIndex.get(st) || [];
    }
    /**
     * Finds a species by its dex number.
     * @param {number} number - The dex number.
     * @returns {Species|null} The Pokemon species with the given dex number, or null if not found.
     */
    findSpeciesByNumber(number) {
        this.checkinitialized();
        return this.pokemon[number] || null;
    }
    /**
     * Gets a Map of species indexed by their name.
     * @type {Map<string, Species[]>}
     */
    get speciesByNameIndex() {
        this.checkinitialized();
        const ret = new Map();
        for (const pokemon of Object.values(this.pokemon)) {
            const guess = pokemon.correct_guesses;
            for (let name of guess) {
                if (typeof name != 'string')
                    name = name[0];
                if (!ret.has(name)) {
                    ret.set(name, []);
                }
                ret.get(name).push(pokemon);
            }
        }
        return ret;
    }
    /**
     * Finds a species by its name.
     * @param {string} name - The name of the Pokemon species.
     * @returns {Species|null} The Pokemon species with the given name, or null if not found.
     */
    speciesByName(name) {
        this.checkinitialized();
        const st = deaccent(name.toLowerCase().replace('′', '\''));
        const speciesList = this.speciesByNameIndex.get(st);
        return speciesList ? speciesList[0] : null;
    }
    /**
     * Finds an item by its ID.
     * @param {number} number - The ID of the item.
     * @returns {Item|null} The item with the given ID, or null if not found.
     */
    itemByNumber(number) {
        this.checkinitialized();
        return this.items[number] || null;
    }
    /**
     * Gets a Map of items indexed by their name.
     * @type {Map<string, Item>}
     */
    get itemByNameIndex() {
        this.checkinitialized();
        const ret = new Map();
        for (const item of Object.values(this.items)) {
            ret.set(item.name.toLowerCase(), item);
        }
        return ret;
    }
    /**
     * Finds an item by its name.
     * @param {string} name - The name of the item.
     * @returns {Item|null} The item with the given name, or null if not found.
     */
    itemByName(name) {
        this.checkinitialized();
        return this.itemByNameIndex.get(deaccent(name.toLowerCase().replace('′', '\'')));
    }
    /**
     * Finds a move by its ID.
     * @param {number} number - The ID of the move.
     * @returns {Move|null} The move with the given ID, or null if not found.
     */
    moveByNumber(number) {
        this.checkinitialized();
        return this.moves[number] || null;
    }
    /**
     * Gets a Map of moves indexed by their name.
     * @type {Map<string, Move>}
     */
    get moveByNameIndex() {
        this.checkinitialized();
        const ret = new Map();
        for (const move of Object.values(this.moves)) {
            ret.set(move.name.toLowerCase(), move);
        }
        return ret;
    }
    /**
     * Finds a move by its name.
     * @param {string} name - The name of the move.
     * @returns {Move|null} The move with the given name, or null if not found.
     */
    moveByName(name) {
        this.checkinitialized();
        return this.moveByNameIndex.get(deaccent(name.toLowerCase().replace('′', '’')));
    }
    /**
 * Returns a random Pokemon species.
 *
 * @param {string} [rarity='normal'] - The rarity of the Pokemon to spawn. Can be 'normal', 'mythical', 'legendary', or 'ultra_beast'.
 * @returns {Species} A random Pokemon species.
 */
    randomSpawn(rarity = 'normal') {
        this.checkinitialized();
        let pool;
        if (rarity === 'mythical') {
            pool = this.allPokemon().filter(x => x.catchable && x.mythical);
        }
        else if (rarity === 'legendary') {
            pool = this.allPokemon().filter(x => x.catchable && x.legendary);
        }
        else if (rarity === 'ultra_beast') {
            pool = this.allPokemon().filter(x => x.catchable && x.ultra_beast);
        }
        else {
            pool = this.allPokemon().filter(x => x.catchable);
        }
        const weights = pool.map(x => x.abundance);
        const randomIndex = this.weightedRandomChoice(weights);
        return pool[randomIndex];
    }
    /**
     * Returns a random Pokemon.
     *
     * @param {string} [rarity='normal'] - The rarity of the Pokemon to spawn. Can be 'normal', 'mythical', 'legendary', or 'ultra_beast'.
     * @returns {PokemonBase} A random Pokemon instance.
     */
    randomSpawnPokemon(rarity = 'normal') {
        this.checkinitialized();
        const spawned = this.randomSpawn(rarity);
        return new PokemonBase(spawned, this);
    }
    /**
     * Chooses a random element from a list of weights using weighted random selection.
     * @param {number[]} weights - A list of weights.
     * @returns {number} The index of the chosen element.
     */
    weightedRandomChoice(weights) {
        this.checkinitialized();
        const total = weights.reduce((acc, w) => acc + w, 0);
        let threshold = Math.random() * total;
        for (let i = 0; i < weights.length; i++) {
            if (weights[i] >= threshold) {
                return i;
            }
            threshold -= weights[i];
        }
        return 0;
    }
    get spawnWeights() {
        this.checkinitialized();
        return Object.values(this.pokemon).map(p => p.abundance);
    }
}
export { DataManagerBase, MoveMeta, MoveResult, MoveMethod, Move, ItemTrigger, LevelTrigger, LevelMethod, PokemonMove, Species, StatStages, Stats, StatChange, OtherTrigger, TradeTrigger, Item, MoveEffect, Evolution, EvolutionTrigger, EvolutionList, };