local GC = select(2, ...)

local UI = {}
GC.UI = UI

local private = {
    MAIN_UI = nil,
    LITE_UI = nil,
    TIMER_UI = nil,
    TIMER_UI_TAB = nil,
    LAST_NOTEWOTHYITEM_UI = nil,
    WOW_TOKEN = nil
    --        START_SESSION_PROMPT = nil
}
UI.b1 = nil -- private

local LibStub = LibStub
local LibToast = LibStub("LibToast-1.0")
local AceGUI = LibStub("AceGUI-3.0")
-- local AceEvent = LibStub("AceEvent-3.0")

-- AceEvent:Embed(UI)

-- wow api
local GetItemQualityColor, CreateFrame, UIFrameFadeOut, UIFrameFadeIn,
      IsShiftKeyDown, PlaySound, GameTooltip, GetBestMapForUnit, GetMapInfo,
      ResetInstances, IsInGroup, SendChatMessage, GetMerchantNumItems,
      DeleteCursorItem, SecondsToTime = GetItemQualityColor, CreateFrame,
                                        UIFrameFadeOut, UIFrameFadeIn,
                                        IsShiftKeyDown, PlaySound, GameTooltip,
                                        C_Map.GetBestMapForUnit,
                                        C_Map.GetMapInfo, ResetInstances,
                                        IsInGroup, SendChatMessage,
                                        GetMerchantNumItems,
                                        DeleteCursorItem, SecondsToTime
local _G = _G

-- lua api
local pairs, tostring, time, select, date, sort, tinsert =
    pairs, tostring, time, select, date, sort, table.insert

-- define toast template
LibToast:Register("GallywixCoins",
                  function(toast, text, iconTexture, qualityID, amountGained,
                           itemValue, source)
    local _, _, _, hex = GetItemQualityColor(qualityID)

    toast:SetFormattedTitle("|c%s%s|r %s", hex, text, amountGained and
                                _G.PARENS_TEMPLATE:format(amountGained) or "")

    if source then
        -- party loot
        -- GC:Print("party loot toast")
        toast:SetFormattedText("|cFF2DA6ED%s|r\n|cFFFFFFFF%s|r", source,
                               itemValue)
    else
        toast:SetFormattedText("|cFFFFFFFF%s|r", itemValue)
    end

    if iconTexture then toast:SetIconTexture(iconTexture) end
end)

--[[------------------------------------------------------------------------
    Toast positioning: install a _G.Toaster shim so LibToast reads our saved
    anchor position.  Only runs if the real "Toaster" addon is not loaded.
--------------------------------------------------------------------------]]
function UI.SetupToastPositioning()
    if _G.Toaster then return end  -- real Toaster addon takes priority

    _G.Toaster = {
        SpawnPoint = function()
            local anchor = GC.db and GC.db.profile and GC.db.profile.notification.toastAnchor
            return (anchor and anchor.point) or "BOTTOMRIGHT"
        end,
        SpawnOffsetX = function()
            local anchor = GC.db and GC.db.profile and GC.db.profile.notification.toastAnchor
            return (anchor and anchor.x) or -20
        end,
        SpawnOffsetY = function()
            local anchor = GC.db and GC.db.profile and GC.db.profile.notification.toastAnchor
            return (anchor and anchor.y) or 30
        end,
        -- Replicate LibToast built-in defaults for the remaining hooks
        TitleColors      = function() return 0.510, 0.773, 1 end,
        TextColors       = function() return 0.486, 0.518, 0.541 end,
        BackgroundColors = function() return 0, 0, 0 end,
        Duration         = function() return 5 end,
        Opacity          = function() return 0.75 end,
        FloatingIcon     = function() return false end,
        HideToasts           = function() return false end,
        HideToastsFromSource = function() return false end,
        MuteToasts           = function() return false end,
        MuteToastsFromSource = function() return false end,
        IconSize         = function() return 30 end,
    }
end

--[[------------------------------------------------------------------------
    Toggle a draggable 250×50 anchor frame that previews where toasts appear.
    Drag it to reposition; right-click to close.
--------------------------------------------------------------------------]]
function UI.ShowToastPositioner()
    local anchor = GC.db and GC.db.profile and GC.db.profile.notification.toastAnchor
    if not anchor then return end

    if private.TOAST_POSITIONER then
        -- Restore to current saved position each time it is shown
        private.TOAST_POSITIONER:ClearAllPoints()
        private.TOAST_POSITIONER:SetPoint(
            anchor.point or "BOTTOMRIGHT", UIParent,
            anchor.point or "BOTTOMRIGHT",
            anchor.x or -20, anchor.y or 30)
        if private.TOAST_POSITIONER:IsShown() then
            private.TOAST_POSITIONER:Hide()
        else
            private.TOAST_POSITIONER:Show()
        end
        return
    end

    local f = CreateFrame("Frame", "LAToastPositioner", UIParent,
                          BackdropTemplateMixin and "BackdropTemplate" or nil)
    f:SetSize(250, 50)
    f:SetFrameStrata("HIGH")
    f:SetBackdrop({
        bgFile   = [[Interface\FriendsFrame\UI-Toast-Background]],
        edgeFile = [[Interface\FriendsFrame\UI-Toast-Border]],
        tile = true, tileSize = 12, edgeSize = 12,
        insets = {left = 5, right = 5, top = 5, bottom = 5}
    })
    f:SetBackdropColor(0.1, 0.3, 0.7, 0.9)
    f:SetBackdropBorderColor(1, 1, 0, 1)
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")

    f:SetScript("OnDragStart", function(self) self:StartMoving() end)
    f:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()

        -- Determine the closest UIParent corner and compute offsets from it.
        -- StartMoving() uses a TOPLEFT anchor internally, so we derive the
        -- correct corner-based offset from the frame's center position.
        local cx, cy = self:GetCenter()
        local sw, sh = UIParent:GetWidth(), UIParent:GetHeight()
        local bx, by = self:GetWidth() / 2, self:GetHeight() / 2

        local point, x, y
        if cx > sw / 2 then
            if cy > sh / 2 then
                point = "TOPRIGHT"
                x = (cx + bx) - sw
                y = (cy + by) - sh
            else
                point = "BOTTOMRIGHT"
                x = (cx + bx) - sw
                y = cy - by
            end
        else
            if cy > sh / 2 then
                point = "TOPLEFT"
                x = cx - bx
                y = (cy + by) - sh
            else
                point = "BOTTOMLEFT"
                x = cx - bx
                y = cy - by
            end
        end

        GC.db.profile.notification.toastAnchor.point = point
        GC.db.profile.notification.toastAnchor.x     = x
        GC.db.profile.notification.toastAnchor.y     = y

        -- Re-anchor cleanly so it sits exactly where the toast corner will be
        self:ClearAllPoints()
        self:SetPoint(point, UIParent, point, x, y)
    end)

    -- Right-click to dismiss
    f:SetScript("OnMouseDown", function(self, btn)
        if btn == "RightButton" then self:Hide() end
    end)

    local label = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
    label:SetAllPoints(f)
    label:SetJustifyH("CENTER")
    label:SetJustifyV("MIDDLE")
    label:SetText("|cFFFFFF00Toast Anchor|r\n|cFFFFFFFFDrag  •  Right-click to close|r")

    f:SetPoint(anchor.point or "BOTTOMRIGHT", UIParent,
               anchor.point or "BOTTOMRIGHT",
               anchor.x or -20, anchor.y or 30)

    private.TOAST_POSITIONER = f
    f:Show()
end

-- the timer ui
local timerUItotal = 0
function UI.ShowTimerWindow()
    GC.Debug.Log("ShowTimerWindow")

    if private.TIMER_UI then
        private.TIMER_UI:Show()
        return
    end

    private.TIMER_UI = AceGUI:Create("GCLiteWindow")
    private.TIMER_UI:Hide()

    private.TIMER_UI:SetTitle("|cffffffff" .. date("!%X", 0) .. "|r")
    private.TIMER_UI:SetStatusTable(GC.db.profile.timerUI)
    private.TIMER_UI:SetWidth(110)
    private.TIMER_UI:SetHeight(30)
    private.TIMER_UI.frame:SetScript("OnUpdate", function(event, elapsed)
        timerUItotal = timerUItotal + elapsed
        if timerUItotal >= 1 then
            UI.RefreshUIs()
            timerUItotal = 0
        end
    end)

    -- tab --
    private.TIMER_UI_TAB = CreateFrame("Frame", nil, private.TIMER_UI.frame)
    private.TIMER_UI_TAB.prevMouseIsOver = false
    private.TIMER_UI_TAB:SetSize(104, 47)
    private.TIMER_UI_TAB:SetPoint("BOTTOMLEFT", private.TIMER_UI.frame,
                                  "BOTTOMLEFT", 3, 3)
    private.TIMER_UI_TAB:SetScript("OnUpdate", function(self)
        if self.prevMouseIsOver then
            if (not self:IsMouseOver()) then
                UIFrameFadeOut(private.TIMER_UI_TAB, CHAT_FRAME_FADE_TIME)
                self.prevMouseIsOver = false
            end
        else
            if self:IsMouseOver() then
                UIFrameFadeIn(private.TIMER_UI_TAB, CHAT_FRAME_FADE_TIME)
                self.prevMouseIsOver = true
            end
        end
    end)
    UIFrameFadeOut(private.TIMER_UI_TAB, CHAT_FRAME_FADE_TIME);

    local l1 = private.TIMER_UI_TAB:CreateTexture(nil, "BACKGROUND")
    l1:SetTexture([[Interface\ChatFrame\ChatFrameTab]])
    l1:SetSize(7, 24)
    l1:SetPoint("TOPLEFT", private.TIMER_UI_TAB, "TOPLEFT", 26, 0)
    l1:SetTexCoord(0.03125, 0.140625, 0.28125, 1.0)

    local l2 = private.TIMER_UI_TAB:CreateTexture(nil, "BACKGROUND")
    l2:SetTexture([[Interface\ChatFrame\ChatFrameTab]])
    l2:SetSize(31, 24)
    l2:SetPoint("LEFT", l1, "RIGHT")
    l2:SetTexCoord(0.140625, 0.859375, 0.28125, 1.0)

    local l3 = private.TIMER_UI_TAB:CreateTexture(nil, "BACKGROUND")
    l3:SetTexture([[Interface\ChatFrame\ChatFrameTab]])
    l3:SetSize(7, 24)
    l3:SetPoint("LEFT", l2, "RIGHT")
    l3:SetTexCoord(0.859375, 0.96875, 0.28125, 1.0)

    GC.UI.b1 = CreateFrame("Button", nil, private.TIMER_UI_TAB)
    GC.UI.b1:SetSize(20, 20)
    if GC.Session.GetPauseStart() then
        GC.UI.b1:SetNormalTexture(
            [[Interface\Buttons\UI-SpellbookIcon-NextPage-Up]])
    else
        GC.UI.b1:SetNormalTexture([[Interface\TimeManager\PauseButton]])
    end
    GC.UI.b1:SetHighlightTexture([[Interface\Buttons\UI-Common-MouseHilight]],
                                 "ADD")
    GC.UI.b1:SetPoint("BOTTOMLEFT", l1, "BOTTOMLEFT", 4, 1)
    GC.UI.b1:SetScript("OnClick", function(self)
        -- GC.Debug.Log("  GC.UI.b1")
        if GC.Session.GetPauseStart() then
            GC.UI.b1:SetNormalTexture(
                [[Interface\Buttons\UI-SpellbookIcon-NextPage-Up]])
            --                private.OnBtnStartSessionClick()
            GC.Session.Restart()
        else
            GC.UI.b1:SetNormalTexture([[Interface\TimeManager\PauseButton]])
            -- private.OnBtnStopSessionClick()
            GC.Session.Pause()
        end
    end)

    local b2 = CreateFrame("Button", nil, private.TIMER_UI_TAB)
    b2:SetSize(20, 20)
    b2:SetNormalTexture([[Interface\TimeManager\ResetButton]])
    b2:SetHighlightTexture([[Interface\Buttons\UI-Common-MouseHilight]], "ADD")
    b2:SetPoint("LEFT", GC.UI.b1, "RIGHT", -2, 0)
    b2:SetScript("OnClick", function(self)
        -- GC.Debug.Log("  b2")
        GC.Session.New()
    end)

    private.TIMER_UI:Show()
end

-- the last noteworthy item ui
function UI.ShowLastNoteworthyItemWindow()
    GC.Debug.Log("ShowLastNoteworthyItemWindow")

    if private.LAST_NOTEWOTHYITEM_UI then
        private.LAST_NOTEWOTHYITEM_UI:Show()
        return
    end

    private.LAST_NOTEWOTHYITEM_UI = AceGUI:Create("GCLiteWindow")
    private.LAST_NOTEWOTHYITEM_UI:Hide()
    private.LAST_NOTEWOTHYITEM_UI:SetStatusTable(GC.db.profile
                                                     .lastNotewothyItemUI)
    private.LAST_NOTEWOTHYITEM_UI:SetWidth(350)
    private.LAST_NOTEWOTHYITEM_UI:SetHeight(30)
    private.LAST_NOTEWOTHYITEM_UI:SetTitle("Gold Alert Threshold")
    private.LAST_NOTEWOTHYITEM_UI:Show()

end

-- the lite ui
function UI.ShowLiteWindow()
    GC.Debug.Log("ShowLiteWindow")

    if private.LITE_UI then
        private.LITE_UI:Show()
        return
    end

    private.LITE_UI = AceGUI:Create("GCLiteWindow")
    private.LITE_UI:Hide()
    private.LITE_UI:SetStatusTable(GC.db.profile.liteUI)
    private.LITE_UI:SetWidth(150)
    private.LITE_UI:SetHeight(30)
    local totalItemValue = GC.Session.GetCurrentSession("liv") or 0
    private.LITE_UI:SetTitle("|cffffffff" ..
                                 GC.Util.MoneyToString(totalItemValue) .. "|r")
    private.LITE_UI:Show()
end

-- returns the configured font size (default 10)
function private.GetFontSize()
    return GC.db.profile.display.fontSize or 12
end

function private.GetBlizzardFont()
    -- Native Blizzard font (Friz Quadrata for most, or chat/standard font)
    local font = GameFontHighlight:GetFont()
    return font or "Fonts\\FRIZQT__.TTF"
end

-- Returns usable content width inside the main frame (subtracts AceGUI Frame border padding: 17px each side)
function private.GetInnerWidth()
    if private.MAIN_UI and private.MAIN_UI.frame then
        local w = private.MAIN_UI.frame:GetWidth()
        if w > 0 then return w - 40 end
    end
    return 360 -- fallback
end

-- applies the current font size to all label and button widgets in the main UI
function UI.ApplyFontSize()
    if not private.MAIN_UI then return end

    -- Apply font to buttons (created once; not rebuilt by PrepareDataContainer)
    local fontSize = private.GetFontSize()
    local font = private.GetBlizzardFont()
    local flags = "" -- thin border/flags
    
    local buttonKeys = {
        "button_newSession", "button_stopSession",
        "button_sellTrash", "button_destroyTrash", "button_resetInstance"
    }
    for _, key in pairs(buttonKeys) do
        local btn = private.MAIN_UI:GetUserData(key)
        if btn then
            btn.text:SetFont(font, fontSize, flags)
        end
    end

    -- Apply to Pill text
    local pill = private.MAIN_UI:GetUserData("pill_window")
    if pill then
        local t1 = private.MAIN_UI:GetUserData("pill_timerText")
        local t2 = private.MAIN_UI:GetUserData("pill_gphText")
        if t1 then t1:SetFont(font, fontSize, flags) end
        if t2 then t2:SetFont(font, fontSize, flags) end
    end

    -- Apply to Session Data labels
    local dataContainer = private.MAIN_UI:GetUserData("data_container")
    if dataContainer and dataContainer.labels then
        for _, lbl in ipairs(dataContainer.labels) do
            lbl:SetFont(font, fontSize, flags)
        end
    end

    -- Rebuild loot collected list (re-applies font to all rows)
    UI.RefreshLootCollectedList()

    GC.UI.PrepareDataContainer()
    private.MAIN_UI:DoLayout()
end

-- the main ui
local mainUItotal = 0
local updateTimer = 0

function UI.ShowMainWindow(showMainUI)
    GC.Debug.Log("ShowMainWindow")

    -- UI already initialised: just show/hide the Pill as requested and bail out
    if private.MAIN_UI then
        local pill = private.MAIN_UI:GetUserData("pill_window")
        if pill and showMainUI then pill:Show() end
        return
    end

    -- MOCK AceGUI object
    private.MAIN_UI = {}
    private.MAIN_UI.userData = {}
    private.MAIN_UI.GetUserData = function(self, key) return self.userData[key] end
    private.MAIN_UI.SetUserData = function(self, key, val) self.userData[key] = val end
    private.MAIN_UI.SetStatusTable = function() end
    private.MAIN_UI.SetTitle = function() end
    private.MAIN_UI.SetLayout = function() end
    private.MAIN_UI.SetWidth = function() end
    private.MAIN_UI.EnableResize = function() end
    private.MAIN_UI.DoLayout = function() end
    private.MAIN_UI.AddChild = function() end
    private.MAIN_UI.GetChild = function() end
    private.MAIN_UI.SetStatusText = function() end
    private.MAIN_UI.ApplyStatus = function() end

    _G.LAMainFrame = private.MAIN_UI

    ------------------------------------
    -- 1. PILL UI
    ------------------------------------
    -- 1. MAIN INTEGRATED FRAME
    local main = CreateFrame("Button", "GallywixCoinsMain", UIParent, BackdropTemplateMixin and "BackdropTemplate" or nil)
    main:SetSize(300, 30) -- Min-width 300
    main:SetClampedToScreen(true)
    
    -- Positioning
    main:ClearAllPoints()
    local savedPos = GC.db and GC.db.profile and GC.db.profile.pillUI
    if savedPos and savedPos.x then
        main:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", savedPos.x, savedPos.y)
    else
        -- Better default for new users: middle-top area
        main:SetPoint("TOP", UIParent, "TOP", 0, -200)
    end

    main:SetMovable(true)
    main:EnableMouse(true)
    main:RegisterForDrag("LeftButton")
    main:SetScript("OnDragStart", main.StartMoving)
    main:SetScript("OnDragStop", function(self)
        self:StopMovingOrSizing()
        -- Force re-anchoring to TOPLEFT relative to UIParent BOTTOMLEFT for stable expansion direction
        local x = self:GetLeft()
        local y = self:GetTop()
        self:ClearAllPoints()
        self:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y)
        
        if GC.db and GC.db.profile then
            GC.db.profile.pillUI = { x = x, y = y }
        end
    end)
    
    main:SetBackdrop({
        bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
        edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
        tile = false, edgeSize = 16,
        insets = { left = 4, right = 4, top = 4, bottom = 4 }
    })
    main:SetBackdropColor(0.1, 0.1, 0.1, 0.9)
    main:SetBackdropBorderColor(1, 0.8, 0, 1)

    -- HEADER AREA (Pill View)
    local header = CreateFrame("Frame", nil, main)
    header:SetPoint("TOPLEFT", 0, 0)
    header:SetPoint("TOPRIGHT", 0, 0)
    header:SetHeight(30)

    -- Header Divider (Separation line between pill and details)
    local headerDivider = header:CreateTexture(nil, "ARTWORK")
    headerDivider:SetHeight(1)
    headerDivider:SetPoint("BOTTOMLEFT", header, "BOTTOMLEFT", 4, 0)
    headerDivider:SetPoint("BOTTOMRIGHT", header, "BOTTOMRIGHT", -4, 0)
    headerDivider:SetColorTexture(1, 0.82, 0, 0.6)
    headerDivider:Hide() -- Only show when expanded
    main.headerDivider = headerDivider

    -- Click header to toggle expansion
    main:SetScript("OnClick", function()
        main.isExpanded = not main.isExpanded
        UI.RefreshUIs()
    end)
    
    -- Helper to toggle the entire frame visibility (pill)
    function UI.ToggleMainWindow()
        if main:IsShown() then
            main:Hide()
        else
            main:Show()
            UI.RefreshUIs()
        end
    end

    -- Pill Elements (anchored to header)
    local pillTimerText = header:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    pillTimerText:SetJustifyH("CENTER")
    pillTimerText:SetWordWrap(false)
    pillTimerText:SetText("|cffffd100—|r")

    local pillGphText = header:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
    pillGphText:SetJustifyH("CENTER")
    pillGphText:SetWordWrap(false)
    pillGphText:SetText("|cffffffff—|r")

    local pillSep1 = header:CreateTexture(nil, "ARTWORK")
    pillSep1:SetSize(1, 16)
    pillSep1:SetColorTexture(1, 0.8, 0, 0.4)
    local pillSep2 = header:CreateTexture(nil, "ARTWORK")
    pillSep2:SetSize(1, 16)
    pillSep2:SetColorTexture(1, 0.8, 0, 0.4)

    local function CreateHeaderButton(parent, iconTexture, tooltipTxt)
        local btn = CreateFrame("Button", nil, parent)
        btn:SetSize(16, 16)
        btn:SetNormalTexture(iconTexture)
        btn:SetHighlightTexture("Interface\\Buttons\\UI-Common-MouseHilight", "ADD")
        btn:SetScript("OnEnter", function()
            GameTooltip:SetOwner(btn, "ANCHOR_TOP")
            GameTooltip:SetText(tooltipTxt)
            GameTooltip:Show()
        end)
        btn:SetScript("OnLeave", function() GameTooltip:Hide() end)
        return btn
    end

    local btnPillStop = CreateHeaderButton(header, "Interface\\TimeManager\\ResetButton", "Stop Session")
    btnPillStop:SetScript("OnClick", function()
        GC.Session.Stop(false)
        UI.RefreshUIs()
    end)

    local btnPillPlay = CreateHeaderButton(header, "Interface\\Buttons\\UI-SpellbookIcon-NextPage-Up", "Play / Pause")
    btnPillPlay:SetScript("OnClick", function()
        if not GC.Session.IsRunning() then GC.Session.Start(false)
        elseif GC.Session.IsPaused() then GC.Session.Restart()
        else GC.Session.Pause() end
        UI.RefreshUIs()
    end)

    -- DETAILS AREA (Expandable View)
    local details = CreateFrame("Frame", nil, main)
    details:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, 0)
    details:SetPoint("BOTTOMRIGHT", 0, 0)
    details:Hide()

    -- Stats Container
    local dataContainer = CreateFrame("Frame", nil, details)
    dataContainer:SetPoint("TOPLEFT", 10, -10)
    dataContainer:SetPoint("TOPRIGHT", -10, -10)
    dataContainer:SetHeight(40)
    
    -- Divider
    local divider = details:CreateTexture(nil, "ARTWORK")
    divider:SetHeight(1)
    divider:SetPoint("TOPLEFT", dataContainer, "BOTTOMLEFT", 0, -4)
    divider:SetPoint("TOPRIGHT", dataContainer, "BOTTOMRIGHT", 0, -4)
    divider:SetColorTexture(1, 0.82, 0, 0.3)

    -- Loot List
    local lootContainer = CreateFrame("Frame", nil, details)
    lootContainer:SetPoint("TOPLEFT", divider, "BOTTOMLEFT", 0, -8)
    lootContainer:SetPoint("BOTTOMRIGHT", -12, 12)
    
    local lootScroll = AceGUI:Create("ScrollFrame")
    lootScroll:SetLayout("Flow")
    lootScroll.frame:SetParent(lootContainer)
    lootScroll.frame:SetAllPoints(lootContainer)
    lootScroll.frame:Show()

    -- Global Registry
    private.MAIN_UI.frame = main
    private.MAIN_UI.header = header
    private.MAIN_UI.details = details
    private.MAIN_UI:SetUserData("pill_timerText", pillTimerText)
    private.MAIN_UI:SetUserData("pill_gphText", pillGphText)
    private.MAIN_UI:SetUserData("button_toggleTimer", btnPillPlay)
    private.MAIN_UI:SetUserData("button_pillStop", btnPillStop)
    private.MAIN_UI:SetUserData("pill_sep1", pillSep1)
    private.MAIN_UI:SetUserData("pill_sep2", pillSep2)
    private.MAIN_UI:SetUserData("data_container", dataContainer)
    private.MAIN_UI:SetUserData("divider", divider)
    private.MAIN_UI:SetUserData("data_lootCollectedContainer", lootContainer)
    private.MAIN_UI:SetUserData("data_lootCollected", lootScroll)

    -- Resize Grip
    main:SetResizable(true)
    if main.SetResizeBounds then main:SetResizeBounds(300, 30, 800, 800)
    elseif main.SetMinResize then main:SetMinResize(300, 30) end

    local resizeGrip = CreateFrame("Frame", nil, main)
    resizeGrip:SetSize(16, 16)
    resizeGrip:SetPoint("BOTTOMRIGHT", -2, 2)
    resizeGrip:EnableMouse(true)
    local gripTex = resizeGrip:CreateTexture(nil, "OVERLAY")
    gripTex:SetTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrab-Down")
    gripTex:SetAllPoints(resizeGrip)
    resizeGrip:SetScript("OnMouseDown", function() 
        if main.isExpanded then 
            main.isResizing = true
            main:StartSizing("BOTTOMRIGHT") 
        end
    end)
    resizeGrip:SetScript("OnMouseUp", function() 
        main:StopMovingOrSizing()
        main.isResizing = false
        private.MAIN_UI.userResized = true
        private.MAIN_UI.targetHeight = main:GetHeight() - 30
    end)

    main:SetScript("OnSizeChanged", function(self, w, h)
        if main.isExpanded then
            UI.RefreshLootCollectedList()
        end
    end)

    -- Animation & Layout Loop
    main:SetScript("OnUpdate", function(self, elapsed)
        mainUItotal = mainUItotal + elapsed
        if mainUItotal >= 1 then
            UI.RefreshUIs()
            mainUItotal = 0
            GC.UI.PrepareDataContainer()
        end

        -- Height Animation
        local targetH = 30
        if main.isExpanded then
            local hasLoot = private.AggregatedLoot and next(private.AggregatedLoot) ~= nil
            local fontSize = private.GetFontSize()
            local dataH = (fontSize + 8) * 4
            local minDetailsH = 10 + dataH + 10
            targetH = 30 + (hasLoot and (private.MAIN_UI.targetHeight or 200) or minDetailsH)
            details:Show()
            if main.headerDivider then main.headerDivider:Show() end
        else
            details:Hide()
            if main.headerDivider then main.headerDivider:Hide() end
        end

        local curH = self:GetHeight()
        
        -- If user is manually resizing, don't animate height, just capture it.
        if main.isResizing then
            private.MAIN_UI.targetHeight = curH - 30
        else
            local hDiff = targetH - curH
            if math.abs(hDiff) > 0.5 then
                self:SetHeight(curH + hDiff * elapsed * 15)
            else
                self:SetHeight(targetH)
            end
        end

        -- Partitioned Header Layout (25/50/25)
        local W = self:GetWidth()
        pillSep1:SetPoint("CENTER", self, "TOPLEFT", W * 0.25, -15)
        pillSep2:SetPoint("CENTER", self, "TOPLEFT", W * 0.75, -15)
        pillTimerText:SetPoint("CENTER", self, "TOPLEFT", W * 0.125, -15)
        pillGphText:SetPoint("CENTER", self, "TOPLEFT", W * 0.5, -15)
        btnPillPlay:SetPoint("CENTER", self, "TOPLEFT", W * 0.85, -15)
        btnPillStop:SetPoint("LEFT", btnPillPlay, "RIGHT", 4, 0)
        
        -- Transparency
        if not InCombatLockdown() then
            if self:IsMouseOver() then self:SetAlpha(1.0) else self:SetAlpha(main.isExpanded and 0.9 or 0.7) end
        end
    end)

    GC.UI.PrepareDataContainer()
    main.isExpanded = false
    if showMainUI then main:Show() end
end


function UI.PrepareDataContainer()
    -- Note: no session guard here. Labels are created once when the window is first
    -- built. RefreshUIs will populate them live once the session is running.
    if not private.MAIN_UI then return end
    local dataContainer = private.MAIN_UI:GetUserData("data_container")
    if not dataContainer then return end

    -- Only create labels once. If they already exist, skip creation.
    if dataContainer._labelsCreated then return end
    dataContainer._labelsCreated = true
    dataContainer.labels = {}

    local currentMapID = GetBestMapForUnit("player")
    local zoneInfo = currentMapID and GetMapInfo(currentMapID)
    local zoneName = zoneInfo and zoneInfo.name or "—"

    local function CreateRowLabel(parent, isValue)
        local lbl = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
        local font = private.GetBlizzardFont()
        local fontSize = private.GetFontSize()
        lbl:SetFont(font, fontSize, "")
        lbl:SetJustifyH(isValue and "RIGHT" or "LEFT")
        table.insert(parent.labels, lbl)
        return lbl
    end

    local fontSize = private.GetFontSize()
    local rowHeight = fontSize + 8
    dataContainer:SetHeight(rowHeight * 4)

    -- Row 1: Zone | Timer
    local l_zone = CreateRowLabel(dataContainer, false)
    l_zone:SetPoint("TOPLEFT", 0, 0)
    l_zone:SetText("|cffffffff" .. zoneName .. "|r")
    private.MAIN_UI:SetUserData("data_zone", l_zone)

    local l_time = CreateRowLabel(dataContainer, true)
    l_time:SetPoint("TOPRIGHT", 0, 0)
    l_time:SetText("—")
    private.MAIN_UI:SetUserData("data_sessionDuration", l_time)

    -- Row 2: Gold Dropped
    local l_gold_lbl = CreateRowLabel(dataContainer, false)
    l_gold_lbl:SetPoint("TOPLEFT", 0, -rowHeight)
    l_gold_lbl:SetText("|cffffd100Gold dropped:|r")

    local l_gold_val = CreateRowLabel(dataContainer, true)
    l_gold_val:SetPoint("TOPRIGHT", 0, -rowHeight)
    l_gold_val:SetText("|cff888888—|r")
    private.MAIN_UI:SetUserData("data_rawGold", l_gold_val)

    -- Row 3: Total Session Value
    local l_liv_lbl = CreateRowLabel(dataContainer, false)
    l_liv_lbl:SetPoint("TOPLEFT", 0, -(rowHeight * 2))
    l_liv_lbl:SetText("Total Value:")

    local l_liv_val = CreateRowLabel(dataContainer, true)
    l_liv_val:SetPoint("TOPRIGHT", 0, -(rowHeight * 2))
    l_liv_val:SetText("|cff888888—|r")
    private.MAIN_UI:SetUserData("data_lootedItemValue", l_liv_val)

    -- Row 4: Gold Per Hour
    local l_gph_lbl = CreateRowLabel(dataContainer, false)
    l_gph_lbl:SetPoint("TOPLEFT", 0, -(rowHeight * 3))
    l_gph_lbl:SetText("|cff00ff7fGold/h:|r")

    local l_gph_val = CreateRowLabel(dataContainer, true)
    l_gph_val:SetPoint("TOPRIGHT", 0, -(rowHeight * 3))
    l_gph_val:SetText("|cff888888—|r")
    private.MAIN_UI:SetUserData("data_gph", l_gph_val)
end

-- refresh the status bar with the current settings
function UI.RefreshStatusText()
    -- Native UI no longer uses the bottom status bar to save space
end

-- refresh the main ui
function UI.RefreshUIs()
    if not private.MAIN_UI then return end

    -- Handle visibility of loot listing (hide if empty)
    local hasLoot = private.AggregatedLoot and next(private.AggregatedLoot) ~= nil
    local divider = private.MAIN_UI:GetUserData("divider")
    local lootContainer = private.MAIN_UI:GetUserData("data_lootCollectedContainer")
    if divider then divider:SetShown(hasLoot) end
    if lootContainer then lootContainer:SetShown(hasLoot) end

    -- 1. Session duration
    local sessionDurationUI = private.MAIN_UI:GetUserData("data_sessionDuration")
    if sessionDurationUI then
        if GC.Session.IsRunning() then
            local offset = GC.Session.GetPauseStart() or time()
            local delta = offset - GC.Session.GetCurrentSession("start") - GC.Session.GetSessionPause()
            local noSeconds = delta > 3600
            if GC.Session.IsPaused() then
                sessionDurationUI:SetText("|cffff8800" .. SecondsToTime(delta, noSeconds, false) .. "|r")
            else
                sessionDurationUI:SetText(SecondsToTime(delta, noSeconds, false))
            end
        else
            sessionDurationUI:SetText("|cff888888Not started|r")
        end
    end

    -- 2. Zone info
    local zoneUI = private.MAIN_UI:GetUserData("data_zone")
    if zoneUI then
        local currentMapID = GetBestMapForUnit("player")
        if currentMapID then
            local zoneInfo = GetMapInfo(currentMapID)
            zoneUI:SetText("|cffffffff" .. (zoneInfo and zoneInfo.name or "Unknown Zone") .. "|r")
        end
    end

    -- 3. Looted item value + raw gold + GPH
    local session = GC.Session.IsRunning() and GC.Session.GetCurrentSession()
    local rawGoldCopper = (session and session.totalLootedCurrency) or 0
    local itemValueCopper = (session and session.liv) or 0
    local combinedTotal = itemValueCopper + rawGoldCopper

    local rawGoldUI = private.MAIN_UI:GetUserData("data_rawGold")
    if rawGoldUI then
        rawGoldUI:SetText(GetCoinTextureString(rawGoldCopper))
    end

    local lootedItemValueUI = private.MAIN_UI:GetUserData("data_lootedItemValue")
    if lootedItemValueUI then
        lootedItemValueUI:SetText(GetCoinTextureString(combinedTotal))
    end
    
    local gphUI = private.MAIN_UI:GetUserData("data_gph")
    if gphUI then
        -- Remove the " / h" suffix as requested
        local gphVal = GC.Session.IsRunning() and private.CalcLootedItemValuePerHourRaw() or 0
        gphUI:SetText(GetCoinTextureString(gphVal))
    end

    if UI.LITE_UI then
        private.LITE_UI:SetTitle("|cffffffff" .. GetCoinTextureString(GC.Session.GetCurrentSession("liv") or 0) .. "|r")
    end
    
    if private.TIMER_UI then
        if GC.Session.IsRunning() then
            local offset = GC.Session.GetPauseStart() or time()
            local delta = offset - GC.Session.GetCurrentSession("start") - GC.Session.GetSessionPause()
            if GC.Session.GetPauseStart() and time() % 2 ~= 0 then
                private.TIMER_UI:SetTitle(" ")
            else
                private.TIMER_UI:SetTitle("|cffffffff" .. date("!%X", delta) .. "|r")
            end
        else
            private.TIMER_UI:SetTitle("|cffffffff" .. date("!%X", 0) .. "|r")
        end
    end

    -- Pill Timer
    local pillTimerFS = private.MAIN_UI:GetUserData("pill_timerText")
    local pillGphFS   = private.MAIN_UI:GetUserData("pill_gphText")
    if pillTimerFS then
        local timerStr = "|cff888888--|r"
        if GC.Session.IsRunning() then
            local offset = GC.Session.GetPauseStart() or time()
            local delta = offset - GC.Session.GetCurrentSession("start") - GC.Session.GetSessionPause()
            local noSeconds = delta > 3600
            timerStr = "|cffffd100" .. string.trim(SecondsToTime(delta, noSeconds, false)) .. "|r"
        end
        pillTimerFS:SetText(timerStr)
    end
    if pillGphFS then
        -- Show session Total Value (items + raw gold)
        local combinedTotal = 0
        if GC.Session.IsRunning() then
            local session = GC.Session.GetCurrentSession()
            local rawGoldCopper  = (session and session.totalLootedCurrency) or 0
            local itemValueCopper = (session and session.liv) or 0
            combinedTotal  = itemValueCopper + rawGoldCopper
        end
        pillGphFS:SetText(GetCoinTextureString(combinedTotal))
    end

    -- The targetHeight and animation are now handled in the main frame's OnUpdate
    -- for better stability and synchronization.

    -- Play/Pause Pill Button
    local btnToggleTimer = private.MAIN_UI:GetUserData("button_toggleTimer")
    if btnToggleTimer then
        if GC.Session.IsPaused() or not GC.Session.IsRunning() then
            btnToggleTimer:SetNormalTexture("Interface\\Buttons\\UI-SpellbookIcon-NextPage-Up")
        else
            btnToggleTimer:SetNormalTexture("Interface\\TimeManager\\PauseButton")
        end
    end
end

function UI.AddToResetInfo(data)
    if not private.MAIN_UI then return end

    private.resetInfo = private.resetInfo or {}

    tinsert(private.resetInfo, data)

    local buttonresetInstance = private.MAIN_UI:GetUserData(
                                    "button_resetInstance")
    if buttonresetInstance then
        buttonresetInstance:SetText("Reset Instances (" ..
                                        GC.Util.tablelength(private.resetInfo) ..
                                        "/9)") -- add lockouts
    end
end

function UI.UpdateLastNoteworthyItemUI(itemLink, quantity, singleItemValue,
                                       formattedValue)

    if private.LAST_NOTEWOTHYITEM_UI then
        if tonumber(quantity) > 1 then
            local qtyValue = tonumber(singleItemValue) * tonumber(quantity)
            local formattedItemValue = GC.Util.MoneyToString(qtyValue) or 0
            private.LAST_NOTEWOTHYITEM_UI:SetTitle(
                itemLink .. "|cffffffff x" .. quantity .. ": " ..
                    formattedItemValue .. "|r")
        else
            private.LAST_NOTEWOTHYITEM_UI:SetTitle(
                itemLink .. "|cffffffff x" .. quantity .. ": " .. formattedValue ..
                    "|r")
        end

    end

end

function UI.UpdateLiteWindowUI()
    if private.LITE_UI then
        local totalItemValue = GC.Session.GetCurrentSession("liv") or 0
        private.LITE_UI:SetTitle("|cffffffff" ..
                                     GC.Util.MoneyToString(totalItemValue) ..
                                     "|r")
        GC.Debug.Log("Updating GC Lite value: " .. tostring(totalItemValue))
    end
end

private.AggregatedLoot = private.AggregatedLoot or {}

function UI.RefreshLootCollectedList()
    local lootCollectedUI = private.MAIN_UI:GetUserData("data_lootCollected")
    if not lootCollectedUI then return end

    lootCollectedUI:ReleaseChildren()

    local sortedItems = {}
    for id, data in pairs(private.AggregatedLoot) do
        table.insert(sortedItems, data)
    end

    table.sort(sortedItems, function(a, b)
        if a.rarity ~= b.rarity then
            return (a.rarity or 0) > (b.rarity or 0)
        end
        local totalA = (a.qty or 0) * (a.val or 0)
        local totalB = (b.qty or 0) * (b.val or 0)
        return totalA > totalB
    end)

    local _lFont = private.GetBlizzardFont()
    local _lSize = private.GetFontSize()
    local _lFlags = ""
    local _iconSize = _lSize + 2
    local scrollFrame = lootCollectedUI.scrollframe
    local innerWidth = (scrollFrame:GetWidth() or 240) - 20 -- Gap for scrollbar
    if innerWidth < 100 then innerWidth = 240 end

    local valWidth = 80 -- Fixed width for gold value
    local iconWidth = _iconSize + 25 -- Icon + Qty part
    local nameWidth = innerWidth - valWidth - iconWidth

    for _, data in ipairs(sortedItems) do
        local totalValue = data.qty * data.val
        local formattedValue = GC.Util.MoneyToString(totalValue) or "0c"
        local iconText = (data.icon and ("|T" .. data.icon .. ":" .. _iconSize .. ":" .. _iconSize .. ":0:0|t ") or "")
        
        local rowGrp = AceGUI:Create("SimpleGroup")
        rowGrp:SetLayout("Flow")
        rowGrp:SetFullWidth(true)

        -- 1. Qty + Icon (Left)
        local qtyLabel = AceGUI:Create("GCLabel")
        qtyLabel:SetFont(_lFont, _lSize, _lFlags)
        qtyLabel:SetText(iconText .. data.qty .. "x")
        qtyLabel:SetWidth(iconWidth)
        rowGrp:AddChild(qtyLabel)

        -- 2. Item Link (Center, Truncated if needed)
        local nameLabel = AceGUI:Create("GCLabel")
        nameLabel:SetFont(_lFont, _lSize, _lFlags)
        nameLabel:SetText(data.link or "Unknown")
        nameLabel:SetWidth(nameWidth)
        nameLabel:SetWordWrap(false) -- Crucial for truncation
        nameLabel:SetJustifyH("LEFT")
        nameLabel:SetCallback("OnEnter", function()
            GameTooltip:SetOwner(private.MAIN_UI.frame, "ANCHOR_CURSOR")
            GameTooltip:SetHyperlink(data.link or "")
            GameTooltip:Show()
        end)
        nameLabel:SetCallback("OnLeave", function() GameTooltip:Hide() end)
        rowGrp:AddChild(nameLabel)

        -- 3. Value (Right)
        local valLabel = AceGUI:Create("GCLabel")
        valLabel:SetFont(_lFont, _lSize, _lFlags)
        -- Use GetCoinTextureString for proper coin icons instead of text g/s/c
        valLabel:SetText(GetCoinTextureString(totalValue))
        valLabel:SetWidth(valWidth)
        valLabel:SetJustifyH("RIGHT")
        rowGrp:AddChild(valLabel)
        
        lootCollectedUI:AddChild(rowGrp)
    end
end

-- add a given item to the top of the loot colletced list
function UI.AddItem2LootCollectedList(itemID, link, quantity, marketValue,
                                      source, disenchanted)
    if source and not GC.GetFromDb("display", "addGroupDropsToLootedItemList") then
        return
    end

    private.AggregatedLoot = private.AggregatedLoot or {}

    local _, _, rarity, _, _, _, _, _, _ = GetItemInfo(itemID)
    local icon = GetItemIcon(itemID) or 134400 

    if private.AggregatedLoot[itemID] then
        private.AggregatedLoot[itemID].qty = private.AggregatedLoot[itemID].qty + quantity
        private.AggregatedLoot[itemID].val = marketValue
    else
        private.AggregatedLoot[itemID] = {
            id = itemID,
            link = link,
            qty = quantity,
            val = marketValue,
            rarity = rarity,
            source = source,
            icon = icon,
            disenchanted = disenchanted
        }
    end

    UI.RefreshLootCollectedList()
    
    -- update height check
    if private.MAIN_UI and private.MAIN_UI.DoLayout then 
        private.MAIN_UI:DoLayout() 
    end
end

function UI.ClearLootCollectedList()
    local lootCollectedUI = private.MAIN_UI:GetUserData("data_lootCollected")
    if lootCollectedUI then
        lootCollectedUI:ReleaseChildren()
        -- Reset scroll position so the scrollbar disappears after stop
        if lootCollectedUI.scrollframe then
            lootCollectedUI.scrollframe:SetVerticalScroll(0)
        end
        lootCollectedUI:DoLayout()  -- force layout recalculation
    end
    private.MAIN_UI:SetUserData("data_lootCollected_lastEntry", nil)
    if private.AggregatedLoot then table.wipe(private.AggregatedLoot) end
end

function UI.ClearLastNoteworthyItemUI()

    if private.LAST_NOTEWOTHYITEM_UI then
        private.LAST_NOTEWOTHYITEM_UI:SetTitle("Gold Alert Threshold")
    end
end

-- resets all loot appraiser frames
function UI.ResetFrames()
    GC.Debug.Log("reset frames")

    local parentHeight = UIParent:GetHeight()
    GC.Debug.Log("ParentHeight: " .. parentHeight)
    GC.db.profile.lastNotewothyItemUI = {
        ["height"] = 30,
        ["top"] = (parentHeight - 100),
        ["left"] = 50,
        ["width"] = 350
    }

    if private.LAST_NOTEWOTHYITEM_UI then
        private.LAST_NOTEWOTHYITEM_UI:ApplyStatus()
    end

    GC.db.profile.liteUI = {
        ["height"] = 30,
        ["top"] = (parentHeight - 135),
        ["left"] = 50,
        ["width"] = 150
    }
    if private.LITE_UI then private.LITE_UI:ApplyStatus() end

    GC.db.profile.timerUI = {
        ["height"] = 30,
        ["top"] = (parentHeight - 135),
        ["left"] = 290,
        ["width"] = 110
    }
    if private.TIMER_UI then private.TIMER_UI:ApplyStatus() end

    GC.db.profile.mainUI.top = parentHeight - 170
    GC.db.profile.mainUI.left = 50

    if private.MAIN_UI then private.MAIN_UI:ApplyStatus() end
end

function UI.GetTokenPrice() return GC.UI.WOW_TOKEN end

-- Event handler for button 'reset instances'
function private.OnBtnResetInstancesClick()
    ResetInstances()

    -- if in a party - send party msg for awareness --
    local inInstanceGroup = IsInGroup(LE_PARTY_CATEGORY_INSTANCE)
    local inInstanceGroupRealm = IsInGroup(LE_PARTY_CATEGORY_HOME)
    if inInstanceGroup or inInstanceGroupRealm then
        SendChatMessage("Instances have been reset.", "PARTY", nil)
    end

    -- GC.Debug.TableToString(GC.ResetInfo)
end

function private.OnBtnSellTrashClick()

    local merchantitems = GetMerchantNumItems() -- check if vendor is open first
    if merchantitems > 0 then
        -- toggle already checked - proceed with finding gray items and selling
        -- Search for gray items in bags
        local totalItemValueOfGrays = 0 -- value holder for total currency sold
        local rarityCounter = 0 -- counter for rarity items

        for bag = 0, NUM_BAG_SLOTS do
            -- get details about each item in slot
            for slot = 1, C_Container.GetContainerNumSlots(bag) do
                local itemLink = C_Container.GetContainerItemLink(bag, slot)
                -- if an itemLink is available

                if itemLink ~= nil then
                    local _, _, itemID = strfind(itemLink, "item:(%d+):")
                    if itemID == nil then
                        GC.Debug.Log("No itemID found for " .. itemLink ..
                                         " in bag slot " .. bag)
                    else
                        local _, _, rarity, _, _, _, _, _, _ = GetItemInfo(
                                                                   itemID)
                        local itemInfo = GetItemInfo(itemID)
                        local currentItemValue =
                            private.GetItemValue(itemID, "VendorSell") or 0

                        -- enumerate how many of the same item for multiplier
                        local iStackCount = GetItemCount(itemInfo)
                        if iStackCount > 1 then
                            currentItemValue = currentItemValue * iStackCount
                        end

                        -- detect if rarity of 0 (poor gray item)
                        if rarity == 0 and currentItemValue ~= 0 then -- added currentItemValue

                            rarityCounter = rarityCounter + 1
                            GC.Debug.Log(
                                "selling gray item: " .. itemLink .. " x" ..
                                    iStackCount .. ": " ..
                                    GetCoinTextureString(currentItemValue))
                            totalItemValueOfGrays =
                                totalItemValueOfGrays + currentItemValue
                            -- output to player
                            -- if Verbose is enabled, then only show the total and don't do an output of the sale per item
                            if GC.db.profile.general
                                .sellGrayItemsToVendorVerbose == true then
                                GC:Print(
                                    "Selling " .. itemLink .. " x" ..
                                        iStackCount .. ": " ..
                                        GetCoinTextureString(currentItemValue))
                            end
                            local id = C_Container.GetContainerItemID(bag, slot)
                            if id and
                                GC.TSM
                                    .IsItemInGroup(id, GC.GetFromDb("sellTrash",
                                                                    "tsmGroup")) then
                                -- GC.Debug.Log("  id=" .. id .. ", found=" .. tostring(trashItems["i:" .. id]) .. ", link=" .. link)
                                C_Container.UseContainerItem(bag, slot)
                            else
                                C_Container.UseContainerItem(bag, slot) -- perform selling of item
                            end
                        end
                    end
                end
            end
        end

        ---------------------------------------------------------------------
        -- Update Vendor Sales on UI
        ---------------------------------------------------------------------
        if GC.GetFromDb("display", "showValueSoldToVendor") == true then
            local formattedTotalVendorSoldCurrency = GC.Session
                                                         .GetCurrentSession(
                                                         "vendorSoldCurrencyUI") or
                                                         0
            local totalSoldValue = formattedTotalVendorSoldCurrency +
                                       totalItemValueOfGrays or 0
            GC.Debug.Log("totalSoldValue: " .. totalSoldValue)
            GC.Session.SetCurrentSession("vendorSoldCurrencyUI", totalSoldValue)
            GC.Debug.Log("Set Session: " ..
                             GC.Session
                                 .GetCurrentSession("vendorSoldCurrencyUI"))
            private.HandleVendorSales(totalSoldValue)
        end
        ---------------------------------------------------------------------
        ---------------------------------------------------------------------

        if rarityCounter > 0 then
            GC:Print("Total Gray Sales: " ..
                         GetCoinTextureString(totalItemValueOfGrays))
        end

    else
        GC:Print("You must have a merchant open before selling.")
    end
end

---------------------------------------------------------------------
-- handle vendor sales currency
---------------------------------------------------------------------
function private.HandleVendorSales(totalSoldValue)
    GC.Debug.Log("handle vendor sales: " .. totalSoldValue)
    -- add to total looted currency
    -- GC.Session.private.totalLootedCurrency = GC.Session.private.totalLootedCurrency + lootedCopper
    local totalVendorSalesCurrency = (GC.Session.GetCurrentSession(
                                         "vendorSoldCurrencyUI")) or 0
    GC.Debug.Log("total value: " .. totalVendorSalesCurrency)
    GC.Session.SetCurrentSession("vendorSoldCurrencyUI",
                                 totalVendorSalesCurrency)
    -- GC.UI.RefreshUIs()
end
---------------------------------------------------------------------
---------------------------------------------------------------------

function private.GetItemValue(itemID, priceSource)
    if (GC.Util.startsWith(GC.CONST.PRICE_SOURCE[GC.GetFromDb("pricesource",
                                                              "source")], "OE:") or
        (GC.Util.startsWith(
            GC.CONST.PRICE_SOURCE[GC.GetFromDb("pricesource", "source")], "AN:")) or
        (GC.Util.startsWith(
            GC.CONST.PRICE_SOURCE[GC.GetFromDb("pricesource", "source")],
            "BLIZ:"))) then

        if (priceSource == "VendorSell") then
            local VendorSell = select(11, GetItemInfo(itemID)) or 0
            return VendorSell

            -- Blizzard's native pricing for vendor selling
        elseif priceSource == "VendorValue" then
            local BlizzardVendorSell = select(11, GetItemInfo(itemID)) or 0
            return BlizzardVendorSell

            -- new catch for OribosExchange pricing sources	
        elseif priceSource == "region" then
            local priceInfo = {}
            OEMarketInfo(itemID, priceInfo)
            return priceInfo[priceSource]

            -- Auctionator pricing
        elseif priceSource == "Auctionator" then
            -- Usage Auctionator.API.v1.GetAuctionPriceByItemID(string, number)
            return Auctionator.API.v1.GetAuctionPriceByItemID(
                       "GallywixCoins", itemID)

        else
            -- battle pet handling
            local newItemID = GC.PetData.ItemID2Species(itemID)
            if newItemID == itemID then
                local priceInfo = {}
                return priceInfo[priceSource]
            else
                local priceInfo = {}
                return priceInfo[priceSource]
            end
        end
    else
        -- TSM price source
        -- GC.Debug.Log("GetItemValue Here: " .. tostring(priceSource))
        return GC.TSM.GetItemValue(itemID, priceSource)
    end
end

-- Event handler for button 'destroy trash'
function private.OnBtnDestroyTrashClick()
    local destroyCounter = 0

    for bag = 0, 4 do
        for slot = 1, C_Container.GetContainerNumSlots(bag) do
            local link = C_Container.GetContainerItemLink(bag, slot)

            -- grey items
            if link and link:find("cnIQ0") then -- Poor = ff9d9d9d
                C_Container.PickupContainerItem(bag, slot)
                DeleteCursorItem()

                destroyCounter = destroyCounter + 1
            end

            -- blacklist
            if link and private.IsDestroyBlacklistedItems() then
                local itemID = GC.Util.ToItemID(link)
                if GC.IsItemBlacklisted(itemID) then
                    C_Container.PickupContainerItem(bag, slot)
                    DeleteCursorItem()

                    destroyCounter = destroyCounter + 1
                end
            end
        end
    end

    if destroyCounter == 0 then
        GC:Print("There are currently no items to trash.")
    end

    if destroyCounter >= 1 then
        GC:Print("Destroyed " .. destroyCounter .. " item(s).")
    end
end

-- add a row with label and value to the frame
function private.DefineRowForFrame(frame, id, name, value, availableWidth)
    GC.Debug.Log("  defineRowForFrame: id=%s, name=%s, value=%s", id, name,
                 value)

    if not private.IsDisplayEnabled(id) or frame == nil then
        GC.Debug.Log("  -> not visible")
        return
    end

    local totalWidth = availableWidth or private.GetInnerWidth()
    local labelWidth = math.floor(totalWidth * 0.40)
    local valueWidth = totalWidth - labelWidth

    local grp = AceGUI:Create("SimpleGroup")
    grp:SetLayout("flow")
    grp:SetFullWidth(true)
    frame:AddChild(grp)

    local font = private.GetBlizzardFont()
    local fontSize = private.GetFontSize()
    local flags = "" -- thin border/flags

    -- add label...
    local label = AceGUI:Create("GCLabel")
    label:SetFont(font, fontSize, flags) -- must be before SetText so UpdateImageAnchor uses correct height
    label:SetText(name)
    label:SetWidth(labelWidth)
    label:SetJustifyH("LEFT")
    grp:AddChild(label)

    -- ...and value
    local VALUE = AceGUI:Create("GCLabel")
    VALUE:SetFont(font, fontSize, flags)
    VALUE:SetText(value)
    VALUE:SetWidth(valueWidth)
    VALUE:SetJustifyH("RIGHT")
    grp:AddChild(VALUE)

    return VALUE
end

-- add a blank line to the given frame
function private.AddSpacer(frame)
    local SPACER = AceGUI:Create("GCLabel")
    SPACER:SetJustifyH("LEFT")
    SPACER:SetText("   ")
    SPACER:SetWidth(private.GetInnerWidth())
    frame:AddChild(SPACER)
end

function private.IsDestroyBlacklistedItems()
    if not GC.TSM.IsTSMLoaded() then return false end

    if GC.db.profile.blacklist.addBlacklistedItems2DestroyTrash then
        return true
    end
    return false
end

-- calculate looted item value / hour
-- OLD /gph
-- function private.CalcLootedItemValuePerHour(key)
--    local offset = GC.Session.GetPauseStart() or time()
--	local delta = offset - GC.Session.GetCurrentSession("start") - GC.Session.GetSessionPause()
--	local factor = 3600	--1 hour in seconds (3600)
--
--    if delta < factor then
--        factor = delta
--    end
--
--    local totalItemValue = GC.Session.GetCurrentSession(key) or 0
--    local livGoldPerHour
--	
--    if totalItemValue == 0 then
--        livGoldPerHour = 0
--    else
--        local livPerHour = (totalItemValue/delta*factor)
--        livGoldPerHour = floor(livPerHour/10000)
--    end
--    return tostring(livGoldPerHour)
-- end

-- Calculate looted item value + raw gold per hour, returns raw copper integer
-- Used for GetCoinTextureString display
function private.CalcLootedItemValuePerHourRaw()
    if not GC.Session.IsRunning() then return 0 end
    local session = GC.Session.GetCurrentSession()
    if not session then return 0 end

    local offset = GC.Session.GetPauseStart() or time()
    local delta = offset - session.start - GC.Session.GetSessionPause()
    if delta <= 0 then return 0 end

    -- Combine item value + raw gold drops
    local totalCopper = (session.liv or 0) + (session.totalLootedCurrency or 0)
    if totalCopper == 0 then return 0 end

    -- Gold per hour in copper
    local copperPerHour = (totalCopper / delta) * 3600
    return math.floor(copperPerHour)
end

-- Calculate looted item value / hour (legacy, returns formatted string via MoneyToString)
function private.CalcLootedItemValuePerHour(key)
    local offset = GC.Session.GetPauseStart() or time()
    local delta = offset - GC.Session.GetCurrentSession("start") -
                      GC.Session.GetSessionPause()

    local totalItemValue = GC.Session.GetCurrentSession(key) or 0
    -- GC.Debug.Log("total value " .. totalItemValue)
    local livGoldPerHour
    if (totalItemValue == 0 or delta == 0 or totalItemValue / delta < 1) then
        livGoldPerHour = 0
    else
        local copperPerSec = totalItemValue / delta
        livGoldPerHour = copperPerSec * 60 * 60
    end

    return GC.Util.MoneyToString(livGoldPerHour)
end

function private.IsDisplayEnabled(name)
    if GC.db.profile.display[name] == nil then
        GC.db.profile.display[name] = GC.dbDefaults.profile.display[name]
    end

    return GC.db.profile.display[name]
end



