--[[ Core/HouseLevel.lua - will it FIT, not just do you own it.

     The score answers one question: do you own the pieces. It has never
     answered the other one: is there room for them. Every house has a decor
     budget, the budget grows with house level, and a blueprint that costs
     more than the ceiling cannot be imported no matter what you own. Measured
     on 2026-08-19: a level-1 house holds 200 exterior / 910 interior, and
     every one of the 20 exteriors in the library costs 231-350. So a new
     player saw "100% buildable" on twenty builds that would refuse to import,
     tried two, and wrote on CurseForge that the addon does not work.

     This turns cost into a house level - "needs house level 7" - which is a
     goal rather than a wall, and tells the player how far off they are.

     Level table from housing.wowdb.com/progression, matching Wowhead's
     datamining. Only budgetType 1 (the main decor budget) is used; it is the
     one the baked `budget` field holds and the one that blocks imports. ]]--

local _, ns = ...
local HL = {}
if ns then ns.HouseLevel = HL end   -- loadable outside WoW for tests

-- level, interior ceiling, exterior ceiling
local LEVELS = {
  {  1,  910, 200 }, {  2, 1155, 200 }, {  3, 1450, 250 }, {  4, 1745, 250 },
  {  5, 2050, 300 }, {  6, 2360, 300 }, {  7, 3180, 350 }, {  8, 3500, 350 },
  {  9, 4340, 350 }, { 10, 4675, 350 }, { 11, 5545, 350 }, { 12, 5975, 350 },
}
local MAX_LEVEL = LEVELS[#LEVELS][1]

local COL = { interior = 2, exterior = 3 }

--- Lowest house level whose ceiling holds `cost` on `side`. nil when nothing
--- does, which with today's table means "over the game's maximum".
function HL.NeededLevel(side, cost)
  local col = COL[side]
  if not col or type(cost) ~= "number" or cost <= 0 then return nil end
  for _, row in ipairs(LEVELS) do
    if row[col] >= cost then return row[1] end
  end
  return nil
end

--- The level a house with these ceilings is at - the highest row whose
--- ceilings the house meets. Used only for the sentence "your house is level
--- N"; the ceilings themselves are what gate an import.
function HL.LevelFor(interiorMax, exteriorMax)
  local best
  for _, row in ipairs(LEVELS) do
    if (interiorMax or 0) >= row[2] and (exteriorMax or 0) >= row[3] then best = row[1] end
  end
  return best
end

------------------------------------------------------------------------
-- The player's own ceilings. They ride on every blueprint-contents reply
-- (budgetInfo.*Budgets[].max), so the first live answer of any kind teaches
-- us the house, and it is kept in SavedVariables so the next session knows
-- before asking. Levels only go up; a stale ceiling can only be too low,
-- which errs towards a warning that Verify then clears.
------------------------------------------------------------------------
local function pickMax(list)
  if type(list) ~= "table" then return nil end
  for _, b in pairs(list) do
    if type(b) == "table" and b.budgetType == 1 and type(b.max) == "number" then
      return b.max
    end
  end
end

--- Feed a live payload's budgetInfo. Returns true if the ceilings changed.
function HL.Record(budgetInfo)
  if type(budgetInfo) ~= "table" or not (ns and ns.db) then return false end
  local i = pickMax(budgetInfo.interiorBudgets)
  local e = pickMax(budgetInfo.exteriorBudgets)
  if not (i or e) then return false end
  local c = ns.db.houseCeilings or {}
  local changed = (i and i ~= c.interior) or (e and e ~= c.exterior)
  c.interior = i or c.interior
  c.exterior = e or c.exterior
  c.at = time()
  ns.db.houseCeilings = c
  return changed and true or false
end

--- { interior = n, exterior = n, level = n } or nil while still unknown.
function HL.Ceilings()
  local c = ns and ns.db and ns.db.houseCeilings
  if not c or not (c.interior or c.exterior) then return nil end
  return { interior = c.interior, exterior = c.exterior,
           level = HL.LevelFor(c.interior, c.exterior) }
end

------------------------------------------------------------------------
-- Verdict for one library entry.
------------------------------------------------------------------------
--- Returns nil when the entry carries no cost we can judge (unbaked), else
---   { side, cost, needLevel, ceiling, houseLevel, fits, text, short }
--- fits: true / false / nil (nil = cost known, player's ceiling not yet)
--- text:  a full sentence for the detail pane
--- short: "Lvl 7" style, for a card
function HL.Verdict(entry)
  local b = entry and entry.budget
  if type(b) ~= "table" then return nil end
  -- A house blueprint carries both sides; judge by whichever is harder.
  local worst
  for side, cost in pairs({ interior = b.interior, exterior = b.exterior }) do
    if type(cost) == "number" and cost > 0 then
      local need = HL.NeededLevel(side, cost)
      local rank = need or (MAX_LEVEL + 1)
      if not worst or rank > worst.rank then
        worst = { side = side, cost = cost, needLevel = need, rank = rank }
      end
    end
  end
  if not worst then return nil end

  local v = { side = worst.side, cost = worst.cost, needLevel = worst.needLevel }
  local c = HL.Ceilings()
  if c then
    v.ceiling = c[v.side]
    v.houseLevel = c.level
    if v.ceiling then v.fits = v.cost <= v.ceiling end
  end

  -- head = the verdict in three words, detail = the numbers behind it.
  -- The pane colours the head and dims the detail; text joins them for
  -- places that take one string (the Import refusal, a card tooltip).
  local sideWord = v.side .. " budget"
  if v.needLevel == nil then
    v.short  = "Over max"
    v.head   = "Over the maximum"
    v.detail = ("%d %s; no house level allows more than %d")
      :format(v.cost, sideWord, LEVELS[#LEVELS][COL[v.side]])
  elseif v.fits == false then
    v.short  = "Lvl " .. v.needLevel
    v.head   = "Needs house level " .. v.needLevel
    v.detail = ("%d %s, your house holds %d%s")
      :format(v.cost, sideWord, v.ceiling,
              v.houseLevel and (" (level " .. v.houseLevel .. ")") or "")
  elseif v.fits == true then
    v.short  = nil                             -- nothing to warn about
    v.head   = "Fits your house"
    v.detail = ("%d of %d %s"):format(v.cost, v.ceiling, sideWord)
  elseif v.needLevel <= 1 then
    -- Every house is at least level 1. Nothing worth a line.
    return v
  else
    v.short  = "Lvl " .. v.needLevel
    v.head   = "Needs house level " .. v.needLevel
    v.detail = ("%d %s; Verify checks it against your house"):format(v.cost, sideWord)
  end
  v.text = v.head .. " - " .. v.detail .. "."
  return v
end

if not ns then return HL end
