import { RdDUtility } from "../rdd-utility.js";
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
import { STATUSES } from "../settings/status-effects.js";
import { ITEM_TYPES } from "../constants.js";
import { RdDBaseActorReve } from "./base-actor-reve.js";
import { RdDDice } from "../rdd-dice.js";
import { RdDItemBlessure } from "../item/blessure.js";
import { ChatUtility } from "../chat-utility.js";
import { Misc } from "../misc.js";
import { RdDBaseActor } from "./base-actor.js";
import { CARACS } from "../rdd-carac.js";

/**
 * Classe de base pour les acteurs qui peuvent subir des blessures
 * - créatures
 * - humanoides
 */
export class RdDBaseActorSang extends RdDBaseActorReve {

  async _preUpdate(changed, options, user) {
    const updatedEndurance = changed?.system?.sante?.endurance
    if (updatedEndurance && options.diff) {
      await this.setEffect(STATUSES.StatusUnconscious, updatedEndurance.value == 0)
    }
    return super._preUpdate(changed, options, user)
  }

  getCarac() {
    const carac = super.getCarac()
    foundry.utils.mergeObject(carac, this.getCaracChanceActuelle())
    foundry.utils.mergeObject(carac, this.getCaracVie())
    return carac
  }

  getForce() { return Misc.toInt(this.system.carac.force?.value) }
  getConstitution() { return Misc.toInt(this.system.carac.constitution?.value) }
  getVolonte() { return Misc.toInt(this.system.carac.volonte?.value) }

  getCaracVie() { return { [CARACS.VIE]: { label: "Vie", value: this.getVieMax(), type: "number" } } }
  getVieMax() { return Misc.toInt(this.system.sante.vie?.max) }
  getEnduranceMax() { return Math.max(1, this.getTaille() + this.getConstitution()) }
  getFatigueMax() { return this.getEnduranceMax() * 2 }

  getProtectionNaturelle() { return Misc.toInt(this.system.attributs?.protection?.value) }

  getFatigueActuelle() {
    if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
      return Math.max(0, Math.min(this.getFatigueMax(), Misc.toInt(this.system.sante.fatigue?.value)))
    }
    return 0;
  }

  isCumulFatigueCauseSommeil(cumulFatigue) {
    return ReglesOptionnelles.isUsing("appliquer-fatigue")
      ? (this.getFatigueRestante() <= cumulFatigue)
      : (this.getEnduranceActuelle() <= cumulFatigue)
  }
  getFatigueRestante() { return this.getFatigueMax() - this.getFatigueActuelle() }
  getFatigueMin() { return this.system.sante.endurance.max - this.system.sante.endurance.value }

  malusFatigue() {
    if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
      return RdDUtility.calculMalusFatigue(this.getFatigueActuelle(), this.getEnduranceMax())
    }
    return 0;
  }

  /* -------------------------------------------- */
  isSurenc() { return this.computeMalusSurEncombrement() < 0 }

  computeMalusSurEncombrement() {
    return Math.min(0, Math.floor(this.getEncombrementMax() - this.encTotal));
  }

  countBlessures(filter) { return this.itemTypes[ITEM_TYPES.blessure].filter(filter).length }
  isDead() { return this.system.sante.vie.value < -this.getSConst() }

  /* -------------------------------------------- */
  computeResumeBlessure() {
    function descBlessure(count, name) {
      return count > 0 ? [`${count} ${name}${count > 1 ? "s" : ""}`] : []
    }

    const nbContusions = this.countBlessures(it => it.isContusion())
    const nbLegeres = this.countBlessures(it => it.isLegere())
    const nbGraves = this.countBlessures(it => it.isGrave())
    const nbCritiques = this.countBlessures(it => it.isCritique())
    return [
      ... (nbCritiques > 0 ? ['une CRITIQUE !'] : []),
      ...descBlessure(nbGraves, 'grave'),
      ...descBlessure(nbLegeres, 'légère'),
      ...descBlessure(nbContusions, 'contusion'),
    ]
  }

  blessuresASoigner() { return [] }

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

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

  async onAppliquerJetEncaissement(encaissement, attackerToken) {
    const santeOrig = foundry.utils.deepClone(this.system.sante);
    const blessure = this.nouvelleBlessure(encaissement.gravite, {
      localisation: encaissement.dmg?.loc.label ?? '',
      origine: attackerToken?.name ?? ''
    })
    if (blessure.system.gravite == encaissement.gravite) {
      blessure.system.vie = encaissement.vie
      blessure.system.endurance = encaissement.endurance
    }
    else { // aggravation du fait du nombre de blessures
      blessure.system.vie = blessure.system.vie
      const rollPerteEndurance = new Roll(blessure.system.endurance)
      await rollPerteEndurance.evaluate()
      blessure.system.endurance = rollPerteEndurance.total
    }
    const isCritique = blessure.system.gravite >= 6 || encaissement._reduireEndAZero;
    if (isCritique) {
      blessure.system.endurance = this.getEnduranceActuelle()
    }
    // Will update the result table
    if (blessure.system.gravite > 6) {
      this.setEffect(STATUSES.StatusComma, true)
      encaissement.mort = "à seconde blessure critique"
    }

    const perteVie = await this.santeIncDec("vie", -encaissement.vie)
    const perteEndurance = await this.santeIncDec("endurance", -encaissement.endurance, isCritique)
    await this.createEmbeddedDocuments('Item', [blessure])

    foundry.utils.mergeObject(encaissement, {
      resteEndurance: perteEndurance.newValue,
      sonne: perteEndurance.sonne,
      jetEndurance: perteEndurance.jetEndurance,
      endurance: perteEndurance.perte,
      vie: santeOrig.vie.value - perteVie.newValue,
      blessure: blessure
    })
  }

  /* -------------------------------------------- */
  async santeIncDec(name, inc, isCritique = false) {
    if (name == 'fatigue' && !ReglesOptionnelles.isUsing("appliquer-fatigue")) {
      return
    }
    if (!this.system.sante[name]) {
      return
    }
    const sante = foundry.utils.deepClone(this.system.sante)
    const compteur = sante[name]
    const result = { sonne: false }
    let perteEndurance = 0
    let minValue = name == "vie" ? -this.getSConst() - 1 : 0;

    result.newValue = Math.max(minValue, Math.min(compteur.value + inc, compteur.max));
    let fatigue = 0;

    if (name == "endurance") {
      if (result.newValue == 0 && inc < 0 && !isCritique) {
        // perte endurance et endurance devient 0 (sauf critique) -> -1 vie
        sante.vie.value--
        result.perteVie = true
      }
      result.newValue = Math.max(0, result.newValue);
      if (inc > 0) { // le max d'endurance s'applique seulement à la récupération
        result.newValue = Math.min(result.newValue, this._computeEnduranceMax())
      }
      perteEndurance = compteur.value - result.newValue;
      result.perte = perteEndurance
      if (sante.fatigue && inc < 0) { // Each endurance lost -> fatigue lost
        fatigue = perteEndurance;
      }
    }
    compteur.value = result.newValue;
    // If endurance lost, then the same amount of fatigue cannot be recovered
    if (ReglesOptionnelles.isUsing("appliquer-fatigue") && sante.fatigue && fatigue > 0) {
      sante.fatigue.value = Math.max(sante.fatigue.value + fatigue, this.getFatigueMin());
    }
    await this.update({ "system.sante": sante })

    if (perteEndurance > 1) {
      // Peut-être sonné si 2 points d'endurance perdus d'un coup
      foundry.utils.mergeObject(result, await this.jetEndurance(result.newValue));
    } else if (name == "endurance" && inc > 0) {
      await this.setSonne(false)
    }

    if (this.isDead()) {
      await this.setEffect(STATUSES.StatusComma, true)
    }
    return result
  }

  async callbackPremiersSoins(blessureId, rollData, soigneurId) {
    await this.onRollTachePremiersSoins(blessureId,
      rollData.v2 ? rollData.current.tache.tache : rollData.tache,
      rollData.rolled.isETotal,
      soigneurId)
  }
  async onRollTachePremiersSoins(blessureId, tache, isETotal, soigneurId) {
    if (!this.isOwner) {
      return RdDBaseActor.remoteActorCall({
        tokenId: this.token?.id,
        actorId: this.id,
        method: 'onRollTachePremiersSoins', args: [blessureId, tache, isETotal, soigneurId]
      })
    }

    const blessure = this.getItem(blessureId, 'blessure')
    if (blessure && !blessure.system.premierssoins.done) {
      if (isETotal) {
        await blessure.update({
          'system.difficulte': blessure.system.difficulte - 1,
          'system.premierssoins.tache': Math.max(0, tache.system.points_de_tache_courant)
        })
      }
      else {
        const bonus = tache.system.points_de_tache_courant - tache.system.points_de_tache
        await blessure.update({
          'system.premierssoins': {
            done: (bonus >= 0),
            bonus: Math.max(0, bonus),
            tache: Math.max(0, tache.system.points_de_tache_courant)
          }
        })
        if (bonus >= 0 && soigneurId) {
          const soigneur = game.actors.get(soigneurId)
          await soigneur.deleteEmbeddedDocuments('Item', [tache.id], { render: true })
        }
      }
    }
  }

  async onRollSoinsComplets(blessureId, rollData) {
    if (!this.isOwner) {
      return RdDBaseActor.remoteActorCall({
        tokenId: this.token?.id,
        actorId: this.id,
        method: 'onRollSoinsComplets', args: [blessureId, rollData]
      })
    }
    const blessure = this.getItem(blessureId, 'blessure')
    if (blessure && blessure.system.premierssoins.done && !blessure.system.soinscomplets.done) {
      // TODO: update de la blessure: passer par le MJ!
      if (rollData.rolled.isETotal) {
        await blessure.setSoinsBlessure({
          difficulte: blessure.system.difficulte - 1,
          premierssoins: { done: false, bonus: 0 }, soinscomplets: { done: false, bonus: 0 },
        })
      }
      else {
        // soins complets finis
        await blessure.setSoinsBlessure({
          soinscomplets: { done: true, bonus: Math.max(0, rollData.rolled.ptTache) },
        })
      }
    }
  }

  /* -------------------------------------------- */
  _computeEnduranceMax() {
    const diffVie = this.system.sante.vie.max - this.system.sante.vie.value;
    const maxEndVie = this.system.sante.endurance.max - (diffVie * 2);
    const nbGraves = this.countBlessures(it => it.isGrave()) > 0
    const nbCritiques = this.countBlessures(it => it.isCritique()) > 0
    const maxEndGraves = Math.floor(this.system.sante.endurance.max / (2 * nbGraves));
    const maxEndCritiques = nbCritiques > 0 ? 1 : this.system.sante.endurance.max;
    return Math.max(0, Math.min(maxEndVie, maxEndGraves, maxEndCritiques));
  }

  async onCreateItem(item, options, id) {
    await this.changeItemEffects(item)
    await super.onCreateItem(item, options, id)
  }

  async onUpdateItem(item, updates, options, id) {
    await this.changeItemEffects(item);
    await super.onUpdateItem(item, updates, options, id)
  }

  async onDeleteItem(item, options, id) {
    await this.changeItemEffects(item);
    await super.onDeleteItem(item, options, id)
  }

  async changeItemEffects(item) {
    switch (item.type) {
      case ITEM_TYPES.blessure:
        await this.changeStateBleeding()
        break;
      case ITEM_TYPES.maladie:
      case ITEM_TYPES.poison:
        await this.changeStateMalade()
        break
    }
  }

  async changeStateBleeding() {
    const bleeding = this.itemTypes[ITEM_TYPES.blessure].find(it => it.isBleeding())
    await this.setEffect(STATUSES.StatusBleeding, bleeding ? true : false)
  }

  async changeStateMalade() {
    const maladiePoisons = this.getMaladiesPoisons()
    await this.setEffect(STATUSES.StatusMalade, maladiePoisons.length > 0)
  }

  getMaladiesPoisons() {
    return this.items.filter(it => [ITEM_TYPES.maladie, ITEM_TYPES.poison].includes(it.type))
  }

  /* -------------------------------------------- */
  nouvelleBlessure(gravite, options = { origine: undefined, localisation: '' }) {
    if (gravite < 0) return
    if (gravite > 0) {
      while (this.countBlessures(it => it.system.gravite == gravite) >= RdDItemBlessure.maxBlessures(gravite) && gravite <= 6) {
        // Aggravation
        gravite += 2
      }
    }
    return RdDItemBlessure.prepareBlessure(gravite, options);
  }

  async supprimerBlessure({ gravite }) {
    const toDelete = this.itemTypes[ITEM_TYPES.blessure].find(it => it.system.gravite == gravite)?.id
    if (toDelete) {
      await this.deleteEmbeddedDocuments('Item', [toDelete]);
    }
  }

  async supprimerBlessures(filterToDelete) {
    const toDelete = this.filterItems(filterToDelete, ITEM_TYPES.blessure)
      .map(it => it.id)
    await this.deleteEmbeddedDocuments('Item', toDelete)
  }

  countBlessures(filter = it => !it.isContusion()) {
    return this.filterItems(filter, ITEM_TYPES.blessure).length
  }

  /* -------------------------------------------- */
  async jetDeVie() {
    if (this.isDead()) {
      return await ChatMessage.create({
        content: `Jet de Vie: ${this.getAlias()} est déjà mort, ce n'est pas la peine d'en rajouter !!!!!`,
        whisper: ChatUtility.getOwners(this)
      })
    }
    const jetDeVie = await RdDDice.roll("1d20");
    const sConst = this.getSConst();
    const vie = this.system.sante.vie.value;
    const isCritique = this.countBlessures(it => it.isCritique()) > 0
    const isGrave = this.countBlessures(it => it.isGrave())
    const isEchecTotal = jetDeVie.total == 20
    const isSuccess = jetDeVie.total == 1 || jetDeVie.total <= vie;
    const perte = isSuccess ? 0 : 1 + (isEchecTotal ? vie + sConst : 0)
    const prochainJet = (jetDeVie.total == 1 && vie > 0 ? 20 : 1) * (isCritique ? 1 : isGrave > 0 ? sConst : 0)

    let msgText = `Jet de Vie: <strong>${jetDeVie.total} / ${vie}</strong>`
    if (isSuccess) {
      msgText += "<br>Réussi, pas de perte de point de vie."
    } else {
      msgText += `<br>Echoué, perte ${perte} point de vie`;
      await this.santeIncDec("vie", -perte);
    }
    if (this.isDead()) {
      msgText += `<br><strong>${this.getAlias()} est mort !!!!</strong>`;
    }
    else if (prochainJet > 0) {
      msgText += `<br>Prochain jet de vie dans ${prochainJet} ${isCritique ? 'round' : 'minute'}${prochainJet > 1 ? 's' : ''} ${isCritique ? '(état critique)' : '(état grave)'}`
    }
    return await ChatMessage.create({
      content: msgText,
      whisper: ChatUtility.getOwners(this)
    })
  }

  /* -------------------------------------------- */
  async jetEndurance(resteEndurance = undefined) {
    const jetEndurance = (await RdDDice.roll("1d20")).total;
    const sonne = jetEndurance == 20 || jetEndurance > (resteEndurance ?? this.system.sante.endurance.value)
    if (sonne) {
      await this.setSonne(true)
    }
    return { jetEndurance, sonne }
  }

  async finDeRoundBlessures() {
    const nbGraves = this.itemTypes[ITEM_TYPES.blessure]
      .filter(it => it.isGrave() && !it.system.premierssoins.done).length;
    if (nbGraves > 0) {
      // Gestion blessure graves : -1 pt endurance par blessure grave
      await this.santeIncDec("endurance", -nbGraves);
    }
  }

  async setSonne(sonne = true) {
    if (!game.combat && sonne) {
      // TODO: vérifier si comportement toujours valable
      ui.notifications.info(`${this.getAlias()} est hors combat, il ne reste donc pas sonné`);
      return
    }
    await this.setEffect(STATUSES.StatusStunned, sonne)
  }

  isSonne() {
    return this.getEffectsByStatus(STATUSES.StatusStunned).length > 0
  }

  isEffectAllowed(effectId) { return true }

  /* -------------------------------------------- */
  async computeEtatGeneral() { this.system.compteurs.etat.value = this.malusVie() + this.malusFatigue() + this.malusEthylisme() }
  getEtatGeneral(options = { ethylisme: false }) { return this.system.compteurs.etat.value }

  malusVie() { return Math.min(this.system.sante.vie.value - this.system.sante.vie.max, 0) }
  malusEthylisme() { return 0 }


}
