import { ChatUtility } from "../chat-utility.js";
import { SYSTEM_SOCKET_ID } from "../constants.js";
import { Monnaie } from "../item-monnaie.js";
import { Misc } from "../misc.js";
import { RdDAudio } from "../rdd-audio.js";
import { RdDUtility } from "../rdd-utility.js";
import { SystemCompendiums } from "../settings/system-compendiums.js";
import { APP_ASTROLOGIE_REFRESH } from "../sommeil/app-astrologie.js";

export class RdDBaseActor extends Actor {

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

  /* -------------------------------------------- */
  static init() {
    Hooks.on("preUpdateItem", (item, change, options, id) => RdDBaseActor.getParentActor(item)?.onPreUpdateItem(item, change, options, id));
    Hooks.on("createItem", (item, options, id) => RdDBaseActor.getParentActor(item)?.onCreateItem(item, options, id));
    Hooks.on("deleteItem", (item, options, id) => RdDBaseActor.getParentActor(item)?.onDeleteItem(item, options, id));
    Hooks.on("updateActor", (actor, change, options, actorId) => actor.onUpdateActor(change, options, actorId));
  }


  static onSocketMessage(sockmsg) {
    switch (sockmsg.msg) {
      case "msg_remote_actor_call":
        return RdDBaseActor.onRemoteActorCall(sockmsg.data, sockmsg.userId);
      case "msg_reset_nombre_astral":
        game.user.character.resetNombresAstraux();
        game.system.rdd.calendrier.notifyChangeNombresAstraux();
        return;
      case "msg_refresh_nombre_astral":
        Hooks.callAll(APP_ASTROLOGIE_REFRESH);
        return;
      }
  }

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

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

  static getParentActor(document) {
    return document?.parent instanceof Actor ? document.parent : undefined
  }

  /**
   * Cet 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) {
      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);
      }
    }
    super(docData, context);
  }

  isCreatureEntite() { return this.type == 'creature' || this.type == 'entite'; }
  isCreature() { return this.type == 'creature'; }
  isEntite() { return this.type == 'entite'; }
  isPersonnage() { return this.type == 'personnage'; }
  isVehicule() { return this.type == 'vehicule'; }

  getItem(id, type = undefined) {
    const item = this.items.get(id);
    if (type == undefined || (item?.type == type)) {
      return item;
    }
    return undefined;
  }

  listItems(type = undefined) { return (type ? this.itemTypes[type] : this.items); }
  filterItems(filter, type = undefined) { return this.listItems(type)?.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'); }

  recompute() { }


  /* -------------------------------------------- */
  async onPreUpdateItem(item, change, options, id) { }

  async onCreateItem(item, options, id) { }

  async onDeleteItem(item, options, id) { }

  async onUpdateActor(update, options, actorId) { }

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

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

  /* -------------------------------------------- */
  async monnaieIncDec(id, value) {
    let monnaie = this.getMonnaie(id);
    if (monnaie) {
      const quantite = Math.max(0, monnaie.system.quantite + value);
      await this.updateEmbeddedDocuments('Item', [{ _id: monnaie.id, '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);
    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 !";
    }

    let message = {
      whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
      content: msg
    };
    ChatMessage.create(message);
  }

  async depenserSols(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 && !game.user.isGM) {
      RdDBaseActor.remoteActorCall({
        userId: Misc.connectedGMOrUser(),
        actorId: this.id,
        method: 'ajouterSols', args: [sols, fromActorId]
      });
    }
    else {
      const fromActor = game.actors.get(fromActorId)
      await Monnaie.optimiserFortune(this, sols + this.getFortune());

      RdDAudio.PlayContextAudio("argent"); // Petit son
      ChatMessage.create({
        whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
        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;
    }
    if (!Misc.isUniqueConnectedGM()) {
      RdDBaseActor.remoteActorCall({
        actorId: achat.vendeurId ?? achat.acheteurId,
        method: 'achatVente',
        args: [achat]
      });
      return;
    }

    const cout = Number(achat.prixTotal ?? 0);
    const vendeur = achat.vendeurId ? game.actors.get(achat.vendeurId) : undefined;
    const acheteur = achat.acheteurId ? game.actors.get(achat.acheteurId) : undefined;
    const vente = achat.vente;
    const quantite = (achat.choix.nombreLots ?? 1) * (vente.tailleLot);
    const itemVendu = vendeur?.getItem(vente.item._id);
    if (!this.verifierQuantite(vendeur, 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 this.decrementerVente(vendeur, itemVendu, quantite, cout);
    if (acheteur) {
      await acheteur.depenserSols(cout);
      const createdItemId = await acheteur.creerQuantiteItem(vente.item, quantite);
      await acheteur.consommerNourritureAchetee(achat, vente, createdItemId);
    }
    if (cout > 0) {
      RdDAudio.PlayContextAudio("argent");
    }
    const chatAchatItem = duplicate(vente);
    chatAchatItem.quantiteTotal = quantite;
    ChatMessage.create({
      user: achat.userId,
      speaker: { alias: (acheteur ?? vendeur).name },
      whisper: ChatUtility.getWhisperRecipientsAndGMs(this.name),
      content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-achat-item.html', chatAchatItem)
    });

    if (!vente.quantiteIllimite) {
      if (vente.quantiteNbLots <= achat.choix.nombreLots) {
        ChatUtility.removeChatMessageId(achat.chatMessageIdVente);
      }
      else if (achat.chatMessageIdVente) {
        vente["properties"] = itemVendu.getProprietes();
        vente.quantiteNbLots -= achat.choix.nombreLots;
        vente.jsondata = JSON.stringify(vente.item);
        const messageVente = game.messages.get(achat.chatMessageIdVente);
        messageVente.update({ content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-vente-item.html', vente) });
        messageVente.render(true);
      }
    }
  }

  async decrementerVente(vendeur, itemVendu, quantite, cout) {
    if (vendeur) {
      await vendeur.ajouterSols(cout);
      await vendeur.decrementerQuantiteItem(itemVendu, quantite);
    }
  }

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

  verifierQuantite(vendeur, item, quantiteTotal) {
    const disponible = vendeur?.getQuantiteDisponible(item);
    return disponible == undefined || disponible >= quantiteTotal;
  }

  async consommerNourritureAchetee(achat, vente, createdItemId) {
    if (achat.choix.consommer && vente.item.type == 'nourritureboisson' && createdItemId != undefined) {
      achat.choix.doses = achat.choix.nombreLots;
      await this.consommerNourritureboisson(createdItemId, achat.choix, vente.actingUserId);
    }
  }


  async decrementerQuantiteItem(item, quantite, options = { supprimerSiZero: true }) {
    if (item.isService()) {
      return;
    }
    let resteQuantite = (item.system.quantite ?? 1) - quantite;
    if (resteQuantite <= 0) {
      if (options.supprimerSiZero) {
        await this.deleteEmbeddedDocuments("Item", [item.id]);
      }
      else {
        await this.updateEmbeddedDocuments("Item", [{ _id: item.id, 'system.quantite': 0 }]);
      }
      if (resteQuantite < 0) {
        ui.notifications.warn(`La quantité de ${item.name} était insuffisante, l'objet a donc été supprimé`)
      }
    }
    else if (resteQuantite > 0) {
      await this.updateEmbeddedDocuments("Item", [{ _id: item.id, 'system.quantite': resteQuantite }]);
    }
  }

  async creerQuantiteItem(item, quantite) {
    if (this.canReceive(item)) {
      const isItemEmpilable = "quantite" in item.system;
      const baseItem = {
        type: item.type,
        img: item.img,
        name: item.name,
        system: mergeObject(item.system, { quantite: isItemEmpilable ? quantite : undefined })
      };
      const newItems = isItemEmpilable ? [baseItem] : Array.from({ length: quantite }, (_, i) => baseItem);
      const items = await this.createEmbeddedDocuments("Item", newItems);
      return items.length > 0 ? items[0].id : undefined;
    }
  }

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

  getEncombrementMax() {
    return 0;
  }

  async computeEncTotal() {
    if (!this.pack) {
      this.encTotal = this.items.map(it => it.getEncTotal()).reduce(Misc.sum(), 0);
      return this.encTotal;
    }
    return 0;
  }

  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 itemId = params.itemId;
    const destId = params.destId;
    const srcId = params.srcId;
    if (sourceActorId && sourceActorId != targetActorId) {
      console.log("Moving objects", sourceActorId, targetActorId, itemId);
      this.moveItemsBetweenActors(itemId, sourceActorId);
      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.getContenantOrParent(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;
  }

  getContenantOrParent(dest) {
    if (!dest || dest.isConteneur()) {
      return dest;
    }
    return this.getContenant(dest);
  }

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


  /* -------------------------------------------- */
  conteneurPeutContenir(dest, item) {
    if (!dest) {
      return true;
    }
    if (!dest.isConteneur()) {
      return false;
    }
    const destData = dest
    if (this._isConteneurContenu(item, dest)) {
      ui.notifications.warn(`Impossible de déplacer un conteneur parent (${item.name}) dans un de ses contenus ${destData.name} !`);
      return false; // Loop detected !
    }

    // Calculer le total actuel des contenus
    let encContenu = this.getRecursiveEnc(dest) - Number(destData.system.encombrement);
    let newEnc = this.getRecursiveEnc(item); // Calculer le total actuel du nouvel objet

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

  /* -------------------------------------------- */
  _isConteneurContenu(item, conteneur) {
    if (item?.isConteneur()) { // Si c'est un conteneur, il faut vérifier qu'on ne le déplace pas vers un sous-conteneur lui appartenant
      for (let id of item.system.contenu) {
        let subObjet = this.getItem(id);
        if (subObjet?.id == conteneur.id) {
          return true; // Loop detected !
        }
        if (subObjet?.isConteneur()) {
          return this._isConteneurContenu(subObjet, conteneur);
        }
      }
    }
    return false;
  }
  /* -------------------------------------------- */
  getRecursiveEnc(objet) {
    if (!objet) {
      return 0;
    }
    const tplData = objet.system;
    if (objet.type != 'conteneur') {
      return Number(tplData.encombrement) * Number(tplData.quantite);
    }
    const encContenus = tplData.contenu.map(idContenu => this.getRecursiveEnc(this.getItem(idContenu)));
    return encContenus.reduce(Misc.sum(), 0)
      + Number(tplData.encombrement) /* TODO? Number(tplData.quantite) -- on pourrait avoir plusieurs conteneurs...*/
  }

  /* -------------------------------------------- */
  /** Ajoute un item dans un conteneur, sur la base
   * de leurs ID */
  async ajouterDansConteneur(item, conteneur, onAjouterDansConteneur) {
    if (!conteneur) {
      // TODO: afficher
      item.estContenu = false;
    }
    else if (conteneur.isConteneur()) {
      item.estContenu = true;
      await this.updateEmbeddedDocuments('Item', [{
        _id: conteneur.id,
        'system.contenu': [...conteneur.system.contenu, item.id]
      }]);
      onAjouterDansConteneur(item.id, conteneur.id);
    }
  }

  /* -------------------------------------------- */
  /** 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.type == 'conteneur' && item.system.contenu.length > 0) {
            corrections.push({ _id: item.id, 'system.contenu': [] });
          }
        }
        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?.isConteneur()) {
      item.estContenu = false;
      await this.updateEmbeddedDocuments('Item', [{
        _id: conteneur.id,
        'system.contenu': conteneur.system.contenu.filter(id => id != item.id)
      }]);
      onEnleverDeConteneur();
    }
  }

  /* -------------------------------------------- */
  async moveItemsBetweenActors(itemId, sourceActorId) {
    let itemsList = []
    let sourceActor = game.actors.get(sourceActorId);
    itemsList.push({ id: itemId, conteneurId: undefined }); // Init list
    sourceActor.buildSubConteneurObjetList(itemId, itemsList); // Get itemId list

    const itemsDataToCreate = itemsList.map(it => sourceActor.getItem(it.id))
      .map(it => duplicate(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
        let newConteneurId = itemMap[item.conteneurId]; // Get conteneur
        let newConteneur = this.getItem(newConteneurId);

        let newItemId = itemMap[item.id]; // Get newItem

        console.log('New conteneur filling!', newConteneur, newItemId, item);
        let contenu = duplicate(newConteneur.system.contenu);
        contenu.push(newItemId);
        await this.updateEmbeddedDocuments('Item', [{ _id: newConteneurId, 'system.contenu': contenu }]);
      }
    }
    for (let item of itemsList) {
      await sourceActor.deleteEmbeddedDocuments('Item', [item.id]);
    }
  }

  _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.name,
      system: { description: this.system.description }
    }
    renderTemplate('systems/foundryvtt-reve-de-dragon/templates/post-actor.html', chatData)
      .then(html => ChatMessage.create(RdDUtility.chatDataSetup(html, modeOverride)));
  }


}