import { RdDBaseActor } from "./actor/base-actor.js";
import { LOG_HEAD, SYSTEM_RDD } from "./constants.js";
import { Environnement } from "./environnement.js";
import { Grammar } from "./grammar.js";
import { Monnaie } from "./item-monnaie.js";
import { RdDItem } from "./item.js";
import { RdDTimestamp } from "./rdd-timestamp.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);
      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);
    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() {
    await game.actors
      .filter((actor) => actor.type == "personnage")
      .filter((actor) => actor.system.reve?.reserve?.list?.length ?? 0 > 0)
      .forEach(async (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 = 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) {
    return {
      _id: it.id,
      'system.rarete': undefined,
      'system.environnement': [{ milieu: it.system.milieu, rarete: it.system.rarete, frequence: Environnement.getFrequenceRarete(it.system.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: 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(), ""];
    }
  }
}

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(),
    ];
  }

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

  migrate() {
    const currentVersion = game.settings.get(SYSTEM_RDD, "systemMigrationVersion");
    if (isNewerVersion(game.system.version, currentVersion)) {
    //if (true) { /* comment previous and uncomment here to test before upgrade  */
      const migrations = Migrations.getMigrations().filter(m => isNewerVersion(m.version, currentVersion));
      if (migrations.length > 0) {
        migrations.sort((a, b) => this.compareVersions(a, b));
        migrations.forEach(async (m) => {
          ui.notifications.info(
            `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;
  }
}