#pragma once

#include <cstdint>
#include <vector>
#include <string>
#include <unordered_map>
#include <chrono>
#include <flecs.h>
#include <Ogre.h>

/**
 * @brief Cell flags for building geometry
 * 
 * Each cell in the 3D grid can have these features:
 * - Floor/Ceiling
 * - External walls (4 directions)
 * - External doors/windows (4 directions each)
 * - Internal walls (4 directions)
 * - Internal doors/windows (4 directions each)
 */
namespace CellFlags {
    constexpr uint64_t Floor        = 1ULL << 0;
    constexpr uint64_t Ceiling      = 1ULL << 1;
    // External walls
    constexpr uint64_t WallXNeg     = 1ULL << 2;   // Wall on -X side
    constexpr uint64_t WallXPos     = 1ULL << 3;   // Wall on +X side
    constexpr uint64_t WallZPos     = 1ULL << 4;   // Wall on +Z side
    constexpr uint64_t WallZNeg     = 1ULL << 5;   // Wall on -Z side
    // External doors
    constexpr uint64_t DoorXNeg     = 1ULL << 6;
    constexpr uint64_t DoorXPos     = 1ULL << 7;
    constexpr uint64_t DoorZPos     = 1ULL << 8;
    constexpr uint64_t DoorZNeg     = 1ULL << 9;
    // External windows
    constexpr uint64_t WindowXNeg   = 1ULL << 10;
    constexpr uint64_t WindowXPos   = 1ULL << 11;
    constexpr uint64_t WindowZPos   = 1ULL << 12;
    constexpr uint64_t WindowZNeg   = 1ULL << 13;
    // Internal walls
    constexpr uint64_t IntWallXNeg  = 1ULL << 14;
    constexpr uint64_t IntWallXPos  = 1ULL << 15;
    constexpr uint64_t IntWallZPos  = 1ULL << 16;
    constexpr uint64_t IntWallZNeg  = 1ULL << 17;
    // Internal doors
    constexpr uint64_t IntDoorXNeg  = 1ULL << 18;
    constexpr uint64_t IntDoorXPos  = 1ULL << 19;
    constexpr uint64_t IntDoorZPos  = 1ULL << 20;
    constexpr uint64_t IntDoorZNeg  = 1ULL << 21;
    // Internal windows
    constexpr uint64_t IntWindowXNeg = 1ULL << 22;
    constexpr uint64_t IntWindowXPos = 1ULL << 23;
    constexpr uint64_t IntWindowZPos = 1ULL << 24;
    constexpr uint64_t IntWindowZNeg = 1ULL << 25;
    
    // Masks
    constexpr uint64_t AllExternalWalls = WallXNeg | WallXPos | WallZPos | WallZNeg;
    constexpr uint64_t AllInternalWalls = IntWallXNeg | IntWallXPos | IntWallZPos | IntWallZNeg;
    constexpr uint64_t AllWalls = AllExternalWalls | AllInternalWalls;
    constexpr uint64_t AllDoors = DoorXNeg | DoorXPos | DoorZPos | DoorZNeg | 
                                  IntDoorXNeg | IntDoorXPos | IntDoorZPos | IntDoorZNeg;
    constexpr uint64_t AllWindows = WindowXNeg | WindowXPos | WindowZPos | WindowZNeg |
                                    IntWindowXNeg | IntWindowXPos | IntWindowZPos | IntWindowZNeg;
    
    // Combined masks for corners (walls + doors + windows in each direction)
    constexpr uint64_t AllXNeg = WallXNeg | DoorXNeg | WindowXNeg;
    constexpr uint64_t AllXPos = WallXPos | DoorXPos | WindowXPos;
    constexpr uint64_t AllZPos = WallZPos | DoorZPos | WindowZPos;
    constexpr uint64_t AllZNeg = WallZNeg | DoorZNeg | WindowZNeg;
    constexpr uint64_t AllIntXNeg = IntWallXNeg | IntDoorXNeg | IntWindowXNeg;
    constexpr uint64_t AllIntXPos = IntWallXPos | IntDoorXPos | IntWindowXPos;
    constexpr uint64_t AllIntZPos = IntWallZPos | IntDoorZPos | IntWindowZPos;
    constexpr uint64_t AllIntZNeg = IntWallZNeg | IntDoorZNeg | IntWindowZNeg;
}

/**
 * @brief A single cell in the 3D grid
 */
struct Cell {
    int x = 0, y = 0, z = 0;
    uint64_t flags = 0;
    
    // Generate unique key for this cell
    int64_t getKey() const {
        // Support -1024 to 1024 range for each axis
        return (int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z;
    }
    
    bool hasFlag(uint64_t flag) const { return (flags & flag) != 0; }
    void setFlag(uint64_t flag) { flags |= flag; }
    void clearFlag(uint64_t flag) { flags &= ~flag; }
    void toggleFlag(uint64_t flag) { flags ^= flag; }
};

/**
 * @brief Furniture placement in a cell
 */
struct FurnitureCell {
    int x = 0, y = 0, z = 0;
    std::vector<std::string> tags;
    std::string furnitureType;
    int rotation = 0; // 0-3, representing 0, 90, 180, 270 degrees
    
    int64_t getKey() const {
        return (int64_t)x + 2048 * (int64_t)y + 2048 * 2048 * (int64_t)z;
    }
};

/**
 * @brief Cell grid for procedural building generation
 * 
 * This component stores a sparse 3D grid of cells that define
 * building geometry (walls, floors, doors, windows).
 * 
 * Used by: House/Lot generation, dungeon generation
 */
struct CellGridComponent {
    // Grid dimensions (in cells)
    int width = 10;   // X dimension
    int height = 1;   // Y dimension (floors)
    int depth = 10;   // Z dimension
    
    // Cell size in world units
    float cellSize = 4.0f;
    float cellHeight = 4.0f;
    
    // The cell data (sparse storage)
    std::vector<Cell> cells;
    std::vector<FurnitureCell> furnitureCells;
    
    // Generation script (Lua or custom format)
    std::string generationScript;
    
    // Texture rectangle names for different parts (from ProceduralTexture)
    std::string floorRectName;
    std::string ceilingRectName;
    std::string extWallRectName;
    std::string intWallRectName;
    // Frame texture rectangles
    std::string extDoorFrameRectName;
    std::string intDoorFrameRectName;
    std::string extWindowFrameRectName;
    std::string intWindowFrameRectName;
    
    // Roof texture rectangles
    std::string roofTopRectName;
    std::string roofSideRectName;
    
    // Physics properties for generated colliders
    float friction = 0.5f;
    
    // Dirty flag - triggers rebuild
    bool dirty = true;
    unsigned int version = 0;
    
    // Find cell at position (returns nullptr if not found)
    Cell* findCell(int x, int y, int z);
    const Cell* findCell(int x, int y, int z) const;
    
    // Get or create cell at position
    Cell& getOrCreateCell(int x, int y, int z);
    
    // Remove cell at position
    void removeCell(int x, int y, int z);
    
    // Find furniture cell
    FurnitureCell* findFurnitureCell(int x, int y, int z);
    
    // Get or create furniture cell
    FurnitureCell& getOrCreateFurnitureCell(int x, int y, int z);
    
    // Remove furniture cell
    void removeFurnitureCell(int x, int y, int z);
    
    // Clear all cells
    void clear();
    
    // Mark for rebuild
    void markDirty() { dirty = true; version++; }
    
    // Convert local cell position to world position
    Ogre::Vector3 cellToWorld(int x, int y, int z) const;
    
    // Convert world position to cell coordinates
    void worldToCell(const Ogre::Vector3& worldPos, int& x, int& y, int& z) const;
    
    // Get bit name for editor display
    static const char* getFlagName(uint64_t flag);
    static uint64_t getFlagByName(const std::string& name);
    static std::vector<std::pair<uint64_t, const char*>> getAllFlags();
};

/**
 * @brief Room definition within a cell grid
 * 
 * Rooms are rectangular areas that can be connected by doors.
 * This is the ECS version of the Lua room() function.
 */
struct RoomComponent {
    // Room bounds (in cell coordinates)
    // A room from (minX, minZ) to (maxX-1, maxZ-1) inclusive
    int minX = 0, minY = 0, minZ = 0;
    int maxX = 1, maxY = 1, maxZ = 1;  // exclusive max (size = max - min)
    
    // Room name/tag for furniture placement rules
    std::string roomType; // e.g., "bedroom", "kitchen", "hallway"
    std::vector<std::string> tags;
    
    // Generation flags
    bool createFloor = true;    // Create floor cells
    bool createCeiling = true;  // Create ceiling cells
    bool createInteriorWalls = true;  // Create iwallx-/+, iwallz-/+ around the room
    bool createWindows = false; // Convert exterior-facing walls to windows
    bool fillRoomWithFurniture = false; // Automatically place furniture based on tags
    unsigned int furnitureSeed = 42;    // Seed for deterministic furniture placement
    float furnitureYOffset = 0.05f;     // Y offset for all furniture in this room
    
    // Dirty flag - triggers regeneration of cell grid
    bool dirty = true;
    
    void markDirty() { dirty = true; }
    
    // Helper to get size
    int getSizeX() const { return maxX - minX; }
    int getSizeY() const { return maxY - minY; }
    int getSizeZ() const { return maxZ - minZ; }
    
    // Check if a cell position is inside this room
    bool contains(int x, int y, int z) const {
        return x >= minX && x < maxX && 
               y >= minY && y < maxY && 
               z >= minZ && z < maxZ;
    }
    
    // Check if a cell is on the room edge (for wall placement)
    bool isOnEdge(int x, int z) const {
        return x == minX || x == maxX - 1 || z == minZ || z == maxZ - 1;
    }
    
    // Get the side of the room this cell is on (0=Z-, 1=Z+, 2=X-, 3=X+, -1=not on edge)
    int getEdgeSide(int x, int z) const {
        if (!isOnEdge(x, z)) return -1;
        if (z == minZ) return 0;      // Z- (north)
        if (z == maxZ - 1) return 1;  // Z+ (south)
        if (x == minX) return 2;      // X- (west)
        if (x == maxX - 1) return 3;  // X+ (east)
        return -1;
    }
    
    // Persistent unique ID for this room (survives save/load)
    // If empty, will be auto-generated during serialization
    std::string persistentId;
    
    // Connected room persistent IDs (bidirectional connections)
    // Using persistent IDs instead of entity IDs because entity IDs change on save/load
    std::vector<std::string> connectedRoomIds;
    
    // Exit doors for outside-facing walls (0=Z-, 1=Z+, 2=X-, 3=X+)
    // An exit is always a door placed on a wall that faces outside (not connected to another room)
    bool exits[4] = { false, false, false, false };  // Z-, Z+, X-, X+
    
    // Generate a persistent ID if one doesn't exist
    void ensurePersistentId(flecs::entity_t entityId = 0) {
        if (persistentId.empty()) {
            // Generate unique ID based on timestamp + random + optional entity ID
            auto now = std::chrono::high_resolution_clock::now();
            auto nanos = std::chrono::duration_cast<std::chrono::nanoseconds>(
                now.time_since_epoch()).count();
            persistentId = "room_" + std::to_string(nanos);
            if (entityId != 0) {
                persistentId += "_" + std::to_string(entityId);
            }
        }
    }
    
    // Add a connection to another room (bidirectional, by persistent ID)
    void addConnection(const std::string& otherRoomId) {
        // Check if not already connected
        for (const auto& id : connectedRoomIds) {
            if (id == otherRoomId) return;
        }
        connectedRoomIds.push_back(otherRoomId);
    }
    
    // Remove a connection to another room
    void removeConnection(const std::string& otherRoomId) {
        connectedRoomIds.erase(
            std::remove(connectedRoomIds.begin(), connectedRoomIds.end(), otherRoomId),
            connectedRoomIds.end()
        );
    }
    
    // Check if connected to a room (by persistent ID)
    bool isConnectedTo(const std::string& otherRoomId) const {
        for (const auto& id : connectedRoomIds) {
            if (id == otherRoomId) return true;
        }
        return false;
    }
    
    // Helper to set all exits at once
    void setAllExits(bool value) {
        for (int i = 0; i < 4; i++) {
            exits[i] = value;
        }
    }
    
    // Helper to set a specific exit
    void setExit(int side, bool value) {
        if (side >= 0 && side < 4) {
            exits[side] = value;
        }
    }
};

/**
 * @brief Room exits - defines external exits from a room
 * 
 * This replaces the Lua create_exit0/1/2/3() functions.
 * Creates external walls/doors/windows on the room edges that face outside.
 */
/**
 * @brief Clear Area - clears cells in a region before room generation
 * 
 * This replaces the Lua clear_area() and clear_furniture_area() functions.
 * It clears all cells within the specified bounds before any room generation happens.
 * This component is processed first in the RoomLayoutSystem.
 */
struct ClearAreaComponent {
    // Area bounds (in cell coordinates, inclusive min, exclusive max)
    int minX = 0, minY = 0, minZ = 0;
    int maxX = 1, maxY = 1, maxZ = 1;
    
    // What to clear
    bool clearCells = true;           // Clear cell flags (walls, floors, etc.)
    bool clearFurniture = true;       // Clear furniture placements
    bool clearRoofs = false;          // Clear roof definitions (children with RoofComponent)
    bool clearRooms = false;          // Remove existing room entities (children with RoomComponent)
    
    // Processed flag - cleared each time dirty is set
    bool processed = false;
    
    // Dirty flag - clear again when true
    bool dirty = true;
    
    void markDirty() { dirty = true; }
    
    // Helper to set bounds
    void setBounds(int x1, int z1, int x2, int z2, int y = 0) {
        minX = x1; minZ = z1; minY = y;
        maxX = x2; maxZ = z2; maxY = y + 1;
    }
    
    // Helper to get size
    int getSizeX() const { return maxX - minX; }
    int getSizeY() const { return maxY - minY; }
    int getSizeZ() const { return maxZ - minZ; }
};

/**
 * @brief Roof definition for a lot
 */
struct RoofComponent {
    enum Type {
        Flat = 0,
        Normal = 1,
        Normal2 = 2,
        Cone = 3,
        Cylinder = 4
    };
    
    Type type = Flat;
    
    // Position (in cell coordinates)
    int posX = 0, posY = 0, posZ = 0;
    
    // Size (in cells)
    int sizeX = 1, sizeZ = 1;
    
    // Offset from cell position (world units)
    float offsetX = 0.0f, offsetY = 0.0f, offsetZ = 0.0f;
    
    // Height parameters
    float baseHeight = 0.5f;
    float maxHeight = 0.5f;
    
    // Dirty flag - triggers rebuild when changed
    bool dirty = true;
    
    void markDirty() { dirty = true; }
};

/**
 * @brief Lot component - represents a single house/building lot
 * 
 * A lot contains:
 * - Cell grid (walls, floors, etc.)
 * - Room definitions
 * - Roof definitions
 * - Furniture placement rules
 */
struct LotComponent {
    // Lot dimensions (in cells)
    int width = 10;
    int depth = 10;
    
    // Elevation offset from district
    float elevation = 0.0f;
    
    // Rotation around district center (degrees)
    float angle = 0.0f;
    
    // Position offset (for manual placement)
    float offsetX = 0.0f, offsetZ = 0.0f;
    
    // Template name if this lot was created from a template
    std::string templateName;
    
    // Procedural material for lot base (optional - falls back to District, then Town)
    flecs::entity proceduralMaterialEntity;
    std::string proceduralMaterialEntityId; // For serialization
    
    // Texture rectangle name from ProceduralTexture for UV mapping
    std::string textureRectName;
    
    // Physics properties for generated colliders
    float friction = 0.5f;

    // Dirty flag
    bool dirty = true;
    
    void markDirty() { dirty = true; }
};

/**
 * @brief District component - contains multiple lots arranged in a circle
 */
struct DistrictComponent {
    // District radius (distance from center to lots)
    float radius = 50.0f;
    
    // Elevation offset
    float elevation = 0.0f;
    
    // Height of district base (for plaza)
    float height = 0.2f;
    
    // Is this a plaza (circular base)?
    bool isPlaza = false;
    
    // Lot templates for this district
    std::vector<std::string> lotTemplateNames;
    
    // Procedural material for plaza (references entity with ProceduralMaterialComponent)
    flecs::entity proceduralMaterialEntity;
    std::string proceduralMaterialEntityId; // For serialization
    
    // Texture rectangle name from ProceduralTexture for UV mapping
    std::string textureRectName;
    
    // Physics properties for generated colliders
    float friction = 0.5f;
    
    // Dirty flag
    bool dirty = true;
    
    void markDirty() { dirty = true; }
};

/**
 * @brief Town component - top-level container
 */
struct TownComponent {
    std::string townName = "New Town";
    
    // Color rectangles for texture atlas
    struct ColorRect {
        float left = 0.0f, top = 0.0f, right = 1.0f, bottom = 1.0f;
        Ogre::ColourValue color = Ogre::ColourValue::White;
    };
    std::unordered_map<std::string, ColorRect> colorRects;
    
    // Material name (created from color rects)
    std::string materialName;
    
    // Procedural material for town (used by districts and lots)
    flecs::entity proceduralMaterialEntity;
    std::string proceduralMaterialEntityId; // For serialization
    
    // Texture rectangle name from ProceduralTexture for UV mapping
    std::string textureRectName;
    
    // Dirty flag
    bool dirty = true;
    bool materialDirty = true;
    
    void markDirty() { dirty = true; }
    void markMaterialDirty() { materialDirty = true; }
};

/**
 * @brief Furniture template - defines a furniture item
 */
struct FurnitureTemplateComponent {
    std::string templateName;
    std::vector<std::string> tags; // e.g., "bed", "essential", "bedroom"
    
    // Mesh/model info
    std::string meshName;
    std::string materialName;
    
    // Placement rules
    bool requiresWall = false;
    bool requiresFloor = true;
    bool blocksPath = true;
    
    // Offset from cell center
    float offsetX = 0.0f, offsetY = 0.0f, offsetZ = 0.0f;
    
    // Random selection weight
    float weight = 1.0f;
};
