-- TOG Tools — Whisper Log sub-tab (UI)
-- Renders inside the Logs outer tab's nested AceGUI TabGroup. Layout:
--   Filter row 1:  Character ▾   Direction ▾   Type ▾   GM ▾   Date range ▾
--   Filter row 2:  Partner: [edit]    Text: [edit]
--   Data table:    Time · Character · Other · Text
--
-- Filter state lives on the module table so it survives Draw rebuilds
-- triggered by capture refresh, but resets on /reload. Partner and Text
-- search boxes apply OnEnterPressed (Enter or the green check) — not on
-- every keystroke, to avoid rebuilding the table per character typed.

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

local WhisperLog = addon.logCategories["whispers"]

WhisperLog._filterPlayer       = WhisperLog._filterPlayer       or nil
WhisperLog._filterDir          = WhisperLog._filterDir          or nil
WhisperLog._filterType         = WhisperLog._filterType         or nil
WhisperLog._filterGM           = WhisperLog._filterGM           or nil
WhisperLog._filterPartner      = WhisperLog._filterPartner      or ""
WhisperLog._filterText         = WhisperLog._filterText         or ""
WhisperLog._filterRange        = WhisperLog._filterRange        or addon.DateRangePicker.DEFAULT_KEY
WhisperLog._filterCustomFrom   = WhisperLog._filterCustomFrom   or nil
WhisperLog._filterCustomTo     = WhisperLog._filterCustomTo     or nil
WhisperLog._filterCalendarOpen = WhisperLog._filterCalendarOpen or false

-- ---------------------------------------------------------------------------
-- Cell formatters
-- ---------------------------------------------------------------------------
local function fmtTime(ts)
    if not ts or ts <= 0 then return "—" end
    local now = (GetServerTime and GetServerTime()) or time()
    local diff = now - ts
    if diff < 60      then return string.format("%ds ago", math.floor(diff)) end
    if diff < 3600    then return string.format("%dm ago", math.floor(diff / 60)) end
    if diff < 86400   then return string.format("%dh ago", math.floor(diff / 3600)) end
    if diff < 604800  then return string.format("%dd ago", math.floor(diff / 86400)) end
    return date("%m/%d/%y", ts)
end

-- Other column folds direction + isBN + isGM annotation into a single cell
-- to keep the table compact. Green arrow = incoming, orange = outgoing,
-- purple [BN] tag for Battle.net whispers, red [GM] for Blizzard staff.
--
-- For in-game whispers, the partner name is wrapped in a |Hplayer:NAME|h
-- hyperlink so left-click opens a whisper edit box and right-click pops
-- the standard chat-name context menu (whisper / invite / inspect /
-- ignore / report). RowList.lua routes player links through SetItemRef
-- which has version-specific dispatch baked in. Skipped for BN whispers
-- (different link type — |HBNplayer:|h — and cross-version BN routing
-- is fragile enough that v1 just renders BN partners as plain text) and
-- for unknown senders ("?").
local function fmtOther(e)
    local other = e.other or "?"
    local prefix
    if e.dir == "in"  then prefix = "|cff66ff66<-|r "
    elseif e.dir == "out" then prefix = "|cffffaa55->|r "
    else                       prefix = "" end
    local tags = ""
    if e.isBN then tags = tags .. "|cffaa44ff[BN]|r " end
    if e.isGM then tags = tags .. "|cffff5555[GM]|r " end
    local nameCell
    if not e.isBN and other ~= "?" and other ~= "" then
        nameCell = "|Hplayer:" .. other .. "|h[" .. other .. "]|h"
    else
        nameCell = other
    end
    return prefix .. tags .. nameCell
end

local function fmtText(text)
    if not text or text == "" then return "|cff888888—|r" end
    return text
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(WhisperLog:GetPlayerKeys()) do
        list[k] = k
        tinsert(order, k)
    end
    dd:SetList(list, order)
    dd:SetValue(WhisperLog._filterPlayer or "__all")
    dd:SetCallback("OnValueChanged", function(_, _, key)
        WhisperLog._filterPlayer = (key == "__all") and nil or key
        onChange()
    end)
    return dd
end

local function buildDirDropdown(onChange)
    local dd = AceGUI:Create("Dropdown")
    dd:SetLabel("Direction")
    dd:SetWidth(120)
    dd:SetList({ __both = "Both", ["in"] = "Received", ["out"] = "Sent" },
               { "__both", "in", "out" })
    dd:SetValue(WhisperLog._filterDir or "__both")
    dd:SetCallback("OnValueChanged", function(_, _, key)
        WhisperLog._filterDir = (key == "__both") and nil or key
        onChange()
    end)
    return dd
end

local function buildTypeDropdown(onChange)
    local dd = AceGUI:Create("Dropdown")
    dd:SetLabel("Type")
    dd:SetWidth(130)
    dd:SetList({ __both = "Both", ingame = "In-game", bn = "Battle.net" },
               { "__both", "ingame", "bn" })
    dd:SetValue(WhisperLog._filterType or "__both")
    dd:SetCallback("OnValueChanged", function(_, _, key)
        WhisperLog._filterType = (key == "__both") and nil or key
        onChange()
    end)
    return dd
end

local function buildGMDropdown(onChange)
    local dd = AceGUI:Create("Dropdown")
    dd:SetLabel("GM")
    dd:SetWidth(110)
    dd:SetList({ __all = "All", nogm = "Hide GM", gm = "GM only" },
               { "__all", "nogm", "gm" })
    dd:SetValue(WhisperLog._filterGM or "__all")
    dd:SetCallback("OnValueChanged", function(_, _, key)
        WhisperLog._filterGM = (key == "__all") and nil or key
        onChange()
    end)
    return dd
end

local function buildPartnerEdit(onChange)
    local eb = AceGUI:Create("EditBox")
    eb:SetLabel("Partner (substring)")
    eb:SetWidth(200)
    eb:SetText(WhisperLog._filterPartner or "")
    eb:SetCallback("OnEnterPressed", function(_, _, val)
        WhisperLog._filterPartner = strtrim(val or "")
        onChange()
    end)
    return eb
end

local function buildTextEdit(onChange)
    local eb = AceGUI:Create("EditBox")
    eb:SetLabel("Text search (substring)")
    eb:SetWidth(260)
    eb:SetText(WhisperLog._filterText or "")
    eb:SetCallback("OnEnterPressed", function(_, _, val)
        WhisperLog._filterText = strtrim(val or "")
        onChange()
    end)
    return eb
end

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

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

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

    filterRow:AddChild(buildPlayerDropdown(rebuild))
    filterRow:AddChild(buildDirDropdown(rebuild))
    filterRow:AddChild(buildTypeDropdown(rebuild))
    filterRow:AddChild(buildGMDropdown(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 = WhisperLog:GetEntries(self._filterPlayer, self._filterDir,
            self._filterType, self._filterGM, self._filterPartner, self._filterText,
            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

    -- ---- Filter row 2: text-search edit boxes ---------------------------
    local searchRow = AceGUI:Create("SimpleGroup")
    searchRow:SetFullWidth(true)
    searchRow:SetLayout("Flow")
    container:AddChild(searchRow)

    searchRow:AddChild(buildPartnerEdit(rebuild))
    searchRow:AddChild(buildTextEdit(rebuild))

    -- Spacer between filter rows 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)

    local fromTs, toTs = addon.DateRangePicker:Resolve(
        self._filterRange, self._filterCustomFrom, self._filterCustomTo)
    local entries = WhisperLog:GetEntries(self._filterPlayer, self._filterDir,
        self._filterType, self._filterGM, self._filterPartner, self._filterText,
        fromTs, toTs)

    if #entries == 0 then
        local empty = AceGUI:Create("Label")
        empty:SetFullWidth(true)
        empty:SetText("|cff888888No whisper entries match the current filters.|r\n\nWhisper Log captures every whisper sent or received (in-game and Battle.net). Try widening the date range, clearing the |cffffd700Partner|r and |cffffd700Text|r boxes, or selecting |cffffd700All characters|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 whisper was sent or received. Recent entries show as \"Nm/h/d ago\"; older entries fall back to MM/DD/YY. Click to sort by time. Column auto-sizes to fit the longest visible timestamp.",
              format = function(e) return fmtTime(e.ts) end },

            { key = "player", header = "Character", autoFit = true, minWidth = 60, justify = "LEFT",
              headerTip = "Character",
              headerTipDesc = "The alt that sent or received this whisper. Whisper Log is account-wide; use the Character filter dropdown above to narrow to one alt. Column auto-sizes to fit the longest visible name.",
              format = function(e) return e.player or "?" end },

            { key = "other", header = "Other", autoFit = true, minWidth = 80, justify = "LEFT",
              headerTip = "Other",
              headerTipDesc = "The other side of the whisper. Green |cff66ff66<-|r prefix = received from this player; orange |cffffaa55->|r prefix = sent to this player. Purple |cffaa44ff[BN]|r tag = Battle.net whisper; red |cffff5555[GM]|r tag = Blizzard GM whisper. For in-game partners, |cffffd700left-click|r the bracketed name to open a whisper edit box, |cffffd700right-click|r to pop the standard chat-name menu (whisper / invite / inspect / ignore / report). Battle.net partners render as plain text in this version. Use the Partner edit box above for substring search.",
              format = function(e) return fmtOther(e) end },

            { key = "text", header = "Message", justify = "LEFT",
              headerTip = "Message",
              headerTipDesc = "The whisper body. This is the rightmost stretchy column — the only column that may truncate when the window is too narrow; every other column auto-sizes to fit its content. Use the Text search box above to find specific phrases (case-insensitive substring match).",
              format = function(e) return fmtText(e.text) end },
        },
    })
    rl:SetSort("ts", true)
    rl:SetData(entries)

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