-- HousingDecorGuide/Core/HDG_RoomShapesDB.lua
-- DB2-sourced room shape data.
-- Generated 2026-05-02 from wago.tools build 12.0.5.66741.
--
-- Sources:
--   HouseRoom         https://wago.tools/db2/HouseRoom?build=12.0.5.66741
--   RoomWmoData       https://wago.tools/db2/RoomWmoData?build=12.0.5.66741
--   RoomComponent     https://wago.tools/db2/RoomComponent?build=12.0.5.66741
--   RoomGridLine      https://wago.tools/db2/RoomGridLine?build=12.0.5.66741
--   UiTextureAtlasElement (local DevTools/atlas/data/UiTextureAtlasElement.csv)
--
-- Manual augmentation:
--   circle_daylight / circle_evening doors — no Type=1 RoomComponent rows in DB2;
--   single "N" door is a best-estimate from in-game observation (needs in-game probe).
--   entry atlas — DB2 UiTextureAtlasElementID=0; fallback to Layout_Square_XS_Icon.
--
-- Coordinate system:  X+ = East, Y+ = North. All door positions are local-space
-- (relative to the room's own origin at default orientation).
-- World-space facing = (facing_local + room_rotation) % (2*math.pi).
--
-- facing_local convention (radians, counterclockwise from East):
--   E = 0,  N = math.pi/2,  W = math.pi,  S = 3*math.pi/2

local _, privateDB = ...
local M = {}

-- Grid pitch = 12 yards per cell.
-- Rooms occupy (cells * 12 - 1) yards in each dimension.
-- Door offsets: 1-cell = ±6, 2-cell = ±12, 3-cell = ±18, 4-cell = ±24.

local PI   = math.pi
local PI_2 = math.pi / 2
local PI32 = 3 * math.pi / 2

-- Per-shape data sourced from DB2.
-- Fields:
--   houseroom_id     HouseRoom.ID (primary key in DB2)
--   wmo_id           RoomWmoData.ID (geometry asset)
--   atlas            UiTextureAtlasElement name (layout icon)
--   cells            {width, height} in 12-yard grid cells (best-fit for non-rectangular shapes)
--   bounds_m         {width_yards, depth_yards} from RoomWmoData.BoundingBox (authoritative)
--   budget_cost      HouseRoom.WeightCost (counts toward the layout room budget)
--   localized_name   HouseRoom.Name_lang enUS
--   flags            HouseRoom.Flags (0=large, 1=plot/entry, 4=standard, 6=stairwell, 8=prefab/circle)
--   doors            Per-template door slots.
--                    pos={x,y} = local position from room center (yards)
--                    facing_local = local-space radian (convention above)
--                    ConnectionType=1 doors only (standard connections; 0=blocked/optional excluded)
local SHAPES = {

    -- -------------------------------------------------------------------------
    -- Square Rooms
    -- -------------------------------------------------------------------------

    square_xs = {
        houseroom_id   = 7,
        wmo_id         = 3,
        atlas          = "Layout_Square_XS_Icon",
        cells          = { 1, 1 },
        bounds_m       = { 11.0, 11.0 },
        budget_cost    = 2,
        localized_name = "Square Room (Tiny)",
        flags          = 4,
        doors = {
            { pos = {  6,  0 }, facing_local = 0    },   -- E
            { pos = {  0,  6 }, facing_local = PI_2 },   -- N
            { pos = { -6,  0 }, facing_local = PI   },   -- W
            { pos = {  0, -6 }, facing_local = PI32 },   -- S
        },
    },

    square_s = {
        houseroom_id   = 1,
        wmo_id         = 5,
        atlas          = "Layout_Square_S_Icon",
        cells          = { 2, 2 },
        bounds_m       = { 23.0, 23.0 },
        budget_cost    = 5,
        localized_name = "Square Room (Small)",
        flags          = 4,
        doors = {
            { pos = { 12,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  12 }, facing_local = PI_2 },  -- N
            { pos = { -12,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -12 }, facing_local = PI32 },  -- S
        },
    },

    square_m = {
        houseroom_id   = 11,
        wmo_id         = 160,
        atlas          = "Layout_Square_M_Icon",
        cells          = { 3, 3 },
        bounds_m       = { 35.0, 35.0 },
        budget_cost    = 12,
        localized_name = "Square Room (Medium)",
        flags          = 0,
        doors = {
            { pos = { 18,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  18 }, facing_local = PI_2 },  -- N
            { pos = { -18,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -18 }, facing_local = PI32 },  -- S
        },
    },

    square_l = {
        houseroom_id   = 12,
        wmo_id         = 6,
        atlas          = "Layout_Square_L_Icon",
        cells          = { 4, 4 },
        bounds_m       = { 47.0, 47.0 },
        budget_cost    = 20,
        localized_name = "Square Room (Large)",
        flags          = 0,
        doors = {
            { pos = { 24,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  24 }, facing_local = PI_2 },  -- N
            { pos = { -24,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -24 }, facing_local = PI32 },  -- S
        },
    },

    -- -------------------------------------------------------------------------
    -- Octagon Rooms
    -- -------------------------------------------------------------------------

    octagon_s = {
        houseroom_id   = 14,
        wmo_id         = 164,
        atlas          = "Layout_Octogon_S_Icon",
        cells          = { 2, 2 },
        bounds_m       = { 24.3, 24.3 },
        budget_cost    = 4,
        localized_name = "Octagon Room (Small)",
        flags          = 4,
        doors = {
            { pos = { 12,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  12 }, facing_local = PI_2 },  -- N
            { pos = { -12,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -12 }, facing_local = PI32 },  -- S
        },
    },

    octagon_m = {
        houseroom_id   = 9,
        wmo_id         = 165,
        atlas          = "Layout_Octogon_M_Icon",
        cells          = { 3, 3 },
        bounds_m       = { 36.4, 36.4 },
        budget_cost    = 8,
        localized_name = "Octagon Room (Medium)",
        flags          = 4,
        doors = {
            { pos = { 18,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  18 }, facing_local = PI_2 },  -- N
            { pos = { -18,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -18 }, facing_local = PI32 },  -- S
        },
    },

    octagon_l = {
        houseroom_id   = 15,
        wmo_id         = 166,
        atlas          = "Layout_Octogon_L_Icon",
        cells          = { 4, 4 },
        bounds_m       = { 48.3, 48.3 },
        budget_cost    = 16,
        localized_name = "Octagon Room (Large)",
        flags          = 0,
        doors = {
            { pos = { 24,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  24 }, facing_local = PI_2 },  -- N
            { pos = { -24,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -24 }, facing_local = PI32 },  -- S
        },
    },

    -- -------------------------------------------------------------------------
    -- Special-Shape Rooms
    -- -------------------------------------------------------------------------

    -- L-Shape: bounding box ~17×17; only N and E doors (no W or S door in DB2).
    -- Cells listed as {2,2} for canvas rendering; actual shape occupies lower-right
    -- quadrant of that bounding box.
    l_shape = {
        houseroom_id   = 8,
        wmo_id         = 163,
        atlas          = "Layout_L-Shape_S_Icon",
        cells          = { 2, 2 },
        bounds_m       = { 17.0, 17.0 },
        budget_cost    = 3,
        localized_name = "L-Shaped Room",
        flags          = 4,
        doors = {
            { pos = { 12,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  12 }, facing_local = PI_2 },  -- N
            -- W and S doors are absent from DB2 RoomComponent for this shape.
        },
    },

    -- T-Shape: bounding box ~17×23; W door is asymmetric (at ±6, not ±12).
    t_shape = {
        houseroom_id   = 6,
        wmo_id         = 162,
        atlas          = "Layout_T-Shape_S_Icon",
        cells          = { 2, 2 },
        bounds_m       = { 17.0, 23.0 },
        budget_cost    = 3,
        localized_name = "T-Shaped Room",
        flags          = 4,
        doors = {
            { pos = { 12,   0 }, facing_local = 0    },  -- E (2-cell offset)
            { pos = {  0,  12 }, facing_local = PI_2 },  -- N
            { pos = { -6,   0 }, facing_local = PI   },  -- W (1-cell offset — asymmetric arm)
            { pos = {  0, -12 }, facing_local = PI32 },  -- S
        },
    },

    -- Cross: same bounding box as Square S (23×23) but shaped as a plus/cross.
    cross_shape = {
        houseroom_id   = 13,
        wmo_id         = 161,
        atlas          = "Layout_Cross_S_Icon",
        cells          = { 2, 2 },
        bounds_m       = { 23.0, 23.0 },
        budget_cost    = 4,
        localized_name = "Cross-Shaped Room",
        flags          = 4,
        doors = {
            { pos = { 12,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  12 }, facing_local = PI_2 },  -- N
            { pos = { -12,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -12 }, facing_local = PI32 },  -- S
        },
    },

    -- Hallway: 2×1 bounding box; E/W doors at full 2-cell offset (±12),
    -- N/S doors at 1-cell offset (±6).
    hallway = {
        houseroom_id   = 2,
        wmo_id         = 7,
        atlas          = "Layout_Hallway_S_Icon",
        cells          = { 2, 1 },
        bounds_m       = { 23.0, 11.0 },
        budget_cost    = 3,
        localized_name = "Hallway",
        flags          = 4,
        doors = {
            { pos = { 12,  0 }, facing_local = 0    },  -- E
            { pos = {  0,  6 }, facing_local = PI_2 },  -- N (1-cell because depth=1)
            { pos = { -12, 0 }, facing_local = PI   },  -- W
            { pos = {  0, -6 }, facing_local = PI32 },  -- S
        },
    },

    -- -------------------------------------------------------------------------
    -- Narrow Rooms (Closet, Entry)
    -- Bounding box: 5×11 yards — same geometry. 0.5-cell wide in X axis.
    -- E/W doors at ±3 yards, N/S doors at ±6 yards.
    -- -------------------------------------------------------------------------

    -- DB2 localized name is just "Closet" (no "XS" suffix).
    closet_xs = {
        houseroom_id   = 3,
        wmo_id         = 4,
        atlas          = "Layout_Closet_XS_Icon",
        cells          = { 1, 1 },  -- best-fit; actual width is 0.5 cells (5 yards)
        bounds_m       = { 5.0, 11.0 },
        budget_cost    = 1,
        localized_name = "Closet",
        flags          = 4,
        doors = {
            { pos = {  3,  0 }, facing_local = 0    },  -- E (half-cell)
            { pos = {  0,  6 }, facing_local = PI_2 },  -- N
            { pos = { -3,  0 }, facing_local = PI   },  -- W (half-cell)
            { pos = {  0, -6 }, facing_local = PI32 },  -- S
        },
    },

    -- Entry: identical geometry to Closet; no layout atlas icon in DB2.
    -- Fallback atlas to Layout_Square_XS_Icon (same as original placeholder).
    -- Dual-door topology: S=external (front of house, captured by pin frames);
    -- N=internal (connects Entry to the house interior; NEVER captured by Blizzard
    -- pin frames — HDG_TopologyCapture injects it as implicit-occupied).
    -- RotationDeducer skips kind="external" doors during matching.
    entry = {
        houseroom_id   = 46,
        wmo_id         = 177,
        atlas          = "Layout_Closet_XS_Icon",     -- DB2 AtlasElementID=0 (no native atlas); reuses Closet's icon since Entry is visually identical (per Vamoose, in-game editor confirms)
        cells          = { 1, 1 },
        bounds_m       = { 5.0, 11.0 },
        budget_cost    = 0,
        localized_name = "Entry",
        flags          = 1,
        doors = {
            -- S external door (front of house, captured by pin frames)
            { pos = {  0, -6 }, facing_local = PI32, kind = "external" },
            -- N internal door (NEVER captured by Blizzard pin frames, but topologically real;
            -- HDG_TopologyCapture injects this as implicit-occupied for entry-shaped rooms)
            { pos = {  0,  6 }, facing_local = PI_2, kind = "internal" },
        },
    },

    -- -------------------------------------------------------------------------
    -- Stairwells  (Flags=6)
    -- All 23×23 yards, 2×2 cells, 4 doors.
    -- -------------------------------------------------------------------------

    -- "Stairwell (Left)" in DB2 — atlas = Layout_Staircase_S_Icon
    staircase = {
        houseroom_id   = 10,
        wmo_id         = 8,
        atlas          = "Layout_Staircase_S_Icon",
        cells          = { 2, 2 },
        bounds_m       = { 23.0, 23.0 },
        budget_cost    = 7,
        localized_name = "Stairwell (Left)",
        flags          = 6,
        doors = {
            { pos = { 12,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  12 }, facing_local = PI_2 },  -- N
            { pos = { -12,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -12 }, facing_local = PI32 },  -- S
        },
    },

    -- "Stairwell (Right)" in DB2 — atlas = Layout_Staircase_Mirrored_S_Icon
    staircase_mirror = {
        houseroom_id   = 50,
        wmo_id         = 180,
        atlas          = "Layout_Staircase_Mirrored_S_Icon",
        cells          = { 2, 2 },
        bounds_m       = { 23.0, 23.0 },
        budget_cost    = 7,
        localized_name = "Stairwell (Right)",
        flags          = 6,
        doors = {
            { pos = { 12,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  12 }, facing_local = PI_2 },  -- N
            { pos = { -12,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -12 }, facing_local = PI32 },  -- S
        },
    },

    -- "Stairwell Room (Empty)" in DB2 — atlas = Layout_TallRoom_S_Icon
    -- Note: DB2 localized name is NOT "Tall Room"; the atlas name is misleading.
    tall_room = {
        houseroom_id   = 48,
        wmo_id         = 179,
        atlas          = "Layout_TallRoom_S_Icon",
        cells          = { 2, 2 },
        bounds_m       = { 23.0, 23.0 },
        budget_cost    = 6,
        localized_name = "Stairwell Room (Empty)",
        flags          = 6,
        doors = {
            { pos = { 12,   0 }, facing_local = 0    },  -- E
            { pos = {  0,  12 }, facing_local = PI_2 },  -- N
            { pos = { -12,  0 }, facing_local = PI   },  -- W
            { pos = {  0, -12 }, facing_local = PI32 },  -- S
        },
    },

    -- -------------------------------------------------------------------------
    -- Circle Rooms  (Flags=8)
    -- 4×4 bounding cells, ~48×50 yards.
    -- No Type=1 RoomComponent doors found in DB2 — single-door estimate from
    -- in-game observation. TODO: needs in-game probe to confirm count and facing.
    -- -------------------------------------------------------------------------

    circle_daylight = {
        houseroom_id   = 223,
        wmo_id         = 233,
        atlas          = "Full_Layout_Artisinal_Garden_Daylight",
        cells          = { 4, 4 },
        bounds_m       = { 48.2, 49.6 },
        budget_cost    = 8,
        localized_name = "Daylight Circle Room",
        flags          = 8,
        doors = {
            -- TODO: not in DB2 (no Type=1 RoomComponent rows for WmoID=233); needs in-game probe
            { pos = { 0, 24 }, facing_local = PI_2 },   -- N assumed
        },
    },

    circle_evening = {
        houseroom_id   = 233,
        wmo_id         = 255,
        atlas          = "Full_Layout_Artisinal_Garden_Evening",
        cells          = { 4, 4 },
        bounds_m       = { 48.2, 49.6 },
        budget_cost    = 8,
        localized_name = "Evening Circle Room",
        flags          = 8,
        doors = {
            -- TODO: not in DB2 (no Type=1 RoomComponent rows for WmoID=255); needs in-game probe
            { pos = { 0, 24 }, facing_local = PI_2 },   -- N assumed
        },
    },
}

-- ---------------------------------------------------------------------------
-- Public API
-- ---------------------------------------------------------------------------

--- Returns the full shape record, or nil if unknown.
function M.GetShape(shapeID)
    return SHAPES[shapeID]
end

--- Returns the atlas element name for the layout icon, or nil.
function M.GetAtlas(shapeID)
    local s = SHAPES[shapeID]
    return s and s.atlas
end

--- Returns {width_cells, height_cells} for canvas rendering.
function M.GetCells(shapeID)
    local s = SHAPES[shapeID]
    return s and s.cells or { 1, 1 }
end

--- Returns {width_yards, depth_yards} from RoomWmoData bounding box.
function M.GetBoundsMeters(shapeID)
    local s = SHAPES[shapeID]
    return s and s.bounds_m or { 11.0, 11.0 }
end

--- Returns the door list for this shape (local-space, unrotated template).
--- Each door: { pos={x,y}, facing_local=radians }
function M.GetDoors(shapeID)
    local s = SHAPES[shapeID]
    return s and s.doors or {}
end

--- Returns the HouseRoom.WeightCost for layout budget accounting.
function M.GetBudgetCost(shapeID)
    local s = SHAPES[shapeID]
    return s and s.budget_cost or 1
end

--- Returns the DB2 localized name (enUS).
function M.GetLocalizedName(shapeID)
    local s = SHAPES[shapeID]
    return s and s.localized_name
end

--- Returns the HouseRoom.ID (DB2 primary key).
function M.GetHouseRoomID(shapeID)
    local s = SHAPES[shapeID]
    return s and s.houseroom_id
end

--- Returns our internal shape key for a given DB2 HouseRoom.ID, or nil.
function M.HouseRoomIDToShape(houseRoomID)
    for id, s in pairs(SHAPES) do
        if s.houseroom_id == houseRoomID then return id end
    end
end

--- Returns our internal shape key for a given localized name, or nil.
function M.LocalizedNameToShape(name)
    for id, s in pairs(SHAPES) do
        if s.localized_name == name then return id end
    end
end

--- Returns true if shapeID is a known shape.
function M.IsKnown(shapeID)
    return SHAPES[shapeID] ~= nil
end

--- Returns an iterator over all shapes: for id, shape in M.Each() do ... end
function M.Each()
    return pairs(SHAPES)
end

if privateDB then privateDB.HDG_RoomShapesDB = M end
return M
