local PT = AhUI
if not PT then return end

PT.AccountOverview_Data = PT.AccountOverview_Data or {}
local D = PT.AccountOverview_Data

local function GetCurrentCharKey()
    local name = UnitName("player")
    local realm = GetNormalizedRealmName()
    if not name then return nil end
    return name .. "-" .. (realm or "")
end

local function EnsureDB()
    AhUI_DB = AhUI_DB or {}
    AhUI_DB.accountOverview = AhUI_DB.accountOverview or {}
    AhUI_DB.accountOverview.chars = AhUI_DB.accountOverview.chars or {}
    return AhUI_DB.accountOverview.chars
end

local function CleanupInvalidCharacterKeys()
    local db = EnsureDB()

    for key in pairs(db) do
        if type(key) == "string" then
            local name, realm = strsplit("-", key)

            if not name or not realm or realm == "" then
                db[key] = nil
            end
        end
    end
end

local function NormalizeCharacterKeys()
    local db = EnsureDB()
    local realm = GetNormalizedRealmName()
    if not realm then return end

    for key, entry in pairs(db) do
        if type(key) == "string" then
            local n, r = strsplit("-", key)

            if n and (not r or r == "") then
                local newKey = n .. "-" .. realm

                if not db[newKey] then
                    db[newKey] = entry
                else

                    local a = db[newKey]
                    local aUpd = a and a.updated or 0
                    local bUpd = entry and entry.updated or 0
                    if bUpd > aUpd then
                        db[newKey] = entry
                    end
                end

                db[key] = nil
            end
        end
    end
end

local function CleanupLegacyConcentrationData()
    local db = EnsureDB()

    for _, entry in pairs(db) do
        if entry.concentration then
            for exp, snap in pairs(entry.concentration) do
				if type(snap) ~= "table"
				   or type(snap.profs) ~= "table"
				then
					entry.concentration[exp] = nil
				end
            end
        end
    end
end

local CONCENTRATION_MAX = 1000
local CONCENTRATION_NOTIFY_THRESHOLD = 995
local CONCENTRATION_REGEN_SEC = 6 * 60 

local function ApplyConcentrationRegen(value, lastUpdate)
    if type(value) ~= "number" or not lastUpdate then
        return value, lastUpdate
    end

    local now = time()
    local elapsed = now - lastUpdate
    if elapsed <= 0 then
        return value, lastUpdate
    end

    local regen = math.floor(elapsed / CONCENTRATION_REGEN_SEC)
    if regen <= 0 then
        return value, lastUpdate
    end

    local newValue = math.min(CONCENTRATION_MAX, value + regen)

    local used = regen * CONCENTRATION_REGEN_SEC
    local newUpdate = lastUpdate + used

    return newValue, newUpdate
end

local function GetCurrencyMap(expansion)
    if not PT.ConcentrationCurrencyMap then
        return nil
    end
    return PT.ConcentrationCurrencyMap[expansion:upper()]
end

local function GetPlayerConcentrationByProfession(expansion)
	if InCombatLockdown() then
		return nil
	end
    local map = PT.ConcentrationCurrencyMap
        and PT.ConcentrationCurrencyMap[expansion:upper()]
    if not map then return nil end

    local result = {}

    local p1, p2 = GetProfessions()
    for _, slot in ipairs({ p1, p2 }) do
        if slot then
            local name, icon, _, _, _, _, skillLine = GetProfessionInfo(slot)
            local currencyId = map[skillLine]

			if currencyId then
				local info = C_CurrencyInfo.GetCurrencyInfo(currencyId)

				result[skillLine] = {
					value = info and info.quantity or nil,
					name  = name,
					icon  = icon,
				}
			end
        end
    end

    return next(result) and result or nil
end

local function HasConcentrationProfession_Live(expansion)
    local map = PT.ConcentrationCurrencyMap
        and PT.ConcentrationCurrencyMap[expansion:upper()]
    if not map then return false end

    local p1, p2 = GetProfessions()
    for _, slot in ipairs({ p1, p2 }) do
        if slot then
            local _, _, _, _, _, _, skillLine = GetProfessionInfo(slot)
            if skillLine and map[skillLine] then
                return true
            end
        end
    end

    return false
end

local function GetCompletedWeeklyQuests_Live(expansion)
    local completed = {}

    local db = PT.DB and (PT.DB[expansion] or PT.DB[expansion:upper()])
    if not db then return completed end

    for _, prof in pairs(db) do
        for _, entry in ipairs(prof.entries or {}) do
            local qid = entry.questId
            if qid then
                if type(qid) == "number" then
                    if C_QuestLog.IsQuestFlaggedCompleted(qid) then
                        completed[qid] = true
                    end
                elseif type(qid) == "table" then
                    for _, q in ipairs(qid) do
                        if C_QuestLog.IsQuestFlaggedCompleted(q) then
                            completed[q] = true
                        end
                    end
                end
            end
        end
    end

    return completed
end

local function GetLearnedProfessions_Live(expansion)
    local result = {}

	local db = PT.DB and (PT.DB[expansion] or PT.DB[expansion:upper()])
	if not db then return {} end

    local learned = {}
    local p1, p2 = GetProfessions()

    for _, slot in ipairs({ p1, p2 }) do
        if slot then
            local name, icon, _, _, _, _, skillLine = GetProfessionInfo(slot)
            if skillLine then
                learned[skillLine] = {
                    name = name,
                    icon = icon,
                }
            end
        end
    end

    for profId, prof in pairs(db) do
        if prof.skillLine and learned[prof.skillLine] then
            result[profId] = {
                name = learned[prof.skillLine].name or prof.name,
                icon = learned[prof.skillLine].icon or prof.icon,
            }
        end
    end

    return result
end

local function GetWeeklyKeyFromEntry(entry)
    if not entry then return nil end

    if getmetatable(entry) == PT.AhUI_Treatise then
        return "treatise"
    end

    if getmetatable(entry) == PT.AhUI_WeeklyTreasure
       or getmetatable(entry) == PT.AhUI_WeeklyQuestItem
    then
        return "treasure"
    end

    if getmetatable(entry) == PT.AhUI_Instructor
       or getmetatable(entry) == PT.AhUI_Consortium
       or getmetatable(entry) == PT.AhUI_WorkOrder
       or getmetatable(entry) == PT.AhUI_CraftingOrder
    then
        return "trainer"
    end

    return nil
end

local function IsEntryDone(entry, isQuestDone)
    local qid = entry.questId
    if not qid then return false end

    if type(qid) == "number" then
        return isQuestDone(qid)
    elseif type(qid) == "table" then
        for _, q in ipairs(qid) do
            if isQuestDone(q) then
                return true
            end
        end
    end

    return false
end

local function BuildWeeklyStatusForProfession(prof, isQuestDone)
    local trainer = {
        instructor = nil,
        consortium = nil,
        questitem  = nil,
    }

    local treasure = {
        total = 0,
        done  = 0,
    }

    local treatise = {
        exists = false,
        done   = false,
    }

    for _, entry in ipairs(prof.entries or {}) do
        local done = IsEntryDone(entry, isQuestDone)
        local mt = getmetatable(entry)

        if mt == PT.AhUI_Instructor then
            trainer.instructor = done
        elseif mt == PT.AhUI_Consortium then
            trainer.consortium = done
        elseif mt == PT.AhUI_WeeklyQuestItem then
            trainer.questitem = done

        elseif mt == PT.AhUI_WeeklyTreasure then
            treasure.total = treasure.total + 1
            if done then
                treasure.done = treasure.done + 1
            end

        elseif mt == PT.AhUI_Treatise then
            treatise.exists = true
            treatise.done   = done
        end
    end

    local trainerDone = true
    for _, v in pairs(trainer) do
        if v == false then
            trainerDone = false
            break
        end
    end

    return {
        trainer  = trainerDone and next(trainer) ~= nil,
        treasure = (treasure.total > 0) and (treasure.done == treasure.total),
        treatise = treatise.exists and treatise.done,
    }
end

D.TIMEWALKING = {
    CLASSIC = {
        quests = { 83285 },
        atlas  = "accountupgradebanner-classic",
    },
    TBC = {
        quests = { 40168 },
        atlas  = "accountupgradebanner-bc",
    },
    WOTLK = {
        quests = { 40173 },
        atlas  = "accountupgradebanner-wotlk",
    },
    CATA = {
        quests = { 40787, 40786 },
        atlas  = "accountupgradebanner-cataclysm",
    },
    MOP = {
        quests = { 45563 },
        atlas  = "accountupgradebanner-mop",
    },
    WOD = {
        quests = { 55498, 55499 },
        atlas  = "accountupgradebanner-wod",
    },
    LEGION = {
        quests = { 64710 },
        atlas  = "accountupgradebanner-legion",
    },
    BFA = {
        quests = { 89223 },
        atlas  = "accountupgradebanner-bfa",
    },
    SL = {
        quests = { 92650 },
        atlas  = "accountupgradebanner-shadowlands",
    },
}

local EVENT_TO_TW = {
    [167] = "CLASSIC",
    [168] = "TBC",
    [169] = "WOTLK",
    [170] = "CATA",
    [171] = "MOP",
    [172] = "WOD",
    [173] = "LEGION",
    [174] = "BFA",
    [175] = "SL",
}

local TIMEWALKING_EVENT_IDS = {
    167, -- Classic Timewalking
    168, -- Burning Crusade
    169, -- Wrath of the Lich King
    170, -- Cataclysm
    171, -- Mists of Pandaria
    172, -- Warlords of Draenor
    173, -- Legion
    174, -- Battle for Azeroth
    175, -- Shadowlands
}

local TW_AURA_TO_KEY = {
    [452307]  = "CLASSIC", -- Sign of the Past
    [335148]  = "TBC",     -- Sign of the Twisting Nether
    [335149]  = "WOTLK",   -- Sign of the Scourge
    [335150]  = "CATA",    -- Sign of the Destroyer
    [335151]  = "MOP",     -- Sign of the Mists
    [335152]  = "WOD",     -- Sign of Iron
    [359082]  = "LEGION",  -- Sign of the Legion
    [1223878] = "BFA",     -- Sign of Azeroth 
    [1256081] = "SL",  	   -- Sign of Azeroth(Shadowlands)
}

local function GetActiveTimewalkingFromAura()
	if InCombatLockdown() then
		return nil
	end
	
    if not C_UnitAuras or not C_UnitAuras.GetAuraDataByIndex then
        return nil
    end

    local index = 1
    while true do
        local aura = C_UnitAuras.GetAuraDataByIndex("player", index, "HELPFUL")
        if not aura then
            break
        end

		local spellId = aura.spellId
		if type(spellId) == "number" then
			local key = TW_AURA_TO_KEY[spellId]
			if key then
				return key
			end
		end

        index = index + 1
    end

    return nil
end

local function GetActiveTimewalking()
    local twKey = GetActiveTimewalkingFromAura()
    if twKey then
        return twKey
    end

    local today = C_DateAndTime.GetCurrentCalendarTime().monthDay
    local num = C_Calendar.GetNumDayEvents(0, today)

    for i = 1, num do
        local ev = C_Calendar.GetDayEvent(0, today, i)
        if ev and ev.calendarType == "HOLIDAY" and ev.eventID then
            for _, id in ipairs(TIMEWALKING_EVENT_IDS) do
                if ev.eventID == id then
                    return EVENT_TO_TW[ev.eventID]
                end
            end
        end
    end

    return nil
end

local function GetServerDayKey()
    local d = C_DateAndTime.GetCurrentCalendarTime()
    return d.year * 10000 + d.month * 100 + d.monthDay
end

local function GetServerWeekKey()
    local d = C_DateAndTime.GetCurrentCalendarTime()
    local week = math.floor((d.monthDay + d.weekday) / 7)
    return d.year * 100 + week
end

local function IsSafeEnvironment()
    if InCombatLockdown() then
        return false
    end

    local inInstance = IsInInstance()
    if inInstance then
        return false
    end

    return true
end

function D:SaveCurrentCharacterSnapshot()
    if not IsSafeEnvironment() then
        return
    end
	
    local charKey = GetCurrentCharKey()
    if not charKey then return end

    local db = EnsureDB()
    local entry = db[charKey] or {}
    db[charKey] = entry

    entry.updated = time()
    entry.concentration = entry.concentration or {}

    for _, exp in ipairs({ "df", "tww", "mid" }) do
        local data = GetPlayerConcentrationByProfession(exp)

        if type(data) ~= "table" or not next(data) then
            entry.concentration[exp] = nil
        else
            entry.concentration[exp] = entry.concentration[exp] or {
                updated = time(),
                profs = {}
            }

            local snap = entry.concentration[exp]
            snap.updated = time()
            snap.profs = snap.profs or {}

            for skillLine, info in pairs(data) do
                if type(skillLine) == "number" then
                    local key = tonumber(skillLine)
                    if key then
                        local prev = snap.profs[key]

                        snap.profs[key] = {
                            value        = tonumber(info.value),
                            name         = tostring(info.name or ""),
                            icon         = tonumber(info.icon),
                            hadCurrency  = true,
                            lastUpdate = prev and prev.lastUpdate or time()
                        }
                    end
                end
            end
        end
    end

	entry.kp = entry.kp or {}

	local weekKey = GetServerWeekKey()

	for _, exp in ipairs({ "df", "tww", "mid" }) do
		local ok, value = pcall(PT.GetRemainingKPByExpansion, exp)
			if ok and type(value) == "number" then
				entry.kp[exp] = {
					value   = value,
					updated = time(),
					weekKey = weekKey,
				}
			end
	end

	entry.kpByProf = entry.kpByProf or {}

	local weekKey = GetServerWeekKey()

    for _, exp in ipairs({ "df", "tww", "mid" }) do
        local data = PT.GetRemainingKPByProfession(exp)

        if type(data) == "table" then
            local safe = {}

            for k, v in pairs(data) do
                if type(k) == "number" then
                    safe[tonumber(k)] = v
                end
            end

            entry.kpByProf[exp] = {
                updated = time(),
                weekKey = weekKey,
                profs   = safe,
            }
        else
            entry.kpByProf[exp] = nil
        end
    end

	entry.weekly = entry.weekly or {}

	local weekKey = GetServerWeekKey()

	for _, exp in ipairs({ "df", "tww", "mid" }) do
		local prev = entry.weekly[exp]

			if not prev or prev.weekKey ~= weekKey then
			local liveProfs = GetLearnedProfessions_Live(exp) or {}
			local safeProfs = {}

			for k, v in pairs(liveProfs) do
				if type(k) == "number" then
					safeProfs[tonumber(k)] = {
						name = tostring(v.name or ""),
						icon = tonumber(v.icon),
					}
				end
			end

			entry.weekly[exp] = {
				updated = time(),
				weekKey = weekKey,
				completedQuests = GetCompletedWeeklyQuests_Live(exp),
				profs = safeProfs,
			}

        else

            prev.completedQuests = prev.completedQuests or {}

            local live = GetCompletedWeeklyQuests_Live(exp)
            for qid, done in pairs(live) do
                if done then
                    prev.completedQuests[qid] = true
                end
            end

            local liveProfs = GetLearnedProfessions_Live(exp) or {}
            local safeProfs = {}

            for k, v in pairs(liveProfs) do
                if type(k) == "number" then
                    safeProfs[tonumber(k)] = {
                        name = tostring(v.name or ""),
                        icon = tonumber(v.icon),
                    }
                end
            end

            prev.profs = safeProfs
            prev.updated = time()
        end
	end
	
	entry.skinning = nil

	if PT.SkinningCheck and PT.SkinningCheck:IsAvailable() then
		local done, total = PT.SkinningCheck:GetProgress()
		if done and total then
			entry.skinning = {
				done   = done,
				total  = total,
				updated = time(),
				dayKey = GetServerDayKey(),
			}
		end
	end

    local twKey = GetActiveTimewalking()
    if twKey and D.TIMEWALKING[twKey] then
        entry.timewalking = entry.timewalking or {}

        local done = false
        for _, q in ipairs(D.TIMEWALKING[twKey].quests) do
            if C_QuestLog.IsQuestFlaggedCompleted(q) then
                done = true
                break
            end
        end

        entry.timewalking[twKey] = {
            done    = done,
            updated = time(),
        }
    end
end

function D:GetRemainingKP(charKey, expansion)
    if not charKey or not expansion then
        return nil
    end
	
    local current = GetCurrentCharKey()

    if charKey == current then
        local ok, value = pcall(PT.GetRemainingKPByExpansion, expansion)
        if ok then
            return value
        end
        return nil
    end

    local db = EnsureDB()
    local entry = db[charKey]
    if not entry or not entry.kp then
        return nil
    end

	local snap = entry.kp[expansion]
	if not snap then
		return nil
	end

	if snap.weekKey ~= GetServerWeekKey() then
		snap.weekKey = GetServerWeekKey()
	end

	return snap.value

end

function D:GetKPByProfession(charKey, expansion)
    if not charKey or not expansion then return nil end
    
    local current = GetCurrentCharKey()

    if charKey == current then
        return PT.GetRemainingKPByProfession(expansion)
    end

    local db = EnsureDB()
    local entry = db[charKey]
    if not entry or not entry.kpByProf then
        return nil
    end

    local snap = entry.kpByProf[expansion]
	if not snap then
		return nil
	end

	if snap.weekKey ~= GetServerWeekKey() then
		snap.weekKey = GetServerWeekKey()
	end

	return snap

end

function D:GetWeeklyStatus(charKey, expansion)
    if not charKey or not expansion then
        return nil
    end

	local db = PT.DB and (PT.DB[expansion] or PT.DB[expansion:upper()])
	if type(db) ~= "table" then
		return nil
	end

    local current = GetCurrentCharKey()
    local completed = {}

    if charKey == current then
        completed = GetCompletedWeeklyQuests_Live(expansion)
    else
        local chars = EnsureDB()
        local snap = chars[charKey]
            and chars[charKey].weekly
            and chars[charKey].weekly[expansion]

        if snap then
			if snap.weekKey ~= GetServerWeekKey() then
				snap.weekKey = GetServerWeekKey()
				snap.completedQuests = {}
				snap.profs = {}
			end
            completed = snap.completedQuests or {}
        end
    end

	local learned = {}

	if charKey == current then
		learned = GetLearnedProfessions_Live(expansion) or {}
	else
		local chars = EnsureDB()
		local snap = chars[charKey]
			and chars[charKey].weekly
			and chars[charKey].weekly[expansion]

		learned = (snap and snap.profs) or {}
	end

    local result = {}

	for profId, meta in pairs(learned or {}) do
		local prof = db[profId]
		if prof then
			local function isQuestDone(qid)
				if charKey == current then
					return C_QuestLog.IsQuestFlaggedCompleted(qid)
				end
				return completed and completed[qid] == true
			end

			result[profId] = {
				name   = meta.name or prof.name,
				icon   = meta.icon or prof.icon,
				weekly = BuildWeeklyStatusForProfession(prof, isQuestDone),
			}
		end
	end

    return next(result) and result or nil
end

function D:GetSkinningProgress(charKey)
    if not charKey then return nil end

    local current = GetCurrentCharKey()

    if charKey == current then
        if PT.SkinningCheck then
            return PT.SkinningCheck:GetProgress()
        end
        return nil
    end

	local db = EnsureDB()
	local entry = db[charKey]
	local snap = entry and entry.skinning

	if not snap then
		return nil
	end

	if snap.dayKey ~= GetServerDayKey() then
		snap.dayKey = GetServerDayKey()
		snap.done = 0
	end

	return snap.done, snap.total

end

function D:GetConcentration(charKey, expansion)
    if not charKey or not expansion then return nil end

    local current = GetCurrentCharKey()

	if charKey == current and not InCombatLockdown() then
		local live = GetPlayerConcentrationByProfession(expansion)
		if live then
			return live
		end
	end

    local db = EnsureDB()
    local entry = db[charKey]
	
    if not entry or not entry.concentration then
        return nil
    end

    local snap = entry.concentration[expansion]
	
    if type(snap) ~= "table" then
        return nil
    end

    if not snap.updated then
        return nil
    end

    local result = {}
    for skillLine, info in pairs(snap.profs) do
		local v = info.value

		if info.hadCurrency
		   and type(v) == "number"
		   and v > 0
		   and type(info.lastUpdate) == "number"
		then
			local newValue, newUpdate = ApplyConcentrationRegen(v, info.lastUpdate)

            v = newValue
            info.lastUpdate = newUpdate
            info.value = newValue
		end
        result[skillLine] = {
            value = v,
            name  = info.name,
            icon  = info.icon,
        }
    end

    return next(result) and result or nil
end

function D:GetTimewalkingStatus(charKey)
    local twKey = GetActiveTimewalking()
    if not twKey then
        return nil
    end

    local info = D.TIMEWALKING[twKey]
    if not info then
        return nil
    end

    local current = GetCurrentCharKey()
    local done = false

    if charKey == current then

        for _, q in ipairs(info.quests) do
            if C_QuestLog.IsQuestFlaggedCompleted(q) then
                done = true
                break
            end
        end
    else

        local db = EnsureDB()
        local entry = db[charKey]

        done = entry
            and entry.timewalking
            and entry.timewalking[twKey]
            and entry.timewalking[twKey].done
            or false
    end

    return {
        key  = twKey,
		atlas = info.atlas,
        done = done,
    }
end

function D:ResetAll()
    if AhUI_DB and AhUI_DB.accountOverview then
        AhUI_DB.accountOverview = {
            chars = {}
        }
    end

    if PT.AccountOverview and PT.AccountOverview.Refresh then
        PT.AccountOverview:Refresh()
    end
end

local pendingFullConc = {}
D.pendingFullConc = pendingFullConc
local notifiedFullConc = {}
D.notifiedFullConc = notifiedFullConc

function D:CheckFullConcentration(charKey)
    if not charKey then return end

    notifiedFullConc[charKey] = notifiedFullConc[charKey] or {}

    for _, exp in ipairs({ "df", "tww", "mid" }) do
            local data = self:GetConcentration(charKey, exp)

            if type(data) == "table" then
                notifiedFullConc[charKey][exp] =
                    notifiedFullConc[charKey][exp] or {}

				for skillLine, info in pairs(data) do
					local profKey = tostring(skillLine)
                    local wasNotified =
                        notifiedFullConc[charKey][exp][profKey]

                    if info.value
                       and info.value >= CONCENTRATION_NOTIFY_THRESHOLD
                       and not wasNotified
                       and D:IsConcentrationProfEnabled(charKey, exp, skillLine)
                    then
                        pendingFullConc[charKey] =
                            pendingFullConc[charKey] or {}

                        table.insert(pendingFullConc[charKey], {
                            charKey   = charKey,
                            exp       = exp,
                            value     = info.value,
                            name      = info.name,
                            icon      = info.icon,
                            skillLine = skillLine,
                        })

                        notifiedFullConc[charKey][exp][profKey] = true
                    end

                    if info.value
                       and info.value < CONCENTRATION_NOTIFY_THRESHOLD
                    then
                        notifiedFullConc[charKey][exp][profKey] = nil
                    end
                end
            end
    end
end

local ev = CreateFrame("Frame")
ev:RegisterEvent("PLAYER_LOGIN")
ev:RegisterEvent("PLAYER_LOGOUT")
ev:RegisterEvent("QUEST_TURNED_IN")
ev:RegisterEvent("SKILL_LINES_CHANGED")   
ev:RegisterEvent("TRADE_SKILL_SHOW")
ev:SetScript("OnEvent", function(_, event)
	if event == "PLAYER_LOGIN" then
		CleanupInvalidCharacterKeys()
		CleanupLegacyConcentrationData()
		D:GetColumnFilters()

        AhUI_Config = AhUI_Config or {}
        local profs = AhUI_Config.concNotifyProfs

        if type(profs) == "table" then
            for charKey, data in pairs(profs) do

                local isOld = false
                for k, v in pairs(data) do
                    if type(v) == "boolean" then
                        isOld = true
                        break
                    end
                end

                if isOld then
                    local new = {}

                    new.df = {}

                    for skillLine, enabled in pairs(data) do
                        if enabled == true then
                            new.df[tostring(skillLine)] = true
                        end
                    end

                    profs[charKey] = new
                end
            end
        end
	end
	if IsSafeEnvironment() then
		D:SaveCurrentCharacterSnapshot()
	end

	local chars = AhUI_DB
		and AhUI_DB.accountOverview
		and AhUI_DB.accountOverview.chars

	if chars then
		for charKey in pairs(chars) do
			D:CheckFullConcentration(charKey)
		end

        if PT.AccountOverview_UI and next(D.pendingFullConc) then
            PT.AccountOverview_UI:ShowConcentrationNotification()
        end
	end

    if event ~= "PLAYER_LOGOUT"
       and PT.AccountOverview
       and PT.AccountOverview.Refresh
    then
        PT.AccountOverview:Refresh()
    end
end)

if not D._concTicker then
	D._concTicker = C_Timer.NewTicker(CONCENTRATION_REGEN_SEC, function()
        if not IsSafeEnvironment() then
            return
        end

		local chars = AhUI_DB
			and AhUI_DB.accountOverview
			and AhUI_DB.accountOverview.chars

		if not chars then return end

		for charKey in pairs(chars) do
			D:CheckFullConcentration(charKey)
		end

        if PT.AccountOverview_UI and next(D.pendingFullConc) then
            PT.AccountOverview_UI:ShowConcentrationNotification()
        end
	end)
end

function D:Reset()
    if AhUI_DB and AhUI_DB.accountOverview then
        wipe(AhUI_DB.accountOverview)
    end
end

local DEFAULT_COLUMN_FILTERS = {
    c_df   = true,
    c_tww  = true,
    c_mid  = true,

    kp_df  = true,
    kp_tww = true,
    kp_mid = true,

    skin   = true,
    tw     = true,
}

function D:GetColumnFilters()
    AhUI_Config = AhUI_Config or {}
    AhUI_Config.accountOverviewCols =
        AhUI_Config.accountOverviewCols or {}

    local filters = AhUI_Config.accountOverviewCols

    for key, def in pairs(DEFAULT_COLUMN_FILTERS) do
        if filters[key] == nil then
            filters[key] = def
        end
    end

    return filters
end

function D:GetConcentrationNotifyConfig()
    AhUI_Config = AhUI_Config or {}
    AhUI_Config.concNotifyProfs = AhUI_Config.concNotifyProfs or {}
    return AhUI_Config.concNotifyProfs
end

function D:IsConcentrationProfEnabled(charKey, expansion, skillLine)
    local profs = AhUI_Config and AhUI_Config.concNotifyProfs
    if not profs or not charKey or not expansion then
        return false
    end

    local charProfs = profs[charKey]
    if not charProfs then return false end

    local expProfs = charProfs[expansion]
    if not expProfs then return false end

    return expProfs[tostring(skillLine)] == true
end

function D:SetConcentrationProfEnabled(charKey, expansion, skillLine, enabled)
    AhUI_Config = AhUI_Config or {}
    AhUI_Config.concNotifyProfs = AhUI_Config.concNotifyProfs or {}

    AhUI_Config.concNotifyProfs[charKey] =
        AhUI_Config.concNotifyProfs[charKey] or {}

    AhUI_Config.concNotifyProfs[charKey][expansion] =
        AhUI_Config.concNotifyProfs[charKey][expansion] or {}

    if enabled then
        AhUI_Config.concNotifyProfs[charKey][expansion][tostring(skillLine)] = true
    else
        AhUI_Config.concNotifyProfs[charKey][expansion][tostring(skillLine)] = nil
    end
end

SLASH_AHUICONC1 = "/ahuiconc"
SlashCmdList["AHUICONC"] = function()
    local name = UnitName("player")
    local realm = GetNormalizedRealmName()
    if not name or not realm then return end

    local charKey = name .. "-" .. realm

	pendingFullConc[charKey] = {
		{
			charKey = charKey,
			exp     = "df",
			value   = 1000,
			name    = "Debug Profession",
			icon    = 136240,
		}
	}
end