import { RDD_CONFIG, renderTemplate, SHOW_DICE, SYSTEM_RDD } from "../constants.js";
import { Grammar } from "../grammar.js";
import { Misc } from "../misc.js";
import { RdDResolutionTable } from "../rdd-resolution-table.js";
import { RdDEncaisser } from "../rdd-roll-encaisser.js";
import { RdDRoll } from "../rdd-roll.js";
import { RdDUtility } from "../rdd-utility.js";
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
import { RdDBaseActor } from "./base-actor.js";
import { ITEM_TYPES } from "../constants.js";
import { StatusEffects, STATUSES } from "../settings/status-effects.js";
import { Targets } from "../combat/targets.js";
import { RdDConfirm } from "../rdd-confirm.js";
import { CARACS, RdDCarac } from "../rdd-carac.js";
import { RdDRollResult } from "../rdd-roll-result.js";

import { RdDItemArme } from "../item/arme.js";
import { RdDItemCompetence } from "../item-competence.js";

import { ChatUtility } from "../chat-utility.js";
import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js";
import { PasseArme } from "../combat/passe-arme.mjs";
import { RdDEmpoignade } from "../combat/rdd-empoignade.js";
import { RdDPossession } from "../combat/rdd-possession.js";
import { RdDPossessionV2 } from "../combat/rdd-possession-v2.mjs";

import { BASE_CORPS_A_CORPS, BASE_ESQUIVE, CATEGORIES_COMPETENCES_CREATURES, SANS_DRACONIC } from "../item/base-items.js";
import { RollDataAjustements } from "../rolldata-ajustements-v1.js";
import { MappingCreatureArme } from "../item/mapping-creature-arme.mjs";
import RollDialog from "../roll/roll-dialog.mjs";
import { DEFAULT_ROLL_TYPES, DIFF, ROLL_TYPE_ATTAQUE, ROLL_TYPE_COMP, ROLL_TYPE_REVE_RESISTANCE } from "../roll/roll-constants.mjs";
import { OptionsAvancees, ROLL_DIALOG_V2 } from "../settings/options-avancees.js";
import { RdDInitiative } from "../combat/initiative.mjs";
import { RdDItemCompetenceCreature } from "../item-competencecreature.js";

/**
 * Classe de base pour les acteurs disposant de rêve (donc, pas des objets)
 * - Entités de rêve
 * - Créatures de "sang": créatures et humanoides
 */
export class RdDBaseActorReve extends RdDBaseActor {

  getCarac() {
    const carac = super.getCarac()
    foundry.utils.mergeObject(carac, this.getCaracReveActuel())
    foundry.utils.mergeObject(carac, this.getCaracCompetenceCreature(), { overwrite: false })
    return carac
  }

  getTaille() { return Misc.toInt(this.system.carac.taille?.value) }
  getConstitution() { return this.getReve() }

  getForce() { return this.getReve() }
  getAgilite() { return this.getForce() }
  getReve() { return Misc.toInt(this.system.carac.reve?.value) }
  getChance() { return this.getReve() }
  getChanceActuel() { return this.getChance() }
  getCaracChanceActuelle() { return { [CARACS.CHANCE_ACTUELLE]: { label: 'Chance actuelle', value: this.getChanceActuel(), type: "number" } } }

  getReveActuel() { return this.getReve() }
  getCaracReveActuel() { return { [CARACS.REVE_ACTUEL]: { label: "Rêve Actuel", value: this.getReveActuel(), type: "number" } } }

  getEnduranceMax() { return Math.max(1, this.getTaille() + this.getConstitution()) }
  getEncombrementMax() { return (this.getForce() + this.getTaille()) / 2 }
  getBonusDegat() { return RdDCarac.getCaracDerivee(this.getEncombrementMax()).plusdom }

  getMoralTotal() { return 0 }
  listeAmoureux() { return [] }
  getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) }
  getSConst() { return 0 }

  /* -------------------------------------------- */
  computeMalusSurEncombrement() { return 0 }

  ajustementAstrologique() { return 0 }
  getMalusArmure() { return 0 }

  getEnduranceActuelle() {
    return Number(this.system.sante?.endurance?.value ?? 0);
  }
  async jetEndurance(resteEndurance = undefined) { return { jetEndurance: 0, sonne: false } }
  isDead() { return false }
  isSonne() { return false }
  blessuresASoigner() { return [] }
  getEtatGeneral(options = { ethylisme: false }) { return 0 }
  isActorCombat() { return true }

  getCaracInit(competence) {
    if (!competence) {
      return 0
    }
    if (competence.type == ITEM_TYPES.competencecreature) {
      return competence.system.carac_value
    }
    return this.system.carac[competence.system.defaut_carac].value;
  }

  getDraconics() { return [] }

  getDraconicOuNaturel(options = {possession: false}) {
    return [
      ...this.getDraconics(),
      ...this.itemTypes[ITEM_TYPES.competencecreature].filter(it => it.isCompetencePossession()),
      options.possession ? this.getConjurationNaturelle() : SANS_DRACONIC
    ].sort(Misc.descending(it => it.system.niveau));
  }

  getBestDraconic(options = {possession: false}) {
    return this.getDraconicOuNaturel(options)
      .find(it => true)
  }

  getConjurationNaturelle() {
    return new RdDItemCompetenceCreature({
      name: 'Sans compétence',
      type: ITEM_TYPES.competencecreature,
      img: RDD_CONFIG.icons.possession,
      system: {
        carac_value: this.getReveActuel(),
        niveau: 0,
        default_diffLibre: 0,
        categorie: CATEGORIES_COMPETENCES_CREATURES.possession.key,
        ispossession: true,
      }
    })
  }

  toActionConjuration(comp) {
    const caracCode = this.isPersonnage() ? CARACS.REVE_ACTUEL : CARACS.REVE
    const caracValue = this.getReveActuel()
    const ajustement = this.getEtatGeneral()
    return {
      label: `Conjuration (${comp.name})`,
      action: 'possession',
      initOnly: false,
      comp: comp,
      carac: { key: caracCode, value: caracValue },
      initiative: RdDInitiative.getRollInitiative(caracValue, comp.system.niveau, ajustement)
    }
  }

  listActions() {
    return this.itemTypes[ITEM_TYPES.competencecreature]
      .filter(it => it.isAttaque())
      .map(it => it.attaqueCreature())
      .filter(it => it != undefined)
  }

  async computeArmure(dmg) { return this.getProtectionNaturelle() }
  async remiseANeuf() { }
  async ajoutExperience(rollData, hideChatMessage = 'show') { }

  computeResumeBlessure() { return [] }
  countBlessures(filter = it => !it.isContusion()) { return 0 }
  async santeIncDec(name, inc, isCritique) { }

  async finDeRound(options = { terminer: false }) {
    await this.finDeRoundSuppressionEffetsTermines(options)
    await this.finDeRoundBlessures()
    await this.finDeRoundSupprimerObsoletes()
    await this.finDeRoundEmpoignade()
    await this.finDeRoundPossession()
  }

  async finDeRoundSuppressionEffetsTermines(options) {
    const effects = this.getEffects();
    for (let effect of effects) {
      if (effect.duration.type !== 'none' && (effect.duration.remaining <= 0 || options.terminer)) {
        await effect.delete()
        await ChatMessage.create({ content: `${this.getAlias()} n'est plus ${Misc.lowerFirst(game.i18n.localize(effect.system.label))} !` })
      }
    }
  }

  async finDeRoundBlessures() {
  }

  async finDeRoundSupprimerObsoletes() {
    const obsoletes = []
      .concat(this.itemTypes[ITEM_TYPES.empoignade].filter(it => it.system.pointsemp <= 0))
      .map(it => it.id)
    await this.deleteEmbeddedDocuments('Item', obsoletes);
  }

  async finDeRoundEmpoignade() {
    await Promise.all(this.itemTypes[ITEM_TYPES.empoignade]
      .filter(it => it.system.pointsemp >= 2 && it.system.empoigneurid == this.id)
      .map(async it => await RdDEmpoignade.onImmobilisation(this, it)))
  }

  async finDeRoundPossession() {
    await Promise.all(this.itemTypes[ITEM_TYPES.possession]
      .map(async it => await RdDPossessionV2.onPossession(this, it)))
  }

  async setSonne(sonne = true) { }

  /* -------------------------------------------- */
  getCompetence(idOrName, options = {}) {
    if (idOrName instanceof Item) {
      return idOrName.isCompetence() ? idOrName : undefined
    }
    return RdDItemCompetence.findCompetence(
      this.items.filter(it => [ITEM_TYPES.competence, ITEM_TYPES.competencecreature].includes(it.type)),
      idOrName, options)
  }

  getCompetences(name = undefined, options = { onMessage: message => { } }) {
    const all = [...this.itemTypes[ITEM_TYPES.competence], ...this.itemTypes[ITEM_TYPES.competencecreature]]
    if (name == undefined) {
      return all
    }
    return RdDItemCompetence.findCompetences(all, name, options)
  }

  getCompetenceCorpsACorps(options = { onMessage: message => { } }) {
    return this.getCompetence(BASE_CORPS_A_CORPS.name, options) ?? BASE_CORPS_A_CORPS
  }

  getCompetencesEsquive(options = { onMessage: message => { } }) {
    return this.getCompetences(BASE_ESQUIVE.name, options) ?? [BASE_ESQUIVE]
  }

  getArmeParade(armeParadeId) {
    return RdDItemArme.getArme(armeParadeId ? this.getEmbeddedDocument('Item', armeParadeId) : undefined)
  }

  isForceInsuffisante(forceRequise) {
    return false
  }

  getPossession(possessionId) {
    return this.itemTypes[ITEM_TYPES.possession].find(it => it.system.possessionid == possessionId);
  }

  getEmpoignades() {
    return this.itemTypes[ITEM_TYPES.empoignade];
  }

  async updateCarac(caracName, to) {
    const path = `system.carac.${caracName}.value`;
    let updates = {};
    updates[path] = Number.parseInt(to)
    await this.update(updates, { noHook: true });
  }

  /* -------------------------------------------- */
  async updateCreatureCompetence(idOrName, fieldName, value) {
    let competence = this.getCompetence(idOrName);
    if (competence) {
      function getFieldPath(fieldName) {
        switch (fieldName) {
          case "niveau": return 'system.niveau'
          case "dommages": return 'system.dommages'
          case "carac_value": return 'system.carac_value'
        }
        return undefined
      }
      const path = getFieldPath(fieldName)
      if (path) {
        await competence.update({ [path]: value })
      }
    }
  }

  /* -------------------------------------------- */
  isDemiReve() {
    return this.getEffectsByStatus(STATUSES.StatusDemiReve).length > 0
  }

  getSurprise(isCombat = undefined, forceRequise = undefined) {
    return StatusEffects.getSurprise(this.getEffects(e => true, isCombat, forceRequise), isCombat)
  }

  /* -------------------------------------------- */
  async computeEtatGeneral() {
    // Par défaut, on ne calcule pas d'état général, seuls les personnages/créatures sont affectés
    this.system.compteurs.etat.value = 0;
  }

  /* -------------------------------------------- */
  async openRollDialog({ name, label, template, rollData, callbacks }) {
    const dialog = await RdDRoll.create(this, rollData,
      { html: template, close: async html => await this._onCloseRollDialog(html) },
      { name: name, label: label, callbacks: [this.createCallbackExperience(), this.createCallbackAppelAuMoral()].concat(callbacks) })
    dialog.render(true)
    return dialog
  }


  /* -------------------------------------------- */
  createCallbackExperience() {
    return { action: r => this.ajoutExperience(r) }
  }

  /* -------------------------------------------- */
  createCallbackAppelAuMoral() {
    /* Si l'appel au moral est utilisé, on l'affiche dans le chat et on diminue éventuellement le moral */
    return { action: r => this.appliquerAppelMoral(r) }
  }

  async ajoutExperience(rollData, hideChatMessage = 'show') { }
  async appliquerAppelMoral(rollData) { }

  async _onCloseRollDialog(html) { }

  async rollCaracCompetence(caracName, compName, diff, options = { title: "" }) {
    RdDEmpoignade.checkEmpoignadeEnCours(this)

    if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
      const competence = this.getCompetence(compName);
      const rollData = {
        ids: { actorId: this.id },
        type: { allowed: DEFAULT_ROLL_TYPES, current: ROLL_TYPE_COMP },
        selected: {
          carac: { key: caracName },
          comp: { key: competence.name },
          diff: { value: diff }
        }
      }
      return await RollDialog.create(rollData, options)
    }

    const competence = this.getCompetence(compName);
    await this.openRollDialog({
      name: 'jet-competence',
      label: competence ? 'Jet ' + Grammar.apostrophe('de', competence.name) : `Jet sans compétence (${compName})`,
      template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.hbs',
      rollData: {
        alias: this.getAlias(),
        carac: this.system.carac,
        selectedCarac: this.getCaracByName(caracName),
        selectedCaracName: caracName,
        diffLibre: diff,
        competence: competence,
        show: { title: options?.title ?? '' }
      },
      callbacks: [
        async r => this.$onRollCompetence(r, options),
        ...(options?.callbacks ?? [])
      ]
    });
  }
  /**
   * Méthode pour faire un jet prédéterminé sans ouvrir la fenêtre de dialogue
   * 
   * @param {*} caracName code ou label de la caractéristique. On peut utiliser 'intel' pour Intellect.
   * @param {*} compName nom de compétence ou nom abrégé.
   * @param {*} diff difficulté (0 si undefined)
   * @param {*} options
   * @returns le jet effectué
   */
  async doRollCaracCompetence(caracName, compName, diff, options = { title: "" }) {
    const carac = this.getCaracByName(caracName);
    if (!carac) {
      ui.notifications.warn(`${this.name} n'a pas de caractéristique correspondant à ${caracName}`)
      return
    }
    const competence = this.getCompetence(compName);
    let rollData = {
      alias: this.getAlias(),
      caracValue: Number(carac.value),
      selectedCarac: carac,
      competence: competence,
      diffLibre: diff ?? 0,
      current: { rollmode: { key: game.settings.get("core", "messageMode") } },
      show: { title: options?.title ?? '' }
    }
    RollDataAjustements.calcul(rollData, this);
    await RdDResolutionTable.rollData(rollData);
    this.ajoutExperience(rollData);
    await RdDRollResult.displayRollData(rollData, this)
    return rollData.rolled;
  }

  /* -------------------------------------------- */
  async roll() {
    if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
      const rollData = {
        ids: { actorId: this.id },
        type: {
          allowed: DEFAULT_ROLL_TYPES,
          current: ROLL_TYPE_COMP,
        },
        selected: {
          diff: { type: DIFF.DEFAUT }
        }
      }
      return await RollDialog.create(rollData)
    }
    RdDEmpoignade.checkEmpoignadeEnCours(this)

    const carac = this.getCarac()
    const selectedCaracName = ['apparence', 'perception', 'force', 'reve'].find(it => carac[it] != undefined)

    await this.openRollDialog({
      name: 'jet-quelconque',
      label: 'Jet',
      template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.hbs',
      rollData: {
        alias: this.getAlias(),
        carac: carac,
        selectedCarac: carac[selectedCaracName],
        selectedCaracName: selectedCaracName,
        competences: this.itemTypes['competence']
      },
      callbacks: [{ action: r => this.$onRollCaracResult(r) }]
    })
  }
  /* -------------------------------------------- */
  async rollReveResistance(diff = -8) {
    return await RollDialog.create(
      {
        ids: { actorId: this.id },
        type: {
          allowed: [ROLL_TYPE_REVE_RESISTANCE],
          current: ROLL_TYPE_REVE_RESISTANCE,
          resistance: true
        },
        selected: {
          carac: { key: CARACS.REVE_ACTUEL, forced: true },
          comp: { key: undefined, forced: true },
          diff: { type: DIFF.DEFAUT, value: diff }
        }
      },
      {
        onRollDone: (dialog, roll) => RollDialog.onRollDoneClose(dialog, roll)
      })
  }

  async rollReveActuel({ diff = 0, resistance = false }) {
    if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
      const rollData = {
        ids: { actorId: this.id },
        type: {
          allowed: [ROLL_TYPE_REVE_RESISTANCE],
          current: ROLL_TYPE_REVE_RESISTANCE,
          resistance: resistance
        },
        selected: {
          carac: { key: CARACS.REVE_ACTUEL, forced: true },
          comp: resistance ? { key: undefined, forced: true } : undefined,
          diff: { type: DIFF.DEFAUT, value: diff }
        }
      }
      return await RollDialog.create(rollData)
    }
    return this.rollCarac(CARACS.REVE_ACTUEL, { diff, resistance })
  }

  async rollCarac(caracName, options = {}) {
    if (Grammar.equalsInsensitive(caracName, CARACS.TAILLE)) {
      return
    }
    if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
      const rollData = {
        ids: { actorId: this.id },
        type: {
          allowed: options.resistance ? [ROLL_TYPE_COMP] : DEFAULT_ROLL_TYPES,
          current: options.resistance ? ROLL_TYPE_COMP : ROLL_TYPE_COMP,
          resistance: options.resistance
        },
        selected: {
          carac: { key: caracName },
          comp: options.resistance ? { key: undefined, forced: true } : undefined,
          diff: { type: DIFF.DEFAUT, value: options.diff ?? 0 }
        }
      }
      return await RollDialog.create(rollData, options)
    }

    foundry.utils.mergeObject(options, { resistance: false, diff: 0 }, { overwrite: false })
    RdDEmpoignade.checkEmpoignadeEnCours(this)
    let selectedCarac = this.getCaracByName(caracName)
    const title = 'Jet ' + Grammar.apostrophe('de', selectedCarac.label);
    const jetResistance = options.resistance ? caracName : undefined;
    await this.openRollDialog({
      name: 'jet-' + caracName,
      label: title,
      template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.hbs',
      rollData: {
        alias: this.getAlias(),
        selectedCarac: selectedCarac,
        competences: this.itemTypes['competence'],
        diffLibre: options.diff ?? 0,
        jetResistance: jetResistance
      },
      callbacks: [{ action: r => this.$onRollCaracResult(r) }]
    });
  }

  /* -------------------------------------------- */
  async $onRollCaracResult(rollData) {
    // Final chat message
    await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-general.hbs');
  }

  async rollCompetence(idOrName, options = { tryTarget: true, arme: undefined }) {
    RdDEmpoignade.checkEmpoignadeEnCours(this)
    const competence = this.getCompetence(idOrName);
    if (OptionsAvancees.isUsing(ROLL_DIALOG_V2)) {
      const rollData = {
        ids: { actorId: this.id },
        type: { allowed: DEFAULT_ROLL_TYPES },
        selected: {
          carac: competence.type == ITEM_TYPES.competencecreature ? { key: competence.name } : undefined,
          comp: { key: competence.name },
          diff: { type: DIFF.LIBRE, value: competence.system.default_diffLibre ?? 0 },
        }
      }
      return await RollDialog.create(rollData)
    }

    let rollData = {
      carac: this.system.carac,
      competence: competence,
      arme: options.arme
    }
    if (competence.type == ITEM_TYPES.competencecreature) {
      const token = RdDUtility.getSelectedToken(this)
      const arme = MappingCreatureArme.armeCreature(competence)
      if (arme && options.tryTarget && Targets.hasTargets()) {
        Targets.selectOneTargetToken(target => {
          if (arme.action == "possession") {
            RdDPossession.onAttaquePossession(target, this, competence)
          }
          else {
            PasseArme.rddCombatTarget(target, this, token).attaque(competence, arme)
          }
        });
        return
      }
      // Transformer la competence de créature
      MappingCreatureArme.setRollDataCreature(rollData)
    }
    const dialogLabel = 'Jet ' + Grammar.apostrophe('de', competence.name);
    await this.openRollDialog({
      name: 'jet-competence',
      label: dialogLabel,
      template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.hbs',
      rollData: rollData,
      callbacks: [{ action: r => this.$onRollCompetence(r, options) }]
    });
  }

  async $onRollCompetence(rollData, options) {
    await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-competence.hbs')
    if (options?.onRollAutomate) {
      options.onRollAutomate(rollData);
    }
  }

  rollAttaque(token) {
    token = token ?? RdDUtility.getSelectedToken(this)

    if (Targets.hasTargets()) {
      Targets.selectOneTargetToken(target => {
        if (Targets.isTargetEntite(target)) {
          ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée!!!!`)
          return
        }

        PasseArme.rddCombatTarget(target, this, token).attaqueV2();
      })
    }
    else {
      return RdDConfirm.confirmer({
        settingConfirmer: "confirmer-combat-sans-cible",
        content: `<p>Voulez vous faire une attaque sans choisir de cible valide?
                  <br>Tous les jets de combats devront être gérés à la main
                  </p>`,
        title: 'Ne pas utiliser les automatisation de combat',
        buttonLabel: "Pas d'automatisation",
        onAction: async () => {
          const rollData = {
            ids: { actorId: this.id, actorTokenId: token?.id, },
            type: {
              allowed: [ROLL_TYPE_ATTAQUE], current: ROLL_TYPE_ATTAQUE
            }
          }
          return await RollDialog.create(rollData, { onRollDone: RollDialog.onRollDoneClose })
        }
      })
    }
  }

  /** --------------------------------------------
   * @param {*} arme item d'arme/compétence de créature
   * @param {*} maniement catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession
   * @returns
   */
  async rollArme(arme, maniement = 'competence', token = undefined) {
    token = token ?? RdDUtility.getSelectedToken(this)
    const compToUse = RdDItemArme.getCompetenceArme(arme, maniement)

    if (!RdDItemArme.isUtilisable(arme)) {
      ui.notifications.warn(`Arme inutilisable: ${arme.name} non équipée ou avec une résistance de 0 ou moins`)
      return
    }

    if (!Targets.hasTargets()) {
      RdDConfirm.confirmer({
        settingConfirmer: "confirmer-combat-sans-cible",
        content: `<p>Voulez vous faire un jet de ${compToUse} sans choisir de cible valide?
                  <br>Tous les jets de combats devront être gérés à la main
                  </p>`,
        title: 'Ne pas utiliser les automatisation de combat',
        buttonLabel: "Pas d'automatisation",
        onAction: async () => {
          this.rollCompetence(compToUse, { tryTarget: false, arme: arme })
        }
      })
      return
    }

    Targets.selectOneTargetToken(target => {
      if (Targets.isTargetEntite(target)) {
        ui.notifications.warn(`Vous ne pouvez pas attaquer une entité non incarnée avec votre ${arme.name}!!!!`);
        return
      }

      const comp = this.getCompetence(compToUse)
      if (comp.isCompetencePossession()) {
        // TODO: vérifier si c'est possible, sinon simplifier
        return RdDPossession.onAttaquePossession(target, this, comp);
      }
      PasseArme.rddCombatTarget(target, this, token).attaque(comp, arme, maniement)
    })
  }

  rollPossession() {
    RdDPossessionV2.rollAttaquePossession(this)
  }

  async verifierForceMin(item) { }

  /* -------------------------------------------- */
  async encaisser() { await RdDEncaisser.encaisser(this) }

  async encaisserDommages(dmg, attacker = undefined, show = undefined, attackerToken = undefined, defenderToken = undefined) {
    if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
      return
    }
    if (!Misc.isOwnerPlayer(this)) {
      return RdDBaseActor.remoteActorCall({
        tokenId: attackerToken?.id ?? this.token?.id,
        actorId: this.id,
        method: 'encaisserDommages', args: [dmg, attacker, show, attackerToken, defenderToken]
      })
    }

    const armure = await this.computeArmure(dmg)
    if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) {
      await this.encaisserDommagesValidationGR(dmg, armure, show, attackerToken, defenderToken);
    }
    else {
      const jet = await RdDUtility.jetEncaissement(this, dmg, armure, { showDice: SHOW_DICE });
      await this.$onEncaissement(jet, show, attackerToken, defenderToken)
    }
  }

  async encaisserDommagesValidationGR(dmg, armure, show, attackerToken, defenderToken) {
    if (!game.user.isGM) {
      RdDBaseActor.remoteActorCall({
        tokenId: this.token?.id,
        actorId: this.id,
        method: 'encaisserDommagesValidationGR', args: [dmg, armure, show, attackerToken, defenderToken]
      })
    } else {
      DialogValidationEncaissement.validerEncaissement(this, dmg, armure,
        jet => this.$onEncaissement(jet, show, attackerToken, defenderToken));
    }
  }
  async onAppliquerJetEncaissement(encaissement, attackerToken) { }

  async $onEncaissement(encaissement, show, attackerToken, defenderToken) {
    await this.onAppliquerJetEncaissement(encaissement, attackerToken);

    foundry.utils.mergeObject(encaissement, {
      alias: defenderToken?.name ?? this.getAlias(),
      hasPlayerOwner: this.hasPlayerOwner,
      show: show ?? {}
    }, { overwrite: false });

    await ChatMessage.create(ChatUtility.adaptVisibility(
      {
        roll: encaissement.roll,
        content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.hbs', encaissement)
      },
      { actor: this }))

    if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
      encaissement = foundry.utils.deepClone(encaissement)
      encaissement.isGM = true
      await ChatMessage.create({
        whisper: ChatUtility.getGMs(),
        content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.hbs', encaissement)
      })
    }
  }

  async encaisserRecul(force, dmgArme = 0) {
    const diffRecul = this.getTaille() - force - dmgArme
    const rolled = await RdDResolutionTable.roll(10, diffRecul)
    if (rolled.isSuccess) {
      return 'encaisse'
    }
    if (rolled.isETotal || (await this.rollEquilibre(diffRecul)).isEchec) {
      await this.setEffect(STATUSES.StatusProne, true)
      return 'chute'
    }
    return 'recul'
  }

  /* -------------------------------------------- */
  async rollEquilibre(diff) {
    // TODO: accrobatie optionnelle sur jet d'équilibre?
    if (ReglesOptionnelles.isSet('acrobatie-pour-recul')) {
      diff += Math.max(0, this.getCompetence('acrobatie')?.system.niveau ?? 0)
    }
    return await RdDResolutionTable.roll(this.getAgilite(), diff);
  }

  /* -------------------------------------------- */
  async accorder(entite, when = 'avant-encaissement') {
    if (when != game.settings.get(SYSTEM_RDD, "accorder-entite-cauchemar")
      || entite == undefined
      || !entite.isEntiteIncarnee()
      || entite.isEntiteAccordee(this)) {
      return true
    }
    const rolled = await RdDResolutionTable.roll(this.getReveActuel(), - Number(entite.getNiveau()))
    const rollData = {
      alias: this.getAlias(),
      rolled: rolled,
      entite: entite.name,
      selectedCarac: this.system.carac.reve
    };

    if (rolled.isSuccess) {
      await entite.setEntiteReveAccordee(this)
    }

    await RdDRollResult.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.hbs')
    await this.ajoutExperience(rollData, true)
    return rolled.isSuccess;
  }

  isEntiteAccordee(attacker) { return true }

  async setEntiteReveAccordee(actor) {
    ui.notifications.error("Impossible de s'accorder à " + this.getAlias() + ": ce n'est pas une entité incarnée");
  }

}
