import Cog from "@robotical/webapp-types/dist-types/src/application/RAFTs/Cog/Cog";
import { getConnectedCog } from "../../utils/CogManager";
import PlayScene from "../scenes/PlayScene";
import { EventBus } from "../EventBus";
import { safeAddSound } from "../../utils/safe-add-sound";

type TiltInput = number | "left" | "right" | "none";
type TiltDirection = "left" | "right" | "none";

export class Player extends Phaser.Physics.Arcade.Sprite {
  scene: PlayScene;
  hitSound: Phaser.Sound.HTML5AudioSound | undefined;
  canGoRight: boolean;
  lastInputDirection: TiltDirection = "none";

  constructor(scene: PlayScene) {
    const x = 1000;
    const y = 1000;
    super(scene, x, y, "marty-ski-straight");
    this.scene = scene;
    this.canGoRight = true;

    scene.add.existing(this);
    scene.physics.add.existing(this);

    this.init();
    this.scene.events.on(Phaser.Scenes.Events.UPDATE, this.update, this);
    this.once(Phaser.GameObjects.Events.DESTROY, this.cleanup, this);
  }

  init() {
    this
      .setOrigin(0.5, 1)
      .setCollideWorldBounds(true, undefined, undefined, true)
      .setBodySize(90, 180)
      .setOffset(35, 20)
      .setDepth(4)
      .setVisible(false);

    this.registerSounds();

    this.scene.scale.on('resize', this.scaleToFit, this);
    this.scene.scale.on('resize', this.positionOnMountain, this);
  }

  positionOnMountain() {
    this.setY(600 * (this.scene.mountain?.scaleFactors.height || 1));
    this.setX(this.scene.scale.width / 2);
    this.setVisible(true);
  }

  scaleToFit() {
    const scaleFactor = this.scene.mountain?.scaleFactors.height || 1;
    const width = 180 * .8;
    const height = 285 * .8;

    this.setDisplaySize(width * scaleFactor, height * scaleFactor);
  }

  getStartTrigger(): TiltInput {
    const connectedCog = getConnectedCog();
    if (connectedCog) {
      const data = (connectedCog as Cog).raftStateInfo.accelerometer;
      return data.ax as number;
    }

    if (!this.scene.cursors) return "none";
    const { left, right } = this.scene.cursors;
    if (left.isDown) {
      return "left";
    }
    if (right.isDown) {
      return "right";
    }
    return "none";
  }

  actionDecider(tiltInput: TiltInput, inputSource: "cog" | "fallback-keyboard" | "fallback-button" = typeof tiltInput === "number" ? "cog" : "fallback-keyboard") {
    const tiltDirection = getTiltDirection(tiltInput);
    this.emitInputEvent(tiltDirection, inputSource);

    if (tiltDirection === "none") return "none";
    const selectedBlock = tiltDirection === "left"
      ? this.scene.selectedCogTiltLeftBlock
      : this.scene.selectedCogTiltRightBlock;

    if (!selectedBlock || selectedBlock.block === "dontmove") return "none";

    let actualMove: "left" | "right" | "none" = "none";
    const moveInput = getMoveInput(tiltInput, tiltDirection);

    if (selectedBlock.block === "moveleft") {
      this.moveLeft(moveInput);
      actualMove = "left";
    } else if (selectedBlock.block === "moveright") {
      this.moveRight(moveInput);
      actualMove = "right";
    }

    if (actualMove !== "none") {
      EventBus.emit('action-executed', { block: selectedBlock.block, trigger: tiltDirection });
    }

    return actualMove;
  }

  emitInputEvent(tiltDirection: TiltDirection, source: "cog" | "fallback-keyboard" | "fallback-button") {
    if (tiltDirection !== "none" && this.lastInputDirection === "none") {
      EventBus.emit('input-received', { source, direction: tiltDirection });
    }
    this.lastInputDirection = tiltDirection;
  }

  async moveLeft(tiltValue: number | string) {
    if (!this.scene?.sys || !this.active) return;
    if (typeof tiltValue === "string") {
      const move = 10;
      for (let i = 0; i < move; i++) {
        if (!this.scene?.sys || !this.active) return;
        this.setX(this.x - .3);
        await new Promise((resolve) => setTimeout(resolve, 10));
      }
    } else {
      this.setX(this.x - (tiltValue * 4));
      await new Promise((resolve) => setTimeout(resolve, 10));
    }
  }

  async moveRight(tiltValue: number | string) {
    if (!this.scene?.sys || !this.active || !this.canGoRight) return;
    if (typeof tiltValue === "string") {
      const move = 10;
      for (let i = 0; i < move; i++) {
        if (!this.scene?.sys || !this.active || !this.canGoRight) return;
        this.setX(this.x + .3);
        await new Promise((resolve) => setTimeout(resolve, 10));
      }
    } else {
      this.setX(this.x + (tiltValue * 4));
      await new Promise((resolve) => setTimeout(resolve, 10));
    }
  }

  update() {
    if (!this.scene?.sys || !this.body) return;
    const tiltDirection = this.getStartTrigger();

    if (!this.scene.isGameRunning) {
      this.anims.pause();
      this.emitInputEvent("none", "fallback-keyboard");
      return;
    }

    const actualMove = this.actionDecider(tiltDirection);

    if (actualMove === "right") {
      this.setBodySize(90, 30);
      this.setOffset(80, 250);
      this.setTextureIfNeeded("marty-ski-right");
    } else if (actualMove === "left") {
      this.setBodySize(90, 30);
      this.setOffset(20, 250);
      this.setTextureIfNeeded("marty-ski-left");
    } else {
      this.setTextureIfNeeded("marty-ski-straight");
      this.setBodySize(90, 30);
      this.setOffset(35, 180);
    }
  }

  setTextureIfNeeded(textureKey: string) {
    if (this.texture.key !== textureKey) {
      this.setTexture(textureKey);
    }
  }

  registerSounds() {
    this.hitSound = safeAddSound(this.scene, "hit", { volume: 0.4 }) as Phaser.Sound.HTML5AudioSound | undefined;
  }

  die() {
    this.anims.pause();
    this.setTextureIfNeeded("marty-ski-hurt");
    this.hitSound?.play();
  }

  cleanup() {
    this.scene?.events.off(Phaser.Scenes.Events.UPDATE, this.update, this);
    this.scene?.scale.off('resize', this.scaleToFit, this);
    this.scene?.scale.off('resize', this.positionOnMountain, this);
  }
}

function getTiltDirection(tiltInput: TiltInput): TiltDirection {
  if (tiltInput === "left" || tiltInput === "right") {
    return tiltInput;
  }

  if (typeof tiltInput === "number") {
    if (tiltInput < -0.1) return "left";
    if (tiltInput > 0.1) return "right";
  }

  return "none";
}

function getMoveInput(tiltInput: TiltInput, direction: "left" | "right") {
  if (typeof tiltInput === "number") {
    return Math.abs(tiltInput);
  }

  return direction;
}
