import BaseFogExploration from "@common/documents/fog-exploration.mjs";
import ClientDocumentMixin from "./abstract/client-document.mjs";

/**
 * The client-side FogExploration document which extends the common BaseFogExploration model.
 * @extends BaseFogExploration
 * @mixes ClientDocumentMixin
 * @category Documents
 *
 * @see {@link foundry.documents.collections.FogExplorations}: The world-level collection of
 *   FogExploration documents
 */
export default class FogExploration extends ClientDocumentMixin(BaseFogExploration) {
  /**
   * Obtain the fog of war exploration progress for a specific Scene, Level, and User.
   * @param {object} [query]
   * @param {string|Scene} [query.scene]
   * @param {string|User} [query.user]
   * @param {object} [options={}]
   * @returns {Promise<FogExploration|null>}
   */
  static async load({scene, user}={}, options={}) {
    const sceneId = (typeof scene === "string") ? scene : (scene?.id ?? canvas.scene?.id ?? null);
    const sceneDoc = (typeof scene === "string") ? game.scenes.get(scene) : (scene ?? canvas.scene);
    user ??= game.user;
    if ( !sceneId || !sceneDoc || !user ) return null;

    if ( !(game.user.isGM || user.isSelf) ) {
      throw new Error("You do not have permission to access the FogExploration object of another user");
    }

    const level = sceneDoc._view;
    const userId = user.id;
    const collection = game.collections.get("FogExploration");

    // Union is only required in SHARED mode
    const requireUnion = user.isSelf && (sceneDoc.fog.mode === CONST.FOG_EXPLORATION_MODES.SHARED);

    const matchesTriple = d => {
      const dUserId = d?.user?.id ?? d?.user;
      const dSceneId = d?.scene?.id ?? d?.scene;
      return (dUserId === userId) && (dSceneId === sceneId) && (d.level === level);
    };

    // Return cached exploration only when we don't need shared union docs
    if ( user.isSelf && !requireUnion ) {
      const cached = collection.find(matchesTriple);
      if ( cached ) return cached;
    }

    // Request exploration documents (server may return multiple docs when sharedFog is enabled)
    const query = {scene: sceneId, level, user: userId};
    const docs = await this.database.get(this, {query, ...options});

    // Cache returned documents for self loads
    if ( user.isSelf ) {

      // Replace cached docs for this scene+level when unionization is requested
      if ( requireUnion ) {
        for ( const doc of collection.filter(d => {
          const dSceneId = d?.scene?.id ?? d?.scene;
          return (dSceneId === sceneId) && (d.level === level);
        }) ) {
          collection.delete(doc.id);
        }
      }
      for ( const doc of docs ) {
        if ( doc?.id ) collection.set(doc.id, doc);
      }
    }

    // Return the requested user doc and prefer newest if duplicates exist
    const candidates = docs.filter(matchesTriple);
    if ( !candidates.length ) return null;

    let exploration = candidates[0];
    for ( const doc of candidates ) {
      if ( (doc.timestamp ?? 0) > (exploration.timestamp ?? 0) ) exploration = doc;
    }

    if ( exploration?.id && user.isSelf ) collection.set(exploration.id, exploration);
    return exploration;
  }

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

  /**
   * Transform the explored base64 data into a PIXI.Texture object
   * @returns {PIXI.Texture|null}
   */
  getTexture() {
    if ( !this.explored ) return null;
    const bt = new PIXI.BaseTexture(this.explored, {alphaMode: PIXI.ALPHA_MODES.NPM});
    return new PIXI.Texture(bt);
  }

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

  /** @inheritDoc */
  _onCreate(data, options, userId) {
    super._onCreate(data, options, userId);
    if ( options.loadFog === false ) return;

    const dUserId = this.user?.id ?? this.user;
    const dSceneId = this.scene?.id ?? this.scene;

    if ( (dUserId === game.user.id)
      && (dSceneId === canvas.scene?.id)
      && (this.level === canvas.scene?._view) ) {
      canvas.fog.load();
    }
  }

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

  /** @inheritDoc */
  _onUpdate(changed, options, userId) {
    super._onUpdate(changed, options, userId);
    if ( options.loadFog === false ) return;

    const dUserId = this.user?.id ?? this.user;
    const dSceneId = this.scene?.id ?? this.scene;

    if ( (dUserId === game.user.id)
      && (dSceneId === canvas.scene?.id)
      && (this.level === canvas.scene?._view) ) {
      canvas.fog.load();
    }
  }

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

  /** @inheritDoc */
  _onDelete(options, userId) {
    super._onDelete(options, userId);
    if ( options.loadFog === false ) return;

    const dUserId = this.user?.id ?? this.user;
    const dSceneId = this.scene?.id ?? this.scene;

    if ( (dUserId === game.user.id)
      && (dSceneId === canvas.scene?.id)
      && (this.level === canvas.scene?._view) ) {
      canvas.fog.load();
    }
  }
}
