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

import { logger } from "../utils/logger.js";
import { getGame } from "../utils/utils.js";

/**
 * @template T
 * @typedef {(document: T) => Promise<void>} Migrator
 */

/**
 * Migrate a collection.
 * @template T
 * @param {WorldCollection} collection
 * @param {Migrator<T>}     migrateDocument
 * @returns {Promise<import("./migration.js").Result>} A promise that resolves once the migration is complete
 */
export async function migrateCollection(collection, migrateDocument) {
  /** @type {import("./migration.js").Result} */
  let result = "success";
  const { documentName } = collection.constructor;
  for (const document of collection) {
    logger.info(`Migrating ${documentName} document ${document.name} (${document.id})`);
    try {
      await migrateDocument(document);
    } catch (err) {
      logger.error(
        `Error during migration of ${documentName} document ${document.name} (${document.id}), continuing anyways.`,
        err,
      );
      result = "error";
    }
  }
  return result;
}

/**
 * @param {Migrator<ActiveEffect>}  [migrateActiveEffect]
 * @returns {Migrator<Scene>}
 */
export function getItemMigrator(migrateActiveEffect) {
  /**
   * @param {Item} item
   */
  return async (item) => {
    if (migrateActiveEffect) {
      for (const effect of item.effects) {
        await migrateActiveEffect(effect);
      }
    }
  };
}

/**
 * @param {Migrator<Item>}          [migrateItem]
 * @param {Migrator<ActiveEffect>}  [migrateActiveEffect]
 * @returns {Migrator<Scene>}
 */
export function getActorMigrator(migrateItem, migrateActiveEffect) {
  /**
   * @param {Actor} actor
   */
  return async (actor) => {
    if (migrateItem) {
      for (const item of actor.items) {
        await migrateItem(item);
      }
    }
    if (migrateActiveEffect) {
      for (const effect of actor.effects) {
        await migrateActiveEffect(effect);
      }
    }
  };
}

/**
 * @param {Migrator<Actor>} [migrateActor]
 * @returns {Migrator<Scene>}
 */
export function getSceneMigrator(migrateActor) {
  /**
   * @param {Scene} scene
   */
  return async (scene) => {
    if (migrateActor) {
      for (const token of scene.tokens) {
        if (!token.actorLink && token.actor) {
          await migrateActor(token.actor);
        }
      }
    }
  };
}

/** @typedef {(pack: CompendiumCollection) => Promise<import("./migration.js").Result>} CompendiumMigrator */

/**
 * Migrate world compendium packs.
 * @param {CompendiumMigrator} migrateCompendium A function for migrating a single compendium pack
 * @returns {Promise<import("./migration.js").Result>} A promise that resolves once the migration is complete
 */
export async function migrateCompendiums(migrateCompendium) {
  /** @type {import("./migration.js").Result} */
  let result = "success";
  for (const compendium of getGame().packs ?? []) {
    if (compendium.metadata.package !== "world") continue;
    if (!["Actor", "Item", "Scene"].includes(compendium.metadata.type)) continue;
    const r = await migrateCompendium(compendium);
    if (r === "error") {
      result = "error";
    }
  }
  return result;
}

/**
 * @typedef {object} Migrators
 * @property {Migrator<Item>}   [migrateItem]
 * @property {Migrator<Actor>}  [migrateActor]
 * @property {Migrator<Scene>}  [migrateScene]
 */

/**
 * Get a compendium migrator for the given migrators.
 * @param {Migrators} [migrators={}]                        The functions to use for getting update data
 * @param {{migrateToTemplateEarly?: boolean}} [options={}] Additional options for the compendium migrator
 * @returns {CompendiumMigrator} The resulting compendium migrator
 */
export function getCompendiumMigrator(
  { migrateItem, migrateActor, migrateScene } = {},
  { migrateToTemplateEarly = true } = {},
) {
  return async (pack) => {
    /** @type {import("./migration.js").Result} */
    let result = "success";

    const type = pack.metadata.type;
    const migrateDocument = {
      Item: migrateItem,
      Actor: migrateActor,
      Scene: migrateScene,
    }[type];

    if (migrateDocument) {
      const wasLocked = pack.locked;
      await pack.configure({ locked: false });
      if (migrateToTemplateEarly) {
        await pack.migrate();
      }

      const documents = await pack.getDocuments();

      for (const doc of documents) {
        try {
          logger.info(`Migrating document ${doc.name} (${doc.id}) in compendium ${pack.collection}`);
          await migrateDocument(doc);
        } catch (err) {
          logger.error(
            `Error during migration of document ${doc.name} (${doc.id}) in compendium ${pack.collection}, continuing anyways.`,
            err,
          );
          result = "error";
        }
      }

      if (!migrateToTemplateEarly) {
        await pack.migrate();
      }
      await pack.configure({ locked: wasLocked });
    }

    return result;
  };
}
