-- AFKdetector.lua
-- Minimal AFK detector: first line = total inactive, second line (hover) = names with class color and minutes
AFKDetectorDB = AFKDetectorDB or {}
AFKDetectorActivityDB = AFKDetectorActivityDB or {}
AFKDetectorActivityDB.lastActive = AFKDetectorActivityDB.lastActive or {}

local addonName = "AFKdetector"
local frame = CreateFrame("Frame", addonName.."Frame", UIParent)
frame:SetSize(300, 30)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
frame:EnableMouse(true)
frame:SetMovable(true)
-- Do not register drag on the whole frame to avoid accidental drags
-- frame:RegisterForDrag("LeftButton")
frame:SetClampedToScreen(true)
-- Do not expand clickable area beyond visual bounds
frame:SetHitRectInsets(0, 0, 0, 0)

-- Use the custom font provided in the addon folder
local FONT_PATH = "Interface\\AddOns\\AFKdetector\\LXGWWenKaiTC-Bold.ttf"

-- Config
local INACTIVE_THRESHOLD = 300 -- seconds (default 5 minutes)
local UPDATE_INTERVAL = 1 -- seconds

-- Alignment helper: set anchors and justification depending on which half of the screen the frame center is in
local function UpdateAlignment()
    local centerX = frame:GetCenter()
    if not centerX then return end
    local screenWidth = UIParent:GetWidth() or GetScreenWidth()
    local leftSide = centerX <= (screenWidth / 2)

    frame.summary:ClearAllPoints()
    frame.detail:ClearAllPoints()

    if leftSide then
        frame.summary:SetPoint("TOPLEFT", frame, "TOPLEFT", 8, -6)
        frame.detail:SetPoint("TOPLEFT", frame.summary, "BOTTOMLEFT", 0, -6)
        frame.summary:SetJustifyH("LEFT")
        frame.detail:SetJustifyH("LEFT")
    else
        frame.summary:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -8, -6)
        frame.detail:SetPoint("TOPRIGHT", frame.summary, "BOTTOMRIGHT", 0, -6)
        frame.summary:SetJustifyH("RIGHT")
        frame.detail:SetJustifyH("RIGHT")
    end
end

-- Lines (font size 16)
frame.summary = frame:CreateFontString(nil, "OVERLAY")
frame.summary:SetFont(FONT_PATH, 16, "OUTLINE")
frame.summary:SetShadowOffset(1, -1)
frame.summary:SetShadowColor(0,0,0,0.8)
frame.summary:EnableMouse(true)

frame.detail = frame:CreateFontString(nil, "OVERLAY")
frame.detail:SetFont(FONT_PATH, 16, "OUTLINE")
frame.detail:SetShadowOffset(1, -1)
frame.detail:SetShadowColor(0,0,0,0.8)
frame.detail:SetNonSpaceWrap(true)
frame.detail:EnableMouse(false)

-- Start hidden
frame:Hide()
frame.detail:Hide()

-- Runtime tables
local lastActiveByName = {}
local trackedNames = {}
local nameToClass = {}
local prevGroupCount = GetNumGroupMembers() or 1

-- Load saved activity timestamps
local function LoadSaved()
    for name, ts in pairs(AFKDetectorActivityDB.lastActive) do
        if type(name) == "string" and type(ts) == "number" then
            lastActiveByName[name] = ts
        end
    end
end

local function SaveActivity(name, ts)
    if not name or name == "" then return end
    AFKDetectorActivityDB.lastActive[name] = ts
end

local function ResetActivity()
    wipe(lastActiveByName)
    wipe(AFKDetectorActivityDB.lastActive)
end

-- Helpers
local function IterateGroupUnits()
    local units = {}
    if IsInRaid() then
        for i = 1, GetNumGroupMembers() do table.insert(units, "raid"..i) end
    elseif IsInGroup() then
        table.insert(units, "player")
        for i = 1, GetNumSubgroupMembers() do table.insert(units, "party"..i) end
    else
        table.insert(units, "player")
    end
    return units
end

local function ClassColorCode(classFile)
    if RAID_CLASS_COLORS and RAID_CLASS_COLORS[classFile] then
        local c = RAID_CLASS_COLORS[classFile]
        if c.colorStr then
            return "|c" .. c.colorStr
        else
            return string.format("|cff%02x%02x%02x", math.floor((c.r or 1)*255), math.floor((c.g or 1)*255), math.floor((c.b or 1)*255))
        end
    end
    return "|cFFFFFFFF"
end

local function UpdateGroupTracking()
    wipe(trackedNames)
    wipe(nameToClass)
    local units = IterateGroupUnits()
    local now = GetTime()
    for _, unit in ipairs(units) do
        if UnitExists(unit) then
            local name = UnitName(unit)
            if name and name ~= "" then
                trackedNames[name] = true
                if not lastActiveByName[name] then
                    lastActiveByName[name] = now
                    SaveActivity(name, now)
                end
                local _, classFile = UnitClass(unit)
                if classFile and classFile ~= "" then
                    nameToClass[name] = classFile
                end
            end
        end
    end
end

local function FormatInactiveList(inactiveList)
    if #inactiveList == 0 then
        return "No inactive players detected."
    end
    return table.concat(inactiveList, ", ")
end

local function Clamp(v, a, b) if v < a then return a end if v > b then return b end return v end

local function UpdateAlignmentAndSize()
    -- Ensure alignment matches current frame position and adjust size to content
    UpdateAlignment()

    -- Measure and resize dynamically based on current text
    local padX, padY = 12, 10
    local minW, maxW = 220, 900

    -- Temporarily show detail to measure accurate width/height
    local wasDetailShown = frame.detail:IsShown()
    frame.detail:Show()

    local summaryW = frame.summary:GetStringWidth() or 0
    local detailW  = frame.detail:GetStringWidth() or 0
    local summaryH = frame.summary:GetStringHeight() or 0
    local detailH  = frame.detail:GetStringHeight() or 0

    if not wasDetailShown then frame.detail:Hide() end

    local neededW = math.max(summaryW, detailW) + padX * 2
    local neededH = summaryH + detailH + padY * 2

    neededW = Clamp(neededW, minW, maxW)
    neededH = math.max(neededH, 36)

    frame:SetSize(neededW, neededH)

    -- Adjust hit rect so OnEnter remains true while hovering the detail line
    local bottomInset = -(detailH + padY)
    -- keep insets non-negative to avoid expanding clickable area beyond visual bounds
    if bottomInset > 0 then bottomInset = 0 end
    frame:SetHitRectInsets(0, 0, 0, 0)
end

local function UpdateDisplay()
    local now = GetTime()
    local inactiveCount = 0
    local inactiveEntries = {}

    for name, _ in pairs(trackedNames) do
        local last = lastActiveByName[name] or 0
        local elapsed = now - last
        if elapsed >= INACTIVE_THRESHOLD then
            inactiveCount = inactiveCount + 1
            local minutes = math.floor(elapsed / 60)
            if minutes < 1 then minutes = 1 end
            local classFile = nameToClass[name]
            local color = ClassColorCode(classFile)
            local entry = string.format("%s%s|r (%d min)", color, name, minutes)
            table.insert(inactiveEntries, entry)
        end
    end

    -- Summary line: only total inactive
    frame.summary:SetText(string.format("Potentially AFK: %d", inactiveCount))

    -- Detail line: names colored with minutes, visible on hover only
    frame.detail:SetText(FormatInactiveList(inactiveEntries))

    -- Show/hide frame depending on group
    if not IsInGroup() and not IsInRaid() then
        frame:Hide()
        frame.detail:Hide()
    else
        frame:Show()
        if not frame:IsMouseOver() and not frame.summary:IsMouseOver() then
            frame.detail:Hide()
        end
    end

    UpdateAlignmentAndSize()
end

local function MarkActive(name, unit)
    if not name or name == "" then return end
    if not trackedNames[name] then return end
    local now = GetTime()
    lastActiveByName[name] = now
    SaveActivity(name, now)
    if unit and UnitExists(unit) then
        local _, classFile = UnitClass(unit)
        if classFile and classFile ~= "" then nameToClass[name] = classFile end
    end
    UpdateDisplay()
end

local function IsGroupUnit(unit)
    if not unit then return false end
    if unit == "player" then return true end
    if string.match(unit, "^party%d+$") then return true end
    if string.match(unit, "^raid%d+$") then return true end
    return false
end

-- Event handlers
local function UnitEventHandler(self, event, unit, ...)
    if not unit or not IsGroupUnit(unit) then return end
    local name = UnitName(unit)
    if not name or name == "" then return end
    if not trackedNames[name] then UpdateGroupTracking() end

    if event == "UNIT_HEALTH" or event == "UNIT_POWER_UPDATE"
    or event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_SUCCEEDED"
    or event == "UNIT_SPELLCAST_STOP" then
        MarkActive(name, unit)
    end
end

local function OnGroupRosterUpdate()
    local newCount = GetNumGroupMembers() or 1
    if prevGroupCount <= 1 and newCount > 1 then
        ResetActivity()
        UpdateGroupTracking()
        UpdateDisplay()
    else
        UpdateGroupTracking()
        UpdateDisplay()
    end
    prevGroupCount = newCount
end

-- Frame events
frame:RegisterEvent("PLAYER_LOGIN")
frame:RegisterEvent("GROUP_ROSTER_UPDATE")
frame:RegisterEvent("PLAYER_ENTERING_WORLD")

frame:SetScript("OnEvent", function(self, event, ...)
    if event == "PLAYER_LOGIN" then
        -- restore position
        if AFKDetectorDB.point and AFKDetectorDB.relativePoint and AFKDetectorDB.x and AFKDetectorDB.y then
            self:ClearAllPoints()
            self:SetPoint(AFKDetectorDB.point, UIParent, AFKDetectorDB.relativePoint, AFKDetectorDB.x, AFKDetectorDB.y)
        end
        LoadSaved()
        C_Timer.After(0.5, function()
            UpdateGroupTracking()
            UpdateDisplay()
        end)
    else
        C_Timer.After(0.1, OnGroupRosterUpdate)
    end
end)

-- After login create unit watcher and OnUpdate
C_Timer.After(0.5, function()
    local unitWatcher = CreateFrame("Frame")
    unitWatcher:SetScript("OnEvent", UnitEventHandler)
    unitWatcher:RegisterEvent("UNIT_HEALTH")
    unitWatcher:RegisterEvent("UNIT_POWER_UPDATE")
    unitWatcher:RegisterEvent("UNIT_SPELLCAST_START")
    unitWatcher:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
    unitWatcher:RegisterEvent("UNIT_SPELLCAST_STOP")

    frame:SetScript("OnUpdate", function(self, elapsed)
        self._acc = (self._acc or 0) + elapsed
        if self._acc < UPDATE_INTERVAL then return end
        self._acc = 0
        UpdateDisplay()
    end)
end)

-- Drag handlers: allow dragging only when clicking the summary line
frame.summary:SetScript("OnMouseDown", function(self, button)
    if button == "LeftButton" then
        frame:StartMoving()
    end
end)
frame.summary:SetScript("OnMouseUp", function(self, button)
    if button == "LeftButton" then
        frame:StopMovingOrSizing()
        -- save position
        local point, _, relativePoint, xOfs, yOfs = frame:GetPoint()
        AFKDetectorDB.point = point
        AFKDetectorDB.relativePoint = relativePoint
        AFKDetectorDB.x = xOfs
        AFKDetectorDB.y = yOfs
        UpdateAlignment()
    end
end)

-- Ctrl + right-click on frame to recenter (keeps previous behavior)
frame:SetScript("OnMouseUp", function(self, button)
    if button == "RightButton" and IsControlKeyDown() then
        self:ClearAllPoints()
        self:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
        AFKDetectorDB.point = "CENTER"
        AFKDetectorDB.relativePoint = "CENTER"
        AFKDetectorDB.x = 0
        AFKDetectorDB.y = 0
        UpdateAlignment()
    end
end)

-- Hover to show detail line (both frame and summary)
frame:SetScript("OnEnter", function(self)
    if self:IsShown() then
        self.detail:Show()
        UpdateDisplay()
    end
end)
frame:SetScript("OnLeave", function(self)
    self.detail:Hide()
    UpdateDisplay()
end)

frame.summary:SetScript("OnEnter", function(self)
    if frame:IsShown() then
        frame.detail:Show()
        UpdateDisplay()
    end
end)
frame.summary:SetScript("OnLeave", function(self)
    frame.detail:Hide()
    UpdateDisplay()
end)
