import { getConnectedCog } from "../../utils/CogManager";
import { EventBus } from "../EventBus";
export class BubbleUtils {
    // Properties
    initialBubble = null;
    arrow = null;
    nextBubble = null;
    bubblesGroup = null;
    leftWall = null;
    rightWall = null;
    topWall = null;
    grid = [];
    bubbleSize = 32;
    canvasHeight = 500;
    canvasWidth = 592;
    colors = ['red', 'pink', 'blue', 'green', 'yellow', 'purple'];
    rows = 15;
    columns = 17;
    filledRows = 9;
    marginX = this.bubbleSize;
    marginY = this.bubbleSize;
    currentBubbleColor = Phaser.Utils.Array.GetRandom(this.colors);
    nextBubbleColor = Phaser.Utils.Array.GetRandom(this.colors);
    extraBubbles = [];
    extraBubbleCount = 5;
    lifeCount = 5;
    isGameOver = false;
    score = 0;
    scoreIncrement = 10; // Increment for each bubble
    scoreTarget = 1000; // Target score to show "Should I stay or should I go"
    scoreText = null;
    bubbleSound = null;
    bubbleshoot = null;
    projectionLine = null;
    protractorGraphics = null;
    angleText = null;
    shouldYouGoBeenShown = false;
    constructor() {
        // Initialize grid for debugging purposes
        // Bind methods to ensure proper 'this' context
        this.findClosestEmptySquare = this.findClosestEmptySquare.bind(this);
        this.updateGridWithBubble = this.updateGridWithBubble.bind(this);
        this.findConnectedBalls = this.findConnectedBalls.bind(this);
        this.createNewRowColors = this.createNewRowColors.bind(this);
    }
    findClosestEmptySquare(x, y) {
        let closestSquare = null;
        let closestDistance = Infinity;
        for (let row = 0; row < this.grid.length; row++) {
            for (let col = 0; col < this.grid[row].length; col++) {
                const square = this.grid[row][col];
                if (square.color === "blank") {
                    const distance = Math.sqrt(Math.pow(square.centerX - x, 2) + Math.pow(square.centerY - y, 2));
                    if (distance < closestDistance) {
                        closestDistance = distance;
                        closestSquare = square;
                    }
                }
            }
        }
        return closestSquare;
    }
    isRowShiftedRight(row) {
        if (row === 14) {
            return (this.grid[row][0].centerX - this.grid[row - 1][0].centerX) < 0;
        }
        return (this.grid[row][0].centerX - this.grid[row + 1][0].centerX) > 0;
    }
    updateGridWithBubble(x, y, color, bubble) {
        const col = Math.floor(((x - this.marginX) - this.bubbleSize / 2) / this.bubbleSize);
        const row = Math.floor(((y - this.marginY) - this.bubbleSize) / this.bubbleSize) + 1;
        if (row >= 0 && row < this.grid.length && col >= 0 && col < this.grid[row].length) {
            const isShiftedRight = this.isRowShiftedRight(row);
            this.grid[row][col] = {
                centerX: col * this.bubbleSize + this.bubbleSize / 2 + (isShiftedRight ? this.bubbleSize / 2 : 0) + this.marginX,
                centerY: row * this.bubbleSize + this.bubbleSize / 2 + this.marginY,
                color: color,
                bubble: bubble
            };
        }
        else {
            console.warn("Bubble is outside the grid bounds!");
        }
    }
    findConnectedBalls(x, y, color) {
        const col = Math.floor(((x - this.marginX) - this.bubbleSize / 2) / this.bubbleSize);
        const row = Math.floor(((y - this.marginY) - this.bubbleSize) / this.bubbleSize) + 1;
        const visited = new Set();
        const connectedBalls = [];
        const isValid = (r, c) => {
            return r >= 0 && r < this.grid.length && c >= 0 && c < this.grid[r].length;
        };
        const dfs = (r, c) => {
            const key = `${r},${c}`;
            if (!isValid(r, c) || visited.has(key) || this.grid[r][c].color !== color) {
                return;
            }
            visited.add(key);
            connectedBalls.push({ row: r, col: c, ...this.grid[r][c] });
            const isShiftedRight = this.isRowShiftedRight(r);
            const offsets = !isShiftedRight
                ? [[-1, -1], [-1, 0], [0, -1], [0, 1], [1, -1], [1, 0]]
                : [[-1, 0], [-1, 1], [0, -1], [0, 1], [1, 0], [1, 1]];
            for (const [dr, dc] of offsets) {
                dfs(r + dr, c + dc);
            }
        };
        dfs(row, col);
        return connectedBalls.length > 2 ? connectedBalls : [];
    }
    createNewRowColors() {
        const newRowColors = [];
        for (let col = 0; col < this.columns; col++) {
            const randomColor = Phaser.Utils.Array.GetRandom(this.colors);
            newRowColors.push(randomColor);
        }
        console.log("New row colors created:", newRowColors);
        return newRowColors;
    }
    async removeFloatingIslands() {
        const rows = this.grid.length;
        const columns = this.grid[0].length;
        const visited = createVisitedArray(rows, columns);
        const this_ = this;
        markConnectedBubbles();
        const floatingBalls = removeUnvisitedBubbles();
        function createVisitedArray(rows, columns) {
            return Array.from({ length: rows }, () => Array(columns).fill(false));
        }
        function getNeighbors(row, col) {
            const isShiftedRight = this_.isRowShiftedRight(row);
            return [
                [row - 1, col],
                [row + 1, col],
                [row, col - 1],
                [row, col + 1],
                [row - 1, col + (!isShiftedRight ? -1 : 1)],
                [row + 1, col + (!isShiftedRight ? -1 : 1)],
            ];
        }
        function dfs(row, col) {
            if (row < 0 || row >= rows ||
                col < 0 || col >= columns ||
                visited[row][col] ||
                !this_.grid[row][col].bubble)
                return;
            visited[row][col] = true;
            for (const [neighborRow, neighborCol] of getNeighbors(row, col)) {
                dfs(neighborRow, neighborCol);
            }
        }
        function markConnectedBubbles() {
            for (let col = 0; col < columns; col++) {
                if (this_.grid[0][col].bubble && !visited[0][col]) {
                    dfs(0, col);
                }
            }
        }
        function removeUnvisitedBubbles() {
            const floatingBalls = [];
            for (let row = 0; row < rows; row++) {
                for (let col = 0; col < columns; col++) {
                    if (this_.grid[row][col].bubble && !visited[row][col]) {
                        floatingBalls.push({ row, col, bubble: this_.grid[row][col].bubble });
                        this_.grid[row][col].bubble.destroy();
                        this_.grid[row][col] = { centerX: this_.grid[row][col].centerX, centerY: this_.grid[row][col].centerY, color: "blank", bubble: null };
                    }
                }
            }
            return floatingBalls;
        }
        if (floatingBalls.length > 0) {
            this.score += floatingBalls.length * this.scoreIncrement;
            this.scoreText?.setText("Score: " + this.score);
        }
    }
    removeConnectedBalls(connectedBalls, sound, scene) {
        return new Promise(async (resolve) => {
            if (connectedBalls.length === 0) {
                this.isGameOver = await this.checkForEndgame();
                if (this.isGameOver) {
                    resolve();
                    return;
                }
                if (this.lifeCount > 0) {
                    this.extraBubbles[this.lifeCount - 1].setTexture("shadowBubbles");
                }
                this.lifeCount--;
                if (this.lifeCount === -1) {
                    this.extraBubbleCount--;
                    if (this.extraBubbleCount === 0) {
                        this.extraBubbleCount = 5;
                    }
                    this.updateBubblesTextures(this.extraBubbleCount);
                }
                sound.play();
                resolve();
                return;
            }
            let delay = 0;
            let completed = 0;
            this.score += (connectedBalls.length - 3) * 20 + 30;
            this.scoreText?.setText("Score:" + this.score);
            connectedBalls.forEach(({ row, col }) => {
                setTimeout(() => {
                    const bubble = this.grid[row][col].bubble;
                    if (bubble) {
                        sound.play();
                        bubble.destroy();
                    }
                    this.grid[row][col] = {
                        centerX: this.grid[row][col].centerX,
                        centerY: this.grid[row][col].centerY,
                        color: "blank",
                        bubble: null,
                    };
                    completed += 1;
                    if (completed === connectedBalls.length) {
                        resolve();
                    }
                }, delay);
                delay += 80;
            });
        });
    }
    setCanvasStyle() {
        const canvas = document.querySelector("canvas");
        if (canvas) {
            // canvas.style.borderRadius = "20px";
        }
    }
    createBackground(scene) {
        const sectionBackground = scene.add.image(311, 264, "backgroundImage");
        sectionBackground.setDisplaySize(599, 496);
        sectionBackground.setOrigin(0.5);
    }
    createWalls(scene) {
        scene.createWall = (x, y, displayWidth, displayHeight) => {
            const wall = scene.add.rectangle(x, y, displayWidth, displayHeight, 0x000000, 0);
            scene.physics.add.existing(wall, true);
            wall.body.setSize(displayWidth, displayHeight);
            return wall;
        };
        this.leftWall = scene.createWall(20, 510, 10, 1000);
        this.rightWall = scene.createWall(600, 510, 10, 1000);
        this.topWall = scene.createWall(295, 11, 575, 10);
    }
    createRestartButton(scene) {
        const restartButton = scene.add.image(650, 30, "restartButtonImage").setInteractive();
        restartButton.on("pointerdown", () => {
            scene.scene.restart();
            this.grid = [];
            this.isGameOver = false;
            this.extraBubbleCount = 5;
            this.lifeCount = 5;
            this.score = 0;
        });
        restartButton.on("pointerover", () => scene.input.setDefaultCursor("pointer"));
        restartButton.on("pointerout", () => scene.input.setDefaultCursor("default"));
    }
    createBubbleElements(scene) {
        this.bubblesGroup = scene.physics.add.group();
        this.createBubbles(scene);
        this.arrow = scene.add.image(scene.sys.game.config.width / 2 - 50, 546, "arrow").setOrigin(0.5, 1).setScale(0.3);
        this.initialBubble = scene.add.image(scene.sys.game.config.width / 2 - 50, 560 - this.bubbleSize / 2, this.currentBubbleColor)
            .setOrigin(0.5, .5)
            .setAlpha(1);
        this.nextBubble = scene.add.image(this.bubbleSize, 550 - this.bubbleSize / 2, this.nextBubbleColor).setOrigin(0.5, 0).setAlpha(1);
        this.projectionLine = scene.add.line(0, 0, this.arrow.x, this.arrow.y - this.arrow.displayHeight, this.arrow.x, this.arrow.y - 300, 0x000000, 0.3)
            .setOrigin(0.5, 0); // Adjust the line to start at the end of the arrow
        this.protractorGraphics = scene.add.graphics({ x: this.arrow.x, y: this.arrow.y });
        this.protractorGraphics.setVisible(true);
        this.angleText = scene.add.text(this.arrow.x, this.arrow.y - 120, "0°", {
            fontSize: "16px",
            color: "#0000ff",
            fontFamily: "Arial",
            fontStyle: "bold",
        }).setOrigin(0.5).setVisible(true);
        this.extraBubbles = [];
        this.fillExtraBubbles(this.extraBubbleCount, scene);
    }
    setupInputHandlers(scene) {
        const connectedCog = getConnectedCog();
        if (connectedCog) {
            const ay = connectedCog.raftStateInfo.accelerometer.ay;
            const ax = connectedCog.raftStateInfo.accelerometer.ax;
            const raw = Math.atan2(ay, ax); // radians
            const display = raw - Math.PI / 2; // shift so that (0,1)→up
            this.arrow?.setRotation(display);
            this.initialBubble?.setRotation(display);
            this.updateProjectionLine(display);
            this.updateProtractorArc(display); // Update the protractor arc and angle text
            // shoot when cog's button is pressed
        }
        else {
            scene.input.on("pointermove", (pointer) => {
                let angle = Phaser.Math.Angle.Between(this.arrow?.x || 0, this.arrow?.y || 0, pointer.x, pointer.y) + Math.PI / 2;
                this.arrow?.setRotation(angle);
                this.initialBubble?.setRotation(angle);
                this.updateProjectionLine(angle);
                this.updateProtractorArc(angle); // Update the protractor arc and angle text
            });
            scene.input.on("pointerdown", (pointer) => {
                if (pointer.y > (this.rows + 0.5) * this.bubbleSize)
                    return;
                this.shootBubble(scene);
            });
        }
    }
    updateProjectionLine(angle) {
        const lineLength = 300; // Length of the projection line
        if (!this.arrow || !this.projectionLine)
            return; // Ensure arrow and projectionLine are defined
        const startX = this.arrow.x + Math.cos(angle - Math.PI / 2) * this.arrow.displayHeight;
        const startY = this.arrow.y + Math.sin(angle - Math.PI / 2) * this.arrow.displayHeight;
        const endX = startX + Math.cos(angle - Math.PI / 2) * lineLength;
        const endY = startY + Math.sin(angle - Math.PI / 2) * lineLength;
        this.projectionLine.setTo(startX, startY, endX, endY); // Update line endpoints
    }
    updateProtractorArc(angle) {
        // 1) Convert to degrees, then shift so 0° = up
        let degrees = Phaser.Math.RadToDeg(angle) + 0;
        // 2) Wrap into [0, 360)
        degrees = Phaser.Math.Wrap(degrees, 0, 360);
        // 3) Clear and begin drawing
        this.protractorGraphics?.clear();
        this.protractorGraphics?.fillStyle(0x0000ff, 0.3);
        // 4) Compute start/end in radians
        const startRad = -Math.PI / 2; // straight up
        const sweepRad = Phaser.Math.DegToRad(degrees); // how big the arc is
        const endRad = startRad + sweepRad;
        // 5) Draw the filled slice
        this.protractorGraphics?.slice(0, 0, 100, startRad, endRad, false);
        this.protractorGraphics?.fillPath();
        // 6) Update the text (rounded) and position it out along the arrow
        if (!this.arrow || !this.angleText) {
            return;
        }
        this.angleText
            .setText(`${Math.round(degrees)}°`)
            .setPosition(this.arrow.x + Math.cos(angle - Math.PI / 2) * 120, this.arrow.y + Math.sin(angle - Math.PI / 2) * 120);
    }
    loadSounds(scene) {
        this.bubbleSound = scene.sound.add("bubblepop");
        this.bubbleshoot = scene.sound.add("bubbleshoot");
    }
    createScoreText(scene) {
        this.scoreText = scene.add.text(612, 60, "Score: 0", {
            fontSize: "14px",
            color: "#283747",
            fontFamily: "Arial",
            fontStyle: "bold",
        });
    }
    createBubbles(scene) {
        for (let row = 1; row <= this.rows; row++) {
            const rowArray = [];
            for (let col = 1; col <= this.columns; col++) {
                const x = col * this.bubbleSize - (row % 2 === 0 ? this.bubbleSize / 2 : this.bubbleSize) + this.marginX;
                const y = row * this.bubbleSize - this.bubbleSize + this.marginY;
                const centerX = x + this.bubbleSize / 2;
                const centerY = y + this.bubbleSize / 2;
                if (row <= this.filledRows) {
                    const color = Phaser.Utils.Array.GetRandom(this.colors);
                    const bubble = scene.physics.add.image(centerX, centerY, color);
                    bubble.setOrigin(0.5, 0.5);
                    bubble.body.setImmovable(true);
                    this.bubblesGroup?.add(bubble);
                    rowArray.push({ centerX, centerY, color, bubble });
                }
                else {
                    rowArray.push({ centerX, centerY, color: "blank", bubble: null });
                }
            }
            this.grid.push(rowArray);
        }
    }
    shootBubble(scene) {
        if (this.isGameOver)
            return;
        this.bubbleshoot?.play();
        const angle = this.arrow?.rotation;
        const bubble = this.createMovingBubble(scene, angle || 0);
        this.updateNextBubbleTextures();
        if (this.leftWall && this.rightWall) {
            scene.physics.add.collider(bubble, this.leftWall);
            scene.physics.add.collider(bubble, this.rightWall);
        }
        if (this.bubblesGroup && this.topWall) {
            scene.physics.add.overlap(bubble, [...this.bubblesGroup.getChildren(), this.topWall], async (movingBubble, stationaryObject) => {
                await this.handleBubbleCollision(scene, movingBubble, stationaryObject);
                if (this.lifeCount === -1) {
                    let newColors = this.createNewRowColors();
                    this.addNewRowToTop(newColors, scene);
                    this.lifeCount = this.extraBubbleCount;
                    this.isGameOver = await this.checkForEndgame();
                    this.isGameOver && this.showFinalScore(scene);
                }
            });
        }
    }
    createMovingBubble(scene, angle) {
        if (!this.arrow || !this.currentBubbleColor) {
            throw new Error("Arrow or currentBubbleColor is not set");
        }
        const bubble = scene.physics.add.image(this.arrow.x, this.arrow.y, this.currentBubbleColor);
        bubble.body.setCircle(this.bubbleSize / 5);
        bubble.body.setVelocity(Math.cos(angle - Math.PI / 2) * 800, Math.sin(angle - Math.PI / 2) * 800);
        bubble.body.setBounce(1, 1);
        return bubble;
    }
    updateNextBubbleTextures() {
        if (this.colors.includes(this.nextBubbleColor)) {
            this.currentBubbleColor = this.nextBubbleColor;
        }
        else {
            this.currentBubbleColor = Phaser.Utils.Array.GetRandom(this.colors);
        }
        this.initialBubble?.setTexture(this.currentBubbleColor);
        this.nextBubbleColor = Phaser.Utils.Array.GetRandom(this.colors);
        this.nextBubble?.setTexture(this.nextBubbleColor);
    }
    async handleBubbleCollision(scene, movingBubble, stationaryObject) {
        const dx = movingBubble.x - stationaryObject.x;
        const dy = movingBubble.y - stationaryObject.y;
        const distance = Math.sqrt(dx * dx + dy * dy);
        if (distance > this.bubbleSize && stationaryObject !== this.topWall)
            return;
        if (movingBubble.body) {
            movingBubble.body.stop();
        }
        let closestSquare = this.findClosestEmptySquare(movingBubble.x, movingBubble.y);
        if (!closestSquare)
            return;
        let snappedX = closestSquare.centerX;
        let snappedY = closestSquare.centerY;
        movingBubble.destroy();
        const snappedBubble = scene.physics.add.image(snappedX, snappedY, movingBubble.texture.key);
        snappedBubble.setOrigin(0.5, 0.5);
        snappedBubble.body.setImmovable(true);
        snappedBubble.body.setCircle(this.bubbleSize / 2);
        this.bubblesGroup?.add(snappedBubble);
        this.updateGridWithBubble(snappedX, snappedY, movingBubble.texture.key, snappedBubble);
        let connectedBalls = this.findConnectedBalls(snappedX, snappedY, movingBubble.texture.key);
        if (this.bubbleSound) {
            await this.removeConnectedBalls(connectedBalls, this.bubbleSound, scene);
        }
        await this.removeFloatingIslands();
        this.updateAvailableColors();
        if (this.isGameOver) {
            this.showFinalScore(scene);
        }
        // if the score reaches the target show the ShouldIStayOrShouldIGo modal
        if (this.score >= this.scoreTarget && !this.shouldYouGoBeenShown) {
            this.shouldYouGoBeenShown = true;
            EventBus.emit('target-achieved', scene);
        }
    }
    updateAvailableColors() {
        const presentColors = new Set();
        for (let row of this.grid) {
            for (let cell of row) {
                if (cell.bubble) {
                    presentColors.add(cell.color);
                }
            }
        }
        if (this.colors.length !== presentColors.size) {
            this.colors = this.colors.filter(color => presentColors.has(color));
            this.updateNextBubbleTextures();
        }
    }
    addNewRowToTop(newRowColors, scene) {
        this.shiftRowsDown();
        this.createNewTopRow(newRowColors, scene);
    }
    createNewRowColorsWrapper() {
        return this.createNewRowColors();
    }
    shiftRowsDown() {
        for (let row = this.grid.length - 1; row >= 0; row--) {
            for (let col = 0; col < this.grid[row].length; col++) {
                this.shiftBubble(row, col);
                if (row + 1 < this.grid.length) {
                    this.grid[row + 1][col] = this.grid[row][col];
                }
            }
        }
    }
    shiftBubble(row, col) {
        // Only shift the y-coordinate of the bubble
        if (this.grid[row][col].bubble) {
            this.grid[row][col].bubble.y += this.bubbleSize;
        }
        this.grid[row][col].centerY += this.bubbleSize;
    }
    createNewTopRow(newRowColors, scene) {
        // Determine the x-offset for the new row based on the previous row
        // if the difference between the first two rows is positive, it means the first row is shifted right, so we need to shift the new row left
        // if the difference is negative, it means the first row is shifted left, so we need to shift the new row right
        const isShiftedRight = this.isRowShiftedRight(1); // Check if the first row is shifted right
        const newRowOffset = isShiftedRight ? -(this.bubbleSize / 2) : (this.bubbleSize / 2); // Alternate offset
        for (let col = 0; col < newRowColors.length; col++) {
            const centerX = this.grid[0][col].centerX + newRowOffset;
            const centerY = this.marginY + this.bubbleSize / 2;
            const color = newRowColors[col];
            const newBubble = this.createBubble(scene, centerX, centerY, color);
            this.grid[0][col] = { centerX, centerY, color, bubble: newBubble };
            this.bubblesGroup?.add(newBubble);
        }
        console.log("grid", this.grid);
    }
    createBubble(scene, x, y, color) {
        const bubble = scene.physics.add.image(x, y, color);
        bubble.setOrigin(0.5, 0.5);
        bubble.body.setImmovable(true);
        bubble.body.setCircle(this.bubbleSize / 2);
        return bubble;
    }
    fillExtraBubbles(length, scene) {
        for (let i = 0; i < length; i++) {
            const extraBubble = scene.add.image(this.bubbleSize * (i + 2) + i * 5 + 30, 550 - this.bubbleSize / 2, "lifeBubbles").setOrigin(0.5, 0).setAlpha(1);
            this.extraBubbles.push(extraBubble);
        }
    }
    updateBubblesTextures(length) {
        for (let i = 0; i < length; i++) {
            this.extraBubbles[i].setTexture("lifeBubbles");
        }
    }
    async checkForEndgame() {
        for (let col = 0; col < this.grid[this.grid.length - 1].length; col++) {
            if (this.grid[this.grid.length - 1][col].bubble) {
                return true;
            }
        }
        return false;
    }
    showFinalScore(scene) {
        const width = 300;
        const height = 150;
        const x = ((this.columns + 3) * this.bubbleSize - width) / 2;
        const y = 150;
        const graphics = scene.add.graphics();
        graphics.fillStyle(0x000000, 0.7);
        graphics.fillRect(x, y, width, height);
        graphics.lineStyle(4, 0xffffff);
        graphics.strokeRect(x, y, width, height);
        scene.add.text(x + width / 2, y + height / 2 - 20, "Game Over", {
            fontSize: "24px",
            fontFamily: "Arial",
            color: "#ffffff",
        }).setOrigin(0.5);
        scene.add.text(x + width / 2, y + height / 2 + 20, `Final Score: ${this.score}`, {
            fontSize: "20px",
            fontFamily: "Arial",
            color: "#ffff00",
        }).setOrigin(0.5);
    }
}
