local addonName, ns = ...

-- ============================================================================
-- BrightQoL – DataPanels Module
-- Configurable info bars showing: Time, Gold, Coords, System, Bags,
-- Item Level, Zone, M+ Key, Durability.
-- Each bar has N equal-width slots, each slot assigned to one data module.
-- ============================================================================

local DataPanels = {}
ns.DataPanels = DataPanels

local function DB() return BrightQoL.dataPanels end

---------------------------------------------------------------------------
-- Helpers
---------------------------------------------------------------------------
local BRIGHT_FONT = "Interface\\AddOns\\BrightQoL\\Assets\\Fonts\\Bright.ttf"
local W8          = "Interface\\Buttons\\WHITE8X8"

local function GetTextColor()
    local db = DB()
    if db.useClassColor then
        local _, cf = UnitClass("player")
        if RAID_CLASS_COLORS and RAID_CLASS_COLORS[cf] then
            local c = RAID_CLASS_COLORS[cf]
            return c.r, c.g, c.b
        end
    end
    return db.customColorR or 0, db.customColorG or (207/255), db.customColorB or 1.0
end

local function FormatMoney(copper)
    local gold   = math.floor(copper / 10000)
    local silver = math.floor((copper % 10000) / 100)
    if gold > 0 then
        return string.format("|cffffcc00%dg|r |cffc0c0c0%ds|r", gold, silver)
    elseif silver > 0 then
        return string.format("|cffc0c0c0%ds|r", silver)
    else
        return string.format("|cffb87333%dcp|r", copper % 100)
    end
end

---------------------------------------------------------------------------
-- Data Modules Table
-- Each module: { GetText, GetTooltip, OnClick, events, interval }
-- GetText(cr, cg, cb) → string
-- GetTooltip()        → { {line1, r,g,b}, ... }  nil = no tooltip
-- OnClick()           → optional
-- events              → { "EVENT_NAME", ... }  nil = none
-- interval            → number seconds for timer update; nil = event-only
---------------------------------------------------------------------------
local DATA_MODULES = {}

-- ── Time ─────────────────────────────────────────────────────────────────
DATA_MODULES["Time"] = {
    interval = 30,
    GetText = function(cr, cg, cb)
        local hour, min
        if GetCVarBool("timeMgrUseLocalTime") then
            hour = tonumber(date("%H")); min = tonumber(date("%M"))
        else
            hour, min = GetGameTime()
        end
        hour = math.floor(hour); min = math.floor(min)
        if not GetCVarBool("timeMgrUseMilitaryTime") then
            local ampm = hour >= 12 and "PM" or "AM"
            if hour == 0 then hour = 12 elseif hour > 12 then hour = hour - 12 end
            return string.format("|cff%02x%02x%02x%d:%02d %s|r", cr*255, cg*255, cb*255, hour, min, ampm)
        end
        return string.format("|cff%02x%02x%02x%d:%02d|r", cr*255, cg*255, cb*255, hour, min)
    end,
    GetTooltip = function()
        local lines = {}
        local daily  = SecondsToTime(C_DateAndTime.GetSecondsUntilDailyReset())
        local weekly = SecondsToTime(C_DateAndTime.GetSecondsUntilWeeklyReset())
        lines[#lines+1] = { "|cffffff00Daily reset:|r  " .. daily }
        lines[#lines+1] = { "|cffffff00Weekly reset:|r " .. weekly }
        return lines
    end,
    OnClick = function()
        if TimeManagerFrame then
            if TimeManagerFrame:IsShown() then TimeManagerFrame:Hide()
            else TimeManagerFrame:Show() end
        end
    end,
}

-- ── Gold ─────────────────────────────────────────────────────────────────
DATA_MODULES["Gold"] = {
    events = { "PLAYER_MONEY" },
    GetText = function()
        return FormatMoney(GetMoney())
    end,
    GetTooltip = function()
        local copper = GetMoney()
        local gold   = math.floor(copper / 10000)
        return {{ string.format("|cffffff00Total: |cffffcc00%d|r gold", gold) }}
    end,
}

-- ── Coords ───────────────────────────────────────────────────────────────
DATA_MODULES["Coords"] = {
    interval = 1,
    GetText = function(cr, cg, cb)
        local mapID = C_Map.GetBestMapForUnit("player")
        if mapID then
            local pos = C_Map.GetPlayerMapPosition(mapID, "player")
            if pos then
                return string.format("|cff%02x%02x%02x%.0f, %.0f|r",
                    cr*255, cg*255, cb*255, pos.x * 100, pos.y * 100)
            end
        end
        return "|cff888888–, –|r"
    end,
}

-- ── System ────────────────────────────────────────────────────────────────
DATA_MODULES["System"] = {
    interval = 2,
    GetText = function(cr, cg, cb)
        local fps = math.floor(GetFramerate())
        local _, _, latHome, latWorld = GetNetStats()
        local lat = math.max(latHome or 0, latWorld or 0)
        local fpsColor = fps >= 60 and "00ff00" or fps >= 30 and "ffff00" or "ff4444"
        local latColor = lat <= 100 and "00ff00" or lat <= 300 and "ffff00" or "ff4444"
        return string.format("|cff%s%d fps|r |cff%s%d ms|r", fpsColor, fps, latColor, lat)
    end,
    GetTooltip = function()
        local bwIn, bwOut = GetNetStats()
        return {
            { string.format("|cffffff00Bandwidth in:|r  %.1f KB/s", (bwIn or 0) / 1024) },
            { string.format("|cffffff00Bandwidth out:|r %.1f KB/s", (bwOut or 0) / 1024) },
        }
    end,
}

-- ── Bags ─────────────────────────────────────────────────────────────────
DATA_MODULES["Bags"] = {
    events = { "BAG_UPDATE" },
    GetText = function(cr, cg, cb)
        local free, total = 0, 0
        for bag = 0, NUM_BAG_SLOTS do
            local slots = C_Container.GetContainerNumSlots(bag)
            if slots > 0 then
                total = total + slots
                free  = free  + C_Container.GetContainerNumFreeSlots(bag)
            end
        end
        local pct = total > 0 and (1 - free / total) or 0
        local r = pct < 0.75 and 0 or (pct < 0.9 and 1 or 1)
        local g = pct < 0.75 and 1 or (pct < 0.9 and 1 or 0)
        local b = 0
        return string.format("|cff%02x%02x%02x%d / %d|r", r*255, g*255, b, free, total)
    end,
    OnClick = function() ToggleAllBags() end,
}

-- ── Item Level ────────────────────────────────────────────────────────────
DATA_MODULES["Item Level"] = {
    events = { "PLAYER_EQUIPMENT_CHANGED", "PLAYER_LOGIN" },
    GetText = function(cr, cg, cb)
        local equipped, max = GetAverageItemLevel()
        equipped = math.floor(equipped * 10) / 10
        max      = math.floor(max      * 10) / 10
        return string.format("|cff%02x%02x%02x%.1f|r |cff888888/ %.1f|r",
            cr*255, cg*255, cb*255, equipped, max)
    end,
    GetTooltip = function()
        local equipped, max = GetAverageItemLevel()
        return {
            { string.format("|cffffff00Equipped:|r %.1f", equipped) },
            { string.format("|cffffff00Max:     |r %.1f", max) },
        }
    end,
}

-- ── Zone ─────────────────────────────────────────────────────────────────
DATA_MODULES["Zone"] = {
    events = { "ZONE_CHANGED", "ZONE_CHANGED_INDOORS", "ZONE_CHANGED_NEW_AREA" },
    GetText = function(cr, cg, cb)
        local zone = GetMinimapZoneText() or GetZoneText() or "–"
        return string.format("|cff%02x%02x%02x%s|r", cr*255, cg*255, cb*255, zone)
    end,
}

-- ── M+ Key ────────────────────────────────────────────────────────────────
DATA_MODULES["M+ Key"] = {
    events = { "BAG_UPDATE", "CHALLENGE_MODE_MAPS_UPDATE", "PLAYER_LOGIN" },
    GetText = function(cr, cg, cb)
        -- Scan bags for keystone item (item ID 180653)
        for bag = 0, NUM_BAG_SLOTS do
            for slot = 1, C_Container.GetContainerNumSlots(bag) do
                local itemID = C_Container.GetContainerItemID(bag, slot)
                if itemID == 180653 then
                    local link = C_Container.GetContainerItemLink(bag, slot)
                    if link then
                        -- Extract keystone level from name/link
                        local level = link:match("%+(%d+)")
                        local name = link:match("%[(.-)%]")
                        if name then
                            -- Abbreviate dungeon name
                            local short = name:match("(%a+)%s*of%s*") or
                                          name:match("^(%a+)") or name
                            if level then
                                return string.format("|cff%02x%02x%02x%s +%s|r",
                                    cr*255, cg*255, cb*255, short, level)
                            end
                            return string.format("|cff%02x%02x%02x%s|r",
                                cr*255, cg*255, cb*255, short)
                        end
                    end
                end
            end
        end
        return "|cff888888No Key|r"
    end,
    OnClick = function()
        if not InCombatLockdown() then
            PVEFrame_ShowFrame("ChallengesFrame")
        end
    end,
}

-- ── Durability ────────────────────────────────────────────────────────────
DATA_MODULES["Durability"] = {
    events = { "UPDATE_INVENTORY_DURABILITY", "PLAYER_EQUIPMENT_CHANGED" },
    GetText = function(cr, cg, cb)
        local cur, max = 0, 0
        for i = 1, 18 do
            local c, m = GetInventoryItemDurability(i)
            if c and m and m > 0 then cur = cur + c; max = max + m end
        end
        if max == 0 then return "|cff888888–|r" end
        local pct = math.floor(cur / max * 100)
        local r = pct > 66 and 0 or pct > 33 and 1 or 1
        local g = pct > 66 and 1 or pct > 33 and 0.5 or 0
        return string.format("|cff%02x%02x%02x%d%%|r", r*255, g*255, 0, pct)
    end,
    GetTooltip = function()
        local cur, max = 0, 0
        for i = 1, 18 do
            local c, m = GetInventoryItemDurability(i)
            if c and m and m > 0 then cur = cur + c; max = max + m end
        end
        local pct = max > 0 and math.floor(cur / max * 100) or 0
        return {{ string.format("|cffffff00Durability:|r %d%%  (%d/%d)", pct, cur, max) }}
    end,
}

-- ── Exposed list ──────────────────────────────────────────────────────────
DataPanels.DATA_MODULE_NAMES = {
    "Time", "Gold", "Coords", "System", "Bags",
    "Item Level", "Zone", "M+ Key", "Durability",
}

---------------------------------------------------------------------------
-- Panel / Slot management
---------------------------------------------------------------------------
local panels = {}   -- array of Frame objects

local function GetFont()
    local db = DB()
    return BRIGHT_FONT, db.fontSize or 12, db.fontOutline or "NONE"
end

local function BuildTooltip(slot, modDef)
    local lines = modDef.GetTooltip and modDef.GetTooltip()
    if not lines or #lines == 0 then return end
    GameTooltip:SetOwner(slot, "ANCHOR_TOP")
    GameTooltip:ClearLines()
    for _, l in ipairs(lines) do
        GameTooltip:AddLine(l[1], l[2] or 1, l[3] or 1, l[4] or 1, true)
    end
    GameTooltip:Show()
end

local function AttachModule(slot, modName)
    local modDef = DATA_MODULES[modName]
    if not modDef then
        slot.text:SetText("|cff888888" .. (modName or "–") .. "|r")
        return
    end
    slot._modDef  = modDef
    slot._modName = modName

    -- Initial text
    local cr, cg, cb = GetTextColor()
    slot.text:SetText(modDef.GetText(cr, cg, cb))

    -- Event handler
    if modDef.events then
        slot:RegisterEvent(table.unpack(modDef.events))
        slot:SetScript("OnEvent", function(s, _event)
            local r, g, b = GetTextColor()
            s.text:SetText(modDef.GetText(r, g, b))
        end)
    end

    -- Timer update
    if modDef.interval then
        slot._ticker = C_Timer.NewTicker(modDef.interval, function()
            if slot:IsShown() then
                local r, g, b = GetTextColor()
                slot.text:SetText(modDef.GetText(r, g, b))
            end
        end)
    end

    -- Mouse
    if modDef.GetTooltip then
        slot:SetScript("OnEnter", function(s) BuildTooltip(s, modDef) end)
        slot:SetScript("OnLeave", function()  GameTooltip:Hide() end)
    end
    if modDef.OnClick then
        slot:SetScript("OnClick", modDef.OnClick)
    end
end

local function DetachModule(slot)
    if slot._modDef then
        if slot._modDef.events then slot:UnregisterAllEvents() end
        if slot._ticker then slot._ticker:Cancel(); slot._ticker = nil end
        slot:SetScript("OnEvent", nil)
        slot:SetScript("OnEnter", nil)
        slot:SetScript("OnLeave", nil)
        slot:SetScript("OnClick", nil)
        slot._modDef  = nil
        slot._modName = nil
    end
    slot.text:SetText("")
end

local function BuildPanel(idx, cfg)
    local barW = cfg.width    or 400
    local barH = cfg.height   or 22
    local n    = cfg.numSlots or #cfg.slots

    local bar = CreateFrame("Frame", "BrightQoL_DataPanel_" .. idx, UIParent, "BackdropTemplate")
    bar:SetSize(barW, barH)
    bar:SetFrameStrata("MEDIUM")
    bar:SetMovable(true)
    bar:SetClampedToScreen(true)
    bar:RegisterForDrag("LeftButton")
    bar:SetBackdrop({
        bgFile   = W8, edgeFile = W8, edgeSize = 1,
        insets   = { left=1, right=1, top=1, bottom=1 },
    })
    bar:SetBackdropColor(cfg.bgR or 0.03, cfg.bgG or 0.03, cfg.bgB or 0.06, cfg.bgAlpha or 0.75)
    if cfg.showBorder then
        bar:SetBackdropBorderColor(cfg.borderR or 0, cfg.borderG or 207/255, cfg.borderB or 1.0, cfg.borderAlpha or 0.5)
    else
        bar:SetBackdropBorderColor(0,0,0,0)
    end

    -- Position
    if cfg.savedPoint then
        bar:SetPoint(cfg.savedPoint, UIParent, cfg.savedRelPoint, cfg.savedX, cfg.savedY)
    else
        bar:SetPoint(cfg.point or "BOTTOM", UIParent, cfg.relPoint or "BOTTOM", cfg.x or 0, cfg.y or 4)
    end

    bar:SetScript("OnDragStart", function(f)
        if DB().locked then return end
        f:StartMoving()
    end)
    bar:SetScript("OnDragStop", function(f)
        f:StopMovingOrSizing()
        local a, _, b, c, d = f:GetPoint()
        cfg.savedPoint    = a; cfg.savedRelPoint = b
        cfg.savedX        = c; cfg.savedY        = d
    end)

    -- Slot frames
    bar.slots = {}
    local slotW = barW / math.max(n, 1)
    for i = 1, n do
        local slot = CreateFrame("Button", nil, bar)
        slot:SetSize(slotW, barH)
        slot:SetPoint("LEFT", bar, "LEFT", (i - 1) * slotW, 0)
        slot:EnableMouse(true)
        slot:RegisterForClicks("AnyUp")

        slot.text = slot:CreateFontString(nil, "OVERLAY")
        slot.text:SetFont(GetFont())
        slot.text:SetPoint("CENTER")
        slot.text:SetJustifyH("CENTER")
        slot.text:SetWordWrap(false)

        -- Hover highlight
        slot:SetScript("OnEnter", function(s)
            if not s._modDef or not s._modDef.GetTooltip then return end
            s:SetAlpha(0.85)
        end)
        slot:SetScript("OnLeave", function(s)
            s:SetAlpha(1)
            GameTooltip:Hide()
        end)

        local modName = cfg.slots and cfg.slots[i]
        if modName and modName ~= "" then
            AttachModule(slot, modName)
        end

        bar.slots[i] = slot
    end

    bar:SetShown(cfg.enabled ~= false and DB().enabled)
    panels[idx] = bar
    return bar
end

---------------------------------------------------------------------------
-- Public API
---------------------------------------------------------------------------
function DataPanels:RebuildAll()
    -- Destroy existing panels
    for i, bar in ipairs(panels) do
        if bar then
            for _, slot in ipairs(bar.slots or {}) do DetachModule(slot) end
            bar:Hide()
            bar:SetParent(nil)
        end
        panels[i] = nil
    end
    panels = {}

    local db = DB()
    if not db.enabled then return end
    if not db.panels  then return end

    for i, cfg in ipairs(db.panels) do
        BuildPanel(i, cfg)
    end
end

function DataPanels:RefreshFonts()
    for _, bar in ipairs(panels) do
        if bar then
            for _, slot in ipairs(bar.slots or {}) do
                if slot.text then
                    slot.text:SetFont(GetFont())
                end
            end
        end
    end
end

function DataPanels:ApplyLock(locked)
    for _, bar in ipairs(panels) do
        if bar then bar:SetMovable(not locked) end
    end
end

function DataPanels:UpdateAllText()
    local cr, cg, cb = GetTextColor()
    for _, bar in ipairs(panels) do
        if bar then
            for _, slot in ipairs(bar.slots or {}) do
                if slot._modDef and slot._modDef.GetText then
                    slot.text:SetText(slot._modDef.GetText(cr, cg, cb))
                end
            end
        end
    end
end

function DataPanels:SetPanelVisibility(idx, show)
    if panels[idx] then panels[idx]:SetShown(show) end
end

function DataPanels:UpdateDisplay()
    DataPanels:RebuildAll()
end

---------------------------------------------------------------------------
-- Init
---------------------------------------------------------------------------
local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_LOGIN")
initFrame:SetScript("OnEvent", function(self)
    self:UnregisterEvent("PLAYER_LOGIN")
    if DB().enabled then
        DataPanels:RebuildAll()
    end
end)
