-- ============================================================
--  Core.lua  |  v2.1.17
--  AzerothAngler — Main logic
--
--  Part of the ForgeDMC Addon Suite
--  Author  : ForgeDMC
--  Updated : 2026-03-21
--  License : MIT
-- ============================================================


local _, ns = ...

ns.Core = {}
local Core = ns.Core

ns.Debug = ns.Debug or {}
local Debug = ns.Debug

local function trimDebugText(text)
    text = tostring(text or "Unknown error")
    text = text:gsub("\r", "")
    text = text:gsub("\n\n+", "\n")
    return text
end

function Debug:Initialize()
    if not ns.DB then return end
    ns.DB.debug = ns.DB.debug or {}
    ns.DB.debug.logs = ns.DB.debug.logs or {}
    ns.DB.debug.lastError = ns.DB.debug.lastError or ""
end

function Debug:GetMaxEntries()
    local configured = ns.DB and ns.DB.profile and ns.DB.profile.debug and ns.DB.profile.debug.maxEntries or 120
    configured = tonumber(configured) or 120
    return math.max(20, math.min(500, configured))
end

function Debug:GetLines()
    if not (ns.DB and ns.DB.debug and ns.DB.debug.logs) then return {} end
    local out = {}
    for i, line in ipairs(ns.DB.debug.logs) do
        out[i] = line
    end
    return out
end

function Debug:GetText()
    local lines = self:GetLines()
    if #lines == 0 then
        return "No debug messages captured yet."
    end
    return table.concat(lines, "\n\n")
end

function Debug:Clear()
    self:Initialize()
    for i = #(ns.DB.debug.logs or {}), 1, -1 do
        table.remove(ns.DB.debug.logs, i)
    end
    ns.DB.debug.lastError = ""
end

function Debug:Capture(context, errText)
    if not ns.DB then return end
    if ns.DB.profile and ns.DB.profile.debug and ns.DB.profile.debug.captureLuaErrors == false then
        return
    end

    self:Initialize()
    local line = string.format("[%s] %s\n%s", date("%H:%M:%S"), tostring(context or "Error"), trimDebugText(errText))
    table.insert(ns.DB.debug.logs, 1, line)
    while #(ns.DB.debug.logs) > self:GetMaxEntries() do
        table.remove(ns.DB.debug.logs)
    end
    ns.DB.debug.lastError = line

    local now = GetTime and GetTime() or 0
    if (ns.state.lastDebugNoticeAt or 0) + 5 <= now then
        ns.state.lastDebugNoticeAt = now
        ns.Utils:Print(string.format("%sDebug captured an error.|r Use /angler debug to inspect logs.", ns.constants.RED_COLOR))
    end

    if ns.UI and ns.state.debugFrame and ns.state.debugFrame:IsShown() and ns.UI.RefreshDebug then
        ns.UI:RefreshDebug()
    end
end

function Core:InitializeDB()
    if type(AzerothAnglerDB) ~= "table" then
        AzerothAnglerDB = ns.Utils:CopyDefaults()
    else
        ns.Utils:MergeDefaults(AzerothAnglerDB, ns.defaults)
    end
    ns.DB = AzerothAnglerDB

    -- NOTE: Scanner is feature-gated; it respects the user's saved setting.
    -- Do NOT force-reset scanner.enabled here — that would clobber SavedVariables
    -- on every login and prevent users from enabling the scanner even manually.
    -- If the scanner should ship disabled by default, the default is already
    -- false in ns.defaults.profile.scanner.enabled (see Data.lua).

    -- Normalize minimap icon path to the logo asset.
    if ns.DB.profile and ns.DB.profile.minimap then
        local icon = ns.DB.profile.minimap.iconTexture or ""
        if icon == "" or icon:find("generated\\emblem_round", 1, true) or icon:find("generated\\app_icon_face", 1, true) then
            ns.DB.profile.minimap.iconTexture = "Interface\\AddOns\\AzerothAngler\\Media\\AzerothAnglerLogo.tga"
        end
        ns.DB.profile.minimap.radius = math.max(82, tonumber(ns.DB.profile.minimap.radius) or 90)
    end
end

function Core:ApplySavedPoints()
    if ns.state.frame and ns.DB.profile.ui.point then
        local p = ns.DB.profile.ui.point
        ns.state.frame:ClearAllPoints()
        ns.state.frame:SetPoint(p[1], _G[p[2]] or UIParent, p[3], p[4], p[5])
    end
    if ns.state.compactFrame and ns.DB.profile.ui.compactPoint then
        local p = ns.DB.profile.ui.compactPoint
        ns.state.compactFrame:ClearAllPoints()
        ns.state.compactFrame:SetPoint(p[1], _G[p[2]] or UIParent, p[3], p[4], p[5])
    end
end

function Core:SaveFramePosition(frame, key)
    if not frame or not ns.DB then return end
    local point, _, relativePoint, x, y = frame:GetPoint(1)
    ns.DB.profile.ui[key] = { point, "UIParent", relativePoint, math.floor(x + 0.5), math.floor(y + 0.5) }
end

local function ownedByBugForgeOrForgeMaster(fn)
    if type(fn) ~= "function" then return false end
    if not (debug and debug.getinfo) then return false end
    local ok, info = pcall(debug.getinfo, fn, "S")
    if not ok or not info then return false end
    local src = tostring(info.source or ""):lower()
    return src:find("bugforge", 1, true) ~= nil
        or src:find("forgemaster", 1, true) ~= nil
end

local function addonPresent(name)
    if not name or name == "" then return false end
    if C_AddOns and C_AddOns.GetAddOnMetadata then
        local v = C_AddOns.GetAddOnMetadata(name, "Version")
        if v and v ~= "" then return true end
    end
    if GetAddOnMetadata then
        local v = GetAddOnMetadata(name, "Version")
        if v and v ~= "" then return true end
    end
    return false
end

function Core:InstallErrorHandler()
    if ns.state.debugHandlerInstalled then return end
    if not (geterrorhandler and seterrorhandler) then return end

    local previous = geterrorhandler()

    -- Avoid chaining custom global handlers from other addons.
    -- BugForge/ForgeMaster already own error capture; wrapping them adds
    -- AzerothAngler to external stacks and can amplify handler conflicts.
    if addonPresent("BugForge") or addonPresent("ForgeMaster")
        or (IsAddOnLoaded and (IsAddOnLoaded("BugForge") or IsAddOnLoaded("ForgeMaster")))
        or ownedByBugForgeOrForgeMaster(previous) then
        return
    end

    ns.state.previousErrorHandler = previous

    seterrorhandler(function(err)
        if ns.Debug and ns.Debug.Capture then
            ns.Debug:Capture("Lua Error", err)
        end
        if previous then
            pcall(previous, err)
        end
    end)

    ns.state.debugHandlerInstalled = true
end

function Core:Startup()
    self:InitializeDB()
    if ns.Session and ns.Session.ResetForLogin then
        ns.Session:ResetForLogin()
    end
    if ns.Debug and ns.Debug.Initialize then ns.Debug:Initialize() end
    self:InstallErrorHandler()
    ns.UI:CreateMainFrame()
    ns.Minimap:CreateButton()
    if ns.Nearby and ns.Nearby.Initialize then ns.Nearby:Initialize() end
    ns.Tracking:RefreshAll()
    self:ApplySavedPoints()

    if ns.DB.profile.ui.showMainOnLogin and ns.state.frame then ns.state.frame:Show() else ns.state.frame:Hide() end
    if ns.state.compactFrame then ns.state.compactFrame:SetShown(ns.DB.profile.ui.compactMode) end

    ns.state.initialized = true
    local addonVersion = (C_AddOns and C_AddOns.GetAddOnMetadata and C_AddOns.GetAddOnMetadata(ns.addonName, "Version"))
                       or (GetAddOnMetadata and GetAddOnMetadata(ns.addonName, "Version"))
                       or "2.1.17"
    ns.Utils:Print(string.format("%sv%s|r loaded. Type %s/aa|r to open. Nearby app pings and minimap tracking are active.", ns.constants.GOLD_COLOR, addonVersion, ns.constants.GREEN_COLOR))
end

function Core:OnUpdate(elapsed)
    if not ns.state.initialized then return end
    if ns.Nearby and ns.Nearby.OnUpdate then ns.Nearby:OnUpdate(elapsed) end
    self._refreshElapsed = (self._refreshElapsed or 0) + (elapsed or 0)
    if self._refreshElapsed < 0.5 then return end
    self._refreshElapsed = 0

    local mainShown = ns.state.frame and ns.state.frame:IsShown()
    local compactShown = ns.state.compactFrame and ns.state.compactFrame:IsShown()
    if not (ns.DB.currentSession.active or mainShown or compactShown) then return end

    ns.DB.currentSession.zone = ns.Utils:GetZoneName()
    ns.DB.currentSession.fishingSkill = ns.Utils:GetFishingSkill()
    if ns.Minimap and ns.Minimap.RefreshButton then ns.Minimap:RefreshButton() end
    if ns.UI then ns.UI:RefreshAll() end
end
