import { renderTemplate, SHOW_DICE, SYSTEM_RDD } from "./constants.js";
import { RollDataAjustements } from "./rolldata-ajustements-v1.js";
import { RdDUtility } from "./rdd-utility.js";
import { COORD_TMR_INCONNU, TMRUtility } from "./tmr-utility.js";
import { RdDResolutionTable } from "./rdd-resolution-table.js";
import { RdDTMRRencontreDialog } from "./rdd-tmr-rencontre-dialog.js";
import { ChatUtility } from "./chat-utility.js";
import { RdDRoll } from "./rdd-roll.js";
import { Poetique } from "./poetique.js";
import { EffetsDraconiques } from "./tmr/effets-draconiques.js";
import { PixiTMR } from "./tmr/pixi-tmr.js";
import { Draconique } from "./tmr/draconique.js";
import { HtmlUtility } from "./html-utility.js";
import { ReglesOptionnelles } from "./settings/regles-optionnelles.js";
import { RdDDice } from "./rdd-dice.js";
import { RdDRencontre } from "./item/rencontre.js";
import { ITEM_TYPES } from "./constants.js";
import { Misc } from "./misc.js";
import { CARACS } from "./rdd-carac.js";

const TMR_DISPLAY_SIZE = {
  code: 'tmr-display-size',
  range: {
    min: 32,
    max: 128,
    step: 8,
  },
  def: 64,
  clamp: (size, inc = 0) => Math.max(TMR_DISPLAY_SIZE.range.min, Math.min(size + (inc * TMR_DISPLAY_SIZE.range.step), TMR_DISPLAY_SIZE.range.max)),
  get: () => TMR_DISPLAY_SIZE.clamp(game.settings.get(SYSTEM_RDD, TMR_DISPLAY_SIZE.code) ?? TMR_DISPLAY_SIZE.def),
  set: (size) => game.settings.set(SYSTEM_RDD, TMR_DISPLAY_SIZE.code, TMR_DISPLAY_SIZE.clamp(size)),
};

/* -------------------------------------------- */
export class RdDTMRDialog extends foundry.applications.api.DialogV2 {
  static initSettings() {
    game.settings.register(SYSTEM_RDD, TMR_DISPLAY_SIZE.code, {
      name: 'Taille des cases des TMR',
      hint: "Taille en pixel des cases des TMR (réglable directement dans la fenêtre des TMR)",
      scope: "client",
      config: true,
      default: TMR_DISPLAY_SIZE.def,
      type: Number,
      range: TMR_DISPLAY_SIZE.range
    })
  }

  static async create(actor, tmrData) {
    await PixiTMR.init()
    let content = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/dialog-tmr.hbs', tmrData);
    if (tmrData.mode != 'visu' && !game.user.isGM) {
      await ChatMessage.create({ content: actor.name + " est monté dans les TMR en mode : " + tmrData.mode, whisper: ChatUtility.getGMs() });
    }
    return new RdDTMRDialog(content, actor, tmrData)
  }

  /* -------------------------------------------- */
  constructor(content, actor, tmrData) {
    super({
      title: "Terres Médianes de Rêve",
      content,
      classes: ["tmrdialog"],
      window: { positioned: true },
      position: { width: 'fit-content', height: 'fit-content' },
      buttons: [{ action: "close", label: "Fermer", icon: "fa-solid fa-xmark", callback: () => this.close() }]
    });
    this.tmrdata = foundry.utils.deepClone(tmrData);
    this.actor = actor;
    this.actor.tmrApp = this; // reference this app in the actor structure
    this.viewOnly = tmrData.mode == "visu"
    this.fatigueParCase = this.viewOnly ? 0 : this.actor.getCoutFatigueTMR();
    this.cumulFatigue = 0;
    this.loadRencontres();
    this.loadCasesSpeciales();
    this.allTokens = [];
    this.rencontreState = 'aucune';
    this.subdialog = undefined
    this.displaySize = undefined
    if (!this.viewOnly && !game.user.isGM) {
      ChatUtility.tellToGM(this.actor.name + " monte dans les terres médianes (" + tmrData.mode + ")");
    }
    this.callbacksOnAnimate = [];
    const displaySize = TMR_DISPLAY_SIZE.clamp(game.settings.get(SYSTEM_RDD, TMR_DISPLAY_SIZE.code) ?? TMR_DISPLAY_SIZE.def);
    this.pixiTMR = new PixiTMR(this, displaySize);
    this.resizePixiTMR(displaySize)
  }

  resizePixiTMR(displaySize) {
    if (displaySize != this.displaySize) {
      this.displaySize = displaySize
      this.pixiTMR.resizeTMR(displaySize);
      this._removeTokens()
      this.allTokens = []
      this.createPixiSprites()
      this.pixiTMR.loadAnimations();
    }
  }

  /* -------------------------------------------- */
  async _onRender(context, options) {
    await super._onRender(context, options);
    this.html = $(this.element);

    // this.activateTMRSize()
    this.addTMRMap()
    this.html.find('div.tmr-size a.tmr-size-zoom-minus').click(event => {
      this.$changeTMRSize(-1)
    });
    this.html.find('div.tmr-size a.tmr-size-zoom-plus').click(event => {
      this.$changeTMRSize(1)
    });


    HtmlUtility.showControlWhen(this.html.find(".lire-signe-draconique"), this.actor.isResonanceSigneDraconique(this._getCoordActor()));

    this.html.find('div.tmr-dialog *').click(event => {
      if (this.subdialog?.rendered) {
        this.subdialog?.bringToFront()
      }
    })

    // Roll Sort
    this._ensureTMRButtons();
    this.html.find('.lancer-sort').click(event => this.lancerUnSort());
    this.html.find('.lire-signe-draconique').click(event => this.actor.rollLireSigneDraconique(this._getCoordActor()));

    if (this.viewOnly) return;

    HtmlUtility.showControlWhen(this.html.find(".appliquerFatigue"), ReglesOptionnelles.isUsing("appliquer-fatigue"));

    this.html.find('img.tmr-move').click(event => this.deplacementTMR(this.html.find(event.currentTarget)?.data('move')));

    // Gestion du cout de montée en points de rêve
    await this.actor.reveActuelIncDec(await this.calculCoutMonteeTMR());
    this.cumulFatigue += this.fatigueParCase;

    // Le reste...
    this.$updateValuesDisplay();
  }

  _ensureTMRButtons() {
    if (!this.html.find('.lancer-sort').length) {
      const actions = this.html.find('.tmr-actions');
      const separator = $('<hr>');
      const lancerSort = $('<div class="flex-group-center flex-shrink lancer-sort"><a>Lancer un Sort</a></div>');
      const lireSigne = $('<div class="flex-group-center flex-shrink lire-signe-draconique"><a>Lire un signe draconique</a></div>');
      actions.find('.flex-grow').before(separator, lancerSort, lireSigne);
    }
  }

  lancerUnSort() {
    if (this.subdialog) {
      return this.forceTMRContinueAction();
    }
    return this.actor.rollUnSort(this._getCoordActor());
  }

  async onDeplacement() {
    await this.manageRencontre(TMRUtility.getTMR(this._getCoordActor()));
  }

  addTMRMap() {
    const tmrCell = document.querySelector(".tmr-map");
    if (!tmrCell) return;
    for (const node of [...tmrCell.childNodes]) tmrCell.removeChild(node);
    tmrCell.append(this.pixiTMR.view);
  }

  $changeTMRSize(inc) {
    let displaySize = TMR_DISPLAY_SIZE.clamp(this.displaySize, inc)
    if (displaySize != this.displaySize) {
      game.settings.set(SYSTEM_RDD, TMR_DISPLAY_SIZE.code, TMR_DISPLAY_SIZE.clamp(displaySize))
      this.resizePixiTMR(displaySize)
      this.render()
    }
  }

  async forceTMRDisplay() {
    if (this.rendered) {
      this.bringToFront()
      this.bringSubDialogToFront();
    }
  }

  bringSubDialogToFront() {
    if (this.subdialog?.bringToFront && this.subdialog?.element) {
      this.subdialog.bringToFront();
    }
  }

  async restoreTMRAfterAction() {
    this.subdialog = undefined
    await this.maximize()
    if (this.rendered) {
      this.bringToFront()
    }
  }

  forceTMRContinueAction() {
    ui.notifications.warn('Vous devez finir votre action avant de continuer dans les TMR');
    this.bringSubDialogToFront();
    return false
  }

  setTMRPendingAction(dialog) {
    this.subdialog = dialog
    this.forceTMRDisplay()
  }

  isDemiReveCache() {
    return !game.user.isGM && this.actor.isTMRCache();
  }

  /* -------------------------------------------- */
  loadCasesSpeciales() {
    this.casesSpeciales = this.actor.items.filter(item => Draconique.isCaseTMR(item));
  }

  get sortsReserve() {
    return this.actor.itemTypes[ITEM_TYPES.sortreserve];
  }

  getSortsReserve(coord) {
    return this.sortsReserve.filter(// Reserve sur une case fleuve ou normale
      TMRUtility.getTMR(coord).type == 'fleuve'
        ? it => TMRUtility.getTMR(it.system.coord).type == 'fleuve'
        : it => it.system.coord == coord
    );
  }

  /* -------------------------------------------- */
  loadRencontres() {
    this.rencontresExistantes = this.actor.getRencontresTMR();
  }

  /* -------------------------------------------- */
  createPixiSprites() {
    this.pixiTMR.setup()
    this.updateTokens()
    this.forceDemiRevePositionView()
  }

  /* -------------------------------------------- */
  _createTokens() {
    if (!this.isDemiReveCache()) {
      this.demiReve = this._tokenDemiReve();
      this._trackToken(this.demiReve);
    }
    this._getTokensCasesTmr().forEach(t => this._trackToken(t))
    this._getTokensRencontres().forEach(t => this._trackToken(t))
    this._getTokensSortsReserve().forEach(t => this._trackToken(t))
  }

  /* -------------------------------------------- */
  updateTokens() {
    this._removeTokens(t => true);
    this.allTokens = []
    this.loadRencontres();
    this.loadCasesSpeciales();
    this._createTokens();
  }

  /* -------------------------------------------- */
  removeToken(tmr, casetmr) {
    this._removeTokens(t => t.coordTMR() == tmr.coord && t.caseSpeciale?.id == casetmr.id);
    this.updateTokens()
  }

  /* -------------------------------------------- */
  _getTokensCasesTmr() {
    return Misc.concat(this.casesSpeciales.map(caseSpeciale =>
      Draconique.get(caseSpeciale.system.specific)?.token(this.pixiTMR, caseSpeciale, () => caseSpeciale.system.coord)
    ))
  }

  _getTokensRencontres() {
    return Misc.concat(this.rencontresExistantes.map(rencontre =>
      EffetsDraconiques.rencontre.tokens(this.pixiTMR, rencontre, () => rencontre.system.coord)
    ))
  }

  _getTokensSortsReserve() {
    return Misc.concat(this.sortsReserve.map(sortReserve =>
      EffetsDraconiques.sortReserve.tokens(this.pixiTMR, sortReserve, () => sortReserve.system.coord)))
  }

  _tokenDemiReve() {
    return EffetsDraconiques.demiReve.token(this.pixiTMR, this.actor, () => this.actor.system.reve.tmrpos.coord);
  }

  forceDemiRevePositionView() {
    this.notifierResonanceSigneDraconique(this._getCoordActor());
    this._trackToken(this.demiReve);
  }

  _getCoordActor() {
    return this.actor.system.reve.tmrpos.coord;
  }

  /* -------------------------------------------- */
  async deplacementTMR(move) {
    if (this.subdialog) {
      return this.forceTMRContinueAction();
    }

    const coordOrig = this._getCoordActor();
    const coordTarget = TMRUtility.deplacement(coordOrig, move);
    await this._deplacerDemiReve(coordTarget, 'normal');
    await this.$checkQuitterTMR();
  }

  async calculCoutMonteeTMR() {
    return ((this.tmrdata.isRapide && !EffetsDraconiques.isDeplacementAccelere(this.actor)) ? -2 : -1) - (await this.actor.countMonteeLaborieuse())
  }

  /* -------------------------------------------- */
  async $updateValuesDisplay() {
    if (this.viewOnly || !this.rendered) {
      return;
    }
    const coord = this._getCoordActor();
    HtmlUtility.showControlWhen(this.html.find(".lire-signe-draconique"), this.actor.isResonanceSigneDraconique(coord));

    const ptsreve = document.getElementById("tmr-pointsreve-value");
    if (ptsreve) ptsreve.innerHTML = this.actor.system.reve.reve.value;

    const tmrpos = document.getElementById("tmr-pos");
    if (tmrpos) {
      if (this.isDemiReveCache()) {
        tmrpos.innerHTML = `?? ( ${TMRUtility.getTMRType(coord)})`;
      } else {
        tmrpos.innerHTML = `${coord} ( ${TMRUtility.getTMRLabel(coord)})`;
      }
    }

    const etat = document.getElementById("tmr-etatgeneral-value");
    if (etat) etat.innerHTML = this.actor.getEtatGeneral();

    const refoulement = document.getElementById("tmr-refoulement-value");
    if (refoulement) refoulement.innerHTML = this.actor.system.reve.refoulement.value;

    if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
      const fatigueItem = document.getElementById("tmr-fatigue-table");
      if (fatigueItem) fatigueItem.innerHTML = "<table class='table-fatigue'>" + RdDUtility.makeHTMLfatigueMatrix(this.actor.system.sante.fatigue.value, this.actor.system.sante.endurance.max).html() + "</table>";
    }
  }

  /* -------------------------------------------- */
  async close() {
    await this.$forceClose(this.actor.name + " a quitté les terres médianes")
  }

  async $forceClose(message) {
    if (this.subdialog) {
      this.forceTMRContinueAction()
      return false
    }
    this.descenteTMR = true
    await this.actor?.quitterTMR(message, this.viewOnly, this.cumulFatigue)
    this.pixiTMR?.close()
    this.pixiTMR = undefined
    await super.close()
    return true
  }

  /* -------------------------------------------- */
  async onActionRencontre(action, tmr, rencontre) {
    if (!this.currentRencontre) {
      ui.notifications.warn("#612 Rencontre perdue, récupération en cours. Vous pouvez contacter l'équipe avec les logs pour aider à résoudre ce problème")
      console.error("#612 Rencontre perdue", action, tmr, rencontre, this);
      this.currentRencontre = rencontre;
    }
    switch (action) {
      case 'derober':
        await this.$derober()
        this.restoreTMRAfterAction()
        return
      case 'refouler':
        await this.$refouler()
        break
      case 'maitriser':
        await this.$maitriserRencontre()
        break
      case 'ignorer':
        await this.$ignorerRencontre()
        break
    }
    await this.postRencontre(tmr);
    this.restoreTMRAfterAction();
  }

  async $derober() {
    if (this.currentRencontre) {
      console.log("-> derober", this.currentRencontre);
      await this.actor.addTMRRencontre(this.currentRencontre);
      this.$forceClose(`${this.actor.name} s'est dérobé face à la rencontre ${this.currentRencontre.name}, et quitte les TMR.`)
    }
  }

  /* -------------------------------------------- */
  async $refouler() {
    if (this.currentRencontre) {
      console.log("-> refouler", this.currentRencontre);
      await this.actor.ajouterRefoulement(this.currentRencontre.system.refoulement, `${this.currentRencontre.system.genre == 'f' ? 'une' : 'un'} ${this.currentRencontre.name}`);
      await this.$deleteRencontreTMRAtPosition()
      this.updateTokens();
      this.$updateValuesDisplay();
      this.$nettoyerRencontre();
    }
  }

  /* -------------------------------------------- */
  async $ignorerRencontre() {
    if (this.currentRencontre) {
      console.log("-> ignorer", this.currentRencontre);
      ChatUtility.tellToGM(this.actor.name + " a ignoré: " + this.currentRencontre.name);
      await this.$deleteRencontreTMRAtPosition()
      this.updateTokens();
      this.$updateValuesDisplay();
      this.$nettoyerRencontre();
    }
  }

  /* -------------------------------------------- */
  // garder la trace de l'état en cours
  setRencontreState(state, listCoordTMR) {
    this.rencontreState = state;
    this.$marquerCasesTMR(listCoordTMR ?? []);
  }

  /* -------------------------------------------- */
  $marquerCasesTMR(listCoordTMR) {
    this.currentRencontre.locList = foundry.utils.deepClone(listCoordTMR); // And track of allowed location
    this.currentRencontre.graphics = listCoordTMR.map(coordTMR => this.pixiTMR.addMarkTMR(coordTMR))
  }

  /* -------------------------------------------- */
  async $checkQuitterTMR() {
    const reason = this.actor.isDead()
      ? "est mort"
      : this.actor.isCumulFatigueCauseSommeil(this.cumulFatigue)
        ? "s'écroule de fatigue"
        : (this.actor.getReveActuel() == 0)
          ? "tombe à 0 Points de Rêve"
          : undefined
    if (reason) {
      if (!this.actor.isDead()) {
        await this.$refouler()
      }
      this.$forceClose(`${this.actor.name} ${reason} et quitte les Terres médianes !`)
      return true
    }
    return false
  }

  /* -------------------------------------------- */
  async $maitriserRencontre() {
    console.log("-> maitriser", this.currentRencontre);

    await this.$deleteRencontreTMRAtPosition();
    this.updateTokens();

    let rencontreData = {
      actor: this.actor,
      alias: this.actor.getAlias(),
      reveDepart: this.actor.getReveActuel(),
      competence: this.actor.getBestDraconic(),
      rencontre: this.currentRencontre,
      nbRounds: 1,
      canClose: false,
      selectedCarac: { label: "reve-actuel" },
      tmr: TMRUtility.getTMR(this._getCoordActor())
    }

    await this.$tentativeMaitrise(rencontreData);
  }

  async $deleteRencontreTMRAtPosition() {
    const position = this.actor.getDemiReve()
    const rencontreIds = this.actor.itemTypes[ITEM_TYPES.rencontre]
      .filter(it => it.system.coord == position)
      .map(it => it.id)
    if (rencontreIds.length > 0) {
      await this.actor.deleteEmbeddedDocuments('Item', rencontreIds)
    }
  }

  /* -------------------------------------------- */
  async $tentativeMaitrise(rencData) {
    this.rencontreState = 'normal';

    rencData.reve = this.actor.getReveActuel();
    rencData.etat = this.actor.getEtatGeneral();

    RollDataAjustements.calcul(rencData, this.actor);

    rencData.rolled = rencData.presentCite
      ? this.$rollPresentCite(rencData)
      : await RdDResolutionTable.roll(rencData.reve, RollDataAjustements.sum(rencData.ajustements));

    const result = rencData.rolled.isSuccess
      ? rencData.rencontre.system.succes
      : rencData.rencontre.system.echec;

    await RdDRencontre.appliquer(result.effets, this, rencData);

    rencData.poesie = { extrait: result.poesie, reference: result.reference };
    rencData.message = this.$formatMessageRencontre(rencData, result.message);

    await ChatMessage.create({
      whisper: ChatUtility.getOwners(this.actor),
      content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-rencontre-tmr.hbs`, rencData)
    });

    this.$updateValuesDisplay();
    if (await this.$checkQuitterTMR()) {
      return;
    }
    if (this.rencontreState == 'persistant') {
      this.$nouvelleTentativeMaitrise(rencData);
    }
    else if (!this.isRencontreDeplacement()) {
      this.$nettoyerRencontre();
    }
  }

  $nouvelleTentativeMaitrise(rencData) {
    setTimeout(() => {
      /**
       * TODO: remplacer par une boucle while(this.currentRencontre) ?
       * ajouter un moyen d'attendre la saisie de l'utilisateur (jet obligatoire)?
       */
      rencData.nbRounds++;
      if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
        this.cumulFatigue += this.fatigueParCase;
      }
      this.$tentativeMaitrise(rencData);
      this.$deleteTmrMessages(rencData.actor, rencData.nbRounds);
    }, 2000);
  }

  $formatMessageRencontre(rencData, template) {
    let messageDuree = ''
    if (rencData.nbRounds > 1) {
      if (rencData.rolled.isSuccess) {
        messageDuree = ` Au total, vous avez passé ${rencData.nbRounds} rounds à vous battre!`;
      }
      else {
        messageDuree = ` Vous avez passé ${rencData.nbRounds} rounds à lutter!`;
      }
    }
    try {
      const compiled = Handlebars.compile(template);
      return compiled(rencData) + messageDuree;
    } catch (error) {
      return template + messageDuree;
    }
  }

  /* -------------------------------------------- */
  $rollPresentCite(rencData) {
    let rolled = RdDResolutionTable.computeChances(rencData.reve, 0);
    foundry.utils.mergeObject(rolled, { caracValue: rencData.reve, finalLevel: 0, roll: rolled.score });
    RdDResolutionTable.succesRequis(rolled);
    return rolled;
  }

  /* -------------------------------------------- */
  $deleteTmrMessages(actor, nbRounds = -1) {
    setTimeout(() => {
      if (nbRounds < 0) {
        ChatUtility.removeChatMessageContaining(`<h4 data-categorie="tmr" data-actor-id="${actor.id}"`);
      }
      else {
        for (let i = 1; i < nbRounds; i++) {
          ChatUtility.removeChatMessageContaining(`<h4 data-categorie="tmr" data-actor-id="${actor.id}" data-rencontre-round="${i}">`);
        }
      }
    }, 500);
  }

  /* -------------------------------------------- */
  async manageRencontre(tmr) {
    if (this.viewOnly) {
      return;
    }
    this.descenteTMR = false;
    this.currentRencontre = undefined;
    if (await this._presentCite(tmr)) {
      return;
    }
    this.currentRencontre = await this._jetDeRencontre(tmr);
    if (this.currentRencontre) {
      if (this.rencontresExistantes.find(it => it.id == this.currentRencontre.id)) {
        // rencontre en attente suite à dérobade
        await this.$maitriserRencontre();
      }
      else {
        const dialog = new RdDTMRRencontreDialog(this.actor, this.currentRencontre, tmr);
        await dialog.render(true);
        this.setTMRPendingAction(dialog);
      }
    }
    else {
      this.postRencontre(tmr);
    }
  }

  /* -------------------------------------------- */
  async _presentCite(tmr) {
    const presentCite = this.casesSpeciales.find(c => EffetsDraconiques.presentCites.isCase(c, tmr.coord));
    if (presentCite) {
      const caseData = presentCite;
      const dialog = await EffetsDraconiques.presentCites.choisirUnPresent(caseData, present => {
        this._utiliserPresentCite(presentCite, present, tmr)
        this.restoreTMRAfterAction();
      });
      this.setTMRPendingAction(dialog);
    }
    return presentCite;
  }

  /* -------------------------------------------- */
  async _utiliserPresentCite(presentCite, present, tmr) {
    this.currentRencontre = present.clone({
      'system.force': await RdDDice.rollTotal(present.system.formule),
      'system.coord': tmr.coord
    }, { save: false });

    await EffetsDraconiques.presentCites.ouvrirLePresent(this.actor, presentCite);
    this.removeToken(tmr, presentCite);

    // simuler une rencontre
    let rencontreData = {
      actor: this.actor,
      alias: this.actor.getAlias(),
      reveDepart: this.actor.getReveActuel(),
      competence: this.actor.getBestDraconic(),
      rencontre: this.currentRencontre,
      tmr: tmr,
      presentCite: presentCite
    };
    await this.$tentativeMaitrise(rencontreData);
    this.postRencontre(tmr);
  }

  /* -------------------------------------------- */
  async _jetDeRencontre(tmr) {
    let rencontre = this.lookupRencontreExistente(tmr);
    if (rencontre) {
      return game.system.rdd.rencontresTMR.calculRencontre(rencontre, tmr);
    }
    const coordTMR = (this.isDemiReveCache()
      ? TMRUtility.getTMRType(tmr.coord) + " ??"
      : tmr.label + " (" + tmr.coord + ")");

    this.setTMRPendingAction({ bringToFront: () => { } })
    const myRoll = await RdDDice.rollTotal("1dt", { showDice: SHOW_DICE });
    this.restoreTMRAfterAction()
    if (myRoll == 7) {
      ChatUtility.tellToUser(myRoll + ": Rencontre en " + coordTMR);
      return await game.system.rdd.rencontresTMR.getRencontreAleatoire(tmr, await this.actor.isMauvaiseRencontre())
    } else {
      ChatUtility.tellToUser(myRoll + ": Pas de rencontre en " + coordTMR);
      return undefined;
    }
  }

  lookupRencontreExistente(tmr) {
    return this.rencontresExistantes.find(it => it.system.coord == tmr.coord)
      ?? this.rencontresExistantes.find(it => it.system.coord == "");
  }

  /* -------------------------------------------- */
  async manageTmrInnaccessible(tmr) {
    if (!tmr) {
      return await this.actor.reinsertionAleatoire('Sortie de carte');
    }
    const caseTmrInnaccessible = this.casesSpeciales.find(c => EffetsDraconiques.isInnaccessible(c, tmr.coord));
    if (caseTmrInnaccessible) {
      return await this.actor.reinsertionAleatoire(caseTmrInnaccessible.name);
    }
    return tmr;
  }

  /* -------------------------------------------- */
  async manageCaseHumide(tmr) {
    if (this.isCaseHumide(tmr)) {
      let rollData = {
        actor: this.actor,
        competence: this.actor.getBestDraconic(),
        tmr: tmr,
        canClose: false,
        diffLibre: -7,
        forceCarac: this.actor.getCaracReveActuel(),
        maitrise: { verbe: 'maîtriser', action: 'Maîtriser le fleuve' }
      }
      rollData.double = EffetsDraconiques.isDoubleResistanceFleuve(this.actor) ? true : undefined
      rollData.competence.system.defaut_carac = CARACS.REVE_ACTUEL
      await this._rollMaitriseCaseHumide(rollData)
    }
  }

  /* -------------------------------------------- */
  async _rollMaitriseCaseHumide(rollData) {
    await this._maitriserTMR(rollData, r => this._resultatMaitriseCaseHumide(r));
  }

  async _resultatMaitriseCaseHumide(rollData) {
    if (rollData.rolled.isSuccess && rollData.double) {
      rollData.previous = { rolled: rollData.rolled, ajustements: rollData.ajustements };
      rollData.double = undefined;
      await this._rollMaitriseCaseHumide(rollData);
      return;
    }
    rollData.poesie = await Poetique.getExtrait();
    await ChatMessage.create({
      whisper: ChatUtility.getOwners(this.actor),
      content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-maitrise-tmr.hbs`, rollData)
    });
    if (rollData.rolled.isEchec) {
      await this.$forceClose(`n'a pas maîtrisé la case ${rollData.tmr.label} et quitte les terres médianes`)
    }
    await this.souffleSiEchecTotal(rollData);
  }

  /* -------------------------------------------- */
  async souffleSiEchecTotal(rollData) {
    if (rollData.rolled.isETotal) {
      rollData.souffle = await this.actor.ajouterSouffle({ chat: false });
    }
  }

  /* -------------------------------------------- */
  isCaseHumide(tmr) {
    if (!(TMRUtility.isCaseHumide(tmr) || this.isCaseHumideAdditionelle(tmr))) {
      return false
    }
    if (this.isCaseMaitrisee(tmr.coord)) {
      ChatMessage.create({
        content: tmr.label + ": cette case humide est déja maitrisée grâce à votre Tête <strong>Quête des Eaux</strong>",
        whisper: ChatUtility.getOwners(this.actor)
      })
      return false
    }
    return true
  }

  /* -------------------------------------------- */
  isCaseHumideAdditionelle(tmr) {
    if (tmr.type == 'pont' && EffetsDraconiques.isPontImpraticable(this.actor)) {
      ChatMessage.create({
        content: tmr.label + ": Vous êtes sous le coup d'une Impraticabilité des Ponts : ce pont doit être maîtrisé comme une case humide.",
        whisper: ChatUtility.getOwners(this.actor)
      });
      return true
    }
    if (this.isCaseInondee(tmr.coord)) {
      ChatMessage.create({
        content: tmr.label + ": cette case est inondée, elle doit être maîtrisée comme une case humide.",
        whisper: ChatUtility.getOwners(this.actor)
      });
      return true
    }
    return false
  }

  /* -------------------------------------------- */
  async conquerirCiteFermee(tmr) {
    if (EffetsDraconiques.fermetureCites.find(this.casesSpeciales, tmr.coord)) {
      await this._conquerir(tmr, {
        difficulte: -9,
        action: 'Conquérir la cité',
        onConqueteReussie: r => EffetsDraconiques.fermetureCites.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
        onConqueteEchec: r => {
          this.souffleSiEchecTotal(rollData);
          this.$forceClose(`${this.actor.name} n'a pas maîtrisé la ${tmr.label}, et quitte les TMR.`)
        },
        canClose: false
      });
    }
  }
  /* -------------------------------------------- */
  async purifierPeriple(tmr) {
    if (EffetsDraconiques.periple.find(this.casesSpeciales, tmr.coord)) {
      await this._conquerir(tmr, {
        difficulte: EffetsDraconiques.periple.getDifficulte(tmr),
        action: 'Purifier ' + TMRUtility.getTMRDescr(tmr.coord),
        onConqueteReussie: r => EffetsDraconiques.periple.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
        onConqueteEchec: r => {
          this.souffleSiEchecTotal(rollData);
          this.$forceClose(`${this.actor.name} n'a pas purifié la ${tmr.label}, et quitte les TMR.`)
        },
        canClose: false
      });
    }
  }

  /* -------------------------------------------- */
  async conquerirTMR(tmr) {
    if (EffetsDraconiques.conquete.find(this.casesSpeciales, tmr.coord)) {
      await this._conquerir(tmr, {
        difficulte: -7,
        action: 'Conquérir',
        onConqueteReussie: r => EffetsDraconiques.conquete.onVisiteSupprimer(r.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr)),
        onConqueteEchec: r => this.$forceClose(`${this.actor.name} n'a pas conquis la case ${tmr.label}, et quitte les TMR.`),
        canClose: false
      });
    }
  }

  /* -------------------------------------------- */
  async _conquerir(tmr, options) {
    let rollData = {
      actor: this.actor,
      competence: this.actor.getBestDraconic(),
      tmr: tmr,
      canClose: options.canClose ?? false,
      diffLibre: options.difficulte ?? -7,
      forceCarac: this.actor.getCaracReveActuel(),
      maitrise: { verbe: 'conquérir', action: options.action }
    }
    rollData.competence.system.defaut_carac = CARACS.REVE_ACTUEL

    await this._maitriserTMR(rollData, r => this._onResultatConquerir(r, options))
  }

  /* -------------------------------------------- */
  async _onResultatConquerir(rollData, options) {
    if (rollData.rolled.isETotal) {
      rollData.souffle = await this.actor.ajouterSouffle({ chat: false })
    }
    rollData.poesie = await Poetique.getExtrait()
    await ChatMessage.create({
      whisper: ChatUtility.getOwners(this.actor),
      content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-resultat-maitrise-tmr.hbs`, rollData)
    })
    if (rollData.rolled.isEchec) {
      options.onConqueteEchec(rollData, options.effetDraconique)
    }
    else {
      await options.onConqueteReussie(rollData, options.effetDraconique)
      this.updateTokens()
    }
  }

  /* -------------------------------------------- */
  async _maitriserTMR(rollData, callbackMaitrise) {
    rollData.isTMRCache = rollData.actor.isTMRCache();
    const dialog = await RdDRoll.create(this.actor, rollData,
      {
        html: 'systems/foundryvtt-reve-de-dragon/templates/dialog-roll-maitrise-tmr.hbs',
      },
      {
        name: rollData.maitrise.verbe, label: rollData.maitrise.action,
        callbacks: [
          this.actor.createCallbackExperience(),
          { action: r => { this.restoreTMRAfterAction() } },
          { action: callbackMaitrise }
        ]
      }
    );
    dialog.render(true);
    this.setTMRPendingAction(dialog);
  }

  /* -------------------------------------------- */
  async validerVisite(tmr) {
    await EffetsDraconiques.pelerinage.onVisiteSupprimer(this.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr));
    await EffetsDraconiques.urgenceDraconique.onVisiteSupprimer(this.actor, tmr, (casetmr) => this.removeToken(tmr, casetmr));
  }


  /* -------------------------------------------- */
  async declencheSortEnReserve(coord) {
    const sorts = this.getSortsReserve(coord);
    if (sorts.length > 0) {
      if (EffetsDraconiques.isSortReserveImpossible(this.actor)) {
        ui.notifications.error("Une queue ou un souffle vous empèche de déclencher de sort!");
        return;
      }
      const reserveSecurite = EffetsDraconiques.isReserveEnSecurite(this.actor);
      const reserveExtensible = this.isReserveExtensible(coord);
      if (!EffetsDraconiques.isUrgenceDraconique(this.actor) && (reserveSecurite || reserveExtensible)) {
        await ChatMessage.create({
          content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-demande-declencher-sort.hbs`, {
            actor: this.actor,
            sorts: sorts,
            coord: coord,
            tete: { reserveSecurite: reserveSecurite, reserveExtensible: reserveExtensible }
          }),
          whisper: ChatUtility.getOwners(this.actor)
        })
        return
      }
      await this.processSortReserve(sorts[0]);
    }
  }

  /* -------------------------------------------- */
  async lancerSortEnReserve(coord, sortId) {
    const sort = this.getSortsReserve(coord)
      .find(it => it.id == sortId);
    if (sort) {
      await this.processSortReserve(sort);
    } else {
      await ChatMessage.create({
        content: "Une erreur est survenue : impossible de récupérer le sort en réserve demandé.",
        whisper: ChatUtility.getOwners(this.actor)
      });
    }
  }

  /* -------------------------------------------- */
  async processSortReserve(sortReserve) {
    await this.actor.deleteEmbeddedDocuments('Item', [sortReserve.id]);
    console.log("declencheSortEnReserve", sortReserve)
    const sort = sortReserve.system.sortid ? this.actor.items.get(sortReserve.system.sortid) : undefined
    const message = await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-lancement-sortreserve.hbs`,
      { sortReserve, sort })
    await this.$forceClose(message)
  }

  /* -------------------------------------------- */
  $nettoyerRencontre() {
    // Suppression des dessins des zones possibles
    this.currentRencontre?.graphics?.forEach(graphic => this.pixiTMR.removeGraphic(graphic))
    // Nettoyage de la structureet de l'état
    this.currentRencontre = undefined;
    this.rencontreState = 'aucune';
  }

  /* -------------------------------------------- */
  isCaseInondee(coord) {
    return EffetsDraconiques.debordement.find(this.casesSpeciales, coord);
  }

  isCiteFermee(coord) {
    return EffetsDraconiques.fermetureCites.find(this.casesSpeciales, coord);
  }

  /* -------------------------------------------- */
  isTerreAttache(coord) {
    return EffetsDraconiques.terreAttache.find(this.casesSpeciales, coord);
  }

  /* -------------------------------------------- */
  isCaseMaitrisee(coord) {
    return EffetsDraconiques.queteEaux.find(this.casesSpeciales, coord);
  }

  /* -------------------------------------------- */
  isReserveExtensible(coord) {
    return EffetsDraconiques.reserveExtensible.find(this.casesSpeciales, coord);
  }

  /* -------------------------------------------- */
  isConnaissanceFleuve(tmrApp, nextTMR) {
    return TMRUtility.getTMR(tmrApp).type == 'fleuve' &&
      TMRUtility.getTMR(nextTMR).type == 'fleuve' &&
      EffetsDraconiques.isConnaissanceFleuve(this.actor);
  }

  /* -------------------------------------------- */
  async onClickTMR(event) {
    if (this.viewOnly) {
      return;
    }
    if (this.subdialog) {
      return this.forceTMRContinueAction()
    }
    const currentCoord = this._getCoordActor()
    const currentOddq = TMRUtility.coordTMRToOddq(currentCoord)
    const targetOddq = this.pixiTMR.computeEventOddq(event)
    const targetCoord = TMRUtility.oddqToCoordTMR(targetOddq)

    if (targetCoord == COORD_TMR_INCONNU) {
      ui.notifications.error("Vous ne pouvez pas vous déplacer ici");
      return
    }
    // Validation de la case de destination (gestion du cas des rencontres qui peuvent téléporter)
    const typeDeplacement = this._calculDeplacement(targetCoord, currentCoord, currentOddq, targetOddq);

    if (this.isDemiReveCache()) {
      if (this.isTerreAttache(targetCoord)
        || this.isConnaissanceFleuve(currentCoord, targetCoord)
        || typeDeplacement == 'changeur') {
        // déplacement possible
        await this.actor.setTMRVisible(true);
        this.demiReve = this._tokenDemiReve();
        this._trackToken(this.demiReve);
      }
      else {
        ui.notifications.error(`Vous ne connaissez plus votre position dans les TMR.
        Vous devez utiliser les boutons de direction pour vous déplacer.
        Une fois que vous aurez retrouvé votre demi-rêve, demandez au gardien de vérifier et rendre les TMR visibles.
        `);
        return;
      }
    }

    switch (typeDeplacement) {
      case 'normal':
      case 'changeur':
      case 'passeur':
        await this._deplacerDemiReve(targetCoord, typeDeplacement);
        break;
      case 'messager':
        await this._messagerDemiReve(targetCoord);
        break;
      default:
        ui.notifications.error("Vous ne pouvez vous déplacer que sur des cases adjacentes à votre position ou valides dans le cas d'une rencontre");
        console.log("STATUS :", this.rencontreState, this.currentRencontre);
    }

    await this.$checkQuitterTMR();
  }

  /* -------------------------------------------- */
  _calculDeplacement(targetCoord, currentCoord, fromOddq, toOddq) {
    if (this.isRencontreDeplacement()) {
      if (this.currentRencontre?.locList?.find(coord => coord == targetCoord)) {
        return this.rencontreState;
      }
    }
    else {
      if (this.isTerreAttache(targetCoord) || this.isConnaissanceFleuve(currentCoord, targetCoord) || TMRUtility.distanceOddq(fromOddq, toOddq) <= 1) {
        return 'normal'
      }
    }
    return 'erreur';
  }

  isRencontreDeplacement() {
    return ['passeur', 'changeur', 'messager'].includes(this.rencontreState);
  }

  /* -------------------------------------------- */
  async _messagerDemiReve(targetCoord) {
    /*
     TODO:
     Si la case a un sort en réserve, lancer ce sort.
     Si la case est le demi-rêve, ne pas lancer de sort.
     Si un lancement de sort est en cours, trouver un moyen de réafficher cette fenêtre
       si on essaie de lancer un sort (ou bloquer le lancer de sort)
    */
    this.notifierResonanceSigneDraconique(targetCoord);
    await this.actor.rollUnSort(targetCoord);
    this.$nettoyerRencontre();
  }

  /* -------------------------------------------- */
  externalRefresh() {
    this.createPixiSprites();
    this.$updateValuesDisplay();
    this.updateTokens();
    console.log("TMR REFRESHED !!!");
  }

  /* -------------------------------------------- */
  async _deplacerDemiReve(targetCoord, deplacementType) {
    if (this.subdialog) {
      return this.forceTMRContinueAction()
    }
    if (this.currentRencontre != 'normal') {
      this.$nettoyerRencontre();
    }
    let tmr = TMRUtility.getTMR(targetCoord);
    // Gestion cases spéciales type Trou noir, etc
    tmr = await this.manageTmrInnaccessible(tmr);

    await this.actor.updateCoordTMR(tmr.coord);

    this.forceDemiRevePositionView();
    if (ReglesOptionnelles.isUsing("appliquer-fatigue")) {
      this.cumulFatigue += this.fatigueParCase;
    }
    this.$updateValuesDisplay();
    this.actor.notifyRefreshTMR();

    if (deplacementType == 'normal') { // Pas de rencontres après un saut de type passeur/changeur/...
      await this.manageRencontre(tmr);
    }
    else {
      await this.postRencontre(tmr);
    }
  }

  async notifierResonanceSigneDraconique(coord) {
    if (!this.viewOnly && this.actor.isResonanceSigneDraconique(coord)) {
      ChatMessage.create({
        whisper: ChatUtility.getOwners(this.actor),
        content: await renderTemplate(`systems/foundryvtt-reve-de-dragon/templates/chat-signe-draconique-resonance.hbs`, {
          alias: this.actor.getAlias(),
          typeTMR: TMRUtility.getTMRType(coord)
        })
      });
    }
  }

  /* -------------------------------------------- */
  async postRencontre(tmr) {
    if (!(this.viewOnly || this.currentRencontre)) {
      // TODO: vérifier que la méthode s'arrête en cas de non-maîtrise
      if (!this.descenteTMR) await this.manageCaseHumide(tmr);
      if (!this.descenteTMR) await this.conquerirCiteFermee(tmr);
      if (!this.descenteTMR) await this.purifierPeriple(tmr);
      if (!this.descenteTMR) await this.conquerirTMR(tmr);
      if (!this.descenteTMR) await this.validerVisite(tmr);
      if (!this.descenteTMR) await this.declencheSortEnReserve(tmr.coord);
      if (!this.descenteTMR) await this.actor.checkSoufflePeage(tmr);
    }
  }

  /* -------------------------------------------- */
  async positionnerDemiReve(coord) {
    if (this.subdialog) {
      return this.forceTMRContinueAction()
    }

    await this.actor.updateCoordTMR(coord);
    this.forceDemiRevePositionView();
    let tmr = TMRUtility.getTMR(coord);
    await this.postRencontre(tmr);
    return tmr;
  }

  getTokensDetails(coordTMR) {
    const tmrTooltip = `${coordTMR}: ${TMRUtility.getTMRLabel(coordTMR)}`
    const tokenTooltips = this.allTokens
      .filter(token => token.coordTMR() == coordTMR)
      .map(token => token.tooltip);
    return [tmrTooltip, ...tokenTooltips].reduce(Misc.joining('\n'))
  }

  /* -------------------------------------------- */
  _removeTokens(filter = it => true) {
    this.allTokens.filter(filter).forEach(token => this.pixiTMR.removeToken(token))
  }

  /* -------------------------------------------- */
  _trackToken(token) {
    if (!token) {
      return
    }
    if (this.isDemiReveCache() && [EffetsDraconiques.rencontre.code(), EffetsDraconiques.demiReve.code()].includes(token.code)) {
      return
    }
    this.pixiTMR.positionToken(token)
    if (!this.allTokens.includes(token)) {
      this.allTokens.push(token)
    }
  }
}
