import { ChatVente } from "../achat-vente/chat-vente.js";
import { ChatUtility } from "../chat-utility.js";
import { renderTemplate, SYSTEM_SOCKET_ID } from "../constants.js";
import { Grammar } from "../grammar.js";
import { Monnaie } from "../item-monnaie.js";
import { ITEM_TYPES } from "../constants.js";
import { Misc } from "../misc.js";
import { RdDAudio } from "../rdd-audio.js";
import { RdDConfirm } from "../rdd-confirm.js";
import { RdDUtility } from "../rdd-utility.js";
import { SystemCompendiums } from "../settings/system-compendiums.js";
import { RdDItem } from "../item.js";
import { StatusEffects, STATUSES } from "../settings/status-effects.js";
import { Apprecier } from "../moral/apprecier.mjs";
import { CARACS } from "../rdd-carac.js";

export class RdDBaseActor extends Actor {

  static _findCaracNode(carac, name) {
    return Object.entries(carac)
      .filter(it => Grammar.equalsInsensitive(it[1].label, name))
      .map(it => it[0])
      .find(it => it)
  }

  static $findCaracByName(carac, name) {
    const caracList = Object.entries(carac)
    let entry = Misc.findFirstLike(name, caracList, { mapper: it => it[0], description: 'caractéristique', onMessage: m => { } });
    if (!entry || entry.length == 0) {
      entry = Misc.findFirstLike(name, caracList, { mapper: it => it[1].label, description: 'caractéristique' });
    }
    return entry && entry.length > 0 ? carac[entry[0]] : undefined;
  }

  static getDefaultValue(actorType, path) {
    if (path.includes('.')) {
      path = path.split('.')
    }
    let obj = game.model.Actor[actorType]
    for (let p of path) {
      obj = obj ? obj[p] : undefined
    }
    return obj
  }

  static getDefaultImg(itemType) {
    return game.system.rdd.actorClasses[itemType]?.defaultIcon ?? defaultItemImg[itemType];
  }

  static init() {
    Handlebars.registerHelper('actor-isFeminin', actor => actor.isFeminin())
    Hooks.on("createItem", (item, options, id) => Misc.documentIfResponsible(item.parent)?.onCreateItem(item, options, id))
    Hooks.on("updateItem", (item, updates, options, id) => Misc.documentIfResponsible(item.parent)?.onUpdateItem(item, updates, options, id))
    Hooks.on("deleteItem", (item, options, id) => Misc.documentIfResponsible(item.parent)?.onDeleteItem(item, options, id))
  }

  static onSocketMessage(sockmsg) {
    switch (sockmsg.msg) {
      case "msg_remote_actor_call":
        return RdDBaseActor.onRemoteActorCall(sockmsg.data, sockmsg.userId);
    }
  }

  static remoteActorCall(callData, userId = undefined) {
    if (game.user.isGM) {
      RdDBaseActor.onRemoteActorCall(callData, game.user.id)
      return false
    }
    else {
      const gmUserId = Misc.firstConnectedGMId()
      if (gmUserId) {
        game.socket.emit(SYSTEM_SOCKET_ID, {
          msg: "msg_remote_actor_call",
          data: callData,
          userId: gmUserId
        })
        return true
      }
      return false
    }
  }

  static onRemoteActorCall(callData, userId) {
    if (userId == game.user.id) {
      // Seul le joueur choisi effectue l'appel: le joueur courant si propriétaire de l'actor, ou le MJ sinon
      const actor = RdDBaseActor.getRealActor(callData?.actorId, callData?.tokenId);
      const args = callData.args;
      console.info(`RdDBaseActor.onRemoteActorCall: pour l'Actor ${callData.actorId}, appel de RdDBaseActor.${callData.method}(`, ...args, ')');
      actor[callData.method](...args);
    }
  }

  static getRealActor(actorId, tokenId) {
    const actor = tokenId ? canvas?.tokens?.get(tokenId)?.actor : undefined
    return actor ?? game.actors.get(actorId)
  }

  static extractActorMin(actor) { return { id: actor?.id, type: actor?.type, name: actor?.name, img: actor?.img } }

  /**
   * Cette methode surcharge Actor.create() pour ajouter si besoin des Items par défaut:
   * compétences et monnaies.
   *
   * @param {Object} actorData template d'acteur auquel ajouter des informations.
   * @param {Object} options   optionspour customiser la création
   */
  static async create(actorData, options) {
    // import depuis un compendium
    if (actorData instanceof Array) {
      return super.create(actorData, options);
    }
    // Création d'un acteur avec des items (uniquement en cas de duplication): pas besoin d'ajouter d'items
    if (actorData.items) {
      return await super.create(actorData, options);
    }
    actorData.items = [];
    if (actorData.type == "personnage") {
      const competences = await SystemCompendiums.getCompetences(actorData.type);
      actorData.items = actorData.items.concat(competences.map(i => i.toObject()))
        .concat(Monnaie.monnaiesStandard());
    }
    else if (actorData.type == "commerce") {
      actorData.items = actorData.items.concat(Monnaie.monnaiesStandard());
    }
    return super.create(actorData, options);
  }

  constructor(docData, context = {}) {
    if (!context.rdd?.ready) {
      foundry.utils.mergeObject(context, { rdd: { ready: true } });
      const ActorConstructor = game.system.rdd.actorClasses[docData.type];
      if (ActorConstructor) {
        if (!docData.img) {
          docData.img = ActorConstructor.defaultIcon;
        }
        return new ActorConstructor(docData, context);
      }
    }
    context.rdd = undefined
    super(docData, context);
  }

  getAlias() {
    if (this.token?.name != null && this.token != this.prototypeToken) {
      return this.token.name
    }
    return this.name
  }

  getCarac() {
    return foundry.utils.deepClone(this.system.carac)
  }

  findCaracByName(name) {
    name = Grammar.toLowerCaseNoAccent(name)
    switch (name) {
      case 'reve actuel':
        name = CARACS.REVE_ACTUEL
        break
      case 'chance actuelle':
        name = CARACS.CHANCE_ACTUELLE
        break
    }
    const carac = this.getCarac()
    return RdDBaseActor.$findCaracByName(carac, name);
  }

  getCaracCompetenceCreature() {
    return this.isCreatureOuEntite()
      ? Object.fromEntries(this.itemTypes[ITEM_TYPES.competencecreature].map(it => [Grammar.toLowerCaseNoAccent(it.name), { label: it.name, value: it.system.carac_value }]))
      : {}
  }

  mapCarac(caracCode) { return caracCode }

  getCaracByName(name) {
    name = this.mapCarac(Grammar.toLowerCaseNoAccent(name)) ?? name
    switch (name) {
      case 'reve actuel':
        name = CARACS.REVE_ACTUEL
        break
      case 'chanceactuelle':
        name = CARACS.CHANCE_ACTUELLE
        break
    }
    switch (name) {
      case CARACS.REVE_ACTUEL:
        return this.getCaracReveActuel()[CARACS.REVE_ACTUEL]
      case CARACS.CHANCE_ACTUELLE:
        return this.getCaracChanceActuelle()[CARACS.CHANCE_ACTUELLE]
    }
    return this.findCaracByName(name);
  }

  /* -------------------------------------------- */
  async _preCreate(data, options, user) {
    await super._preCreate(data, options, user);

    // Configure prototype token settings
    if (this.type === "personnage") {
      this.updateSource({
        sight: { enabled: true },
        actorLink: options.fromCompendium ? data.prototypeToken.actorLink : true,
        disposition: CONST.TOKEN_DISPOSITIONS.FRIENDLY
      })
    } else {
      const prototypeToken = {
        sight: { enabled: true },
        disposition: CONST.TOKEN_DISPOSITIONS.NEUTRAL
      }
      this.updateSource({ prototypeToken });
    }
  }

  async _preUpdate(changed, options, user) {
    const updatedCarac = changed?.system?.carac
    if (updatedCarac && (updatedCarac.force || updatedCarac.reve || updatedCarac.taille)) {
      await this.setEffect(STATUSES.StatusSurEnc, this.isSurenc())
    }
    await this.delayedRenderSheet('_preUpdate', changed, options)
    return super._preUpdate(changed, options, user)
  }
  
  _onUpdate(changed, options, userId) {
    super._onUpdate(changed, options, userId)
    if (userId == game.user.id){
      this.delayedRenderSheet('_onUpdate', changed, options)
    }
  }

  async delayedRenderSheet(caller, data, options) {
    this.refreshDelayCounter = (this.refreshDelayCounter ?? 0)+1
    if (this.refreshDelayCounter == 1) {
      this.renderAfterDelay(this.refreshDelayCounter)
    }
  }

  renderAfterDelay(currentCounter) {
    setTimeout(async () => {
      if (currentCounter == this.refreshDelayCounter) {
        this.sheet?.render()
        this.refreshDelayCounter = 0
      }
      else {
        this.renderAfterDelay(this.refreshDelayCounter)
      }
    }, 50)
  }

  /* -------------------------------------------- */
  prepareData() {
    super.prepareData()
    this.cleanupConteneurs()
    this.computeEtatGeneral()
    this.computeEncTotal()
  }

  async computeEtatGeneral() { }

  /* -------------------------------------------- */
  findPlayer() {
    return game.users.players.find(player => player.active && player.character?.id == this.id);
  }

  isPersonnageJoueur() { return false }
  isCreatureOuEntite() { return this.isCreature() || this.isEntite() }
  isCreature() { return false }
  isEntite() { return false }
  isEntiteIncarnee() { return false }
  isEntiteNonIncarnee() { return false }
  isEntiteBlurette() { return false }
  isHautRevant() { return false }
  isVehicule() { return false }
  isPersonnage() { return false }
  isFeminin() { return false }
  getItem(id, type = undefined) {
    const item = this.items.get(id);
    if (type == undefined || (item?.type == type)) {
      return item;
    }
    return undefined;
  }

  listeSuivants(filter = suivant => true) { return [] }
  listeSuivants(filter = suivant => true) { return [] }
  listItems(type = undefined) { return (type ? this.itemTypes[type] : this.items); }
  filterItems(filter, type = undefined) { return (type ? this.itemTypes[type] : this.items)?.filter(filter) ?? []; }
  findItemLike(idOrName, type) {
    return this.getItem(idOrName, type)
      ?? Misc.findFirstLike(idOrName, this.listItems(type), { description: Misc.typeName('Item', type) });
  }

  getMonnaie(id) { return this.findItemLike(id, 'monnaie'); }
  getEncombrementMax() { return 0 }
  isSurenc() { return false }

  /* -------------------------------------------- */
  isEffectAllowed(effectId) { return false }

  getEffects(filter = e => true, forceRequise = undefined) {
    const effects = this.getEmbeddedCollection("ActiveEffect")
    const selected = effects.filter(filter)
    if (forceRequise && this.isForceInsuffisante(forceRequise)) {
      selected.push(StatusEffects.prepareActiveEffect(STATUSES.StatusForceWeak))
    }
    return selected
  }

  getEffectsByStatus(effectId) {
    return this.getEffects().filter(it => it.statuses.has(effectId))
  }

  async setEffect(effectId, status, options = { render: true }) {
    if (this.isEffectAllowed(effectId)) {
      const effects = this.getEffectsByStatus(effectId)
      if (!status && effects.length > 0) {
        await this.deleteEmbeddedDocuments('ActiveEffect', effects.map(it => it.id), options)
      }
      if (status && effects.length == 0) {
        await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(effectId)], options)
      }
    }
  }

  async removeEffect(id) {
    this.removeEffects(it => it.id == id)
  }

  async removeEffects(filter = e => true, options = { render: true }) {
    if (game.user.isGM) {
      const ids = this.getEffects(filter)
        .filter(it => this.canRemoveEffects(it))
        .map(it => it.id)
      if (ids.length > 0) {
        await this.deleteEmbeddedDocuments('ActiveEffect', ids, options)
      }

    }
  }
  canRemoveEffects(effect) {
    if (effect.statuses.has(STATUSES.StatusSurEnc)) {
      return !this.isSurenc()
    }
    if (effect.statuses.has(STATUSES.StatusDemiReve)) {
      return !this.tmrApp
    }
    return true
  }

  /* -------------------------------------------- */
  async updateCarac(caracName, to) {
  }

  /* -------------------------------------------- */


  async onCreateItem(item, options, id) {
  }

  async onUpdateItem(item, updates, options, id) {
    const conteneur = item.findConteneur()
    conteneur?.render(options.render)
  }

  async onDeleteItem(item, options, id) {
    if (item.isInventaire()) {
      await this._removeItemFromConteneur(item)
    }
  }

  async _removeItemFromConteneur(item) {
    const conteneur = this.findConteneur(item);
    if (conteneur) {
      const nouveauContenu = conteneur.system.contenu.filter(id => id != item.id)
      const updates = { _id: conteneur.id, 'system.contenu': nouveauContenu }
      if (updates.length > 0) {
        await this.updateEmbeddedDocuments('Item', updates)
      }
    }
  }

  async onTimeChanging(oldTimestamp, newTimestamp) {
    await Promise.all(this.items.filter(it => it.isFinPeriode(oldTimestamp, newTimestamp))
      .map(async it => await it.onFinPeriodeTemporel(oldTimestamp, newTimestamp)))
  }

  async creerObjetParMJ(object) {
    if (this.isOwner) {
      await this.createEmbeddedDocuments('Item', [object])
      return
    }
    RdDBaseActor.remoteActorCall({
      tokenId: this.token?.id,
      actorId: this.id,
      method: 'creerObjetParMJ', args: [object]
    })
  }

  /* -------------------------------------------- */
  async cleanupConteneurs() {
    if (Misc.isOwnerPlayer(this)) {
      let updates = this.itemTypes[ITEM_TYPES.conteneur]
        .filter(c => c.system.contenu.filter(id => this.getItem(id) == undefined).length > 0)
        .map(c => { return { _id: c.id, 'system.contenu': c.system.contenu.filter(id => this.getItem(id) != undefined) } });
      if (updates.length > 0) {
        await this.updateEmbeddedDocuments("Item", updates)
      }
    }
  }

  /* -------------------------------------------- */
  getFortune() {
    return Monnaie.getFortune(this.itemTypes[ITEM_TYPES.monnaie]);
  }

  /* -------------------------------------------- */
  async itemQuantiteIncDec(id, value) {
    let item = this.getItem(id);
    if (item && item.isInventaire()) {
      const quantite = Math.max(0, item.system.quantite + value);
      await item.update({ 'system.quantite': quantite });
    }
  }

  computePrixTotalEquipement() {
    return this.items.filter(it => it.isInventaire())
      .filter(it => !it.isMonnaie())
      .map(it => it.valeurTotale())
      .reduce(Misc.sum(), 0);
  }

  async payerSols(depense) {
    depense = Number(depense);
    if (depense == 0) {
      return;
    }
    let fortune = this.getFortune();
    console.log("payer", game.user.character, depense, fortune);
    // TODO: passer en handlebars
    let msg = "";
    if (fortune >= depense) {
      await Monnaie.optimiserFortune(this, fortune - depense);
      msg = `Vous avez payé <strong>${depense} Sols</strong>, qui ont été soustraits de votre argent.`;
      RdDAudio.PlayContextAudio("argent"); // Petit son
    } else {
      msg = "Vous n'avez pas assez d'argent pour payer cette somme !";
    }

    await ChatMessage.create({
      whisper: ChatUtility.getOwners(this),
      content: msg
    })
  }

  async depenserSols(sols) {
    if (!this.isOwner) {
      return RdDBaseActor.remoteActorCall({
        userId: Misc.connectedGMOrUser(),
        tokenId: this.token?.id,
        actorId: this.id,
        method: 'depenserSols', args: [sols]
      })
    }
    let reste = this.getFortune() - Number(sols);
    if (reste >= 0) {
      await Monnaie.optimiserFortune(this, reste);
    }
    return reste;
  }

  async ajouterSols(sols, fromActorId = undefined) {
    sols = Number(sols)
    if (sols == 0) {
      return
    }
    if (sols < 0) {
      ui.notifications.error(`Impossible d'ajouter un gain de ${sols} <0`)
      return
    }
    if (fromActorId && !this.isOwner) {
      return RdDBaseActor.remoteActorCall({
        userId: Misc.connectedGMOrUser(),
        tokenId: this.token?.id,
        actorId: this.id,
        method: 'ajouterSols', args: [sols, fromActorId]
      })
    }

    const fromActor = game.actors.get(fromActorId)
    await Monnaie.optimiserFortune(this, sols + this.getFortune());

    RdDAudio.PlayContextAudio("argent"); // Petit son
    await ChatMessage.create({
      whisper: ChatUtility.getOwners(this),
      content: `Vous avez reçu <strong>${sols} Sols</strong> ${fromActor ? " de " + fromActor.name : ''}, qui ont été ajoutés à votre argent.`
    })
  }

  /* -------------------------------------------- */

  getQuantiteDisponible(item) {
    return item?.isService() ? undefined : item?.getQuantite();
  }

  /* -------------------------------------------- */
  async achatVente(achat) {
    if (achat.vendeurId == achat.acheteurId) {
      ui.notifications.info("Inutile de se vendre à soi-même")
      return
    }
    const cout = Number(achat.prixTotal ?? 0)
    const acheteur = achat.acheteurId ? game.actors.get(achat.acheteurId) : undefined

    if (acheteur && !acheteur.isOwner) {
      ui.notifications.warn(`${game.user.id} n'est pas propriétaire de ${this.name} et effectue un achat pour lui!`)
      RdDBaseActor.remoteActorCall({
        actorId: achat.acheteurId,
        method: 'achatVente', args: [achat]
      });
      return
    }


    const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined
    const quantite = (achat.choix.nombreLots ?? 1) * (achat.vente.tailleLot);
    const itemVendu = vendeur?.getItem(achat.vente.item._id) ?? game.items.get(achat.vente.item._id);
    if (!itemVendu) {
      ChatUtility.notifyUser(achat.userId, 'warn', vendeur ? `Le vendeur n'a pas plus de ${achat.vente.item.name} !` : `Impossible de retrouver: ${achat.vente.item.name} !`);
      return
    }
    if (vendeur && !vendeur.verifierQuantite(itemVendu, quantite)) {
      ChatUtility.notifyUser(achat.userId, 'warn', `Le vendeur n'a pas assez de ${itemVendu.name} !`);
      return
    }
    if (acheteur && !acheteur.verifierFortune(cout)) {
      ChatUtility.notifyUser(achat.userId, 'warn', `Vous n'avez pas assez d'argent pour payer ${Math.ceil(cout / 100)} sols !`);
      return
    }
    await vendeur?.vendre(itemVendu, quantite, cout, acheteur);
    await acheteur?.acheter(itemVendu, quantite, cout, achat, vendeur)

    if (cout > 0) {
      RdDAudio.PlayContextAudio("argent");
    }
    const chatAchatItem = foundry.utils.deepClone(achat.vente);
    chatAchatItem.quantiteTotal = quantite;
    await ChatMessage.create({
      user: achat.userId,
      speaker: { alias: (acheteur ?? vendeur).getAlias() },
      whisper: ChatUtility.getOwners(this),
      content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-achat-item.hbs', chatAchatItem)
    });

    if (!achat.vente.quantiteIllimite) {
      await this.updateMessageVente(achat.chatMessageIdVente, achat.choix.nombreLots);
    }
  }

  async updateMessageVente(chatMessageIdVente, nombreLots) {
    const chatMessage = game.messages.get(chatMessageIdVente)
    if (!chatMessage.isOwner) {
      return RdDBaseActor.remoteActorCall({
        userId: Misc.firstConnectedGMId(),
        actorId: this.id,
        method: 'updateMessageVente', args: [chatMessageIdVente, nombreLots]
      })
    }
    await ChatVente.diminuerQuantiteAchatVente(chatMessageIdVente, nombreLots);
  }

  async vendre(item, quantite, cout, acheteur) {
    await this.ajouterSols(cout, acheteur?.id)
    await this.decrementerQuantiteItem(item.id, quantite)
  }

  async acheter(item, quantite, cout, achat) {
    await this.depenserSols(cout)
    if (!this.isOwner) {
      ui.notifications.warn(`${game.user.id} n'est pas propriétaire de ${this.name} et effectue un achat!`)
    }
    const createdItemId = await this.creerQuantiteItem(item, quantite)
    if (item.type == ITEM_TYPES.nourritureboisson && achat.choix.consommer && createdItemId != undefined) {
      achat.choix.doses = achat.choix.nombreLots;
      await this.consommerNourritureboisson(createdItemId, achat.choix, achat.vente.actingUserId);
    }
    if (item.type == ITEM_TYPES.service) {
      new Apprecier(this, item.system.appreciation, item.system.qualite).apprecier()
    }
  }

  verifierFortune(cout) {
    return this.getFortune() >= cout;
  }

  verifierQuantite(item, quantiteDemande) {
    const disponible = this.getQuantiteDisponible(item);
    return disponible == undefined || disponible >= quantiteDemande;
  }

  async consommerNourritureboisson(itemId, choix, userId) { }

  async decrementerQuantiteItem(itemId, quantite, options = { supprimerSiZero: true }) {
    const item = this.items.get(itemId)
    if (item.isService()) {
      return
    }

    if (!this.isOwner) {
      return RdDBaseActor.remoteActorCall({
        userId: Misc.connectedGMOrUser(),
        tokenId: this.token?.id,
        actorId: this.id,
        method: 'decrementerQuantiteItem', args: [itemId, quantite, options]
      })
    }

    const resteQuantite = Math.max((item.system.quantite ?? 1) - quantite, 0)
    if (resteQuantite <= 0 && options.supprimerSiZero) {
      await this.deleteEmbeddedDocuments("Item", [itemId]);
    }
    else {
      await this.updateEmbeddedDocuments("Item", [{ _id: itemId, 'system.quantite': resteQuantite }])
    }
  }

  async creerQuantiteItem(item, quantite) {
    if (!this.canReceive(item)) {
      return
    }

    const isItemEmpilable = "quantite" in item.system;
    const baseItem = {
      type: item.type,
      img: item.img,
      name: item.name,
      system: foundry.utils.mergeObject(item.system, { quantite: isItemEmpilable ? quantite : undefined }, { inplace: false })
    }

    const newItems = isItemEmpilable ? [baseItem] : Array.from({ length: quantite }, (_, i) => baseItem);
    const items = await this.createEmbeddedDocuments("Item", newItems);
    return newItems.length > 0 ? items[0].id : undefined;
  }

  /* -------------------------------------------- */
  async computeEncTotal() {
    if (this.pack) {
      this.encTotal = 0
    }
    else {
      const wasSurenc = this.isSurenc()
      this.encTotal = this.items.filter(it => RdDItem.getItemTypesInventaire().includes(it.type))
        .filter(it => it.type !== ITEM_TYPES.conteneur)
        .map(it => it.getEncTotal()).reduce(Misc.sum(), 0)
      const isSurenc = this.isSurenc()
      if (isSurenc != wasSurenc) {
        await this.setEffect(STATUSES.StatusSurEnc, isSurenc)
      }
    }
  }

  getEncTotal() {
    return Number(this.encTotal?.toFixed(2) ?? 0.00)
  }

  async createItem(type, name = undefined) {
    if (!name) {
      name = 'Nouveau ' + Misc.typeName('Item', type);
    }
    await this.createEmbeddedDocuments('Item', [{ name: name, type: type }], { renderSheet: true });
  }

  canReceive(item) {
    return false;
  }

  async processDropItem(params) {
    const targetActorId = this.id
    const sourceActorId = params.sourceActorId
    const sourceTokenId = params.sourceTokenId
    const itemId = params.itemId
    const destId = params.destId
    const srcId = params.srcId
    if (sourceActorId && sourceActorId != targetActorId) {
      console.log("Moving objects", sourceActorId, sourceTokenId, targetActorId, itemId);
      this.moveItemsBetweenActors(itemId, sourceActorId, sourceTokenId);
      return false;
    }
    let result = true;
    const item = this.getItem(itemId);
    if (item?.isInventaire('all') && sourceActorId == targetActorId) {
      // rangement
      if (srcId != destId && itemId != destId) { // déplacement de l'objet
        const src = this.getItem(srcId);
        const dest = this.getItem(destId);
        const cible = this.findConteneurOrParent(dest);
        const [empilable, message] = item.isInventaireEmpilable(dest);
        if (empilable) {
          await dest.empiler(item)
          result = false;
        }
        // changer de conteneur
        else if (!cible || this.conteneurPeutContenir(cible, item)) {
          await this.enleverDeConteneur(item, src, params.onEnleverConteneur);
          await this.ajouterDansConteneur(item, cible, params.onAjouterDansConteneur);
          if (message && !dest.isConteneur()) {
            ui.notifications.info(cible
              ? `${message}<br>${item.name} a été déplacé dans: ${cible.name}`
              : `${message}<br>${item.name} a été sorti du conteneur`);
          }
        }
      }
      await this.computeEncTotal()
    }
    return result;
  }

  findConteneurOrParent(dest) {
    if (!dest || dest.isConteneur()) {
      return dest;
    }
    return this.findConteneur(dest)
  }

  findConteneur(item) {
    return this.itemTypes[ITEM_TYPES.conteneur].find(it => it.system.contenu.includes(item.id));
  }


  /* -------------------------------------------- */
  conteneurPeutContenir(dest, moved) {
    if (!dest) {
      return true;
    }
    if (!dest.isConteneur()) {
      return false;
    }
    if (moved.isConteneurContenu(dest)) {
      ui.notifications.warn(`Impossible de déplacer un conteneur parent (${moved.name}) dans un de ses contenus ${dest.name} !`);
      return false;
    }

    // Calculer le total actuel des contenus
    const encContenu = dest.getEncContenu();
    const newEnc = moved.getEncTotal(); // Calculer le total actuel du nouvel objet
    const placeDisponible = Misc.keepDecimals(dest.system.capacite - encContenu - newEnc, 4)

    // Teste si le conteneur de destination a suffisament de capacité pour recevoir le nouvel objet
    if (placeDisponible < 0) {
      ui.notifications.warn(
        `Le conteneur ${dest.name} a une capacité de ${dest.system.capacite}, et contient déjà ${encContenu}.
        Impossible d'y ranger: ${moved.name} d'encombrement ${newEnc}!`);
      return false;
    }
    return true;
  }

  /* -------------------------------------------- */
  /** Ajoute un item dans un conteneur, sur la base de leurs ID */
  async ajouterDansConteneur(item, conteneur, onAjouterDansConteneur) {
    if (conteneur?.isConteneur()) {
      item.estContenu = true;
      const nouveauContenu = [...conteneur.system.contenu, item.id];
      await conteneur.update({ 'system.contenu': nouveauContenu });
      onAjouterDansConteneur(item.id, conteneur.id)
    }
    else {
      item.estContenu = false;
      await conteneur?.update({ 'system.-=contenu': undefined })
    }
  }

  /* -------------------------------------------- */
  /** Fonction de remise à plat de l'équipement (ie vide les champs 'contenu') */
  async nettoyerConteneurs() {
    RdDConfirm.confirmer({
      settingConfirmer: "confirmation-vider",
      content: `<p>Etes vous certain de vouloir vider tous les conteneurs ?</p>`,
      title: 'Vider les conteneurs',
      buttonLabel: 'Vider',
      onAction: async () => {
        const corrections = [];
        for (let item of this.items) {
          if (item.estContenu) {
            item.estContenu = undefined;
          }
          if (item.system.contenu != undefined) {
            if (item.type == 'conteneur') {
              corrections.push({ _id: item.id, 'system.contenu': [] });
            }
            else {
              corrections.push({ _id: item.id, 'system.-=contenu': undefined });
            }
          }
        }
        if (corrections.length > 0) {
          await this.updateEmbeddedDocuments('Item', corrections);
        }
      }
    });
  }

  /* -------------------------------------------- */
  buildSubConteneurObjetList(conteneurId, deleteList) {
    let conteneur = this.getItem(conteneurId);
    if (conteneur?.type == 'conteneur') { // Si c'est un conteneur
      for (let subId of conteneur.system.contenu) {
        let subObj = this.getItem(subId);
        if (subObj) {
          if (subObj.type == 'conteneur') {
            this.buildSubConteneurObjetList(subId, deleteList);
          }
          deleteList.push({ id: subId, conteneurId: conteneurId });
        }
      }
    }
  }

  /* -------------------------------------------- */
  async deleteAllConteneur(itemId, options) {
    let list = [];
    list.push({ id: itemId, conteneurId: undefined }); // Init list
    this.buildSubConteneurObjetList(itemId, list);
    await this.deleteEmbeddedDocuments('Item', list.map(it => it.id), options);
  }

  /* -------------------------------------------- */
  /**
   * Supprime un item d'un conteneur, sur la base de leurs ID
   */
  async enleverDeConteneur(item, conteneur, onEnleverDeConteneur) {
    if (conteneur) {
      if (conteneur.isConteneur()) {
        const contenu = conteneur.system.contenu.filter(id => id != item.id);
        await conteneur.update({ 'system.contenu': contenu });
        onEnleverDeConteneur();
      }
      else {
        await conteneur.update({ 'system.-=contenu': undefined })
      }
    }
    item.estContenu = false;
  }

  /* -------------------------------------------- */
  async moveItemsBetweenActors(itemId, sourceActorId, sourceTokenId) {
    let sourceActor = RdDBaseActor.getRealActor(sourceActorId, sourceTokenId)
    let itemsList = [{ id: itemId, conteneurId: undefined }]
    sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list

    const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id))
      .map(it => foundry.utils.deepClone(it))
      .map(it => { it.system.contenu = []; return it; });
    let newItems = await this.createEmbeddedDocuments('Item', itemsDataToCreate);

    let itemMap = this._buildMapOldNewId(itemsList, newItems);

    for (let item of itemsList) { // Second boucle pour traiter la remise en conteneurs
      // gestion conteneur/contenu
      if (item.conteneurId) { // l'Objet était dans un conteneur
        const newConteneurId = itemMap[item.conteneurId];
        const newConteneur = this.getItem(newConteneurId);
        const newItemId = itemMap[item.id]; // Get newItem

        console.log('New conteneur filling!', newConteneur, newItemId, item);
        const nouveauContenu = [...newConteneur.system.contenu, newItemId]
        await newConteneur.update({ 'system.contenu': nouveauContenu })
      }
    }
    const deletedItemIds = itemsList.map(it => it.id)
    await sourceActor.deleteEmbeddedDocuments('Item', deletedItemIds);
  }

  _buildMapOldNewId(itemsList, newItems) {
    let itemMap = {};
    for (let i = 0; i < itemsList.length; i++) {
      itemMap[itemsList[i].id] = newItems[i].id; // Pour garder le lien ancien / nouveau
    }
    return itemMap;
  }

  /* -------------------------------------------- */
  async postActorToChat(modeOverride) {
    let chatData = {
      doctype: 'Actor',
      id: this.id,
      type: this.type,
      img: this.img,
      pack: this.pack,
      name: this.getAlias(),
      system: { description: this.system.description }
    }
    renderTemplate('systems/foundryvtt-reve-de-dragon/templates/post-actor.hbs', chatData)
      .then(async html => await ChatMessage.create(RdDUtility.chatDataSetup(html, modeOverride)));
  }

  actionImpossible(action) {
    ui.notifications.info(`${this.getAlias()} ne peut pas faire cette action: ${action}`)
  }
  async doRollCaracCompetence(caracName, compName, diff, options = { title: "" }) {
  }
  ajoutExperience(rollData) { }
  isAlcoolise() { return false }
  async jetEthylisme() { this.actionImpossible("jet d'éthylisme") }
  async rollAppelChance() { this.actionImpossible("appel à la chance") }
  async jetDeMoral() { this.actionImpossible("jet de moral") }

  async creerCopieLue(item) {
    if (item.type !== ITEM_TYPES.livre) return
    const existante = this.items.find(i => i.type === 'livre' && i.system.estCopieLue && i.name === item.name)
    if (existante) return
    const copie = item.toObject()
    copie.system.estCopieLue = true
    copie.system.lu = true
    copie.system.quantite = 0
    delete copie._id
    await this.createEmbeddedDocuments('Item', [copie], { renderSheet: false })
  }

  async resetItemUse() { }
  async incDecItemUse(itemId, shouldIncrease = true) { }
  getItemUse(itemId) { return 0; }

  async finDeRound(options = { terminer: false }) { }
  isActorCombat() { return false }
  getCaracInit(competence) { return 0 }

  listActions() { return [] }

  listActionsPossessions() {
    return this.itemTypes[ITEM_TYPES.possession]
      .filter(it => !it.system.possede)
      .map(p => {
        return {
          label: p.name,
          action: 'possession',
          possessionid: p.system.possessionid,
        }
      })
  }

}