export class VermineUtils {
  /**
   * Rolls dice with Vermine2047-specific rules.
   * @param {Object} options - Roll options
   * @param {Actor} options.actor - The actor rolling
   * @param {number} options.NoD - Base dice pool
   * @param {number} [options.Reroll=0] - Reroll count
   * @param {number} [options.difficulty=7] - Difficulty threshold
   * @param {number} [options.self_control=0] - Self control used
   * @param {string} [options.rollLabel="jet custom"] - Roll label
   * @param {Object} [options.totems={}] - Totems used {human: boolean, adapted: boolean}
   * @param {number} [options.max_effort=0] - Max effort
   * @param {string} [options.skillCategory=null] - Skill category for domain bonuses
   * @param {string} [options.keepTotem=null] - Totem to keep ('human' or 'adapted')
   * @param {number} [options.skillLevel=null] - Skill level for auto-successes
   * @param {boolean} [options.hasSpecialty=false] - Whether a specialty is used
   * @returns {Promise<Roll>} The roll result
   */
  static async roll({ 
    actor, 
    NoD, 
    Reroll = 0, 
    difficulty = 7, 
    self_control = 0, 
    rollLabel = "jet custom", 
    totems = { human: false, adapted: false }, 
    max_effort = 0, 
    skillCategory = null, 
    keepTotem = null, 
    skillLevel = null, 
    hasSpecialty = false,
    handicap = 0
  }) {
    // Validate inputs
    if (!actor) {
      throw new Error("Actor is required for rolling");
    }

    // Sanitize user name for use in dice flavor
    const safeUserName = (game.user?.name ?? "user").replace(/[^a-zA-Z0-9_]/g, '_');

    // Declare variables
    let formula = "";
    let modFormula = null;
    let totemBonus = { human: 0, adapted: 0 };

    // Calculate domain bonuses for totems
    if (skillCategory) {
      totemBonus = this._calculateTotemDomainBonuses(skillCategory, actor);
    }

    // Apply auto-successes and auto-thresholds
    let autoSuccesses = 0;
    let adjustedDifficulty = difficulty;

    if (skillLevel !== null && skillLevel !== undefined) {
      autoSuccesses = this._calculateAutoSuccesses(skillLevel, hasSpecialty);
      const autoThreshold = this._getAutoThreshold(skillLevel);
      if (autoThreshold !== null) {
        adjustedDifficulty = autoThreshold;
      }
    }

    // Handle human totem
    if (totems.human) {
      NoD--;
      const humanDifficulty = skillLevel !== null ? Math.max(adjustedDifficulty, difficulty) : adjustedDifficulty;
      const humanFormula = `(1D10cs>=${humanDifficulty}[human_${safeUserName}]*2)`;

      // Apply domain bonus/malus
      if (totemBonus.human !== 0) {
        NoD += totemBonus.human;
      }

      modFormula = humanFormula;
    }

    // Handle adapted totem
    if (totems.adapted) {
      NoD--;
      const adaptedDifficulty = skillLevel !== null ? Math.max(adjustedDifficulty, difficulty) : adjustedDifficulty;
      const adaptedFormula = `(1D10cs>=${adaptedDifficulty}[adapted_${safeUserName}]*2)`;

      // Apply domain bonus/malus
      if (totemBonus.adapted !== 0) {
        NoD += totemBonus.adapted;
      }

      // Build combined formula
      if (modFormula !== null) {
        modFormula = `${modFormula}+${adaptedFormula}`;
      } else {
        modFormula = adaptedFormula;
      }
    }

    // Handle keepTotem selection (if both totems are active)
    if (totems.human && totems.adapted && keepTotem) {
      if (keepTotem === 'human' && totems.adapted) {
        modFormula = `(1D10cs>=${adjustedDifficulty}[human_${safeUserName}]*2)`;
        NoD++; // Cancel the decrement for adapted
      } else if (keepTotem === 'adapted' && totems.human) {
        modFormula = `(1D10cs>=${adjustedDifficulty}[adapted_${safeUserName}]*2)`;
        NoD++;       // Cancel the decrement for human
      }
    }

    // Apply handicap (wounds/maluses reduce dice pool)
    NoD = Math.max(1, NoD - handicap)

    // Build base formula
    const baseFormula = `${NoD}d10cs>=${adjustedDifficulty}[regular_${safeUserName}]`;

    // Build final formula
    formula = modFormula !== null ? `${baseFormula}+${modFormula}` : baseFormula;

    // Create the roll
    const roll = new Roll(formula, actor.getRollData());
    
    // Store metadata for display
    roll.vermineData = {
      totemsUsed: { ...totems },
      keepTotem: keepTotem,
      difficulty: adjustedDifficulty,
      originalDifficulty: difficulty,
      skillCategory: skillCategory,
      skillLevel: skillLevel,
      hasSpecialty: hasSpecialty,
      autoSuccesses: autoSuccesses,
      totemBonuses: { ...totemBonus },
      baseNoD: NoD,
      rerolls: Reroll,
      selfControl: self_control
    };

    // Evaluate the roll
    await roll.evaluate();
    
    // Show 3D dice if available
    await VermineUtils.showDiceSoNice(roll);
    
    // Display result in chat
    VermineUtils.diplayChatRoll(roll, { 
      actor, 
      NoD, 
      Reroll, 
      difficulty, 
      self_control, 
      rollLabel, 
      totems, 
      max_effort, 
      skillCategory, 
      keepTotem, 
      skillLevel, 
      hasSpecialty 
    });
    
    return roll;
  }
  
  /**
   * Calculates domain bonuses/penalties for totems.
   * @param {string} skillCategory - The skill category
   * @param {Actor} actor - The actor
   * @returns {Object} Bonuses for each totem {human: number, adapted: number}
   */
  static _calculateTotemDomainBonuses(skillCategory, actor) {
    const bonuses = { human: 0, adapted: 0 };

    // Validate inputs
    if (!CONFIG.VERMINE?.totemDomains || !actor?.system?.identity?.totem) {
      return bonuses;
    }

    const actorTotem = actor.system.identity.totem;
    
    // Check if actor's totem exists in configuration
    if (!CONFIG.VERMINE.totemDomains[actorTotem]) {
      return bonuses;
    }

    const totemConfig = CONFIG.VERMINE.totemDomains[actorTotem];

    if (!totemConfig?.domains) {
      return bonuses;
    }

    // Get actor's preferred skill category
    const preferredCategory = actor.system.skill_categories?.preferred;

    // Bonus for actor's totem if preferred category is in its domains
    if (preferredCategory && totemConfig.domains.includes(preferredCategory)) {
      bonuses[actorTotem] = totemConfig.bonus || 1;
    }

    // Penalty for opposite totem if preferred category is in its domains
    const oppositeTotem = CONFIG.VERMINE.totem_opposites?.[actorTotem];
    if (oppositeTotem && CONFIG.VERMINE.totemDomains[oppositeTotem]) {
      const oppositeConfig = CONFIG.VERMINE.totemDomains[oppositeTotem];
      if (preferredCategory && oppositeConfig?.domains?.includes(preferredCategory)) {
        bonuses[oppositeTotem] = -(oppositeConfig.bonus || 1);
      }
    }

    return bonuses;
  }
  
  /**
   * Calculates automatic successes based on skill mastery level.
   * @param {number} skillLevel - Skill level (0-5)
   * @param {boolean} [hasSpecialty=false] - Whether a specialty is used
   * @returns {number} Number of automatic successes
   */
  static _calculateAutoSuccesses(skillLevel, hasSpecialty = false) {
    // According to Vermine2047 rules, automatic successes are based on mastery level:
    // Level 0 (Incompetent): 0 automatic successes
    // Level 1 (Beginner): 0 automatic successes
    // Level 2 (Proficient): 1 automatic success if specialty is used
    // Level 3 (Expert): 1 automatic success
    // Level 4 (Master): 1 automatic success + 1 if specialty is used
    // Level 5 (Legend): 2 automatic successes
    
    if (!skillLevel) return 0;
    
    let autoSuccesses = 0;
    
    switch (skillLevel) {
      case 2: // Compétent
        if (hasSpecialty) autoSuccesses = 1;
        break;
      case 3: // Expert
        autoSuccesses = 1;
        break;
      case 4: // Maître
        autoSuccesses = 1;
        if (hasSpecialty) autoSuccesses += 1;
        break;
      case 5: // Légende
        autoSuccesses = 2;
        break;
      default:
        autoSuccesses = 0;
    }
    
    return autoSuccesses;
  }
  
  /**
   * Determines the automatic threshold if the skill is not mastered.
   * @param {number} skillLevel - Skill level
   * @returns {number|null} Automatic threshold or null if skill is mastered
   */
  static _getAutoThreshold(skillLevel) {
    // If the skill is not mastered (level 0 or 1), use a default threshold
    // Level 0 (Incompetent): threshold = 9 (very hard)
    // Level 1 (Beginner): threshold = 7 (hard)
    // Level >= 2: null (use normal threshold)
    
    if (skillLevel === 0) return 9; // Very hard
    if (skillLevel === 1) return 7; // Hard
    
    return null; // Utiliser le seuil normal
  }

  /**
   * Handles reroll events on dice in chat messages.
   * @param {Object} message - The chat message containing the reroll event
   * @param {Object} ev - The reroll event
   * @returns {Promise<boolean>} Whether the reroll was successful
   */
  static async onReroll(message, ev) {
    // Verify user permissions
    const msgUserId = message.user?.id ?? message.user;
    if (msgUserId !== game.user?.id && !game.user?.isGM) {
      ui.notifications.warn(game.i18n.localize('VERMINE.error_cannot_reroll'));
      return false;
    }

    // Get reroll count
    const rollMessage = ev.currentTarget.closest('div.vermine-roll-message');
    if (!rollMessage) {
      return false;
    }

    let rerollCount = rollMessage.querySelector('#allowed_reroll')?.innerText;
    
    // Check if rerolls are available
    if (!rerollCount || parseInt(rerollCount, 10) < 1) {
      ui.notifications.warn(game.i18n.localize('VERMINE.error_no_rerolls_left'));
      const rerollables = ev.currentTarget.closest('ul')?.querySelectorAll('.rerollable');
      if (rerollables) {
        rerollables.forEach(el => el.classList.remove('rerollable'));
      }
      return false;
    }

    ev.currentTarget.classList.add('rerolled');

    // Set reroll flag
    await message.setFlag("world", "reroll", true);

    // Get difficulty and dice type
    const ulElement = ev.currentTarget.closest('ul');
    const difficulty = ulElement?.dataset.difficulty ?? 7;
    let diceType = ev.currentTarget.dataset.diceType;

    // Sanitize user name
    const safeUserName = (game.user?.name ?? "user").replace(/[^a-zA-Z0-9_]/g, '_');

    // Build reroll formula
    let formula = `1d10cs>=${difficulty}`;
    
    switch ((diceType ?? '').trim()) {
      case 'human':
        formula = `(1d10cs>=${difficulty}[human_${safeUserName}])*2`;
        break;
      case 'adapted':
        formula = `(1d10cs>=${difficulty}[adapted_${safeUserName}])*2`;
        break;
      default:
        formula += `[regular_${safeUserName}]`;
        break;
    }

    // Create and evaluate reroll
    const reroll = new Roll(formula);
    await reroll.evaluate();

    // Show 3D dice if available
    await VermineUtils.showDiceSoNice(reroll);

    // Update die display
    const result = reroll.dice[0]?.results[0]?.result ?? 0;
    const dieSpan = ev.currentTarget.querySelector('span');
    if (dieSpan) {
      dieSpan.innerText = result;
    }

    // Update total if successful
    const success = reroll.dice[0]?.results[0]?.success;
    if (success) {
      ev.currentTarget.classList.add('success');
      const totalElement = rollMessage.querySelector('#total');
      if (totalElement) {
        const currentTotal = parseInt(totalElement.innerText, 10) || 0;
        totalElement.innerText = currentTotal + reroll.total;
      }
    }

    // Update message content
    ev.currentTarget.classList.remove("rerollable");
    
    const messageContent = ev.currentTarget.closest('div.message-content');
    if (messageContent) {
      const newRerollCount = parseInt(rerollCount, 10) - 1;
      rollMessage.querySelector('#allowed_reroll').innerText = newRerollCount;
      
      await message.update({
        content: messageContent.outerHTML
      });
    }

    return true;
  }

  /**
   * Sets up event listeners for chat messages.
   * @param {HTMLElement} html - The HTML element containing chat events.
   */
  static async chatListenners(html) {
    // Get reroll count
    const rerollCountElement = html.querySelector('#allowed_reroll');
    const rerollCount = rerollCountElement?.innerText;

    // Enable/disable rerolls based on count
    const dieClass = !rerollCount || parseInt(rerollCount, 10) < 1 ? 'remove' : 'add';
    html.querySelectorAll('.die').forEach(el => el.classList[dieClass]("rerollable"));

    // Add click event for rerollable dice
    html.querySelectorAll('.rerollable').forEach(el => {
      el.addEventListener('click', async (ev) => {
        ev.preventDefault();
        const msgId = ev.currentTarget.closest("li.message")?.dataset?.messageId;
        if (msgId) {
          const message = await game.messages.get(msgId);
          await VermineUtils.onReroll(message, ev);
        }
      });
    });

    // Update granted reroll label
    const effortReroll = html.querySelector("#effort-reroll");
    if (effortReroll) {
      effortReroll.addEventListener('change', ev => {
        const label = html.querySelector("#granted-reroll");
        if (label) {
          label.innerText = ev.currentTarget.value;
        }
      });
    }

    // Add click event for granting rerolls
    html.querySelectorAll("button.grant-reroll").forEach(el => {
      el.addEventListener('click', async (ev) => {
        const grantedRerollElement = html.querySelector('#granted-reroll');
        const allowedRerollElement = html.querySelector("#allowed_reroll");

        if (grantedRerollElement && allowedRerollElement) {
          allowedRerollElement.innerText = grantedRerollElement.innerText;
        }

        const mesEl = ev.currentTarget.closest('[data-message-id]');
        const messageId = mesEl?.dataset?.messageId;

        if (messageId) {
          ev.currentTarget.closest('.reroll-from-effort').style.display = "none";

          const rollMessage = ev.currentTarget.closest(".vermine-roll-message");
          if (rollMessage) {
            const content = rollMessage.outerHTML;
            const message = await game.messages.get(messageId);
            await message.update({ content: content });
          }
        }
      });
    });
  }

  /**
   * Displays dice rolls in 3D if available.
   * @param {Roll} roll - The roll to display
   * @param {string} [rollMode] - The roll mode (uses game settings if not provided)
   * @returns {Promise<boolean>} Whether 3D dice were shown
   */
  static async showDiceSoNice(roll, rollMode) {
    if (!game.dice3d) {
      return false;
    }

    rollMode = rollMode ?? game.settings.get("core", "rollMode");
    let whisper = null;
    let blind = false;

    switch (rollMode) {
      case "blindroll": // GM only
        blind = true;
        // Falls through
      case "gmroll": // GM + rolling player
        whisper = game.users?.filter(user => user.isGM) || [];
        break;
      case "roll": // Everybody
        whisper = game.users?.filter(user => user.active) || [];
        break;
      case "selfroll":
        whisper = [game.user.id];
        break;
    }

    await game.dice3d.showForRoll(roll, game.user, true, whisper, blind);
    return true;
  }

  /**
   * Displays a dice roll in the chat.
   * @param {Roll} roll - The roll to display
   * @param {Object} param - Roll parameters
   * @returns {Promise<ChatMessage>} The created chat message
   */
  static async diplayChatRoll(roll, param) {
    const content = await foundry.applications.handlebars.renderTemplate("systems/vermine2047/templates/roll-message.hbs", { roll, param });
    const chatData = {
      user: game.user?._id,
      speaker: ChatMessage.getSpeaker(),
      content: content,
      rolls: [roll]
    };
    const msg = await ChatMessage.create(chatData);
    return msg;
  }


}



