import MouseInteractionManager from "../../interaction/mouse-handler.mjs";
import PreciseText from "./precise-text.mjs";
import {RenderFlagsMixin} from "@client/canvas/interaction/render-flags.mjs";
import {getTexture, loadTexture} from "../../loader.mjs";

/**
 * A generic helper for drawing a standard Control Icon.
 */
export default class ControlIcon extends RenderFlagsMixin(PIXI.Container) {
  /**
   * @param {object} [options]
   * @param {PIXI.Texture|string} [options.texture]   The (URL of the) icon texture
   * @param {number} [options.size]                   The size of the icon
   * @param {PIXI.ColorSource} [options.tint]         The icon tint
   * @param {PIXI.ColorSource} [options.borderColor]  The border color
   * @param {number} [options.elevation]              The elevation
   */
  constructor({texture=PIXI.Texture.EMPTY, size=40, borderColor=CONFIG.Canvas.dispositionColors.CONTROLLED,
    tint=0xFFFFFF, elevation=0}={}) {
    if ( tint === null ) {
      foundry.utils.logCompatibilityWarning("Passing null for tint to the ControlIcon constructor is deprecated. Pass 0xFFFFFF or undefined instead.",
        {since: 14, until: 16, once: true});
      tint = 0xFFFFFF;
    }
    super();
    this.#texture = texture;
    this.#loadedTexture = typeof texture === "string" ? getTexture(texture) : texture;
    this.#size = size;
    this.#elevation = elevation;
    this.bg = this.addChild(new PIXI.Graphics());
    this.bg.eventMode = "none";
    this.bg.tint = 0x000000;
    this.bg.alpha = 0.4;
    this.icon = this.addChild(new PIXI.Sprite());
    this.icon.tint = tint;
    this.icon.eventMode = "none";
    this.border = this.addChild(new PIXI.Graphics());
    this.border.eventMode = "none";
    this.border.tint = borderColor;
    this.tooltip = this.addChild(new PreciseText("", CONFIG.canvasTextStyle));
    this.tooltip.eventMode = "none";
    this.hitArea = new PIXI.Rectangle(0, 0, 0, 0);
    this.interactiveChildren = false;
    this.eventMode = "static";
    this.cursor = "pointer";
  }

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

  /** @override */
  static RENDER_FLAG_PRIORITY = "INTERFACE";

  /** @override */
  static RENDER_FLAGS = {
    redraw: {propagate: ["refresh"]},
    refresh: {}
  };

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

  /**
   * The (URL of the) icon texture used by this control icon.
   * @type {PIXI.Texture|string}
   */
  get texture() {
    return this.#texture;
  }

  set texture(value) {
    if ( value === this.#texture ) return;
    this.#texture = value;
    this.#loadedTexture = typeof value === "string" ? getTexture(value) : value;
    this.renderFlags.set({redraw: !this.#loadedTexture, refresh: true});
  }

  #texture;

  /** @type {PIXI.Texture|null} */
  #loadedTexture;

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

  /**
   * The size of the control icon.
   * @type {number}
   */
  get size() {
    return this.#size;
  }

  set size(value) {
    if ( value === this.#size ) return;
    this.#size = value;
    this.refresh();
  }

  #size;

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

  /**
   * The elevation of the control icon, which is displayed in its tooltip text.
   * @type {number}
   */
  get elevation() {
    return this.#elevation;
  }

  set elevation(value) {
    if ( (typeof value !== "number") || !Number.isFinite(value) ) {
      throw new Error("ControlIcon#elevation must be a finite numeric value.");
    }
    if ( value === this.#elevation ) return;
    this.#elevation = value;
    this.refresh();
  }

  #elevation = 0;

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

  /**
   * The background of this control icon.
   * @type {PIXI.Graphics}
   */
  bg;

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

  /**
   * The border of this control icon.
   * @type {PIXI.Graphics}
   */
  border;

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

  /**
   * The icon of this control icon.
   * @type {PIXI.Sprite}
   */
  icon;

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

  /**
   * The tooltip of this control icon.
   * @type {PreciseText}
   */
  tooltip;

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

  /**
   * An internal reference to a Promise in-progress to draw this control icon.
   * @type {Promise<this>}
   */
  #drawing = Promise.resolve(this);

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

  /**
   * Has this control icons been drawn and is there no drawing in progress?
   * @type {boolean}
   */
  #drawn = false;

  /* -------------------------------------------- */
  /*  Incremental Refresh                         */
  /* -------------------------------------------- */

  /** @override */
  applyRenderFlags() {
    if ( !this.renderFlags.size || this._destroyed ) return;
    const flags = this.renderFlags.clear();

    // Full re-draw
    if ( flags.redraw ) {
      this.draw();
      return;
    }

    // Don't refresh until this control icon are drawn
    if ( !this.#drawn ) return;

    if ( flags.refresh ) this._refresh();
  }

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

  /**
   * Draw the visualization of this control icon.
   * @returns {Promise<this>}
   */
  async draw() {
    return this.#drawing = this.#drawing.finally(async () => {
      this.#drawn = false;
      this._clear();
      await this._draw();
      this.refresh();
      this.#drawn = true;
    });
  }

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

  /**
   * Draw this control icon.
   * @returns {Promise<void>}
   * @protected
   */
  async _draw() {
    this.#loadedTexture = typeof this.#texture === "string" ? await loadTexture(this.#texture) : this.#texture;
  }

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

  /**
   * Clear this control icon.
   * @protected
   */
  _clear() {
    this.bg.clear();
    this.icon.texture = PIXI.Texture.EMPTY;
    this.icon.width = this.icon.height = 0;
    this.border.clear();
    this.tooltip.text = "";
    this.hitArea.width = this.hitArea.height = 0;
    MouseInteractionManager.emulateMoveEvent();
  }

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

  /**
   * Refresh the visualization of this control icon.
   * @protected
   */
  _refresh() {
    const s = canvas.dimensions.uiScale;
    const thickness = 2 * s;
    const radius = 5 * s;
    const rect = new PIXI.RoundedRectangle(0, 0, this.size, this.size, radius);

    // Pivot
    this.pivot.set(this.size / 2, this.size / 2);

    // Background
    this.bg.clear();
    this.bg.beginFill(0xFFFFFF);
    this.bg.drawShape(rect);
    this.bg.endFill();

    // Icon
    this.icon.texture = this.#loadedTexture;
    this.icon.width = this.icon.height = this.size;

    // Border
    this.border.clear();
    this.border.lineStyle({width: thickness, color: 0x000000, alignment: 0.75});
    this.border.drawShape(rect);
    this.border.lineStyle({width: thickness / 2, color: 0xFFFFFF, alignment: 1});
    this.border.drawShape(rect);

    // Tooltip
    const elevation = this.elevation.toNearest(0.01);
    this.tooltip.text = `${elevation.signedString()} ${canvas.grid.units}`.trim();
    this.tooltip.visible = elevation !== 0;
    this.tooltip.position.set(this.size / 2, -12 * s);
    this.tooltip.anchor.set(0.5, 1);
    this.tooltip.scale.set(s, s);

    // Hit area
    this.hitArea.width = this.hitArea.height = this.size;
    MouseInteractionManager.emulateMoveEvent();
  }

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

  /**
   * Refresh the visualization of this control icon.
   * @overload
   */
  /**
   * @overload
   * @param {object} options
   * @deprecated since v14
   * @ignore
   */
  refresh(options) {
    /** @deprecated since v14*/
    if ( options ) {
      foundry.utils.logCompatibilityWarning("ControlIcon#refresh(options) has been deprecated. "
        + "Set ControlIcon#visible, ControlIcon#icon#tint, and ControlIcon#border#tint instead.", {since: 14, until: 16, once: true});
      const {visible, iconColor, borderColor, borderVisible} = options;
      if ( iconColor !== undefined ) this.icon.tint = iconColor ?? 0xFFFFFF;
      if ( borderColor !== undefined ) this.border.tint = borderColor;
      if ( borderVisible !== undefined ) this.border.visible = borderVisible;
      if ( visible !== undefined && (this.visible !== visible) ) {
        this.visible = visible;
        MouseInteractionManager.emulateMoveEvent();
      }
      return this;
    }
    this.renderFlags.set({refresh: true});
  }

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

  /** @inheritDoc */
  destroy(options) {
    this.renderFlags.clear();
    MouseInteractionManager.emulateMoveEvent();
    super.destroy(options);
  }

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

  /**
   * @deprecated since v14
   * @ignore
   */
  get rect() {
    foundry.utils.logCompatibilityWarning("ControlIcon#rect has been deprecated in favor of ControlIcon#size.",
      {since: 14, until: 16, once: true});
    const s = canvas.dimensions.uiScale;
    return [-2 * s, -2 * s, this.size + (4 * s), this.size + (4 * s)];
  }

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

  /**
   * @deprecated since v14
   * @ignore
   */
  get tintColor() {
    foundry.utils.logCompatibilityWarning("ControlIcon#tintColor has been deprecated in favor of ControlIcon#icon.tint.",
      {since: 14, until: 16, once: true});
    return this.icon.tint;
  }

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

  /**
   * @deprecated since v14
   * @ignore
   */
  get borderColor() {
    foundry.utils.logCompatibilityWarning("ControlIcon#borderColor has been deprecated in favor of ControlIcon#border.tint.",
      {since: 14, until: 16, once: true});
    return this.border.tint;
  }

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

  /**
   * @deprecated since v14
   * @ignore
   */
  get iconSrc() {
    foundry.utils.logCompatibilityWarning("ControlIcon#iconSrc has been deprecated in favor of ControlIcon#texture.",
      {since: 14, until: 16, once: true});
    return this.texture;
  }

  set iconSrc(value) {
    foundry.utils.logCompatibilityWarning("ControlIcon#iconSrc has been deprecated in favor of ControlIcon#texture.",
      {since: 14, until: 16, once: true});
    this.texture = value;
  }
}
