// Import document classes.
import { MyriadSystemActor } from './documents/actor.mjs';
import { MyriadSystemItem } from './documents/item.mjs';
// Import sheet classes.
import { MyriadSystemActorSheet } from './sheets/actor-sheet.mjs';
import { MyriadSystemItemSheet } from './sheets/item-sheet.mjs';
// Import helper/utility classes and constants.
import { preloadHandlebarsTemplates } from './helpers/templates.mjs';
import { MYRIAD_SYSTEM } from './helpers/config.mjs';
// Import effects management module
import * as effects from './helpers/effects.mjs';
// Import DataModel classes
import * as models from './data/_module.mjs';

/* -------------------------------------------- */
/*  Init Hook                                   */
/* -------------------------------------------- */

Hooks.once('init', function () {
  // Add utility classes to the global game object so that they're more easily
  // accessible in global contexts.
  game.myriadsystem = {
    MyriadSystemActor,
    MyriadSystemItem,
    rollItemMacro,
  };
  
  // Add effects module to the global game object
  game.myriad = {
    effects: effects
  };

  // Add custom constants for configuration.
  CONFIG.MYRIAD_SYSTEM = MYRIAD_SYSTEM;

  /**
   * Set an initiative formula for the system
   * @type {String}
   */
  CONFIG.Combat.initiative = {
    formula: '1d20 + @coordination.mod',
    decimals: 2,
  };

  // Define custom Document and DataModel classes
  CONFIG.Actor.documentClass = MyriadSystemActor;

  // Note that you don't need to declare a DataModel
  // for the base actor/item classes - they are included
  // with the Character/NPC as part of super.defineSchema()
  CONFIG.Actor.dataModels = {
    character: models.MyriadSystemCharacter,
    npc: models.MyriadSystemNPC
  }
  CONFIG.Item.documentClass = MyriadSystemItem;
  CONFIG.Item.dataModels = {
    item: models.MyriadSystemItem,
    feature: models.MyriadSystemFeature,
    spell: models.MyriadSystemSpell,
    path: models.MyriadSystemPath,
    art: models.MyriadSystemArt,
    domain: models.MyriadSystemDomain
  }

  // Active Effects are never copied to the Actor,
  // but will still apply to the Actor from within the Item
  // if the transfer property on the Active Effect is true.
  CONFIG.ActiveEffect.legacyTransferral = false;

  // Register sheet application classes
  foundry.documents.collections.Actors.unregisterSheet('core', foundry.appv1.sheets.ActorSheet);
  foundry.documents.collections.Actors.registerSheet('myriad-system', MyriadSystemActorSheet, {
    makeDefault: true,
    label: 'MYRIAD_SYSTEM.SheetLabels.Actor',
  });
  foundry.documents.collections.Items.unregisterSheet('core', foundry.appv1.sheets.ItemSheet);
  foundry.documents.collections.Items.registerSheet('myriad-system', MyriadSystemItemSheet, {
    makeDefault: true,
    label: 'MYRIAD_SYSTEM.SheetLabels.Item',
  });

  // Preload Handlebars templates.
  return preloadHandlebarsTemplates();
});

/* -------------------------------------------- */
/*  Handlebars Helpers                          */
/* -------------------------------------------- */

// If you need to add Handlebars helpers, here is a useful example:
Handlebars.registerHelper('toLowerCase', function (str) {
  return str.toLowerCase();
});

/* -------------------------------------------- */
/*  Ready Hook                                  */
/* -------------------------------------------- */

Hooks.once('ready', function () {
  // Wait to register hotbar drop hook on ready so that modules could register earlier if they want to
  Hooks.on('hotbarDrop', (bar, data, slot) => createItemMacro(data, slot));

  // NOTE: AP no longer auto-resets each round until multi-turn logic is implemented.
  // If needed later, re-enable a round reset here.

  // Ensure newly created actors use actionPoints on token bar 2 instead of power
  Hooks.on('preCreateActor', (actor, data, options, userId) => {
    if (!data.prototypeToken) data.prototypeToken = {};
    // Assign health to bar1 automatically already handled by foundry if using attributes
  // Set bar2 to Action Points root object so bar shows value/max proportion
  data.prototypeToken.bar2 = { attribute: 'system.actionPoints' };
  });

  // Initialize AP when a combatant is created (joining combat mid-encounter)
  Hooks.on('createCombatant', async (combatant) => {
    const actor = combatant.actor;
    if (!actor?.system?.actionPoints) return;
    const stance = actor.system.actionPoints.stance || 'defensive';
    const max = stance === 'aggressive' ? 10 : 5;
    await actor.update({ 'system.actionPoints.max': max, 'system.actionPoints.value': max });
  });

  // If AP stance changes during combat, recalc max and optionally keep proportion of remaining
  Hooks.on('updateActor', async (actor, diff) => {
    if (!diff?.system?.actionPoints?.stance) return;
    if (!actor.system?.actionPoints) return;
    const inCombat = !!game.combat?.combatants.find(c => c.actor?.id === actor.id);
    if (!inCombat) return; // will init when entering combat
    const stance = actor.system.actionPoints.stance || 'defensive';
    const max = stance === 'aggressive' ? 10 : 5;
    // Option: conserve remaining proportion (simple: clamp)
    const current = Math.min(actor.system.actionPoints.value, max);
    await actor.update({ 'system.actionPoints.max': max, 'system.actionPoints.value': current });
  });

  // Re-render combat tracker when AP value changes
  Hooks.on('updateActor', (actor, diff) => {
    if (diff?.system?.actionPoints?.value !== undefined && ui.combat) ui.combat.render(false);
  });

  // Inject AP display into Combat Tracker list entries
  Hooks.on('renderCombatTracker', (app, html, data) => {
    try {
      if (!html) return;
      // html may be a jQuery object, an HTMLElement, or an ApplicationV2-ish wrapper
      const root = html[0] instanceof HTMLElement ? html[0] : (html instanceof HTMLElement ? html : null);
      if (!root) return; // can't safely traverse
      const combat = app.viewed ?? game.combat;
      if (!combat) return;
      const rows = root.querySelectorAll?.('li.combatant');
      if (!rows) return;
      for (const li of rows) {
        const cid = li.dataset.combatantId;
        if (!cid) continue;
        const c = combat.combatants.get(cid);
        if (!c?.actor) continue;
        const ap = c.actor.system?.actionPoints;
        li.querySelectorAll('.myriad-ap-badge').forEach(n => n.remove());
        if (!ap || (ap.max ?? 0) <= 0) continue;
        const badge = document.createElement('span');
        badge.classList.add('myriad-ap-badge');
        badge.title = game.i18n.localize('MYRIAD_SYSTEM.ActionPoints.label');
        badge.textContent = `${ap.value}/${ap.max}`;
        const nameElem = li.querySelector('.token-name, h4.name, .combatant-name');
        if (nameElem) nameElem.appendChild(badge); else li.appendChild(badge);
      }
    } catch (err) {
      console.error('Myriad System: renderCombatTracker AP badge error', err);
    }
  });

  // Helper to spend Action Points via a hook call
  game.myriad = game.myriad || {};
  game.myriad.spendActionPoints = async (actor, amount = 1) => {
    if (!actor?.system?.actionPoints) return false;
    const current = actor.system.actionPoints.value || 0;
    const newValue = Math.max(0, current - amount);
    await actor.update({ 'system.actionPoints.value': newValue });
    // After spending, check if all combatants are at 0 -> advance round automatically
    const combat = actor.combatant?.combat;
    if (combat) {
      const allSpent = combat.combatants.every(c => {
        const ap = c.actor?.system?.actionPoints;
        // Only consider combatants who have AP (player characters / npcs maybe)
        return ap ? (ap.value <= 0) : true;
      });
      if (allSpent) {
        // Advance to next round
        await combat.nextRound();
      }
    }
    return true;
  };

  // One-time migration: switch existing actor prototype tokens bar2 from power to actionPoints
  (async () => {
    const flagKey = 'myriad-system.migrations.apBarApplied';
    if (game.settings) {
      // Register a setting to avoid repeating migration
      if (!game.settings.settings.has(flagKey)) {
        game.settings.register('myriad-system', 'migrations.apBarApplied', { scope: 'world', config: false, type: Boolean, default: false });
      }
      const already = game.settings.get('myriad-system', 'migrations.apBarApplied');
      if (!already) {
        for (const actor of game.actors.contents) {
          const pt = actor.prototypeToken;
          if (pt?.bar2?.attribute === 'power' || pt?.bar2?.attribute === 'actionPoints' || pt?.bar2?.attribute === 'system.actionPoints.value') {
            await actor.update({ 'prototypeToken.bar2': { attribute: 'system.actionPoints' } });
          }
        }
        await game.settings.set('myriad-system', 'migrations.apBarApplied', true);
        console.log('Myriad System: Action Points token bar migration applied.');
      }
    }
  })();
});

/* -------------------------------------------- */
/*  Active Effect Hooks                         */
/* -------------------------------------------- */

// Validate characteristic limits after applying effects
Hooks.on("applyActiveEffect", (actor, change, currentValue, delta, changes) => {
  // Check if this is a characteristic value change
  const characteristicMatch = change.key.match(/system\.characteristics\.(\w+)\.value/);
  if (characteristicMatch && actor) {
    const charName = characteristicMatch[1];
    const char = actor.system.characteristics?.[charName];
    
    // If we have a valid characteristic
    if (char) {
      // Check for min/max limits
      if (currentValue < 0) {
        changes[change.key] = 0;
      } else if (currentValue > char.max) {
        changes[change.key] = char.max;
      }
    }
  }
});

/* -------------------------------------------- */
/*  Hotbar Macros                               */
/* -------------------------------------------- */

/**
 * Create a Macro from an Item drop.
 * Get an existing item macro if one exists, otherwise create a new one.
 * @param {Object} data     The dropped data
 * @param {number} slot     The hotbar slot to use
 * @returns {Promise}
 */
async function createItemMacro(data, slot) {
  // First, determine if this is a valid owned item.
  if (data.type !== 'Item') return;
  if (!data.uuid.includes('Actor.') && !data.uuid.includes('Token.')) {
    return ui.notifications.warn(
      'You can only create macro buttons for owned Items'
    );
  }
  // If it is, retrieve it based on the uuid.
  const item = await Item.fromDropData(data);

  // Create the macro command using the uuid.
  const command = `game.myriadsystem.rollItemMacro("${data.uuid}");`;
  let macro = game.macros.find(
    (m) => m.name === item.name && m.command === command
  );
  if (!macro) {
    macro = await Macro.create({
      name: item.name,
      type: 'script',
      img: item.img,
      command: command,
      flags: { 'myriad-system.itemMacro': true },
    });
  }
  game.user.assignHotbarMacro(macro, slot);
  return false;
}

/**
 * Create a Macro from an Item drop.
 * Get an existing item macro if one exists, otherwise create a new one.
 * @param {string} itemUuid
 */
function rollItemMacro(itemUuid) {
  // Reconstruct the drop data so that we can load the item.
  const dropData = {
    type: 'Item',
    uuid: itemUuid,
  };
  // Load the item from the uuid.
  Item.fromDropData(dropData).then((item) => {
    // Determine if the item loaded and if it's an owned item.
    if (!item || !item.parent) {
      const itemName = item?.name ?? itemUuid;
      return ui.notifications.warn(
        `Could not find item ${itemName}. You may need to delete and recreate this macro.`
      );
    }

    // Trigger the item roll
    item.roll();
  });
}
