import Edge from "./edge.mjs";
import Quadtree from "../quadtree.mjs";

/**
 * A specialized Map class that manages all edges used to restrict perception in a Scene.
 * Integrates with a Quadtree for efficient spatial queries.
 * @extends {Map<string, Edge>}
 */
export default class CanvasEdges extends Map {
  constructor(level) {
    super();
    if ( !(level instanceof foundry.documents.Level) ) {
      throw new Error("The CanvasEdges may only be constructed with a Level instance.");
    }
    this.#level = level;
    this.#quadtree = new Quadtree(new PIXI.Rectangle(), {
      maxObjects: CanvasEdges.#QUADTREE_MAX_OBJECTS,
      maxDepth: CanvasEdges.#QUADTREE_MAX_DEPTH
    });
  }

  /**
   * Maximum number of objects per node in the Quadtree.
   * @type {number}
   */
  static #QUADTREE_MAX_OBJECTS = 100;

  /**
   * Maximum depth of the Quadtree.
   * @type {number}
   */
  static #QUADTREE_MAX_DEPTH = 6;

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

  /**
   * The Level these edges belong to.
   * @type {Level}
   */
  get level() {
    return this.#level;
  }

  #level;

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

  /**
   * Edges representing the outer boundaries of the Scene.
   * @type {Set<Edge>}
   */
  #outerBounds = new Set();

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

  /**
   * Edges representing the inner boundaries of the Scene.
   * @type {Set<Edge>}
   */
  #innerBounds = new Set();

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

  /**
   * Internal Quadtree instance for spatial indexing of edges.
   * @type {Quadtree}
   */
  #quadtree;

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

  /**
   * Do edge intersections need to be re-computed?
   * @type {boolean}
   */
  #dirty = false;

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

  /** @inheritDoc */
  set(id, edge) {
    if ( !this.level.parent.initializedEdges ) throw new Error(`The edges of ${this.level.parent.uuid} haven't been initialized yet.`);

    // Delete the old edge first to remove it from the quadtree
    this.delete(id);
    super.set(id, edge);
    if ( edge.type === "outerBounds" ) this.#outerBounds.add(edge);
    else if ( edge.type === "innerBounds" ) this.#innerBounds.add(edge);
    else {
      this.#quadtree.bounds = this.#level.parent.dimensions.rect;
      this.#quadtree.insert({t: edge, r: edge.bounds});
    }
    this.#dirty = true;
    return this;
  }

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

  /** @inheritDoc */
  delete(id) {
    const edge = this.get(id);
    if ( !edge ) return false;
    if ( edge.type === "outerBounds" ) this.#outerBounds.delete(edge);
    else if ( edge.type === "innerBounds" ) this.#innerBounds.delete(edge);
    else this.#quadtree.remove(edge);
    this.#dirty = true;
    return super.delete(id);
  }

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

  /** @inheritDoc */
  clear() {
    super.clear();
    this.#quadtree.clear();
    this.#outerBounds.clear();
    this.#innerBounds.clear();
    this.#dirty = false;
    return this;
  }


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

  /**
   * Retrieves edges that overlap with a given rectangle.
   * Utilizes the Quadtree for efficient spatial querying.
   * This function computes edge intersections if necessary.
   * @param {PIXI.Rectangle} rect The rectangle to query against.
   * @param {object} options
   * @param {boolean} [options.includeInnerBounds=false] Should inner bounds be added?
   * @param {boolean} [options.includeOuterBounds=true] Should outer bounds be added?
   * @param {(edge: Edge) => boolean} [options.collisionTest] Collision function to test edge inclusion.
   * @param {boolean} [options.collisionTestBounds=false] Apply collision test to bounds?
   * @returns {Set<Edge>} A set of Edge instances that intersect with the provided rectangle.
   */
  getEdges(rect, {includeInnerBounds=false, includeOuterBounds=true, collisionTest, collisionTestBounds=false}={}) {
    this.#level.parent.initializeEdges();
    this.#quadtree.bounds = this.#level.parent.dimensions.rect;
    const edges = this.#quadtree.getObjects(rect, collisionTest ? {collisionTest: o => collisionTest(o.t)} : {});
    if ( !collisionTestBounds ) collisionTest = undefined;
    if ( includeInnerBounds ) {
      for ( const inner of this.#innerBounds ) {
        if ( !collisionTest || collisionTest(inner) ) edges.add(inner);
      }
    }
    if ( includeOuterBounds ) {
      for ( const outer of this.#outerBounds ) {
        if ( !collisionTest || collisionTest(outer) ) edges.add(outer);
      }
    }
    if ( edges.size ) this.identifyIntersections();
    return edges;
  }

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

  /**
   * Identify all edge intersections.
   */
  identifyIntersections() {
    if ( !this.#dirty ) return;
    Edge.identifyEdgeIntersections(this.values(), this.#level.id);
    this.#dirty = false;
  }

  /* -------------------------------------------- */
  /*  Deprecations and Compatibility              */
  /* -------------------------------------------- */

  /**
   * @deprecated since v14
   * @ignore
   */
  initialize() {
    foundry.utils.logCompatibilityWarning("CanvasEdges#inititalize has been deprecated. Use Scene#initializeEdges instead.",
      {since: 14, until: 16, once: true});
    this.level.parent.initializeEdges();
  }

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

  /**
   * @deprecated since v14
   * @ignore
   */
  refresh() {
    foundry.utils.logCompatibilityWarning("CanvasEdges#refresh has been deprecated. CanvasEdges#getEdges computes edge intersections automatically if necessary.",
      {since: 14, until: 16, once: true});
    Edge.identifyEdgeIntersections(this.values(), this.#level.id);
  }
}
