#include "DungeonModel.h"
#ifndef LV_ABS
#define LV_ABS(x) ((x) > 0 ? (x) : -(x))
#endif
namespace PocketDungeon {

uint32_t DungeonModel::nextRandom() {
    seed = seed * 1103515245u + 12345u;
    return (seed >> 16u) & 0x7FFFu;
}

void DungeonModel::reset(uint32_t seedValue, bool tutorials) {
    seed = seedValue;
    state = DungeonState {};
    state.floor = tutorials ? 0 : 2;
    state.hp = 5;
    state.gold = 0;
    state.gameOver = false;
    generateFloor();
}

void DungeonModel::clearAllTiles() {
    for (uint8_t y = 0; y < DUNGEON_ROWS; ++y)
        for (uint8_t x = 0; x < DUNGEON_COLS; ++x)
            state.tiles[y][x] = Tile::Floor;
    for (uint8_t x = 0; x < DUNGEON_COLS; ++x) { state.tiles[0][x] = Tile::Wall; state.tiles[DUNGEON_ROWS-1][x] = Tile::Wall; }
    for (uint8_t y = 0; y < DUNGEON_ROWS; ++y) { state.tiles[y][0] = Tile::Wall; state.tiles[y][DUNGEON_COLS-1] = Tile::Wall; }
}

void DungeonModel::addEnemy(EntityType type, GameKit::GridPos pos, int8_t hp) {
    if (state.entityCount >= MAX_ENTITIES) return;
    state.entities[state.entityCount++] = Entity { type, pos, hp, true };
}

void DungeonModel::generateTutorialFloor(uint8_t tId) {
    clearAllTiles();
    state.entityCount = 0;
    state.renderRows = TUTORIAL_ROWS;
    for (uint8_t y = TUTORIAL_ROWS + 1; y < DUNGEON_ROWS; ++y)
        for (uint8_t x = 0; x < DUNGEON_COLS; ++x)
            state.tiles[y][x] = Tile::Wall;
    state.playerPos = {1, 1};
    if (tId == 0) {
        state.tiles[1][DUNGEON_COLS - 2] = Tile::Stairs;
    } else {
        state.tiles[1][3] = Tile::Treasure;
        state.tiles[1][DUNGEON_COLS - 2] = Tile::Stairs;
        addEnemy(EntityType::Bat, {5, 1}, 1);
    }
}

void DungeonModel::generateFloor() {
    if (state.floor < 2) { generateTutorialFloor(static_cast<uint8_t>(state.floor)); return; }
    state.renderRows = DUNGEON_ROWS;
    for (uint8_t y = 0; y < DUNGEON_ROWS; ++y)
        for (uint8_t x = 0; x < DUNGEON_COLS; ++x) {
            const bool edge = (x == 0 || y == 0 || x == DUNGEON_COLS - 1 || y == DUNGEON_ROWS - 1);
            state.tiles[y][x] = edge ? Tile::Wall : Tile::Floor;
        }
    state.playerPos = {1, 1};
    state.entityCount = 0;
    state.tiles[2][4] = Tile::Wall;
    state.tiles[3][4] = Tile::Wall;
    state.tiles[5][2 + (state.floor % 2)] = Tile::Treasure;
    state.tiles[5][7] = Tile::Stairs;
    if (state.floor > 2) {
        auto x = static_cast<uint8_t>(2 + (nextRandom() % 5));
        auto y = static_cast<uint8_t>(1 + (nextRandom() % 5));
        if (!(x == 1 && y == 1) && !(x == 7 && y == 5)) state.tiles[y][x] = Tile::Wall;
    }
    uint16_t realFloor = state.floor - 1;
    addEnemy(EntityType::Slime, {5, 2}, 1 + static_cast<int8_t>(realFloor / 3));
    addEnemy(EntityType::Bat, {3, 5}, 1);
    if (realFloor >= 3) addEnemy(EntityType::Slime, {7, 4}, 2);
}

int DungeonModel::findEnemyAt(GameKit::GridPos pos) const {
    for (uint8_t i = 0; i < state.entityCount; ++i) {
        const auto& e = state.entities[i];
        if (e.alive && e.pos == pos) return i;
    }
    return -1;
}

bool DungeonModel::isWalkable(GameKit::GridPos pos) const {
    if (pos.x < 0 || pos.y < 0 || pos.x >= DUNGEON_COLS || pos.y >= DUNGEON_ROWS) return false;
    return state.tiles[pos.y][pos.x] != Tile::Wall;
}

MoveResult DungeonModel::movePlayer(int dx, int dy) {
    if (state.gameOver) return MoveResult::Dead;
    GameKit::GridPos target { static_cast<int16_t>(state.playerPos.x + dx), static_cast<int16_t>(state.playerPos.y + dy) };
    if (!isWalkable(target)) return MoveResult::Blocked;
    const int enemyIdx = findEnemyAt(target);
    if (enemyIdx >= 0) {
        Entity& enemy = state.entities[enemyIdx];
        enemy.hp -= 1;
        if (enemy.hp <= 0) { enemy.alive = false; state.gold += enemy.type == EntityType::Bat ? 2 : 1; }
        if (!isTutorial()) moveMonsters();
        if (state.hp <= 0) { state.gameOver = true; return MoveResult::Dead; }
        return MoveResult::Attacked;
    }
    state.playerPos = target;
    MoveResult result = MoveResult::Moved;
    Tile& tile = state.tiles[target.y][target.x];
    if (tile == Tile::Treasure) {
        state.gold += 5;
        tile = Tile::Floor;
        result = MoveResult::Treasure;
    } else if (tile == Tile::Stairs) {
        state.floor += 1;
        if (state.floor >= 2) {
            if (state.floor - 1 > bestFloor) bestFloor = state.floor - 1;
            if (state.gold > bestGold) bestGold = state.gold;
        }
        generateFloor();
        return MoveResult::Stairs;
    }
    if (!isTutorial()) moveMonsters();
    if (state.hp <= 0) { state.gameOver = true; return MoveResult::Dead; }
    if (state.floor >= 2) {
        if (state.floor - 1 > bestFloor) bestFloor = state.floor - 1;
        if (state.gold > bestGold) bestGold = state.gold;
    }
    return result;
}

void DungeonModel::moveMonsters() {
    for (uint8_t i = 0; i < state.entityCount; ++i) {
        Entity& enemy = state.entities[i];
        if (!enemy.alive) continue;
        const int16_t dx = state.playerPos.x - enemy.pos.x;
        const int16_t dy = state.playerPos.y - enemy.pos.y;
        if ((dx == 0 && (dy == 1 || dy == -1)) || (dy == 0 && (dx == 1 || dx == -1))) { state.hp -= 1; continue; }
        GameKit::GridPos target = enemy.pos;
        if (enemy.type == EntityType::Bat && (nextRandom() % 3 == 0)) {
            const int dirs[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
            const int* dir = dirs[nextRandom() % 4];
            target.x += dir[0]; target.y += dir[1];
        } else if (LV_ABS(dx) > LV_ABS(dy)) {
            target.x += dx > 0 ? 1 : -1;
        } else if (dy != 0) {
            target.y += dy > 0 ? 1 : -1;
        }
        if (target == state.playerPos) state.hp -= 1;
        else if (isWalkable(target) && findEnemyAt(target) < 0) enemy.pos = target;
    }
}

} // namespace PocketDungeon
