import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';

export default class DHRoll extends Roll {
    baseTerms = [];
    constructor(formula, data = {}, options = {}) {
        super(formula, data, options);
        if (!this.data || !Object.keys(this.data).length) this.data = options.data;
    }

    get title() {
        return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
    }

    static messageType = 'adversaryRoll';

    static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs';

    static DefaultDialog = D20RollDialog;

    static async build(config = {}, message = {}) {
        const roll = await this.buildConfigure(config, message);
        if (!roll) return;
        await this.buildEvaluate(roll, config, (message = {}));
        await this.buildPost(roll, config, (message = {}));
        return config;
    }

    static async buildConfigure(config = {}, message = {}) {
        config.hooks = [...this.getHooks(), ''];
        config.dialog ??= {};

        const actorIdSplit = config.source?.actor?.split('.');
        if (actorIdSplit) {
            const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
            config.tagTeamSelected = Boolean(tagTeamSettings.members[actorIdSplit[actorIdSplit.length - 1]]);
        }

        for (const hook of config.hooks) {
            if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
        }

        this.applyKeybindings(config);

        this.temporaryModifierBuilder(config);

        let roll = new this(config.roll.formula, config.data, config);
        if (config.dialog.configure !== false) {
            // Open Roll Dialog
            const DialogClass = config.dialog?.class ?? this.DefaultDialog;
            const configDialog = await DialogClass.configure(roll, config, message);
            if (!configDialog) return;
        }

        for (const hook of config.hooks) {
            if (
                Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false
            )
                return [];
        }
        return roll;
    }

    static async buildEvaluate(roll, config = {}, message = {}) {
        if (config.evaluate !== false) {
            await roll.evaluate();
            config.roll = this.postEvaluate(roll, config);
        }
    }

    static async buildPost(roll, config, message) {
        for (const hook of config.hooks) {
            if (Hooks.call(`${CONFIG.DH.id}.postRoll${hook.capitalize()}`, config, message) === false) return null;
        }

        if (config.skips?.createMessage) {
            if (game.modules.get('dice-so-nice')?.active) {
                await game.dice3d.showForRoll(roll, game.user, true);
            }
        } else if (!config.source?.message) {
            config.message = await this.toMessage(roll, config);
        }
    }

    static postEvaluate(roll, config = {}) {
        return {
            total: roll.total,
            formula: roll.formula,
            dice: roll.dice.map(d => ({
                dice: d.denomination,
                total: d.total,
                formula: d.formula,
                results: d.results
            }))
        };
    }

    static async toMessage(roll, config) {
        const cls = getDocumentClass('ChatMessage'),
            msgData = {
                type: this.messageType,
                user: game.user.id,
                title: roll.title,
                speaker: cls.getSpeaker({ actor: roll.data?.parent }),
                sound: config.mute ? null : CONFIG.sounds.dice,
                system: config,
                rolls: [roll]
            };

        config.selectedRollMode ??= game.settings.get('core', 'rollMode');

        if (roll._evaluated) {
            const message = await cls.create(msgData, { rollMode: config.selectedRollMode });

            if (config.tagTeamSelected) {
                game.system.api.applications.dialogs.TagTeamDialog.assignRoll(message.speakerActor, message);
            }

            if (roll.formula !== '' && game.modules.get('dice-so-nice')?.active) {
                await game.dice3d.waitFor3DAnimationByMessageID(message.id);
            }

            return message;
        } else return msgData;
    }

    /** @inheritDoc */
    async render({ flavor, template = this.constructor.CHAT_TEMPLATE, isPrivate = false, ...options } = {}) {
        if (!this._evaluated) return;
        const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
        return foundry.applications.handlebars.renderTemplate(template, chatData);
    }

    /** @inheritDoc */
    async _prepareChatRenderContext({ flavor, isPrivate = false, ...options } = {}) {
        if (isPrivate) {
            return {
                user: game.user.id,
                flavor: null,
                title: '???',
                roll: {
                    total: '??'
                },
                hasRoll: true,
                isPrivate
            };
        } else {
            options.message.system.user = game.user.id;
            return options.message.system;
        }
    }

    static applyKeybindings(config) {
        if (config.event)
            config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey);
    }

    static getHooks(hooks) {
        return hooks ?? [];
    }

    formatModifier(modifier) {
        if (Array.isArray(modifier)) {
            return [
                new foundry.dice.terms.OperatorTerm({ operator: '+' }),
                ...this.constructor.parse(modifier.join(' + '), this.options.data)
            ];
        } else {
            const numTerm = modifier < 0 ? '-' : '+';
            return [
                new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
                new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) })
            ];
        }
    }

    applyBaseBonus() {
        return [];
    }

    addModifiers(roll) {
        roll = roll ?? this.options.roll;
        roll.modifiers?.forEach(m => {
            this.terms.push(...this.formatModifier(m.value));
        });
    }

    getBonus(path, label) {
        const bonus = foundry.utils.getProperty(this.data.bonuses, path),
            modifiers = [];
        if (bonus?.bonus)
            modifiers.push({
                label: label,
                value: bonus?.bonus
            });
        if (bonus?.dice?.length)
            modifiers.push({
                label: label,
                value: bonus?.dice
            });
        return modifiers;
    }

    getFaces(faces) {
        return Number(faces.startsWith('d') ? faces.replace('d', '') : faces);
    }

    constructFormula(config) {
        this.terms = Roll.parse(this.options.roll.formula, config.data);

        this.options.roll.modifiers = this.applyBaseBonus();
        this.addModifiers();

        if (this.options.extraFormula) {
            this.terms.push(
                new foundry.dice.terms.OperatorTerm({ operator: '+' }),
                ...this.constructor.parse(this.options.extraFormula, this.options.data)
            );
        }
        return (this._formula = this.constructor.getFormula(this.terms));
    }

    static calculateTotalModifiers(roll) {
        let modifierTotal = 0;
        for (let i = 0; i < roll.terms.length; i++) {
            if (
                roll.terms[i] instanceof foundry.dice.terms.NumericTerm &&
                !!roll.terms[i - 1] &&
                roll.terms[i - 1] instanceof foundry.dice.terms.OperatorTerm
            )
                modifierTotal += Number(`${roll.terms[i - 1].operator}${roll.terms[i].total}`);
        }
        return modifierTotal;
    }

    static temporaryModifierBuilder(config) {
        return {};
    }
}

export const registerRollDiceHooks = () => {
    Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
        const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
        if (
            automationSettings.countdownAutomation &&
            config.actionType !== CONFIG.DH.ITEM.actionTypes.reaction.id &&
            !config.tagTeamSelected &&
            !config.skips?.updateCountdowns
        ) {
            const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
            await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.actionRoll.id);

            if (config.roll.result.duality === -1) {
                await updateCountdowns(CONFIG.DH.GENERAL.countdownProgressionTypes.fear.id);
            }
        }

        const hopeFearAutomation = automationSettings.hopeFear;
        if (
            !config.source?.actor ||
            (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
            config.actionType === 'reaction' ||
            config.tagTeamSelected ||
            config.skips?.resources
        )
            return;
        const actor = await fromUuid(config.source.actor);
        let updates = [];
        if (!actor) return;
        if (config.roll.isCritical || config.roll.result.duality === 1)
            updates.push({ key: 'hope', value: 1, total: -1, enabled: true });
        if (config.roll.isCritical) updates.push({ key: 'stress', value: 1, total: -1, enabled: true });
        if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1, total: -1, enabled: true });

        if (config.rerolledRoll) {
            if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1)
                updates.push({ key: 'hope', value: -1, total: 1, enabled: true });
            if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: -1, total: 1, enabled: true });
            if (config.rerolledRoll.result.duality === -1)
                updates.push({ key: 'fear', value: -1, total: 1, enabled: true });
        }

        if (updates.length) {
            const target = actor.system.partner ?? actor;
            if (!['dead', 'defeated', 'unconscious'].some(x => actor.statuses.has(x))) {
                if (config.rerolledRoll) target.modifyResource(updates);
                else config.costs = [...(config.costs ?? []), ...updates];
            }
        }

        if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;

        const rollResult = config.roll.success || config.targets.some(t => t.hit),
            looseSpotlight = !rollResult || config.roll.result.duality === -1;

        if (looseSpotlight && game.combat?.active) {
            const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
            if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
        }

        return;
    });
};
