-- TOG Tools — Gratz tab (UI)
--
-- Profile manager rendered FGI-Filters-style: a multi-row top form
-- with Name + Trigger + criteria + Channels + Message + Cooldown +
-- Save, and a RowList below showing every persisted profile. Click a
-- row to load it into the form for editing; Save with an existing
-- name overwrites in place, a new name appends a row.
--
-- Trigger-conditional fields: the criteria row reshapes based on
-- the Trigger dropdown — achievement profiles show scope + min
-- points; level-up profiles show min/max level (+ "every Nth" for
-- the guild flavour). Each Save call writes the full schema regardless
-- of which fields the user could see, so toggling trigger types
-- doesn't lose previously-set values for the other type.

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

local Tab = addon.modules["gratz"]
Tab.tabKey      = "gratz"
Tab.configKey   = "gratz"
Tab.label       = "Gratz"
Tab.WINDOW_SIZE = { width = 720, height = 540, minWidth = 640, minHeight = 440 }
Tab.help = {
    title = "Gratz",
    lines = {
        "Send auto-congratulations on triggered events. Build a named profile in the form above, then tick |cffffd700On|r in the list to activate it.",
        " ",
        "Trigger types: Achievement (a player earns one), Guild achievement (your guild itself earns one), Level up (party), Level up (guild). Pick one per profile.",
        " ",
        "Template placeholders: [player], [guild], [level], [achievement], [link], [points], [category].",
        " ",
        "Skip-self is automatic. Cooldowns (default 300s, per profile + target + event) absorb duplicate events. Messages composed during combat queue and flush when combat ends.",
        " ",
        "|cffffd700Hover any form field or column header for detail.|r",
    },
}

-- ---------------------------------------------------------------------------
-- Form state (per session, not persisted)
-- ---------------------------------------------------------------------------
-- `editing` is the working copy of the form: the profile currently in
-- the editor. nil means "no profile loaded — fresh from defaults".
Tab._editing = Tab._editing or nil

local function workingCopy()
    if Tab._editing then return Tab._editing end
    Tab._editing = Tab:NewProfileDefaults()
    return Tab._editing
end

local function loadIntoForm(profile)
    -- Deep-ish copy so editing the form doesn't mutate the persisted
    -- profile before Save is clicked.
    local copy = {
        name          = profile.name or "",
        enabled       = profile.enabled ~= false,
        trigger       = profile.trigger or "achievement",
        scope         = profile.scope or "guild",
        minPoints     = profile.minPoints or 0,
        minLevel      = profile.minLevel or 1,
        maxLevel      = profile.maxLevel or 999,
        guildInterval = profile.guildInterval or 1,
        message       = profile.message or "",
        channels      = {},
        cooldown      = profile.cooldown or 300,
        count         = profile.count or 0,
    }
    if profile.channels then
        for k, v in pairs(profile.channels) do copy.channels[k] = v end
    end
    Tab._editing = copy
end

local function resetForm()
    Tab._editing = nil
end

-- ---------------------------------------------------------------------------
-- Trigger metadata
-- ---------------------------------------------------------------------------
local TRIGGER_LABEL = {
    ["achievement"]        = "Achievement",
    ["achievement-guild"]  = "Guild achievement",
    ["levelup-party"]      = "Level up (party)",
    ["levelup-guild"]      = "Level up (guild)",
}
local TRIGGER_ORDER = { "achievement", "achievement-guild", "levelup-party", "levelup-guild" }

local SCOPE_LABEL = { guild = "Guild only", any = "Anyone visible" }
local SCOPE_ORDER = { "guild", "any" }

local CHANNEL_LABEL = {
    guild = "Guild", officer = "Officer", party = "Party", raid = "Raid",
    say = "Say", yell = "Yell", instance = "Instance",
}

-- ---------------------------------------------------------------------------
-- Cell formatters for the RowList
-- ---------------------------------------------------------------------------
local function fmtTrigger(p)
    return TRIGGER_LABEL[p.trigger] or p.trigger or "?"
end

local function fmtChannels(p)
    if not p.channels then return "|cff888888—|r" end
    local on = {}
    for _, key in ipairs(addon.modules["gratz"].CHANNEL_ORDER or {
        "guild", "officer", "party", "raid", "say", "yell", "instance",
    }) do
        if p.channels[key] then tinsert(on, CHANNEL_LABEL[key] or key) end
    end
    if #on == 0 then return "|cff888888none|r" end
    return table.concat(on, ", ")
end

-- ---------------------------------------------------------------------------
-- Tooltip helper — wires OnEnter/OnLeave on the AceGUI widget itself via
-- addon.GUI.AttachTooltip (SetCallback-based — AceGUI clears the callback
-- on widget Release, so the handler doesn't leak into the next consumer
-- of the same pooled widget frame). Used to use addon.UI.AttachTooltip
-- (HookScript-based), but that leaked our handlers onto AceConfigDialog's
-- Settings panel toggles when AceGUI recycled the underlying CheckBox /
-- Dropdown / Button frames — caused option tooltips to render twice
-- (v0.5.2 fix).
-- ---------------------------------------------------------------------------
local function attachTip(widget, title, body)
    if widget and addon.GUI and addon.GUI.AttachTooltip then
        addon.GUI.AttachTooltip(widget, title, body)
    end
end

-- Per-channel tooltip body. Same template, different channel name.
local CHANNEL_TIP = {
    guild    = "Send to your guild chat. Skipped silently when you're not in a guild.",
    officer  = "Send to your officer chat. Skipped silently when you're not in a guild (or don't have officer permissions).",
    party    = "Send to your current party. Skipped silently when you're not in a group.",
    raid     = "Send to your current raid. Skipped silently when you're not in a raid.",
    say      = "Send to /say in your current zone. Always works, no group requirement.",
    yell     = "Send to /yell in your current zone. Always works, no group requirement.",
    instance = "Send to instance chat (battlegrounds, dungeons, raids). Skipped silently when not in an instance group.",
}

-- ---------------------------------------------------------------------------
-- Form widgets — build once, re-populated on every Draw
-- ---------------------------------------------------------------------------
local function buildForm(parent, onSave, onDelete, onReset, refreshList)
    local Gratz = addon.modules["gratz"]
    local p = workingCopy()

    -- Row 1: Name, Trigger, Save, Delete (delete visible only when
    -- editing an existing named profile), New
    local row1 = AceGUI:Create("SimpleGroup")
    row1:SetFullWidth(true)
    row1:SetLayout("Flow")
    parent:AddChild(row1)

    local nameInput = AceGUI:Create("EditBox")
    nameInput:SetLabel("Profile name")
    nameInput:SetWidth(200)
    nameInput:SetText(p.name or "")
    nameInput:SetCallback("OnEnterPressed", function(_, _, val)
        p.name = val
        nameInput:ClearFocus()
    end)
    nameInput:SetCallback("OnTextChanged", function(_, _, val) p.name = val end)
    row1:AddChild(nameInput)
    attachTip(nameInput, "Profile name",
        "Unique identifier for this profile. Saving with an existing name OVERWRITES that profile in place — useful for editing. A new name creates a new row in the list below.")

    local triggerDD = AceGUI:Create("Dropdown")
    triggerDD:SetLabel("Trigger")
    triggerDD:SetWidth(180)
    local triggerList = {}
    for k, v in pairs(TRIGGER_LABEL) do triggerList[k] = v end
    triggerDD:SetList(triggerList, TRIGGER_ORDER)
    triggerDD:SetValue(p.trigger)
    triggerDD:SetCallback("OnValueChanged", function(_, _, key)
        p.trigger = key
        -- Rebuild the form so the trigger-conditional row reshapes.
        refreshList()
    end)
    row1:AddChild(triggerDD)
    attachTip(triggerDD, "Trigger",
        "Which event makes this profile fire. " ..
        "|cffffd700Achievement|r (Retail only): fires when an INDIVIDUAL player earns an achievement. Scope filter chooses guild-only vs anyone-visible. " ..
        "|cffffd700Guild achievement|r (Retail only): fires when your GUILD ITSELF earns an achievement (e.g. \"Classy Undead\", \"Working as a Team\"). [player] resolves to the guild name. " ..
        "|cffffd700Level up (party)|r: fires when a party member dings — works on every WoW version. " ..
        "|cffffd700Level up (guild)|r: fires when a guildmate dings — polls the guild roster every 60 s. " ..
        "Changing trigger reshapes the criteria row below.")

    local saveBtn = AceGUI:Create("Button")
    saveBtn:SetWidth(80)
    saveBtn:SetText("Save")
    saveBtn:SetCallback("OnClick", function() onSave() end)
    row1:AddChild(saveBtn)
    attachTip(saveBtn, "Save",
        "Persist the form's current state as a profile. New name = new row in the list. Existing name = overwrite. A profile name is required.")

    local newBtn = AceGUI:Create("Button")
    newBtn:SetWidth(80)
    newBtn:SetText("New")
    newBtn:SetCallback("OnClick", function() onReset() end)
    row1:AddChild(newBtn)
    attachTip(newBtn, "New profile",
        "Reset the form to default values so you can compose a fresh profile. Doesn't delete the currently-loaded profile from the list — just clears the form.")

    if p.name and p.name ~= "" and Gratz:GetProfile(p.name) then
        local delBtn = AceGUI:Create("Button")
        delBtn:SetWidth(80)
        delBtn:SetText("Delete")
        delBtn:SetCallback("OnClick", function() onDelete(p.name) end)
        row1:AddChild(delBtn)
        attachTip(delBtn, "Delete profile",
            "Permanently remove the currently-loaded profile from the list. Only visible when the form is loaded with an existing profile.")
    end

    -- Row 2: trigger-conditional criteria
    local row2 = AceGUI:Create("SimpleGroup")
    row2:SetFullWidth(true)
    row2:SetLayout("Flow")
    parent:AddChild(row2)

    if p.trigger == "achievement" then
        local scopeDD = AceGUI:Create("Dropdown")
        scopeDD:SetLabel("Scope")
        scopeDD:SetWidth(180)
        scopeDD:SetList(SCOPE_LABEL, SCOPE_ORDER)
        scopeDD:SetValue(p.scope)
        scopeDD:SetCallback("OnValueChanged", function(_, _, key) p.scope = key end)
        row2:AddChild(scopeDD)
        attachTip(scopeDD, "Scope",
            "Whose achievements count. " ..
            "|cffffd700Guild only|r: fires only when the player is in your guild (looked up against the current guild roster). " ..
            "|cffffd700Anyone visible|r: fires for any achievement CHAT_MSG_ACHIEVEMENT delivers (your own guildies + anyone nearby on Retail). Useful for public-channel celebration.")

        local pointsInput = AceGUI:Create("EditBox")
        pointsInput:SetLabel("Min points")
        pointsInput:SetWidth(100)
        pointsInput:SetText(tostring(p.minPoints or 0))
        pointsInput:SetCallback("OnEnterPressed", function(_, _, val)
            p.minPoints = tonumber(val) or 0
            pointsInput:SetText(tostring(p.minPoints))
            pointsInput:ClearFocus()
        end)
        row2:AddChild(pointsInput)
        attachTip(pointsInput, "Min points",
            "Filter out small achievements. 0 = any achievement. Most account/mount/raid achievements are 10+ points; trivial ones are 5 or less. Set to 10 to skip easy stuff and only celebrate meaningful achievements.")
    elseif p.trigger == "achievement-guild" then
        -- Guild-as-entity achievements have no "scope" choice (the
        -- sender IS the guild). Only the minPoints filter applies.
        local pointsInput = AceGUI:Create("EditBox")
        pointsInput:SetLabel("Min points")
        pointsInput:SetWidth(100)
        pointsInput:SetText(tostring(p.minPoints or 0))
        pointsInput:SetCallback("OnEnterPressed", function(_, _, val)
            p.minPoints = tonumber(val) or 0
            pointsInput:SetText(tostring(p.minPoints))
            pointsInput:ClearFocus()
        end)
        row2:AddChild(pointsInput)
        attachTip(pointsInput, "Min points",
            "Filter out small guild achievements. 0 = any. Set higher to only celebrate the meaningful guild-wide accomplishments.")
    else
        -- levelup-party / levelup-guild share min/max; guild has interval
        local minInput = AceGUI:Create("EditBox")
        minInput:SetLabel("Min lvl")
        minInput:SetWidth(80)
        minInput:SetText(tostring(p.minLevel or 1))
        minInput:SetCallback("OnEnterPressed", function(_, _, val)
            p.minLevel = tonumber(val) or 1
            minInput:SetText(tostring(p.minLevel))
            minInput:ClearFocus()
        end)
        row2:AddChild(minInput)
        attachTip(minInput, "Min level",
            "Only fire when the new level is at or above this number. Set to 1 to fire for every level-up.")

        local maxInput = AceGUI:Create("EditBox")
        maxInput:SetLabel("Max lvl")
        maxInput:SetWidth(80)
        maxInput:SetText(tostring(p.maxLevel or 999))
        maxInput:SetCallback("OnEnterPressed", function(_, _, val)
            p.maxLevel = tonumber(val) or 999
            maxInput:SetText(tostring(p.maxLevel))
            maxInput:ClearFocus()
        end)
        row2:AddChild(maxInput)
        attachTip(maxInput, "Max level",
            "Only fire when the new level is at or below this number. Useful for level-band profiles (e.g. 1-59 only). Set to 999 (the default) for no upper limit.")

        if p.trigger == "levelup-guild" then
            local intInput = AceGUI:Create("EditBox")
            intInput:SetLabel("Only every Nth")
            intInput:SetWidth(110)
            intInput:SetText(tostring(p.guildInterval or 1))
            intInput:SetCallback("OnEnterPressed", function(_, _, val)
                p.guildInterval = math.max(1, tonumber(val) or 1)
                intInput:SetText(tostring(p.guildInterval))
                intInput:ClearFocus()
            end)
            row2:AddChild(intInput)
            attachTip(intInput, "Only every Nth level",
                "Milestone filter for guild level-ups. " ..
                "1 = every level. " ..
                "5 = only levels 5, 10, 15, 20, ... " ..
                "10 = only levels 10, 20, 30, 40, ... " ..
                "Useful in a high-activity guild where gratzing every single level-up would flood chat.")
        end
    end

    local cooldownInput = AceGUI:Create("EditBox")
    cooldownInput:SetLabel("Cooldown (s)")
    cooldownInput:SetWidth(100)
    cooldownInput:SetText(tostring(p.cooldown or 300))
    cooldownInput:SetCallback("OnEnterPressed", function(_, _, val)
        p.cooldown = math.max(0, tonumber(val) or 0)
        cooldownInput:SetText(tostring(p.cooldown))
        cooldownInput:ClearFocus()
    end)
    row2:AddChild(cooldownInput)
    attachTip(cooldownInput, "Cooldown (seconds)",
        "Minimum gap between two firings of this profile for the same (target, event) pair. Prevents spam when an event accidentally duplicates — common around loading screens for CHAT_MSG_ACHIEVEMENT. " ..
        "Default 300 (5 min) is comfortable for achievements. " ..
        "Set 0 to disable. Cooldown state resets on /reload.")

    -- Row 3: Channels (7 checkboxes)
    local row3 = AceGUI:Create("SimpleGroup")
    row3:SetFullWidth(true)
    row3:SetLayout("Flow")
    parent:AddChild(row3)

    local chanLabel = AceGUI:Create("Label")
    chanLabel:SetWidth(70)
    chanLabel:SetText(addon.UI.Brand("Channels:"))
    row3:AddChild(chanLabel)

    for _, key in ipairs({ "guild", "officer", "party", "raid", "say", "yell", "instance" }) do
        local cb = AceGUI:Create("CheckBox")
        cb:SetWidth(80)
        cb:SetLabel(CHANNEL_LABEL[key])
        cb:SetValue(p.channels and p.channels[key] or false)
        cb:SetCallback("OnValueChanged", function(_, _, val)
            p.channels = p.channels or {}
            p.channels[key] = val and true or nil
        end)
        row3:AddChild(cb)
        attachTip(cb, CHANNEL_LABEL[key], CHANNEL_TIP[key])
    end

    -- Row 4: Message template (wide editbox)
    local row4 = AceGUI:Create("SimpleGroup")
    row4:SetFullWidth(true)
    row4:SetLayout("Flow")
    parent:AddChild(row4)

    local msgInput = AceGUI:Create("EditBox")
    msgInput:SetLabel("Message (placeholders: [player] [guild] [achievement] [link] [points] [category] [level])")
    msgInput:SetFullWidth(true)
    msgInput:SetText(p.message or "")
    msgInput:SetCallback("OnEnterPressed", function(_, _, val)
        p.message = val
        msgInput:ClearFocus()
    end)
    msgInput:SetCallback("OnTextChanged", function(_, _, val) p.message = val end)
    row4:AddChild(msgInput)
    attachTip(msgInput, "Message template",
        "The chat text sent when this profile fires. Use bracket placeholders to insert event data: " ..
        "|cffffd700[player]|r the person being gratzed (or the guild name for Guild achievement triggers); " ..
        "|cffffd700[guild]|r your current guild's name (works in every trigger); " ..
        "|cffffd700[achievement]|r achievement name (achievement triggers); " ..
        "|cffffd700[link]|r clickable achievement link (achievement triggers); " ..
        "|cffffd700[points]|r achievement point value (achievement triggers); " ..
        "|cffffd700[category]|r achievement category name (achievement triggers); " ..
        "|cffffd700[level]|r new level (level-up triggers). " ..
        "Unresolved placeholders become empty strings.")
end

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

    local Gratz = addon.modules["gratz"]

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

    local function onSave()
        local p = workingCopy()
        if not p.name or p.name == "" then
            addon.lib:Print("|cffff5555Gratz: profile name is required.|r")
            return
        end
        -- Persist as a normal table (drop the working-copy isolation).
        Gratz:SaveProfile({
            name          = p.name,
            enabled       = p.enabled ~= false,
            trigger       = p.trigger,
            scope         = p.scope,
            minPoints     = p.minPoints,
            minLevel      = p.minLevel,
            maxLevel      = p.maxLevel,
            guildInterval = p.guildInterval,
            message       = p.message,
            channels      = p.channels or {},
            cooldown      = p.cooldown,
            count         = p.count or 0,
        })
        resetForm()
        rebuild()
    end

    local function onDelete(name)
        Gratz:DeleteProfile(name)
        resetForm()
        rebuild()
    end

    local function onReset()
        resetForm()
        rebuild()
    end

    -- Top form
    buildForm(container, onSave, onDelete, onReset, rebuild)

    -- Spacer
    local sp = AceGUI:Create("Label")
    sp:SetFullWidth(true); sp:SetText(" ")
    container:AddChild(sp)

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

    local profiles = Gratz:GetProfiles()
    if #profiles == 0 then
        local empty = AceGUI:Create("Label")
        empty:SetFullWidth(true)
        empty:SetText("|cff888888No Gratz profiles yet.|r\n\nFill in the form above and click |cffffd700Save|r to create your first profile. Pick a trigger type, set criteria, choose channels, and write a message using the |cffffd700[placeholders]|r. Skip-self is automatic — the engine never congratulates you on your own events.")
        body:AddChild(empty)
        return
    end

    local rl = addon.RowList:New(body.content, {
        rowHeight = 18,
        columns = {
            { key = "enabled", header = "On", width = 30, justify = "CENTER",
              checkbox = true, boxSize = 18,
              headerTip = "On",
              headerTipDesc = "Tick to enable this profile. Disabled profiles stay in the list but their engine path short-circuits — they don't fire even if the trigger event happens. Toggling here saves immediately.",
              onToggle = function(entry, val)
                  entry.enabled = val
                  addon.modules["gratz"]:SaveProfile(entry)
              end },

            { key = "name", header = "Name", autoFit = true, minWidth = 70, justify = "LEFT",
              headerTip = "Name",
              headerTipDesc = "Profile identifier. Click the row to load this profile into the form above for editing. Column auto-sizes to fit the longest visible profile name.",
              format = function(p) return p.name or "?" end },

            { key = "trigger", header = "Trigger", autoFit = true, minWidth = 70, justify = "LEFT",
              headerTip = "Trigger",
              headerTipDesc = "What event the profile reacts to. Column auto-sizes to fit the longest visible trigger label.",
              format = function(p) return fmtTrigger(p) end },

            { key = "channels", header = "Channels", autoFit = true, minWidth = 80, justify = "LEFT",
              headerTip = "Channels",
              headerTipDesc = "Comma-separated list of chat destinations the gratz fires on. Channels the player isn't connected to (e.g. Party when solo) are silently skipped at fire time. Column auto-sizes to fit the longest visible channel list.",
              format = function(p) return fmtChannels(p) end },

            { key = "count", header = "Count", autoFit = true, minWidth = 40, justify = "RIGHT",
              headerTip = "Count",
              headerTipDesc = "Total times this profile has fired since creation. Column auto-sizes to fit the largest visible count.",
              sortDescDefault = true,
              format = function(p) return tostring(p.count or 0) end },
        },
        onRowClick = function(p)
            loadIntoForm(p)
            rebuild()
        end,
    })
    rl:SetSort("name", false)
    rl:SetData(profiles)

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