import { BrickDisplay } from "@common/display/brick";
import Input from "@common/input";

import spritesheetImage from './assets/spritesheet.png';
import { ITEMS, Items, MONSTERS, MONSTERS_ORDER, loadData, type Item, type Monster } from "./data";
import { randBool, weightedChoice } from "@common/utils";
import { gameLoop } from "@common/game";

let display: BrickDisplay;
const spritesheet = BrickDisplay.convertImage(spritesheetImage);
const background = BrickDisplay.extractSprite(spritesheet, 12, 12, 10, 20);
const winScreen = BrickDisplay.extractSprite(spritesheet, 24, 12, 6, 20);
const playerSprite = BrickDisplay.extractSprite(spritesheet, 0, 4, 4, 4);
const playerDeadSprite = BrickDisplay.extractSprite(spritesheet, 4, 4, 4, 4);

const playerY = 2;

let y = 0;
let targetY = 0;
let playerHealth = 10;
let playerBlink = 0;
let missBlink = 0;
let win = false;
let winBlink = false;

let playerTurn = true;
let lootToConfirm: Items | null = null;
let lootBlink = false;

let selectedSlot = 0;
let inventory: Items[] = [Items.STICK];

let monsterAlive = true;
let currentMonster = -1;
let monsterY = 12;
let monsterTargetY = 12;
let monsterBlink = 0;

let bulletY = -1;
let bulletBlink = false;
let bulletTargetY = -1;
let bulletItem: Item | null = null;

let lootY = -1;
let lootItem: Items | null = null;
let secondLootY = -1;
let secondLootItem: Items | null = null;

let frames = 0;
let prevFrameTime: number = 0;

async function frame(time: number) {
    frames++;
    prevFrameTime = time;

    if (frames % 8 == 0) {
        lootBlink = !lootBlink;
    }

    if (win && frames % 16 == 0) {
        winBlink = !winBlink;
    }

    if (playerHealth <= 0) {
        playerHealth = 0;
        playerTurn = false;
    }

    let lootConfirmed = false;
    const item = ITEMS[inventory[selectedSlot]];
    const monster = MONSTERS[MONSTERS_ORDER[currentMonster]];

    if (playerBlink) {
        if (frames % 4 == 0) {
            playerBlink--;
        }
    } else if (monsterBlink) {
        if (frames % 4 == 0) {
            monsterBlink--;
        }
    } else if (missBlink) {
        if (frames % 4 == 0) {
            missBlink--;
        }
    } else if (bulletTargetY !== bulletY) {
        console.log('Bullet animation');
        if (frames % 2 == 0) {
            bulletY += Math.sign(bulletTargetY - bulletY);
        }
        bulletBlink = !bulletBlink;
    } else if (monsterTargetY !== monsterY) {
        console.log('Monster animation');
        if (frames % 3 == 0) {
            monsterY += Math.sign(monsterTargetY - monsterY);
        }
    } else if (targetY !== y) {
        console.log('Player animation');
        if (frames % 3 == 0) {
            y += Math.sign(targetY - y);
        }
    } else if (playerTurn) {
        lootToConfirm = null;
        if (y === lootY) {
            lootToConfirm = lootItem;
            playerTurn = false;
        } else if (y === secondLootY) {
            lootToConfirm = secondLootItem;
            playerTurn = false;
        } else if (Input.isPressed(Input.KeyCode.LEFT)) {
            selectedSlot = (selectedSlot + inventory.length - 1) % inventory.length;
        } else if (Input.isPressed(Input.KeyCode.RIGHT)) {
            selectedSlot = (selectedSlot + 1) % inventory.length;
        } else if (Input.isPressed(Input.KeyCode.UP) && (!monsterAlive || y < monsterY - 5)) {
            targetY = y + 4;
            playerTurn = false;
        } else if (Input.isPressed(Input.KeyCode.DOWN)) {
            targetY = y - 4;
            playerTurn = false;
        } else if (Input.isPressed(Input.KeyCode.SPACE) && (monsterAlive && (y >= monsterY - 5 || item.ranged || item.heal))) {
            if (item.consumable) {
                inventory.splice(selectedSlot, 1);
                selectedSlot = (selectedSlot + inventory.length - 1) % inventory.length;
            }
            if (item.ranged) {
                console.log('Ranged attack')
                bulletY = y + playerY + playerSprite.height;
                bulletTargetY = monsterY + monster.sprite.height;
                bulletItem = item;
            } else if (item.heal) {
                console.log('Heal' + item.heal);
                playerHealth += item.heal;
                playerTurn = false;
            } else {
                console.log('Melee attack');
                damageMonster(item);
                playerTurn = false;
            }
        }
    } else if (lootToConfirm) {
        console.log('Loot confirm');

        if (Input.isPressed(Input.KeyCode.SPACE)) {
            const i = ITEMS[lootToConfirm];
            if (i.instantUse && i.heal) {
                playerHealth += i.heal;
            } else {
                inventory.push(lootToConfirm);
                if (!i.heal) {
                    selectedSlot = inventory.length - 1;
                }
            }

            lootConfirmed = true;
        } else if (Input.isPressed(Input.KeyCode.UP)) {
            targetY = y + 4;
            playerTurn = true;
        } else if (Input.isPressed(Input.KeyCode.DOWN)) {
            targetY = y - 4;
            playerTurn = true;
        }
    } else if (monsterAlive) { // Monster turn
        console.log('Monster turn');
        if (monster.health <= 0) {
            if (monsterBlink === 0) { // wait blink animation finish
                monsterAlive = false;

                const lootTable = { ...monster.lootTable };
                for (const item of inventory) {
                    lootTable[item] = 0;
                }
                const drop = weightedChoice(lootTable);
                lootY = monsterY - 1;
                lootItem = drop;

                if (drop == null) {
                    spawnNextMonster();
                } else {
                    lootTable[drop] = 0;
                    const rnd = Math.random();
                    if (rnd < monster.secondLootChance) {
                        secondLootY = monsterY + 7;
                        secondLootItem = weightedChoice(lootTable);
                    }
                }
                playerTurn = true;
            }
        } else if (monster.ranged) {
            const retreat = randBool() && monsterY - y < 12;
            if (retreat) {
                monsterTargetY = monsterY + 4;
                playerTurn = true;
            } else {
                bulletY = monsterY + monster.sprite.height;
                bulletTargetY = y + playerY + playerSprite.height;
            }
        } else if (y < monsterY - 5) {
            monsterTargetY = monsterY - 4;
            playerTurn = true;
        } else {
            damagePlayer(monster);
            playerTurn = true;
        }
    } else { // return control to player
        playerTurn = true;
    }

    if (y === targetY && (lootItem || secondLootItem) && lootY < y - 10) {
        lootConfirmed = true;
    }
    if (lootConfirmed) {
        lootY = -1;
        secondLootY = -1;
        lootItem = null;
        secondLootItem = null;
        spawnNextMonster();
        playerTurn = true;
    }

    if (bulletY > 0 && bulletTargetY === bulletY) {
        bulletTargetY = bulletY = -1;
        bulletBlink = false;
        if (playerTurn) {
            damageMonster(bulletItem ?? item);
            playerTurn = false;
        } else {
            damagePlayer(monster);
            playerTurn = true;
        }
    }

    display.clear();
    display.clear(true);

    if (lootToConfirm) {
        const i = ITEMS[lootToConfirm];
        display.speed = i.heal ?? i.damage;
    } else {
        display.speed = item.heal ?? item.damage;
    }
    display.score = playerHealth;
    display.gameOver = playerHealth <= 0;

    const bgY = y % display.height;
    if (missBlink % 2 == 0 || playerHealth <= 0) {
        display.drawImage(background, 0, bgY);
        display.drawImage(background, 0, bgY - display.height);
    }

    if (playerBlink % 2 == 0) {
        display.drawImage(playerHealth > 0 ? playerSprite : playerDeadSprite, 3, display.height - playerSprite.height - playerY);
    }

    if (monsterAlive && monsterBlink % 2 == 0 && monster) {
        display.drawImage(monster.sprite, 3, display.height + (y - monsterY) - monster.sprite.height - playerY);
    }

    if (lootItem && lootItem !== lootToConfirm) {
        display.drawImage(ITEMS[lootItem].sprite, 3, display.height + (y - lootY) - ITEMS[lootItem].sprite.height - playerY);
    }

    if (secondLootItem && secondLootItem !== lootToConfirm) {
        display.drawImage(ITEMS[secondLootItem].sprite, 3, display.height + (y - secondLootY) - ITEMS[secondLootItem].sprite.height - playerY);
    }

    if (bulletY > 0) {
        display.setPixel(display.width >> 1, display.height + (y - bulletY), bulletBlink);
    }

    if (lootToConfirm) {
        if (lootBlink) {
            display.drawImage(ITEMS[lootToConfirm].sprite, 0, 0, true);
        }
    } else {
        display.drawImage(item.sprite, 0, 0, true);
    }

    if (winBlink) {
        display.fillRect(2, 0, winScreen.width, winScreen.height, false);
        display.drawImage(winScreen, 2, 0);
    }

    display.update();
}

function spawnNextMonster() {
    currentMonster++;
    monsterAlive = MONSTERS[MONSTERS_ORDER[currentMonster]] != null;
    if (monsterAlive) {
        MONSTERS[MONSTERS_ORDER[currentMonster]].health = MONSTERS[MONSTERS_ORDER[currentMonster]].maxHealth;
        monsterY = y + 20;
        monsterTargetY = y + 13;
    } else {
        win = true;
    }
}

function damageMonster(item: Item) {
    if (!monsterAlive) return;
    const rnd = Math.random();
    console.log(`Attack for ${item.damage}, success: ${rnd.toFixed(1)} < ${item.accuracy}`);
    if (rnd < item.accuracy) {
        MONSTERS[MONSTERS_ORDER[currentMonster]].health -= item.damage;
        monsterBlink = 6;
        console.log(`Monster HP: ${MONSTERS[MONSTERS_ORDER[currentMonster]].health}`);
    } else {
        missBlink = 6;
    }
}

function damagePlayer(monster: Monster) {
    const rnd = Math.random();
    console.log(`Monster attack for ${monster.damage}, success: ${rnd.toFixed(1)} < ${monster.accuracy}`);

    if (rnd < monster.accuracy) {
        playerHealth -= monster.damage;
        playerBlink = 6;
    } else {
        missBlink = 6;
    }
}

const setup = () => {
    display = new BrickDisplay();
    display.init();

    loadData(spritesheet);
    spawnNextMonster();
}

export default gameLoop(setup, frame);