-- HDG_HT_Layout.lua
-- Pure-math bin-packer. Walks ordered widget list; places each in the next
-- available row (3 cols total). Stretches all cards in a row to the tallest
-- minHeight. No anchor chains; cards anchor to parent TOPLEFT only.

local _, privateDB = ...
privateDB.HDG_HT = privateDB.HDG_HT or {}
local Layout = {}
privateDB.HDG_HT.Layout = Layout

local WIDTH_THIRDS = { third = 1, twoThirds = 2, full = 3 }

function Layout.WidthInThirds(name)
    return WIDTH_THIRDS[name] or 1
end

function Layout.WidthInPixels(name, grid)
    local thirds = Layout.WidthInThirds(name)
    local usable = grid.totalWidth - 2 * grid.padding
    local colWidth = (usable - 2 * grid.gutter) / 3
    return thirds * colWidth + (thirds - 1) * grid.gutter
end

-- widgets: ordered list { {id, width, minHeight, growBehavior, ...}, ... }
-- grid:    { totalWidth, padding, gutter,
--            gapFill = "strict" | "greedy" (default "strict") }
-- Returns { [id] = { x, y, width, height } }
--
-- gapFill modes:
--   "strict" — walk in user order; flush row when next widget doesn't fit.
--              Predictable, may leave 1/3 or 2/3 holes. Drag-reorder is 1:1
--              to display order.
--   "greedy" — when a widget doesn't fit the current row, scan AHEAD for a
--              smaller widget that does fit and pull it forward. Resulting
--              row stays full. Display order preserves intent (earlier
--              widgets still place earlier) but later smaller widgets may
--              "jump up" to fill gaps.
function Layout.Compute(widgets, grid)
    local placements = {}
    local x = grid.padding
    local y = grid.padding
    local rowFreeThirds = 3
    local rowMinHeight = 0
    local rowBuffer = {}
    local greedy = (grid.gapFill == "greedy")

    local function flushRow()
        if #rowBuffer == 0 then return end
        for _, w in ipairs(rowBuffer) do
            placements[w.id] = {
                x      = w._x,
                y      = -y,
                width  = Layout.WidthInPixels(w.width, grid),
                height = rowMinHeight,
            }
        end
        y = y + rowMinHeight + grid.gutter
        x = grid.padding
        rowFreeThirds = 3
        rowMinHeight = 0
        rowBuffer = {}
    end

    local function placeInRow(w)
        w._x = x
        rowBuffer[#rowBuffer + 1] = w
        local thirds = Layout.WidthInThirds(w.width)
        x = x + Layout.WidthInPixels(w.width, grid) + grid.gutter
        rowFreeThirds = rowFreeThirds - thirds
        if (w.minHeight or 0) > rowMinHeight then rowMinHeight = w.minHeight end
    end

    local placed = {}  -- greedy-mode bookkeeping (widgets pulled forward)
    for i, w in ipairs(widgets) do
        if not placed[w.id] then
            local thirds = Layout.WidthInThirds(w.width)
            if thirds > rowFreeThirds then
                if greedy then
                    -- Try to fill remaining slots from the rest of the list
                    -- before flushing — keeps the current row fully packed.
                    for j = i + 1, #widgets do
                        local cand = widgets[j]
                        if not placed[cand.id]
                           and Layout.WidthInThirds(cand.width) <= rowFreeThirds then
                            placeInRow(cand)
                            placed[cand.id] = true
                            if rowFreeThirds == 0 then break end
                        end
                    end
                end
                flushRow()
            end
            placeInRow(w)
            placed[w.id] = true
        end
    end
    flushRow()

    return placements
end
