-- HousingDecorGuide/Core/HDG_AutoLayout.lua
-- Pure topology placement: BFS-walk a GIVEN connection graph onto a unit grid.
-- No guessing of who connects to whom — the caller (canvas) builds the
-- connections list by matching shared doorIDs across rooms (each connecting
-- door has the SAME doorID in both rooms' pin frames per Blizzard's API).
--
-- Input:
--   {
--     rooms       = { [id] = { shape, occupiedCardinals } },
--     connections = { { fromID, toID, fromDir, toDir }, ... },
--   }
--
-- Output:
--   { layout = { [id] = { cell = {x, y} } }, connections = <input.connections> }
--
-- Convention: +x East, +y South. North is -y. Entry seeded at (0, 0); the
-- canvas anchors host at the bottom and renders (x, -y), so Entry sits at
-- canvas south and rooms fan north (upward).

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

local DIR_DELTA = { N = { 0, -1 }, E = { 1, 0 }, S = { 0, 1 }, W = { -1, 0 } }

-- Build adjacency from the connection list: { [roomID] = { {to, dir}, ... } }
-- The connection edge is bidirectional — fromID ↔ toID.
local function _buildAdjacency(rooms, connections)
    local adj = {}
    for id in pairs(rooms or {}) do adj[id] = {} end
    for _, c in ipairs(connections or {}) do
        if adj[c.fromID] then table.insert(adj[c.fromID], { to = c.toID,   dir = c.fromDir }) end
        if adj[c.toID]   then table.insert(adj[c.toID],   { to = c.fromID, dir = c.toDir   }) end
    end
    return adj
end

-- Seed selection: prefer the room flagged isBase by C_HousingLayout.IsBaseRoom
-- (deterministic), then shape=="entry" (locale-independent fallback for
-- captures predating isBase tracking), then the most-connected room (densest
-- hub gives the most stable BFS spread). Alpha tiebreak throughout.
function M._pickSeed(rooms, connections)
    if not rooms or not next(rooms) then return nil end
    for id, r in pairs(rooms) do
        if r.isBase then return id end
    end
    for id, r in pairs(rooms) do
        if r.shape == "entry" then return id end
    end
    local count = {}
    for _, c in ipairs(connections or {}) do
        count[c.fromID] = (count[c.fromID] or 0) + 1
        count[c.toID]   = (count[c.toID]   or 0) + 1
    end
    local bestID, bestCount = nil, -1
    for id in pairs(rooms) do
        local n = count[id] or 0
        if n > bestCount or (n == bestCount and (bestID == nil or id < bestID)) then
            bestID, bestCount = id, n
        end
    end
    return bestID
end

function M.compute(input)
    local result = { layout = {}, connections = {} }
    if type(input) ~= "table" then return result end
    local rooms       = input.rooms or {}
    local connections = input.connections or {}
    if not next(rooms) then return result end

    result.connections = connections
    local adj     = _buildAdjacency(rooms, connections)
    local placed  = {}
    local cellMap = {}

    local function _place(id, x, y)
        result.layout[id] = { cell = { x = x, y = y } }
        placed[id] = true
        cellMap[x .. "," .. y] = id
    end

    local function _bfsFrom(rootID, rootX, rootY)
        _place(rootID, rootX, rootY)
        local queue, head = { rootID }, 1
        while head <= #queue do
            local curID = queue[head]; head = head + 1
            local cell  = result.layout[curID].cell
            for _, edge in ipairs(adj[curID] or {}) do
                if not placed[edge.to] then
                    local d = DIR_DELTA[edge.dir]
                    if d then
                        local dx, dy = d[1], d[2]
                        local nx, ny = cell.x + dx, cell.y + dy
                        -- Slide along the same axis if a prior placement
                        -- already took the immediate cell (rare in trees).
                        while cellMap[nx .. "," .. ny] do
                            nx, ny = nx + dx, ny + dy
                        end
                        _place(edge.to, nx, ny)
                        queue[#queue + 1] = edge.to
                    end
                end
            end
        end
    end

    -- Main component
    local seedID = M._pickSeed(rooms, connections)
    if seedID then _bfsFrom(seedID, 0, 0) end

    -- Disconnected components: each gets its own column to the east of the
    -- main component, with a 2-cell gutter so they read as separate clusters.
    local function _nextFreeX()
        local maxX = 0
        for _, info in pairs(result.layout) do
            if info.cell.x > maxX then maxX = info.cell.x end
        end
        return maxX + 2
    end
    local allIDs = {}
    for id in pairs(rooms) do allIDs[#allIDs + 1] = id end
    table.sort(allIDs)
    for _, id in ipairs(allIDs) do
        if not placed[id] then _bfsFrom(id, _nextFreeX(), 0) end
    end

    return result
end

if privateDB then privateDB.HDG_AutoLayout = M end
return M
