-- game_itemtooltip
-- Rich tooltip rendering for the marketplace + general item hovers.
-- Public API consumed by market.lua:
--   modules.game_itemtooltip.toggleMarketHover()
--   modules.game_itemtooltip.newTooltip(tooltipTable)
--
-- Both functions must be safe under nil/garbage input. They MUST NOT
-- throw, because the market UI calls them inside onHoverChange handlers
-- and a raised error would poison every subsequent hover event.

local marketHoverActive = false

-- ---------------------------------------------------------------
-- Helpers
-- ---------------------------------------------------------------

local SEP = "\151"   -- CP1252 em-dash
local DOT = "\149"   -- CP1252 bullet

-- Tier label + ASCII flair. Color codes don't render in g_tooltip's
-- plain-text channel, so we convey rarity with text only.
local TIER_BANNER = {
    common    = nil,
    uncommon  = "[ UNCOMMON ]",
    rare      = "* RARE *",
    epic      = "** EPIC **",
    legendary = "*** LEGENDARY ***",
    mythic    = "**** MYTHIC ****",
}

local function tierBanner(tier)
    if not tier then return nil end
    local key = tostring(tier):lower()
    return TIER_BANNER[key] or ("[ " .. string.upper(tostring(tier)) .. " ]")
end

local function fmtSigned(n)
    n = tonumber(n) or 0
    if n > 0 then return "+" .. n end
    return tostring(n)
end

local function fmtMoneyShort(n)
    n = math.floor(tonumber(n) or 0)
    if n <= 0 then return "0" end
    if n >= 1000000000 then
        local v = n / 1000000000
        if v == math.floor(v) then return math.floor(v) .. "KKK" end
        return string.format("%.1fKKK", v)
    elseif n >= 1000000 then
        local v = n / 1000000
        if v == math.floor(v) then return math.floor(v) .. "KK" end
        return string.format("%.1fKK", v)
    elseif n >= 1000 then
        local v = n / 1000
        if v == math.floor(v) then return math.floor(v) .. "K" end
        return string.format("%.1fK", v)
    end
    return tostring(n)
end

local function fmtDuration(secondsLeft)
    if not secondsLeft or secondsLeft < 0 then return nil end
    local d = math.floor(secondsLeft / 86400)
    local h = math.floor((secondsLeft % 86400) / 3600)
    local m = math.floor((secondsLeft % 3600) / 60)
    if d > 0 then return string.format("%dd %dh", d, h) end
    if h > 0 then return string.format("%dh %dm", h, m) end
    if m > 0 then return string.format("%dm", m) end
    return string.format("%ds", math.max(0, math.floor(secondsLeft)))
end

local LISTING_MODE_LABEL = {
    [1] = "Buy Now",
    [2] = "Auction",
    [3] = "Buy Now or Auction",
}

local function isPositive(v)
    local n = tonumber(v)
    return n and n > 0
end

-- ---------------------------------------------------------------
-- Build the tooltip text block
-- ---------------------------------------------------------------

local function formatTooltip(t)
    if type(t) ~= 'table' then return tostring(t or '') end

    local lines = {}
    local function add(s) if s and s ~= '' then lines[#lines+1] = s end end
    local function rule()
        if #lines > 0 and lines[#lines] ~= "" then
            lines[#lines+1] = string.rep(SEP, 18)
        end
    end

    -- Header: name + count
    local headerName = t.name or t.item_name or t.title or ""
    local count = tonumber(t.count) or 1
    if headerName ~= "" then
        if count > 1 then
            add(headerName .. "  x" .. count)
        else
            add(headerName)
        end
    end

    -- Rarity tier banner (centered text via spaces)
    local banner = tierBanner(t.arcfall_tier or t.tier or t.rarity)
    if banner then add(banner) end

    -- Description / book text / writer
    if t.description and t.description ~= "" then add(t.description) end
    if t.text and t.text ~= "" then add(t.text) end
    if t.writer and t.writer ~= "" then
        add("Written by " .. t.writer .. (t.date and (" (" .. t.date .. ")") or ""))
    end

    -- Combat stats block
    local stats = {}
    if isPositive(t.attack)      then stats[#stats+1] = "Atk " .. tonumber(t.attack)        end
    if isPositive(t.extraattack) then stats[#stats+1] = "+Atk " .. fmtSigned(t.extraattack) end
    if isPositive(t.defense)     then stats[#stats+1] = "Def " .. tonumber(t.defense)       end
    if isPositive(t.extradefense)then stats[#stats+1] = "+Def " .. fmtSigned(t.extradefense)end
    if isPositive(t.armor)       then stats[#stats+1] = "Arm " .. tonumber(t.armor)         end
    if isPositive(t.attackspeed) then stats[#stats+1] = "Speed " .. fmtSigned(t.attackspeed)end
    if isPositive(t.hitchance)   then stats[#stats+1] = "Hit " .. fmtSigned(t.hitchance) .. "%" end
    if isPositive(t.shootrange)  then stats[#stats+1] = "Range " .. tonumber(t.shootrange)  end
    if #stats > 0 then
        rule()
        add(table.concat(stats, "  " .. DOT .. "  "))
    end

    -- Special procs (Arcfall rarity rolls)
    local procs = {}
    if isPositive(t.criticalChance) then
        procs[#procs+1] = "Crit Chance " .. fmtSigned(t.criticalChance) .. "%"
    end
    if isPositive(t.lifeLeech) then
        procs[#procs+1] = "Life Leech " .. fmtSigned(t.lifeLeech) .. "%"
    end
    if isPositive(t.manaLeech) then
        procs[#procs+1] = "Mana Leech " .. fmtSigned(t.manaLeech) .. "%"
    end
    if #procs > 0 then
        rule()
        for _, p in ipairs(procs) do add(p) end
    end

    -- Charges / duration
    if isPositive(t.charges) then
        rule()
        add("Charges: " .. tonumber(t.charges))
    end
    if isPositive(t.duration) then
        local mins = math.floor(tonumber(t.duration) / 60)
        if mins > 0 then add("Duration: " .. mins .. "m") end
    end

    -- Weight (engine-side)
    if isPositive(t.weight) then
        rule()
        add("Weight: " .. string.format("%.2f", tonumber(t.weight) / 100) .. " oz")
    end

    -- Listing metadata (if this is a market hover)
    local listingBits = {}
    if t.listing_mode and LISTING_MODE_LABEL[t.listing_mode] then
        listingBits[#listingBits+1] = LISTING_MODE_LABEL[t.listing_mode]
    end
    if isPositive(t.price) then
        listingBits[#listingBits+1] = fmtMoneyShort(t.price) .. " gold buyout"
    end
    if isPositive(t.bid) then
        listingBits[#listingBits+1] = "bid " .. fmtMoneyShort(t.bid) .. " gold"
    end
    if t.seller_name and t.seller_name ~= "" then
        rule()
        add("Listed by " .. t.seller_name)
    end
    if #listingBits > 0 then
        if not (t.seller_name and t.seller_name ~= "") then rule() end
        add(table.concat(listingBits, "  " .. DOT .. "  "))
    end
    local timeLine = fmtDuration(t.timeleft or t.time_left)
    if timeLine then
        add(timeLine .. " remaining")
    end

    -- Footer hint for fresh players
    if (t.seller_name or t.listing_mode) and #lines > 0 then
        add("")
        add("Double-click to " ..
            ((t.listing_mode == 2) and "place a bid" or "buy out"))
    end

    if #lines == 0 then return '' end
    return table.concat(lines, '\n')
end

-- ---------------------------------------------------------------
-- API
-- ---------------------------------------------------------------

function toggleMarketHover()
    marketHoverActive = not marketHoverActive
    if not marketHoverActive then
        if g_tooltip and g_tooltip.hide then
            pcall(g_tooltip.hide)
        end
    end
end

function newTooltip(tooltip)
    if not tooltip then return end
    local text = formatTooltip(tooltip)
    if text == '' then return end
    if g_tooltip and g_tooltip.display then
        pcall(g_tooltip.display, text)
    end
end

function init()
    -- Nothing to wire up; the market module looks us up by
    -- modules.game_itemtooltip at call time.
end

function terminate()
    marketHoverActive = false
    if g_tooltip and g_tooltip.hide then
        pcall(g_tooltip.hide)
    end
end
