local addonName, SinsToolkit = ...

local Identifier = {}
SinsToolkit.Identifier = Identifier

local frame = CreateFrame("Frame", addonName .. "IdentifierFrame")
local tooltip = CreateFrame("GameTooltip", addonName .. "IdentifierTooltip", UIParent, "GameTooltipTemplate")
local updateElapsed = 0
local lastSummary = ""
local lastFrame

local function Print(message)
    print("|cff9b59b6[Sin's Toolkit QOL]|r " .. message)
end

StaticPopupDialogs["SinsToolkitIdentifierCopy"] = {
    text = "%s\nCTRL-C to copy",
    button1 = "Close",
    OnShow = function(self, data)
        local editBox = self.EditBox or self.editBox
        if not editBox then return end
        editBox:SetMaxLetters(0)
        editBox:SetText(data or "")
        editBox:HighlightText()
        editBox:SetScript("OnEscapePressed", function(box) box:GetParent():Hide() end)
        editBox:SetScript("OnEnterPressed", function(box) box:GetParent():Hide() end)
        editBox:SetScript("OnKeyUp", function(box, key)
            if (IsControlKeyDown() and key == "C") or (IsKeyDown("C") and (key == "LCTRL" or key == "RCTRL")) then
                box:GetParent():Hide()
            end
        end)
    end,
    hasEditBox = true,
    editBoxWidth = 360,
    timeout = 0,
    whileDead = true,
    hideOnEscape = true,
    preferredIndex = 3,
}

local function IsSecret(value)
    return issecretvalue and issecretvalue(value) == true
end

local function SafeCall(method, object, ...)
    if not method or IsSecret(object) or type(object) ~= "table" then return nil end
    local ok, result = pcall(method, object, ...)
    if ok then return result end
    return nil
end

local function GetObjectName(object)
    if type(object) ~= "table" or IsSecret(object) then return nil end
    local name = SafeCall(object.GetName, object)
    if name and name ~= "" then return name end
    local debugName = SafeCall(object.GetDebugName, object)
    if debugName and debugName ~= "" then return debugName end
    return nil
end

local function GetObjectType(object)
    if type(object) ~= "table" or IsSecret(object) then return "Unknown" end
    return SafeCall(object.GetObjectType, object) or "Unknown"
end

local function GetAddonFromSource(source)
    if type(source) ~= "string" then return nil end
    local addon = source:match("^Interface\\AddOns\\([^\\]+)\\")
    return addon
end

local function GetSourceInfo(object)
    if type(object) ~= "table" or type(object.GetSourceLocation) ~= "function" then return nil end
    local ok, source, line = pcall(object.GetSourceLocation, object)
    if not ok or not source then return nil end
    return source, line, GetAddonFromSource(source)
end

local function FormatNumber(value)
    value = tonumber(value)
    if not value then return "?" end
    return ("%.0f"):format(value)
end

local function GetPointSummary(object)
    if type(object) ~= "table" or type(object.GetPoint) ~= "function" then return nil end
    local ok, point, relativeTo, relativePoint, x, y = pcall(object.GetPoint, object, 1)
    if not ok or not point then return nil end
    local relativeName = GetObjectName(relativeTo) or (relativeTo and GetObjectType(relativeTo)) or "UIParent"
    return ("%s -> %s %s (%s, %s)"):format(point, relativeName, relativePoint or "?", FormatNumber(x), FormatNumber(y))
end

local function GetUnitNpcID(unit)
    if not unit or not UnitGUID then return nil end
    local ok, guid = pcall(UnitGUID, unit)
    if not ok or not guid or IsSecret(guid) then return nil end
    local npcID = select(6, strsplit("-", guid))
    return tonumber(npcID)
end

local function GetMouseFrame()
    if GetMouseFocus then
        local ok, focus = pcall(GetMouseFocus)
        if ok and focus and not IsSecret(focus) then return focus end
    end
    if GetMouseFoci then
        local ok, foci = pcall(GetMouseFoci)
        if ok and type(foci) == "table" then
            for _, focus in ipairs(foci) do
                if focus and not IsSecret(focus) then return focus end
            end
        end
    end
    return nil
end

local function BuildLines(object)
    local lines = {}
    if not object then
        lines[#lines + 1] = "No frame under cursor."
        return lines
    end

    local name = GetObjectName(object) or "(anonymous)"
    local objectType = GetObjectType(object)
    local parent = SafeCall(object.GetParent, object)
    local parentName = GetObjectName(parent) or (parent and GetObjectType(parent)) or "none"
    local source, line, addon = GetSourceInfo(object)

    lines[#lines + 1] = ("Frame: %s"):format(name)
    lines[#lines + 1] = ("Type: %s"):format(objectType)
    lines[#lines + 1] = ("Parent: %s"):format(parentName)

    if addon then
        lines[#lines + 1] = ("Source addon: %s"):format(addon)
    elseif source then
        lines[#lines + 1] = "Source addon: Blizzard or unknown"
    end

    if SinsToolkitDB and SinsToolkitDB.identifierDetailed then
        local width = SafeCall(object.GetWidth, object)
        local height = SafeCall(object.GetHeight, object)
        local strata = SafeCall(object.GetFrameStrata, object)
        local level = SafeCall(object.GetFrameLevel, object)
        local alpha = SafeCall(object.GetAlpha, object)
        local shown = SafeCall(object.IsShown, object)
        local point = GetPointSummary(object)

        if width and height then lines[#lines + 1] = ("Size: %s x %s"):format(FormatNumber(width), FormatNumber(height)) end
        if strata then lines[#lines + 1] = ("Strata: %s"):format(strata) end
        if level then lines[#lines + 1] = ("Level: %s"):format(level) end
        if alpha then lines[#lines + 1] = ("Alpha: %.2f"):format(alpha) end
        if shown ~= nil then lines[#lines + 1] = ("Shown: %s"):format(shown and "yes" or "no") end
        if point then lines[#lines + 1] = ("Point: %s"):format(point) end
        if source then lines[#lines + 1] = ("Source: %s%s"):format(source, line and (":" .. tostring(line)) or "") end
    end

    local npcID = GetUnitNpcID("mouseover")
    if npcID then
        lines[#lines + 1] = ("Mouseover NPC ID: %d"):format(npcID)
    end

    return lines
end

local function Summarize(object)
    return table.concat(BuildLines(object), "\n")
end

local function ShowHoverTooltip(object)
    if not SinsToolkitDB or not SinsToolkitDB.identifierTooltip or not object then return end

    tooltip:SetOwner(UIParent, "ANCHOR_CURSOR")
    tooltip:ClearLines()
    tooltip:AddLine("Sin's Toolkit QOL Identifier", 0.74, 0.45, 0.95)
    for _, line in ipairs(BuildLines(object)) do
        tooltip:AddLine(line, 1, 1, 1, true)
    end
    tooltip:Show()
end

local function SetActive(enabled)
    if enabled then
        frame:SetScript("OnUpdate", function(_, elapsed)
            updateElapsed = updateElapsed + elapsed
            if updateElapsed < 0.15 then return end
            updateElapsed = 0

            local focus = GetMouseFrame()
            if focus ~= lastFrame then
                lastFrame = focus
                lastSummary = Summarize(focus)
                ShowHoverTooltip(focus)
            elseif focus then
                ShowHoverTooltip(focus)
            end
        end)
    else
        frame:SetScript("OnUpdate", nil)
        tooltip:Hide()
        lastFrame = nil
    end
end

function Identifier:Apply()
    SetActive(SinsToolkit.IsFeatureActive and SinsToolkit.IsFeatureActive("identifierEnabled"))
end

function Identifier:IdentifyNow()
    local focus = GetMouseFrame()
    lastFrame = focus
    lastSummary = Summarize(focus)
    Print(lastSummary:gsub("\n", " | "))
    ShowHoverTooltip(focus)
end

function Identifier:CopyLast()
    if lastSummary == "" then
        self:IdentifyNow()
    end
    if StaticPopup_Show then
        StaticPopup_Show("SinsToolkitIdentifierCopy", "Sin's Toolkit QOL Identifier", nil, lastSummary)
    else
        Print(lastSummary:gsub("\n", " | "))
    end
end

function Identifier:SelectByName(name)
    if not name or name == "" then
        Print("Usage: /stkid name FrameName")
        return
    end
    local object = _G[name]
    if not object then
        Print("No global frame named " .. name .. ".")
        return
    end
    if type(object) ~= "table" or not object.GetObjectType then
        Print(name .. " exists, but it is not a UI frame.")
        return
    end
    lastFrame = object
    lastSummary = Summarize(object)
    Print(lastSummary:gsub("\n", " | "))
end

function SinsToolkit.ApplyIdentifier()
    Identifier:Apply()
end

local function ShowHelp()
    Print("commands:")
    print("  |cffffff00/stkid|r - identify the frame under your cursor")
    print("  |cffffff00/stkid on|r / |cffffff00off|r - enable or disable hover identify mode")
    print("  |cffffff00/stkid copy|r - copy the last identification text")
    print("  |cffffff00/stkid name <FrameName>|r - identify a global frame by name")
    print("  |cffffff00/stkid simple|r / |cffffff00detail|r - switch output detail")
end

SLASH_SINSTOOLKITIDENTIFIER1 = "/stkid"
SLASH_SINSTOOLKITIDENTIFIER2 = "/stkID"
SlashCmdList.SINSTOOLKITIDENTIFIER = function(msg)
    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
    msg = (msg or ""):gsub("^%s+", ""):gsub("%s+$", "")
    local cmd, rest = msg:match("^(%S*)%s*(.-)$")
    cmd = (cmd or ""):lower()

    if cmd == "" or cmd == "id" or cmd == "identify" then
        Identifier:IdentifyNow()
    elseif cmd == "on" or cmd == "start" then
        SinsToolkitDB.identifierEnabled = true
        Identifier:Apply()
        Print("Identifier hover mode enabled.")
    elseif cmd == "off" or cmd == "stop" then
        SinsToolkitDB.identifierEnabled = false
        Identifier:Apply()
        Print("Identifier hover mode disabled.")
    elseif cmd == "toggle" then
        SinsToolkitDB.identifierEnabled = not SinsToolkitDB.identifierEnabled
        Identifier:Apply()
        Print("Identifier hover mode " .. (SinsToolkitDB.identifierEnabled and "|cff00ff00enabled|r." or "|cffff4040disabled|r."))
    elseif cmd == "copy" then
        Identifier:CopyLast()
    elseif cmd == "name" or cmd == "frame" then
        Identifier:SelectByName(rest)
    elseif cmd == "simple" then
        SinsToolkitDB.identifierDetailed = false
        Print("Identifier detail set to simple.")
    elseif cmd == "detail" then
        SinsToolkitDB.identifierDetailed = true
        Print("Identifier detail set to detailed.")
    else
        ShowHelp()
    end
end

frame:RegisterEvent("PLAYER_LOGIN")
frame:SetScript("OnEvent", function()
    if SinsToolkit.EnsureDB then SinsToolkit.EnsureDB() end
    Identifier:Apply()
end)
