import { ENTITE_INCARNE, 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 { RdDItemCompetence } from "../item-competence.js";
import { RdDItemCompetenceCreature } from "../item-competencecreature.js";
import { RdDItemArme } from "../item-arme.js";
import { StatusEffects } from "../settings/status-effects.js";
import { Targets } from "../targets.js";
import { RdDConfirm } from "../rdd-confirm.js";
import { RdDCarac } from "../rdd-carac.js";
import { RdDRollResult } from "../rdd-roll-result.js";

import { ChatUtility } from "../chat-utility.js";
import { DialogValidationEncaissement } from "../dialog-validation-encaissement.js";
import { RdDCombat } from "../rdd-combat.js";
import { RdDEmpoignade } from "../rdd-empoignade.js";
import { RdDPossession } from "../rdd-possession.js";
import { BASE_CORPS_A_CORPS, BASE_ESQUIVE, POSSESSION_SANS_DRACONIC } from "../item/base-items.js";
import { RollDataAjustements } from "../rolldata-ajustements.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 {

  prepareActorData() {
    super.prepareActorData()
    this.system.attributs.plusdom.value = this.getBonusDegat()
    this.system.sante.endurance.max = this.getEnduranceMax()
    this.system.sante.endurance.value = Math.min(this.system.sante.endurance.value, this.system.sante.endurance.max)
  }

  getCarac() {
    return foundry.utils.mergeObject(this.system.carac,
      {
        'reve-actuel': this.getCaracReveActuel(),
        'chance-actuelle': this.getCaracChanceActuelle()
      },
      { inplace: false })
  }

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

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

  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() }

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

  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 }
  getProtectionNaturelle() { return Number(this.system.attributs?.protection?.value ?? 0) }
  getSConst() { 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 }
  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;
  }
  listActionsCombat() {
    return this.itemTypes[ITEM_TYPES.competencecreature]
      .filter(it => RdDItemCompetenceCreature.isAttaque(it))
      .map(it => RdDItemCompetenceCreature.armeCreature(it))
      .filter(it => it != undefined);
  }


  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.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))
      .concat(this.itemTypes[ITEM_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[ITEM_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, options = { onMessage: message => { } }) {
    return RdDItemCompetence.findCompetences(this.items, 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)
  }

  getDraconicOuPossession() { return POSSESSION_SANS_DRACONIC }

  getPossession(possessionId) {
    return this.itemTypes[ITEM_TYPES.possession].find(it => it.system.possessionid == possessionId);
  }
  getEmpoignades() {
    return this.itemTypes[ITEM_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 false }

  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, 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
  }

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

  async rollCaracCompetence(caracName, compName, diff, options = { title: "" }) {
    RdDEmpoignade.checkEmpoignadeEnCours(this)
    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)]
    });
  }
  /**
   * Méthode pour faire un jet prédéterminer 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,
      show: { title: options?.title ?? '' }
    }
    RollDataAjustements.calcul(rollData, this);
    await RdDResolutionTable.rollData(rollData);
    this.gererExperience(rollData);
    await RdDResolutionTable.displayRollData(rollData, this)
    return rollData.rolled;
  }

  gererExperience(rollData) { }

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

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

    await this.openRollDialog({
      rollData: {
        alias: this.getAlias(),
        carac: carac,
        selectedCarac: carac[selectedCaracName],
        selectedCaracName: selectedCaracName,
        competences: this.itemTypes['competence']
      },
      callbacks: [{ action: r => this.$onRollCaracResult(r) }]
    })
  }
  /* -------------------------------------------- */
  async rollCarac(caracName, options = {}) {
    if (Grammar.equalsInsensitive(caracName, 'taille')) {
      return
    }
    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);
    let rollData = {
      carac: this.system.carac,
      competence: competence,
      arme: options.arme
    }
    if (competence.type == ITEM_TYPES.competencecreature) {
      const token = RdDUtility.getSelectedToken(this)
      const arme = RdDItemCompetenceCreature.armeCreature(competence)
      if (arme && options.tryTarget && Targets.hasTargets()) {
        Targets.selectOneTargetToken(target => {
          if (arme.action == "possession") {
            RdDPossession.onAttaquePossession(target, this, competence)
          }
          else {
            RdDCombat.rddCombatTarget(target, this, token).attaque(competence, arme)
          }
        });
        return;
      }
      // Transformer la competence de créature
      RdDItemCompetenceCreature.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);
    }
  }

  /** --------------------------------------------
   * @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, token) {
    token = token ?? RdDUtility.getSelectedToken(this)
    const compToUse = this.$getCompetenceArme(arme, categorieArme)
    if (!RdDItemArme.isUtilisable(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, 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 competence = this.getCompetence(compToUse)
      if (competence.isCompetencePossession()) {
        return RdDPossession.onAttaquePossession(target, this, competence);
      }
      RdDCombat.rddCombatTarget(target, this, token).attaque(competence, arme);
    })
  }

  $getCompetenceArme(arme, competenceName) {
    return RdDItemArme.getCompetenceArme(arme, competenceName)
  }

  verifierForceMin(item) { }

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

  async encaisserDommages(rollData, attacker = undefined, show = undefined, attackerToken = undefined, defenderToken = undefined) {
    if (attacker && !await attacker.accorder(this, 'avant-encaissement')) {
      return;
    }
    const armure = await this.computeArmure(rollData);
    if (ReglesOptionnelles.isUsing('validation-encaissement-gr')) {
      await this.encaisserDommagesValidationGR(rollData, armure, show, attackerToken, defenderToken);
    }
    else {
      const jet = await RdDUtility.jetEncaissement(this, rollData, armure, { showDice: SHOW_DICE });
      await this.$onEncaissement(jet, show, attackerToken, defenderToken)
    }
  }

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

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

  async onAppliquerJetEncaissement(encaissement, attackerToken) { }

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

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

    if (!encaissement.hasPlayerOwner && encaissement.endurance != 0) {
      encaissement = foundry.utils.duplicate(encaissement)
      encaissement.isGM = true
      ChatMessage.create({
        whisper: ChatUtility.getGMs(),
        content: await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/chat-resultat-encaissement.hbs', 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.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.appliquerAjoutExperience(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");
  }

}
