/* -------------------------------------------- */
/*  Foundry VTT Initialization                  */
/* -------------------------------------------- */
import SR6Roll from "./SR6Roll.js";
import { registerSystemSettings } from "./settings.js";
import { registerSystemSettingsPosti18n } from "./settings-post-i18n.js";
import { pdfJournalInit } from "./util/pdfJournalInit.js";
import Shadowrun6Combat from "./Shadowrun6Combat.js";
import { SR6Config } from "./config.js";
import { preloadHandlebarsTemplates } from "./templates.js";
import { defineHandlebarHelper } from "./util/helper.js";
import { redefineHandlebarLog } from "./util/helper.js";
import { PreparedRoll, RollType } from "./dice/RollTypes.js";
import { doRoll } from "./Rolls.js";
import { SYSTEM_NAME } from "./constants.js";
import EdgeRoll from "./dice/EdgeRoll.js";
import Shadowrun6Combatant from "./Shadowrun6Combatant.js";
import Shadowrun6CombatTracker from "./Shadowrun6CombatTracker.js";
import statusEffects from "./statusEffects.js";
import SR6TokenHUD from "./SR6TokenHUD.js";
import SR6Token from "./placeables/SR6Token.js";
// import SR6ActiveEffectData from "./datamodels/active-effect-model.mjs";
import * as utils from "./util/helper.js";
import macros from "./util/macros.js";
import { migrateWorld } from "./util/Migrations.js";
import SR6SocketHandler from './util/SR6SocketHandler.js';
import releaseNotes from "../releasenotes/releasenotes.js";
import SR6Keybindings from './util/keybindings.mjs';

// Import Modules
import * as datamodels from "./datamodels/_module.mjs";
import * as documents from "./documents/_module.mjs";
import * as applications from "./applications/_module.mjs";
import * as rollTypes from "./dice/RollTypes.js";

/**
 * Init hook. Called from Foundry when initializing the world
 */
Hooks.once("init", async function () {
    /**
     * Change to true for developer mode
     */
    game.debug = false;

    console.log(`SR6E | Initializing Shadowrun 6 System`);
    if (game.debug) {
        CONFIG.debug.hooks = true;
        CONFIG.debug.dice = true;
        CONFIG.debug.tokens = true;
    }

    game.sr6 = {};
    game.sr6.config = CONFIG.SR6 = new SR6Config();
    game.sr6.datamodels = datamodels;
    game.sr6.documents = documents;
    game.sr6.applications = applications;
    game.sr6.utils = utils;
    game.sr6.rollTypes = rollTypes;
    game.sr6.macros = macros;
    game.sr6.roll = SR6Roll;
    game.sr6.sockets = new SR6SocketHandler();
    game.sr6.releaseNotes = releaseNotes;
    registerSystemSettings();
    defineHandlebarHelper();
    
    CONFIG.Combat.documentClass = Shadowrun6Combat;
    CONFIG.Combatant.documentClass = Shadowrun6Combatant;
    CONFIG.ui.combat = Shadowrun6CombatTracker;
    CONFIG.Dice.rolls = [SR6Roll];
    
    CONFIG.statusEffects = statusEffects.map(status => ({...status, _id: utils.staticId(status.id) }));
    if ( !game.settings.get(SYSTEM_NAME, "bleeding") ) {
        CONFIG.statusEffects = CONFIG.statusEffects.filter(function(effect) { return effect.id != "bleeding"; });
    }

    CONFIG.Token.hudClass = SR6TokenHUD;
    CONFIG.Token.objectClass = SR6Token;

    // Initialize socket handler
    game.sr6.sockets.registerSocketListeners();

    //	(CONFIG as any).compatibility.mode = 0;
    getData(game).initiative = "@initiative.physical.pool + (@initiative.physical.dicePool)d6";

    // Register custom PDF Sheet
    foundry.applications.apps.DocumentSheetConfig.registerSheet(
        CONFIG.JournalEntryPage.documentClass, 
        "shadowrun-de",
        applications.PDFSheet,
        { types: ["pdf"] }
    );

    /**
     * Actor configuration (Datamodel > Document > Sheet)
     * @see template.json (legacy)
     * @see datamodels.SR6SpriteActorData
     * @see documents.Shadowrun6Actor
     * @see sheets.Shadowrun6ActorSheetPC
     * @see sheets.Shadowrun6ActorSheetNPC
     * @see sheets.Shadowrun6ActorSheetVehicle
     */
    Object.assign(CONFIG.Actor.dataModels, {
        Player:  datamodels.SR6PlayerActorData,
        NPC:     datamodels.SR6NPCActorData,
        Critter: datamodels.SR6CritterActorData,
        Spirit:  datamodels.SR6SpiritActorData,
        Vehicle: datamodels.SR6VehicleActorData,
        sprite:  datamodels.SR6SpriteActorData
    });
    CONFIG.Actor.defaultType = "Player";
    CONFIG.Actor.documentClass = documents.Shadowrun6Actor;
    Actors.unregisterSheet("core", ActorSheet);
    Actors.registerSheet("shadowrun-de", applications.Shadowrun6ActorSheetPC, { types: ["Player"], makeDefault: true });
    Actors.registerSheet("shadowrun-de", applications.Shadowrun6ActorSheetNPC, { types: ["NPC", "Critter", "Spirit"], makeDefault: true });
    Actors.registerSheet("shadowrun-de", applications.Shadowrun6ActorSheetVehicle, { types: ["Vehicle"], makeDefault: true });
    Actors.registerSheet("shadowrun-de", applications.SR6SpriteActorSheet, { types: ["sprite"], makeDefault: true });

    /**
     * Item document configuration (Datamodel > Document > Sheet)
     * @see template.json (legacy)
     * @see documents.SR6Item
     * @see sheets.SR6ItemSheet
     */
    Object.assign(CONFIG.Item.dataModels, {
        adeptpower:      datamodels.SR6AdeptpowerItemData,
        complexform:     datamodels.SR6ComplexformItemData,
        contact:         datamodels.SR6ContactItemData,
        critterpower:    datamodels.SR6CritterpowerItemData,
        spritepower:     datamodels.SR6SpritepowerItemData,
        echo:            datamodels.SR6EchoItemData,
        focus:           datamodels.SR6FocusItemData,
        gear:            datamodels.SR6GearItemData,
        lifestyle:       datamodels.SR6LifestyleItemData,
        martialartstyle: datamodels.SR6MartialartstyleItemData,
        martialarttech:  datamodels.SR6MartialartechItemData,
        metamagic:       datamodels.SR6MetamagicItemData,
        quality:         datamodels.SR6QualityItemData,
        ritual:          datamodels.SR6RitualItemData,
        sin:             datamodels.SR6SinItemData,
        skill:           datamodels.SR6SkillItemData,
        software:        datamodels.SR6SoftwareItemData,
        spell:           datamodels.SR6SpellItemData,
        mod:             datamodels.SR6ModItemData
    });
    CONFIG.Item.defaultType = "gear";
    CONFIG.Item.documentClass = documents.SR6Item;
    Items.registerSheet("shadowrun-de", applications.SR6ItemSheet, {
        types: [
            "gear",
            "martialarttech",
            "martialartstyle",
            "quality",
            "skill",
            "spell",
            "adeptpower",
            "ritual",
            "metamagic",
            "focus",
            "echo",
            "complexform",
            "spritepower",
            "sin",
            "contact",
            "lifestyle",
            "critterpower",
            "software",
            "mod"
        ],
        makeDefault: true
    });

    CONFIG.ActiveEffect.dataModels.base = datamodels.SR6ActiveEffectData;
    DocumentSheetConfig.unregisterSheet(ActiveEffect, 'core', ActiveEffectConfig);
    DocumentSheetConfig.registerSheet(ActiveEffect, 'shadowrun-de', applications.SR6ActiveEffectConfig, { makeDefault: true });

    // Change Canvas Placeables Font to Shadowrun
    CONFIG.defaultFontFamily = 'Play';
    CONFIG.canvasTextStyle.fontFamily = 'Play';

    // Add Compendium search options
    CONFIG.Item.compendiumIndexFields = ["name", "type", "system.genesisID"];

    document.body.classList.add('foundry-modern');
    utils.loadI18nCss();

    // Add System specific keybindings
    SR6Keybindings.initialize();

    preloadHandlebarsTemplates();

    Hooks.once("diceSoNiceReady", (dice3d) => {
        dice3d.addSystem({ id: "SR6", name: "Shadowrun 6 - Eden", defaultValue: true }, true, "default");
        dice3d.addDicePreset({
            type: "d6",
            labels: [
                "1",
                "2",
                "3",
                "4",
                "systems/shadowrun-de/icons/SR6_D6_5_o.png",
                "systems/shadowrun-de/icons/SR6_D6_6_o.png"
            ],
            bumpMaps: [
                ,
                ,
                ,
                , 
                "systems/shadowrun-de/icons/SR6_D6_5_o_map.png",
                "systems/shadowrun-de/icons/SR6_D6_6_o_map.png"
            ],
            colorset: "SR6_dark",
            system: "SR6"
            , defaultValue: true
        });
        dice3d.addDicePreset({
            type: "dc",
            labels: ["systems/shadowrun-de/images/EdgeToken.webp", "systems/shadowrun-de/images/EdgeToken.webp"],
            bumpMaps: [,],
            colorset: "SR6_dark",
            system: "SR6"
            , defaultValue: true
        });
        dice3d.addColorset({
            name: "SR6_dark",
            description: "- Shadowrun 6 -",
            category: "Colors",
            foreground: "#660265",
            background: "#000000",
            outline: "#2e2b2e",
            texture: "none",
            edge: "#470146",
            material: "metal",
            font: "Arial Black",
            fontScale: {
                d6: 1.1,
                df: 2.5
            },
            visibility: "visible",
            system: "SR6"
            , defaultValue: true
        }, "default");

    });
    /*
     * Change default icon
     */
    function onPreCreateItem(itemDoc, options, userId) {
        let item = getActorData(itemDoc);
        let system = getSystemData(itemDoc);
        console.log("SR6E | onCreateItem  " + item.type);
        if (item.img == "systems/shadowrun-de/icons/compendium/gear/tech_bag.svg" && CONFIG.SR6.icons[item.type]) {
            item.img = CONFIG.SR6.icons[item.type].default;
            item.updateSource({ ["img"]: item.img });
        }
        // If it is a compendium item, copy over text description
        let key = item.type + "." + system.genesisID;
        console.log("SR6E | Item with genesisID - check for " + key);
        if (!game.i18n.localize(key + "name").startsWith(key)) {
            system.description = game.i18n.localize(key + ".desc");
            item.name = game.i18n.localize(key + ".name");
            item.updateSource({ ["description"]: system.description });
        }
        console.log("SR6E | onCreateItem: " + item.img);
    }
    Hooks.on("preCreateItem", (doc, options, userId) => onPreCreateItem(doc, options, userId));
    
    /*
     * Change chat dice icon
     * Called via var $(document).on("click", "sr6-dice-roll-v12"
     */
    Hooks.on("renderChatLog", (doc, options, userId) => {
        // Replace D20 chat icon with D6's
        if (game.release.generation < 13) {
            let chatDiceIcon = document.querySelector(".chat-control-icon .fas.fa-dice-d20");
            if (chatDiceIcon) {
                chatDiceIcon.setAttribute("class", "fas fa-dice sr6-dice-roll-v12");
                chatDiceIcon.setAttribute("title", game.i18n.localize("shadowrun6.roll.create"));
            }
        }
    });
    Hooks.on("getSceneControlButtons", (controls) => {    
        if (game.release.generation >= 13) {
            const shadowrunRoll = {
                name: "shadowrunRoll",
                order: 99,
                title: "shadowrun6.roll.create",
                icon: "fa-solid fa-dice sr6-icon-roll",
                onClick: () => _onClickDiceRoll(),
                button: true
            };
            controls.tokens.tools.shadowrunRoll = shadowrunRoll;
            
            if (game.user.isGM) {
                const shadowrunResetEdge = {
                    name: "shadowrunResetEdge",
                    order: 99,
                    title: "shadowrun6.resetEdge",
                    icon: "sr6-icon-edge",
                    onClick: () => game.sr6.utils.resetEdge(),
                    button: true
                };
                controls.tokens.tools.shadowrunResetEdge = shadowrunResetEdge;
            }
        }
    });

    Hooks.on("ready", async () => {
        redefineHandlebarLog();
        
        // Reassign CONFIG so translations are run
        game.sr6.config = CONFIG.SR6 = new SR6Config();   
        if ( game.settings.get(SYSTEM_NAME, "hackSlashMatrix") ) {
            CONFIG.SR6.MATRIX_ACTIONS = {...CONFIG.SR6.MATRIX_ACTIONS, ...CONFIG.SR6.MATRIX_ACTIONS_HS};
        }
        CONFIG.SR6.DATA_ENTRY = game.settings.get(game.system.id, "dataEntry") || false;
        await migrateWorld();
        await pdfJournalInit();
        game.sr6.releaseNotes();

        
        // Add Compendium search options
        for (const pack of game.packs.filter(p => p.documentName === "Item")) {
            await pack.getIndex()
        }

        // Render a dice roll dialog on click for V12
        $(document).on("click", ".sr6-dice-roll-v12", (e) => {
            _onClickDiceRoll();
        });
        // make Release Note link in sidebar clickable
        const releaseNoteLink = document.querySelector("#system-releasenotes");
        releaseNoteLink.addEventListener("click", (e) => {
            game.sr6.releaseNotes({force: true});
        });

    });
    Hooks.on("renderShadowrun6ActorSheetPC", (doc, options, userId) => {
        console.log("SR6E | renderShadowrun6ActorSheetPC hook called", doc);
    });
    Hooks.on("renderShadowrun6ActorSheetVehicle", (app, html, data) => {
        //    console.log("SR6E | renderShadowrun6ActorSheetVehicle hook called");
        _onRenderVehicleSheet(app, html, data);
    });
    Hooks.on("renderSR6ItemSheet", (app, html, data) => {
        console.log("SR6E | renderSR6ItemSheet hook called");
        _onRenderItemSheet(app, html, data);
    });
    /**
     * Allowing items and effects to drop on Tokens on the canvas
     */
    Hooks.on("dropCanvasData", (canvas, data) => {
        console.log("SR6E | dropCanvasData hook called", canvas, data);
        if (!(data.type === "Item" || data.type === "ActiveEffect")) {
            return true;
        }

        // TODO FoundryVTT - Will be a core feature in V14
        const dropTarget = [...canvas.tokens.placeables]
            .sort((a, b) => b.document.sort - a.document.sort)
            .sort((a, b) => b.document.elevation - a.document.elevation)
            .find((t) => t.visible && t.bounds.contains(data.x, data.y));

        const actor = dropTarget?.actor;
        if (actor) {
            const dataTransfer = new DataTransfer();
            dataTransfer.setData("text/plain", JSON.stringify(data));
            const event = new DragEvent("drop", { altKey: game.keyboard.isModifierActive("Alt"), dataTransfer });
            actor.sheet._onDrop(event);
            return false; // Prevent modules from doing anything further
        }

        return true;
    });
    /*
     * Something has been dropped on the HotBar
     */
    Hooks.on("hotbarDrop", async (bar, droppedData, slot) => {
        console.log("SR6E | DROP to Hotbar", droppedData);
        let macroData = {
            name: "",
            command: "",
            type: "script",
            img: "systems/shadowrun-de/icons/compendium/default/Default_Dice.svg"
        };
        const { type, ...data } = droppedData;

        if (type === 'Item') {
            // Do nothing, FoundryVTT Native
            return;
        } if (type === 'Roll') {
            if (data.action === 'roll') {   // SheetV2
                const item = await fromUuidSync(data.uuid);
                macroData.name = game.i18n.format("SR6.title.useItem", { name: item.name });
                macroData.img = item.img;
            } 
            else if (data.classList.includes('weapon-roll') || data.classList.includes('spell-roll') || data.classList.includes('ritual-roll') || data.classList.includes('complexform-roll') || data.classList.includes('spritepower-roll')) {
                const item = await fromUuidSync(data.uuid);
                macroData.name = item.name;
                if (data.skill==="firearms") {
                    macroData.img = "systems/shadowrun-de/icons/compendium/weapons/air_pistol.svg";
                } else if (data.skill==="close_combat") {
                    macroData.img = "systems/shadowrun-de/icons/compendium/weapons/unarmed.svg"
                } else if (data.classList.includes('spell-roll')) {
                    macroData.img = "systems/shadowrun-de/icons/compendium/default/acid.svg"
                } else if (data.classList.includes('ritual-roll')) {
                    macroData.img = "systems/shadowrun-de/icons/compendium/programs/nervescrub.svg"
                } else if (data.classList.includes('complexform-roll')) {
                    macroData.img = "systems/shadowrun-de/icons/compendium/the_12_days_of_cybermas/sycust_fleshweave.svg"
                } else if (data.classList.includes('spritepower-roll')) {
                    macroData.img = "systems/shadowrun-de/icons/compendium/programs/nervescrub.svg"
                }
                else {
                    // special weapons
                    macroData.img = "systems/shadowrun-de/icons/compendium/cyberweapons/wolvers.svg";
                }
            } 
            else {
                macroData.name = game.sr6.utils.rollText(data.classList, data.rollId??data.skill??data.matrixId, data.skillspec);
            }
            macroData.command = `game.sr6.macros.simpletest(${JSON.stringify(data)})`;
        } else {
            console.warn("SR6E | Draggable object not supported to convert onto the hotbar", droppedData);
        }

        if (macroData.command != "" && macroData.name != "") {
            let macro = await Macro.create(macroData, { displaySheet: false });
            game.user.assignHotbarMacro(macro, slot);
        }
    });
    Hooks.on("preCreateMacro", (macro, data, options, user) => {
        if (macro.img === "icons/svg/book.svg") {
            macro.updateSource({ img: "systems/shadowrun-de/icons/compendium/default/Default_Container.svg" });
        }
    });

    Hooks.on("renderChatMessage", function (app, html, data) {
        console.log("SR6E | ENTER renderChatMessage");
        
        html.on("click", ".chat-edge", (ev) => {
            let event = ev;
            event.preventDefault();
            let roll = $(event.currentTarget);
            let tip = roll.find(".chat-edge-collapsible");
            if (!tip.is(":visible")) {
                tip.slideDown(200);
            }
            else {
                tip.slideUp(200);
            }
        });
        html.find(".rollable").click((event) => {
            //TODO allow multiple targets
            console.log("SR6E | ENTER renderChatMessage.rollable.click -> event.currentTarget = ", event.currentTarget);
            const button = event.currentTarget;
            const dataset = button.dataset;
            const actorUuid = dataset.actorUuid;
            let actorId = dataset["actorid"] ? dataset["actorid"] : dataset["targetActor"];
            let sceneId = dataset["sceneid"];
            let tokenId = dataset["targetid"] ? dataset["targetid"] : dataset["targetToken"];
            let actor;
            let token;

            if (actorUuid) {
                actor = game.actors.get(actorUuid);
            } else if (actorId && sceneId && tokenId) {
                console.log("SR6E | Target is a token");
                // If scene and token ID is given, resolve token
                let scene = game.scenes.get(sceneId);
                if (scene) {
                    token = scene.tokens.get(tokenId);
                    console.log("SR6E | Token ", token);
                    actor = token.actor;
                }
            } else if (actorId) {
                console.log("SR6E | Target isn't a token but actorId is known");
                actor = game.actors.get(actorId);
            } else if (game.user.targets.size) {
                console.log("SR6E | No original target, selecting current user target");
                //Use current target token
                game.user.targets.forEach((target) => {
                    token = target;
                });
                actor = (token?.actor.isOwner) ? token?.actor : null;
            } else {
                console.log("SR6E | No targets, selecting current token selected");
                //Use currently selected token
                canvas.tokens.controlled.forEach((target) => {
                    token = target;
                });
                actor = (token?.actor.isOwner) ? token?.actor : null;   
            }

            if (!actor) {
                console.log("SR6E | No owned target found - use player's character if possible");
                actor = game.user.character;
            }
            if (actor) {
                console.log("SR6E | Actor targetted by roll", actor);
                if (!actor.isOwner) {
                    console.log("SR6E | Current user not owner of target ", actor.name);
                    ui.notifications.warn("shadowrun6.ui.notifications.You_are_not_owner_of_the_target", { localize: true });
                    return;
                }
            } else {
                console.log("SR6E | No target actor found");
                ui.notifications.warn("shadowrun6.ui.notifications.Target_or_select_a_token_before_rolling", { localize: true });
                return;
            }
            const rollType = dataset.rollType;
            console.log("SR6E | Clicked on rollable : " + rollType);
            console.log("SR6E | dataset : ", dataset);
            const threshold = parseInt(dataset.threshold);
            const damage = dataset.damage ? parseInt(dataset.damage) : 0;
            console.log("SR6E | Target actor ", actor);
            console.log("SR6E | Target actor ", actor.name);
            let dialogConfig;
            switch (rollType) {
                case RollType.ContinueExtendedTest:
                    console.log("SR6E | Processing Rollable Chatbutton for Continueing an open ended Extended Test");
                    const extendedRoll = new PreparedRoll();
                    extendedRoll.pool = parseInt(dataset.pool) - 1;
                    extendedRoll.rollType = RollType.ContinueExtendedTest;
                    extendedRoll.extendedTotal = parseInt(dataset.hits);
                    extendedRoll.extended = true;
                    extendedRoll.actionText = dataset.actionText;
                    extendedRoll.checkText = game.i18n.format("shadowrun6.dice.extended.continue");
                    extendedRoll.allowBuyHits = false;
                    extendedRoll.threshold = 0;
                    extendedRoll.interval = parseInt(dataset.interval);
                    extendedRoll.intervalScale = dataset.intervalScale;
                    extendedRoll.timePassed = parseInt(dataset.timePassed);
                    dialogConfig = {
                        useWoundModifier: false,
                        useSustainedSpellModifier: false
                    };
                    actor.rollCommonCheck(extendedRoll, dialogConfig);

                    break;
                case RollType.Legwork:
                    console.log("SR6E | Processing Rollable Chatbutton for Legwork");
                    const loyaltyRoll = new PreparedRoll();
                    loyaltyRoll.pool = actor.system.skills.influence.pool + parseInt(dataset.loyalty);
                    loyaltyRoll.rollType = RollType.Legwork;
                    loyaltyRoll.actionText = game.sr6.utils.rollText('', 'legwork');
                    loyaltyRoll.allowBuyHits = true;
                    loyaltyRoll.threshold = 1;
                    loyaltyRoll.checkText = game.i18n.format("shadowrun6.legwork.loyalty_description", { name: dataset.contact });
                    loyaltyRoll.legwork = { legworkResult: parseInt(dataset.legworkResult) };
                    dialogConfig = {
                        useWoundModifier: false,
                        useSustainedSpellModifier: false
                    };
                    actor.rollCommonCheck(loyaltyRoll, dialogConfig);
                    break;
                case RollType.Defense:
                    /* Avoid being hit/influenced */
                    console.log("SR6E | TODO: call rollDefense with threshold " + threshold);
                    if (actor) {
                        const defendWith = dataset.defendWith;
                        const monitor = dataset.monitor;
                        const itemUuid = dataset.itemUuid;
                        actor.rollDefense(defendWith, threshold, damage, monitor, itemUuid);
                    }
                    break;
                case RollType.Soak:
                    /* Resist incoming netto damage */
                    //TODO call rollSoak with threshold ?
                    // console.log("SR6E | TODO: call rollSoak with threshold " + threshold + " on monitor " + dataset.soakWith);
                    if (actor) {
                        let soak = dataset.soak;
                        actor.rollSoak(soak, damage);
                    }
                    break;
                case RollType.Damage:
                    /* Do not roll - just apply damage */
                    const monitor = dataset.monitor;
                    const chatMessageId = event.target.closest(".chat-message").dataset.messageId;
                    const chatMessage = game.messages.get(chatMessageId);
                    const roll = chatMessage.rolls[0];

                    if (button.classList.contains('damageApplied') || button.classList.contains('revertDamageCheck')) {
                        // Damage was applied in the past, reverting back
                        button.blur();
                        if (button.classList.contains('revertDamageCheck')) {
                            button.classList.remove("revertDamageCheck");
                            if (button.classList.contains('damageConvertedStun')) {
                                roll.finished.damageApplied2 = false;
                            } else {
                                roll.finished.damageApplied = false;
                            }
                            roll.finished.damageApplied = false;
                            chatMessage.update({ 'rolls': [roll] });
                            actor.applyDamage( monitor, damage*-1 );
                        } else {
                            button.classList.remove("damageApplied");
                            button.classList.add("revertDamageCheck");
                        }

                    } else {
                        button.blur();
                        if (button.classList.contains('damageConvertedStun')) {
                            roll.finished.damageApplied2 = true;
                        } else {
                            roll.finished.damageApplied = true;
                        }
                        chatMessage.update({ 'rolls': [roll] });
                        actor.applyDamage( monitor, damage );
                    }
                    console.log("SR6E | Applied "+monitor+" "+damage+" damage to", actor);
                    break;
            }
            /*
            if (rollType === RollType.Defense) {
                    const actor = (game as Game).actors!.get(targetId);
                console.log("SR6E | Target actor ",actor);
                    console.log("SR6E | TODO: call rollDefense with threshold "+threshold);
                    if (actor) {
                        let defendWith : Defense = (dataset.defendWith! as Defense);
                        let damage     : number  = (dataset.damage)?parseInt(dataset.damage):0;
                        (actor as Shadowrun6Actor).rollDefense(defendWith, threshold, damage);
                    }
            }
                */
        });
        html.on("click", ".chat-edge", (event) => {
            event.preventDefault();
            let roll = $(event.currentTarget);
            let tip = roll.find(".chat-edge-collapsible");
            if (!tip.is(":visible")) {
                tip.slideDown(200);
            }
            else {
                tip.slideUp(200);
            }
        });
        // Collapsible Chat Dice tooltip
        html.on("click", ".chat-edge-post", (event) => {
            console.log("SR6E | Chat roll tooltip clicked for Edge");
            event.preventDefault();
            let roll = $(event.currentTarget.parentElement);
            let tip = roll.find(".chat-edge-post-collapsible");
            if (!tip.is(":visible")) {
                const chatMsg = game.messages.get( event.currentTarget.closest("[data-message-id]").dataset.messageId );
                const chatSpeakerActor = game.sr6.utils.getActor(chatMsg.speaker.actor, chatMsg.speaker.scene, chatMsg.speaker.token);
                let selectedActor;
                
                // Don't do edge boosts when its an extended test (complicated implementation & only for riggers)
                if (!chatMsg.rolls[0].configured.extended) {
                    selectedActor = game.sr6.utils.getSelectedActor();
                }
                chatMsg.edgeTooltipUsedBy = selectedActor?.uuid;
                
                if (chatSpeakerActor?.uuid === selectedActor?.uuid && selectedActor?.isOwner) {
                    if (!chatMsg.rolls[0]?.finished.edge_use)
                        tip.addClass("edgeable edge-own-roll");
                } else if (chatSpeakerActor?.uuid !== selectedActor?.uuid && selectedActor?.isOwner) {
                    if (!chatMsg.rolls[0]?.finished.edge_use_opponent)
                        tip.addClass("edgeable edge-opponent-roll");
                }
                tip.slideDown(200);
            }
            else {
                const chatMsg = game.messages.get( event.currentTarget.closest("[data-message-id]").dataset.messageId );
                chatMsg.rolls[0]?.finished?.results?.forEach((die) => {
                    die.selectedDie = false;
                });
                tip.find('li').removeClass("selectedDie");
                tip.removeClass("edgeable edge-own-roll edge-opponent-roll");
                tip.slideUp(200);
            }
        });
        html.on("click", ".chat-spell", (event) => {
            console.log("SR6E | chat-spell");
            event.preventDefault();
            let roll = $(event.currentTarget);
            let tip = roll.find(".chat-spell-collapsible");
            if (!tip.is(":visible")) {
                tip.slideDown(200);
            }
            else {
                tip.slideUp(200);
            }
        });
        
        // Implement selecting dice in chat
        html.on("click", ".edgeable.edge-own-roll .die.miss, .edgeable.edge-opponent-roll .die.hit", (event) => {
            console.log("SR6E | Dice clicked for Edge boost");
            event.preventDefault();
            const selected = event.currentTarget.classList.toggle("selectedDie");
            const chatMsg = game.messages.get( event.currentTarget.closest("[data-message-id]").dataset.messageId );
            const roll = chatMsg.rolls[0];
            const die = roll.finished.results[ event.currentTarget.dataset.index ];
            die.selectedDie = selected; // true or false dependent on .toggle
        });
        // Implement Edge Boost button in chat
        html.on("click", ".dice-tooltip.edgeable .edgePerform", (event) => {
            console.log("SR6E | Edge Boost button clicked in chat");
            event.preventDefault();
            const edgeType = event.currentTarget.form["edgeBoostSelect"].selectedOptions[0].value;
            const chatMsg = game.messages.get( event.currentTarget.closest("[data-message-id]").dataset.messageId );
            const roll = chatMsg.rolls[0];
            const selectedActor = game.sr6.utils.getSelectedActor();

            if (chatMsg?.edgeTooltipUsedBy === selectedActor?.uuid) {
                EdgeRoll.performPostEdgeBoost(chatMsg.id, edgeType);
            } else {
                html.find(".chat-edge-post").trigger( "click" );
                const msg = game.i18n.format("shadowrun6.ui.notifications.reopen_dice_tooltip");
                ui.notifications.warn(msg);
            }
        });
        
        console.log("SR6E | LEAVE renderChatMessage");
    });
    /**
     * If a player actor is created, change default token settings
     */
    Hooks.on("preCreateActor", (actor, createData, options, userId) => {
        actor.prototypeToken.updateSource({ 
            'sight.enabled': true,
            displayName: CONST.TOKEN_DISPLAY_MODES.OWNER_HOVER,
            displayBars: CONST.TOKEN_DISPLAY_MODES.NONE
        });
        if (actor.type === "Player") {
            actor.prototypeToken.updateSource({
                actorLink: true,
                displayName: CONST.TOKEN_DISPLAY_MODES.HOVER,
                disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
            });
        } else if (actor.type === "NPC") {
            actor.prototypeToken.updateSource({
                prependAdjective: true
            });
        } else if (actor.type === "Vehicle") {
            actor.prototypeToken.updateSource({
                disposition: CONST.TOKEN_DISPOSITIONS.NEUTRAL,
                width: 2,
                height: 3
            });
        }

    });
    /**
     * If a token is created, change shape to circle Ellipse
     */
    Hooks.on("preCreateToken", (token, createData, options, userId) => {
        token.updateSource({ 
            shape: CONST.TOKEN_SHAPES.ELLIPSE_1
        });
    });
    Hooks.once("dragRuler.ready", (SpeedProvider) => {
        class FictionalGameSystemSpeedProvider extends SpeedProvider {
            get colors() {
                return [
                    { id: "walk", default: 0x00ff00, name: "shadowrun-de.speeds.walk" },
                    { id: "dash", default: 0xffff00, name: "shadowrun-de.speeds.dash" },
                    { id: "run", default: 0xff8000, name: "shadowrun-de.speeds.run" }
                ];
            }
            getRanges(token) {
                const baseSpeed = 5; //token.actor.system.derived.speed not implemented
                // A character can always walk it's base speed and dash twice it's base speed
                const ranges = [
                    { range: 10, color: "walk" },
                    { range: 15, color: "dash" }
                ];
                // Characters that aren't wearing armor are allowed to run with three times their speed
                // TODO isWearingArmor is not implemented
                // if (!token.actor.isWearingArmor) {
                //     ranges.push({ range: baseSpeed * 3, color: "dash" });
                // }
                return ranges;
            }
        }
        // @ts-ignore
        dragRuler.registerSystem("shadowrun-de", FictionalGameSystemSpeedProvider);
    });
    Hooks.on("renderGamePause", async function (GamePause, html, options, renderOptions) {
        document.querySelector('#pause')?.classList.add('fa-beat-fade');
        document.querySelector('#pause figcaption')?.classList.add('glitch');
    });
    Hooks.on("preCreateScene", (scene, createData, options, userId) => {
        // Default Grid Opacity
        scene.updateSource({ 'grid.alpha': 0 })
    });

    Hooks.on("renderSettings", async (_app, $html) => {
        const html = $html instanceof jQuery ? $html[0] : $html;
        // Additional system information resources
        const systemRow = html.querySelector(".settings-sidebar .system");
        const systemInfo = systemRow?.cloneNode(false);
        if (!(systemInfo instanceof HTMLElement)) {
            console.error("SR6 | Unexpected error attaching system information to settings sidebar");
        }

        systemInfo.classList.remove("system");
        systemInfo.classList.add("system-links");
        const links = [
            {
                url: game.system.authors.values().next().value.url,
                label: "SYSTEM.Sidebar.readme"
            },
            {
                url: game.system.bugs,
                label: "SYSTEM.Sidebar.bugs"
            },
            {
                id: "system-releasenotes",
                url: "javascript:void(0);",
                label: "SYSTEM.Sidebar.release_notes"
            }
        ].map((data) => {
            const anchor = document.createElement("a");
            anchor.innerText = game.i18n.localize(data.label);
            anchor.href = data.url ?? "";
            if (data.id) 
                anchor.id = data.id;
            else
                anchor.target = "_blank";
            return anchor;
        });
        systemInfo.append(...links);
        systemRow?.after(systemInfo);
        systemInfo?.after(document.createElement("hr"));
    });

});

/**
 * Init hook. Called from Foundry when i18n localization is ready
 */
Hooks.once("i18nInit", async function () {
    console.log(`SR6E | Continue to initialize Shadowrun 6 System post i18nInit`);

    registerSystemSettingsPosti18n();
});

/**
 * ####################################################################################
 * Legacy Helper functions
 */

$.fn.closestData = function (dataName, defaultValue = "") {
    let value = this.closest(`[data-${dataName}]`)?.data(dataName);
    return value ? value : defaultValue;
};
/* -------------------------------------------- */
function _onRenderVehicleSheet(application, html, data) {
    console.log("SR6E | _onRenderVehicleSheet for " + data.actor);
}
function _onRenderItemSheet(sheet, html, item) {
    console.log("SR6E | _onRenderItemSheet for ", item);
}
function _onClickDiceRoll (ev) {
    console.log("SR6E | sr6-dice-roll clicked  ");
    let roll = new PreparedRoll();
    roll.pool = 0;
    roll.speaker = ChatMessage.getSpeaker({ actor: this });
    roll.rollType = RollType.Common;
    doRoll(roll);
}
function getData(obj) {
    if (game.release.generation >= 10)
        return obj;
    return obj.data;
}
function getRoll(obj) {
    if (game.release.generation >= 10)
        return obj.rolls[0];
    return obj.roll;
}
function getSystemData(obj) {
    if (game.release.generation >= 10)
        return obj.system;
    return obj.data.data;
}
function getActorData(obj) {
    if (game.release.generation >= 10)
        return obj;
    return obj.data;
}