
import {DocumentSheetV2, HandlebarsApplicationMixin} from "../api/_module.mjs";
import FormDataExtended from "../ux/form-data-extended.mjs";

/**
 * @import Document from "@common/abstract/document.mjs"
 */

/**
 * The Application responsible for configuring a Placeable document within a parent Scene.
 * @extends DocumentSheetV2
 * @mixes HandlebarsApplication
 */
export default class PlaceableConfig extends HandlebarsApplicationMixin(DocumentSheetV2) {
  /** @inheritDoc */
  static DEFAULT_OPTIONS = {
    preview: true
  };

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

  /**
   * The preview of this config.
   * @type {Document|null}
   * @protected
   */
  _preview = null;

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

  /**
   * Was the object controlled when the config was rendered?
   * If so, the controlled state of the object is restored when the config is closed.
   * @type {boolean|null}
   */
  #wasControlled = null;

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

  /** @inheritDoc */
  async _prepareContext(options) {
    if ( options.isFirstRender ) await this._initializePreview();
    const context = await super._prepareContext(options);
    const document = this._preview;
    const scene = document.parent;
    return Object.assign(context, {
      document,
      model: document,
      source: document._source,
      gridUnits: scene?.grid.units || _loc("MEASUREMENT.GridUnits"),
      selectableLevels: scene?.levels.reduce((levels, id) => {
        const level = scene.levels.get(id);
        if ( level ) levels.push(level);
        return levels;
      }, Array.from(scene.availableLevels).reverse()).map(l => ({ value: l.id, label: l.name })) ?? [],
      inputs: {
        createMultiSelectInput: (field, config) => foundry.applications.fields.createMultiSelectInput(config)
      }
    });
  }

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

  /** @inheritDoc */
  async _postRender(context, options) {
    await super._postRender(context, options);

    // Update the preview to match the form data on re-render
    if ( !options.isFirstRender ) {
      const formData = new FormDataExtended(this.form);
      const submitData = this._prepareSubmitData(event, this.form, formData);
      this._previewChanges(submitData);
    }
  }

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

  /** @inheritDoc */
  _onChangeForm(formConfig, event) {
    super._onChangeForm(formConfig, event);
    if ( !this.options.preview ) return;
    const formData = new FormDataExtended(this.form);
    const submitData = this._prepareSubmitData(event, this.form, formData);
    this._previewChanges(submitData);
  }

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

  /** @inheritDoc */
  async _preClose(options) {
    await super._preClose(options);
    this._resetPreview();
  }

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

  /**
   * Initialize the preview.
   * @returns {Promise<void>}
   * @protected
   */
  async _initializePreview() {
    if ( this.document.rendered && this.document.object.isPreview ) {
      this._preview = this.document;
    }
    else this._preview = await this._createPreview(this._preview?.toObject());
  }

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

  /**
   * Create the preview.
   * @param {object} [data={}]  Additional data which overrides current document data at the time of creation
   * @returns {Promise<Document>}
   * @protected
   */
  async _createPreview(data={}) {
    let preview;
    if ( !this.options.preview || !this.document.rendered ) {
      preview = this.document.clone(data);
      preview._destroyed = true;
    } else {
      const object = this.document.object;
      this.#wasControlled ??= object.controlled; // Keep original state on re-initialization
      const clone = object.clone();
      clone._previewType = "config";
      preview = clone.document;
      preview.updateSource(data);
      await preview.object.draw();
      preview.object.layer._configPreview.addChild(preview.object);
      object.renderFlags.set({refreshState: true});
      preview.object.layer.releaseAll();
    }
    return preview;
  }

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

  /**
   * Destroy the preview.
   * @protected
   */
  _destroyPreview() {
    if ( !this._preview.rendered ) return;
    this._preview.object.destroy({children: true});
    this._preview._original?.renderFlags.set({refreshState: true});
  }

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

  /**
   * Preview changes.
   * @param {object} changes  The changes to preview.
   * @protected
   */
  _previewChanges(changes) {
    this._preview.updateSource(changes);
    if ( this._preview.rendered ) this._preview.object.renderFlags.set({refresh: true});
  }

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

  /**
   * Reset the preview.
   * @protected
   */
  _resetPreview() {
    this._destroyPreview();
    this._preview = null;
    if ( this.options.preview && this.document.rendered && !this.document.object.isPreview ) {
      const object = this.document.object;
      object.renderFlags.set({refreshState: true});
      if ( this.#wasControlled && !object.layer.controlled.length ) {
        object.control({force: true, releaseOthers: false});
      }
    }
    this.#wasControlled = null;
  }
}
