import { RdDBaseActor } from "./actor/base-actor.js";
import { LOG_HEAD, SYSTEM_RDD } from "./constants.js";
import { Grammar } from "./grammar.js";
import { Monnaie } from "./item-monnaie.js";
import { RdDItem, ITEM_TYPES } from "./item.js";
import { RdDTimestamp } from "./time/rdd-timestamp.js";
import { RdDRaretes } from "./item/raretes.js";
import { VOIES_DRACONIC } from "./item-sort.js";

class Migration {
  get code() { return "sample"; }
  get version() { return "0.0.0"; }
  async migrate() { }

  async applyItemsUpdates(computeUpdates) {
    await game.actors.forEach(async (actor) => {
      const actorItemUpdates = computeUpdates(actor.items).filter(it => it != undefined);
      if (actorItemUpdates.length > 0) {
        console.log(
          this.code,
          `Applying updates on actor ${actor.name} items`,
          actorItemUpdates
        );
        await actor.updateEmbeddedDocuments("Item", actorItemUpdates);
      }
    });

    const itemUpdates = computeUpdates(game.items).filter(it => it != undefined);
    if (itemUpdates.length > 0) {
      console.log(this.code, "Applying updates on items", itemUpdates);
      await Item.updateDocuments(itemUpdates);
    }
  }

}

class _1_5_34_migrationPngWebp {
  get code() { return "migrationPngWebp"; }
  get version() { return "1.5.34"; }
  async migrate() {

    const regexOldPngJpg = /(systems\/foundryvtt-reve-de-dragon\/icons\/.*)\.(png|jpg)/;
    const replaceWithWebp = '$1.webp';
    function convertImgToWebp(img) {
      return img.replace(regexOldPngJpg, replaceWithWebp);
    }
    function prepareDocumentsImgUpdate(documents) {
      return documents.filter(it => it.img && it.img.match(regexOldPngJpg))
        .map(it => {
          return { _id: it.id, img: convertImgToWebp(it.img) }
        });
    }

    const itemsUpdates = prepareDocumentsImgUpdate(game.items);
    const actorsUpdates = prepareDocumentsImgUpdate(game.actors);
    //Migrate system png to webp
    await Item.updateDocuments(itemsUpdates);
    await Actor.updateDocuments(actorsUpdates);
    game.actors.forEach(actor => {
      if (actor.token?.img && actor.token.img.match(regexOldPngJpg)) {
        actor.update({ "token.img": convertImgToWebp(actor.token.img) });
      }
      const actorItemsToUpdate = prepareDocumentsImgUpdate(actor.items);
      actor.updateEmbeddedDocuments('Item', actorItemsToUpdate);
    });
  }
}

class _10_0_16_MigrationSortsReserve extends Migration {
  get code() { return "creation-item-sort-reserve"; }
  get version() { return "10.0.16"; }

  async migrate() {
    const actors = game.actors.filter((actor) => actor.type == "personnage" && (actor.system.reve?.reserve?.list?.length ?? 0 > 0))
    Promise.all(actors.map(async it => await this.convertirSortsReserveActeur(it)))
  }

  async convertirSortsReserveActeur(actor) {
    const sortsReserve = actor.system.reve.reserve.list.map(this.conversionSortReserve);
    console.log(`${LOG_HEAD} Migration des sorts en réserve de ${actor.name}`, sortsReserve);
    await actor.createEmbeddedDocuments("Item", sortsReserve, {
      renderSheet: false,
    });
    await actor.update({ 'system.reve.reserve': undefined });
  }

  conversionSortReserve(it) {
    return {
      type: 'sortreserve',
      name: it.sort.name,
      img: it.sort.img,
      system: {
        // ATTENTION, utilisation de data / _id possibles, encore présents pour les anciens sorts en réserve
        sortid: it.sort._id,
        draconic: it.sort.draconic,
        ptreve: (it.sort.system ?? it.sort.data).ptreve_reel,
        coord: it.coord,
        heurecible: 'Vaisseau',
      },
    };
  }
}

class _10_0_17_MigrationCompetenceCreature extends Migration {
  get code() { return "competences-creature-parade"; }
  get version() { return "10.0.17"; }

  async migrate() {
    await this.applyItemsUpdates(items => items
      .filter(it => it.type == "competencecreature" && it.system.isparade && it.system.categorie_parade == "")
      .map(it => { return { _id: it.id, "system.categorie_parade": "armes-naturelles" } }));

    await this.applyItemsUpdates(items => items
      .filter(it => it.type == "competencecreature" && it.system.iscombat)
      .map(it => { return { _id: it.id, "system.categorie": (Grammar.includesLowerCaseNoAccent(it.name, "lancee") ? "lancer" : "melee") } })
    );

  }
}

class _10_0_21_VehiculeStructureResistanceMax extends Migration {
  get code() { return "vehicule-structure-resistance-max"; }
  get version() { return "10.0.21"; }

  async migrate() {
    await game.actors
      .filter((actor) => actor.type == "vehicule")
      .forEach(async (actor) => {
        await actor.update({
          'system.etat.resistance.value': actor.system.resistance,
          'system.etat.resistance.max': actor.system.resistance,
          'system.etat.structure.value': actor.system.structure,
          'system.etat.structure.max': actor.system.structure
        })
      });
  }
}

class _10_0_33_MigrationNomsDraconic extends Migration {
  get code() { return "competences-creature-parade"; }
  get version() { return "10.0.33"; }

  migrationNomDraconic(ancien) {
    if (typeof ancien == 'string') {
      switch (ancien) {
        case 'oniros': case "Voie d'Oniros": return "Voie d'Oniros";
        case 'hypnos': case "Voie d'Hypnos": return "Voie d'Hypnos";
        case 'narcos': case "Voie de Narcos": return "Voie de Narcos";
        case 'thanatos': case "Voie de Thanatos": return "Voie de Thanatos";
      }
      return ancien;
    }
    else if (typeof ancien.name == 'string') {
      return this.migrationNomDraconic(ancien.name)
    }
    return ancien;
  }
  async migrate() {

    await this.applyItemsUpdates(items => items
      .filter(it => ["sort", "sortreserve"].includes(it.type)
        && (typeof it.system.draconic == 'string') || (typeof it.system.draconic?.name == 'string'))
      .map(it => { return { _id: it.id, "system.draconic": this.migrationNomDraconic(it.system.draconic) } }));
  }
}

class _10_2_5_ArmesTirLancer extends Migration {
  constructor() {
    super();
    this.dagues = { "system.competence": 'Dague', "system.lancer": 'Dague de jet', "system.portee_courte": 3, "system.portee_moyenne": 8, "system.portee_extreme": 15 }
    this.javelot = { "system.competence": 'Lance', "system.lancer": 'Javelot', "system.portee_courte": 6, "system.portee_moyenne": 12, "system.portee_extreme": 20 }
    this.fouet = { "system.competence": '', "system.lancer": 'Fouet', "system.portee_courte": 2, "system.portee_moyenne": 2, "system.portee_extreme": 3, "system.penetration": -1 }
    this.arc = { "system.competence": '', "system.tir": 'Arc' }
    this.arbalete = { "system.competence": '', "system.tir": 'Arbalète' }
    this.fronde = { "system.competence": '', "system.tir": 'Fronde' }

    this.mappings = {
      'dague': { filter: it => true, updates: this.dagues },
      'dague de jet': { filter: it => true, updates: this.dagues },
      'javelot': { filter: it => true, updates: this.javelot },
      'lance': { filter: it => it.name == 'Javeline', updates: this.javelot },
      'fouet': { filter: it => true, updates: this.fouet },
      'arc': { filter: it => true, updates: this.arc },
      'arbalete': { filter: it => true, updates: this.arbalete },
      'fronde': { filter: it => true, updates: this.fronde },
    }
  }

  get code() { return "separation-competences-tir-lancer"; }
  get version() { return "10.2.5"; }

  migrateArmeTirLancer(it) {
    let updates = foundry.utils.mergeObject({ _id: it.id }, this.getMapping(it).updates);
    console.log(it.name, updates);
    return updates;
  }

  async migrate() {
    await this.applyItemsUpdates(items => items
      .filter(it => "arme" == it.type)
      .filter(it => this.isTirLancer(it))
      .filter(it => this.getMapping(it).filter(it))
      .map(it => this.migrateArmeTirLancer(it)));
  }


  isTirLancer(it) {
    return Object.keys(this.mappings).includes(this.getCompKey(it));
  }

  getMapping(it) {
    return this.mappings[this.getCompKey(it)];
  }

  getCompKey(it) {
    return Grammar.toLowerCaseNoAccent(it.system.competence);
  }
}

class _10_2_10_DesirLancinant_IdeeFixe extends Migration {
  get code() { return "desir-lancinat-idee-fixe"; }
  get version() { return "10.2.10"; }

  migrateQueue(it) {
    let categorie = undefined
    let name = it.name
    if (Grammar.toLowerCaseNoAccent(name).includes('desir')) {
      categorie = 'lancinant';
      name = it.name.replace('Désir lancinant : ', '');

    }
    if (Grammar.toLowerCaseNoAccent(name).includes('idee fixe')) {
      categorie = 'ideefixe';
      name = it.name.replace('Idée fixe : ', '')
    }
    return {
      _id: it.id, name: name,
      'system.ideefixe': undefined,
      'system.lancinant': undefined,
      'system.categorie': categorie
    }
  }

  async migrate() {
    await this.applyItemsUpdates(items => items
      .filter(it => ['queue', 'ombre'].includes(it.type))
      .map(it => this.migrateQueue(it))
    );
  }
}

class _10_3_0_Inventaire extends Migration {
  get code() { return "migration-equipement-inventaire"; }
  get version() { return "10.3.0"; }

  async migrate() {
    await this.applyItemsUpdates(items => {
      return this._updatesMonnaies(items)
        .concat(this._updatesNonEquipe(items))
        .concat(this._updatesObjets(items))
    });
  }

  _updatesNonEquipe(items) {
    return items
      .filter(it => ['munition'].includes(it.type))
      .map(it => { return { _id: it.id, 'system.equipe': undefined } });
  }
  _updatesObjets(items) {
    return items
      .filter(it => ['objet'].includes(it.type))
      .map(it => { return { _id: it.id, 'system.resistance': undefined, 'system.equipe': undefined } });
  }
  _updatesMonnaies(items) {
    return items
      .filter(it => ['monnaie'].includes(it.type) && it.system.valeur_deniers != undefined)
      .map(it => { return { _id: it.id, 'system.cout': it.system.valeur_deniers / 100, 'system.valeur_deniers': undefined } });
  }
}

class _10_3_0_FrequenceEnvironnement extends Migration {
  get code() { return "migration-frequence-resources"; }
  get version() { return "10.3.0"; }

  async migrate() {
    await this.applyItemsUpdates(items => items.filter(it => ['herbe', 'ingredient'].includes(it.type))
      .map(it => this._updatesFrequences(it)));
  }

  _updatesFrequences(it) {
    const rarete = RdDRaretes.byCode(it.system.rarete);
    return {
      _id: it.id,
      'system.rarete': undefined,
      'system.environnement': [{ milieu: it.system.milieu, rarete: rarete.code, frequence: rarete.frequence }]
    }
  }
}

class _10_3_17_Monnaies extends Migration {
  constructor() {
    super();
    this.mapValeur = {
      "Etain (1 denier)": { name: 'Denier (étain)', system: { cout: 0.01 } },
      "Bronze (10 deniers)": { name: "Sou (bronze)", system: { cout: 0.1 } },
      "Argent (1 sol)": { name: "Sol (argent)", system: { cout: 1 } },
      "Or (10 sols)": { name: "Dragon (or)", system: { cout: 10 } }
    };
  }
  get code() { return "migration-monnaies"; }
  get version() { return "10.3.17"; }

  async migrate() {
    await this.applyItemsUpdates(items => this._updatesMonnaies(items));
  }

  _updatesMonnaies(items) {
    return items
      .filter(it => 'monnaie' == it.type)
      .filter(it => this.mapValeur[it.name] != undefined)
      .map(it => {
        const correction = this.mapValeur[it.name];
        return {
          _id: it.id,
          'name': correction.name,
          'system.cout': correction.system.cout,
          'system.valeur_deniers': undefined
        }
      });
  }
}

class _10_4_6_ServicesEnCommerces extends Migration {

  get code() { return "migration-service-acteurs"; }
  get version() { return "10.4.6"; }

  async migrate() {
    const servicesToMigrate = game.items.filter(it => it.type == 'service');
    servicesToMigrate.forEach(async service => {
      const commerce = await this.convertServiceToCommerce(service);
      await RdDBaseActor.create(commerce, { renderSheet: false });
      await service.delete();
    });
  }

  async convertServiceToCommerce(service) {
    return {
      name: service.name, img: service.img, type: 'commerce',
      system: {
        description: service.system.description,
        notesmj: service.system.descriptionmj,
        illimite: service.system.illimite
      },
      items: await this.transformInventaireCommerce(service)
    }
  }
  async transformInventaireCommerce(service) {
    const serviceItems = (service.system.items ?? []);
    const commerceItems = await Promise.all(serviceItems.map(async (it) => { return await this.transformToItemBoutique(it); }));
    return commerceItems.concat(Monnaie.monnaiesStandard());
  }

  async transformToItemBoutique(serviceRefItem) {
    const item = await RdDItem.getCorrespondingItem(serviceRefItem);
    const itemToCreate = {
      name: item.name, img: item.img, type: item.type,
      system: foundry.utils.mergeObject({ cout: serviceRefItem.system.cout, quantite: serviceRefItem.system.quantite }, item.system, { overwrite: false })
    };
    return itemToCreate;
  }
}

class _10_5_0_UpdatePeriodicite extends Migration {
  get code() { return "migration-periodicite-poisons-maladies"; }
  get version() { return "10.5.0"; }

  async migrate() {
    await this.applyItemsUpdates(items => this._updatePeriodicite(items));
  }

  _updatePeriodicite(items) {
    return items.filter(it => ['poison', 'maladie'].includes(it.type))
      .filter(it => it.system.periodicite != "")
      .map(it => {
        let [incubation, periodicite] = this.getPeriodicite(it);
        const periode = periodicite.split(' ');
        let unite = periode.length == 2
          ? RdDTimestamp.formulesPeriode().find(it => Grammar.includesLowerCaseNoAccent(periode[1], it.code))?.code
          : undefined
        if (unite && Number(periode[0])) {
          return {
            _id: it.id,
            'system.periodicite': undefined,
            'system.incubation': incubation,
            'system.periode.nombre': Number.parseInt(periode[0]),
            'system.periode.unite': unite
          };
        }
        else {
          return {
            _id: it.id,
            'system.periodicite': undefined,
            'system.incubation': it.system.periodicite
          };
        }
      }).filter(it => it != undefined);
  }

  getPeriodicite(it) {
    let p = it.system.periodicite.split(/[\/\\]/);
    switch (p.length) {
      case 2: return [p[0].trim(), p[1].trim()];
      case 1: return ["", it.system.periodicite.trim()];
      default: return [it.system.periodicite.trim(), ""];
    }
  }
}

class _10_7_0_MigrationBlessures extends Migration {
  get code() { return "migration-blessures"; }
  get version() { return "10.7.0"; }

  async migrate() {
    const timestamp = game.system.rdd.calendrier.getTimestamp()
    await Promise.all(game.actors.filter(it => it.isPersonnage() || it.isCreature())
      .map(async (actor) => {
        const legeres = actor.system.blessures?.legeres.liste.filter(it => it.active).map(it => this.creerBlessure(2, 'légère', it, timestamp)) ?? [];
        const graves = actor.system.blessures?.graves.liste.filter(it => it.active).map(it => this.creerBlessure(4, 'grave', it, timestamp)) ?? [];
        const critiques = actor.system.blessures?.critiques.liste.filter(it => it.active).map(it => this.creerBlessure(6, 'critique', it, timestamp));
        const blessures = legeres.concat(graves).concat(critiques);
        if (blessures.length > 0) {
          await actor.createEmbeddedDocuments("Item", blessures);
        }
        await actor.update({
          'system.blessures.legeres.liste': [],
          'system.blessures.graves.liste': [],
          'system.blessures.critiques.liste': []
        })
      }));
  }
  creerBlessure(gravite, graviteTexte, blessure, timestamp) {
    const dateBlessure = timestamp.addJours(-blessure.jours);
    const datePremiereRecup = dateBlessure.addJours(gravite);
    return {
      name: `Blessure ${graviteTexte}`,
      type: 'blessure',
      img: `systems/foundryvtt-reve-de-dragon/icons/sante/blessure${blessure.psdone ? '-soins' : ''}.webp`,
      system: {
        gravite: gravite,
        difficulte: -gravite,
        debut: { indexDate: dateBlessure.indexDate, indexMinute: 0 },
        fin: { indexDate: datePremiereRecup.indexDate, indexMinute: 0 },
        premierssoins: { done: blessure.psdone, bonus: blessure.premiers_soins },
        soinscomplets: { done: blessure.scdone, bonus: blessure.soins_complets },
        localisation: blessure.localisation
      }
    }
  }
}

class _10_7_19_CategorieCompetenceCreature extends Migration {
  get code() { return "categorie-competence-creature"; }
  get version() { return "10.7.19"; }

  async migrate() {
    await this.applyItemsUpdates(items => items
      .filter(it => ITEM_TYPES.competencecreature == it.type)
      .map(it => this.migrateCompetenceCreature(it))
    );
  }

  migrateCompetenceCreature(it) {
    const categorie = this.getCategorie(it)
    if (categorie == it.system.categorie) {
      return undefined
    }
    return { _id: it.id, 'system.categorie': categorie }
  }

  getCategorie(it) {
    if (it.system.ispossession) {
      return 'possession'
    }
    switch (it.system.categorie) {
      case "melee":
        if (it.system.isnaturelle) {
          return 'naturelle'
        }
        return 'melee'
      case "particuliere": case "specialisee": case "connaissance":
        return "generale"
      default:
        return it.system.categorie
    }
  }
}

class _10_7_19_PossessionsEntiteVictime extends Migration {
  get code() { return "possessions-entite-victime"; }
  get version() { return "10.7.19"; }

  async migrate() {
    await this.applyItemsUpdates(items => items
      .filter(it => ITEM_TYPES.possession == it.type)
      .map(it => this.migratePossession(it))
    );
  }

  migratePossession(it) {
    return {
      _id: it.id,
      'system.entite.actorid': it.system.possesseurid,
      'system.victime.actorid': it.system.possedeid
    }
  }
}

class _11_2_20_MigrationAstrologie extends Migration {
  get code() { return "migration-astrologie" }
  get version() { return "11.2.20" }

  async migrate() {
    const nombresAstraux = game.system.rdd.calendrier.getNombresAstraux()
    nombresAstraux.forEach(na => {
      na.lectures = na.valeursFausses
      na.valeursFausses = undefined
    })
    await game.system.rdd.calendrier.setNombresAstraux(nombresAstraux)
  }
}

class _12_0_24_MigrationVoieSorts extends Migration {
  get code() { return "migration-voies-sorts" }
  get version() { return "12.0.24" }

  async migrate() {
    await this.applyItemsUpdates(items => items
      .filter(it => ITEM_TYPES.sort == it.type)
      .map(it => this.migrateSort(it))
    )
  }
  migrateSort(it) {
    return {
      _id: it.id,
      'system.draconic': this.convertDraconic(it.system.draconic),
    }
  }
  convertDraconic(draconic) {
    for (let v of VOIES_DRACONIC) {
      if ([v.label, v.short, v.code].includes(draconic)) {
        return v.short
      }
    }
    return draconic
  }
}

export class Migrations {
  static getMigrations() {
    return [
      new _1_5_34_migrationPngWebp(),
      new _10_0_16_MigrationSortsReserve(),
      new _10_0_17_MigrationCompetenceCreature(),
      new _10_0_21_VehiculeStructureResistanceMax(),
      new _10_0_33_MigrationNomsDraconic(),
      new _10_2_5_ArmesTirLancer(),
      new _10_2_10_DesirLancinant_IdeeFixe(),
      new _10_3_0_Inventaire(),
      new _10_3_0_FrequenceEnvironnement(),
      new _10_3_17_Monnaies(),
      new _10_4_6_ServicesEnCommerces(),
      new _10_5_0_UpdatePeriodicite(),
      new _10_7_0_MigrationBlessures(),
      new _10_7_19_CategorieCompetenceCreature(),
      new _10_7_19_PossessionsEntiteVictime(),
      new _11_2_20_MigrationAstrologie(),
    ];
  }

  constructor() {
    game.settings.register(SYSTEM_RDD, "systemMigrationVersion", {
      name: "System Migration Version",
      scope: "world",
      config: false,
      type: String,
      default: "0.0.0",
    });
  }

  migrate() {
    let currentVersion = game.settings.get(SYSTEM_RDD, "systemMigrationVersion")
    if (currentVersion.startsWith("v")) {
      currentVersion = currentVersion.substring(1)
    }
    if (foundry.utils.isNewerVersion(game.system.version, currentVersion)) {
      // if (true) { /* comment previous and uncomment here to test before upgrade  */
      const migrations = Migrations.getMigrations().filter(m => foundry.utils.isNewerVersion(m.version, currentVersion));
      if (migrations.length > 0) {
        migrations.sort((a, b) => this.compareVersions(a, b));
        migrations.forEach(async (m) => {
          ui.notifications.info(
            `${LOG_HEAD} Executing migration ${m.code}: version ${currentVersion} is lower than ${m.version}`
          );
          await m.migrate();
        });
        ui.notifications.info(
          `Migrations done, version will change to ${game.system.version}`
        );
      } else {
        console.log(`${LOG_HEAD} No migration needeed, version will change to ${game.system.version}`
        );
      }

      game.settings.set(
        SYSTEM_RDD,
        "systemMigrationVersion",
        game.system.version
      );
    } else {
      console.log(`${LOG_HEAD} No system version changed`);
    }
  }

  compareVersions(a, b) {
    return isNewerVersion(a.version, b.version) ? 1 : isNewerVersion(b.version, a.version) ? -1 : 0;
  }
}