import { BaseCharacterSheetL5r5e } from "./base-character-sheet.js";
import { TwentyQuestionsDialog } from "./twenty-questions-dialog.js";

/**
 * Actor / Character Sheet
 */
export class CharacterSheetL5r5e extends BaseCharacterSheetL5r5e {
    static get defaultOptions() {
        return foundry.utils.mergeObject(super.defaultOptions, {
            classes: ["l5r5e", "sheet", "actor"],
            template: CONFIG.l5r5e.paths.templates + "actors/character-sheet.html",
            tabs: [
                { navSelector: ".sheet-tabs", contentSelector: ".sheet-body", initial: "skills" },
                { navSelector: ".advancements-tabs", contentSelector: ".advancements-body", initial: "last" },
            ],
        });
    }

    /**
     * Add the TwentyQuestions button in L5R specific bar
     * @override
     * @return {{label: string, class: string, icon: string, onclick: Function|null}[]}
     */
    _getL5rHeaderButtons() {
        const buttons = super._getL5rHeaderButtons();
        if (!this.isEditable || this.actor.limited) {
            return buttons;
        }

        buttons.unshift({
            label: game.i18n.localize("l5r5e.twenty_questions.bt_abrev"),
            class: "twenty-questions",
            icon: "fas fa-graduation-cap",
            onclick: async () => {
                await new TwentyQuestionsDialog(this.actor).render(true);
            },
        });
        return buttons;
    }

    /**
     * Commons datas
     */
    async getData(options = {}) {
        const sheetData = await super.getData(options);

        // Min rank = 1
        this.actor.system.identity.school_rank = Math.max(1, this.actor.system.identity.school_rank);

        // Money — read separate fields directly (koku/bu/zeni stored independently)
        sheetData.data.system.money = {
            koku: this.actor.system.koku ?? 0,
            bu: this.actor.system.bu ?? 0,
            zeni: this.actor.system.zeni ?? 0,
        };

        // Split school advancements by rank, and calculate xp spent and add it to total
        this._prepareSchoolAdvancement(sheetData);

        // Split Others advancements, and calculate xp spent and add it to total
        this._prepareOthersAdvancement(sheetData);

        // Update spent_xp to actor
        this.actor.system.xp_spent = sheetData.data.system.xp_spent;

        // Total
        sheetData.data.system.xp_saved = Math.floor(
            parseInt(sheetData.data.system.xp_total) - parseInt(sheetData.data.system.xp_spent)
        );

        // Chiaroscuro: Skill ranks list for <select>
        sheetData.data.skillRanksList = Object.keys(CONFIG.l5r5e.skillRanks).map((id) => ({
            id,
            label: game.i18n.localize(`chiaroscuro.skill_ranks.${id}`),
        }));

        // Chiaroscuro: Normalize skill values 0 (number) → "0" (string) for selectOptions matching
        for (const category of Object.values(sheetData.data.system.skills)) {
            for (const [key, value] of Object.entries(category)) {
                if (value === 0) category[key] = "0";
            }
        }

        // Chiaroscuro: Aspects gauge data
        const aspectsData = sheetData.data.system.aspects?.aspects ?? {};
        const gauge = aspectsData.gauge ?? 0;
        sheetData.data.aspectsData = {
            solar: aspectsData.solar ?? 0,
            lunar: aspectsData.lunar ?? 0,
            gauge,
            gaugePercent: ((gauge + 10) / 20) * 100,
            gaugeColor: gauge > 0 ? "#d4a855" : gauge < 0 ? "#5588aa" : "#888888",
        };

        // Chiaroscuro: Invocations split by type (direct from items)
        const invocations = sheetData.items.filter((i) => i.type === "mot_invocation");
        sheetData.data.splitInvocationsList = {
            general: invocations.filter((t) => !t.system.invocation_type || t.system.invocation_type === "general"),
            neutre: invocations.filter((t) => t.system.invocation_type === "neutre"),
            precis: invocations.filter((t) => t.system.invocation_type === "precis"),
        };

        // Chiaroscuro: Arcane items
        sheetData.data.arcaneItems = sheetData.items.filter((i) => i.type === "arcane");

        // Chiaroscuro: Mystere items
        sheetData.data.mystereItems = sheetData.items.filter((i) => i.type === "mystere");

        // Chiaroscuro: Technique École items
        sheetData.data.techniqueEcoleItems = sheetData.items.filter((i) => i.type === "technique_ecole");

        // Chiaroscuro: Identity tabs enriched HTML
        sheetData.data.enrichedHtml.identity_text1 = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
            this.actor.system.identity_text1 ?? "", { async: true }
        );
        sheetData.data.enrichedHtml.identity_text2 = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
            this.actor.system.identity_text2 ?? "", { async: true }
        );
        sheetData.data.enrichedHtml.notes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
            this.actor.system.notes ?? "", { async: true }
        );

        return sheetData;
    }

    /**
     * Subscribe to events from the sheet.
     * @param {jQuery} html HTML content of the sheet.
     */
    activateListeners(html) {
        super.activateListeners(html);

        // *** Everything below here is only needed if the sheet is editable ***
        if (!this.isEditable) {
            return;
        }

        // Autocomplete
        game.l5r5e.HelpersL5r5e.autocomplete(
            html,
            "system.identity.clan",
            game.l5r5e.HelpersL5r5e.getLocalizedClansList()
        );
        game.l5r5e.HelpersL5r5e.autocomplete(
            html,
            "system.identity.family",
            CONFIG.l5r5e.families.get(
                Object.entries(game.l5r5e.HelpersL5r5e.getLocalizedRawObject("l5r5e.clans")).find(
                    ([k, v]) => v === this.actor.system.identity.clan
                )?.[0]
            )
        );
        game.l5r5e.HelpersL5r5e.autocomplete(
            html,
            "system.identity.school",
            game.l5r5e.HelpersL5r5e.getSchoolsList(),
            ","
        );
        game.l5r5e.HelpersL5r5e.autocomplete(
            html,
            "system.identity.roles",
            game.l5r5e.HelpersL5r5e.getLocalizedRolesList(),
            ","
        );

        // Open linked school curriculum journal
        html.find(".school-journal-link").on("click", this._openLinkedJournal.bind(this));

        // Curriculum management
        html.find(".item-curriculum").on("click", this._switchSubItemCurriculum.bind(this));
        html.find("button[name=validate-curriculum]").on("click", this._actorAddOneToRank.bind(this));

        // Money +/-
        html.find(".money-control").on("click", this._modifyMoney.bind(this));

        // XP +/-
        html.find(".xp-control").on("click", this._modifyXP.bind(this));

        // Chiaroscuro: set default ring on ring-name click
        html.find(".ring-set-default").on("click", (event) => {
            event.preventDefault();
            const ring = $(event.currentTarget).data("ring");
            this.actor.update({ "system.default_ring": ring });
        });


        // Advancements Tab to current rank onload
        // TODO class "Active" Bug on load, dunno why :/
        this._tabs
            .find((e) => e._navSelector === ".advancements-tabs")
            .activate("advancement_rank_" + (this.actor.system.identity.school_rank || 0));
    }

    /**
     * Override to handle mot_invocation creation with invocation_type from the column.
     * @param {Event} event
     * @override
     */
    async _addSubItem(event) {
        const type = $(event.currentTarget).data("item-type");
        if (type === "mot_invocation") {
            event.preventDefault();
            event.stopPropagation();
            const invocationType = $(event.currentTarget).data("invocation-type") || "general";
            const created = await this.actor.createEmbeddedDocuments("Item", [
                {
                    name: game.i18n.localize(`TYPES.Item.mot_invocation`),
                    type: "mot_invocation",
                    img: `${CONFIG.l5r5e.paths.assets}icons/items/mot_invocation.svg`,
                    system: { invocation_type: invocationType },
                },
            ]);
            if (created?.length > 0) {
                const item = this.actor.items.get(created[0].id);
                item?.sheet.render(true);
            }
            return;
        }
        return super._addSubItem(event);
    }

    /**
     * Override base dice picker to open Chiaroscuro d6 dialog.
     * @param {Event} event
     */
    _openDicePickerForSkill(event) {
        event.preventDefault();
        const el = $(event.currentTarget);
        const weapon = this._getWeaponInfos(el.data("weapon-id") || null);
        const skillId = weapon?.skill || el.data("skill");
        const ringId = el.data("ring") || this.actor.system?.default_ring || "void";
        new game.l5r5e.ChiaroscuroDiceDialog({ actor: this.actor, ringId, skillId, itemUuid: weapon?.uuid }).render(true);
    }

    /**
     * Split the school advancement, calculate the total xp spent and the current total xp spent by rank
     */
    _prepareSchoolAdvancement(sheetData) {
        const adv = [];
        sheetData.data.system.xp_spent = 0;
        sheetData.items
            .filter((item) => ["peculiarity", "technique", "advancement"].includes(item.type))
            .forEach((item) => {
                const { xp_used_total, xp_used } = game.l5r5e.HelpersL5r5e.getItemsXpCost(item);
                sheetData.data.system.xp_spent += xp_used_total;

                const rank = Math.max(0, item.system.bought_at_rank);
                if (!adv[rank]) {
                    adv[rank] = {
                        rank: rank,
                        spent: {
                            total: 0,
                            curriculum: 0,
                        },
                        goal: CONFIG.l5r5e.xp.costPerRank[rank] || null,
                        list: [],
                    };
                }
                adv[rank].list.push(item);
                adv[rank].spent.total += xp_used_total;
                adv[rank].spent.curriculum += xp_used;
            });

        // If we finished the rank but haven't added anything to the next rank we should show an empty tab
        // note: adv is index from 1, not 0 because of rank starting at 1
        if(adv.length -1 < sheetData.data.system.identity.school_rank) {
            adv.push({list: [], rank: sheetData.data.system.identity.school_rank, spent: { total: 0, curriculum: 0}});
        }
        sheetData.data.advancementsListByRank = adv;
    }

    /**
     * Prepare Bonds, Item Pattern, Signature Scroll and get xp spend
     */
    _prepareOthersAdvancement(sheetData) {
        // Split OthersAdvancement from items
        sheetData.data.advancementsOthers = sheetData.items.filter((item) =>
            ["bond", "item_pattern", "title", "signature_scroll"].includes(item.type)
        );

        // Sort by rank desc
        sheetData.data.advancementsOthers.sort((a, b) => (b.system.rank || 0) - (a.system.rank || 0));

        // Total xp spent in curriculum & total
        sheetData.data.advancementsOthersTotalXp = sheetData.data.advancementsOthers.reduce(
            (acc, item) => acc + parseInt(item.system.xp_used_total || item.system.xp_used || 0),
            0
        );

        // Update the total spent
        sheetData.data.system.xp_spent += sheetData.data.advancementsOthersTotalXp;
    }

    /**
     * Update the actor.
     * @param event
     * @param formData
     */
    _updateObject(event, formData) {
        // Clan tag trim if autocomplete in school name
        if (
            formData["autoCompleteListName"] === "system.identity.school" &&
            formData["autoCompleteListSelectedIndex"] >= 0 &&
            !!formData["system.identity.clan"] &&
            formData["system.identity.school"].indexOf(` [${formData["system.identity.clan"]}]`) !== -1
        ) {
            formData["system.identity.school"] = formData["system.identity.school"].replace(
                ` [${formData["system.identity.clan"]}]`,
                ""
            );
        }

        // Store money as separate fields with auto-conversion (10 zeni = 1 bu, 10 bu = 1 koku)
        if (formData["system.money.koku"] !== undefined || formData["system.money.bu"] !== undefined || formData["system.money.zeni"] !== undefined) {
            let koku = parseInt(formData["system.money.koku"] ?? 0) || 0;
            let bu = parseInt(formData["system.money.bu"] ?? 0) || 0;
            let zeni = parseInt(formData["system.money.zeni"] ?? 0) || 0;
            // Auto-convert
            if (zeni >= 10) { bu += Math.floor(zeni / 10); zeni = zeni % 10; }
            if (bu >= 10) { koku += Math.floor(bu / 10); bu = bu % 10; }
            formData["system.koku"] = koku;
            formData["system.bu"] = bu;
            formData["system.zeni"] = zeni;
            delete formData["system.money.koku"];
            delete formData["system.money.bu"];
            delete formData["system.money.zeni"];
        }

        // Chiaroscuro: convert skill rank "0" (string from <select>) back to 0 (number)
        for (const [key, value] of Object.entries(formData)) {
            if (key.startsWith("system.skills.") && value === "0") {
                formData[key] = 0;
            }
        }

        // Save computed values
        const currentData = this.object.system;
        formData["system.focus"] = currentData.focus;
        formData["system.vigilance"] = currentData.vigilance;
        formData["system.endurance"] = currentData.endurance;
        formData["system.composure"] = currentData.composure;
        formData["system.fatigue.max"] = currentData.fatigue.max;
        formData["system.strife.max"] = currentData.strife.max;
        formData["system.void_points.max"] = currentData.void_points.max;

        return super._updateObject(event, formData);
    }

    /**
     * Convert a sum in Zeni to Zeni, Bu and Koku
     * @param {number} zeni
     * @return {{bu: number, koku: number, zeni: number}}
     * @private
     */
    _zeniToMoney(zeni) {
        const money = {
            koku: 0,
            bu: 0,
            zeni: zeni,
        };

        if (money.zeni >= CONFIG.l5r5e.money[0]) {
            money.koku = Math.floor(money.zeni / CONFIG.l5r5e.money[0]);
            money.zeni = Math.floor(money.zeni % CONFIG.l5r5e.money[0]);
        }
        if (money.zeni >= CONFIG.l5r5e.money[1]) {
            money.bu = Math.floor(money.zeni / CONFIG.l5r5e.money[1]);
            money.zeni = Math.floor(money.zeni % CONFIG.l5r5e.money[1]);
        }

        return money;
    }

    /**
     * Convert a sum in Zeni, Bu and Koku to Zeni
     * @param {number} koku
     * @param {number} bu
     * @param {number} zeni
     * @return {number}
     * @private
     */
    _moneyToZeni(koku, bu, zeni) {
        return Math.floor(koku * CONFIG.l5r5e.money[0]) + Math.floor(bu * CONFIG.l5r5e.money[1]) + Math.floor(zeni);
    }

    /**
     * Add or Subtract money (+/- buttons)
     * @param {Event} event
     * @private
     */
    _modifyMoney(event) {
        event.preventDefault();
        event.stopPropagation();

        const elmt = $(event.currentTarget);
        const type = elmt.data("type");
        const mod = parseInt(elmt.data("value")) || 0;
        if (!mod || !type) return;

        let koku = parseInt(this.actor.system.koku) || 0;
        let bu = parseInt(this.actor.system.bu) || 0;
        let zeni = parseInt(this.actor.system.zeni) || 0;

        if (type === "koku") koku += mod;
        else if (type === "bu") bu += mod;
        else zeni += mod;

        // Auto-convert
        if (zeni >= 10) { bu += Math.floor(zeni / 10); zeni = zeni % 10; }
        if (bu >= 10) { koku += Math.floor(bu / 10); bu = bu % 10; }
        // Clamp negatives
        if (zeni < 0) { bu += Math.ceil(zeni / 10); zeni = ((zeni % 10) + 10) % 10; }
        if (bu < 0) { koku += Math.ceil(bu / 10); bu = ((bu % 10) + 10) % 10; }
        koku = Math.max(0, koku);

        this.actor.update({ system: { koku, bu, zeni } });
        this.render(false);
    }

    /**
     * Add or Subtract XP (+/- buttons)
     * @param {Event} event
     * @private
     */
    async _modifyXP(event) {
        event.preventDefault();
        event.stopPropagation();

        const elmt = $(event.currentTarget);
        let mod = elmt.data("value");
        if (!mod) {
            return;
        }

        const new_xp_total = Math.max(0, this.actor.system.xp_total + mod);
        this.actor.update({
            system: {
                xp_total: new_xp_total,
            },
        });

        if(this.actor.system.xp_spent > new_xp_total) {
            ui.notifications.warn("l5r5e.advancements.warning.total_less_then_spent", { localize: true })
        }

        this.render(false);
    }

    /**
     * Add +1 to actor school rank
     * @param {Event} event
     * @private
     */
    async _actorAddOneToRank(event) {
        event.preventDefault();
        event.stopPropagation();

        this.actor.system.identity.school_rank = this.actor.system.identity.school_rank + 1;
        await this.actor.update({
            system: {
                identity: {
                    school_rank: this.actor.system.identity.school_rank,
                },
            },
        });
        this.render(false);
    }

    /**
     * Open the linked school curriculum journal
     * @param {Event} event
     * @private
     */
    async _openLinkedJournal(event) {
        event.preventDefault();
        event.stopPropagation();

        const actorJournal = this.actor.system.identity.school_curriculum_journal;
        if (!actorJournal.id) {
            return;
        }

        const journal = await game.l5r5e.HelpersL5r5e.getObjectGameOrPack({
            id: actorJournal.id,
            pack: actorJournal.pack,
            type: "JournalEntry",
        });
        if (journal) {
            journal.sheet.render(true);
        }
    }
}
