import { SYSTEM } from './module/config/system.mjs';
import * as applications from './module/applications/_module.mjs';
import * as data from './module/data/_module.mjs';
import * as models from './module/data/_module.mjs';
import * as documents from './module/documents/_module.mjs';
import { macros } from './module/_module.mjs';
import * as collections from './module/documents/collections/_module.mjs';
import * as dice from './module/dice/_module.mjs';
import * as fields from './module/data/fields/_module.mjs';
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
import { BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll } from './module/dice/_module.mjs';
import {
    handlebarsRegistration,
    runMigrations,
    settingsRegistration,
    socketRegistration
} from './module/systemRegistration/_module.mjs';
import { placeables, DhTokenLayer } from './module/canvas/_module.mjs';
import './node_modules/@yaireo/tagify/dist/tagify.css';
import TokenManager from './module/documents/tokenManager.mjs';

CONFIG.DH = SYSTEM;
CONFIG.TextEditor.enrichers.push(...enricherConfig);

CONFIG.Dice.rolls = [BaseRoll, DHRoll, DualityRoll, D20Roll, DamageRoll, FateRoll];
CONFIG.Dice.daggerheart = {
    DHRoll: DHRoll,
    DualityRoll: DualityRoll,
    D20Roll: D20Roll,
    DamageRoll: DamageRoll,
    FateRoll: FateRoll
};

CONFIG.RegionBehavior.dataModels = {
    ...CONFIG.RegionBehavior.dataModels,
    ...data.regionBehaviors
};

Object.assign(CONFIG.Dice.termTypes, dice.diceTypes);

CONFIG.Actor.documentClass = documents.DhpActor;
CONFIG.Actor.dataModels = models.actors.config;
CONFIG.Actor.collection = collections.DhActorCollection;

CONFIG.Item.documentClass = documents.DHItem;
CONFIG.Item.dataModels = models.items.config;

CONFIG.ActiveEffect.documentClass = documents.DhActiveEffect;
CONFIG.ActiveEffect.dataModels = models.activeEffects.config;
CONFIG.ActiveEffect.changeTypes = { ...CONFIG.ActiveEffect.changeTypes, ...models.activeEffects.changeEffects };

CONFIG.Combat.documentClass = documents.DhpCombat;
CONFIG.Combat.dataModels = { base: models.DhCombat };
CONFIG.Combatant.documentClass = documents.DHCombatant;
CONFIG.Combatant.dataModels = { base: models.DhCombatant };

CONFIG.ChatMessage.dataModels = models.chatMessages.config;
CONFIG.ChatMessage.documentClass = documents.DhChatMessage;
CONFIG.ChatMessage.template = 'systems/daggerheart/templates/ui/chat/chat-message.hbs';

CONFIG.Canvas.rulerClass = placeables.DhRuler;
CONFIG.Canvas.layers.regions.layerClass = placeables.DhRegionLayer;
CONFIG.Canvas.layers.tokens.layerClass = DhTokenLayer;

CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;

CONFIG.Region.objectClass = placeables.DhRegion;

CONFIG.RollTable.documentClass = documents.DhRollTable;
CONFIG.RollTable.resultTemplate = 'systems/daggerheart/templates/ui/chat/table-result.hbs';

CONFIG.Scene.documentClass = documents.DhScene;

CONFIG.Token.documentClass = documents.DhToken;
CONFIG.Token.prototypeSheetClass = applications.sheetConfigs.DhPrototypeTokenConfig;
CONFIG.Token.objectClass = placeables.DhTokenPlaceable;
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;

CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.nav = applications.ui.DhSceneNavigation;
CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.effectsDisplay = applications.ui.DhEffectsDisplay;
CONFIG.ui.hotbar = applications.ui.DhHotbar;
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
CONFIG.ui.actors = applications.sidebar.DhActorDirectory;
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
CONFIG.ui.resources = applications.ui.DhFearTracker;
CONFIG.ui.countdowns = applications.ui.DhCountdowns;
CONFIG.ux.ContextMenu = applications.ux.DHContextMenu;
CONFIG.ux.TooltipManager = documents.DhTooltipManager;
CONFIG.ux.TokenManager = new TokenManager();
CONFIG.debug.triggers = false;

Hooks.once('init', () => {
    game.system.api = {
        applications,
        data,
        models,
        documents,
        macros,
        dice,
        fields
    };

    game.system.registeredTriggers = new game.system.api.data.RegisteredTriggers();

    const { DocumentSheetConfig } = foundry.applications.apps;
    DocumentSheetConfig.unregisterSheet(TokenDocument, 'core', foundry.applications.sheets.TokenConfig);
    DocumentSheetConfig.registerSheet(TokenDocument, SYSTEM.id, applications.sheetConfigs.DhTokenConfig, {
        makeDefault: true
    });

    const sheetLabel = typePath => () =>
        game.i18n.format('DAGGERHEART.GENERAL.typeSheet', {
            type: game.i18n.localize(typePath)
        });

    const { Items, Actors, RollTables } = foundry.documents.collections;
    Items.unregisterSheet('core', foundry.applications.sheets.ItemSheetV2);
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Ancestry, {
        types: ['ancestry'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.ancestry')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Community, {
        types: ['community'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.community')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Class, {
        types: ['class'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.class')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Subclass, {
        types: ['subclass'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.subclass')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Feature, {
        types: ['feature'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.feature')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.DomainCard, {
        types: ['domainCard'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.domainCard')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Loot, {
        types: ['loot'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.loot')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Consumable, {
        types: ['consumable'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.consumable')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Weapon, {
        types: ['weapon'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.weapon')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Armor, {
        types: ['armor'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.armor')
    });
    Items.registerSheet(SYSTEM.id, applications.sheets.items.Beastform, {
        types: ['beastform'],
        makeDefault: true,
        label: sheetLabel('TYPES.Item.beastform')
    });

    Actors.unregisterSheet('core', foundry.applications.sheets.ActorSheetV2);
    Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Character, {
        types: ['character'],
        makeDefault: true,
        label: sheetLabel('TYPES.Actor.character')
    });
    Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Companion, {
        types: ['companion'],
        makeDefault: true,
        label: sheetLabel('TYPES.Actor.companion')
    });
    Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Adversary, {
        types: ['adversary'],
        makeDefault: true,
        label: sheetLabel('TYPES.Actor.adversary')
    });
    Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Environment, {
        types: ['environment'],
        makeDefault: true,
        label: sheetLabel('TYPES.Actor.environment')
    });
    Actors.registerSheet(SYSTEM.id, applications.sheets.actors.Party, {
        types: ['party'],
        makeDefault: true,
        label: sheetLabel('TYPES.Actor.party')
    });

    RollTables.unregisterSheet('core', foundry.applications.sheets.RollTableSheet);
    RollTables.registerSheet(SYSTEM.id, applications.sheets.rollTables.RollTableSheet, {
        types: ['base'],
        makeDefault: true
    });

    DocumentSheetConfig.unregisterSheet(
        CONFIG.ActiveEffect.documentClass,
        'core',
        foundry.applications.sheets.ActiveEffectConfig
    );
    DocumentSheetConfig.registerSheet(
        CONFIG.ActiveEffect.documentClass,
        SYSTEM.id,
        applications.sheetConfigs.ActiveEffectConfig,
        {
            types: ['base', 'beastform', 'horde'],
            makeDefault: true,
            label: sheetLabel('DOCUMENT.ActiveEffect')
        }
    );

    game.socket.on(`system.${SYSTEM.id}`, socketRegistration.handleSocketEvent);

    // Make Compendium Dialog resizable
    foundry.applications.sidebar.apps.Compendium.DEFAULT_OPTIONS.window.resizable = true;

    DocumentSheetConfig.unregisterSheet(foundry.documents.Scene, 'core', foundry.applications.sheets.SceneConfig);
    DocumentSheetConfig.registerSheet(foundry.documents.Scene, SYSTEM.id, applications.scene.DhSceneConfigSettings, {
        makeDefault: true,
        label: sheetLabel('DOCUMENT.Scene')
    });

    settingsRegistration.registerDHSettings();
    RegisterHandlebarsHelpers.registerHelpers();

    return handlebarsRegistration();
});

Hooks.on('i18nInit', () => {
    // Setup homebrew resources
    game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).refreshConfig();
});

Hooks.on('setup', () => {
    CONFIG.statusEffects = [
        ...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
        ...Object.values(SYSTEM.GENERAL.conditions()).map(x => ({
            ...x,
            name: game.i18n.localize(x.name),
            systemEffect: true
        }))
    ];

    const damageThresholds = ['damageThresholds.major', 'damageThresholds.severe'];
    const traits = Object.keys(game.system.api.data.actors.DhCharacter.schema.fields.traits.fields).map(
        trait => `traits.${trait}.value`
    );
    const resistance = Object.values(game.system.api.data.actors.DhCharacter.schema.fields.resistance.fields).flatMap(
        type => Object.keys(type.fields).map(x => `resistance.${type.name}.${x}`)
    );
    const actorCommon = {
        bar: ['resources.stress'],
        value: [...resistance, 'advantageSources', 'disadvantageSources']
    };
    CONFIG.Actor.trackableAttributes = {
        character: {
            bar: [...actorCommon.bar, 'resources.hitPoints', 'resources.hope'],
            value: [
                ...actorCommon.value,
                ...traits,
                ...damageThresholds,
                'proficiency',
                'evasion',
                'scars',
                'levelData.level.current'
            ]
        },
        adversary: {
            bar: [...actorCommon.bar, 'resources.hitPoints'],
            value: [...actorCommon.value, ...damageThresholds, 'criticalThreshold', 'difficulty']
        },
        companion: {
            bar: [...actorCommon.bar],
            value: [...actorCommon.value, 'evasion', 'levelData.level.current']
        }
    };
});

Hooks.on('ready', async () => {
    const appearanceSettings = game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.appearance);
    ui.resources = new CONFIG.ui.resources();
    if (appearanceSettings.displayFear !== 'hide') ui.resources.render({ force: true });

    if (appearanceSettings.displayCountdownUI) {
        ui.countdowns = new CONFIG.ui.countdowns();
        ui.countdowns.render({ force: true });
    }

    ui.effectsDisplay = new CONFIG.ui.effectsDisplay();
    ui.effectsDisplay.render({ force: true });

    if (!(ui.compendiumBrowser instanceof applications.ui.ItemBrowser))
        ui.compendiumBrowser = new applications.ui.ItemBrowser();

    socketRegistration.registerSocketHooks();
    socketRegistration.registerUserQueries();

    if (!game.user.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage)) {
        const welcomeMessage = await foundry.utils.fromUuid(CONFIG.DH.GENERAL.compendiumJournals.welcome);
        if (welcomeMessage) {
            welcomeMessage.sheet.render({ force: true });
            game.user.setFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.userFlags.welcomeMessage, true);
        }
    }

    runMigrations();
});

Hooks.once('dicesoniceready', () => {});

Hooks.on('renderChatMessageHTML', (document, element) => {
    enricherRenderSetup(element);
    const cssClass = document.flags?.daggerheart?.cssClass;
    if (cssClass) cssClass.split(' ').forEach(cls => element.classList.add(cls));
});

Hooks.on('renderJournalEntryPageProseMirrorSheet', (_, element) => {
    enricherRenderSetup(element);
});

Hooks.on('renderHandlebarsApplication', (_, element) => {
    enricherRenderSetup(element);
});

Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
    if (data.openForAllPlayers && data.partyId) {
        const party = game.actors.get(data.partyId);
        if (!party) return;

        const TagTeamDialog = game.system.api.applications.dialogs.TagTeamDialog;
        const dialog = foundry.applications.instances.get(`TagTeamDialog-${party.id}`) ?? new TagTeamDialog(party);
        dialog.tabGroups.application = 'tagTeamRoll';
        await dialog.render({ force: true });
    }
});

Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => {
    if (data.openForAllPlayers && data.partyId) {
        const party = game.actors.get(data.partyId);
        if (!party) return;

        const GroupRollDialog = game.system.api.applications.dialogs.GroupRollDialog;
        const dialog = foundry.applications.instances.get(`GroupRollDialog-${party.id}`) ?? new GroupRollDialog(party);
        dialog.tabGroups.application = 'groupRoll';
        await dialog.render({ force: true });
    }
});

const updateActorsRangeDependentEffects = async token => {
    if (!token) return;

    const rangeMeasurement = game.settings.get(
        CONFIG.DH.id,
        CONFIG.DH.SETTINGS.gameSettings.variantRules
    ).rangeMeasurement;

    for (let effect of token.actor?.allApplicableEffects() ?? []) {
        if (!effect.system.rangeDependence?.enabled) continue;
        const { target, range, type } = effect.system.rangeDependence;

        // If there are no targets, assume false. Otherwise, start with the effect enabled.
        let enabledEffect = game.user.targets.size !== 0;
        // Expect all targets to meet the rangeDependence requirements
        for (let userTarget of game.user.targets) {
            const disposition = userTarget.document.disposition;
            if ((target === 'friendly' && disposition !== 1) || (target === 'hostile' && disposition !== -1)) {
                enabledEffect = false;
                break;
            }

            // Get required distance and special case 5 feet to test adjacency
            const required = rangeMeasurement[range];
            const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
            const inRange = userTarget.distanceTo(token.object) <= required;
            if (reverse ? inRange : !inRange) {
                enabledEffect = false;
                break;
            }
        }

        await effect.update({ disabled: !enabledEffect });
    }
};

const updateAllRangeDependentEffects = async () => {
    const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
    if (!effectsAutomation.rangeDependent) return;

    const tokens = canvas.scene?.tokens;
    if (!tokens) return;

    if (game.user.character) {
        // The character updates their character's token. There can be only one token.
        const characterToken = tokens.find(x => x.actor === game.user.character);
        updateActorsRangeDependentEffects(characterToken);
    } else if (game.user.isActiveGM) {
        // The GM is responsible for all other tokens.
        const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character);
        for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) {
            updateActorsRangeDependentEffects(token);
        }
    }
};

const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50);

Hooks.on('targetToken', () => {
    debouncedRangeEffectCall();
});

Hooks.on('refreshToken', (token, options) => {
    if (options.refreshPosition && !token._original) {
        debouncedRangeEffectCall();
    }
});

Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));
Hooks.on('renderDocumentDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));

/* Non actor-linked Actors should unregister the triggers of their tokens if a scene's token layer is torn down */
Hooks.on('canvasTearDown', canvas => {
    game.system.registeredTriggers.unregisterSceneTriggers(canvas.scene);
});

/* Non actor-linked Actors should register the triggers of their tokens on a readied scene */
Hooks.on('canvasReady', canas => {
    game.system.registeredTriggers.registerSceneTriggers(canvas.scene);
});
