import { BAR_LABEL, DJ_LABEL, EXIT_LABEL, GIRL_LABEL, SISTER_LABEL, WINGMAN_LABEL, SHYGUY_LABEL } from "./constants";
import { nameToLabel } from "./story_engine.js";

const WINGMAN_SPEED = 5;
const SHYGUY_SPEED = 0.5;

const IS_DEBUG = true;

class SpriteEntity {
  constructor(x0, y0, imageSrc, speed = 0, width = 24, height = 64, frameRate = 8, frameCount = 1) {
    this.x = x0;
    this.y = y0;
    this.width = width;
    this.height = height;
    this.image = new Image();
    this.image.src = imageSrc;
    this.frameRate = frameRate;
    this.frameCount = frameCount;

    // properties for the game engine
    this.moving = false;
    this.speed = speed;

    // frame index in the sprite sheet
    this.frameX = 0;
    this.frameY = 0; // 0 for right, 1 for left
  }

  stop() {
    this.moving = false;
  }

  start() {
    this.moving = true;
  }

  setSpeed(speed) {
    this.speed = speed;
  }
}

class GuidedSpriteEntity extends SpriteEntity {
  constructor(x0, y0, imageSrc, speed = 0, width = 24, height = 64, frameRate = 8, frameCount = 1) {
    super(x0, y0, imageSrc, speed, width, height, frameRate, frameCount);
    this.target = null;
  }

  setTarget(target) {
    this.target = target;
  }
}

class SpriteImage {
  constructor(imageSrc, width = 32, height = 32) {
    this.image = new Image();
    this.image.src = imageSrc;
    this.width = width;
    this.height = height;
  }
}

class Target {
  constructor(label, x, y, width, height, color, enabled = true) {
    this.label = label;
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
    this.debugColor = color;
    this.enabled = enabled;
  }
}

export class GameEngine {
  static introMessages = [
    {
      message:
        "Hey man, this is really not my cup of tea. I see Jessica in the corner, I wonder if I can finally tell her I love her.",
      character: SHYGUY_LABEL,
    },
    {
      message: "Man, tonight is your night. I'll get you through it and you'll go home with Jessica.",
      character: WINGMAN_LABEL,
    },
    {
      message: "Geez, that's impossible! Even if I replay the night a million times, I couldn't do it.",
      character: SHYGUY_LABEL,
    },
    {
      message: "Okay, just follow my advice! I'll push you around if needed.",
      character: WINGMAN_LABEL,
    },
  ];

  constructor(shyguy, shyguyLLM, storyEngine, speechToTextClient, elevenLabsClient) {
    this.shyguy = shyguy;
    this.shyguyLLM = shyguyLLM;
    this.storyEngine = storyEngine;
    this.speechToTextClient = speechToTextClient;
    this.elevenLabsClient = elevenLabsClient;

    this.canvasWidth = 960;
    this.canvasHeight = 640;
    this.canvas = document.getElementById("gameCanvas");
    if (!this.canvas) {
      console.error("Canvas not found");
    }
    this.ctx = this.canvas.getContext("2d");

    // View management
    this.gameView = document.getElementById("gameView");
    this.dialogueView = document.getElementById("dialogueView");
    this.currentView = "game";

    this.shouldContinue = true;

    this.gameOver = false;
    this.gameSuccessful = false;

    this.gameChatContainer = document.getElementById("chatMessages");
    this.messageInput = document.getElementById("messageInput");
    this.sendButton = document.getElementById("sendButton");
    this.microphoneButton = document.getElementById("micButton");
    this.gameOverImage = document.getElementById("gameOverImage");
    this.gameOverText = document.getElementById("gameOverText");

    this.dialogueChatContainer = document.getElementById("dialogueMessages");
    this.dialogueContinueButton = document.getElementById("dialogueContinueButton");
    this.dialogueNextButton = document.getElementById("dialogueNextButton");

    this.gameFrame = 0;
    this.keys = {
      ArrowUp: false,
      ArrowDown: false,
      ArrowLeft: false,
      ArrowRight: false,
    };

    // Bind methods
    this.switchView = this.switchView.bind(this);
    this.update = this.update.bind(this);
    this.draw = this.draw.bind(this);
    this.run = this.run.bind(this);
    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleKeyUp = this.handleKeyUp.bind(this);
    this.setNewTarget = this.setNewTarget.bind(this);
    this.checkTargetReached = this.checkTargetReached.bind(this);
    this.updateGuidedSpriteDirection = this.updateGuidedSpriteDirection.bind(this);
    this.updateSprite = this.updateSprite.bind(this);
    this.handleSpriteCollision = this.handleSpriteCollision.bind(this);
    this.initDebugControls = this.initDebugControls.bind(this);
    this.stopShyguyAnimation = this.stopShyguyAnimation.bind(this);
    this.handlePlayAgain = this.handlePlayAgain.bind(this);
    this.handleMicrophone = this.handleMicrophone.bind(this);
    this.handleSendMessage = this.handleSendMessage.bind(this);
    this.handleMicrophone = this.handleMicrophone.bind(this);
    this.handleDialogueContinue = this.handleDialogueContinue.bind(this);
    this.handleFirstStartGame = this.handleFirstStartGame.bind(this);
    this.setGameOver = this.setGameOver.bind(this);
    this.handleDialogueNext = this.handleDialogueNext.bind(this);

    this.pushEnabled = false;
    this.voiceEnabled = !IS_DEBUG;

    // Debug controls
    this.initDebugControls();

    // if we have other obstacles, we can add them here
    this.gridMapTypes = {
      floor: 0,
      wall: 1,
      door: 2,
    };

    // load assets for drawing the scene
    this.wall = new SpriteImage("/assets/assets/wall_sprite.png");
    this.floor = new SpriteImage("/assets/assets/floor-tile.png");
    this.door = new SpriteImage("/assets/assets/door_sprite.png");

    this.gridCols = Math.ceil(this.canvasWidth / this.wall.width);
    this.gridRows = Math.ceil(this.canvasHeight / this.wall.height);

    // initialize grid map
    this.backgroundGridMap = [];
    this.initBackgroundGridMap();

    // initialize players
    const cx = this.canvasWidth / 2;
    const cy = this.canvasHeight / 2;
    this.shyguySprite = new GuidedSpriteEntity(cx, cy, "/assets/assets/shyguy_sprite.png", SHYGUY_SPEED);
    this.wingmanSprite = new SpriteEntity(
      this.wall.width,
      this.canvasHeight - this.wall.height - 64,
      "/assets/assets/wingman_sprite.png",
      WINGMAN_SPEED
    );

    this.jessicaSprite = new SpriteImage("/assets/assets/jessica_sprite.png", 64, 64);
    this.djSprite = new SpriteImage("/assets/assets/dj_sprite.png", 64, 64);
    this.barSprite = new SpriteImage("/assets/assets/bar_sprite.png", 64, 64);
    this.sisterSprite = new SpriteImage("/assets/assets/sister_sprite.png", 64, 64);

    this.targets = {
      exit: new Target(EXIT_LABEL, this.wall.width, this.wall.height, this.wall.width, this.wall.height, "red", true),
      girl: new Target(
        GIRL_LABEL,
        this.canvasWidth - this.wall.width - this.jessicaSprite.width,
        (this.canvasHeight - this.wall.height - this.jessicaSprite.height) / 2,
        this.jessicaSprite.width,
        this.jessicaSprite.height,
        "pink",
        true
      ),
      bar: new Target(
        BAR_LABEL,
        (this.canvasWidth - this.wall.width - this.barSprite.width) / 2,
        this.wall.height,
        this.barSprite.width,
        this.barSprite.height,
        "blue",
        true
      ),
      dj: new Target(
        DJ_LABEL,
        this.wall.width,
        (this.canvasHeight - this.wall.height - this.djSprite.height) / 2,
        this.djSprite.width,
        this.djSprite.height,
        "green",
        true
      ),
      sister: new Target(
        SISTER_LABEL,
        this.canvasWidth - this.wall.width - this.sisterSprite.width,
        this.wall.height,
        this.sisterSprite.width,
        this.sisterSprite.height,
        "yellow",
        true
      ),
    };

    // Add game over view
    this.gameOverView = document.getElementById("gameOverView");
    this.playAgainBtn = document.getElementById("playAgainBtn");

    this.isRecording = false;

    // Add these lines
    this.introView = document.getElementById("introView");
    this.startGameBtn = document.getElementById("startGameBtn");

    this.backgroundMusic = new Audio('assets/assets/tiny-steps-danijel-zambo-main-version-1433-01-48.mp3');
    this.backgroundMusic.loop = true;
    
    this.gameOverMusic = new Audio('/assets/assets/game-over-8bit-music-danijel-zambo-1-00-16.mp3');
    this.gameOverMusic.loop = false;
    
    this.victoryMusic = new Audio('/assets/assets/moonlit-whispers-theo-gerard-main-version-35960-02-34.mp3');
    this.victoryMusic.loop = false;

    // Move character images to class state
    this.leftCharacterImg = document.getElementById("leftCharacterImg");
    this.rightCharacterImg = document.getElementById("rightCharacterImg");
    this.hideCharacterImages();
  }

  showCharacterImages() {
    this.leftCharacterImg.style.display = "block";
    this.rightCharacterImg.style.display = "block";
  }

  hideCharacterImages() {
    this.leftCharacterImg.style.display = "none";
    this.rightCharacterImg.style.display = "none";
  }

  init(firstRun = true) {
    this.canvas.width = this.canvasWidth;
    this.canvas.height = this.canvasHeight;

    document.addEventListener("keydown", this.handleKeyDown);
    document.addEventListener("keyup", this.handleKeyUp);

    // Initialize with game view

    this.sendButton.addEventListener("click", this.handleSendMessage);
    this.dialogueContinueButton.addEventListener("click", this.handleDialogueContinue);
    this.dialogueNextButton.addEventListener("click", this.handleDialogueNext);
    this.playAgainBtn.addEventListener("click", this.handlePlayAgain);
    this.microphoneButton.addEventListener("click", this.handleMicrophone);

    if (firstRun) {
      this.startGameBtn.addEventListener("click", this.handleFirstStartGame);
      this.switchView("intro");
    } else {
      if (this.currentView !== "game") {
        this.switchView("game");
      }
      this.run();
      this.shyguySprite.setTarget(this.targets.exit);
    }
  }

  async handleFirstStartGame() {
    this.switchView("dialogue");
    this.leftCharacterImg.src = "/assets/assets/wingman.jpeg";
    this.rightCharacterImg.src = "/assets/assets/shyguy_headshot.jpeg";
    this.showCharacterImages();
    this.hideContinueButton();

    for (const introMessage of GameEngine.introMessages) {
      const { message, character } = introMessage;
      this.addChatMessage(this.dialogueChatContainer, message, character, true);
      if (this.voiceEnabled) {
        await this.elevenLabsClient.playAudioForCharacter(character, message);
      } else {
        await new Promise((resolve) => setTimeout(resolve, 1000));
      }
    }

    this.showNextButton();
  }

  showNextButton() {
    if (this.dialogueNextButton) {
      this.dialogueNextButton.style.display = "block";
    }
  }

  hideNextButton() {
    if (this.dialogueNextButton) {
      this.dialogueNextButton.style.display = "none";
    }
  }

  handleDialogueNext() {
    this.clearChat(this.dialogueChatContainer);
    this.leftCharacterImg.src = "";
    this.rightCharacterImg.src = "";
    this.hideCharacterImages();
    this.hideNextButton();
    this.showContinueButton();
    this.handleStartGame();
  }

  async handleStartGame() {
    this.switchView("game");
    this.playBackgroundMusic();
    this.run();
    this.shyguySprite.setTarget(this.targets.exit);
  }

  setResetCallback(func) {
    this.resetCallback = func;
  }

  resetGame() {
    if (this.resetCallback) {
      this.resetCallback();
    }
  }

  initBackgroundGridMap() {
    for (let row = 0; row < this.gridRows; row++) {
      this.backgroundGridMap[row] = [];
      for (let col = 0; col < this.gridCols; col++) {
        // Set walls and obstacles (in future)
        if (row === 0 || row === this.gridRows - 1 || col === 0 || col === this.gridCols - 1) {
          this.backgroundGridMap[row][col] = this.gridMapTypes.wall;
        } else {
          this.backgroundGridMap[row][col] = this.gridMapTypes.floor;
        }
      }
    }
    this.backgroundGridMap[0][1] = this.gridMapTypes.door;
  }

  checkWallCollision(sprite, newX, newY) {
    const x = newX;
    const y = newY;
    // For a sprite twice as big as grid, divide by half the sprite width/height
    const gridX = Math.floor(x / (sprite.width * 1.33));
    const gridY = Math.floor(y / (sprite.height / 2));

    // Check all grid cells the sprite overlaps
    // For a sprite twice as big, it can overlap up to 4 cells
    for (let row = gridY; row <= Math.floor((y + sprite.height) / (sprite.height / 2)); row++) {
      for (let col = gridX; col <= Math.floor((x + sprite.width) / (sprite.width * 1.33)); col++) {
        if (row >= 0 && row < this.gridRows && col >= 0 && col < this.gridCols) {
          if (this.backgroundGridMap[row][col] === this.gridMapTypes.wall) {
            return true;
          }
        }
      }
    }

    return false;
  }

  checkSpriteCollision(newX, newY, sprite1, sprite2) {
    return (
      newX < sprite2.x + sprite2.width &&
      newX + sprite1.width > sprite2.x &&
      newY < sprite2.y + sprite2.height &&
      newY + sprite1.height > sprite2.y
    );
  }

  handleSpriteCollision(sprite1, sprite2) {
    if (!this.pushEnabled) {
      return true; // Return true to block movement as before
    }

    // Calculate velocity difference
    let dx = 0;
    let dy = 0;
    if (this.keys.ArrowUp) dy = -sprite1.speed;
    else if (this.keys.ArrowDown) dy = sprite1.speed;
    else if (this.keys.ArrowLeft) dx = -sprite1.speed;
    else if (this.keys.ArrowRight) dx = sprite1.speed;

    // If arrow player isn't moving, stop button player
    if (dx === 0 && dy === 0) {
      return true;
    }

    // Calculate effective push speed (difference in velocities)
    const pushSpeed = Math.max(0, sprite1.speed - sprite2.speed);

    // If arrow player is faster, push button player
    if (pushSpeed > 0) {
      let newX = sprite2.x + (dx !== 0 ? dx : 0);
      let newY = sprite2.y + (dy !== 0 ? dy : 0);

      // Only apply the push if it won't result in a wall collision
      if (!this.checkWallCollision(sprite2, newX, newY)) {
        sprite2.x = newX;
        sprite2.y = newY;
      }
    }

    return true; // Still prevent arrow player from moving through button player
  }

  updateGuidedSprite() {
    if (!this.shyguySprite.target) return;

    const dx = this.shyguySprite.target.x - this.shyguySprite.x;
    const dy = this.shyguySprite.target.y - this.shyguySprite.y;
    const distance = Math.sqrt(dx * dx + dy * dy);

    const moveX = (dx / distance) * this.shyguySprite.speed;
    const moveY = (dy / distance) * this.shyguySprite.speed;

    let newX = this.shyguySprite.x + moveX;
    let newY = this.shyguySprite.y + moveY;

    // Check wall collision first
    if (!this.checkWallCollision(this.shyguySprite, newX, newY)) {
      const willCollide = this.checkSpriteCollision(newX, newY, this.shyguySprite, this.wingmanSprite);

      if (willCollide) {
        if (this.pushEnabled) {
          // Push mechanics enabled - try to push wingman
          const pushSpeed = Math.max(0, this.shyguySprite.speed - this.wingmanSprite.speed);

          if (pushSpeed > 0) {
            let wingmanNewX = this.wingmanSprite.x + moveX;
            let wingmanNewY = this.wingmanSprite.y + moveY;

            if (!this.checkWallCollision(this.wingmanSprite, wingmanNewX, wingmanNewY)) {
              this.wingmanSprite.x = wingmanNewX;
              this.wingmanSprite.y = wingmanNewY;
              this.shyguySprite.x = newX;
              this.shyguySprite.y = newY;
              this.shyguySprite.moving = true;
            }
          }
        }

        // If push is disabled or push failed, try to path around
        if (this.shyguySprite.x === newX && this.shyguySprite.y === newY) {
          const leftPath = { x: newX - this.wingmanSprite.width, y: newY };
          const rightPath = { x: newX + this.wingmanSprite.width, y: newY };
          const upPath = { x: newX, y: newY - this.wingmanSprite.height };
          const downPath = { x: newX, y: newY + this.wingmanSprite.height };

          const paths = [leftPath, rightPath, upPath, downPath];
          let bestPath = null;
          let bestDistance = Infinity;

          for (const path of paths) {
            if (
              !this.checkWallCollision(this.shyguySprite, path.x, path.y) &&
              !this.checkSpriteCollision(path.x, path.y, this.shyguySprite, this.wingmanSprite)
            ) {
              const pathDistance = Math.sqrt(
                Math.pow(this.shyguySprite.target.x - path.x, 2) + Math.pow(this.shyguySprite.target.y - path.y, 2)
              );
              if (pathDistance < bestDistance) {
                bestDistance = pathDistance;
                bestPath = path;
              }
            }
          }

          if (bestPath) {
            this.shyguySprite.x = bestPath.x;
            this.shyguySprite.y = bestPath.y;
            this.shyguySprite.moving = true;
          }
        }
      } else {
        // No collision, proceed normally
        this.shyguySprite.x = newX;
        this.shyguySprite.y = newY;
        this.shyguySprite.moving = true;
      }
    }
  }

  updateSprite() {
    let newX = this.wingmanSprite.x;
    let newY = this.wingmanSprite.y;
    let isMoving = false;

    if (this.keys.ArrowUp) {
      newY -= this.wingmanSprite.speed;
      isMoving = true;
    }
    if (this.keys.ArrowDown) {
      newY += this.wingmanSprite.speed;
      isMoving = true;
    }
    if (this.keys.ArrowLeft) {
      newX -= this.wingmanSprite.speed;
      this.wingmanSprite.frameY = 0; // left
      isMoving = true;
    }
    if (this.keys.ArrowRight) {
      newX += this.wingmanSprite.speed;
      this.wingmanSprite.frameY = 1; // right
      isMoving = true;
    }

    // Check wall collision first
    if (!this.checkWallCollision(this.wingmanSprite, newX, newY)) {
      // Check collision with shyguy
      const willCollide = this.checkSpriteCollision(newX, newY, this.wingmanSprite, this.shyguySprite);

      if (willCollide) {
        if (this.pushEnabled) {
          // Try to push shyguy if push is enabled
          this.handleSpriteCollision(this.wingmanSprite, this.shyguySprite);
        }
        // If push is disabled or push failed, don't move
        return;
      }

      // No collision, proceed with movement
      this.wingmanSprite.x = newX;
      this.wingmanSprite.y = newY;
    }

    this.wingmanSprite.moving = isMoving;
  }

  handleKeyDown(e) {
    if (e.key in this.keys) {
      this.keys[e.key] = true;
      this.wingmanSprite.moving = true;
    } else if (e.key === "Enter" && this.currentView === "game" && !e.shiftKey) {
      e.preventDefault();
      this.handleSendMessage();
    }
  }

  handleKeyUp(e) {
    if (e.key in this.keys) {
      this.keys[e.key] = false;
      this.wingmanSprite.moving = Object.values(this.keys).some((key) => key);
    }
  }

  setNewTarget(target) {
    if (target && target.enabled) {
      this.shyguySprite.setTarget(target);
      this.updateGuidedSpriteDirection(this.shyguySprite);
    }
    if (!target) {
      this.shyguySprite.setTarget(null);
    }
  }

  checkTargetReached(sprite, target) {
    // Check if sprite overlaps with target using AABB collision detection
    const spriteLeft = sprite.x;
    const spriteRight = sprite.x + sprite.width;
    const spriteTop = sprite.y;
    const spriteBottom = sprite.y + sprite.height;

    const targetLeft = target.x;
    const targetRight = target.x + target.width;
    const targetTop = target.y;
    const targetBottom = target.y + target.height;

    // Check for overlap on both x and y axes
    const xOverlap = spriteRight >= targetLeft && spriteLeft <= targetRight;
    const yOverlap = spriteBottom >= targetTop && spriteTop <= targetBottom;

    return xOverlap && yOverlap;
  }

  updateGuidedSpriteDirection(sprite) {
    if (!sprite.target) return;

    const dx = sprite.target.x - sprite.x;

    // Update direction based only on horizontal movement
    if (dx !== 0) {
      sprite.frameY = dx > 0 ? 1 : 0; // 0 for right, 1 for left
    }
  }

  updateSpriteAnimation(sprite) {
    if (sprite.moving) {
      if (this.gameFrame % sprite.frameRate === 0) {
        sprite.frameX = (sprite.frameX + 1) % sprite.frameCount;
      }
    } else {
      sprite.frameX = 0;
    }
  }

  async update() {
    this.gameFrame++;

    // Update Shyguy position
    if (this.shyguySprite.target && this.shyguySprite.target.enabled) {
      this.updateGuidedSprite(this.shyguySprite);
      if (this.shyguySprite.moving) {
        this.updateSpriteAnimation(this.shyguySprite);
      }
    }

    // update Wingman position
    this.updateSprite(this.wingmanSprite);
    if (this.wingmanSprite.moving) {
      this.updateSpriteAnimation(this.wingmanSprite);
    }

    for (const target of Object.values(this.targets)) {
      const isClose = this.checkTargetReached(this.shyguySprite, target);

      // TODO: reenable the target so the player can visit it again
      if (!target.enabled) {
        if (!isClose) {
          target.enabled = true;
        }
        continue;
      }

      if (isClose) {
        // pause the game
        target.enabled = false;
        this.stopShyguyAnimation(target);

        if (target.label === EXIT_LABEL) {
          this.gameOver = true;
          this.gameSuccessful = false;
          this.setGameOver(true);
          this.switchView("gameOver");
        } else {
          await this.handleDialogueWithStoryEngine(target.label);
        }
        break;
      }
    }
  }

  async handleDialogueWithStoryEngine(label) {
    this.switchView("dialogue");
    this.hideContinueButton();

    // Show loading indicator
    const dialogueBox = document.querySelector(".dialogue-box");
    dialogueBox.classList.add("loading");

    const response = await this.storyEngine.onEncounter(label);

    // Hide loading indicator
    dialogueBox.classList.remove("loading");

    // Update character images using class properties
    if (this.leftCharacterImg && response.char2imgpath) {
      this.leftCharacterImg.src = response.char2imgpath;
      this.leftCharacterImg.style.display = "block";
    }

    if (this.rightCharacterImg && response.char1imgpath) {
      this.rightCharacterImg.src = response.char1imgpath;
      this.rightCharacterImg.style.display = "block";
    }

    const conversation = response.conversation;

    // TODO: set the images if they are available

    for (const message of conversation) {
      const { role, content } = message;
      const label = nameToLabel(role);
      this.addChatMessage(this.dialogueChatContainer, content, label, true);

      // Only play audio if voice is enabled
      if (this.voiceEnabled) {
        try {
          this.lowerMusicVolumeALot();
          await this.elevenLabsClient.playAudioForCharacter(label, content);
          this.restoreMusicVolume();
        } catch (error) {
          console.error("Error playing audio:", label);
        }
      }
    }

    if (response.gameSuccesful) {
      this.gameOver = true;
      this.gameSuccessful = true;
    } else if (response.gameOver) {
      this.gameOver = true;
      this.gameSuccessful = false;
    } else {
      this.gameOver = false;
      this.gameSuccessful = false;
    }

    this.showContinueButton();
  }

  stopShyguyAnimation(target) {
    this.shyguySprite.moving = false;
    this.shyguySprite.frameX = 0;
    this.shyguySprite.target = null;
  }

  draw() {
    this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);

    // Draw grid map
    for (let row = 0; row < this.gridRows; row++) {
      for (let col = 0; col < this.gridCols; col++) {
        const x = col * this.wall.width;
        const y = row * this.wall.height;

        if (this.backgroundGridMap[row][col] === this.gridMapTypes.wall) {
          this.ctx.drawImage(this.wall.image, x, y, this.wall.width, this.wall.height);
        } else if (this.backgroundGridMap[row][col] === this.gridMapTypes.floor) {
          this.ctx.drawImage(this.floor.image, x, y, this.floor.width, this.floor.height);
        } else if (this.backgroundGridMap[row][col] === this.gridMapTypes.door) {
          this.ctx.drawImage(this.door.image, x, y, this.door.width, this.door.height);
        }
      }
    }

    this.drawTargetSprite(this.jessicaSprite, this.targets.girl);
    this.drawTargetSprite(this.barSprite, this.targets.bar);
    this.drawTargetSprite(this.djSprite, this.targets.dj);
    this.drawTargetSprite(this.sisterSprite, this.targets.sister);

    // Draw shyguy
    this.ctx.drawImage(
      this.shyguySprite.image,
      this.shyguySprite.frameX * this.shyguySprite.width,
      this.shyguySprite.frameY * this.shyguySprite.height,
      this.shyguySprite.width,
      this.shyguySprite.height,
      this.shyguySprite.x,
      this.shyguySprite.y,
      this.shyguySprite.width,
      this.shyguySprite.height
    );

    // Draw wingman
    this.ctx.drawImage(
      this.wingmanSprite.image,
      this.wingmanSprite.frameX * this.wingmanSprite.width,
      this.wingmanSprite.frameY * this.wingmanSprite.height,
      this.wingmanSprite.width,
      this.wingmanSprite.height,
      this.wingmanSprite.x,
      this.wingmanSprite.y,
      this.wingmanSprite.width,
      this.wingmanSprite.height
    );
  }

  drawTargetSprite(sprite, target) {
    this.ctx.drawImage(sprite.image, target.x, target.y, target.width, target.height);
  }

  switchView(viewName) {
    if (viewName === this.currentView) return;

    this.currentView = viewName;

    // Hide all views first
    this.introView.classList.remove("active");
    this.gameView.classList.remove("active");
    this.dialogueView.classList.remove("active");
    this.gameOverView.classList.remove("active");

    // Show the requested view
    switch (viewName) {
      case "intro":
        this.introView.classList.add("active");
        break;
      case "game":
        this.gameView.classList.add("active");
        break;
      case "dialogue":
        this.dialogueView.classList.add("active");
        break;
      case "gameOver":
        this.gameOverView.classList.add("active");
        break;
    }
  }

  enablePush() {
    this.pushEnabled = true;
  }

  disablePush() {
    this.pushEnabled = false;
  }

  initDebugControls() {
    const targetDoorBtn = document.getElementById("targetDoorBtn");
    const targetGirlBtn = document.getElementById("targetGirlBtn");
    const targetBarBtn = document.getElementById("targetBarBtn");
    const targetDjBtn = document.getElementById("targetDjBtn");
    const targetSisterBtn = document.getElementById("targetSisterBtn");
    const stopNavBtn = document.getElementById("stopNavBtn");
    const togglePushBtn = document.getElementById("togglePushBtn");
    const speedBoostBtn = document.getElementById("speedBoostBtn");
    const toggleVoiceBtn = document.getElementById("toggleVoiceBtn");

    targetDoorBtn.addEventListener("click", () => this.setNewTarget(this.targets.exit));
    targetGirlBtn.addEventListener("click", () => this.setNewTarget(this.targets.girl));
    targetBarBtn.addEventListener("click", () => this.setNewTarget(this.targets.bar));
    targetDjBtn.addEventListener("click", () => this.setNewTarget(this.targets.dj));
    targetSisterBtn.addEventListener("click", () => this.setNewTarget(this.targets.sister));
    stopNavBtn.addEventListener("click", () => this.setNewTarget(null));

    // Add push mechanics toggle
    togglePushBtn.addEventListener("click", () => {
      if (this.pushEnabled) {
        this.disablePush();
      } else {
        this.enablePush();
      }
      togglePushBtn.textContent = this.pushEnabled ? "Disable Push" : "Enable Push";
    });

    // Add speed boost toggle
    speedBoostBtn.addEventListener("click", () => {
      if (this.shyguySprite.speed === SHYGUY_SPEED) {
        this.shyguySprite.setSpeed(10);
        speedBoostBtn.textContent = "Normal Speed";
      } else {
        this.shyguySprite.setSpeed(SHYGUY_SPEED);
        speedBoostBtn.textContent = "Speed Boost";
      }
    });

    // Add voice toggle handler
    toggleVoiceBtn.addEventListener("click", () => {
      this.voiceEnabled = !this.voiceEnabled;
      toggleVoiceBtn.textContent = this.voiceEnabled ? "Disable Voice" : "Enable Voice";
    });
  }

  // Update status text
  updateStatus(message) {
    const statusText = document.getElementById("statusText");
    if (statusText) {
      statusText.textContent = message;
    }
  }

  clearChat(container) {
    if (container) {
      container.innerHTML = "";
    }
  }

  addChatMessage(container, message, character, shyguyIsMain) {
    if (!container) return;

    const isMain = shyguyIsMain ? character === SHYGUY_LABEL : character !== SHYGUY_LABEL;

    const messageDiv = document.createElement("div");
    messageDiv.className = `chat-message ${isMain ? "right-user" : "left-user"}`;

    const bubble = document.createElement("div");
    bubble.className = "message-bubble";
    bubble.textContent = message;

    messageDiv.appendChild(bubble);
    container.appendChild(messageDiv);

    // Auto scroll to bottom
    container.scrollTop = container.scrollHeight;
  }

  resolveAction(action) {
    // TODO: resolve the action
    switch (action) {
      case "stay_idle":
        this.setNewTarget(null);
        break;
      case "go_bar":
        this.setNewTarget(this.targets.bar);
        break;
      case "go_dj":
        this.setNewTarget(this.targets.dj);
        break;
      case "go_sister":
        this.setNewTarget(this.targets.sister);
        break;
      case "go_girl":
        this.setNewTarget(this.targets.girl);
        break;
      case "go_home":
        this.setNewTarget(this.targets.exit);
        break;
      default:
        break;
    }
  }

  async sendMessageToShyguy(message) {
    this.addChatMessage(this.gameChatContainer, message, WINGMAN_LABEL, false);
    this.messageInput.value = "";

    this.shyguyLLM.getShyGuyResponse(message).then(async (response) => {
      const dialogue = response.dialogue;
      const action = response.action;

      this.addChatMessage(this.gameChatContainer, dialogue, SHYGUY_LABEL, false);

      // Only play audio if voice is enabled
      if (this.voiceEnabled) {
        this.disableGameInput();
        this.lowerMusicVolumeALot();
        await this.elevenLabsClient.playAudioForCharacter(SHYGUY_LABEL, dialogue);
        this.enableGameInput();
        this.restoreMusicVolume();
      }

      // TODO: save conversation history
      await this.shyguy.learnFromWingman(message);
      console.log("[ShyguyLLM]: Next action: ", action);
      this.resolveAction(action);
    });
  }

  async handleSendMessage() {
    const message = this.messageInput.value.trim();
    if (message.length === 0) return;
    this.sendMessageToShyguy(message);
  }

  async run() {
    // wait for 16ms
    await new Promise((resolve) => setTimeout(resolve, 16));
    await this.update();
    this.draw();
    if (this.shouldContinue) {
      requestAnimationFrame(this.run);
    }
  }

  handlePlayAgain() {
    this.clearChat(this.gameChatContainer);
    this.resetGame();
    this.switchView("game");
  }

  async handleMicrophone() {
    if (!this.isRecording) {
      // Start recording
      this.isRecording = true;
      this.microphoneButton.classList.add("recording");
      this.microphoneButton.innerHTML = '<i class="fas fa-stop"></i>';

      // Lower music volume while recording
      this.lowerMusicVolumeALot();
      await this.speechToTextClient.startRecording();
    } else {
      // Stop recording
      this.isRecording = false;
      this.microphoneButton.classList.remove("recording");
      this.microphoneButton.innerHTML = '<i class="fas fa-microphone"></i>';

      const result = await this.speechToTextClient.stopRecording();
      // Restore music volume after recording
      this.restoreMusicVolume();
      this.sendMessageToShyguy(result.text);
    }
  }

  showContinueButton() {
    this.dialogueContinueButton.style.display = "block";
  }

  hideContinueButton() {
    this.dialogueContinueButton.style.display = "none";
  }

  setGameOver(fromExit) {
    this.stopBackgroundMusic();
    
    if (this.gameSuccessful) {
      this.gameOverImage.src = "assets/assets/victory.png";
      this.playVictoryMusic();
    } else {
      this.gameOverImage.src = "assets/assets/game-over.png";
      this.playGameOverMusic();
    }

    if (fromExit) {
      this.gameOverText.textContent = "You lost! Shyguy ran away!";
      return;
    }

    this.gameOverText.textContent = this.gameSuccessful
      ? "You won! Shyguy got a date!"
      : "You lost! Shyguy got rejected!";
  }

  handleDialogueContinue() {
    this.clearChat(this.dialogueChatContainer);

    // Hide character images
    const leftCharacterImg = document.getElementById("leftCharacterImg");
    const rightCharacterImg = document.getElementById("rightCharacterImg");

    if (leftCharacterImg) {
      leftCharacterImg.style.display = "none";
    }
    if (rightCharacterImg) {
      rightCharacterImg.style.display = "none";
    }

    // decide if game is over
    if (this.gameOver) {
      this.setGameOver(false);
      this.switchView("gameOver");
      return;
    }

    // Enable push if shyguy has had at least one beer
    if (this.shyguy.num_beers > 0) {
      this.enablePush();
    }

    this.switchView("game");
    this.shyguyLLM.getShyGuyResponse("").then((response) => {
      const next_action = response.action;

      this.resolveAction(next_action);
    });
  }

  disableGameInput() {
    this.sendButton.setAttribute("disabled", "");
    this.microphoneButton.setAttribute("disabled", "");
    this.messageInput.setAttribute("disabled", "");
  }

  enableGameInput() {
    this.sendButton.removeAttribute("disabled");
    this.microphoneButton.removeAttribute("disabled");
    this.messageInput.removeAttribute("disabled");
  }

  playBackgroundMusic() {
    this.backgroundMusic.play().catch(error => {
      console.error("Error playing background music:", error);
    });
  }

  stopBackgroundMusic() {
    this.backgroundMusic.pause();
    this.backgroundMusic.currentTime = 0;
  }

  playGameOverMusic() {
    this.gameOverMusic.play().catch(error => {
      console.error("Error playing game over music:", error);
    });
  }

  playVictoryMusic() {
    this.victoryMusic.play().catch(error => {
      console.error("Error playing victory music:", error);
    });
  }

  stopAllMusic() {
    this.stopBackgroundMusic();
    this.gameOverMusic.pause();
    this.gameOverMusic.currentTime = 0;
    this.victoryMusic.pause();
    this.victoryMusic.currentTime = 0;
  }

  lowerMusicVolume() {
    // Store original volumes if not already stored
    if (!this.originalVolumes) {
        this.originalVolumes = {
            background: this.backgroundMusic.volume,
            gameOver: this.gameOverMusic.volume,
            victory: this.victoryMusic.volume
        };
    }
    
    // Lower all music volumes to 20% of their original values
    this.backgroundMusic.volume = this.originalVolumes.background * 0.2;
    this.gameOverMusic.volume = this.originalVolumes.gameOver * 0.2;
    this.victoryMusic.volume = this.originalVolumes.victory * 0.2;
  }
  lowerMusicVolumeALot() {
    // Store original volumes if not already stored
    if (!this.originalVolumes) {
        this.originalVolumes = {
            background: this.backgroundMusic.volume,
            gameOver: this.gameOverMusic.volume,
            victory: this.victoryMusic.volume
        };
    }
    
    // Lower all music volumes to 20% of their original values
    this.backgroundMusic.volume = this.originalVolumes.background * 0.01;
    this.gameOverMusic.volume = this.originalVolumes.gameOver * 0.01;
    this.victoryMusic.volume = this.originalVolumes.victory * 0.01;
  }

  restoreMusicVolume() {
    // Restore original volumes if they exist
    if (this.originalVolumes) {
      this.backgroundMusic.volume = this.originalVolumes.background * 0.2;
      this.gameOverMusic.volume = this.originalVolumes.gameOver * 0.2;
      this.victoryMusic.volume = this.originalVolumes.victory * 0.2;
    }
  }
}