import {Assets, Container, Polygon, Rectangle, RenderTexture, Sprite, Text} from "pixi.js";
import {Spine} from "pixi-spine";
import Locator from "../../services/Locator";
import { tryToApplyFallbackFontFamily } from "../../utils/utils";

/**
 * Represents a Button that extends the Container class.
 * @event up
 * @event down
 * @event upoutside
 */
export default class Button extends Sprite {
  #text = "text";
  #skin = "default";
  #spine;
  #textStyle = {
    align: "center"
  };
  #textAttachment = null;

  constructor(skin, text, resolution) {
    super();
    this.resolution = resolution;
    this.createSpine();
    this.setupListeners();
    this.skin = skin || this.#skin;
    this.text = text || "";
  }

  set skin(skinName) {
    const skin = this.#spine.spineData.findSkin(skinName);
    if (!skin) return;
    this.#spine.skeleton.setSkin(null);
    this.#spine.skeleton.setSlotsToSetupPose();
    this.#spine.skeleton.setSkinByName(skinName);
    this.#skin = skinName;
    this.#spine.update(0);
    this.resizeTexture();
    this.text = this.#text;
    this.createHitArea();
  }

  get skin() {
    return this.#skin;
  }

  set text(text) {
    const placeholder = this.#spine.skeleton.findSlot("text");
    if (!placeholder) return;
    if (!placeholder.attachment) return;
    this.#text = text;
    this.updateTextTexture();
  }

  set textStyle(textStyle) {
    this.#textStyle = textStyle;
    if (!this.#textAttachment) return;
    this.#textAttachment.text.style = textStyle;
    this.updateTextTexture();
  }

  update(){
    this.#spine.update(0);
  }

  createSpine() {
    this.#spine = new Spine(Assets.get("buttons").spineData);
    this.#spine.eventMode = "static";
    this.#spine.cursor = "pointer";
    this.addChild(this.#spine);

    this.#spine.state.addListener({
      event: (entry, event) => {
        this.emit(event.data.name, event);
      }
    });
  }

  createHitArea() {
    const slot = this.#spine.skeleton.findSlot("bbx");
    if (!slot) {
      this.hitArea = null;
      return;
    }

    if (!slot.attachment) {
      this.hitArea = null;
      return;
    }

    if (!slot.attachment.vertices) {
      this.hitArea = null;
      return;
    }

    this.hitArea = new Polygon(slot.attachment.vertices);
  }

  setupListeners() {
    this.#spine.on("pointerdown", () => {
      this.isPressed = true;
      this.#spine.state.setAnimation(0, "down");
    });
    this.#spine.on("pointerup", () => {
      if (!this.isPressed) {
        this.emit("pointerupoutside");
        return;
      }
      this.isPressed = false;
      this.#spine.state.setAnimation(0, "up");
    });
    this.#spine.on("pointerupoutside", () => {
      this.isPressed = false;
      this.#spine.state.setAnimation(0, "upoutside");
    });
    this.#spine.on("mouseupoutside", () => {
      this.isPressed = false;
      this.#spine.state.setAnimation(0, "upoutside");
    });
    this.#spine.on("pointerover", () => {
      if (this.isSelected) return;
      this.#spine.state.setAnimation(1, "hover");
    });
    this.#spine.on("pointerout", () => {
      if (this.isSelected) return;
      this.#spine.state.setAnimation(1, "out");
    });
  }

  get textAttachment() {
    if (!this.#textAttachment) {
      this.#textAttachment = {
        text: new Text(this.#text, this.#textStyle),
        region: new Rectangle(0, 0, this.#spine.width, this.#spine.height),
        texture: RenderTexture.create({
          width: this.#spine.width,
          height: this.#spine.height,
          resolution: this.resolution,
        })
      };
      tryToApplyFallbackFontFamily(this.#textAttachment.text);
      this.#textAttachment.text.resolution = this.resolution;
      this.#textAttachment.text.anchor.set(0.5);
    }
    return this.#textAttachment;
  }

  updateTextTexture() {
    const {text, texture, region} = this.textAttachment;
    text.text = this.#text;
    text.scale.set(1);
    this.scaleToFit(text, region.width);
    Locator.renderer.render(text, {renderTexture: texture});
    this.#spine.hackTextureBySlotName("text", texture, region);
  }

  resizeTexture() {
    const placeholder = this.#spine.skeleton.findSlot("text");
    if (!placeholder) return;
    if (!placeholder.attachment) return;
    const {width, height} = this.#spine;
    this.textAttachment.region = new Rectangle(0, 0, width, height);
    this.textAttachment.texture.resize(width, height, true);
    this.textAttachment.text.x = width * 0.5;
    this.textAttachment.text.y = height * 0.5;
    this.updateTextTexture();
  }

  /**
   * Scales the given text element to fit within a maximum width.
   * @private
   * @param {Object} textElement - The text element to scale.
   * @param {number} maxWidth - The maximum width to fit the text element within.
   */
  scaleToFit(textElement, maxWidth) {
    const factor = Math.min(1, maxWidth / textElement.width);
    textElement.scale.set(factor);
  }

  disable() {
    this.isPressed = false;
    this.#spine.state.clearTracks();
    this.#spine.skeleton.setToSetupPose();
    this.#spine.state.setAnimation(1, "disable");
    this.#spine.eventMode = "none";
  }

  enable() {
    this.#spine.state.clearTracks();
    this.#spine.skeleton.setToSetupPose();
    if (this.isSelected) {
      this.select();
    }
    this.#spine.eventMode = "static";
  }

  select() {
    this.isSelected = true;
    this.#spine.state.setAnimation(1, "selected");
  }

  unselect() {
    this.isSelected = false;
    this.#spine.eventMode = "static";
    this.#spine.state.clearTracks();
    this.#spine.skeleton.setToSetupPose();
  }

  destroy(_options) {
    this.#spine.destroy();
    if (this.#textAttachment) {
      this.#textAttachment.text.destroy();
      this.#textAttachment.texture.destroy(true);
      this.#textAttachment = null;
    }
    super.destroy(_options);
  }
}
