import { MAX_NOMBRE_ASTRAL, RdDTimestamp, WORLD_TIMESTAMP_SETTING } from "./rdd-timestamp.js";
import { RdDCalendrierEditor } from "./rdd-calendrier-editor.js";
import { RdDResolutionTable } from "../rdd-resolution-table.js";
import { RdDUtility } from "../rdd-utility.js";
import { RdDDice } from "../rdd-dice.js";
import { Misc } from "../misc.js";
import { DialogChronologie } from "../dialog-chronologie.js";
import { HIDE_DICE, SYSTEM_RDD, SYSTEM_SOCKET_ID } from "../constants.js";
import { ReglesOptionnelles } from "../settings/regles-optionnelles.js";
import { DialogChateauDormant } from "../sommeil/dialog-chateau-dormant.js";
import { APP_ASTROLOGIE_REFRESH, AppAstrologie } from "../sommeil/app-astrologie.js";
import { AutoAdjustDarkness } from "./auto-adjust-darkness.js";

const TEMPLATE_CALENDRIER = "systems/foundryvtt-reve-de-dragon/templates/time/calendar.hbs";

const INITIAL_CALENDAR_POS = { top: 200, left: 200, horlogeAnalogique: true };
/* -------------------------------------------- */
export class RdDCalendrier extends Application {
  static init() {
    game.settings.register(SYSTEM_RDD, "liste-nombre-astral", {
      name: "liste-nombre-astral",
      scope: "world",
      config: false,
      default: [],
      type: Object
    });

    game.settings.register(SYSTEM_RDD, "calendrier-pos", {
      name: "calendrierPos",
      scope: "client",
      config: false,
      default: INITIAL_CALENDAR_POS,
      type: Object
    });
  }

  static get defaultOptions() {
    return mergeObject(super.defaultOptions, {
      title: "Calendrier",
      template: TEMPLATE_CALENDRIER,
      classes: ["calendar"],
      popOut: true,
      resizable: false,
      width: 'fit-content',
      height: 'fit-content',
    });
  }

  constructor() {
    super();
    this.timestamp = RdDTimestamp.getWorldTime();
    if (Misc.isUniqueConnectedGM()) { // Uniquement si GM
      RdDTimestamp.setWorldTime(this.timestamp);
      this.nombresAstraux = this.getNombresAstraux();
      this.rebuildNombresAstraux(); // Ensure always up-to-date
    }
    Hooks.on('updateSetting', async (setting, update, options, id) => this.onUpdateSetting(setting, update, options, id));
  }

  get title() {
    const calendrier = this.timestamp.toCalendrier();
    return `${calendrier.heure.label}, ${calendrier.jourDuMois} ${calendrier.mois.label} ${calendrier.annee} (${calendrier.mois.saison})`;
  }

  savePosition() {
    game.settings.set(SYSTEM_RDD, "calendrier-pos", {
      top: this.position.top,
      left: this.position.left,
      horlogeAnalogique: this.horlogeAnalogique
    });
  }

  getSavePosition() {
    const pos = game.settings.get(SYSTEM_RDD, "calendrier-pos");
    if (pos?.top == undefined) {
      return INITIAL_CALENDAR_POS;
    }
    this.horlogeAnalogique = pos.horlogeAnalogique;
    return pos
  }

  setPosition(position) {
    super.setPosition(position)
    this.savePosition()
  }

  display() {
    AutoAdjustDarkness.adjust(RdDTimestamp.getWorldTime().darkness);
    const pos = this.getSavePosition()
    this.render(true, { left: pos.left, top: pos.top });
    return this;
  }

  _getHeaderButtons() {
    if (game.user.isGM) {
      return [
        { class: "calendar-astrologie", icon: "fa-solid fa-moon-over-sun", onclick: ev => this.showAstrologieEditor() },
        { class: "calendar-set-datetime", icon: "fa-solid fa-calendar-pen", onclick: ev => this.showCalendarEditor() },
      ]
    }
    return []
  }

  async close() { }

  async onUpdateSetting(setting, update, options, id) {
    if (setting.key == SYSTEM_RDD + '.' + WORLD_TIMESTAMP_SETTING) {
      this.timestamp = RdDTimestamp.getWorldTime();
      this.positionAiguilles()
      this.render(false);
      Hooks.callAll(APP_ASTROLOGIE_REFRESH);
    }
  }

  getData() {
    const formData = super.getData();
    this.fillCalendrierData(formData);
    return formData;
  }

  /* -------------------------------------------- */
  fillCalendrierData(formData = {}) {
    mergeObject(formData, this.timestamp.toCalendrier());
    formData.isGM = game.user.isGM;
    formData.heures = RdDTimestamp.definitions()
    formData.horlogeAnalogique = this.horlogeAnalogique;
    formData.autoDarkness = AutoAdjustDarkness.isAuto()
    return formData;
  }

  /* -------------------------------------------- */
  /** @override */
  async activateListeners(html) {
    super.activateListeners(html);
    this.html = html;
    this.html.find('.ajout-chronologie').click(ev => DialogChronologie.create());
    this.html.find('.toggle-horloge-analogique').click(ev => this.onToggleHorlogeAnalogique())
    this.html.find('.toggle-auto-darkness').click(ev => this.onToggleAutoDarkness())
    this.html.find('.calendar-btn').click(ev => this.onCalendarButton(ev));
    this.html.find('.horloge-roue .horloge-heure').click(event => {
      const h = this.html.find(event.currentTarget)?.data('heure');
      this.positionnerHeure(Number(h));
    })
    this.html.find('.calendar-set-datetime').click(ev => {
      ev.preventDefault();
      this.showCalendarEditor();
    });

    this.html.find('.calendar-astrologie').click(ev => {
      ev.preventDefault();
      this.showAstrologieEditor();
    });
    this.positionAiguilles()
  }

  positionAiguilles() {
    const timestamp = this.getTimestamp();
    this.html.find(`div.horloge-roue div.horloge-aiguille-heure img`).css(Misc.cssRotation(timestamp.angleHeure));
    this.html.find(`div.horloge-roue div.horloge-aiguille-minute img`).css(Misc.cssRotation(timestamp.angleMinute));
  }

  onToggleHorlogeAnalogique() {
    this.horlogeAnalogique = !this.horlogeAnalogique;
    this.savePosition()
    this.display()
  }

  /* -------------------------------------------- */
  getNombresAstraux() {
    return game.settings.get(SYSTEM_RDD, "liste-nombre-astral") ?? [];
  }

  /* -------------------------------------------- */
  dateCourante() {
    return this.timestamp.formatDate();
  }

  dateReel() {
    return new Date().toLocaleString("sv-SE", {
      year: "numeric",
      month: "2-digit",
      day: "2-digit",
      hour: "2-digit",
      minute: "2-digit"
    });
  }

  isAfterIndexDate(indexDate) {
    // TODO: standardize
    return indexDate < this.timestamp.indexDate;
  }

  /* -------------------------------------------- */
  heureCourante() { return RdDTimestamp.definition(this.timestamp.heure); }

  /* -------------------------------------------- */
  getCurrentMinute() { return this.timestamp.indexMinute; }

  getTimestamp() {
    return this.timestamp;
  }
  getTimestampFinChateauDormant(nbJours = 0) {
    return this.timestamp.nouveauJour().addJours(nbJours);
  }

  getTimestampFinHeure(nbHeures = 0) {
    return this.timestamp.nouvelleHeure().addHeures(nbHeures);
  }

  /* -------------------------------------------- */
  getIndexFromDate(jour, mois) {
    const addYear = mois < this.timestamp.mois || (mois == this.timestamp.mois && jour < this.timestamp.jour)
    const time = RdDTimestamp.timestamp(this.timestamp.annee + (addYear ? 1 : 0), mois, jour);
    return time.indexDate;
  }

  /* -------------------------------------------- */
  getJoursSuivants(count) {
    return Misc.intArray(this.timestamp.indexDate, this.timestamp.indexDate + count)
      .map(i => { return { label: RdDTimestamp.formatIndexDate(i), index: i } })
  }

  /* -------------------------------------------- */
  async ajouterNombreAstral(indexDate) {
    const nombreAstral = await RdDDice.rollTotal("1dh", { showDice: HIDE_DICE, rollMode: "selfroll" });
    return {
      nombreAstral: nombreAstral,
      valeursFausses: [],
      index: indexDate
    }
  }

  /* -------------------------------------------- */
  resetNombresAstraux() {
    this.nombresAstraux = [];
    game.settings.set(SYSTEM_RDD, "liste-nombre-astral", []);

    game.socket.emit(SYSTEM_SOCKET_ID, {
      msg: "msg_reset_nombre_astral",
      data: {}
    });
  }

  /**
   * 
   * @param {*} indexDate la date pour laquelle obtenir le nombre astral. Si undefined, on prend la date du jour
   * @returns le nombre astral pour la date, ou pour la date du jour si la date n'est pas fournie.
   *  Si aucun nombre astral n'est trouvé, retourne 0 (cas où l'on demanderait un nombre astral en dehors des 12 jours courant et à venir)
   */
  getNombreAstral(indexDate = undefined) {
    if (indexDate == undefined) {
      indexDate = this.timestamp.indexDate;
    }
    this.nombresAstraux = this.getNombresAstraux();
    let astralData = this.nombresAstraux.find((nombreAstral, i) => nombreAstral.index == indexDate);
    return astralData?.nombreAstral ?? 0;
  }

  /* -------------------------------------------- */
  async rebuildNombresAstraux() {
    if (Misc.isUniqueConnectedGM()) {
      let newList = [];
      for (let i = 0; i < MAX_NOMBRE_ASTRAL; i++) {
        let dayIndex = this.timestamp.indexDate + i;
        let na = this.nombresAstraux.find(n => n.index == dayIndex);
        if (na) {
          newList[i] = na;
        } else {
          newList[i] = await this.ajouterNombreAstral(dayIndex);
        }
      }
      this.nombresAstraux = newList;
      game.settings.set(SYSTEM_RDD, "liste-nombre-astral", newList);
      game.actors.filter(it => it.isPersonnage()).forEach(actor => actor.supprimerAnciensNombresAstraux());
      this.notifyChangeNombresAstraux();
    }
  }

  notifyChangeNombresAstraux() {
    Hooks.callAll(APP_ASTROLOGIE_REFRESH);
    game.socket.emit(SYSTEM_SOCKET_ID, {
      msg: "msg_refresh_nombre_astral",
      data: {}
    });
  }

  /* -------------------------------------------- */
  async setNewTimestamp(newTimestamp) {
    const oldTimestamp = this.timestamp;
    await Promise.all(game.actors.map(async actor => await actor.onTimeChanging(oldTimestamp, newTimestamp)));
    RdDTimestamp.setWorldTime(newTimestamp);
    if (oldTimestamp.indexDate + 1 == newTimestamp.indexDate && ReglesOptionnelles.isUsing("chateau-dormant-gardien")) {
      await DialogChateauDormant.create();
    }
    this.timestamp = newTimestamp;
    await this.rebuildNombresAstraux();
    this.positionAiguilles()
    this.display();
  }

  /* -------------------------------------------- */
  async onCalendarButton(ev) {
    ev.preventDefault();
    const calendarAvance = ev.currentTarget.attributes['data-calendar-avance'];
    const calendarSet = ev.currentTarget.attributes['data-calendar-set'];
    if (calendarAvance) {
      await this.incrementTime(Number(calendarAvance.value));
    }
    else if (calendarSet) {
      this.positionnerHeure(Number(calendarSet.value));
    }
    this.positionAiguilles()
  }

  /* -------------------------------------------- */
  async incrementTime(minutes = 0) {
    if (game.user.isGM) {
      await this.setNewTimestamp(this.timestamp.addMinutes(minutes));
      Hooks.callAll(APP_ASTROLOGIE_REFRESH);
    }
  }

  /* -------------------------------------------- */
  async incrementerJour() {
    await this.setNewTimestamp(this.timestamp.nouveauJour());
  }

  /* -------------------------------------------- */
  async positionnerHeure(heure) {
    if (game.user.isGM) {
      const indexDate = this.timestamp.indexDate;
      const addDay = this.timestamp.heure < heure ? 0 : 1;
      const newTimestamp = new RdDTimestamp({ indexDate: indexDate + addDay }).addHeures(heure);
      await this.setNewTimestamp(newTimestamp)
      Hooks.callAll(APP_ASTROLOGIE_REFRESH);
    }
  }

  /* -------------------------------------------- */
  getLectureAstrologieDifficulte(dateIndex) {
    let indexNow = this.timestamp.indexDate;
    let diffDay = dateIndex - indexNow;
    return - Math.floor(diffDay / 2);
  }

  /* -------------------------------------------- */
  async requestNombreAstral(request) {
    const actor = game.actors.get(request.id);
    if (Misc.isUniqueConnectedGM()) { // Only once
      console.log(request);
      let jourDiff = this.getLectureAstrologieDifficulte(request.date);
      let niveau = Number(request.astrologie.system.niveau) + Number(request.conditions) + Number(jourDiff) + Number(request.etat);
      let rollData = {
        caracValue: request.carac_vue,
        finalLevel: niveau,
        showDice: HIDE_DICE,
        rollMode: "blindroll"
      };
      await RdDResolutionTable.rollData(rollData);
      request.rolled = rollData.rolled;
      request.isValid = request.rolled.isSuccess;
      request.nbAstral = this.getNombreAstral(request.date);

      if (request.rolled.isSuccess) {
        if (request.rolled.isPart) {
          // Gestion expérience (si existante)
          request.competence = actor.getCompetence('Astrologie')
          request.selectedCarac = actor.system.carac["vue"];
          actor.appliquerAjoutExperience(request, 'hide');
        }
      }
      else {
        request.nbAstral = await RdDDice.rollTotal("1dhr" + request.nbAstral, {
          rollMode: "selfroll", showDice: HIDE_DICE
        });
        // Mise à jour des nombres astraux du joueur
        this.addNbAstralIncorect(request.id, request.date, request.nbAstral);
      }

      if (Misc.getActiveUser(request.userId)?.isGM) {
        RdDUtility.responseNombreAstral(request);
      } else {
        game.socket.emit(SYSTEM_SOCKET_ID, {
          msg: "msg_response_nombre_astral",
          data: request
        });
      }
    }
  }

  addNbAstralIncorect(actorId, date, nbAstral) {
    const astralData = this.nombresAstraux.find((nombreAstral, i) => nombreAstral.index == date);
    astralData.valeursFausses.push({ actorId: actorId, nombreAstral: nbAstral });
    game.settings.set(SYSTEM_RDD, "liste-nombre-astral", this.nombresAstraux);
  }

  /* -------------------------------------------- */
  getAjustementAstrologique(heureNaissance, name = undefined) {
    const defHeure = RdDTimestamp.findHeure(heureNaissance);
    if (defHeure) {
      return RdDTimestamp.ajustementAstrologiqueHeure(defHeure.heure, this.getNombreAstral(), this.timestamp.heure);
    }
    else if (name) {
      ui.notifications.warn(name + " n'a pas d'heure de naissance, ou elle est incorrecte : " + heureNaissance);
    }
    else {
      ui.notifications.warn(heureNaissance + " ne correspond pas à une heure de naissance");
    }
    return 0;
  }

  /* -------------------------------------------- */
  async saveEditeur(calendrierData) {
    const newTimestamp = RdDTimestamp.timestamp(
      Number.parseInt(calendrierData.annee),
      calendrierData.mois.heure,
      Number.parseInt(calendrierData.jourMois),
      calendrierData.heure.heure,
      Number.parseInt(calendrierData.minutes)
    );
    await this.setNewTimestamp(newTimestamp);
  }

  /* -------------------------------------------- */
  async showCalendarEditor() {
    const calendrierData = this.fillCalendrierData();
    if (this.editeur == undefined) {
      const html = await renderTemplate('systems/foundryvtt-reve-de-dragon/templates/time/calendar-editor.hbs', calendrierData);
      this.editeur = new RdDCalendrierEditor(html, this, calendrierData)
    }
    this.editeur.updateData(calendrierData);
    this.editeur.render(true);
  }

  /* -------------------------------------------- */
  async showAstrologieEditor() {
    await AppAstrologie.create();
  }

  async onToggleAutoDarkness() {
    await AutoAdjustDarkness.toggle()
    this.display()
  }
}
