import ApplicationV2 from "../../api/application.mjs";
import HandlebarsApplicationMixin from "../../api/handlebars-application.mjs";

/**
 * @import World from "@client/packages/world.mjs";
 * @import {ApplicationConfiguration, ApplicationFormSubmission} from "../../_types.mjs";
 */

/**
 * @typedef WorldConfigOptions
 * @property {World} world The World being managed
 * @property {boolean} [tour] Is this World being shown as part of a Tour?
 */

/**
 * The World Management setup application
 */
export default class WorldConfig extends HandlebarsApplicationMixin(ApplicationV2) {

  /**
   * @param {Partial<ApplicationConfiguration> & WorldConfigOptions} options Application configuration options
   */
  constructor(options) {
    super(options);
    this.world = options.world;
  }

  /* -------------------------------------------- */

  /** @inheritDoc */
  static DEFAULT_OPTIONS = {
    tag: "form",
    id: "world-config",
    window: {
      icon: "fa-solid fa-globe",
      contentClasses: ["standard-form"]
    },
    position: {
      width: 600
    },
    form: {
      handler: WorldConfig.#onSubmit,
      closeOnSubmit: true
    }
  };

  /** @override */
  static PARTS = {
    config: {
      template: "templates/sidebar/apps/world-config.hbs",
      scrollable: [""]
    },
    footer: {template: "templates/generic/form-footer.hbs"}
  };

  /**
   * The website knowledge base URL.
   * @type {string}
   */
  static #WORLD_KB_URL = "https://foundryvtt.com/article/game-worlds/";

  /* -------------------------------------------- */

  /**
   * The World being configured.
   * @type {World}
   */
  world;

  /** @override */
  get title() {
    return _loc("WORLD.Title.Update", {world: this.world.title});
  }

  /* -------------------------------------------- */

  /** @inheritDoc */
  _onChangeForm(formConfig, event) {
    super._onChangeForm(formConfig, event);
    if ( event.target.name === "title" ) {
      const slug = event.target.value.slugify({strict: true}) || "world-name";
      this.form.elements.id.placeholder = slug;
    }
  }

  /* -------------------------------------------- */

  /** @override */
  async _prepareContext(options={}) {
    const ac = CONST.PACKAGE_AVAILABILITY_CODES;
    const {world} = this;
    const themes = Object.entries(CONST.WORLD_JOIN_THEMES).reduce((themes, [key, label]) => {
      themes[key] = _loc(label);
      return themes;
    }, {});
    const context = {
      source: world._source,
      fields: world.schema.fields,
      rootId: this.id,
      themes,
      worldId: world._source.id,
      worldTitle: world._source.title,
      worldKbUrl: WorldConfig.#WORLD_KB_URL,
      nextSession: this.#formatNextSession(),
      inWorld: !!game.world,
      buttons: [{
        type: "submit",
        cssClass: "submit",
        icon: "fa-solid fa-floppy-disk",
        label: "WORLD.SubmitUpdate"
      }]
    };
    context.showEditFields = !context.inWorld;
    if ( game.systems ) {
      context.systems = game.systems
        .filter(system => {
          if ( world.system === system.id ) return true;
          return ( system.availability <= ac.UNVERIFIED_GENERATION );
        })
        .sort((a, b) => a.title.localeCompare(b.title, game.i18n.lang))
        .reduce((systems, system) => {
          systems[system.id] = system.title;
          return systems;
        }, {});
    }
    else {
      context.systems = {[game.system.id]: game.system.title};
    }
    return context;
  }

  /* -------------------------------------------- */

  /**
   * Format the nextSession isoString to a datetime-local input string
   * @returns {string}
   */
  #formatNextSession() {
    const dt = new Date(this.world._source.nextSession ?? "");
    return dt.isValid() ? dt.toLocaleString("sv-SE") : ""; // Same as ISO 8601 (RFC 3339 profile) sans time zone
  }

  /* -------------------------------------------- */
  /*  Form Submission                             */
  /* -------------------------------------------- */

  /**
   * Handle editing an existing world.
   * @param {object} worldData  The updated world data.
   * @returns {Promise<void>}
   */
  async #onEditWorld(worldData) {
    try {
      worldData.action = "editWorld";
      const response = await this.#submitWorldData(worldData);
      this.world.updateSource(response);
    } catch {}
  }

  /* -------------------------------------------- */

  /**
   * Handle form submission.
   * @this {WorldConfig}
   * @type {ApplicationFormSubmission}
   */
  static async #onSubmit(event, form, formData) {
    const submitData = foundry.utils.expandObject(formData.object);

    // Augment submission actions
    submitData.id = this.world.id;
    if ( !submitData.resetKeys ) delete submitData.resetKeys;
    if ( !submitData.safeMode ) delete submitData.safeMode;

    // Handle next session schedule fields
    const date = new Date(submitData.nextSession);
    submitData.nextSession = date.isValid() ? date.toISOString() : null;

    if ( submitData.joinTheme === CONST.WORLD_JOIN_THEMES.default ) delete submitData.joinTheme;
    return this.#processSubmitData(submitData);
  }

  /* -------------------------------------------- */

  /**
   * Create or update the World.
   * @param {object} submitData
   */
  async #processSubmitData(submitData) {
    const submit = this.form.querySelector("button.submit i");
    submit.parentElement.disabled = true;
    submit.classList.remove("fa-floppy-disk");
    submit.classList.add("fa-spinner", "fa-spin-pulse");

    // Validate the submission data
    try {
      this.world.validate({changes: submitData, clean: true});
    } catch(error) {
      ui.notifications.error(error.message.replace("\n", ". "));
      submit.parentElement.disabled = false;
      submit.classList.remove("fa-spinner", "fa-spin-pulse");
      submit.classList.add("fa-floppy-disk");
      throw error;
    }

    await this.#onEditWorld(submitData);

    submit.parentElement.disabled = false;
    submit.classList.remove("fa-spinner", "fa-spin-pulse");
    submit.classList.add("fa-floppy-disk");
    if ( game.view === "setup" ) ui.setupPackages.render();
  }

  /* -------------------------------------------- */

  /**
   * Submit world data to the /setup endpoint.
   * @param {object} worldData  The data to submit.
   * @returns {Promise<object>}
   * @throws {Error}            If the request was unsuccessful.
   */
  async #submitWorldData(worldData) {
    let response;
    try {
      response = await foundry.utils.fetchJsonWithTimeout(foundry.utils.getRoute("setup"), {
        method: "POST",
        headers: {"Content-Type": "application/json"},
        body: JSON.stringify(worldData)
      });
      if ( response.error ) throw response.error;
    } catch(e) {
      ui.notifications.error(e);
    }
    return response;
  }
}
