-- TOG Tools — Gathering Log sub-tab (UI)
-- Renders inside the Logs outer tab's nested AceGUI TabGroup. Layout:
--   Filter row:   Character ▾   Profession ▾   Zone ▾   Date range ▾
--   Stats panel:  totals + nodes/yield, per-profession, top items, by zone, rate
--   Data table:   Time · Character · Type · Item · Qty · Zone
--
-- Filter state lives on the module table so it survives Draw rebuilds
-- triggered by capture refresh, but resets on /reload. The stats panel is
-- computed from the SAME filtered+date-bounded entry set the table shows,
-- so the numbers always describe exactly what's on screen.

local _, addon = ...
local AceGUI = LibStub("AceGUI-3.0")

local GatheringLog = addon.logCategories["gathering"]

GatheringLog._filterPlayer       = GatheringLog._filterPlayer       or nil
GatheringLog._filterProf         = GatheringLog._filterProf         or nil
GatheringLog._filterZone         = GatheringLog._filterZone         or nil
GatheringLog._filterRange        = GatheringLog._filterRange        or addon.DateRangePicker.DEFAULT_KEY
GatheringLog._filterCustomFrom   = GatheringLog._filterCustomFrom   or nil
GatheringLog._filterCustomTo     = GatheringLog._filterCustomTo     or nil
GatheringLog._filterCalendarOpen = GatheringLog._filterCalendarOpen or false

-- ---------------------------------------------------------------------------
-- Cell formatters
-- ---------------------------------------------------------------------------
-- Absolute MM/DD/YY HH:MM:SS stamp (24-hour). Gathering bursts many nodes
-- in a session, so the exact second is more useful here than a relative
-- "Nm ago". date() formats `ts` in local time, matching the addon's other
-- timestamps (Debug output, chat).
local function fmtTime(ts)
    if not ts or ts <= 0 then return "—" end
    return date("%m/%d/%y %H:%M:%S", ts)
end

local function fmtItem(e)
    return e.link or e.name or ("item:" .. tostring(e.itemID))
end

-- ---------------------------------------------------------------------------
-- Filter widget builders
-- ---------------------------------------------------------------------------
local function buildPlayerDropdown(onChange)
    local dd = AceGUI:Create("Dropdown")
    dd:SetLabel("Character")
    dd:SetWidth(180)
    local list, order = { __all = "All characters" }, { "__all" }
    for _, k in ipairs(GatheringLog:GetPlayerKeys()) do
        list[k] = k
        tinsert(order, k)
    end
    dd:SetList(list, order)
    dd:SetValue(GatheringLog._filterPlayer or "__all")
    dd:SetCallback("OnValueChanged", function(_, _, key)
        GatheringLog._filterPlayer = (key == "__all") and nil or key
        onChange()
    end)
    return dd
end

local function buildProfDropdown(onChange)
    local dd = AceGUI:Create("Dropdown")
    dd:SetLabel("Profession")
    dd:SetWidth(150)
    local list, order = { __all = "All" }, { "__all" }
    for _, p in ipairs(GatheringLog:GetProfsPresent()) do
        list[p] = GatheringLog.PROF_LABEL[p] or p
        tinsert(order, p)
    end
    dd:SetList(list, order)
    dd:SetValue(GatheringLog._filterProf or "__all")
    dd:SetCallback("OnValueChanged", function(_, _, key)
        GatheringLog._filterProf = (key == "__all") and nil or key
        onChange()
    end)
    return dd
end

local function buildZoneDropdown(onChange)
    local dd = AceGUI:Create("Dropdown")
    dd:SetLabel("Zone")
    dd:SetWidth(200)
    local list, order = { __all = "All zones" }, { "__all" }
    for _, z in ipairs(GatheringLog:GetZones()) do
        list[z] = z
        tinsert(order, z)
    end
    dd:SetList(list, order)
    dd:SetValue(GatheringLog._filterZone or "__all")
    dd:SetCallback("OnValueChanged", function(_, _, key)
        GatheringLog._filterZone = (key == "__all") and nil or key
        onChange()
    end)
    return dd
end

-- ---------------------------------------------------------------------------
-- Stats panel
-- ---------------------------------------------------------------------------
-- Builds the multi-line summary shown above the table from the same entry
-- set the table renders. Covers all four requested stat views: per-item
-- totals, nodes-worked + per-node yield, by-zone, and items/hour.
local function buildStatsText(stats, filterProf)
    -- Per-action noun: when filtered to one profession use its noun
    -- (node / skin / catch), otherwise the generic "node".
    local noun
    if filterProf then
        noun = GatheringLog.PROF_NOUN[filterProf] or "node"
    else
        noun = "node"
    end
    local nounPl = noun .. "s"

    local avg = (stats.actions > 0) and (stats.totalItems / stats.actions) or 0
    local rate
    if stats.spanHours >= (1 / 60) then  -- need at least ~1 minute of span to be meaningful
        rate = string.format("%.0f", stats.totalItems / stats.spanHours)
    else
        rate = "—"
    end

    local lines = {}

    -- Summary line.
    tinsert(lines, string.format(
        "|cffffd700%d|r items from |cffffd700%d|r %s  (|cffffd700%.1f|r per %s)  ·  |cffffd700%s|r/hour",
        stats.totalItems, stats.actions, (stats.actions == 1 and noun or nounPl),
        avg, noun, rate))

    -- Per-profession line (only when more than one profession is in view).
    local profParts = {}
    for _, p in ipairs(GatheringLog.PROF_ORDER) do
        local pa = stats.perProf[p]
        if pa then
            tinsert(profParts, string.format("%s |cffffffff%d|r/%d",
                GatheringLog.PROF_LABEL[p] or p, pa.items, pa.actions))
        end
    end
    if #profParts > 1 then
        tinsert(lines, "|cff9d9d9dBy profession (items/" .. nounPl .. "):|r " ..
            table.concat(profParts, "  ·  "))
    end

    -- Top items (single compact line; cap to keep the panel short).
    local itemParts = {}
    for i = 1, math.min(8, #stats.perItem) do
        local it = stats.perItem[i]
        local label = it.link or it.name or "?"
        tinsert(itemParts, string.format("%s |cffffd700×%d|r", label, it.count))
    end
    if #itemParts > 0 then
        tinsert(lines, "|cff9d9d9dTop items:|r " .. table.concat(itemParts, ", "))
    end

    -- By zone (single compact line).
    local zoneParts = {}
    for i = 1, math.min(8, #stats.perZone) do
        local z = stats.perZone[i]
        tinsert(zoneParts, string.format("%s |cffffd700×%d|r", z.zone, z.count))
    end
    if #zoneParts > 0 then
        tinsert(lines, "|cff9d9d9dBy zone:|r " .. table.concat(zoneParts, ", "))
    end

    return table.concat(lines, "\n")
end

-- ---------------------------------------------------------------------------
-- Draw
-- ---------------------------------------------------------------------------
function GatheringLog:Draw(container)
    container:SetLayout("Flow")

    local function rebuild()
        if addon.MainWindow and addon.MainWindow.Refresh then
            addon.MainWindow:Refresh()
        end
    end

    -- ---- Filter row -----------------------------------------------------
    local filterRow = AceGUI:Create("SimpleGroup")
    filterRow:SetFullWidth(true)
    filterRow:SetLayout("Flow")
    container:AddChild(filterRow)

    filterRow:AddChild(buildPlayerDropdown(rebuild))
    filterRow:AddChild(buildProfDropdown(rebuild))
    filterRow:AddChild(buildZoneDropdown(rebuild))
    filterRow:AddChild(addon.DateRangePicker:Create({
        default  = self._filterRange,
        onChange = function(key)
            local enteringCustom = (key == "custom" and self._filterRange ~= "custom")
            self._filterRange = key
            if enteringCustom then self._filterCalendarOpen = true end
            rebuild()
        end,
    }))

    if self._filterRange == "custom" and self._filterCustomFrom
       and not self._filterCalendarOpen then
        local pickBtn = AceGUI:Create("Button")
        pickBtn:SetWidth(140)
        pickBtn:SetText("Pick range...")
        pickBtn:SetCallback("OnClick", function()
            self._filterCalendarOpen = true
            rebuild()
        end)
        filterRow:AddChild(pickBtn)
    end

    if self._filterRange == "custom"
       and (not self._filterCustomFrom or self._filterCalendarOpen) then
        local unbounded = GatheringLog:GetEntries(self._filterPlayer, self._filterProf,
            self._filterZone, nil, nil)
        local timestamps = {}
        for _, e in ipairs(unbounded) do tinsert(timestamps, e.ts) end
        container:AddChild(addon.DateRangePicker:CreateCalendar({
            defaultFrom = self._filterCustomFrom,
            defaultTo   = self._filterCustomTo,
            timestamps  = timestamps,
            onApply     = function(fromTs, toTs)
                self._filterCustomFrom   = fromTs
                self._filterCustomTo     = toTs
                self._filterCalendarOpen = false
                rebuild()
            end,
        }))
        if not self._filterCustomFrom then return end
    end

    -- ---- Resolve filtered data -----------------------------------------
    local fromTs, toTs = addon.DateRangePicker:Resolve(
        self._filterRange, self._filterCustomFrom, self._filterCustomTo)
    local entries = GatheringLog:GetEntries(self._filterPlayer, self._filterProf,
        self._filterZone, fromTs, toTs)

    -- ---- Stats panel ----------------------------------------------------
    if #entries > 0 then
        local stats = GatheringLog:ComputeStats(entries)
        local statsLabel = AceGUI:Create("Label")
        statsLabel:SetFullWidth(true)
        statsLabel:SetText(buildStatsText(stats, self._filterProf))
        container:AddChild(statsLabel)
    end

    -- Spacer between stats and the data table.
    local sp = AceGUI:Create("Label")
    sp:SetFullWidth(true); sp:SetText(" ")
    container:AddChild(sp)

    -- ---- Data table -----------------------------------------------------
    local body = AceGUI:Create("SimpleGroup")
    body:SetFullWidth(true)
    body:SetFullHeight(true)
    body:SetLayout("List")
    container:AddChild(body)

    if #entries == 0 then
        local empty = AceGUI:Create("Label")
        empty:SetFullWidth(true)
        empty:SetText("|cff888888No gathering entries match the current filters.|r\n\nThe Gathering Log fills in as you mine, herb, skin, and fish. Try widening the date range or selecting |cffffd700All characters|r / |cffffd700All zones|r.")
        body:AddChild(empty)
        return
    end

    local rl = addon.RowList:New(body.content, {
        rowHeight = 16,
        columns = {
            { key = "ts", header = "Time", autoFit = true, minWidth = 50, justify = "LEFT",
              sortKey = "ts", sortDescDefault = true,
              headerTip = "Time",
              headerTipDesc = "When the item was gathered, as a MM/DD/YY HH:MM:SS timestamp (24-hour, local time). Click to sort by time.",
              format = function(e) return fmtTime(e.ts) end },

            { key = "player", header = "Character", autoFit = true, minWidth = 60, justify = "LEFT",
              headerTip = "Character",
              headerTipDesc = "The alt that gathered this item. The Gathering Log is account-wide; use the Character filter above to narrow to one alt.",
              format = function(e) return e.player or "?" end },

            { key = "prof", header = "Type", autoFit = true, minWidth = 60, justify = "LEFT",
              headerTip = "Type",
              headerTipDesc = "Which gathering profession produced this drop — Mining, Herbalism, Skinning, or Fishing. Use the Profession filter above to narrow.",
              format = function(e) return GatheringLog.PROF_LABEL[e.prof] or e.prof or "?" end },

            { key = "name", header = "Item", justify = "LEFT",
              headerTip = "Item",
              headerTipDesc = "The gathered item. This is the stretchy column — hover for the tooltip, shift-click to link it into chat. Sorts alphabetically by name.",
              format = function(e) return fmtItem(e) end },

            { key = "count", header = "Qty", autoFit = true, minWidth = 36, justify = "RIGHT",
              sortKey = "count", sortDescDefault = true,
              headerTip = "Quantity",
              headerTipDesc = "How many of the item dropped in this pickup. Click to sort by quantity.",
              format = function(e) return tostring(e.count or 1) end },

            { key = "zone", header = "Zone", autoFit = true, minWidth = 70, justify = "LEFT",
              headerTip = "Zone",
              headerTipDesc = "Where the item was gathered. Use the Zone filter above to narrow to one area.",
              format = function(e) return e.zone or "—" end },
        },
    })
    rl:SetSort("ts", true)
    rl:SetData(entries)

    body:SetCallback("OnRelease", function() rl:Detach() end)
end
