import {LiturgyData} from "../data/miracle/liturgyData.mjs";
import {Zonenruestung, Zonenwunde, Wunde} from "../data/trefferzone.mjs";
import {PlayerCharacterDataModel} from "../data/character.mjs";
import {evaluateRoll} from "../globals/DSARoll.mjs";
import {displayRoll} from "../globals/displayRoll.js";

export class Character extends Actor {

    /**
    import() {
        let input = document.createElement('input')
        input.type = 'file'
        input.accept = '.xml'
        input.onchange = e => {
            importCharacter(this.id, e.target.files[0])
        }
        input.click()
    }*/

    /**
     * @override
     * Augment the actor source data with additional dynamic data. Typically,
     * you'll want to handle most of your calculated/derived data in this step.
     * Data calculated in this step should generally not exist in template.json
     * (such as ability modifiers rather than ability scores) and should be
     * available both inside and outside of character sheets (such as if an actor
     * is queried and has a roll executed directly from it).
     */
    prepareDerivedData() {

        if (this.type === "Character") {
            const actorData = this;
            const systemData = actorData.system;

            systemData.attribute.mu.aktuell = systemData.attribute.mu.start + systemData.attribute.mu.mod
            systemData.attribute.kl.aktuell = systemData.attribute.kl.start + systemData.attribute.kl.mod
            systemData.attribute.in.aktuell = systemData.attribute.in.start + systemData.attribute.in.mod
            systemData.attribute.ch.aktuell = systemData.attribute.ch.start + systemData.attribute.ch.mod

            systemData.attribute.ff.aktuell = systemData.attribute.ff.start + systemData.attribute.ff.mod
            systemData.attribute.ge.aktuell = systemData.attribute.ge.start + systemData.attribute.ge.mod
            systemData.attribute.ko.aktuell = systemData.attribute.ko.start + systemData.attribute.ko.mod
            systemData.attribute.kk.aktuell = systemData.attribute.kk.start + systemData.attribute.kk.mod

            const mu = systemData.attribute.mu.aktuell
            const kl = systemData.attribute.kl.aktuell
            const _in = systemData.attribute.in.aktuell
            const ch = systemData.attribute.ch.aktuell
            const ff = systemData.attribute.ff.aktuell
            const ge = systemData.attribute.ge.aktuell
            const ko = systemData.attribute.ko.aktuell
            const kk = systemData.attribute.kk.aktuell

            systemData.lep.max = Math.round((ko + ko + kk) / 2) + systemData.lep.mod
            systemData.aup.max = Math.round((mu + ko + ge) / 2) + systemData.aup.mod
            systemData.asp.max = Math.round((mu + _in + ch) / 2) + systemData.asp.mod

            systemData.regeneration = systemData.regeneration ?? {
                lep: "1d6",
                asp: "1d6",
                kap: "1",
                ko: 0,
                in: 0
            }

            systemData.at = systemData.at ?? {links: {}, rechts: {}}
            systemData.at.links = systemData.at.links ?? {
                aktuell: 0,
                mods: 0
            }
            systemData.at.rechts = systemData.at.rechts ?? {
                aktuell: 0,
                mods: 0
            }
            systemData.at.basis = Math.round((mu + ge + kk) / 5)
            systemData.at.aktuell = systemData.at.basis
            systemData.at.links.aktuell = systemData.at.basis
            systemData.at.rechts.aktuell = systemData.at.basis
            systemData.pa = systemData.pa ?? {links: {}, rechts: {}}
            systemData.pa.links = systemData.pa.links ?? {
                aktuell: 0,
                mods: 0
            }
            systemData.pa.rechts = systemData.pa.rechts ?? {
                aktuell: 0,
                mods: 0
            }
            systemData.pa.basis = Math.round((_in + ge + kk) / 5)
            systemData.pa.aktuell = systemData.pa.basis
            systemData.pa.links.aktuell = systemData.pa.basis
            systemData.pa.rechts.aktuell = systemData.pa.basis
            systemData.fk = systemData.fk ?? {
                aktuell: 0,
                mods: 0
            }
            systemData.fk.basis = Math.round((_in + ff + kk) / 5);
            systemData.fk.aktuell = systemData.fk.basis

            systemData.ini.basis = Math.round((mu + mu + _in + ge) / 5)
            systemData.ini.aktuell = systemData.ini.basis
            systemData.mr.basis = Math.round((mu + kl + ko) / 5) + systemData.mr.mod
            systemData.mr.aktuell = systemData.mr.basis
            systemData.gs.basis = 6;
            systemData.gs.aktuell = systemData.gs.basis // TOOD: get GS from spezien

            systemData.ausweichen = {}
            systemData.ausweichen.basis = systemData.pa.basis
            systemData.ausweichen.aktuell = systemData.ausweichen.basis

            systemData.ueberanstrengung = 0

            if (game.settings.get("DSA_4-1", "optional_erschoepfung")) {

                systemData.erschoepfung = {
                    aktuell: systemData.erschoepfung.aktuell ?? 0,
                    max: ko
                }
                if (systemData.erschoepfung.aktuell > systemData.erschoepfung.max) {
                    systemData.ueberanstrengung = systemData.erschoepfung.aktuell - systemData.erschoepfung.max
                }
            }


            if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
                systemData.rs = {
                    brust: 0,
                    bauch: 0,
                    armlinks: 0,
                    armrechts: 0,
                    beinlinks: 0,
                    beinrechts: 0,
                    kopf: 0,
                }; // only with DSA_4-1.optional_trefferzonen = true
            } else {
                systemData.rs = 0; // only with DSA_4-1.optional_trefferzonen = false
            }
            systemData.be = 0 + systemData.ueberanstrengung;


            // half KO is the maximum a character can sustain wounds before collapsing
            systemData.wunden.max = Math.round(ko / 2);
            if (game.settings.get("DSA_4-1", "optional_trefferzonen")) {
                systemData.wunden.kopf = 0
                systemData.wunden.brust = 0
                systemData.wunden.bauch = 0
                systemData.wunden.ruecken = 0
                systemData.wunden.armlinks = 0
                systemData.wunden.armrechts = 0
                systemData.wunden.beinlinks = 0
                systemData.wunden.beinrechts = 0
            } else {
                systemData.wunden.gesamt = 0
            }

            systemData.ws = ko / 2;

            // map current set to RS and BE

            const ausruestung = systemData.heldenausruestung[systemData.setEquipped]
            const zonesToCheck = [
                "brust",
                "bauch",
                "ruecken",
                "kopf",
                "armlinks",
                "armrechts",
                "beinlinks",
                "beinrechts"]

            if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
                systemData.rs.gesamt = 0
                systemData.rs.brust = 0
                systemData.rs.bauch = 0
                systemData.rs.ruecken = 0
                systemData.rs.kopf = 0
                systemData.rs.armlinks = 0
                systemData.rs.armrechts = 0
                systemData.rs.beinlinks = 0
                systemData.rs.beinrechts = 0
            } else {
                systemData.rs = 0
            }

            zonesToCheck.forEach((zone) => {
                systemData.be += systemData.parent.items.get(ausruestung[zone])?.system.armorHandicap ?? 0
                if (game.settings.get("DSA_4-1", "optional_ruestungzonen")) {
                    zonesToCheck.forEach((itemZone) => {
                        systemData.rs[itemZone] += systemData.parent.items.get(ausruestung[zone])?.system.armorValue[itemZone] ?? 0
                    })
                } else {
                    systemData.rs += systemData.parent.items.get(ausruestung[zone])?.system.armorValue.total ?? 0
                }
            })

            systemData.kap.max = 0;

            // evaluate deities for KaP

            const deities = systemData.parent.items.filter(p => p.type === "Blessing")
            deities?.forEach((deity) => {
                if (LiturgyData.alverans.includes(deity.system.gottheit)) {
                    systemData.kap.max = 24;
                } else if (systemData.kap.max === 0) {
                    systemData.kap.max += 12;
                }
            }, 0)

            // evaluate SFs

            const propSetter = (mod) => {
                // we don't apply to .mod as that field is only applicable for temporary modifier from ActiveEffects, Spells, Liturgies, Combat Effects
                const property = foundry.utils.getProperty(systemData, mod.name)
                if (property && property.aktuell) { // apply to aktuell
                    const aktuellProperty = foundry.utils.getProperty(systemData, mod.name + ".aktuell")
                    foundry.utils.setProperty(systemData, mod.name + ".aktuell", aktuellProperty + mod.value)
                } else { // apply directly
                    const aktuellProperty = foundry.utils.getProperty(systemData, mod.name)
                    foundry.utils.setProperty(systemData, mod.name, aktuellProperty + mod.value)
                }
            }

            this.itemTypes["SpecialAbility"].forEach(sf => {
                if (sf.system.isActive()) {
                    sf.system.getActiveMod()?.forEach(propSetter)
                }
            })

            this.itemTypes["Advantage"].forEach(adv => {
                if (adv.system.isActive()) {
                    adv.system.getActiveMod()?.forEach(propSetter)
                }
            })
        }
    }

    getModificationsOn(fieldName) {

        const flatActor = foundry.utils.flattenObject(this.system)

        const grabbag = {}

        // mods via SF
        this.itemTypes["SpecialAbility"].forEach(sf => {
            if (sf.system.isActive()) {
                sf.system.getActiveMod()?.forEach(mod => {
                    // we don't apply to .mod as that field is only applicable for temporary modifier from ActiveEffects, Spells, Liturgies, Combat Effects
                    if (mod.name === fieldName || mod.talent === fieldName) {
                        grabbag[sf.name] = mod.value
                    }
                })
            }
        })

        // mods via adv
        this.itemTypes["Advantage"].forEach(adv => {
            if (adv.system.isActive()) {
                adv.system.getActiveMod()?.forEach(mod => {
                    // we don't apply to .mod as that field is only applicable for temporary modifier from ActiveEffects, Spells, Liturgies, Combat Effects
                    if (mod.name === fieldName || mod.talent === fieldName) {
                        grabbag[adv.name] = mod.value
                    }
                })
            }
        })

        // TODO mods via ActiveEffects

        grabbag["Ergebnis"] = flatActor[fieldName]

        return grabbag

    }

    /**
     * Augment the basic Item data model with additional dynamic data.
     */
    prepareData() {
        super.prepareData();
        this.prepareEmbeddedDocuments();
    }


    getRollData() {
        const data = super.getRollData();
        this.prepareDerivedData()

        if (this.type !== 'character' && this.type !== 'creature') return;


        // move sonderfertigkeiten into data, if it isn't in data the actor doesn't have that sonderfertigkeit

        data.sf = {}
        if (data.sonderfertigkeiten) {
            data.sonderfertigkeiten.forEach(sf => {
                data.sf[sf.name] = sf.auswahl;
            })
            delete data.sonderfertigkeiten;
        }

        return data;
    }


    findEquipmentOnSlot(slot, setNumber) {
        return this.items.get(this.system.heldenausruestung[setNumber ?? this.system.setEquipped]?.[slot])
    }

    getEquipmentSetUpdateObject() {
        const updateObject = {}
        Array.from(this.system.heldenausruestung).forEach((equipmentSet, index) => {
            updateObject[`system.heldenausruestung.${index}.links`] = equipmentSet.links;
            updateObject[`system.heldenausruestung.${index}.rechts`] = equipmentSet.rechts;
            updateObject[`system.heldenausruestung.${index}.brust`] = equipmentSet.brust;
            updateObject[`system.heldenausruestung.${index}.bauch`] = equipmentSet.bauch;
            updateObject[`system.heldenausruestung.${index}.ruecken`] = equipmentSet.ruecken;
            updateObject[`system.heldenausruestung.${index}.kopf`] = equipmentSet.kopf;
            updateObject[`system.heldenausruestung.${index}.fernkampf`] = equipmentSet.fernkampf;
            updateObject[`system.heldenausruestung.${index}.munition`] = equipmentSet.munition;
            updateObject[`system.heldenausruestung.${index}.armlinks`] = equipmentSet.armlinks;
            updateObject[`system.heldenausruestung.${index}.armrechts`] = equipmentSet.armrechts;
            updateObject[`system.heldenausruestung.${index}.beinlinks`] = equipmentSet.beinlinks;
            updateObject[`system.heldenausruestung.${index}.beinrechts`] = equipmentSet.beinrechts;

        })
        return updateObject;
    }

    /**
     * reduce the wealth owned by this character by the given amount in Silver
     * @param by amount of Silver to reduce the wealth by the character
     * @returns {boolean}
     */
    async reduceWealth(by) {

        const ducats = this.itemTypes["Equipment"].filter(p => p.name === "Dukate")
        const silver = this.itemTypes["Equipment"].filter(p => p.name === "Silbertaler")
        const kreutzer = this.itemTypes["Equipment"].filter(p => p.name === "Kreuzer")
        const heller = this.itemTypes["Equipment"].filter(p => p.name === "Heller")

        let ducatsAmount = 0
        let silverAmount = 0
        let kreutzerAmount = 0
        let hellerAmount = 0

        ducats.forEach(d => {
            ducatsAmount += d.system.quantity ?? 1
        })

        silver.forEach(s => {
            silverAmount += s.system.quantity ?? 1
        })

        kreutzer.forEach(k => {
            kreutzerAmount += k.system.quantity ?? 1
        })

        heller.forEach(h => {
            hellerAmount += h.system.quantity ?? 1
        })

        // Convert total wealth to silver
        let totalSilver = ducatsAmount * 10 + silverAmount + kreutzerAmount * 0.1 + hellerAmount * 0.01;

        if (totalSilver < by) {
            return false; // Indicate that the reduction can't be performed
        }

        // Subtract the given sum from total silver
        totalSilver -= by;

        // Handle if the total goes below zero
        if (totalSilver < 0) totalSilver = 0;

        // Convert back to coinages
        let newDucats = Math.floor(totalSilver / 10);
        totalSilver %= 10;

        let newSilver = Math.floor(totalSilver);
        totalSilver %= 1;

        let newKreutzer = Math.floor(totalSilver / 0.1);
        totalSilver %= 0.1;

        let newHeller = Math.round(totalSilver / 0.01);

        // remove all coinage items

        let deleteDocuments = []
        ducats.forEach(d => deleteDocuments.push(d._id))
        silver.forEach(s => deleteDocuments.push(s._id))
        kreutzer.forEach(k => deleteDocuments.push(k._id))
        heller.forEach(h => deleteDocuments.push(h._id))

        await this.deleteEmbeddedDocuments('Item', deleteDocuments)

        // rebuild coinage documents
        const compendiumOfCoins = game.packs.get('DSA_4-1.Currency');

        if (newDucats > 0) {
            let coin = compendiumOfCoins.index.find(coin => coin.name === "Dukate")
            const ducatDocument = await compendiumOfCoins.getDocument(coin._id);
            try {
                this.createEmbeddedDocuments('Item', [ducatDocument]).then(
                    embeddedDocuments => {
                        embeddedDocuments[0].update({"system.quantity": newDucats}).then(_ => {
                            console.log("created new Ducats with qty of", newDucats)
                        })
                    })
            } catch (err) {
            }
        }
        if (newSilver > 0) {
            let coin = compendiumOfCoins.index.find(coin => coin.name === "Silbertaler")
            const silverDocument = await compendiumOfCoins.getDocument(coin._id);
            try {
                this.createEmbeddedDocuments('Item', [silverDocument]).then(
                    embeddedDocuments => {
                        embeddedDocuments[0].update({"system.quantity": newSilver}).then(_ => {
                            console.log("created new Silver with qty of", newSilver)
                        })
                    })
            } catch (err) {
            }
        }

        if (newKreutzer > 0) {
            let coin = compendiumOfCoins.index.find(coin => coin.name === "Kreuzer")
            const kreutzerDocument = await compendiumOfCoins.getDocument(coin._id);
            try {
                this.createEmbeddedDocuments('Item', [kreutzerDocument]).then(
                    embeddedDocuments => {
                        embeddedDocuments[0].update({"system.quantity": newKreutzer}).then(_ => {
                            console.log("created new Kreutzer with qty of", newKreutzer)
                        })
                    })
            } catch (err) {
            }
        }

        if (newHeller > 0) {
            let coin = compendiumOfCoins.index.find(coin => coin.name === "Heller")
            const hellerDocument = await compendiumOfCoins.getDocument(coin._id);
            try {
                this.createEmbeddedDocuments('Item', [hellerDocument]).then(
                    embeddedDocuments => {
                        embeddedDocuments[0].update({"system.quantity": newHeller}).then(_ => {
                            console.log("created new Heller with qty of", newHeller)
                        })
                    })
            } catch (err) {
            }
        }

        return true
    }


    isWorn(itemId) {

        const slots = PlayerCharacterDataModel.getSlots()
        const set = this.system.heldenausruestung[this.system.setEquipped]
        if (set) {
            for (const slot of slots) {
                const equipmentSlotId = set[slot]
                if (equipmentSlotId === itemId) {
                    return slot
                }
            }
        }

        return false
    }


    async rollDefense(data) {
        const maneuver = data.manuever
        const weapon = this.itemTypes["Equipment"].find(p => p._id === data.weapon)
        const skill = data.skill !== "Ausweichen" ? this.itemTypes["Skill"].find(p => p._id === data.skill) : "Ausweichen"

        let target = 0
        try {
            target = game.actors.get(game.scenes.current.tokens.find(p => p._id === data.target).actorId)
        } catch (e) {
            target = game.actors.get(data.target)
        }

        let evaluated = await evaluateRoll("1d20", {
            value: data.targetNumber,
            mod: data.mod + data.penalty,
            owner: this,
            werte: [0]
        })

        const context = {
            ...data,
            weapon: weapon.name,
            maneuver,
            target: this.name,
            attacker: this._id,
            mod: data.mod,
            penalty: data.penalty,
            circumstance: data.circumstance,
            die: evaluated.evaluated.terms[0].results[0].result,
            value: data.targetNumber,
        }

        if (skill === "Ausweichen") {
            context.weapon = "Ausweichen"
        }

        if (evaluated.tap>0) {
            context.remaining = evaluated.tap
            context.hit = true
            context.critical = evaluated.meisterlich
        } else {
            context.missing = evaluated.tap
            context.miss = true
            context.fumble = evaluated.patzer
        }

        await displayRoll(evaluated.evaluated, game.user, data.actor, false, false, 'systems/DSA_4-1/templates/chat/attack-chat-message.hbs', context)
    }

    async rollAttack(data) {
        const maneuver = data.manuever
        const weapon = this.itemTypes["Equipment"].find(p => p._id === data.weapon) ?? this.itemTypes["Skill"].find(p => p._id === data.skill)
        let target = 0
        try {
            target = game.actors.get(game.scenes.current.tokens.find(p => p._id === data.target).actorId)
        } catch (e) {
            target = game.actors.get(data.target)
        }

        let evaluated = await evaluateRoll("1d20", {
            value: data.targetNumber,
            mod: data.mod + data.penalty,
            owner: this,
            werte: [0]
        })

        const context = {
            ...data,
            weapon: weapon.name,
            maneuver,
            target: target.name,
            attacker: this._id,
            mod: data.mod,
            penalty: data.penalty,
            circumstance: data.circumstance,
            die: evaluated.evaluated.terms[0].results[0].result,
            value: data.targetNumber,
        }

        if (evaluated.tap>0) {
            context.remaining = evaluated.tap
            context.hit = true
            context.critical = evaluated.meisterlich
        } else {
            context.missing = evaluated.tap
            context.miss = true
            context.fumble = evaluated.patzer
        }

        await displayRoll(evaluated.evaluated, game.user, data.actor, false, false, 'systems/DSA_4-1/templates/chat/attack-chat-message.hbs', context)
    }

    /**
     *
     * @param amount
     * @param zone either null or one of
     * @returns {Promise<void>}
     */
    async takeDamage(amount = null, zone = null) {

        this.prepareDerivedData()

        const playWithZoneArmor = game.settings.get("DSA_4-1", "optional_ruestungzonen")
        const playWithWoundZones = game.settings.get("DSA_4-1", "optional_trefferzonen")
        const previousLeP = this.system.lep.aktuell;

        if (amount == null) {
            // TODO show Dialog for entering damage amount (TP)
        }

        let armorReduction = 0
        let setEquipped = this.system.setEquipped
        let woundThreshold = this.system.ws

        if (playWithZoneArmor) {
            const armorId = this.system.heldenausruestung[setEquipped][Zonenruestung[zone]]
            const zoneArmor = await this.items.find(p => p._id === armorId)
            if (!zoneArmor) {
                return console.error(`zone "${zone}" is not a valid value`)
            }
            armorReduction = zoneArmor.system.armorValue ?? 0
        } else {
            armorReduction = this.system.rs
        }

        let damage = amount - armorReduction
        let wounds = damage / woundThreshold

        let wound = null

        if (playWithWoundZones) {
            wound = await game.packs.get("DSA_4-1.Wounds").index.find(p => p.name === Zonenwunde[zone])
            if (!wound) {
                return console.error(`Wunden Dokument zu "${zone}" konnten nicht gefunden werden`)
            }
        } else {
            wound = await game.packs.get("DSA_4-1.Wounds").index.find(p => p.name === Wunde)
            if (!wound) {
                return console.error(`Wunden Dokument zu "${Wunde}" konnten nicht gefunden werden`)
            }
        }

        // TODO this doesnt work yet, wound documents wont get expanded

        for (let i = 0; i < wounds; i++) {
            await this.createEmbeddedDocuments('Item', [wound])
        }
        await this.update({system: {lep: {aktuell: previousLeP - damage}}})
    }

    getWoundModel() {
        if (this.type === "Character") {
            const actorData = this;
            const systemData = actorData.system;
            let returnValue = {
                max: 0,
                current: 0,
                ws: 0
            }

            returnValue.max = systemData.wunden.max;
            console.log("spiel mit trefferzonen: ", game.settings.get("DSA_4-1", "optional_trefferzonen"));
            if (game.settings.get("DSA_4-1", "optional_trefferzonen")) {
                returnValue.current =
                    systemData.wunden.kopf +
                    systemData.wunden.brust +
                    systemData.wunden.bauch +
                    systemData.wunden.ruecken +
                    systemData.wunden.armlinks +
                    systemData.wunden.armrechts +
                    systemData.wunden.beinlinks +
                    systemData.wunden.beinrechts +
                    systemData.wunden.gesamt;
            } else {
                returnValue.current = systemData.wunden.aktuell;
            }

            returnValue.ws = systemData.ws;
            return returnValue;
        } else {
            return null;
        }
    }
}
