import { ChatUtility } from "../chat-utility.js";
import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js";
import { Grammar } from "../grammar.js";
import { RdDItemCompetence } from "../item-competence.js";
import { Misc } from "../misc.js";
import { RdDEmpoignade } from "../rdd-empoignade.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 { RdDItemCompetenceCreature } from "../item-competencecreature.js";
import { StatusEffects } from "../settings/status-effects.js";
import { TYPES } from "../item.js";
import { Targets } from "../targets.js";
import { RdDPossession } from "../rdd-possession.js";
import { RdDCombat } from "../rdd-combat.js";
import { RdDConfirm } from "../rdd-confirm.js";
import { ENTITE_INCARNE, SHOW_DICE, SYSTEM_RDD } from "../constants.js";
import { RdDItemArme } from "../item-arme.js";

const POSSESSION_SANS_DRACONIC = {
  img: 'systems/foundryvtt-reve-de-dragon/icons/entites/possession.webp',
  name: 'Sans draconic',
  system: {
    niveau: 0,
    defaut_carac: "reve-actuel",
  }
};

/**
 * 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 {

  getCaracChanceActuelle() {
    return {
      label: 'Chance actuelle',
      value: this.getChanceActuel(),
      type: "number"
    };
  }

  getCaracReveActuel() {
    return {
      label: 'Rêve actuel',
      value: this.getReveActuel(),
      type: "number"
    };
  }

  getReveActuel() { return this.getReve() }
  getChanceActuel() { return this.getChance() }

  getReve() { return Number(this.system.carac.reve?.value ?? 0) }
  getForce() { return this.getReve() }
  getTaille() { return Number(this.system.carac.taille?.value ?? 0) }
  getAgilite() { return this.getForce() }
  getChance() { return this.getReve() }
  getMoralTotal() { return 0 }
  getBonusDegat() { return Number(this.system.attributs?.plusdom?.value ?? 0) }
  getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) }
  getSConst() { return 0 }

  /* -------------------------------------------- */
  getEncombrementMax() { return 0 }
  isSurenc() { return false }
  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 }
  blessuresASoigner() { return [] }
  getEtatGeneral(options = { ethylisme: false }) { return 0 }

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

  async santeIncDec(name, inc, isCritique = false) { }

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

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

  async finDeRoundBlessures() {
  }

  async $finDeRoundSupprimerObsoletes() {
    const obsoletes = []
      .concat(this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp <= 0))
      .concat(this.itemTypes[TYPES.possession].filter(it => it.system.compteur < -2 || it.system.compteur > 2))
      .map(it => it.id);
    await this.deleteEmbeddedDocuments('Item', obsoletes);
  }

  async $finDeRoundEmpoignade() {
    const immobilisations = this.itemTypes[TYPES.empoignade].filter(it => it.system.pointsemp >= 2 && it.system.empoigneurid == this.id);
    immobilisations.forEach(emp => RdDEmpoignade.onImmobilisation(this,
      game.actors.get(emp.system.empoigneid),
      emp
    ))
  }

  async setSonne(sonne = true) { }

  /* -------------------------------------------- */
  getCompetence(idOrName, options = {}) {
    if (idOrName instanceof Item) {
      return idOrName.isCompetence() ? idOrName : undefined
    }
    return RdDItemCompetence.findCompetence(this.items, idOrName, options)
  }
  getCompetences(name) {
    return RdDItemCompetence.findCompetences(this.items, name)
  }
  getCompetenceCorpsACorps(options = {}) {
    return this.getCompetence("Corps à corps", options)
  }
  getCompetencesEsquive() {
    return this.getCompetences("esquive")
  }

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

  getDraconicOuPossession() {
    return POSSESSION_SANS_DRACONIC
  }

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

  /* -------------------------------------------- */
  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 });
      }
    }
  }

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

  getEffects(filter = e => true) {
    return this.getEmbeddedCollection("ActiveEffect").filter(filter);
  }

  getEffect(effectId) {
    return this.getEmbeddedCollection("ActiveEffect").find(it => it.statuses?.has(effectId));
  }
  
  async setEffect(effectId, status) {
    if (this.isEffectAllowed(effectId)) {
      const effect = this.getEffect(effectId);
      if (!status && effect) {
        await this.deleteEmbeddedDocuments('ActiveEffect', [effect.id]);
      }
      if (status && !effect) {
        await this.createEmbeddedDocuments("ActiveEffect", [StatusEffects.prepareActiveEffect(effectId)]);
      }
    }
  }
  
  async removeEffect(id) {
    const effect = this.getEmbeddedCollection("ActiveEffect").find(it => it.id == id);
    if (effect) {
      await this.deleteEmbeddedDocuments('ActiveEffect', [id]);
    }
  }

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

  /* -------------------------------------------- */
  getSurprise(isCombat = undefined) {
    let niveauSurprise = this.getEffects()
      .map(effect => StatusEffects.valeurSurprise(effect, isCombat))
      .reduce(Misc.sum(), 0);
    if (niveauSurprise > 1) {
      return 'totale';
    }
    if (niveauSurprise == 1) {
      return 'demi';
    }
    return '';
  }

  /* -------------------------------------------- */
  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, callbackAction }) {
    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(),
          { action: callbackAction }
        ]
      });
    dialog.render(true);
    return dialog
  }

  createEmptyCallback() {
    return {
      condition: r => false,
      action: r => { }
    };
  }
  createCallbackExperience() { return this.createEmptyCallback(); }
  createCallbackAppelAuMoral() { return this.createEmptyCallback(); }
  async _onCloseRollDialog(html) { }

  /* -------------------------------------------- */
  async roll() {
    RdDEmpoignade.checkEmpoignadeEnCours(this)

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

    await this.openRollDialog({
      name: `jet-${this.id}`,
      label: `Jet de ${this.name}`,
      template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll.html',
      rollData: {
        carac: carac,
        selectedCarac: carac[selectedCaracName],
        selectedCaracName: selectedCaracName,
        competences: this.itemTypes['competence']
      },
      callbackAction: r => this.$onRollCaracResult(r)
    });
  }

  getCarac() {
    // TODO: le niveau d'une entité de cauchemar devrait être exclu...
    const carac = mergeObject(duplicate(this.system.carac),
      {
        'reve-actuel': this.getCaracReveActuel(),
        'chance-actuelle': this.getCaracChanceActuelle()
      });
    return carac;
  }

  /* -------------------------------------------- */
  async rollCarac(caracName, jetResistance = undefined) {
    RdDEmpoignade.checkEmpoignadeEnCours(this)
    let selectedCarac = this.getCaracByName(caracName)
    await this.openRollDialog({
      name: 'jet-' + caracName,
      label: 'Jet ' + Grammar.apostrophe('de', selectedCarac.label),
      template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-carac.html',
      rollData: {
        selectedCarac: selectedCarac,
        competences: this.itemTypes['competence'],
        jetResistance: jetResistance ? caracName : undefined
      },
      callbackAction: r => this.$onRollCaracResult(r)
    });
  }

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

  /* -------------------------------------------- */
  async rollCompetence(idOrName, options = { tryTarget: true }) {
    RdDEmpoignade.checkEmpoignadeEnCours(this)
    const competence = this.getCompetence(idOrName);
    let rollData = { carac: this.system.carac, competence: competence }
    if (competence.type == TYPES.competencecreature) {
      const arme = RdDItemCompetenceCreature.armeCreature(competence)
      if (arme && options.tryTarget && Targets.hasTargets()) {
        Targets.selectOneToken(target => {
          if (arme.action == "possession") {
            RdDPossession.onAttaquePossession(target, this, competence)
          }
          else {
            RdDCombat.rddCombatTarget(target, this).attaque(competence, arme)
          }
        });
        return;
      }
      // Transformer la competence de créature
      RdDItemCompetenceCreature.setRollDataCreature(rollData)
    }

    await this.openRollDialog({
      name: 'jet-competence',
      label: 'Jet ' + Grammar.apostrophe('de', competence.name),
      template: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-competence.html',
      rollData: rollData,
      callbackAction: r => this.$onRollCompetence(r, options)
    });
  }

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

  /** --------------------------------------------
   * @param {*} arme item d'arme/compétence de créature
   * @param {*} categorieArme catégorie d'attaque à utiliser: competence (== melee), lancer, tir; naturelle, possession
   * @returns 
   */
  rollArme(arme, categorieArme = "competence") {
    let compToUse = this.$getCompetenceArme(arme, categorieArme)
    if (!RdDItemArme.isArmeUtilisable(arme)) {
      ui.notifications.warn(`Arme inutilisable: ${arme.name} a 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 })
        }
      });
      return
    }

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

      const competence = this.getCompetence(compToUse)
      if (competence.isCompetencePossession()) {
        return RdDPossession.onAttaquePossession(target, this, competence);
      }
      RdDCombat.rddCombatTarget(target, this).attaque(competence, arme);
    })
  }

  $getCompetenceArme(arme, competenceName) {
    switch (arme.type) {
      case TYPES.competencecreature:
        return arme.name
      case TYPES.arme:
        switch (competenceName) {
          case 'competence': return arme.system.competence;
          case 'unemain': return RdDItemArme.competence1Mains(arme);
          case 'deuxmains': return RdDItemArme.competence2Mains(arme);
          case 'tir': return arme.system.tir;
          case 'lancer': return arme.system.lancer;
        }
    }
    return undefined
  }

  verifierForceMin(item) {
  }
  /* -------------------------------------------- */
  async resetItemUse() { }
  async incDecItemUse(itemId, inc = 1) { }
  getItemUse(itemId) { return 0; }

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

  async encaisserDommages(rollData, attacker = undefined, show = undefined) {
    if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
      return;
    }
    const attackerId = attacker?.id;
    if (ReglesOptionnelles.isUsing('validation-encaissement-gr') && !game.user.isGM) {
      RdDBaseActor.remoteActorCall({
        tokenId: this.token?.id,
        actorId: this.id,
        method: 'encaisserDommages',
        args: [rollData, show, attackerId]
      });
      return;
    }

    const armure = await this.computeArmure(rollData);
    if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) {
      DialogValidationEncaissement.validerEncaissement(this, rollData, armure,
        jet => this.$onEncaissement(jet, show, attacker));
    }
    else {
      const jet = await RdDUtility.jetEncaissement(rollData, armure, { showDice: SHOW_DICE });
      await this.$onEncaissement(jet, show, attacker);
    }
  }

  async $onEncaissement(jet, show, attacker) {
    await this.onAppliquerJetEncaissement(jet, attacker);
    await this.$afficherEncaissement(jet, show);
  }

  async onAppliquerJetEncaissement(encaissement, attacker) { }

  async $afficherEncaissement(encaissement, show) {
    mergeObject(encaissement, {
      alias: this.name,
      hasPlayerOwner: this.hasPlayerOwner,
      show: show ?? {}
    });

    await ChatUtility.createChatWithRollMode(this.name, {
      roll: encaissement.roll,
      content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
    });

    if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
      encaissement = duplicate(encaissement);
      encaissement.isGM = true;
      ChatMessage.create({
        whisper: ChatMessage.getWhisperRecipients("GM"),
        content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.html', encaissement)
      });
    }
  }

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

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

    await RdDResolutionTable.displayRollData(rollData, this, 'chat-resultat-accorder-cauchemar.html');
    if (rolled.isPart) {
      await this.appliquerAjoutExperience(rollData, true);
    }
    return rolled.isSuccess;
  }

  isEntiteAccordee(attacker) { return true }

  async setEntiteReveAccordee(attacker) {
    ui.notifications.error("Impossible de s'accorder à " + this.name + ": ce n'est pas une entite de cauchemer/rêve");
  }

}
