import { DS4Actor } from "../actor/actor";
import { DS4 } from "../config";
import { createCheckRoll } from "../rolls/check-factory";
import notifications from "../ui/notifications";
import { AttackType, DS4ItemData } from "./item-data";

/**
 * The Item class for DS4
 */
export class DS4Item extends Item<DS4ItemData> {
    /**
     * @override
     */
    prepareData(): void {
        super.prepareData();
        this.prepareDerivedData();
    }

    prepareDerivedData(): void {
        if (this.data.type === "talent") {
            const data = this.data.data;
            data.rank.total = data.rank.base + data.rank.mod;
        }
        if (this.data.type === "weapon" || this.data.type === "spell") {
            this.data.data.rollable = this.data.data.equipped;
        }
    }

    isNonEquippedEuipable(): boolean {
        return "equipped" in this.data.data && !this.data.data.equipped;
    }

    /**
     * The number of times that active effect changes originating from this item should be applied.
     */
    get activeEffectFactor(): number | undefined {
        if (this.data.type === "talent") {
            return this.data.data.rank.total;
        }
        return 1;
    }

    /**
     * Roll a check for a action with this item.
     */
    async roll(): Promise<void> {
        if (!this.isOwnedItem()) {
            throw new Error(game.i18n.format("DS4.ErrorCannotRollUnownedItem", { name: this.name, id: this.id }));
        }

        switch (this.data.type) {
            case "weapon":
                return this.rollWeapon();
            case "spell":
                return this.rollSpell();
            default:
                throw new Error(game.i18n.format("DS4.ErrorRollingForItemTypeNotPossible", { type: this.data.type }));
        }
    }

    protected async rollWeapon(this: this & { readonly isOwned: true }): Promise<void> {
        if (!(this.data.type === "weapon")) {
            throw new Error(
                game.i18n.format("DS4.ErrorWrongItemType", {
                    actualType: this.data.type,
                    expectedType: "weapon",
                    id: this.id,
                    name: this.name,
                }),
            );
        }

        if (!this.data.data.equipped) {
            throw new Error(
                game.i18n.format("DS4.ErrorItemMustBeEquippedToBeRolled", {
                    name: this.name,
                    id: this.id,
                    type: this.data.type,
                }),
            );
        }

        const ownerDataData = ((this.actor as unknown) as DS4Actor).data.data; // TODO(types): Improve so that the concrete Actor type is known here
        const weaponBonus = this.data.data.weaponBonus;
        const combatValue = await this.getCombatValueKeyForAttackType(this.data.data.attackType);
        const checkTargetValue = (ownerDataData.combatValues[combatValue].total as number) + weaponBonus;
        await createCheckRoll(checkTargetValue, {
            rollMode: game.settings.get("core", "rollMode"),
            maxCritSuccess: ownerDataData.rolling.maximumCoupResult,
            minCritFailure: ownerDataData.rolling.minimumFumbleResult,
        });
    }

    protected async rollSpell(): Promise<void> {
        if (!(this.data.type === "spell")) {
            throw new Error(
                game.i18n.format("DS4.ErrorWrongItemType", {
                    actualType: this.data.type,
                    expectedType: "spell",
                    id: this.id,
                    name: this.name,
                }),
            );
        }

        if (!this.data.data.equipped) {
            throw new Error(
                game.i18n.format("DS4.ErrorItemMustBeEquippedToBeRolled", {
                    name: this.name,
                    id: this.id,
                    type: this.data.type,
                }),
            );
        }

        const ownerDataData = ((this.actor as unknown) as DS4Actor).data.data; // TODO(types): Improve so that the concrete Actor type is known here
        const spellBonus = Number.isNumeric(this.data.data.bonus) ? parseInt(this.data.data.bonus) : undefined;
        if (spellBonus === undefined) {
            notifications.info(
                game.i18n.format("DS4.InfoManuallyEnterSpellBonus", {
                    name: this.name,
                    spellBonus: this.data.data.bonus,
                }),
            );
        }
        const spellType = this.data.data.spellType;
        const checkTargetValue = (ownerDataData.combatValues[spellType].total as number) + (spellBonus ?? 0);

        await createCheckRoll(checkTargetValue, {
            rollMode: game.settings.get("core", "rollMode"),
            maxCritSuccess: ownerDataData.rolling.maximumCoupResult,
            minCritFailure: ownerDataData.rolling.minimumFumbleResult,
        });
    }

    protected async getCombatValueKeyForAttackType(attackType: AttackType): Promise<"meleeAttack" | "rangedAttack"> {
        if (attackType === "meleeRanged") {
            const { melee, ranged } = { ...DS4.i18n.attackTypes };
            const identifier = "attack-type-selection";
            const label = game.i18n.localize("DS4.AttackType");
            const answer = Dialog.prompt({
                title: game.i18n.localize("DS4.AttackTypeSelection"),
                content: await renderTemplate("systems/ds4/templates/common/simple-select-form.hbs", {
                    label,
                    identifier,
                    options: { melee, ranged },
                }),
                label: game.i18n.localize("DS4.GenericOkButton"),
                callback: (html) => {
                    const selectedAttackType = html.find(`#${identifier}`).val();
                    if (selectedAttackType !== "melee" && selectedAttackType !== "ranged") {
                        throw new Error(
                            game.i18n.format("DS4.ErrorUnexpectedAttackType", {
                                actualType: selectedAttackType,
                                expectedTypes: "'melee', 'ranged'",
                            }),
                        );
                    }
                    return `${selectedAttackType}Attack` as const;
                },
                render: () => undefined, // TODO(types): This is actually optional, remove when types are updated )
                options: { jQuery: true },
            });
            return answer;
        } else {
            return `${attackType}Attack` as const;
        }
    }

    /**
     * Type-guarding variant to check if the item is owned.
     */
    isOwnedItem(): this is this & { readonly isOwned: true } {
        return this.isOwned;
    }
}