local _, ns = ...

------------------------------------------------------------
-- Recommendation labels and colors
------------------------------------------------------------
ns.RECOMMEND = {
    FREE_UPGRADE      = { label = "Free upgrade!",           color = "ff00ffff" }, -- cyan
    UPGRADE_NOW       = { label = "Upgrade now",             color = "ff00ff00" }, -- green
    UPGRADE_LATER     = { label = "Upgrade later",           color = "ffffff00" }, -- yellow
    HOLD_CRESTS       = { label = "Hold crests",             color = "ffff8800" }, -- orange
    SAFE_TEMP         = { label = "Safe temporary upgrade",  color = "ff88bbff" }, -- light blue
    BAD_INVESTMENT    = { label = "Bad investment",           color = "ffff0000" }, -- red
    CRAFT_INSTEAD     = { label = "Craft instead",            color = "ffcc66ff" }, -- purple
    USE_LOWER_TRACK   = { label = "Use cheaper crests",        color = "ff66bbff" }, -- light blue
    WAIT_BETTER       = { label = "Wait for better source",   color = "ffaaaaaa" }, -- grey
    MAXED             = { label = "Max rank",                 color = "ff888888" }, -- dark grey
    NO_ITEM           = { label = "",                         color = "ff555555" },
}

------------------------------------------------------------
-- Crest cost system
------------------------------------------------------------
-- Base cost per upgrade
ns.BASE_CREST_COST = 20
-- Discounted cost (high-watermark achievement)
ns.DISCOUNTED_CREST_COST = 10

-- Account-wide achievement IDs for 50% crest cost discount (Midnight S1)
-- When unlocked, upgrades of that crest type cost 10 instead of 20 on alts
ns.DISCOUNT_ACHIEVEMENTS = {
    Adventurer = 61809, -- Adventurer of the Dawn
    Veteran    = 42767, -- Veteran of the Dawn
    Champion   = 42768, -- Champion of the Dawn
    Hero       = 42769, -- Hero of the Dawn
    Myth       = 42770, -- Myth of the Dawn
}

-- Cache achievement status (checked once per session)
local discountCache = {}

function ns:HasDiscountAchievement(crestTrack)
    if discountCache[crestTrack] ~= nil then
        return discountCache[crestTrack]
    end

    local achieveID = ns.DISCOUNT_ACHIEVEMENTS[crestTrack]
    if achieveID then
        local ok, _, _, _, completed = pcall(GetAchievementInfo, achieveID)
        if ok and completed then
            discountCache[crestTrack] = true
            return true
        end
    end

    discountCache[crestTrack] = false
    return false
end

-- Clear cache (call on login/reload if needed)
function ns:ClearDiscountCache()
    wipe(discountCache)
end

function ns:GetCrestCost(crestTrack)
    -- Manual override first (user can toggle via /yyh discount)
    local db = YippYappHelperDB or {}
    local discounts = db.discounts or {}

    if discounts[crestTrack] then
        return ns.DISCOUNTED_CREST_COST
    end

    -- Check account-wide achievement
    if ns:HasDiscountAchievement(crestTrack) then
        return ns.DISCOUNTED_CREST_COST
    end

    return ns.BASE_CREST_COST
end

-- Is the discount missing but potentially coming from the player's main?
-- Used in Alt mode to decide whether to hold spending
function ns:IsDiscountPending(crestTrack)
    local profile = ns:GetCurrentProfile()
    if profile.id ~= "alt" then return false end

    -- If discount already unlocked, not pending
    if ns:HasDiscountAchievement(crestTrack) then return false end

    -- Manual override means player already has it
    local db = YippYappHelperDB or {}
    local discounts = db.discounts or {}
    if discounts[crestTrack] then return false end

    -- In alt mode, lower-value crests are less worth waiting for
    -- Only flag as pending for precious crests
    return ns:IsCrestPrecious(crestTrack)
end

-- How many upgrades can the player afford for a given crest track?
function ns:GetAffordableUpgrades(crestTrack)
    local count = ns:GetCrestCountByTrack(crestTrack)
    local cost = ns:GetCrestCost(crestTrack)
    if cost == 0 then return 99 end
    return math.floor(count / cost)
end

-- How many crests can still be earned this season (current cap - total earned)
function ns:GetEarnableCrests(crestTrack)
    local info = ns:GetCrestWeeklyInfo(crestTrack)
    if not info then return 0 end
    -- seasonCap comes from GetCrestInfo, not weekly info
    for _, crest in ipairs(ns.CRESTS) do
        if crest.track == crestTrack then
            local cInfo = C_CurrencyInfo.GetCurrencyInfo(crest.id)
            if cInfo and cInfo.useTotalEarnedForMaxQty and cInfo.maxQuantity then
                return math.max(cInfo.maxQuantity - (cInfo.totalEarned or 0), 0)
            end
        end
    end
    return 0
end

-- Total budget: current crests + still earnable this season
function ns:GetTotalCrestBudget(crestTrack)
    return ns:GetCrestCountByTrack(crestTrack) + ns:GetEarnableCrests(crestTrack)
end

-- Achievement progress: how many slots still need upgrades to earn a discount achievement?
-- Uses ilvl thresholds so cross-track items count (e.g. Champion 1/5 at 246 satisfies Veteran).
-- Returns: slotsRemaining, totalCrestCost, upgradeableCount, perSlotInfo[]
function ns:GetAchievementProgress(targetTrack)
    local targetIlvl = ns:GetMaxIlvlForTrack(targetTrack)
    if targetIlvl == 0 then return 0, 0, 0, {} end

    local slotsNeeded = {}
    local totalCrestCost = 0
    local upgradeableCount = 0

    for _, slotInfo in ipairs(ns.SLOT_IDS) do
        local info = ns:GetSlotInfo(slotInfo.slot)

        if not info or info.ilvl == 0 then
            -- Empty slot — needs a drop, can't upgrade
            table.insert(slotsNeeded, {
                slotID = slotInfo.slot,
                slotName = slotInfo.name,
                needsReplacement = true,
            })
        elseif info.ilvl < targetIlvl then
            -- Below threshold — check if upgradeable to reach it
            local canUpgrade, upgradeInfo = ns:CanUpgradeItem(slotInfo.slot)
            if upgradeInfo and canUpgrade then
                local track = upgradeInfo.track
                local levels = ns.GEAR_TRACKS[track]
                local trackMax = levels and levels[#levels] or 0

                if trackMax >= targetIlvl then
                    -- Can reach threshold by upgrading within current track
                    local crestTrack = ns.TRACK_CREST[track]
                    local cost = ns:GetCrestCost(crestTrack)
                    local ranksNeeded = 0
                    for r = upgradeInfo.currUpgrade + 1, #levels do
                        ranksNeeded = ranksNeeded + 1
                        if levels[r] >= targetIlvl then break end
                    end
                    local slotCost = ranksNeeded * cost
                    totalCrestCost = totalCrestCost + slotCost
                    upgradeableCount = upgradeableCount + 1
                    table.insert(slotsNeeded, {
                        slotID = slotInfo.slot,
                        slotName = slotInfo.name,
                        currentIlvl = info.ilvl,
                        crestTrack = crestTrack,
                        ranksNeeded = ranksNeeded,
                        crestCost = slotCost,
                    })
                else
                    -- Track caps below threshold — needs a higher-track drop
                    table.insert(slotsNeeded, {
                        slotID = slotInfo.slot,
                        slotName = slotInfo.name,
                        currentIlvl = info.ilvl,
                        needsReplacement = true,
                    })
                end
            else
                -- Can't upgrade (maxed in a low track) — needs replacement
                table.insert(slotsNeeded, {
                    slotID = slotInfo.slot,
                    slotName = slotInfo.name,
                    currentIlvl = info.ilvl,
                    needsReplacement = true,
                })
            end
        end
        -- ilvl >= targetIlvl: slot already counts, skip
    end

    return #slotsNeeded, totalCrestCost, upgradeableCount, slotsNeeded
end

------------------------------------------------------------
-- Replacement risk assessment
------------------------------------------------------------
function ns:GetReplacementRisk(slotID, ilvl)
    local profile = ns:GetCurrentProfile()

    local farmableIlvl = 0
    local vaultIlvl = 0

    -- M+ loot ceiling based on profile key level
    for _, entry in ipairs(ns.DUNGEON_LOOT) do
        local keyNum = tonumber(entry.key:match("M(%d+)"))
        if keyNum and keyNum <= profile.maxKeyLevel then
            if entry.loot > farmableIlvl then farmableIlvl = entry.loot end
            if entry.vault and entry.vault > vaultIlvl then vaultIlvl = entry.vault end
        end
    end

    -- Raid ceiling
    local raidTrack = ns.RAID_TRACKS[profile.raidTier]
    if raidTrack then
        local raidMax = ns:GetMaxIlvlForTrack(raidTrack)
        if raidMax > farmableIlvl then farmableIlvl = raidMax end
    end

    local ceiling = math.max(farmableIlvl, vaultIlvl)

    if ceiling == 0 then
        return "low"
    end

    local gap = ceiling - ilvl

    if gap >= 20 then
        return "high"
    elseif gap >= 10 then
        return "moderate"
    else
        return "low"
    end
end

------------------------------------------------------------
-- Core recommendation engine
------------------------------------------------------------
function ns:GetRecommendation(slotID)
    local info = ns:GetSlotInfo(slotID)
    if not info then
        return ns.RECOMMEND.NO_ITEM, ""
    end

    local canUpgrade, upgradeInfo = ns:CanUpgradeItem(slotID)

    if not upgradeInfo then
        return ns.RECOMMEND.NO_ITEM, ""
    end

    if not canUpgrade then
        return ns.RECOMMEND.MAXED, "Fully upgraded"
    end

    local ilvl = info.ilvl
    local track = upgradeInfo.track or ns:GetTrackFromIlvl(ilvl)
    local crestTrack = ns.TRACK_CREST[track]
    local slotPriority = ns.SLOT_PRIORITY[slotID] or 2
    local replacementRisk = ns:GetReplacementRisk(slotID, ilvl)
    local isFree = ns:IsCrestFree(crestTrack)
    local isPrecious = ns:IsCrestPrecious(crestTrack)
    local trackRank = ns.TRACK_RANK[track] or 1
    local rank = upgradeInfo.currUpgrade
    local maxRank = upgradeInfo.maxUpgrade

    local crestCost = ns:GetCrestCost(crestTrack)
    local affordable = ns:GetAffordableUpgrades(crestTrack)
    local crestCount = ns:GetCrestCountByTrack(crestTrack)
    local upgradesNeeded = maxRank - rank
    local totalCost = upgradesNeeded * crestCost
    local totalBudget = ns:GetTotalCrestBudget(crestTrack)
    local earnable = ns:GetEarnableCrests(crestTrack)

    -- Track overlap: first N ranks of Hero/Myth are covered by cheaper crests
    local overlap = ns.TRACK_FREE_RANKS[track]
    local overlapRanks = overlap and overlap.count or 0

    -- High-water mark: upgrades are free up to previously reached ilvl
    local freeIlvl = ns:GetFreeUpgradeIlvl(slotID)
    local freeRanks = 0
    if freeIlvl > ilvl then
        local levels = ns.GEAR_TRACKS[track]
        if levels then
            for r = rank + 1, maxRank do
                if levels[r] and levels[r] <= freeIlvl then
                    freeRanks = freeRanks + 1
                else
                    break
                end
            end
        end
    end

    -- ============================================================
    -- RULE 0: Free upgrades via high-water mark
    -- If the item can be upgraded for free (previously had higher
    -- rank in this slot), tell the player immediately.
    -- ============================================================
    if freeRanks > 0 then
        if freeRanks >= upgradesNeeded then
            return ns.RECOMMEND.FREE_UPGRADE,
                "All remaining ranks are free — upgrade at the vendor"
        else
            local freeTarget = ns.GEAR_TRACKS[track] and ns.GEAR_TRACKS[track][rank + freeRanks]
            local paidRanks = upgradesNeeded - freeRanks
            local paidCost = paidRanks * crestCost
            return ns.RECOMMEND.FREE_UPGRADE,
                freeRanks .. " free ranks to " .. (freeTarget or "?") ..
                " — then " .. paidCost .. " " .. crestTrack .. " for the rest"
        end
    end

    -- ============================================================
    -- RULE 1: Item at track cap, next track exists
    -- ============================================================
    if rank == maxRank and trackRank < #ns.TRACK_ORDER then
        local nextTrack = ns.TRACK_ORDER[trackRank + 1]
        return ns.RECOMMEND.WAIT_BETTER,
            "Fully upgraded — need a " .. nextTrack .. " drop to go higher"
    end

    -- ============================================================
    -- RULE 2: Alt mode — discount achievement not yet unlocked
    -- ============================================================
    if ns:IsDiscountPending(crestTrack) then
        if slotPriority >= 4 and replacementRisk == "low" then
            return ns.RECOMMEND.SAFE_TEMP,
                "Costs 2x until your main earns " .. crestTrack .. " of the Dawn"
        else
            return ns.RECOMMEND.HOLD_CRESTS,
                "Wait for " .. crestTrack .. " of the Dawn on your main (50% off)"
        end
    end

    -- ============================================================
    -- RULE 3: Precious crest savings via lower-tier upgrade path
    -- For Hero/Myth items at low ranks (within the overlap zone),
    -- advise spending cheaper crests on a lower-track piece first.
    -- The auto-promotion covers the first N ranks for free.
    -- ============================================================
    if overlap and rank < overlapRanks then
        local prevCrestCount = ns:GetCrestCountByTrack(ns.TRACK_CREST[overlap.prevTrack])
        return ns.RECOMMEND.USE_LOWER_TRACK,
            "Rank " .. rank .. "/" .. maxRank .. " — max a " .. overlap.prevTrack ..
            " piece to get free promotion to rank " .. overlapRanks ..
            " (" .. prevCrestCount .. " " .. overlap.prevTrack .. " available)"
    end

    -- ============================================================
    -- RULE 4: Maxed track — need a higher track drop
    -- ============================================================
    if rank == maxRank then
        local nextTrackIdx = ns.TRACK_RANK[track] and ns.TRACK_RANK[track] + 1 or nil
        local nextTrack = nextTrackIdx and ns.TRACK_ORDER[nextTrackIdx]
        if nextTrack then
            return ns.RECOMMEND.WAIT_BETTER,
                "Fully upgraded — need a " .. nextTrack .. " drop to go higher"
        end
    end

    -- ============================================================
    -- RULE 4.5: Achievement completion priority
    -- Check each missing achievement (lowest first). If this slot
    -- is below the threshold and can be upgraded to reach it,
    -- prioritize that. Cross-track items count: e.g. Champion 1/5
    -- at ilvl 246 already satisfies Veteran (max 246).
    -- ============================================================
    for _, achieveTrack in ipairs(ns.TRACK_ORDER) do
        if not ns:HasDiscountAchievement(achieveTrack) then
            local achieveIlvl = ns:GetMaxIlvlForTrack(achieveTrack)
            if ilvl < achieveIlvl then
                -- This slot is below the achievement threshold
                local slotsRemaining, totalAchieveCost, upgradeableCount, slots =
                    ns:GetAchievementProgress(achieveTrack)

                -- Only recommend if all remaining slots are upgradeable (no drops needed)
                if slotsRemaining > 0 and slotsRemaining == upgradeableCount then
                    -- Find this slot's specific cost
                    local thisSlotCost = 0
                    local thisSlotCrestTrack = crestTrack
                    for _, s in ipairs(slots) do
                        if s.slotID == slotID then
                            thisSlotCost = s.crestCost
                            thisSlotCrestTrack = s.crestTrack or crestTrack
                            break
                        end
                    end

                    local achieveName = achieveTrack .. " of the Dawn"
                    if slotsRemaining == 1 then
                        return ns.RECOMMEND.UPGRADE_NOW,
                            "Last slot for " .. achieveName .. "! 50% off for alts (" ..
                            thisSlotCost .. " " .. thisSlotCrestTrack .. ")"
                    else
                        return ns.RECOMMEND.UPGRADE_NOW,
                            slotsRemaining .. " slots to " .. achieveName ..
                            " — 50% off for alts (" .. totalAchieveCost .. " crests total)"
                    end
                end
            end
            break -- Only chase the lowest missing achievement
        end
    end

    -- ============================================================
    -- Crests can ONLY be spent on their own track's gear.
    -- The question is: which items on this track to prioritize.
    -- "Precious" means limited supply — prioritize high-value slots.
    -- "Free" means abundant — upgrade everything.
    -- ============================================================
    -- Afford string: show current/needed, plus earnable if relevant
    local affordStr
    if affordable >= upgradesNeeded then
        affordStr = " (" .. totalCost .. " " .. crestTrack .. ")"
    elseif earnable > 0 then
        affordStr = " (" .. crestCount .. "/" .. totalCost .. " " .. crestTrack .. ", +" .. earnable .. " earnable)"
    else
        affordStr = " (" .. crestCount .. "/" .. totalCost .. " " .. crestTrack .. ", capped)"
    end

    -- Calculate total crests needed for ALL items on this track
    local totalTrackCost = 0
    for _, si in ipairs(ns.SLOT_IDS) do
        local canUp2, upInfo2 = ns:CanUpgradeItem(si.slot)
        if canUp2 and upInfo2 then
            local t2 = upInfo2.track or ns:GetTrackFromIlvl(upInfo2.currIlvl)
            if ns.TRACK_CREST[t2] == crestTrack then
                totalTrackCost = totalTrackCost + (upInfo2.maxUpgrade - upInfo2.currUpgrade) * crestCost
            end
        end
    end
    -- Are crests scarce? (total budget can't cover all items on this track)
    local crestsScarce = totalBudget < totalTrackCost

    -- ============================================================
    -- Calculate upgrade priority rank among all upgradeable items
    -- on the same crest track, sorted by slot priority descending.
    -- ============================================================
    local upgradeRank = 1
    local sameTrackCount = 0
    for _, si in ipairs(ns.SLOT_IDS) do
        local canUp2, upInfo2 = ns:CanUpgradeItem(si.slot)
        if canUp2 and upInfo2 then
            local t2 = upInfo2.track or ns:GetTrackFromIlvl(upInfo2.currIlvl)
            local ct2 = ns.TRACK_CREST[t2]
            if ct2 == crestTrack then
                sameTrackCount = sameTrackCount + 1
                local pri2 = ns.SLOT_PRIORITY[si.slot] or 2
                -- Only count as higher rank if STRICTLY higher priority
                if pri2 > slotPriority then
                    upgradeRank = upgradeRank + 1
                end
            end
        end
    end

    local ordinal = upgradeRank == 1 and "1st" or upgradeRank == 2 and "2nd" or upgradeRank == 3 and "3rd" or (upgradeRank .. "th")
    local rankStr = "Upgrade " .. ordinal .. " of " .. sameTrackCount .. " " .. crestTrack

    -- ============================================================
    -- RULE 5-7: Prioritize by slot value + crest scarcity
    -- ============================================================
    if upgradeRank <= 2 then
        if replacementRisk == "high" then
            return ns.RECOMMEND.SAFE_TEMP, rankStr .. " — may get replaced" .. affordStr
        end
        return ns.RECOMMEND.UPGRADE_NOW, rankStr .. affordStr
    elseif upgradeRank <= 4 then
        if crestsScarce then
            return ns.RECOMMEND.UPGRADE_LATER, rankStr .. " — crests are limited" .. affordStr
        end
        return ns.RECOMMEND.UPGRADE_NOW, rankStr .. affordStr
    else
        if crestsScarce then
            return ns.RECOMMEND.UPGRADE_LATER, rankStr .. " — upgrade top slots first" .. affordStr
        end
        return ns.RECOMMEND.UPGRADE_LATER, rankStr .. affordStr
    end
end

------------------------------------------------------------
-- Batch: get recommendations for all slots
------------------------------------------------------------
function ns:GetAllRecommendations()
    local results = {}
    for _, slotInfo in ipairs(ns.SLOT_IDS) do
        local rec, reason = ns:GetRecommendation(slotInfo.slot)
        results[slotInfo.slot] = {
            recommendation = rec,
            reason = reason,
            slotName = slotInfo.name,
            slotID = slotInfo.slot,
        }
    end
    return results
end
