// SPDX-FileCopyrightText: 2021 Johannes Loher
//
// SPDX-License-Identifier: MIT

import { notifications } from "../ui/notifications.js";
import { logger } from "../utils/logger.js";
import { getGame } from "../utils/utils.js";
import { migration as migration001 } from "./001.js";
import { migration as migration002 } from "./002.js";
import { migration as migration003 } from "./003.js";
import { migration as migration004 } from "./004.js";
import { migration as migration005 } from "./005.js";
import { migration as migration006 } from "./006.js";
import { migration as migration007 } from "./007.js";
import { migration as migration008 } from "./008.js";
import { migration as migration009 } from "./009.js";

/**
 * Perform migrations.
 * @returns {Promise<void>} A promise that resolves once all migrations have completed
 */
async function migrate() {
  if (!getGame().user?.isGM) {
    return;
  }

  const oldMigrationVersion = getCurrentMigrationVersion();

  const targetMigrationVersion = migrations.length;

  if (isFirstWorldStart(oldMigrationVersion)) {
    getGame().settings.set("ds4", "systemMigrationVersion", targetMigrationVersion);
    return;
  }

  return migrateFromTo(oldMigrationVersion, targetMigrationVersion);
}

/**
 * Migrate from a given version to another version.
 * @param {number} oldMigrationVersion The old migration version
 * @param {number} targetMigrationVersion The migration version to migrate to
 * @returns {Promise<void>} A promise the resolves once the migration is complete
 */
async function migrateFromTo(oldMigrationVersion, targetMigrationVersion) {
  if (!getGame().user?.isGM) {
    return;
  }

  const migrationsToExecute = migrations.slice(oldMigrationVersion, targetMigrationVersion);

  if (migrationsToExecute.length > 0) {
    notifications.info(
      getGame().i18n.format("DS4.InfoSystemUpdateStart", {
        currentVersion: oldMigrationVersion,
        targetVersion: targetMigrationVersion,
      }),
      { permanent: true },
    );

    /** @type {Result} */
    let result = "success";
    for (const [i, { migrate }] of migrationsToExecute.entries()) {
      const currentMigrationVersion = oldMigrationVersion + i + 1;
      logger.info("executing migration script", currentMigrationVersion);
      try {
        const r = await migrate();
        getGame().settings.set("ds4", "systemMigrationVersion", currentMigrationVersion);
        if (r === "error") {
          result = "error";
        }
      } catch (err) {
        notifications.error(
          getGame().i18n.format("DS4.ErrorDuringMigration", {
            currentVersion: oldMigrationVersion,
            targetVersion: targetMigrationVersion,
            migrationVersion: currentMigrationVersion,
          }),
          { permanent: true },
        );
        logger.error("Failed ds4 system migration:", err);
        return;
      }
    }

    if (result === "success") {
      notifications.info(
        getGame().i18n.format("DS4.InfoSystemUpdateCompletedSuccessfully", {
          currentVersion: oldMigrationVersion,
          targetVersion: targetMigrationVersion,
        }),
        { permanent: true },
      );
    } else {
      notifications.warn(
        getGame().i18n.format("DS4.WarningSystemUpdateCompletedWithErrors", {
          currentVersion: oldMigrationVersion,
          targetVersion: targetMigrationVersion,
        }),
        { permanent: true },
      );
    }
  }
}

/**
 * Migrate a compendium pack from a given version to another version.
 * @param {CompendiumCollection} pack The compendium pack to migrate
 * @param {number} oldMigrationVersion The old version number
 * @param {number} targetMigrationVersion The target version number
 * @returns {Promise<void>} A promise that resolves once the migration is complete
 */
async function migrateCompendiumFromTo(pack, oldMigrationVersion, targetMigrationVersion) {
  if (!getGame().user?.isGM) {
    return;
  }

  const migrationsToExecute = migrations.slice(oldMigrationVersion, targetMigrationVersion);

  if (migrationsToExecute.length > 0) {
    notifications.info(
      getGame().i18n.format("DS4.InfoCompendiumMigrationStart", {
        pack: pack.title,
        currentVersion: oldMigrationVersion,
        targetVersion: targetMigrationVersion,
      }),
      { permanent: true },
    );

    for (const [i, { migrateCompendium }] of migrationsToExecute.entries()) {
      const currentMigrationVersion = oldMigrationVersion + i + 1;
      logger.info("executing compendium migration ", currentMigrationVersion);
      try {
        await migrateCompendium(pack);
      } catch (err) {
        notifications.error(
          getGame().i18n.format("DS4.ErrorDuringCompendiumMigration", {
            pack: pack.title,
            currentVersion: oldMigrationVersion,
            targetVersion: targetMigrationVersion,
            migrationVersion: currentMigrationVersion,
          }),
          { permanent: true },
        );
        logger.error("Failed ds4 compendium migration:", err);
        return;
      }
    }

    notifications.info(
      getGame().i18n.format("DS4.InfoCompendiumMigrationCompleted", {
        pack: pack.title,
        currentVersion: oldMigrationVersion,
        targetVersion: targetMigrationVersion,
      }),
      { permanent: true },
    );
  }
}

/**
 * Get the current migration version.
 * @returns {number} The current migration version
 */
function getCurrentMigrationVersion() {
  return getGame().settings.get("ds4", "systemMigrationVersion");
}

/**
 * Get the target migration version.
 * @returns {number} The target migration version
 */
function getTargetMigrationVersion() {
  return migrations.length;
}

/** @typedef {"success" | "error"} Result */

/**
 * @typedef {object} Migration
 * @property {() => Promise<Result>} migrate
 * @property {import("./migrationHelpers").CompendiumMigrator} migrateCompendium
 */

/**
 * @type {Migration[]}
 */
const migrations = [
  migration001,
  migration002,
  migration003,
  migration004,
  migration005,
  migration006,
  migration007,
  migration008,
  migration009,
];

/**
 * DOes the migration version indicate the world is being started for the first time?
 * @param {number} migrationVersion A migration version
 * @returns {boolean} Whether the migration version indicates it is the first start of the world
 */
function isFirstWorldStart(migrationVersion) {
  return migrationVersion < 0;
}

export const migration = {
  migrate,
  migrateFromTo,
  getCurrentMigrationVersion,
  getTargetMigrationVersion,
  migrateCompendiumFromTo,
};
