local PT = AhUI
local L = PT.L

PT.spellIdIdx                      = PT.spellIdIdx or {}
PT.questIdIdx                      = PT.questIdIdx or {}
PT.idIdx                           = PT.idIdx or {}
PT.professionSpellIdIdx            = PT.professionSpellIdIdx or {}
PT.professionCatchUpCurrencyIdIdx  = PT.professionCatchUpCurrencyIdIdx or {}

PT.professionCache = PT.professionCache or {}
PT.dbList = PT.dbList or { "DF", "TWW", "MID" }

local spellIdIdx                   = PT.spellIdIdx
local questIdIdx                   = PT.questIdIdx
local idIdx                        = PT.idIdx
local professionSpellIdIdx         = PT.professionSpellIdIdx
local professionCatchUpCurrencyIdIdx = PT.professionCatchUpCurrencyIdIdx

local AhUI_Item = {}
AhUI_Item.__index = AhUI_Item
PT.AhUI_Item = AhUI_Item

PT.MID_FALLBACK_NAMES = {
    [2912] = "Midnight Herbalism",
	[2915] = "Midnight Leatherworking",
	[2916] = "Midnight Mining",
	[2917] = "Midnight Skinning",
	[2918] = "Midnight Tailoring",
	[2906] = "Midnight Alchemy",
	[2907] = "Midnight Blacksmithing",
	[2909] = "Midnight Enchanting",
	[2910] = "Midnight Engineering",
	[2913] = "Midnight Inscription",
	[2914] = "Midnight Jewelcrafting"
}

function AhUI_Item:New(item)
    item = item or {}
    setmetatable(item, self)
    return item
end

local function NormalizeItemId(v)
    if type(v) == "number" then return v end
    if type(v) == "string" then return tonumber(v) end

    if type(v) == "table" then
        if type(v.id) == "number" then return v.id end
        if type(v[1]) == "number" then return v[1] end
    end

    return nil
end

function AhUI_Item:GetId()
    return NormalizeItemId(self.itemId)
end

function AhUI_Item:IsUnique()
    return false
end

function AhUI_Item:IsCatchUp()
    return false
end

function AhUI_Item:Show()
    return self.profession
       and self.profession.expanded
       and self:GetRemainingKnowledgePoints() > 0
end

function AhUI_Item:GetName()
    if self.name then return self.name end

    local id = NormalizeItemId(self.itemId)
    if id and C_Item and C_Item.GetItemNameByID then
        self.name = C_Item.GetItemNameByID(id)
    end

    return self.name or self.text or L["Not loaded yet"]
end

function AhUI_Item:GetIcon()
    if self.icon then return self.icon end
    if self.itemId then
        self.icon = select(5, C_Item.GetItemInfoInstant(self.itemId))
    end
    return self.icon
end

function AhUI_Item:GetCategoryIcon()
    return CreateAtlasMarkup(self.atlasIcon , 16, 16)
end

function AhUI_Item:GetRemainingKnowledgePoints()
    local questCount = #(self.questId or {})
    for _, questId in ipairs(self.questId or {}) do
        if C_QuestLog.IsQuestFlaggedCompleted(questId) then
            questCount = questCount - 1
        end
    end
    return questCount * (self.kp or 0)
end

function AhUI_Item:MeetRenownRequirements()
    if not self.renown then return true end
    local renownLevel = C_MajorFactions.GetCurrentRenownLevel(self.renown.majorFactionId) or 0
    return renownLevel >= self.renown.levelRequired
end

function AhUI_Item:MeetItemRequirements()
    for _, v in pairs(self.itemRequirements or {}) do
        local itemCount = C_Item.GetItemCount(v.id)
        if v.quantity > itemCount then return false end
    end
    return true
end

function AhUI_Item:MeetQuestRequirements()
    if not self.requiresQuestId then return true end

    for _, questId in ipairs(self.requiresQuestId) do
        if C_QuestLog.IsQuestFlaggedCompleted(questId) then
            return true
        end
    end

    return false
end

function AhUI_Item:MeetUnlockRequirements()
    for _, v in pairs(self.unlockRequirements or {}) do
        if v:GetRemainingKnowledgePoints() > 0 then return false end
    end
    return true
end

function AhUI_Item:MeetCurrencyRequirements()
    if not self.currency then return true end

    local list = self.currency.id and { self.currency } or self.currency
    for _, v in ipairs(list) do
        local info = C_CurrencyInfo.GetCurrencyInfo(v.id)
        if not info or info.quantity < (v.quantity or 0) then
            return false
        end
    end

    return true
end


function AhUI_Item:MeetRequirements()
    return self:MeetRenownRequirements()
       and self:MeetQuestRequirements()
       and self:MeetItemRequirements()
       and self:MeetCurrencyRequirements()
       and self:MeetUnlockRequirements()
end

function AhUI_Item:GetRequirementsText()
    local lines = {}
if self.renown then
    local renownInfo = C_MajorFactions.GetMajorFactionData(self.renown.majorFactionId)
    if renownInfo then
        local have = renownInfo.renownLevel or 0
        local need = self.renown.levelRequired or 0
        local ok = have >= need

        local color = ok and "ff33ff33" or "ffff3333"
        table.insert(lines,
            WrapTextInColorCode(
                string.format("%s: %d/%d", renownInfo.name, have, need),
                color
            )
        )
    end
end
    if self.itemRequirements then
        for _, v in ipairs(self.itemRequirements) do
            local name = C_Item.GetItemNameByID(v.id) or ("ItemID "..v.id)
            local have = C_Item.GetItemCount(v.id)
            local need = v.quantity or 1
            local ok = have >= need

            local color = ok and "ff33ff33" or "ffff3333"
            table.insert(lines,
                WrapTextInColorCode(
                    string.format("%d/%d %s", have, need, name),
                    color
                )
            )
        end
    end

if self.currency then
    local list = self.currency.id and { self.currency } or self.currency
    for _, v in ipairs(list) do
        local info = C_CurrencyInfo.GetCurrencyInfo(v.id)
        if info then
            local ok = info.quantity >= v.quantity
            local color = ok and "ff33ff33" or "ffff3333"
            table.insert(lines,
                WrapTextInColorCode(
                    string.format("%d/%d %s", info.quantity, v.quantity, info.name),
                    color
                )
            )
        end
    end
end

    if #lines == 0 then
        return nil
    end

    return table.concat(lines, "\n")
end

function AhUI_Item:GetDescription()
    if not self.waypoint or not self.waypoint.map then
        return self.text
    end

    local renownText = ""
    if not self:MeetRenownRequirements() and self.renown then
        local renownInfo = C_MajorFactions.GetMajorFactionData(self.renown.majorFactionId)
        if renownInfo then
            renownText =
                renownInfo.name..": |cffFF0000"..
                tostring(renownInfo.renownLevel or 0)..
                "|r/"..tostring(self.renown.levelRequired).."\n"
        end
    end

    local itemRequirements = ""
    if self.itemRequirements then
        for _,v in pairs(self.itemRequirements) do
            local itemName = C_Item.GetItemNameByID(v.id) or L["item not loaded"]
            local itemCount = C_Item.GetItemCount(v.id)
            local meets = itemCount >= v.quantity
            local countStr = meets and tostring(itemCount)
                or WrapTextInColorCode(tostring(itemCount), "ffff0000")

            itemRequirements =
                itemRequirements..
                countStr.."/"..tostring(v.quantity).." "..itemName.."\n"
        end
    end

local currencyRequirements = ""
if self.currency then
    local list = self.currency.id and { self.currency } or self.currency
    for _, v in ipairs(list) do
        local info = C_CurrencyInfo.GetCurrencyInfo(v.id)
        if info then
            local meets = info.quantity >= v.quantity
            local currentStr = meets and tostring(info.quantity)
                or WrapTextInColorCode(tostring(info.quantity), "ffff0000")

            currencyRequirements =
                currencyRequirements..
                CreateSimpleTextureMarkup(info.iconFileID, 16, 16)..
                " "..info.name..": "..
                currentStr.."/"..tostring(v.quantity).."\n"
        end
    end
end


    local mapInfo = C_Map.GetMapInfo(self.waypoint.map)
    local mapName = mapInfo and mapInfo.name or L["Unknown"]

    return string.format("%s%s\n%s - x:%.2f y:%.2f",
        renownText..itemRequirements..currencyRequirements,
        self.text or self.name or L["Not loaded yet"],
        mapName,
        (self.waypoint.x or 0) * 100,
        (self.waypoint.y or 0) * 100
    )
end

local AhUI_CatchUp = AhUI_Item:New()
AhUI_CatchUp.__index = AhUI_CatchUp
PT.AhUI_CatchUp = AhUI_CatchUp

function AhUI_CatchUp:GetRemainingKnowledgePoints()
    local weekly = self.profession:GetCatchUpCurrencyLeft()
    for _, requirement in ipairs(self.unlockRequirements or {}) do
        weekly = weekly - requirement:GetRemainingKnowledgePoints()
    end
    return weekly
end

function AhUI_CatchUp:GetType()
    return 4
end

function AhUI_CatchUp:GetName()
    if self.text then return self.text end
    return AhUI_Item.GetName(self)
end

function AhUI_CatchUp:IsCatchUp()
    return true
end

function AhUI_CatchUp:Show()
    return not PT.config.hideCatchUp and AhUI_Item.Show(self)
end

function AhUI_CatchUp:MeetRequirements()
    return self.text ~= nil or AhUI_Item.MeetRequirements(self)
end

local AhUI_DarkmoonQuest = AhUI_Item:New()
AhUI_DarkmoonQuest.__index = AhUI_DarkmoonQuest
PT.AhUI_DarkmoonQuest = AhUI_DarkmoonQuest

function AhUI_DarkmoonQuest:GetId()
    return self.questId[1]
end

function AhUI_DarkmoonQuest:GetName()
    if self.name then return self.name end
    self.name = C_QuestLog.GetTitleForQuestID(self:GetId())
    return self.name
end

function AhUI_DarkmoonQuest:GetIcon()
    return 1100023
end

function AhUI_DarkmoonQuest:IsDmfUp()
    local dayOfWeek = tonumber(date("%w"))
    local dayOfMonth = tonumber(date("%e"))

    local firstSundayOfMonth = ((dayOfMonth - (dayOfWeek + 1)) % 7) + 1
    local daysSinceFirstSunday = dayOfMonth - firstSundayOfMonth
    return daysSinceFirstSunday >= 0 and daysSinceFirstSunday <= 6
end

function AhUI_DarkmoonQuest:GetRemainingKnowledgePoints()
    return not self:IsDmfUp() and 0 or AhUI_Item.GetRemainingKnowledgePoints(self)
end

function AhUI_DarkmoonQuest:GetCategoryIcon()
    return CreateAtlasMarkup("quest-recurring-available", 16, 16)
end

function AhUI_DarkmoonQuest:GetType()
    return 3
end

local AhUI_UniqueBook = AhUI_Item:New()
AhUI_UniqueBook.__index = AhUI_UniqueBook
PT.AhUI_UniqueBook = AhUI_UniqueBook

function AhUI_UniqueBook:IsUnique()
    return true
end

function AhUI_UniqueBook:GetRemainingKnowledgePoints()
    if self._done then return 0 end
    local remainingKp = AhUI_Item.GetRemainingKnowledgePoints(self)
    self._done = remainingKp == 0
    return remainingKp
end

function AhUI_UniqueBook:GetCategoryIcon()
    local atlasIcon = self.atlasIcon or "Levelup-Icon-Bag"
    return CreateAtlasMarkup(atlasIcon, 16, 16)
end

function AhUI_UniqueBook:GetType()
    return 1
end

function AhUI_UniqueBook:Show()
    return not PT.config.weekly and AhUI_Item.Show(self)
end

local AhUI_UniqueTreasure = AhUI_UniqueBook:New()
AhUI_UniqueTreasure.__index = AhUI_UniqueTreasure
PT.AhUI_UniqueTreasure = AhUI_UniqueTreasure

function AhUI_UniqueTreasure:GetCategoryIcon()
    local atlasIcon = self.atlasIcon or "poi-islands-table"
    local waypoint = self.waypoint
    local playerMap = C_Map.GetBestMapForUnit("player")

    if waypoint and playerMap then
        local parentMapInfo = C_Map.GetMapInfo(C_Map.GetMapInfo(playerMap).parentMapID or playerMap)
        local parentMap = parentMapInfo and parentMapInfo.mapID or playerMap
        local parentType = parentMapInfo and parentMapInfo.mapType or 0
        if parentType == 3 then
            playerMap = parentMap
        end

        local waypointMap = waypoint.map
        local parentWaypointInfo = C_Map.GetMapInfo(C_Map.GetMapInfo(waypointMap).parentMapID or waypointMap)
        local parentWaypointMap = parentWaypointInfo and parentWaypointInfo.mapID or waypointMap
        local parentWaypointType = parentWaypointInfo and parentWaypointInfo.mapType or 0
        if parentWaypointType == 3 then
            waypointMap = parentWaypointMap
        end

        if waypointMap == playerMap then
            return CreateAtlasMarkup(atlasIcon, 16, 16)
        end
    end
    return CreateAtlasMarkup("poi-islands-table", 16, 16, 0, 0, 64, 64, 64)
end

local AhUI_WeeklyTreasure = AhUI_Item:New()
AhUI_WeeklyTreasure.__index = AhUI_WeeklyTreasure
PT.AhUI_WeeklyTreasure = AhUI_WeeklyTreasure

function AhUI_WeeklyTreasure:GetCategoryIcon()
    local atlasIcon = self.atlasIcon or "VignetteLoot"
    return CreateAtlasMarkup(atlasIcon, 16, 16)
end

function AhUI_WeeklyTreasure:GetType()
    return 2
end

local AhUI_WeeklyQuestItem = AhUI_Item:New()
AhUI_WeeklyQuestItem.__index = AhUI_WeeklyQuestItem
PT.AhUI_WeeklyQuestItem = AhUI_WeeklyQuestItem

function AhUI_WeeklyQuestItem:GetRemainingKnowledgePoints()
    for _, questId in ipairs(self.questId or {}) do
        if C_QuestLog.IsQuestFlaggedCompleted(questId) then
            return 0
        end
    end
    return self.kp or 0
end

function AhUI_WeeklyQuestItem:GetCategoryIcon()
    return CreateAtlasMarkup("quest-recurring-available", 16, 16)
end

function AhUI_WeeklyQuestItem:GetType()
    return 2
end

local AhUI_Instructor = AhUI_WeeklyQuestItem:New()
AhUI_Instructor.__index = AhUI_Instructor
PT.AhUI_Instructor = AhUI_Instructor

function AhUI_Instructor:GetId()
    if self.questId and self.questId[1] then
        return self.questId[1]
    end
    return self.itemId or 0
end

local AhUI_Consortium = AhUI_WeeklyQuestItem:New()
AhUI_Consortium.__index = AhUI_Consortium
PT.AhUI_Consortium = AhUI_Consortium

function AhUI_Consortium:GetId()
    if self.questId and self.questId[1] then
        return self.questId[1]
    end
    return self.itemId or 0
end

local AhUI_Expedition = AhUI_UniqueTreasure:New()
AhUI_Expedition.__index = AhUI_Expedition
PT.AhUI_Expedition = AhUI_Expedition

function AhUI_Expedition:GetId()
    if self.questId and self.questId[1] then
        return self.questId[1]
    end
    return self.itemId or 0
end

local AhUI_ProfMaster = AhUI_UniqueTreasure:New()
AhUI_ProfMaster.__index = AhUI_ProfMaster
PT.AhUI_ProfMaster = AhUI_ProfMaster

function AhUI_ProfMaster:GetId()
    if self.questId and self.questId[1] then
        return self.questId[1]
    end
    return self.itemId or 0
end

local AhUI_Treatise = AhUI_WeeklyTreasure:New()
AhUI_Treatise.__index = AhUI_Treatise
PT.AhUI_Treatise = AhUI_Treatise

function AhUI_Treatise:Show()
    return not PT.config.hideTreatise and AhUI_Item.Show(self)
end

local AhUI_Profession = {}
AhUI_Profession.__index = AhUI_Profession
PT.AhUI_Profession = AhUI_Profession

function AhUI_Profession:New(professionId, spellId, catchUpCurrencyId)
    local profession = {}
    setmetatable(profession, AhUI_Profession)

    profession.id = professionId
    profession.spellId = spellId
    profession.catchUpCurrencyId = catchUpCurrencyId

    AhUI.professions = AhUI.professions or {}
    AhUI.professions[profession.id] = profession

    if spellId then
        professionSpellIdIdx[spellId] = profession
    end
    if catchUpCurrencyId then
        professionCatchUpCurrencyIdIdx[catchUpCurrencyId] = profession
    end

    if C_TradeSkillUI and C_TradeSkillUI.GetProfessionInfoBySkillLineID then
        local info = C_TradeSkillUI.GetProfessionInfoBySkillLineID(profession.id)
        if info then

            profession.name = info.professionName
                           or info.parentProfessionName
                           or info.skillLineName
                           or ("Profession " .. tostring(profession.id))

profession.skillLine =
    (info.parentProfessionID and info.parentProfessionID > 0 and info.parentProfessionID)
    or (info.professionID and info.professionID > 0 and info.professionID)
    or profession.id

            profession.icon = info.icon
                           or (C_TradeSkillUI.GetTradeSkillTexture and C_TradeSkillUI.GetTradeSkillTexture(profession.id))
                           or 134400
        else
            profession.name      = "Profession " .. tostring(profession.id)
            profession.skillLine = profession.id
            profession.icon      = 134400
        end
    else
        profession.name      = "Profession " .. tostring(profession.id)
        profession.skillLine = profession.id
        profession.icon      = 134400
    end
	
if not profession.name or profession.name == "" then
    if profession.isMID and PT.MID_FALLBACK_NAMES then
        local fallback = PT.MID_FALLBACK_NAMES[profession.id]
        if fallback then
            profession.name = fallback
        end
    end

    profession.name = profession.name or ("Profession " .. tostring(profession.id))
end


    profession.expanded = not PT.config.minimized
    profession.entries = {}
    profession.index = profession.id * 1000
    return profession
end

function AhUI_Profession:RefreshInfo()
    local id = self.id
local cached = PT.professionCache[id]
if cached and cached.name then
    self.name = cached.name
    self.icon = cached.icon
    if cached.skillLine then
        self.skillLine = cached.skillLine
    end
    return
end

if C_TradeSkillUI and C_TradeSkillUI.GetProfessionInfoBySkillLineID then
    local info = C_TradeSkillUI.GetProfessionInfoBySkillLineID(id)
    if info and (info.professionName or info.parentProfessionName or info.skillLineName) then
        self.name = info.professionName or info.parentProfessionName or info.skillLineName
        self.icon = info.icon or self.icon

        self.skillLine =
            (info.parentProfessionID and info.parentProfessionID > 0 and info.parentProfessionID)
            or (info.professionID and info.professionID > 0 and info.professionID)
            or self.id

        PT.professionCache[id] = {
            name = self.name,
            icon = self.icon,
            skillLine = self.skillLine,
        }

        return
    end
end

if self.isMID and PT.MID_FALLBACK_NAMES then
    local fallback = PT.MID_FALLBACK_NAMES[self.id]
    if fallback then
        self.name = fallback
    end
end

end

function PT.UpdateAllProfessionNames()
    for _, dbKey in ipairs(PT.dbList) do
        local db = PT.DB[dbKey]
        if db then
            for _, prof in pairs(db) do
                if type(prof) == "table" then
                    prof:RefreshInfo()
                end
            end
        end
    end
end

function AhUI_Profession:AddEntry(entry)
    entry.profession = self

    if entry:IsUnique() and entry:GetRemainingKnowledgePoints() == 0 then
        return self
    end

    if entry.spell then
        spellIdIdx[entry.spell] = entry
    end

    idIdx[entry:GetId()] = entry

    for _, questId in ipairs(entry.questId or {}) do
        questIdIdx[questId] = entry
    end

    table.insert(self.entries, entry)
    entry.index = self.index + #self.entries

    return self
end

function AhUI_Profession:GetSkillLevel()
    local prof1, prof2, _ = GetProfessions()
    if prof1 then
        local _,_,skillLevel,maxSkillLevel,_,_,skillLine, bonusSkill = GetProfessionInfo(prof1)
        if skillLine == self.skillLine then
            return {skillLevel=skillLevel, maxSkillLevel=maxSkillLevel, bonusSkill=bonusSkill}
        end
    end
    if prof2 then
        local _,_,skillLevel,maxSkillLevel,_,_,skillLine, bonusSkill = GetProfessionInfo(prof2)
        if skillLine == self.skillLine then
            return {skillLevel=skillLevel, maxSkillLevel=maxSkillLevel, bonusSkill=bonusSkill}
        end
    end
    return {skillLevel=0, maxSkillLevel=0, bonusSkill=0}
end

function AhUI_Profession:CalculateRemainingKps()
    local weekly, unique, catchUp = 0, 0, 0
    for _, item in pairs(self.entries) do
        local remaining = item:GetRemainingKnowledgePoints()
        if item:IsUnique() then
            unique = unique + remaining
        elseif item:IsCatchUp() then
            catchUp = catchUp + remaining
        else
            weekly = weekly + remaining
        end
    end
    return { weekly=weekly, unique=unique, catchUp=catchUp }
end

function AhUI_Profession:GetCatchUpCurrencyLeft()
    if not self.catchUpCurrencyId then return 0 end
    local currencyInfo = C_CurrencyInfo.GetCurrencyInfo(self.catchUpCurrencyId)
    if not currencyInfo then return 0 end
    return (currencyInfo.maxQuantity or 0) - (currencyInfo.quantity or 0)
end

local function GetPointsMissingForTree(configID, nodeID)
    local todo = { nodeID }
    local missing = 0
    while next(todo) do
        local nodeID2 = table.remove(todo)
        tAppendAll(todo, C_ProfSpecs.GetChildrenForPath(nodeID2))
        local info = C_Traits.GetNodeInfo(configID, nodeID2)
        if info then
            local enableFix = info.activeRank == 0 and 1 or 0
            missing = missing + info.maxRanks - info.activeRank - enableFix
        end
    end
    return missing
end

function AhUI_Profession:CalculateSpendableKps()
    local configID = C_ProfSpecs.GetConfigIDForSkillLine(self.id)
    if not configID then return 0 end

    local traitTreeIDs = C_ProfSpecs.GetSpecTabIDsForSkillLine(self.id) or {}
    local totalMissing = 0
    for _, traitTreeID in ipairs(traitTreeIDs) do
        local tabInfo = C_ProfSpecs.GetTabInfo(traitTreeID)
        if tabInfo then
            totalMissing = totalMissing + GetPointsMissingForTree(configID, tabInfo.rootNodeID)
        end
    end
    local currencyInfo = C_ProfSpecs.GetCurrencyInfoForSkillLine(self.id) or {numAvailable = 0}
    return totalMissing - currencyInfo.numAvailable
end

function AhUI_Profession:IsLearned()
        return true
end

function AhUI_Profession:GetAvailableItems()
    return self.entries
end

function PT.FindEntryById(entryId)
    return idIdx[entryId]
end

function PT.FindEntryByQuestId(questId)
    return questIdIdx[questId]
end

function PT.FindItemBySpellId(spellId)
    return spellIdIdx[spellId]
end

function PT.FindProfessionById(professionId)
    if PT.GetProfessions then
        return PT.GetProfessions()[professionId]
    end
end

function PT.FindProfessionBySpellId(spellId)
    return professionSpellIdIdx[spellId]
end

function PT.FindProfessionByCatchUpCurrencyId(catchUpCurrencyId)
    return professionCatchUpCurrencyIdIdx[catchUpCurrencyId]
end

local f = CreateFrame("Frame")
f:RegisterEvent("TRADE_SKILL_DATA_SOURCE_CHANGED")
f:RegisterEvent("SKILL_LINES_CHANGED")

f:SetScript("OnEvent", function()
    PT.UpdateAllProfessionNames()
    PT:RefreshData()
    PT:RefreshUI()
end)

return PT
