import { BoLRoll } from "../controllers/bol-rolls.js";

/**
 * BoL Macro API — accessible via game.bol.macros
 *
 * Usage examples (in a Foundry macro):
 *
 *   game.bol.macros.rollAttribute("vigor")
 *   game.bol.macros.rollAttribute("mind")
 *
 *   game.bol.macros.rollAptitude("melee")
 *   game.bol.macros.rollAptitude("ranged")
 *   game.bol.macros.rollAptitude("def")
 *   game.bol.macros.rollAptitude("init")
 *
 *   game.bol.macros.rollWeapon("Épée courte")   // by name (partial match)
 *   game.bol.macros.rollWeapon(0)               // by index (first weapon)
 *
 *   game.bol.macros.rollSpell("Boule de feu")   // by name (partial match)
 *   game.bol.macros.rollSpell(0)                // by index
 *
 *   game.bol.macros.rollAlchemy("Potion de soin") // by name (partial match)
 *   game.bol.macros.rollAlchemy(0)               // by index
 *
 *   game.bol.macros.rollHoroscope("minor")
 *   game.bol.macros.rollHoroscope("major")
 *
 *   // Generic dispatcher:
 *   game.bol.macros.roll("attribute", "vigor")
 *   game.bol.macros.roll("aptitude", "melee")
 *   game.bol.macros.roll("weapon", "Épée courte")
 *   game.bol.macros.roll("spell", "Boule de feu")
 *   game.bol.macros.roll("alchemy", 0)
 *   game.bol.macros.roll("horoscope", "minor")
 */
export class Macros {

  /* -------------------------------------------- */
  /**
   * Resolves the actor for macro use:
   * - If multiple tokens are selected → error (always)
   * - If exactly one token is selected → use it (GM or player)
   * - If no token selected and user is GM → error (GM must select a token)
   * - If no token selected and user is a player → use their assigned character
   * @returns {Actor|null}
   */
  static getSpeakersActor() {
    const tokens = canvas.tokens?.controlled ?? []

    if (tokens.length > 1) {
      ui.notifications.warn(game.i18n.localize('BOL.notification.MacroMultipleTokensSelected'))
      return null
    }

    if (tokens.length === 1) {
      return tokens[0].actor ?? null
    }

    // No token selected
    if (game.user.isGM) {
      ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"))
      return null
    }

    // Player: fall back to their assigned character
    const actor = game.user.character
    if (!actor) {
      ui.notifications.error(game.i18n.localize("BOL.notification.MacroNoTokenSelected"))
      return null
    }
    return actor
  }

  /* -------------------------------------------- */
  /**
   * Finds an item on an actor by name (partial, case-insensitive) or index.
   * @param {Actor} actor
   * @param {string} type  - item type: "weapon", "spell", "alchemy"
   * @param {string|number} nameOrIndex
   * @returns {object|undefined}
   */
  static _findItem(actor, type, nameOrIndex) {
    const items = actor.items.filter(i => i.type === type)
    if (items.length === 0) {
      ui.notifications.warn(`${actor.name} : aucun(e) ${type} trouvé(e).`)
      return undefined
    }
    if (nameOrIndex === undefined || nameOrIndex === null) {
      if (items.length === 1) return foundry.utils.duplicate(items[0])
      const names = items.map((it, i) => `[${i}] ${it.name}`).join(', ')
      ui.notifications.warn(`Précisez le nom ou l'index : ${names}`)
      return undefined
    }
    if (typeof nameOrIndex === "number") {
      const item = items[nameOrIndex]
      if (!item) {
        ui.notifications.warn(`${actor.name} : index ${nameOrIndex} invalide pour ${type}.`)
        return undefined
      }
      return foundry.utils.duplicate(item)
    }
    const lower = String(nameOrIndex).toLowerCase()
    const found = items.find(i => i.name.toLowerCase().includes(lower))
    if (!found) {
      ui.notifications.warn(`${actor.name} : ${type} "${nameOrIndex}" introuvable.`)
      return undefined
    }
    return foundry.utils.duplicate(found)
  }

  /* -------------------------------------------- */
  /**
   * Roll an attribute check.
   * @param {string} key  - "vigor" | "agility" | "mind" | "appeal"
   * @param {Actor} [actor] - optional, defaults to selected/owned token
   */
  static rollAttribute(key = "vigor", actor = undefined) {
    actor = actor ?? this.getSpeakersActor()
    if (!actor) return
    if (!actor.system.attributes[key]) {
      ui.notifications.warn(`Attribut inconnu : "${key}". Valeurs : vigor, agility, mind, appeal`)
      return
    }
    return BoLRoll.attributeCheck(actor, key)
  }

  /* -------------------------------------------- */
  /**
   * Roll an aptitude check.
   * @param {string} key  - "init" | "melee" | "ranged" | "def"
   * @param {Actor} [actor] - optional, defaults to selected/owned token
   */
  static rollAptitude(key = "melee", actor = undefined) {
    actor = actor ?? this.getSpeakersActor()
    if (!actor) return
    if (!actor.system.aptitudes[key]) {
      ui.notifications.warn(`Aptitude inconnue : "${key}". Valeurs : init, melee, ranged, def`)
      return
    }
    return BoLRoll.aptitudeCheck(actor, key)
  }

  /* -------------------------------------------- */
  /**
   * Roll a weapon attack.
   * @param {string|number} [nameOrIndex] - weapon name (partial) or index. Defaults to first weapon if only one.
   * @param {Actor} [actor] - optional, defaults to selected/owned token
   */
  static rollWeapon(nameOrIndex = undefined, actor = undefined) {
    actor = actor ?? this.getSpeakersActor()
    if (!actor) return
    const weapon = this._findItem(actor, "weapon", nameOrIndex)
    if (!weapon) return
    return BoLRoll.weaponCheckWithWeapon(actor, weapon)
  }

  /* -------------------------------------------- */
  /**
   * Roll a spell check.
   * @param {string|number} [nameOrIndex] - spell name (partial) or index
   * @param {Actor} [actor] - optional, defaults to selected/owned token
   */
  static rollSpell(nameOrIndex = undefined, actor = undefined) {
    actor = actor ?? this.getSpeakersActor()
    if (!actor) return
    if ((actor.system.resources.power?.value ?? 1) <= 0) {
      ui.notifications.warn("Plus assez de points de Pouvoir !")
      return
    }
    const spell = this._findItem(actor, "spell", nameOrIndex)
    if (!spell) return
    return BoLRoll.spellCheckWithSpell(actor, spell)
  }

  /* -------------------------------------------- */
  /**
   * Roll an alchemy check.
   * @param {string|number} [nameOrIndex] - alchemy item name (partial) or index
   * @param {Actor} [actor] - optional, defaults to selected/owned token
   */
  static rollAlchemy(nameOrIndex = undefined, actor = undefined) {
    actor = actor ?? this.getSpeakersActor()
    if (!actor) return
    const alchemy = this._findItem(actor, "alchemy", nameOrIndex)
    if (!alchemy) return
    return BoLRoll.alchemyCheckWithItem(actor, alchemy)
  }

  /* -------------------------------------------- */
  /**
   * Roll a horoscope check.
   * @param {"minor"|"major"} [type="minor"]
   * @param {Actor} [actor] - optional, defaults to selected/owned token
   */
  static rollHoroscope(type = "minor", actor = undefined) {
    actor = actor ?? this.getSpeakersActor()
    if (!actor) return
    return BoLRoll.horoscopeCheck(actor, undefined, type)
  }

  /* -------------------------------------------- */
  /**
   * Generic roll dispatcher.
   * @param {"attribute"|"aptitude"|"weapon"|"spell"|"alchemy"|"horoscope"} type
   * @param {string|number} [key] - attribute/aptitude key, item name/index, or horoscope type
   * @param {Actor} [actor] - optional, defaults to selected/owned token
   */
  static roll(type, key = undefined, actor = undefined) {
    switch (type) {
      case "attribute":  return this.rollAttribute(key ?? "vigor", actor)
      case "aptitude":   return this.rollAptitude(key ?? "melee", actor)
      case "weapon":     return this.rollWeapon(key, actor)
      case "spell":      return this.rollSpell(key, actor)
      case "alchemy":    return this.rollAlchemy(key, actor)
      case "horoscope":  return this.rollHoroscope(key ?? "minor", actor)
      default:
        ui.notifications.warn(`Type de jet inconnu : "${type}". Types valides : attribute, aptitude, weapon, spell, alchemy, horoscope`)
    }
  }

  /* -------------------------------------------- */
  // Kept for backward-compat (previously called by old macros)
  static rollMacro = async function (rollType, key) {
    const actor = Macros.getSpeakersActor()
    if (!actor) return
    return Macros.roll(rollType, key, actor)
  }
}
