const fields$d = foundry.data.fields;

function createCharacteristicField(show = true, showMax = false) {
    return new fields$d.SchemaField({
        value: new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true }),
        max: new fields$d.NumberField({ required: false, initial: 0, min: 0, integer: true }),
        dm: new fields$d.NumberField({ required: false, initial: 0, integer: true }),
        show: new fields$d.BooleanField({ required: false, initial: show }),
        showMax: new fields$d.BooleanField({ required: false, initial: showMax })
    });
}

class ItemBaseData extends foundry.abstract.TypeDataModel {
    static defineSchema() {
        return {
            description: new fields$d.StringField({ required: false, blank: true, trim: true, nullable: true }),
            subType: new fields$d.StringField({ required: false, blank: false, nullable: true })
        };
    }
}

class PhysicalItemData extends ItemBaseData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.quantity = new fields$d.NumberField({ required: true, initial: 1, min: 0, integer: true });
        schema.weight = new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: false });
        schema.weightless = new fields$d.BooleanField({ required: false, initial: false });
        schema.cost = new fields$d.NumberField({ required: true, initial: 0, min: 0, integer: true });
        schema.tl = new fields$d.StringField({ required: true, blank: false, initial: "TL12" });
        schema.container = new fields$d.SchemaField({
            id: new fields$d.StringField({ required: false, blank: true })
        });
        schema.roll = new fields$d.SchemaField({
            characteristic: new fields$d.StringField({ required: false, blank: true, trim: true }),
            skill: new fields$d.StringField({ required: false, blank: true, trim: true }),
            difficulty: new fields$d.StringField({ required: false, blank: true, trim: true })
        });
        schema.trash = new fields$d.BooleanField({ required: false, initial: false });
        return schema;
    }
}

const fields$c = foundry.data.fields;

class CharacterData extends foundry.abstract.TypeDataModel {

    static defineSchema() {
        return {
            name: new fields$c.StringField({ required: false, blank: false, trim: true }),
            life: new fields$c.SchemaField({
                value: new fields$c.NumberField({ required: false, initial: 0, integer: true }),
                max: new fields$c.NumberField({ required: true, initial: 0, integer: true })
            }),
            personal: new fields$c.SchemaField({
                title: new fields$c.StringField({ required: false, blank: true, trim: true }),
                species: new fields$c.StringField({ required: false, blank: true, trim: true }),
                speciesText: new fields$c.SchemaField({
                    description: new fields$c.StringField({ required: false, blank: true, trim: true, nullable: true }),
                    descriptionLong: new fields$c.HTMLField({ required: false, blank: true, trim: true })
                }),
                age: new fields$c.StringField({ required: false, blank: true, trim: true }),
                gender: new fields$c.StringField({ required: false, blank: true, trim: true }),
                pronouns: new fields$c.StringField({ required: false, blank: true, trim: true }),
                homeworld: new fields$c.StringField({ required: false, blank: true, trim: true }),
                ucp: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" }),
                traits: new fields$c.ArrayField(
                    new fields$c.SchemaField({
                        name: new fields$c.StringField({ required: true, blank: true, trim: true }),
                        description: new fields$c.StringField({ required: false, blank: true, trim: true })
                    })
                )
            }),
            biography: new fields$c.HTMLField({ required: false, blank: true, trim: true }),

            characteristics: new fields$c.SchemaField({
                strength: createCharacteristicField(true, true),
                dexterity: createCharacteristicField(true, true),
                endurance: createCharacteristicField(true, true),
                intellect: createCharacteristicField(true, false),
                education: createCharacteristicField(true, false),
                social: createCharacteristicField(true, false),
                morale: createCharacteristicField(true, false),
                luck: createCharacteristicField(true, false),
                sanity: createCharacteristicField(true, false),
                charm: createCharacteristicField(true, false),
                psionic: createCharacteristicField(true, false),
                other: createCharacteristicField(true, false)
            }),

            health: new fields$c.SchemaField({
                radiations: new fields$c.NumberField({ required: false, initial: 0, min: 0, integer: true })
            }),
            study: new fields$c.SchemaField({
                skill: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" }),
                total: new fields$c.NumberField({ required: false, initial: 0, min: 0, integer: true }),
                completed: new fields$c.NumberField({ required: false, initial: 0, min: 0, integer: true })
            }),
            finance: new fields$c.SchemaField({
                pension: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
                credits: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
                cashOnHand: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
                debt: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
                livingCost: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
                monthlyShipPayments: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
                notes: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" })
            }),
            containerView: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" }),
            containerDropIn: new fields$c.StringField({ required: false, blank: true, trim: true, initial: "" }),
            notes: new fields$c.HTMLField({ required: false, blank: true, trim: true }),

            inventory: new fields$c.SchemaField({
                armor: new fields$c.NumberField({ required: true, initial: 0, integer: true }),
                weight: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: false }),
                encumbrance: new fields$c.SchemaField({
                    normal: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true }),
                    heavy: new fields$c.NumberField({ required: true, initial: 0, min: 0, integer: true })
                })
            }),
            states: new fields$c.SchemaField({
                encumbrance: new fields$c.BooleanField({ required: false, initial: false }),
                fatigue: new fields$c.BooleanField({ required: false, initial: false }),
                unconscious: new fields$c.BooleanField({ required: false, initial: false }),
                surgeryRequired: new fields$c.BooleanField({ required: false, initial: false })
            }),

            config: new fields$c.SchemaField({
                psionic: new fields$c.BooleanField({ required: false, initial: true }),
                initiative: new fields$c.StringField({ required: false, blank: true, initial: "dexterity" }),
                damages: new fields$c.SchemaField({
                    rank1: new fields$c.StringField({ required: false, blank: true, initial: "strength" }),
                    rank2: new fields$c.StringField({ required: false, blank: true, initial: "dexterity" }),
                    rank3: new fields$c.StringField({ required: false, blank: true, initial: "endurance" })
                })
            })
        };
    }
}

const fields$b = foundry.data.fields;

class VehiculeData extends foundry.abstract.TypeDataModel {

    static defineSchema() {
        return {
            name: new fields$b.StringField({ required: false, blank: false, trim: true }),
            skillId: new fields$b.StringField({ required: false, initial: "", blank: true, trim: true }),
            speed: new fields$b.SchemaField({
                cruise: new fields$b.StringField({ required: false, initial: "Slow", blank: true }),
                maximum: new fields$b.StringField({ required: false, initial: "Medium", blank: true })
            }),
            agility: new fields$b.NumberField({ required: false, min: 0, integer: true }),
            crew: new fields$b.NumberField({ required: false, min: 0, integer: true }),
            passengers: new fields$b.NumberField({ required: false, min: 0, integer: true }),
            cargo: new fields$b.NumberField({ required: false, min: 0, integer: false }),
            life: new fields$b.SchemaField({
                value: new fields$b.NumberField({ required: true, initial: 0, integer: true }),
                max: new fields$b.NumberField({ required: true, initial: 0, integer: true })
            }),
            shipping: new fields$b.NumberField({ required: false, min: 0, integer: true }),
            cost: new fields$b.NumberField({ required: false, min: 0, integer: true }),
            armor: new fields$b.SchemaField({
                front: new fields$b.NumberField({ required: true, initial: 0, integer: true }),
                rear: new fields$b.NumberField({ required: true, initial: 0, integer: true }),
                sides: new fields$b.NumberField({ required: true, initial: 0, integer: true })
            }),
            skills: new fields$b.SchemaField({
                autopilot: new fields$b.NumberField({ required: true, initial: 0, integer: true })
            })
        };
    }
}

const fields$a = foundry.data.fields;

class ItemData extends PhysicalItemData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.subType.initial = "loot";
        schema.software = new fields$a.SchemaField({
            bandwidth: new fields$a.NumberField({ required: false, initial: 0, min: 0, max: 10, integer: true }),
            effect: new fields$a.StringField({ required: false, blank: true, trim: true, initial: "" }),
            computerId: new fields$a.StringField({ required: false, blank: true, initial: "" })
        });
        return schema;
    }
}

const fields$9 = foundry.data.fields;

class EquipmentData extends PhysicalItemData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.equipped = new fields$9.BooleanField({ required: false, initial: false });
        schema.augment = new fields$9.SchemaField({
            improvement: new fields$9.StringField({ required: false, blank: true, trim: true })
        });
        schema.subType.initial = "equipment"; // augment, clothing, trinket, toolkit, equipment
        return schema;
    }
}

const fields$8 = foundry.data.fields;

class DiseaseData extends ItemBaseData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.subType.initial = "disease"; // disease, poison
        schema.difficulty = new fields$8.StringField({ required: true, initial: "Average" });
        schema.damage = new fields$8.StringField({ required: false, blank: true });
        schema.interval = new fields$8.StringField({ required: false, blank: true });
        return schema;
    }
}

const fields$7 = foundry.data.fields;

class CareerData extends ItemBaseData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.difficulty = new fields$7.NumberField({ required: true, initial: 0, min: 0, integer: true });
        schema.damage = new fields$7.StringField({ required: false, blank: true });
        schema.interval = new fields$7.StringField({ required: false, blank: true });
        schema.assignment = new fields$7.StringField({ required: false, blank: true });
        schema.terms = new fields$7.NumberField({ required: false, initial: 0, min: 0, integer: true });
        schema.rank = new fields$7.NumberField({ required: false, initial: 0, min: 0, integer: true });
        schema.events = new fields$7.ArrayField(
            new fields$7.SchemaField({
                age: new fields$7.NumberField({ required: false, integer: true }),
                description: new fields$7.StringField({ required: false, blank: true, trim: true })
            })
        );
        return schema;
    }
}

const fields$6 = foundry.data.fields;

class TalentData extends ItemBaseData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.subType.initial = "skill";
        schema.cost = new fields$6.NumberField({ required: true, initial: 0, min: 0, integer: true });
        schema.level = new fields$6.NumberField({ required: true, initial: 0, min: 0, integer: true });
        schema.skill = new fields$6.SchemaField({
            speciality: new fields$6.StringField({ required: false, blank: true, trim: true }),
            reduceEncumbrance: new fields$6.BooleanField({ required: false, initial: false })
        });
        schema.psionic = new fields$6.SchemaField({
            reach: new fields$6.StringField({ required: false, blank: true, trim: true }),
            cost: new fields$6.NumberField({ required: false, initial: 1, min: 0, integer: true }),
            duration: new fields$6.StringField({ required: false, blank: true, trim: true }),
            durationUnit: new fields$6.StringField({ required: false })
        });
        schema.roll = new fields$6.SchemaField({
            characteristic: new fields$6.StringField({ required: false, blank: true, trim: true }),
            skill: new fields$6.StringField({ required: false, blank: true, trim: true }),
            difficulty: new fields$6.StringField({ required: false, blank: true, trim: true })
        });
        return schema;
    }
}

const fields$5 = foundry.data.fields;

class ContactData extends ItemBaseData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.subType.initial = "skill";
        schema.cost = new fields$5.NumberField({ required: true, initial: 1, min: 0, integer: true });
        schema.skill = new fields$5.SchemaField({
            speciality: new fields$5.StringField({ required: false, blank: true, trim: true }),
            characteristic: new fields$5.StringField({ required: false, blank: true, trim: true })
        });
        schema.status = new fields$5.StringField({ required: false, blank: true, trim: true, initial: "Alive" });
        schema.attitude = new fields$5.StringField({ required: false, blank: true, trim: true, initial: "Unknow" });
        schema.relation = new fields$5.StringField({ required: false, blank: true, trim: true, initial: "Contact" });
        schema.title = new fields$5.StringField({ required: false, blank: true, trim: true });
        schema.nickname = new fields$5.StringField({ required: false, blank: true, trim: true });
        schema.species = new fields$5.StringField({ required: false, blank: true, trim: true });
        schema.gender = new fields$5.StringField({ required: false, blank: true, trim: true });
        schema.pronouns = new fields$5.StringField({ required: false, blank: true, trim: true });
        schema.homeworld = new fields$5.StringField({ required: false, blank: true, trim: true });
        schema.location = new fields$5.StringField({ required: false, blank: true, trim: true });
        schema.occupation = new fields$5.StringField({ required: false, blank: true, trim: true });
        schema.notes = new fields$5.HTMLField({ required: false, blank: true, trim: true });
        return schema;
    }
}

const fields$4 = foundry.data.fields;

class WeaponData extends PhysicalItemData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.equipped = new fields$4.BooleanField({ required: false, initial: false });
        schema.range = new fields$4.SchemaField({
            isMelee: new fields$4.BooleanField({ required: false, initial: false }),
            value: new fields$4.NumberField({ required: false, integer: true, nullable: true }),
            unit: new fields$4.StringField({ required: false, blank: true, nullable: true })
        });
        schema.damage = new fields$4.StringField({ required: false, blank: true, trim: true });
        schema.magazine = new fields$4.NumberField({ required: false, initial: 0, min: 0, integer: true });
        schema.magazineCost = new fields$4.NumberField({ required: false, initial: 0, min: 0, integer: true });
        schema.traits = new fields$4.ArrayField(
            new fields$4.SchemaField({
                name: new fields$4.StringField({ required: true, blank: true, trim: true }),
                description: new fields$4.StringField({ required: false, blank: true, trim: true })
            })
        );
        schema.options = new fields$4.ArrayField(
            new fields$4.SchemaField({
                name: new fields$4.StringField({ required: true, blank: true, trim: true }),
                description: new fields$4.StringField({ required: false, blank: true, trim: true })
            })
        );
        return schema;
    }
}

const fields$3 = foundry.data.fields;

class ArmorData extends PhysicalItemData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.equipped = new fields$3.BooleanField({ required: false, initial: false });
        schema.radiations = new fields$3.NumberField({ required: false, initial: 0, min: 0, integer: true });
        schema.protection = new fields$3.StringField({ required: false, blank: false, trim: true });
        // A Traveller suffers DM-1 to all checks per missing skill level in the required skill.
        schema.requireSkill = new fields$3.StringField({ required: false, blank: false });
        schema.requireSkillLevel = new fields$3.NumberField({ required: false, min: 0, integer: true });
        // Powered armour supports its own weight and is effectively weightless for encumbrance.
        schema.powered = new fields$3.BooleanField({ required: false, initial: false });
        schema.options = new fields$3.ArrayField(
            new fields$3.SchemaField({
                name: new fields$3.StringField({ required: true, blank: true, trim: true }),
                description: new fields$3.StringField({ required: false, blank: true, trim: true })
            })
        );
        return schema;
    }
}

const fields$2 = foundry.data.fields;

class ComputerData extends PhysicalItemData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.processing = new fields$2.NumberField({ required: false, initial: 0, min: 0, integer: true });
        schema.processingUsed = new fields$2.NumberField({ required: false, initial: 0, min: 0, integer: true });
        schema.overload = new fields$2.BooleanField({ required: false, initial: false });
        schema.options = new fields$2.ArrayField(
            new fields$2.SchemaField({
                name: new fields$2.StringField({ required: true, blank: true, trim: true }),
                description: new fields$2.StringField({ required: false, blank: true, trim: true })
            })
        );
        return schema;
    }
}

const fields$1 = foundry.data.fields;

class ItemContainerData extends ItemBaseData {
    static defineSchema() {
        const schema = super.defineSchema();
        schema.onHand = new fields$1.BooleanField({ required: false, initial: false });
        schema.location = new fields$1.StringField({ required: false, blank: true, trim: true });
        schema.count = new fields$1.NumberField({ required: false, initial: 0, integer: true });
        schema.weight = new fields$1.NumberField({ required: false, initial: 0, integer: false });
        schema.weightless = new fields$1.BooleanField({ required: false, initial: false });
        schema.locked = new fields$1.BooleanField({ required: false, initial: false }); // GM only
        schema.lockedDescription = new fields$1.StringField({ required: false, blank: true, trim: true, nullable: true });
        return schema;
    }
}

const fields = foundry.data.fields;

class SpeciesData extends foundry.abstract.TypeDataModel {
    static defineSchema() {
        return {
            description: new fields.StringField({ required: false, blank: true, trim: true, nullable: true }),
            descriptionLong: new fields.HTMLField({ required: false, blank: true, trim: true }),
            traits: new fields.ArrayField(
                new fields.SchemaField({
                    name: new fields.StringField({ required: true, blank: true, trim: true }),
                    description: new fields.StringField({ required: false, blank: true, trim: true })
                })
            ),
            modifiers: new fields.ArrayField(
                new fields.SchemaField({
                    characteristic: new fields.StringField({ required: false, blank: true, trim: true }),
                    value: new fields.NumberField({ required: false, integer: true, nullable: true })
                })
            )
        };
    }
}

const MGT2 = {};

MGT2.MetricRange = Object.freeze({
    meter: "MGT2.MetricRange.meter",
    kilometer: "MGT2.MetricRange.kilometer"
});

MGT2.MetricWeight = Object.freeze({
    kilogram: "MGT2.MetricWeight.kilogram",
    ton: "MGT2.MetricWeight.ton"
});

MGT2.Difficulty = Object.freeze({
    NA: "MGT2.Difficulty.NA",
    Simple: "MGT2.Difficulty.Simple",
    Easy: "MGT2.Difficulty.Easy",
    Routine: "MGT2.Difficulty.Routine",
    Average: "MGT2.Difficulty.Average",
    Difficult: "MGT2.Difficulty.Difficult",
    VeryDifficult: "MGT2.Difficulty.VeryDifficult",
    Formidable: "MGT2.Difficulty.Formidable",
    Impossible: "MGT2.Difficulty.Impossible"
});

MGT2.ItemSubType = Object.freeze({
    loot: "MGT2.ItemSubType.loot",
    software: "MGT2.ItemSubType.software"
});

MGT2.EquipmentSubType = Object.freeze({
    augment: "MGT2.EquipmentSubType.augment",
    clothing: "MGT2.EquipmentSubType.clothing",
    equipment: "MGT2.EquipmentSubType.equipment",
    trinket: "MGT2.EquipmentSubType.trinket",
    toolkit: "MGT2.EquipmentSubType.toolkit"
});

MGT2.TalentSubType = Object.freeze({
    skill: "MGT2.TalentSubType.skill",
    psionic: "MGT2.TalentSubType.psionic"
});

MGT2.DiseaseSubType = Object.freeze({
    disease: "MGT2.DiseaseSubType.disease",
    poison: "MGT2.DiseaseSubType.poison",
    wound: "MGT2.DiseaseSubType.wound"
});

MGT2.PsionicReach = Object.freeze({
    NA: "MGT2.PsionicReach.NA",
    Personal: "MGT2.PsionicReach.Personal",
    Close: "MGT2.PsionicReach.Close",
    Short: "MGT2.PsionicReach.Short",
    Medium: "MGT2.PsionicReach.Medium",
    Long: "MGT2.PsionicReach.Long",
    VeryLong: "MGT2.PsionicReach.VeryLong",
    Distant: "MGT2.PsionicReach.Distant",
    VeryDistant: "MGT2.PsionicReach.VeryDistant",
    Continental: "MGT2.PsionicReach.Continental",
    Planetary: "MGT2.PsionicReach.Planetary"
});

MGT2.ContactRelations = Object.freeze({
    Allie: "MGT2.Contact.Relation.Allie",
    Contact: "MGT2.Contact.Relation.Contact",
    Rival: "MGT2.Contact.Relation.Rival",
    Enemy: "MGT2.Contact.Relation.Enemy"
});

MGT2.ContactStatus = Object.freeze({
    Alive: "MGT2.Contact.Status.Alive",
    Unknow: "MGT2.Contact.Status.Unknow",
    Dead: "MGT2.Contact.Status.Dead"
});

MGT2.Attitudes = Object.freeze({
    Unknow: "MGT2.Contact.Attitude.Unknow",
    Hostile: "MGT2.Contact.Attitude.Hostile",
    Unfriendly: "MGT2.Contact.Attitude.Unfriendly",
    Indifferent: "MGT2.Contact.Attitude.Indifferent",
    Friendly: "MGT2.Contact.Attitude.Friendly",
    Helpful: "MGT2.Contact.Attitude.Helpful",
    Complicated: "MGT2.Contact.Attitude.Complicated"
});

MGT2.Characteristics = Object.freeze({
    strength: "MGT2.Characteristics.strength.name",
    dexterity: "MGT2.Characteristics.dexterity.name",
    endurance: "MGT2.Characteristics.endurance.name",
    intellect: "MGT2.Characteristics.intellect.name",
    education: "MGT2.Characteristics.education.name",
    social: "MGT2.Characteristics.social.name",
    morale: "MGT2.Characteristics.morale.name",
    luck: "MGT2.Characteristics.luck.name",
    sanity: "MGT2.Characteristics.sanity.name",
    charm: "MGT2.Characteristics.charm.name",
    psionic: "MGT2.Characteristics.psionic.name",
    other: "MGT2.Characteristics.other.name"
});

MGT2.InitiativeCharacteristics = Object.freeze({
    dexterity: "MGT2.Characteristics.dexterity.name",
    intellect: "MGT2.Characteristics.intellect.name"
});

MGT2.DamageCharacteristics = Object.freeze({
    strength: "MGT2.Characteristics.strength.name",
    dexterity: "MGT2.Characteristics.dexterity.name",
    endurance: "MGT2.Characteristics.endurance.name"
});

MGT2.TL = Object.freeze({
    NA: "MGT2.TL.NA",
    Unknow: "MGT2.TL.Unknow",
    NotIdentified: "MGT2.TL.NotIdentified",
    TL00: "MGT2.TL.L00",
    TL01: "MGT2.TL.L01",
    TL02: "MGT2.TL.L02",
    TL03: "MGT2.TL.L03",
    TL04: "MGT2.TL.L04",
    TL05: "MGT2.TL.L05",
    TL06: "MGT2.TL.L06",
    TL07: "MGT2.TL.L07",
    TL08: "MGT2.TL.L08",
    TL09: "MGT2.TL.L09",
    TL10: "MGT2.TL.L10",
    TL11: "MGT2.TL.L11",
    TL12: "MGT2.TL.L12",
    TL13: "MGT2.TL.L13",
    TL14: "MGT2.TL.L14",
    TL15: "MGT2.TL.L15"
});

MGT2.Timeframes = Object.freeze({
    Normal: "MGT2.Timeframes.Normal",
    Slower: "MGT2.Timeframes.Slower",
    Faster: "MGT2.Timeframes.Faster"
});

MGT2.SpeedBands = Object.freeze({
    Stoppped: "MGT2.SpeedBands.Stoppped",
    Idle: "MGT2.SpeedBands.Idle",
    VerySlow: "MGT2.SpeedBands.VerySlow",
    Slow: "MGT2.SpeedBands.Slow",
    Medium: "MGT2.SpeedBands.Medium",
    High: "MGT2.SpeedBands.High.",
    Fast: "MGT2.SpeedBands.Fast",
    VeryFast: "MGT2.SpeedBands.VeryFast",
    Subsonic: "MGT2.SpeedBands.Subsonic",
    Hypersonic: "MGT2.SpeedBands.Hypersonic"
});

MGT2.Durations = Object.freeze({
    Seconds: "MGT2.Durations.Seconds",
    Minutes: "MGT2.Durations.Minutes",
    Heures: "MGT2.Durations.Heures"
});

class ActorCharacter {
    static preCreate($this, data, options, user) {
        $this.updateSource({ prototypeToken: { actorLink: true } }); // QoL
    }

    static prepareData(actorData) {
        actorData.initiative = this.getInitiative(actorData);
    }

    static getInitiative($this) {
        let c = $this.system.config.initiative;
        return $this.system.characteristics[c].dm;
    }

    static async onDeleteDescendantDocuments($this, parent, collection, documents, ids, options, userId) {
        const toDeleteIds = [];
        const itemToUpdates = [];

        for (let d of documents) {
            if (d.type === "container") {
                // Delete content
                for (let item of $this.items) {
                    if (item.system.hasOwnProperty("container") && item.system.container.id === d._id)
                        toDeleteIds.push(item._id);
                }
            } else if (d.type === "computer") {
                // Eject software
                for (let item of $this.items) {
                    if (item.system.hasOwnProperty("software") && item.system.computerId === d._id) {
                        let clone = foundry.utils.deepClone(item);
                        clone.system.software.computerId = "";
                        itemToUpdates.push(clone);
                    }
                }
            }
        }

        if (toDeleteIds.length > 0)
            await $this.deleteEmbeddedDocuments("Item", toDeleteIds);

        if (itemToUpdates.length > 0)
            await $this.updateEmbeddedDocuments('Item', itemToUpdates);

        await this.recalculateWeight($this);
    }

    static async onUpdateDescendantDocuments($this, parent, collection, documents, changes, options, userId) {
        await this.calculEncumbranceAndWeight($this, parent, collection, documents, changes, options, userId);
        await this.calculComputers($this, parent, collection, documents, changes, options, userId);
    }

    static async calculComputers($this, parent, collection, documents, changes, options, userId) {
        let change;
        let i = 0;

        let recalculProcessing = false;
        for (let d of documents) {
            if (changes[i].hasOwnProperty("system")) {
                change = changes[i];
                if (d.type === "item" && d.system.subType === "software") {
                    if (change.system.software.hasOwnProperty("bandwidth") || change.system.software.hasOwnProperty("computerId")) {
                        recalculProcessing = true;
                        break;
                    }
                }
            }
        }

        if (recalculProcessing) {
            let updatedComputers = [];
            let computerChanges = {};
            let computers = [];

            for (let item of $this.items) {
                if (item.system.trash === true) continue;
                if (item.type === "computer") {
                    computers.push(item);
                    computerChanges[item._id] = { processingUsed: 0 };
                }
            }

            for (let item of $this.items) {
                if (item.type !== "item" && item.system.subType !== "software") continue;

                if (item.system.software.hasOwnProperty("computerId") && item.system.software.computerId !== "") {
                    computerChanges[item.system.software.computerId].processingUsed += item.system.software.bandwidth;
                }
            }

            for (let computer of computers) {
                let newProcessingUsed = computerChanges[computer._id].processingUsed;
                if (computer.system.processingUsed !== newProcessingUsed) {
                    const cloneComputer = foundry.utils.deepClone($this.getEmbeddedDocument("Item", computer._id));
                    cloneComputer.system.processingUsed = newProcessingUsed;
                    cloneComputer.system.overload = cloneComputer.system.processingUsed > cloneComputer.system.processing;
                    updatedComputers.push(cloneComputer);
                }
            }

            if (updatedComputers.length > 0) {
                await $this.updateEmbeddedDocuments('Item', updatedComputers);
            }
        }
    }

    static async calculEncumbranceAndWeight($this, parent, collection, documents, changes, options, userId) {
        let recalculEncumbrance = false;
        let recalculWeight = false;

        let change;
        let i = 0;
        for (let d of documents) {
            if (changes[i].hasOwnProperty("system")) {
                change = changes[i];

                if (d.type === "armor" ||
                    d.type === "computer" ||
                    d.type === "gear" ||
                    d.type === "item" ||
                    d.type === "weapon") {
                    if (change.system.hasOwnProperty("quantity") ||
                        change.system.hasOwnProperty("weight") ||
                        change.system.hasOwnProperty("weightless") ||
                        change.system.hasOwnProperty("container") ||
                        change.system.hasOwnProperty("equipped") ||
                        d.type === "armor") {
                        recalculWeight = true;
                    }
                } else if (d.type === "talent" && d.system.subType === "skill") {
                    if (change.system.level || (change.system?.hasOwnProperty("skill") && change.system?.skill.hasOwnProperty("reduceEncumbrance"))) {
                        recalculEncumbrance = true;
                    }
                } else if (d.type === "container" && (change.system.hasOwnProperty("onHand") || change.system.hasOwnProperty("weightless"))) {
                    recalculWeight = true;
                }
            }
            i++;
        }

        if (recalculEncumbrance || recalculWeight) {
            const updateData = {};

            this.recalculateArmor($this, updateData);

            if (recalculEncumbrance) {
                const str = $this.system.characteristics.strength.value;
                const end = $this.system.characteristics.endurance.value;
                let sumSkill = 0;
                $this.items.filter(x => x.type === "talent" && x.system.subType === "skill" && x.system.skill.reduceEncumbrance === true).forEach(x => sumSkill += x.system.level);
                let normal = str + end + sumSkill;
                let heavy = normal * 2;

                updateData["system.states.encumbrance"] = $this.system.inventory.weight > normal;
                updateData["system.inventory.encumbrance.normal"] = normal;
                updateData["system.inventory.encumbrance.heavy"] = heavy;
            }

            if (recalculWeight)
                await this.recalculateWeight($this, updateData);
            else if (Object.keys(updateData).length > 0)
                await $this.update(updateData);
        }
    }

    static recalculateArmor($this, updateData) {
        if (updateData === null || updateData === undefined)
            updateData = {};

        let armor = 0;
        for (let item of $this.items) {
            if (item.type === "armor") {
                if (item.system.equipped === true && !isNaN(item.system.protection)) {
                    armor += (+item.system.protection || 0);
                }
            }
        }

        updateData["system.inventory.armor"] = armor;
        return updateData;
    }

    static async recalculateWeight($this, updateData) {

        if (updateData === null || updateData === undefined)
            updateData = {};

        let updatedContainers = [];
        let containerChanges = {};

        let containers = [];

        // List all containers
        for (let item of $this.items) {
            if (item.system.trash === true) continue;

            if (item.type === "container") {
                containers.push(item);
                containerChanges[item._id] = { count: 0, weight: 0 };
            }
        }

        let onHandWeight = 0;
        for (let item of $this.items) {
            if (item.type === "container") continue;
            if (item.system.hasOwnProperty("weightless") && item.system.weightless === true) continue;

            let itemWeight = 0;
            if (item.system.hasOwnProperty("weight")) {
                let itemQty = item.system.quantity;
                if (!isNaN(itemQty) && itemQty > 0) {
                    itemWeight = item.system.weight;
                    if (itemWeight > 0) {
                        itemWeight *= itemQty;
                    }
                }

                if (item.type === "armor") {
                    if (item.system.equipped === true) {
                        if (item.system.powered === true)
                            itemWeight = 0;
                        else
                            itemWeight *= 0.25; // mass of armor that is being worn by 75% OPTIONAL
                    }
                }

                if (item.system.container && item.system.container.id && item.system.container.id !== "") {
                    // bad deleted container id
                    if (containerChanges.hasOwnProperty(item.system.container.id)) {
                        containerChanges[item.system.container.id].weight += Math.round(itemWeight * 10) / 10;
                        containerChanges[item.system.container.id].count += item.system.quantity;
                    }
                } else {
                    onHandWeight += Math.round(itemWeight * 10) / 10;
                }
            }
        }

        // Check containers new weight
        for (let container of containers) {
            let newWeight = containerChanges[container._id].weight;
            let newCount = containerChanges[container._id].count;
            if (container.system.weight !== newWeight || container.system.count !== newCount) {
                updatedContainers.push({
                    _id: container._id,
                    "system.weight": newWeight,
                    "system.count": newCount,
                });

                if (container.system.onHand === true &&
                    (container.system.weight > 0 || container.system.weightless !== true)) {
                    onHandWeight += container.system.weight;
                }
            }
        }

        updateData["system.inventory.weight"] = onHandWeight;
        updateData["system.states.encumbrance"] = onHandWeight > $this.system.inventory.encumbrance.normal;

        await $this.update(updateData);

        if (updatedContainers.length > 0) {
            await $this.updateEmbeddedDocuments('Item', updatedContainers);
        }
    }

    static async preUpdate($this, changed, options, user) {
        // Calc encumbrance

        const newStr = foundry.utils.getProperty(changed, "system.characteristics.strength.value") ?? $this.system.characteristics.strength.value;
        const newEnd = foundry.utils.getProperty(changed, "system.characteristics.endurance.value") ?? $this.system.characteristics.endurance.value;
        if ((newStr !== $this.system.characteristics.strength.value) || (newEnd !== $this.system.characteristics.endurance.value)) {
            let sumSkill = 0;
            $this.items.filter(x => x.type === "talent" && x.system.subType === "skill" && x.system.skill.reduceEncumbrance === true).forEach(x => sumSkill += x.system.level);
            let normal = newStr + newEnd + sumSkill;
            let heavy = normal * 2;
            foundry.utils.setProperty(changed, "system.inventory.encumbrance.normal", normal);
            foundry.utils.setProperty(changed, "system.inventory.encumbrance.heavy", heavy);
        }

        //console.log(foundry.utils.getProperty(changed, "system.characteristics.strength.value"));
        const characteristicModified = this.computeCharacteristics(changed);
        const strengthValue = foundry.utils.getProperty(changed, "system.characteristics.strength.value") ?? $this.system.characteristics.strength.value;
        const strengthMax = foundry.utils.getProperty(changed, "system.characteristics.strength.max") ?? $this.system.characteristics.strength.max;
        const dexterityValue = foundry.utils.getProperty(changed, "system.characteristics.dexterity.value") ?? $this.system.characteristics.dexterity.value;
        const dexterityMax = foundry.utils.getProperty(changed, "system.characteristics.dexterity.max") ?? $this.system.characteristics.dexterity.max;
        const enduranceValue = foundry.utils.getProperty(changed, "system.characteristics.endurance.value") ?? $this.system.characteristics.endurance.value;
        const enduranceMax = foundry.utils.getProperty(changed, "system.characteristics.endurance.max") ?? $this.system.characteristics.endurance.max;
        const lifeValue = strengthValue + dexterityValue + enduranceValue;
        const lifeMax = strengthMax + dexterityMax + enduranceMax;

        if ($this.system.life.value !== lifeValue)
            foundry.utils.setProperty(changed, "system.life.value", lifeValue);
        if ($this.system.life.max !== lifeMax)
            foundry.utils.setProperty(changed, "system.life.max", lifeMax);

        if (characteristicModified && $this.system.personal.ucp === undefined || $this.system.personal.ucp === "") ;
        //}

        // Apply changes in Actor size to Token width/height
        // if ( "size" in (this.system.traits || {}) ) {
        //   const newSize = foundry.utils.getProperty(changed, "system.traits.size");
        //   if ( newSize && (newSize !== this.system.traits?.size) ) {
        //     let size = CONFIG.DND5E.tokenSizes[newSize];
        //     if ( !foundry.utils.hasProperty(changed, "prototypeToken.width") ) {
        //       changed.prototypeToken ||= {};
        //       changed.prototypeToken.height = size;
        //       changed.prototypeToken.width = size;
        //     }
        //   }
        // }
    }

    // static applyHealing($this, amount) {
    //     if (isNaN(amount) || amount === 0) return;

    //     const strength = $this.system.characteristics.strength;
    //     const dexterity = $this.system.characteristics.dexterity;
    //     const endurance = $this.system.characteristics.endurance;

    //     const data = {
    //         strength: { value: strength.value },
    //         dexterity: { value: dexterity.value },
    //         endurance: { value: endurance.value }
    //     };



    //     $this.update({ system: { characteristics: data } });
    // }

    static applyDamage($this, amount) {
        if (isNaN(amount) || amount === 0) return;
        const rank1 = $this.system.config.damages.rank1;
        const rank2 = $this.system.config.damages.rank2;
        const rank3 = $this.system.config.damages.rank3;

        const data = {};
        data[rank1] = { value: $this.system.characteristics[rank1].value };
        data[rank2] = { value: $this.system.characteristics[rank2].value };
        data[rank3] = { value: $this.system.characteristics[rank3].value };

        if (amount < 0) amount = Math.abs(amount);

        for (const [key, rank] of Object.entries(data)) {
            if (rank.value > 0) {
                if (rank.value >= amount) {
                    rank.value -= amount;
                    amount = 0;
                } else {
                    amount -= rank.value;
                    rank.value = 0;
                }
                rank.dm = this.getModifier(rank.value);
                if (amount <= 0) break;
            }
        }

        $this.update({ system: { characteristics: data } });
    }

    static getContainers($this) {
        const containers = [];
        for (let item of $this.items) {
            if (item.type == "container") {
                containers.push(item);
            }
        }

        containers.sort(this.compareByName);

        return containers;
    }

    static getComputers($this) {
        const containers = [];
        for (let item of $this.items) {
            if (item.type == "computer") {
                containers.push(item);
            }
        }

        containers.sort(this.compareByName);

        return containers;
    }

    static getSkills($this) {
        const skills = [];
        for (let item of $this.items) {
            if (item.type === "talent" && item.system.subType === "skill") {
                skills.push(item);
            }
        }

        skills.sort(this.compareByName);

        return skills;
    }

    static computeCharacteristics(changed) {
        let modified = this.computeCharacteristic(changed, "strength");

        if (this.computeCharacteristic(changed, "dexterity") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "endurance") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "intellect") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "education") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "social") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "morale") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "luck") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "sanity") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "charm") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "psionic") && !modified) modified = true;
        if (this.computeCharacteristic(changed, "other") && !modified) modified = true;

        return modified;
    }

    static computeCharacteristic(changed, name) {
        //if (isNaN(c.value) || c.value <= 0) c.value = 0;
        //c.dm = this._getModifier(c.value)
        const path = `system.characteristics.${name}`;
        const newValue = foundry.utils.getProperty(changed, path + ".value");// || this.system.characteristics[name].value;
        if (newValue) {
            const dm = this.getModifier(newValue);
            foundry.utils.setProperty(changed, path + ".dm", dm);
            return true;
        }

        return false;
    }

    static getModifier(value) {
        if (isNaN(value) || value <= 0) return -3;
        if (value >= 1 && value <= 2) return -2;
        if (value >= 3 && value <= 5) return -1;
        if (value >= 6 && value <= 8) return 0;
        if (value >= 9 && value <= 11) return 1;
        if (value >= 12 && value <= 14) return 2;

        return 3;
    }

    static compareByName(a, b) {
        if (!a.hasOwnProperty("name") || !b.hasOwnProperty("name")) {
            return 0;
        }
        return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
    }
}

class MGT2Combatant extends Combatant {
 
}

class TravellerActor extends Actor {


  prepareDerivedData() {
    if (this.type === "character") {
      this.system.initiative = ActorCharacter.getInitiative(this);
    }
  }

  async _preCreate(data, options, user) {
    if ( (await super._preCreate(data, options, user)) === false ) return false;
    
    if (this.type === "character") {
      ActorCharacter.preCreate(this, data, options, user);
    }
  }

  async _onDeleteDescendantDocuments(parent, collection, documents, ids, options, userId) {
    await super._onDeleteDescendantDocuments(parent, collection, documents, ids, options, userId);

    if (this.type === "character") {
      await ActorCharacter.onDeleteDescendantDocuments(this, parent, collection, documents, ids, options, userId);
    }
  }

  async _onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) {
    super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId);
    //console.log("_onUpdateDescendantDocuments");

    if (this.type === "character") {
      await ActorCharacter.onUpdateDescendantDocuments(this, parent, collection, documents, changes, options, userId);
    }
  }

  async _preUpdate(changed, options, user) {
    if ((await super._preUpdate(changed, options, user)) === false) return false;

    if (this.type === "character") {
      await ActorCharacter.preUpdate(this, changed, options, user);
    }
  }

  getInitiative($this) {
    if (this.type === "character") {
      return ActorCharacter.getInitiative(this);
    }
  }

  applyDamage(amount) {
    if (this.type === "character") {
      ActorCharacter.applyDamage(this, amount);
    }
  }

  getContainers() {
    if (this.type === "character") {
      return ActorCharacter.getContainers(this);
    }

    return [];
  }

  getComputers() {
    if (this.type === "character") {
      return ActorCharacter.getComputers(this);
    }

    return [];
  }

  getSkills() {
    if (this.type === "character") {
      return ActorCharacter.getSkills(this);
    }

    return [];
  }

  async recalculateWeight() {
    if (this.type === "character") {
      return ActorCharacter.recalculateWeight(this);
    }
  }

}

class TravellerItem extends Item {

  /** @inheritdoc */
  prepareDerivedData() {
    super.prepareDerivedData();

  }

  async _preUpdate(changed, options, user) {
    if ((await super._preUpdate(changed, options, user)) === false) return false;

    if (this.type === "computer") {
      // Overload
      const newProcessing = foundry.utils.getProperty(changed, "system.processing") ?? this.system.processing;
      if (newProcessing !== this.system.processing) {
        let overload = this.system.processingUsed > newProcessing;
        foundry.utils.setProperty(changed, "system.overload", overload);
      }
    }

    // Qty max 1
    if (this.type === "computer" || this.type === "container" || (this.type === "item" && this.system.subType === "software")) {
      const newQty = foundry.utils.getProperty(changed, "system.quantity") ?? this.system.quantity;
      if (newQty !== this.system.quantity && newQty > 1) {
        foundry.utils.setProperty(changed, "system.quantity", 1);
      }
    }

    // No Weight
    if (this.type === "item" && this.system.subType === "software") {
      const newWeight = foundry.utils.getProperty(changed, "system.weight") ?? this.system.weight;
      if (newWeight !== this.system.weight && newWeight > 0) {
        foundry.utils.setProperty(changed, "system.weight", 0);
      }
    }
  }

  getRollDisplay() {
    if (this.type === "talent") {
      if (this.system.subType === "skill") {
        let label;
        if (this.system.skill.speciality !== "" && this.system.skill.speciality !== undefined) {
          label = `${this.name} (${this.system.skill.speciality})`;
        } else {
          label = this.name;
        }

        if (this.system.level > 0)
          label += ` (+${this.system.level})`;
        else if (this.system.level < 0)
          label += ` (${this.system.level})`;

        return label;
      } else if (this.system.subType === "psionic") ;
    }

    return name;
  }
}

const { HandlebarsApplicationMixin: HandlebarsApplicationMixin$1 } = foundry.applications.api;

class MGT2ActorSheet extends HandlebarsApplicationMixin$1(foundry.applications.sheets.ActorSheetV2) {

    static SHEET_MODES = { EDIT: 0, PLAY: 1 }

    constructor(options = {}) {
        super(options);
        this._sheetMode = this.constructor.SHEET_MODES.PLAY;
    }

    /** @override */
    static DEFAULT_OPTIONS = {
        classes: ["mgt2", "sheet", "actor"],
        position: {
            width: 780,
        },
        form: {
            submitOnChange: true,
            closeOnSubmit: false,
        },
        window: {
            resizable: true,
        },
        dragDrop: [{ dragSelector: ".drag-item-list", dropSelector: ".drop-item-list" }],
        actions: {
            toggleSheet: MGT2ActorSheet.#onToggleSheet,
        },
    }

    get isPlayMode() {
        if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY;
        return this._sheetMode === this.constructor.SHEET_MODES.PLAY;
    }

    get isEditMode() {
        if (this._sheetMode === undefined) this._sheetMode = this.constructor.SHEET_MODES.PLAY;
        return this._sheetMode === this.constructor.SHEET_MODES.EDIT;
    }

    tabGroups = { sidebar: "health" }

    /** @override */
    async _prepareContext() {
        const base = await super._prepareContext();
        const actor = this.document;
        return {
            ...base,
            actor: actor,
            // Flat shorthands for template backward-compat (AppV1 style)
            name: actor.name,
            img: actor.img,
            cssClass: this.isEditable ? "editable" : "locked",
            system: actor.system,
            source: actor.toObject(),
            fields: actor.schema.fields,
            systemFields: actor.system.schema.fields,
            isEditable: this.isEditable,
            isEditMode: this.isEditMode,
            isPlayMode: this.isPlayMode,
            isGM: game.user.isGM,
            config: CONFIG.MGT2,
        };
    }

    /** @override */
    _onRender(context, options) {
        super._onRender(context, options);
        // Inject theme class dynamically (can't use game.settings in static DEFAULT_OPTIONS)
        const theme = game.settings.get("mgt2", "theme");
        if (theme) this.element.classList.add(theme);
        this._activateTabGroups();
    }

    _activateTabGroups() {
        for (const [group, activeTab] of Object.entries(this.tabGroups)) {
            const nav = this.element.querySelector(`nav[data-group="${group}"]`);
            if (!nav) continue;

            nav.querySelectorAll('[data-tab]').forEach(link => {
                link.classList.toggle('active', link.dataset.tab === activeTab);
                link.addEventListener('click', event => {
                    event.preventDefault();
                    this.tabGroups[group] = link.dataset.tab;
                    this.render();
                });
            });

            this.element.querySelectorAll(`[data-group="${group}"][data-tab]`).forEach(content => {
                content.classList.toggle('active', content.dataset.tab === activeTab);
            });
        }
    }

    /** @override */
    _canDragDrop(selector) {
        return this.isEditable;
    }

    static async #onToggleSheet(event) {
        event.preventDefault();
        this._sheetMode = this.isPlayMode
            ? this.constructor.SHEET_MODES.EDIT
            : this.constructor.SHEET_MODES.PLAY;
        this.render();
    }
}

class MGT2Helper {
    static POUNDS_CONVERT = 2.20462262185;

    static decimalSeparator;
    static badDecimalSeparator;

    static {
        this.decimalSeparator = Number(1.1).toLocaleString().charAt(1);
        this.badDecimalSeparator = (this.decimalSeparator === "." ? "," : ".");
    }

    static format = function() {
        var s = arguments[0];
        for (var i = 0; i < arguments.length - 1; i++) {       
            var reg = new RegExp("\\{" + i + "\\}", "gm");             
            s = s.replace(reg, arguments[i + 1]);
        }
        return s;
    }

    static hasValue(object, property) {
        return object !== undefined && object.hasOwnProperty(property) && object[property] !== null && object[property] !== undefined && object[property] !== "";
    }

    static getItemsWeight(items) {
        let weight = 0;
        for (let i of items) {
            let item = i.hasOwnProperty("system") ? i.system : i;
            if (item.hasOwnProperty("weightless") && item.weightless === true) {
                continue;
            }

            if (item.hasOwnProperty("weight")) {
                let itemQty = item.quantity;
                if (!isNaN(itemQty) && itemQty > 0) {
                    let itemWeight = item.weight;
                    if (itemWeight > 0) {
                        weight += itemWeight * itemQty;
                    }
                }
            }
        }
        return weight;
    }

    static generateUID() {
        let result = '';
        const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';

        for (let i = 0; i < 36; i++) {
            const randomIndex = Math.floor(Math.random() * characters.length);
            result += characters.charAt(randomIndex);
            if (i === 8 || i === 12 || i === 16 || i === 20)
                result += "-";
        }

        return result;
    }

    static compareByName(a, b) {
        if (!a.hasOwnProperty("name") || !b.hasOwnProperty("name")) {
            return 0;
        }
        return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
    }

    static getDisplayDM(dm) {
        if (dm === 0) return " (0)";
        if (dm > 0) return ` (+${dm})`;
        if (dm < 0) return ` (${dm})`;
        return "";
    }
    static getFormulaDM(dm) {
        if (dm === 0) return "+0";
        if (dm > 0) return `+${dm}`;
        if (dm < 0) return `${dm}`;
        return "";
    }

    static getDiceResults(roll) {
        const results = [];
        for (const die of roll.dice) {
            results.push(die.results);
        }
        return results.flat(2);
    }

    static getDiceTotal(roll) {
        let total = 0;
        for (const die of roll.dice) {
            total += die.total;
        }
        return total;
    }

    static getDifficultyValue(difficulty) {
        switch(difficulty) {
            case "Simple": return 2;
            case "Easy": return 4;
            case "Routine": return 6;
            case "Average": return 8;
            case "Difficult": return 10;
            case "VeryDifficult": return 12;
            case "Formidable": return 14;
            case "Impossible": return 16;
            default:
              return 0;
          }
    }

    static getDifficultyDisplay(difficulty) {
        switch(difficulty) {
            case "Simple": return game.i18n.localize("MGT2.Difficulty.Simple") + " (2+)";
            case "Easy": return game.i18n.localize("MGT2.Difficulty.Easy") + " (4+)";
            case "Routine": return game.i18n.localize("MGT2.Difficulty.Routine") + " (6+)";
            case "Average": return game.i18n.localize("MGT2.Difficulty.Average") + " (8+)";
            case "Difficult": return game.i18n.localize("MGT2.Difficulty.Difficult") + " (10+)";
            case "VeryDifficult": return game.i18n.localize("MGT2.Difficulty.VeryDifficult") + " (12+)";
            case "Formidable": return game.i18n.localize("MGT2.Difficulty.Formidable") + " (14+)";
            case "Impossible": return game.i18n.localize("MGT2.Difficulty.Impossible") + " (16+)";
            default:
              return null;
          }
    }

    static getRangeDisplay(range) {
        let value = Number(range.value);

        if (isNaN(value)) return null;

        let label;
        //if (game.settings.get("mgt2", "useDistanceMetric") === true) {
            if (range.unit !== null && range.unit !== undefined && range.unit !== "")
                label = game.i18n.localize(`MGT2.MetricRange.${range.unit}`).toLowerCase();
            else
                label = "";
        //} else {
                // TODO
        //}

        return `${value}${label}`;
    }

    static getWeightLabel() {
        //const label = game.settings.get("mgt2", "useWeightMetric") === true ? "MGT2.MetricSystem.Weight.kg" : "MGT2.ImperialSystem.Weight.lb";
        //return game.i18n.localize(label);
        return game.i18n.localize("MGT2.MetricSystem.Weight.kg");
    }

    static getDistanceLabel() {
        //const label = game.settings.get("mgt2", "useDistanceMetric") === true ? "MGT2.MetricSystem.Distance.km" : "MGT2.ImperialSystem.Distance.mi";
        //return game.i18n.localize(label);
        return game.i18n.localize("MGT2.MetricSystem.Distance.km");
    }

    static getIntegerFromInput(data) {
        return Math.trunc(this.getNumberFromInput(data));
    }

    static getNumberFromInput(data) {
        if (data === undefined || data === null) return 0;

        if (typeof data === "string") {
            let converted = Number(data.replace(/\s+/g, '').replace(this.badDecimalSeparator, this.decimalSeparator).trim());
            if (isNaN(converted))
                return 0;

            return converted;
        }

        let converted = Number(data);

        if (isNaN(converted))
            return 0;

        return converted;
    }

    static convertWeightForDisplay(weight) {
        //if (game.settings.get("mgt2", "useWeightMetric") === true || weight === 0)
            return weight;

        // Metric to Imperial
        //const pounds = weight * this.POUNDS_CONVERT;
        //return Math.round(pounds * 10) / 10;
    }

    static convertWeightFromInput(weight) {
        //if (game.settings.get("mgt2", "useWeightMetric") === true || weight === 0)
            return Math.round(weight * 10) / 10;

        // Imperial to Metric
        //const kg = this.POUNDS_CONVERT / weight;
        //return Math.round(kg * 10) / 10;
    }

    static getDataFromDropEvent(event) {
        try {
            return JSON.parse(event.dataTransfer?.getData("text/plain"));
        } catch (err) {
            return false;
        }

        //if ( data.type !== "Item" ) return false;
        //const item = await Item.implementation.fromDropData(data);
    }

    static async getItemDataFromDropData(dropData) {
        //console.log("getItemDataFromDropData");
        let item;
        if (game.modules.get("monks-enhanced-journal")?.active && dropData.itemId && dropData.uuid.includes("JournalEntry")) {
            await fromUuid(dropData.uuid);
        } else if (dropData.hasOwnProperty("uuid")) {
            item = await fromUuid(dropData.uuid);
        } else {
            let uuid = `${dropData.type}.${dropData.data._id}`;
            item = await fromUuid(uuid);
        }

        if (!item) {
            throw new Error(game.i18n.localize("Errors.CouldNotFindItem").replace("_ITEM_ID_", dropData.uuid));
        }
        if (item.pack) {
            const pack = game.packs.get(item.pack);
            item = await pack?.getDocument(item._id);
        }
        return deepClone(item);
    }
}

const { DialogV2: DialogV2$1 } = foundry.applications.api;
const { renderTemplate: renderTemplate$1 } = foundry.applications.handlebars;
const { FormDataExtended: FormDataExtended$1 } = foundry.applications.ux;

class RollPromptHelper {

    static async roll(options) {
        const htmlContent = await renderTemplate$1('systems/mgt2/templates/roll-prompt.html', {
            config: CONFIG.MGT2,
            characteristics: options.characteristics,
            characteristic: options.characteristic,
            skills: options.skills,
            skill: options.skill,
            fatigue: options.fatigue,
            encumbrance: options.encumbrance,
            difficulty: options.difficulty
        });

        game.settings.get("mgt2", "theme");

        return await DialogV2$1.wait({
            window: { title: options.title ?? options.rollTypeName ?? game.i18n.localize("MGT2.RollPrompt.Roll") },
            content: htmlContent,
            rejectClose: false,
            buttons: [
                {
                    action: "boon",
                    label: game.i18n.localize("MGT2.RollPrompt.Boon"),
                    callback: (event, button, dialog) => {
                        const formData = new FormDataExtended$1(dialog.element.querySelector('form')).object;
                        formData.diceModifier = "dl";
                        return formData;
                    }
                },
                {
                    action: "submit",
                    label: game.i18n.localize("MGT2.RollPrompt.Roll"),
                    icon: '<i class="fa-solid fa-dice"></i>',
                    default: true,
                    callback: (event, button, dialog) => {
                        return new FormDataExtended$1(dialog.element.querySelector('form')).object;
                    }
                },
                {
                    action: "bane",
                    label: game.i18n.localize("MGT2.RollPrompt.Bane"),
                    callback: (event, button, dialog) => {
                        const formData = new FormDataExtended$1(dialog.element.querySelector('form')).object;
                        formData.diceModifier = "dh";
                        return formData;
                    }
                }
            ]
        });
    }
}

const { DialogV2 } = foundry.applications.api;
const { renderTemplate } = foundry.applications.handlebars;
const { FormDataExtended } = foundry.applications.ux;

async function _dialogWithForm(title, templatePath, templateData) {
    const htmlContent = await renderTemplate(templatePath, templateData);
    game.settings.get("mgt2", "theme");
    return await DialogV2.wait({
        window: { title },
        content: htmlContent,
        rejectClose: false,
        buttons: [
            {
                action: "submit",
                label: game.i18n.localize("MGT2.Save"),
                icon: '<i class="fa-solid fa-floppy-disk"></i>',
                default: true,
                callback: (event, button, dialog) => {
                    return new FormDataExtended(dialog.element.querySelector('form')).object;
                }
            }
        ]
    });
}

class CharacterPrompts {

    static async openConfig(system) {
        return _dialogWithForm(
            "Configuration",
            "systems/mgt2/templates/actors/actor-config-sheet.html",
            { config: CONFIG.MGT2, system }
        );
    }

    static async openCharacteristic(name, show, showMax, showAll = false) {
        return _dialogWithForm(
            "Configuration: " + name,
            "systems/mgt2/templates/actors/actor-config-characteristic-sheet.html",
            { name, show, showMax, showAll }
        );
    }

    static async openTraitEdit(data) {
        const title = data.name ?? game.i18n.localize("MGT2.Actor.EditTrait");
        return _dialogWithForm(
            title,
            "systems/mgt2/templates/actors/trait-sheet.html",
            { config: CONFIG.MGT2, data }
        );
    }

    static async openEditorFullView(title, html) {
        const htmlContent = await renderTemplate("systems/mgt2/templates/editor-fullview.html", {
            config: CONFIG.MGT2,
            html
        });
        game.settings.get("mgt2", "theme");
        await DialogV2.wait({
            window: { title },
            content: htmlContent,
            rejectClose: false,
            buttons: [
                {
                    action: "close",
                    label: game.i18n.localize("MGT2.Close") || "Fermer",
                    default: true,
                    callback: () => null
                }
            ]
        });
    }
}

class TravellerCharacterSheet extends MGT2ActorSheet {

    /** @override */
    static DEFAULT_OPTIONS = {
        ...super.DEFAULT_OPTIONS,
        classes: [...super.DEFAULT_OPTIONS.classes, "character", "nopad"],
        window: {
            ...super.DEFAULT_OPTIONS.window,
            title: "TYPES.Actor.character",
        },
        actions: {
            ...super.DEFAULT_OPTIONS.actions,
            createItem: TravellerCharacterSheet.#onCreateItem,
            editItem: TravellerCharacterSheet.#onEditItem,
            deleteItem: TravellerCharacterSheet.#onDeleteItem,
            equipItem: TravellerCharacterSheet.#onEquipItem,
            itemStorageIn: TravellerCharacterSheet.#onItemStorageIn,
            itemStorageOut: TravellerCharacterSheet.#onItemStorageOut,
            softwareEject: TravellerCharacterSheet.#onSoftwareEject,
            createContainer: TravellerCharacterSheet.#onContainerCreate,
            editContainer: TravellerCharacterSheet.#onContainerEdit,
            deleteContainer: TravellerCharacterSheet.#onContainerDelete,
            roll: TravellerCharacterSheet.#onRoll,
            openConfig: TravellerCharacterSheet.#onOpenConfig,
            openCharacteristic: TravellerCharacterSheet.#onOpenCharacteristic,
            traitCreate: TravellerCharacterSheet.#onTraitCreate,
            traitEdit: TravellerCharacterSheet.#onTraitEdit,
            traitDelete: TravellerCharacterSheet.#onTraitDelete,
            openEditor: TravellerCharacterSheet.#onOpenEditor,
        },
    }

    /** @override */
    static PARTS = {
        sheet: {
            template: "systems/mgt2/templates/actors/actor-sheet.html",
        },
    }

    /** @override */
    tabGroups = {
        sidebar: "inventory",
        characteristics: "core",
        inventory: "onhand",
    }

    /** @override */
    async _prepareContext() {
        const context = await super._prepareContext();
        const actor = this.document;

        context.settings = {
            weightUnit: "kg",
            usePronouns: game.settings.get("mgt2", "usePronouns"),
            useGender: game.settings.get("mgt2", "useGender"),
            showLife: game.settings.get("mgt2", "showLife"),
        };
        context.isGM = game.user.isGM;
        context.showTrash = false;
        context.initiative = actor.getInitiative();

        this._prepareCharacterItems(context);

        return context;
    }

    _prepareCharacterItems(context) {
        const actor = this.document;
        const settings = context.settings;
        const items = actor.items;

        const weapons = [], armors = [], augments = [], computers = [], softwares = [];
        const miscItems = [], equipments = [], containerItems = [], careers = [];
        const skills = [], psionics = [], diseases = [], wounds = [], contacts = [];
        const actorContainers = [];

        for (let i of items) {
            if (i.type === "container") {
                actorContainers.push(i);
            } else if (i.type === "computer") {
                computers.push(i);
                i._subItems = [];
                if (i.system.overload === true)
                    i._overloadClass = "computer-overload";
            }
        }

        actorContainers.sort(MGT2Helper.compareByName);

        const containers = [{ name: "(tous)", _id: "" }].concat(actorContainers);
        const containerIndex = new Map();

        for (let c of actorContainers) {
            containerIndex.set(c._id, c);

            if (c.system.weight > 0) {
                const w = MGT2Helper.convertWeightForDisplay(c.system.weight) + " " + settings.weightUnit;
                c._display = c.name.length > 12 ? `${c.name.substring(0, 12)}... (${w})` : `${c.name} (${w})`;
            } else {
                c._display = c.name.length > 12 ? c.name.substring(0, 12) + "..." : c.name;
            }

            if (c.system.onHand === true)
                c._subItems = [];
        }

        const containerView = actor.system.containerView;
        let currentContainerView = containerView !== "" ? containerIndex.get(containerView) : null;

        context.containerView = currentContainerView || null;
        context.containerWeight = currentContainerView
            ? MGT2Helper.convertWeightForDisplay(currentContainerView.system.weight)
            : MGT2Helper.convertWeightForDisplay(0);
        context.containerShowAll = containerView === "";

        for (let i of items) {
            const item = i.system;

            if (item.hasOwnProperty("weight") && item.weight > 0) {
                i._weight = isNaN(item.quantity)
                    ? MGT2Helper.convertWeightForDisplay(item.weight) + " " + settings.weightUnit
                    : MGT2Helper.convertWeightForDisplay(item.weight * item.quantity) + " " + settings.weightUnit;
            }

            if (item.hasOwnProperty("container") && item.container.id !== "" && item.container.id !== undefined) {
                const container = containerIndex.get(item.container.id);
                if (container === undefined) {
                    if (context.containerShowAll) {
                        i._containerName = "#deleted#";
                        containerItems.push(i);
                    }
                    continue;
                }

                if (container.system.locked && !game.user.isGM) continue;

                if (container.system.onHand === true)
                    container._subItems.push(i);

                if (context.containerShowAll || actor.system.containerView === item.container.id) {
                    i._containerName = container.name;
                    containerItems.push(i);
                }
                continue;
            }

            if (item.hasOwnProperty("equipped")) {
                i._canEquip = true;
                i._toggleClass = item.equipped ? "active" : "";
            } else {
                i._canEquip = false;
            }

            switch (i.type) {
                case "equipment":
                    (i.system.subType === "augment" ? augments : equipments).push(i);
                    break;

                case "armor":
                    if (i.system.options?.length > 0)
                        i._subInfo = i.system.options.map(x => x.name).join(", ");
                    armors.push(i);
                    break;

                case "computer":
                    if (i.system.options?.length > 0)
                        i._subInfo = i.system.options.map(x => x.name).join(", ");
                    break;

                case "item":
                    if (i.system.subType === "software") {
                        if (i.system.software.computerId && i.system.software.computerId !== "") {
                            const computer = computers.find(x => x._id === i.system.software.computerId);
                            if (computer !== undefined) computer._subItems.push(i);
                            else softwares.push(i);
                        } else {
                            i._display = i.system.software.bandwidth > 0
                                ? `${i.name} (${i.system.software.bandwidth})`
                                : i.name;
                            softwares.push(i);
                        }
                    } else {
                        miscItems.push(i);
                    }
                    break;

                case "weapon":
                    i._range = i.system.range.isMelee
                        ? game.i18n.localize("MGT2.Melee")
                        : MGT2Helper.getRangeDisplay(i.system.range);
                    if (i.system.traits?.length > 0)
                        i._subInfo = i.system.traits.map(x => x.name).join(", ");
                    weapons.push(i);
                    break;

                case "career":
                    careers.push(i);
                    break;

                case "contact":
                    contacts.push(i);
                    break;

                case "disease":
                    (i.system.subType === "wound" ? wounds : diseases).push(i);
                    break;

                case "talent":
                    if (i.system.subType === "skill") {
                        skills.push(i);
                    } else {
                        if (MGT2Helper.hasValue(i.system.psionic, "reach"))
                            i._reach = game.i18n.localize(`MGT2.PsionicReach.${i.system.psionic.reach}`);
                        if (MGT2Helper.hasValue(i.system.roll, "difficulty"))
                            i._difficulty = game.i18n.localize(`MGT2.Difficulty.${i.system.roll.difficulty}`);
                        psionics.push(i);
                    }
                    break;

                case "container":
                    if (i.system.onHand === true)
                        miscItems.push(i);
                    break;
            }
        }

        const byName = MGT2Helper.compareByName;
        const byEquipName = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase());

        context.encumbranceNormal = MGT2Helper.convertWeightForDisplay(actor.system.inventory.encumbrance.normal);
        context.encumbranceHeavy = MGT2Helper.convertWeightForDisplay(actor.system.inventory.encumbrance.heavy);

        const totalWeight = actor.system.inventory.weight;
        if (totalWeight > actor.system.inventory.encumbrance.heavy) {
            context.encumbranceClasses = "encumbrance-heavy";
            context.encumbrance = 2;
        } else if (totalWeight > actor.system.inventory.encumbrance.normal) {
            context.encumbranceClasses = "encumbrance-normal";
            context.encumbrance = 1;
        } else {
            context.encumbrance = 0;
        }

        if (softwares.length > 0) { softwares.sort(byName); context.softwares = softwares; }
        augments.sort(byEquipName);         context.augments = augments;
        armors.sort(byEquipName);           context.armors = armors;
        computers.sort(byEquipName);        context.computers = computers;
        context.careers = careers;
        contacts.sort(byName);              context.contacts = contacts;
        containers.sort(byName);            context.containers = containers;
        diseases.sort(byName);              context.diseases = diseases;
        context.wounds = wounds;
        equipments.sort(byEquipName);       context.equipments = equipments;
        miscItems.sort(byEquipName);        context.items = miscItems;
        actorContainers.sort(byName);       context.actorContainers = actorContainers;
        skills.sort(byName);                context.skills = skills;
        psionics.sort(byName);              context.psionics = psionics;
        weapons.sort(byEquipName);          context.weapons = weapons;

        if (containerItems.length > 0) {
            containerItems.sort((a, b) => {
                const r = a._containerName.localeCompare(b._containerName);
                return r !== 0 ? r : a.name.toLowerCase().localeCompare(b.name.toLowerCase());
            });
        }
        context.containerItems = containerItems;
    }

    // =========================================================
    // Event Binding (AppV2 _onRender — replaces jQuery activateListeners)
    // Templates still use CSS class selectors, so we bind manually here.
    // =========================================================

    /** @override */
    _onRender(context, options) {
        super._onRender(context, options);
        const html = this.element;
        if (!this.isEditable) return;

        this._bindClassEvent(html, ".roll", "click", TravellerCharacterSheet.#onRoll);
        this._bindClassEvent(html, ".cfg-characteristic", "click", TravellerCharacterSheet.#onOpenCharacteristic);
        this._bindClassEvent(html, ".item-create", "click", TravellerCharacterSheet.#onCreateItem);
        this._bindClassEvent(html, ".item-edit", "click", TravellerCharacterSheet.#onEditItem);
        this._bindClassEvent(html, ".item-delete", "click", TravellerCharacterSheet.#onDeleteItem);
        this._bindClassEvent(html, ".item-equip", "click", TravellerCharacterSheet.#onEquipItem);
        this._bindClassEvent(html, ".item-storage-in", "click", TravellerCharacterSheet.#onItemStorageIn);
        this._bindClassEvent(html, ".item-storage-out", "click", TravellerCharacterSheet.#onItemStorageOut);
        this._bindClassEvent(html, ".software-eject", "click", TravellerCharacterSheet.#onSoftwareEject);
        this._bindClassEvent(html, ".container-create", "click", TravellerCharacterSheet.#onContainerCreate);
        this._bindClassEvent(html, ".container-edit", "click", TravellerCharacterSheet.#onContainerEdit);
        this._bindClassEvent(html, ".container-delete", "click", TravellerCharacterSheet.#onContainerDelete);
        this._bindClassEvent(html, ".traits-create", "click", TravellerCharacterSheet.#onTraitCreate);
        this._bindClassEvent(html, ".traits-edit", "click", TravellerCharacterSheet.#onTraitEdit);
        this._bindClassEvent(html, ".traits-delete", "click", TravellerCharacterSheet.#onTraitDelete);
        this._bindClassEvent(html, "[data-editor='open']", "click", TravellerCharacterSheet.#onOpenEditor);
        html.querySelector("[name='config']")?.addEventListener("click", (ev) => TravellerCharacterSheet.#onOpenConfig.call(this, ev, ev.currentTarget));
    }

    /** Helper: bind a handler to all matching elements, with `this` set to the sheet instance */
    _bindClassEvent(html, selector, event, handler) {
        for (const el of html.querySelectorAll(selector)) {
            el.addEventListener(event, (ev) => handler.call(this, ev, ev.currentTarget));
        }
    }

    // =========================================================
    // Drag & Drop
    // =========================================================

    /** @override */
    async _onDrop(event) {
        event.preventDefault();
        event.stopImmediatePropagation();
        const dropData = MGT2Helper.getDataFromDropEvent(event);
        if (!dropData) return false;

        const sourceItemData = await MGT2Helper.getItemDataFromDropData(dropData);

        if (sourceItemData.type === "species") {
            const update = {
                system: {
                    personal: {
                        species: sourceItemData.name,
                        speciesText: {
                            description: sourceItemData.system.description,
                            descriptionLong: sourceItemData.system.descriptionLong,
                        },
                    },
                },
            };
            update.system.personal.traits = this.actor.system.personal.traits.concat(sourceItemData.system.traits);

            if (sourceItemData.system.modifiers?.length > 0) {
                update.system.characteristics = {};
                for (let modifier of sourceItemData.system.modifiers) {
                    if (MGT2Helper.hasValue(modifier, "characteristic") && MGT2Helper.hasValue(modifier, "value")) {
                        const c = this.actor.system.characteristics[modifier.characteristic];
                        const updateValue = { value: c.value + modifier.value };
                        if (c.showMax) updateValue.max = c.max + modifier.value;
                        update.system.characteristics[modifier.characteristic] = updateValue;
                    }
                }
            }

            this.actor.update(update);
            return true;
        }

        if (["contact", "disease", "career", "talent"].includes(sourceItemData.type)) {
            let transferData = {};
            try { transferData = sourceItemData.toJSON(); } catch (e) { transferData = sourceItemData; }
            delete transferData._id;
            delete transferData.id;
            await this.actor.createEmbeddedDocuments("Item", [transferData]);
            return true;
        }

        if (!["armor", "weapon", "computer", "container", "item", "equipment"].includes(sourceItemData.type)) return false;

        const target = event.target.closest(".table-row");
        let targetId = null;
        let targetItem = null;
        if (target !== null) {
            targetId = target.dataset.itemId;
            targetItem = this.actor.getEmbeddedDocument("Item", targetId);
        }

        let sourceItem = this.actor.getEmbeddedDocument("Item", sourceItemData.id);
        if (sourceItem) {
            if (!targetItem) return false;
            sourceItem = foundry.utils.deepClone(sourceItem);
            if (sourceItem._id === targetId) return false;

            if (targetItem.type === "item" || targetItem.type === "equipment") {
                if (targetItem.system.subType === "software")
                    sourceItem.system.software.computerId = targetItem.system.software.computerId;
                else
                    sourceItem.system.container.id = targetItem.system.container.id;
                this.actor.updateEmbeddedDocuments("Item", [sourceItem]);
                return true;
            } else if (targetItem.type === "computer") {
                sourceItem.system.software.computerId = targetId;
                this.actor.updateEmbeddedDocuments("Item", [sourceItem]);
                return true;
            } else if (targetItem.type === "container") {
                if (targetItem.system.locked && !game.user.isGM) {
                    ui.notifications.error("Verrouillé");
                } else {
                    sourceItem.system.container.id = targetId;
                    this.actor.updateEmbeddedDocuments("Item", [sourceItem]);
                    return true;
                }
            }
        } else {
            let transferData = {};
            try { transferData = sourceItemData.toJSON(); } catch (e) { transferData = sourceItemData; }
            delete transferData._id;
            delete transferData.id;

            const recalcWeight = transferData.system.hasOwnProperty("weight");

            if (transferData.system.hasOwnProperty("container")) transferData.system.container.id = "";
            if (transferData.type === "item" && transferData.system.subType === "software") transferData.system.software.computerId = "";
            if (transferData.type === "container") transferData.system.onHand = true;
            if (transferData.system.hasOwnProperty("equipment")) transferData.system.equipped = false;

            if (targetItem !== null) {
                if (transferData.type === "item" && transferData.system.subType === "software") {
                    if (targetItem.type === "item" && targetItem.system.subType === "software")
                        transferData.system.software.computerId = targetItem.system.software.computerId;
                    else if (targetItem.type === "computer")
                        transferData.system.software.computerId = targetItem._id;
                } else if (["armor", "computer", "equipment", "item", "weapon"].includes(transferData.type)) {
                    if (targetItem.type === "container") {
                        if (!targetItem.system.locked || game.user.isGM)
                            transferData.system.container.id = targetId;
                    } else {
                        transferData.system.container.id = targetItem.system.container.id;
                    }
                }
            }

            await this.actor.createEmbeddedDocuments("Item", [transferData]);
            if (recalcWeight) await this.actor.recalculateWeight();
        }
        return true;
    }

    // =========================================================
    // Actions (static private methods)
    // =========================================================

    static async #onCreateItem(event, target) {
        event.preventDefault();
        const data = {
            name: target.dataset.createName,
            type: target.dataset.typeItem,
        };
        if (target.dataset.subtype) {
            data.system = { subType: target.dataset.subtype };
        }
        const cls = getDocumentClass("Item");
        return cls.create(data, { parent: this.actor });
    }

    static async #onEditItem(event, target) {
        event.preventDefault();
        const li = target.closest("[data-item-id]");
        const item = this.actor.getEmbeddedDocument("Item", li?.dataset.itemId);
        if (item) item.sheet.render(true);
    }

    static async #onDeleteItem(event, target) {
        event.preventDefault();
        const li = target.closest("[data-item-id]");
        if (!li?.dataset.itemId) return;
        this.actor.deleteEmbeddedDocuments("Item", [li.dataset.itemId]);
    }

    static async #onEquipItem(event, target) {
        event.preventDefault();
        const li = target.closest("[data-item-id]");
        const item = foundry.utils.deepClone(this.actor.getEmbeddedDocument("Item", li?.dataset.itemId));
        if (!item) return;
        item.system.equipped = !item.system.equipped;
        this.actor.updateEmbeddedDocuments("Item", [item]);
    }

    static async #onItemStorageIn(event, target) {
        event.preventDefault();
        const li = target.closest("[data-item-id]");
        const item = foundry.utils.deepClone(this.actor.getEmbeddedDocument("Item", li?.dataset.itemId));
        if (!item) return;

        if (item.type === "container") {
            item.system.onHand = false;
        } else {
            const containers = this.actor.getContainers();
            let container;
            const dropInId = this.actor.system.containerDropIn;

            if (!dropInId) {
                container = containers.length === 0
                    ? await getDocumentClass("Item").create({ name: "New container", type: "container" }, { parent: this.actor })
                    : containers[0];
            } else {
                container = containers.find(x => x._id === dropInId);
            }

            if (container?.system.locked && !game.user.isGM) {
                ui.notifications.error("Objet verrouillé");
                return;
            }
            item.system.container.id = container._id;
        }
        this.actor.updateEmbeddedDocuments("Item", [item]);
    }

    static async #onItemStorageOut(event, target) {
        event.preventDefault();
        const li = target.closest("[data-item-id]");
        const item = foundry.utils.deepClone(this.actor.getEmbeddedDocument("Item", li?.dataset.itemId));
        if (!item) return;
        item.system.container.id = "";
        this.actor.updateEmbeddedDocuments("Item", [item]);
    }

    static async #onSoftwareEject(event, target) {
        event.preventDefault();
        const li = target.closest("[data-item-id]");
        const item = foundry.utils.deepClone(this.actor.getEmbeddedDocument("Item", li?.dataset.itemId));
        if (!item) return;
        item.system.software.computerId = "";
        this.actor.updateEmbeddedDocuments("Item", [item]);
    }

    static async #onContainerCreate(event) {
        event.preventDefault();
        const cls = getDocumentClass("Item");
        return cls.create({ name: "New container", type: "container" }, { parent: this.actor });
    }

    static async #onContainerEdit(event) {
        event.preventDefault();
        const container = this.actor.getEmbeddedDocument("Item", this.actor.system.containerView);
        if (container) container.sheet.render(true);
    }

    static async #onContainerDelete(event) {
        event.preventDefault();
        const containers = this.actor.getContainers();
        const container = containers.find(x => x._id === this.actor.system.containerView);
        if (!container) return;

        const containerItems = this.actor.items.filter(
            x => x.system.hasOwnProperty("container") && x.system.container.id === container._id
        );

        if (containerItems.length > 0) {
            for (let item of containerItems) {
                let clone = foundry.utils.deepClone(item);
                clone.system.container.id = "";
                this.actor.updateEmbeddedDocuments("Item", [clone]);
            }
        }

        const cloneActor = foundry.utils.deepClone(this.actor);
        cloneActor.system.containerView = "";
        if (cloneActor.system.containerDropIn === container._id) {
            cloneActor.system.containerDropIn = "";
            const remaining = containers.filter(x => x._id !== container._id);
            if (remaining.length > 0) cloneActor.system.containerDropIn = remaining[0]._id;
        }

        this.actor.deleteEmbeddedDocuments("Item", [container._id]);
        this.actor.update(cloneActor);
    }

    static async #onRoll(event, target) {
        event.preventDefault();

        const rollOptions = {
            rollTypeName: game.i18n.localize("MGT2.RollPrompt.Roll"),
            rollObjectName: "",
            characteristics: [{ _id: "", name: "" }],
            characteristic: "",
            skills: [],
            skill: "",
            fatigue: this.actor.system.states.fatigue,
            encumbrance: this.actor.system.states.encumbrance,
            difficulty: null,
            damageFormula: null,
        };

        const cardButtons = [];
        for (const [key, label] of Object.entries(MGT2.Characteristics)) {
            const c = this.actor.system.characteristics[key];
            if (c.show) {
                rollOptions.characteristics.push({
                    _id: key,
                    name: game.i18n.localize(label) + MGT2Helper.getDisplayDM(c.dm),
                });
            }
        }

        for (let item of this.actor.items) {
            if (item.type === "talent" && item.system.subType === "skill")
                rollOptions.skills.push({ _id: item._id, name: item.getRollDisplay() });
        }

        rollOptions.skills.sort(MGT2Helper.compareByName);
        rollOptions.skills = [{ _id: "NP", name: game.i18n.localize("MGT2.Items.NotProficient") }].concat(rollOptions.skills);

        let itemObj = null;
        let isInitiative = false;
        const rollType = target.dataset.roll;

        if (rollType === "initiative") {
            rollOptions.rollTypeName = game.i18n.localize("MGT2.RollPrompt.InitiativeRoll");
            rollOptions.characteristic = this.actor.system.config.initiative;
            isInitiative = true;
        } else if (rollType === "characteristic") {
            rollOptions.characteristic = target.dataset.rollCharacteristic;
            rollOptions.rollTypeName = game.i18n.localize("MGT2.RollPrompt.CharacteristicRoll");
            rollOptions.rollObjectName = game.i18n.localize(`MGT2.Characteristics.${rollOptions.characteristic}.name`);
        } else {
            if (rollType === "skill") {
                rollOptions.skill = target.dataset.rollSkill;
                itemObj = this.actor.getEmbeddedDocument("Item", rollOptions.skill);
                rollOptions.rollTypeName = game.i18n.localize("MGT2.RollPrompt.SkillRoll");
                rollOptions.rollObjectName = itemObj.name;
            } else if (rollType === "psionic") {
                rollOptions.rollTypeName = game.i18n.localize("MGT2.RollPrompt.PsionicRoll");
            }

            if (itemObj === null && target.dataset.itemId) {
                itemObj = this.actor.getEmbeddedDocument("Item", target.dataset.itemId);
                rollOptions.rollObjectName = itemObj.name;
                if (itemObj.type === "weapon") rollOptions.rollTypeName = game.i18n.localize("TYPES.Item.weapon");
                else if (itemObj.type === "armor") rollOptions.rollTypeName = game.i18n.localize("TYPES.Item.armor");
                else if (itemObj.type === "computer") rollOptions.rollTypeName = game.i18n.localize("TYPES.Item.computer");
            }

            if (rollType === "psionic" && itemObj) {
                rollOptions.rollObjectName = itemObj.name;
                if (MGT2Helper.hasValue(itemObj.system.psionic, "duration")) {
                    cardButtons.push({
                        label: game.i18n.localize("MGT2.Items.Duration"),
                        formula: itemObj.system.psionic.duration,
                        message: {
                            objectName: itemObj.name,
                            flavor: "{0} ".concat(game.i18n.localize(`MGT2.Durations.${itemObj.system.psionic.durationUnit}`)),
                        },
                    });
                }
            }

            if (itemObj?.system.hasOwnProperty("damage")) {
                rollOptions.damageFormula = itemObj.system.damage;
                if (itemObj.type === "disease") {
                    if (itemObj.system.subType === "disease")
                        rollOptions.rollTypeName = game.i18n.localize("MGT2.DiseaseSubType.disease");
                    else if (itemObj.system.subType === "poison")
                        rollOptions.rollTypeName = game.i18n.localize("MGT2.DiseaseSubType.poison");
                }
            }

            if (itemObj?.system.hasOwnProperty("roll")) {
                if (MGT2Helper.hasValue(itemObj.system.roll, "characteristic")) rollOptions.characteristic = itemObj.system.roll.characteristic;
                if (MGT2Helper.hasValue(itemObj.system.roll, "skill")) rollOptions.skill = itemObj.system.roll.skill;
                if (MGT2Helper.hasValue(itemObj.system.roll, "difficulty")) rollOptions.difficulty = itemObj.system.roll.difficulty;
            }
        }

        const userRollData = await RollPromptHelper.roll(rollOptions);

        const rollModifiers = [];
        const rollFormulaParts = [];

        if (userRollData.diceModifier) {
            rollFormulaParts.push("3d6", userRollData.diceModifier);
        } else {
            rollFormulaParts.push("2d6");
        }

        if (userRollData.characteristic) {
            const c = this.actor.system.characteristics[userRollData.characteristic];
            rollFormulaParts.push(MGT2Helper.getFormulaDM(c.dm));
            rollModifiers.push(game.i18n.localize(`MGT2.Characteristics.${userRollData.characteristic}.name`) + MGT2Helper.getDisplayDM(c.dm));
        }

        if (userRollData.skill) {
            if (userRollData.skill === "NP") {
                rollFormulaParts.push("-3");
                rollModifiers.push(game.i18n.localize("MGT2.Items.NotProficient"));
            } else {
                const skillObj = this.actor.getEmbeddedDocument("Item", userRollData.skill);
                rollFormulaParts.push(MGT2Helper.getFormulaDM(skillObj.system.level));
                rollModifiers.push(skillObj.getRollDisplay());
            }
        }

        if (userRollData.psionic) {
            const psionicObj = this.actor.getEmbeddedDocument("Item", userRollData.psionic);
            rollFormulaParts.push(MGT2Helper.getFormulaDM(psionicObj.system.level));
            rollModifiers.push(psionicObj.getRollDisplay());
        }

        if (userRollData.timeframes && userRollData.timeframes !== "" && userRollData.timeframes !== "Normal") {
            rollModifiers.push(game.i18n.localize(`MGT2.Timeframes.${userRollData.timeframes}`));
            rollFormulaParts.push(userRollData.timeframes === "Slower" ? "+2" : "-2");
        }

        if (userRollData.encumbrance === true) {
            rollFormulaParts.push("-2");
            rollModifiers.push(game.i18n.localize("MGT2.Actor.Encumbrance") + " -2");
        }

        if (userRollData.fatigue === true) {
            rollFormulaParts.push("-2");
            rollModifiers.push(game.i18n.localize("MGT2.Actor.Fatigue") + " -2");
        }

        if (userRollData.customDM) {
            const s = userRollData.customDM.trim();
            if (/^[0-9]/.test(s)) rollFormulaParts.push("+");
            rollFormulaParts.push(s);
        }

        if (MGT2Helper.hasValue(userRollData, "difficulty")) rollOptions.difficulty = userRollData.difficulty;

        const rollFormula = rollFormulaParts.join("");
        if (!Roll.validate(rollFormula)) {
            ui.notifications.error(game.i18n.localize("MGT2.Errors.InvalidRollFormula"));
            return;
        }

        let roll = await new Roll(rollFormula, this.actor.getRollData()).roll({ rollMode: userRollData.rollMode });

        if (isInitiative && this.token?.combatant) {
            await this.token.combatant.update({ initiative: roll.total });
        }

        const chatData = {
            user: game.user.id,
            speaker: this.actor ? ChatMessage.getSpeaker({ actor: this.actor }) : null,
            formula: roll._formula,
            tooltip: await roll.getTooltip(),
            total: Math.round(roll.total * 100) / 100,
            showButtons: true,
            showLifeButtons: false,
            showRollRequest: false,
            rollTypeName: rollOptions.rollTypeName,
            rollObjectName: rollOptions.rollObjectName,
            rollModifiers: rollModifiers,
            showRollDamage: rollOptions.damageFormula !== null && rollOptions.damageFormula !== "",
            cardButtons: cardButtons,
        };

        if (MGT2Helper.hasValue(rollOptions, "difficulty")) {
            chatData.rollDifficulty = rollOptions.difficulty;
            chatData.rollDifficultyLabel = MGT2Helper.getDifficultyDisplay(rollOptions.difficulty);
            if (roll.total >= MGT2Helper.getDifficultyValue(rollOptions.difficulty))
                chatData.rollSuccess = true;
            else
                chatData.rollFailure = true;
        }

        const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
        chatData.content = html;

        let flags = null;
        if (rollOptions.damageFormula) {
            flags = { mgt2: { damage: { formula: rollOptions.damageFormula, rollObjectName: rollOptions.rollObjectName, rollTypeName: rollOptions.rollTypeName } } };
        }
        if (cardButtons.length > 0) {
            if (!flags) flags = { mgt2: {} };
            flags.mgt2.buttons = cardButtons;
        }
        if (flags) chatData.flags = flags;

        return roll.toMessage(chatData);
    }

    static async #onOpenConfig(event) {
        event.preventDefault();
        const userConfig = await CharacterPrompts.openConfig(this.actor.system);
        if (userConfig) this.actor.update({ "system.config": userConfig });
    }

    static async #onOpenCharacteristic(event, target) {
        event.preventDefault();
        const name = target.dataset.cfgCharacteristic;
        const c = this.actor.system.characteristics[name];

        let showAll = false;
        for (const value of Object.values(this.actor.system.characteristics)) {
            if (!value.show) { showAll = true; break; }
        }

        const userConfig = await CharacterPrompts.openCharacteristic(
            game.i18n.localize(`MGT2.Characteristics.${name}.name`),
            c.show, c.showMax, showAll
        );

        if (userConfig) {
            const data = { system: { characteristics: {} } };
            data.system.characteristics[name] = { show: userConfig.show, showMax: userConfig.showMax };

            if (userConfig.showAll === true) {
                for (const [key, value] of Object.entries(this.actor.system.characteristics)) {
                    if (key !== name && !value.show)
                        data.system.characteristics[key] = { show: true };
                }
            }
            this.actor.update(data);
        }
    }

    static async #onTraitCreate(event) {
        event.preventDefault();
        let traits = this.actor.system.personal.traits;
        let newTraits;
        if (traits.length === 0) {
            newTraits = [{ name: "", description: "" }];
        } else {
            newTraits = [...traits, { name: "", description: "" }];
        }
        return this.actor.update({ system: { personal: { traits: newTraits } } });
    }

    static async #onTraitEdit(event, target) {
        event.preventDefault();
        const element = target.closest("[data-traits-part]");
        const index = Number(element.dataset.traitsPart);
        const trait = this.actor.system.personal.traits[index];
        const result = await CharacterPrompts.openTraitEdit(trait);
        const traits = [...this.actor.system.personal.traits];
        traits[index] = { ...traits[index], name: result.name, description: result.description };
        return this.actor.update({ system: { personal: { traits: traits } } });
    }

    static async #onTraitDelete(event, target) {
        event.preventDefault();
        const element = target.closest("[data-traits-part]");
        const index = Number(element.dataset.traitsPart);
        const traits = foundry.utils.deepClone(this.actor.system.personal.traits);
        const newTraits = Object.entries(traits)
            .filter(([key]) => Number(key) !== index)
            .map(([, value]) => value);
        return this.actor.update({ system: { personal: { traits: newTraits } } });
    }

    static async #onOpenEditor(event) {
        event.preventDefault();
        await CharacterPrompts.openEditorFullView(
            this.actor.system.personal.species,
            this.actor.system.personal.speciesText.descriptionLong
        );
    }
}

class TravellerVehiculeSheet extends MGT2ActorSheet {

    /** @override */
    static DEFAULT_OPTIONS = {
        ...super.DEFAULT_OPTIONS,
        classes: [...super.DEFAULT_OPTIONS.classes, "vehicule", "nopad"],
        window: {
            ...super.DEFAULT_OPTIONS.window,
            title: "TYPES.Actor.vehicule",
        },
    }

    /** @override */
    static PARTS = {
        sheet: {
            template: "systems/mgt2/templates/actors/vehicule-sheet.html",
        },
    }

    /** @override */
    tabGroups = { primary: "stats" }
}

const { HandlebarsApplicationMixin } = foundry.applications.api;

class TravellerItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {

    /** @override */
    static DEFAULT_OPTIONS = {
        classes: ["mgt2", "sheet", "item"],
        position: { width: 630 },
        form: {
            submitOnChange: true,
            closeOnSubmit: false,
        },
        window: { resizable: true },
        actions: {
            careerEventCreate: TravellerItemSheet.#onCareerEventCreate,
            careerEventDelete: TravellerItemSheet.#onCareerEventDelete,
            optionCreate: TravellerItemSheet.#onOptionCreate,
            optionDelete: TravellerItemSheet.#onOptionDelete,
            modifierCreate: TravellerItemSheet.#onModifierCreate,
            modifierDelete: TravellerItemSheet.#onModifierDelete,
        },
    }

    /** Dynamic PARTS: template resolved per item type */
    get PARTS() {
        const type = this.document?.type ?? "item";
        return {
            sheet: {
                template: `systems/mgt2/templates/items/${type}-sheet.html`,
            },
        };
    }

    /** Resolve template dynamically based on item type */
    get template() {
        return `systems/mgt2/templates/items/${this.document.type}-sheet.html`;
    }

    tabGroups = { primary: "tab1" }

    /** @override */
    async _prepareContext() {
        const item = this.document;
        const source = item.toObject();

        const settings = {
            usePronouns: game.settings.get("mgt2", "usePronouns"),
        };

        let containers = null;
        let computers = null;
        let hadContainer = false;

        if (item.actor !== null) {
            hadContainer = true;
            containers = [{ name: "", _id: "" }].concat(item.actor.getContainers());
            computers = [{ name: "", _id: "" }].concat(item.actor.getComputers());
        }

        let weight = null;
        if (item.system.hasOwnProperty("weight")) {
            weight = MGT2Helper.convertWeightForDisplay(item.system.weight);
        }

        let skills = [];
        if (this.actor !== null) {
            for (let actorItem of this.actor.items) {
                if (actorItem.type === "talent" && actorItem.system.subType === "skill")
                    skills.push({ _id: actorItem._id, name: actorItem.getRollDisplay() });
            }
        }
        skills.sort(MGT2Helper.compareByName);
        skills = [{ _id: "NP", name: game.i18n.localize("MGT2.Items.NotProficient") }].concat(skills);

        return {
            item: item,
            document: item,
            cssClass: this.isEditable ? "editable" : "locked",
            system: item.system,
            source: source.system,
            fields: item.schema.fields,
            systemFields: item.system.schema.fields,
            isEditable: this.isEditable,
            isGM: game.user.isGM,
            config: CONFIG.MGT2,
            settings: settings,
            containers: containers,
            computers: computers,
            hadContainer: hadContainer,
            weight: weight,
            unitlabels: { weight: MGT2Helper.getWeightLabel() },
            skills: skills,
        };
    }

    /** @override — resolve the per-type template before rendering */
    async _renderHTML(context, options) {
        const templatePath = `systems/mgt2/templates/items/${this.document.type}-sheet.html`;
        const html = await foundry.applications.handlebars.renderTemplate(templatePath, context);
        return { sheet: html };
    }

    /** @override — put rendered HTML into the window content */
    _replaceHTML(result, content, options) {
        content.innerHTML = result.sheet;
        // Inject theme class dynamically (can't use game.settings in static DEFAULT_OPTIONS)
        const theme = game.settings.get("mgt2", "theme");
        if (theme) this.element.classList.add(theme);
        this._activateTabGroups();
        this._bindItemEvents();
    }

    /** Bind CSS class-based events (templates not yet migrated to data-action) */
    _bindItemEvents() {
        const html = this.element;
        if (!this.isEditable) return;
        const bind = (sel, handler) => {
            for (const el of html.querySelectorAll(sel)) {
                el.addEventListener("click", (ev) => handler.call(this, ev, ev.currentTarget));
            }
        };
        bind(".event-create", TravellerItemSheet.#onCareerEventCreate);
        bind(".event-delete", TravellerItemSheet.#onCareerEventDelete);
        bind(".options-create", TravellerItemSheet.#onOptionCreate);
        bind(".options-delete", TravellerItemSheet.#onOptionDelete);
        bind(".modifiers-create", TravellerItemSheet.#onModifierCreate);
        bind(".modifiers-delete", TravellerItemSheet.#onModifierDelete);
    }

    _activateTabGroups() {
        for (const [group, activeTab] of Object.entries(this.tabGroups)) {
            const nav = this.element.querySelector(`nav[data-group="${group}"], .horizontal-tabs`);
            if (!nav) continue;

            nav.querySelectorAll('[data-tab]').forEach(link => {
                link.classList.toggle('active', link.dataset.tab === activeTab);
                link.addEventListener('click', event => {
                    event.preventDefault();
                    this.tabGroups[group] = link.dataset.tab;
                    this.render();
                });
            });

            this.element.querySelectorAll(`.itemsheet-panel [data-tab], [data-group="${group}"][data-tab]`).forEach(content => {
                content.classList.toggle('active', content.dataset.tab === activeTab);
            });
        }
    }

    /** @override — process form data before submit (weight/qty/cost conversions + container logic) */
    _prepareSubmitData(event, form, formData) {
        const data = foundry.utils.expandObject(formData.object);

        if (data.hasOwnProperty("weight")) {
            data.system = data.system || {};
            data.system.weight = MGT2Helper.convertWeightFromInput(data.weight);
            delete data.weight;
        }

        if (data.system?.hasOwnProperty("quantity")) {
            data.system.quantity = MGT2Helper.getIntegerFromInput(data.system.quantity);
        }

        if (data.system?.hasOwnProperty("cost")) {
            data.system.cost = MGT2Helper.getIntegerFromInput(data.system.cost);
        }

        // Container/equipped logic
        if (data.system?.hasOwnProperty("container") && this.document.system.hasOwnProperty("equipped")) {
            const equippedChange = this.document.system.equipped !== data.system.equipped;
            const containerChange = this.document.system.container?.id !== data.system.container?.id;

            if (equippedChange && data.system.equipped === true) {
                data.system.container = { id: "" };
            } else if (containerChange && data.system.container?.id !== "" && this.document.system.container?.id === "") {
                data.system.equipped = false;
            }
        }

        return foundry.utils.flattenObject(data);
    }

    // =========================================================
    // Actions
    // =========================================================

    static async #onCareerEventCreate(event) {
        event.preventDefault();
        const events = this.document.system.events;
        let newEvents;
        if (!events || events.length === 0) {
            newEvents = [{ age: "", description: "" }];
        } else {
            newEvents = [...events, { age: "", description: "" }];
        }
        return this.document.update({ system: { events: newEvents } });
    }

    static async #onCareerEventDelete(event, target) {
        event.preventDefault();
        const element = target.closest("[data-events-part]");
        const index = Number(element.dataset.eventsPart);
        const events = foundry.utils.deepClone(this.document.system.events);
        const newEvents = Object.entries(events)
            .filter(([key]) => Number(key) !== index)
            .map(([, val]) => val);
        return this.document.update({ system: { events: newEvents } });
    }

    static async #onOptionCreate(event, target) {
        event.preventDefault();
        const property = target.dataset.property;
        const options = this.document.system[property];
        let newOptions;
        if (!options || options.length === 0) {
            newOptions = [{ name: "", description: "" }];
        } else {
            newOptions = [...options, { name: "", description: "" }];
        }
        return this.document.update({ [`system.${property}`]: newOptions });
    }

    static async #onOptionDelete(event, target) {
        event.preventDefault();
        const element = target.closest("[data-options-part]");
        const property = element.dataset.property;
        const index = Number(element.dataset.optionsPart);
        const options = foundry.utils.deepClone(this.document.system[property]);
        const newOptions = Object.entries(options)
            .filter(([key]) => Number(key) !== index)
            .map(([, val]) => val);
        return this.document.update({ [`system.${property}`]: newOptions });
    }

    static async #onModifierCreate(event) {
        event.preventDefault();
        const modifiers = this.document.system.modifiers;
        let newModifiers;
        if (!modifiers || modifiers.length === 0) {
            newModifiers = [{ characteristic: "Endurance", value: null }];
        } else {
            newModifiers = [...modifiers, { characteristic: "Endurance", value: null }];
        }
        return this.document.update({ system: { modifiers: newModifiers } });
    }

    static async #onModifierDelete(event, target) {
        event.preventDefault();
        const element = target.closest("[data-modifiers-part]");
        const index = Number(element.dataset.modifiersPart);
        const modifiers = foundry.utils.deepClone(this.document.system.modifiers);
        const newModifiers = Object.entries(modifiers)
            .filter(([key]) => Number(key) !== index)
            .map(([, val]) => val);
        return this.document.update({ system: { modifiers: newModifiers } });
    }
}

/**
 * Define a set of template paths to pre-load
 * Pre-loaded templates are compiled and cached for fast access when rendering
 * @return {Promise}
 */
const preloadHandlebarsTemplates = async function() {

    const templatePaths = [
      "systems/mgt2/templates/items/armor-sheet.html",
      "systems/mgt2/templates/items/career-sheet.html",
      "systems/mgt2/templates/items/computer-sheet.html",
      "systems/mgt2/templates/items/contact-sheet.html",
      "systems/mgt2/templates/items/container-sheet.html",
      "systems/mgt2/templates/items/disease-sheet.html",
      "systems/mgt2/templates/items/equipment-sheet.html",
      "systems/mgt2/templates/items/item-sheet.html",
      "systems/mgt2/templates/items/species-sheet.html",
      "systems/mgt2/templates/items/talent-sheet.html",
      "systems/mgt2/templates/items/weapon-sheet.html",
      "systems/mgt2/templates/items/parts/sheet-configuration.html",
      "systems/mgt2/templates/items/parts/sheet-physical-item.html",
      "systems/mgt2/templates/roll-prompt.html",
      "systems/mgt2/templates/chat/roll.html",
      //"systems/mgt2/templates/chat/roll-characteristic.html",
      "systems/mgt2/templates/actors/actor-config-sheet.html",
      "systems/mgt2/templates/actors/actor-config-characteristic-sheet.html",
      "systems/mgt2/templates/actors/trait-sheet.html",
      "systems/mgt2/templates/editor-fullview.html"
    ];
  
    return loadTemplates(templatePaths);
  };

class ChatHelper {

    static setupCardListeners(message, element, messageData) {
        if (!message || !element) {
            return;
        }
        element.querySelectorAll('button[data-action="rollDamage"]').forEach(el => {
            el.addEventListener('click', async event => {
                await this._processRollDamageButtonEvent(message, event);
            });
        });

        element.querySelectorAll('button[data-action="damage"]').forEach(el => {
            el.addEventListener('click', async event => {
                await this._applyChatCardDamage(message, event);
            });
        });

        element.querySelectorAll('button[data-action="healing"]').forEach(el => {
            el.addEventListener('click', async event => {
                ui.notifications.warn("healing");
            });
        });

        element.querySelectorAll('button[data-index]').forEach(el => {
            el.addEventListener('click', async event => {
                await this._processRollButtonEvent(message, event);
            });
        });
    }

    static async _processRollButtonEvent(message, event) {
        event.preventDefault();
        event.stopPropagation();
        let buttons = message.flags.mgt2.buttons;
        const index = event.target.dataset.index;
        const button = buttons[index];
        let roll = await new Roll(button.formula, {}).roll();

        const chatData = {
            user: game.user.id,
            speaker: message.speaker,
            formula: roll._formula,
            tooltip: await roll.getTooltip(),
            total: Math.round(roll.total * 100) / 100,
            rollObjectName: button.message.objectName,
            rollMessage: MGT2Helper.format(button.message.flavor, Math.round(roll.total * 100) / 100),
          };

          const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
          chatData.content = html;
          return roll.toMessage(chatData);
    }

    static async _processRollDamageButtonEvent(message, event) {
        event.preventDefault();
        event.stopPropagation();
        let rollFormula = message.flags.mgt2.damage.formula;

        let roll = await new Roll(rollFormula, {}).roll();

        let speaker;
        let selectTokens = canvas.tokens.controlled;
        if (selectTokens.length > 0) {
            speaker = selectTokens[0].actor;
        } else {
            speaker = game.user.character;
        }

        let rollTypeName = message.flags.mgt2.damage.rollTypeName ? message.flags.mgt2.damage.rollTypeName + " DAMAGE" : null;

        const chatData = {
            user: game.user.id,
            speaker: ChatMessage.getSpeaker({ actor: speaker }),
            formula: roll._formula,
            tooltip: await roll.getTooltip(),
            total: Math.round(roll.total * 100) / 100,
            showButtons: true,
            hasDamage: true,
            rollTypeName: rollTypeName,
            rollObjectName: message.flags.mgt2.damage.rollObjectName
        };

        const html = await foundry.applications.handlebars.renderTemplate("systems/mgt2/templates/chat/roll.html", chatData);
        chatData.content = html;

        return roll.toMessage(chatData);
    }

    static _applyChatCardDamage(message, event) {
        const roll = message.rolls[0];
        return Promise.all(canvas.tokens.controlled.map(t => {
            const a = t.actor;
            return a.applyDamage(roll.total);
        }));
    }
}

const registerSettings = function () {

    game.settings.register("mgt2", "theme", {
        name: "MGT2.Settings.theme.name",
        hint: "MGT2.Settings.theme.hint",
        scope: "client",
        config: true,
        default: "black-and-red",
        type: String,
        choices: {
            "black-and-red": "MGT2.Themes.BlackAndRed",
            "mwamba": "MGT2.Themes.Mwamba",
            "blue": "MGT2.Themes.Blue"
        },
        requiresReload: true
    });

    game.settings.register('mgt2', 'usePronouns', {
        name: "MGT2.Settings.usePronouns.name",
        hint: "MGT2.Settings.usePronouns.hint",
        default: false,
        scope: 'world',
        type: Boolean,
        config: true,
        requiresReload: false
    });

    game.settings.register('mgt2', 'useGender', {
        name: "MGT2.Settings.useGender.name",
        hint: "MGT2.Settings.useGender.hint",
        default: false,
        scope: 'world',
        type: Boolean,
        config: true,
        requiresReload: false
    });

    game.settings.register('mgt2', 'showLife', {
        name: "MGT2.Settings.showLife.name",
        hint: "MGT2.Settings.showLife.hint",
        default: false,
        scope: 'world',
        type: Boolean,
        config: true,
        requiresReload: false
    });

    // game.settings.register('mgt2', 'useWeightMetric', {
    //     name: "MGT2.Settings.useWeightMetric.name",
    //     hint: "MGT2.Settings.useWeightMetric.hint",
    //     default: true,
    //     scope: 'world',
    //     type: Boolean,
    //     config: true,
    //     requiresReload: true
    // });

    // game.settings.register('mgt2', 'useDistanceMetric', {
    //     name: "MGT2.Settings.useDistanceMetric.name",
    //     hint: "MGT2.Settings.useDistanceMetric.hint",
    //     default: true,
    //     scope: 'world',
    //     type: Boolean,
    //     config: true,
    //     requiresReload: true
    // });

    // game.settings.register('mgt2', 'showTrash', {
    //     name: "Show Trash tab to Player",
    //     hint: "Player can see the Trash tab and recover item",
    //     default: false,
    //     scope: 'world',
    //     type: Boolean,
    //     config: true,
    //     requiresReload: false
    // });

    /*game.settings.register('mgt2', 'containerDropIn', {
        name: "Test",
        hint: "Mon hint",
        default: true,
        scope: 'client',
        type: Boolean,
        config: true
    });*/
};

function registerHandlebarsHelpers() {
  Handlebars.registerHelper('showDM', function (dm) {
    if (dm === 0) return "0";
    if (dm > 0) return `+${dm}`;
    if (dm < 0) return `${dm}`;
    return "";
  });
}

Hooks.once("init", async function () {
  CONFIG.MGT2 = MGT2;
  CONFIG.Combat.initiative = {
    formula: "2d6 + @initiative",
    decimals: 2
  };

  CONFIG.Actor.trackableAttributes = {
    character: {
      bar: ["life",
        "characteristics.strength",
        "characteristics.dexterity",
        "characteristics.endurance",
        "characteristics.intellect",
        "characteristics.education",
        "characteristics.social",
        "characteristics.morale",
        "characteristics.luck",
        "characteristics.sanity",
        "characteristics.charm",
        "characteristics.psionic",
        "characteristics.other"
      ],
      value: ["life.value",
      "health.radiations",
        "characteristics.strength.value",
        "characteristics.dexterity.value",
        "characteristics.endurance.value",
        "characteristics.intellect.value",
        "characteristics.education.value",
        "characteristics.social.value",
        "characteristics.morale.value",
        "characteristics.luck.value",
        "characteristics.sanity.value",
        "characteristics.charm.value",
        "characteristics.psionic.value",
        "characteristics.other.value"]
    }
  };

  game.mgt2 = {
    TravellerActor,
    TravellerItem
  };

  registerHandlebarsHelpers();
  registerSettings();

  CONFIG.Combatant.documentClass = MGT2Combatant;
  CONFIG.Actor.documentClass = TravellerActor;
  CONFIG.Item.documentClass = TravellerItem;

  foundry.documents.collections.Actors.unregisterSheet("core", foundry.appv1.sheets.ActorSheet);
  foundry.documents.collections.Actors.registerSheet("mgt2", TravellerCharacterSheet, { types: ["character"], makeDefault: true, label: "Traveller Sheet" });
  foundry.documents.collections.Actors.registerSheet("mgt2", TravellerVehiculeSheet, { types: ["vehicule"], makeDefault: true, label: "Vehicule Sheet" });

  foundry.documents.collections.Items.unregisterSheet("core", foundry.appv1.sheets.ItemSheet);
  foundry.documents.collections.Items.registerSheet("mgt2", TravellerItemSheet, { makeDefault: true });

  Object.assign(CONFIG.Actor.dataModels, {
    "character": CharacterData,
    "vehicule": VehiculeData
  });

  Object.assign(CONFIG.Item.dataModels, {
    "item": ItemData,
    "equipment": EquipmentData,
    "disease": DiseaseData,
    "career": CareerData,
    "talent": TalentData,
    "contact": ContactData,
    "weapon": WeaponData,
    "computer": ComputerData,
    "armor": ArmorData,
    "container": ItemContainerData,
    "species": SpeciesData
  });


  Hooks.on("renderChatMessageHTML", (message, element, messageData) => {
    ChatHelper.setupCardListeners(message, element, messageData);
  });

  // Preload template partials
  await preloadHandlebarsTemplates();
});

export { MGT2 };
//# sourceMappingURL=mgt2.bundle.js.map
