local addonName, ns = ...

ns.upgradeVendorOpen = false

ns.SLOT_IDS = {
    { slot = 1,  name = "Head",      anchor = "TOPLEFT",  x = 24,  y = -78  },
    { slot = 2,  name = "Neck",      anchor = "TOPLEFT",  x = 24,  y = -118 },
    { slot = 3,  name = "Shoulder",  anchor = "TOPLEFT",  x = 24,  y = -158 },
    { slot = 15, name = "Back",      anchor = "TOPLEFT",  x = 24,  y = -198 },
    { slot = 5,  name = "Chest",     anchor = "TOPLEFT",  x = 24,  y = -238 },
    { slot = 9,  name = "Wrist",     anchor = "TOPLEFT",  x = 24,  y = -278 },
    { slot = 10, name = "Hands",     anchor = "TOPRIGHT", x = -24, y = -78  },
    { slot = 6,  name = "Waist",     anchor = "TOPRIGHT", x = -24, y = -118 },
    { slot = 7,  name = "Legs",      anchor = "TOPRIGHT", x = -24, y = -158 },
    { slot = 8,  name = "Feet",      anchor = "TOPRIGHT", x = -24, y = -198 },
    { slot = 11, name = "Ring 1",    anchor = "TOPRIGHT", x = -24, y = -238 },
    { slot = 12, name = "Ring 2",    anchor = "TOPRIGHT", x = -24, y = -278 },
    { slot = 13, name = "Trinket 1", anchor = "TOPLEFT",  x = 24,  y = -318 },
    { slot = 14, name = "Trinket 2", anchor = "TOPRIGHT", x = -24, y = -318 },
    { slot = 16, name = "Main Hand", anchor = "BOTTOMLEFT",  x = 24,  y = 60   },
    { slot = 17, name = "Off Hand",  anchor = "BOTTOMRIGHT", x = -24, y = 60   },
}

------------------------------------------------------------
-- Hidden tooltip for scanning upgrade track info
------------------------------------------------------------
local upgradeScanTooltip = CreateFrame("GameTooltip", "YYHUpgradeScanTooltip", nil, "GameTooltipTemplate")
upgradeScanTooltip:SetOwner(WorldFrame, "ANCHOR_NONE")

-- Cache tooltip scan results; invalidated on UNIT_INVENTORY_CHANGED
local scanCache = {}
function ns:InvalidateScanCache()
    wipe(scanCache)
end

local function ScanUpgradeTrack(slotID)
    -- Return cached result if available
    if scanCache[slotID] then
        return scanCache[slotID].track, scanCache[slotID].rank, scanCache[slotID].maxRank, scanCache[slotID].crafted
    end
    upgradeScanTooltip:ClearLines()
    local ok = pcall(upgradeScanTooltip.SetInventoryItem, upgradeScanTooltip, "player", slotID)
    if not ok then return nil, nil, nil, false end

    local foundTrack, foundRank, foundMax
    local isCrafted = false

    for i = 1, upgradeScanTooltip:NumLines() do
        local line = _G["YYHUpgradeScanTooltipTextLeft" .. i]
        if line then
            local text = line:GetText() or ""
            -- Match "Upgrade Level: Champion 3/5" or similar
            if not foundTrack then
                local trackName, currRank, maxRank = text:match("Upgrade Level:%s+(%S+)%s+(%d+)/(%d+)")
                if trackName then
                    foundTrack = trackName
                    foundRank = tonumber(currRank)
                    foundMax = tonumber(maxRank)
                end
            end
            -- Detect crafted items: quality tier atlas icons (Professions-Icon-Quality-*)
            -- or crafted-by line
            if text:find("Professions%-Icon%-Quality") or text:find("Professions%-ChatIcon%-Quality") then
                isCrafted = true
            end
        end
    end

    scanCache[slotID] = { track = foundTrack, rank = foundRank, maxRank = foundMax, crafted = isCrafted }
    return foundTrack, foundRank, foundMax, isCrafted
end

-- Returns item info for an equipment slot
function ns:GetSlotInfo(slotID)
    local itemLink = GetInventoryItemLink("player", slotID)
    if not itemLink then
        return nil
    end

    local icon = GetInventoryItemTexture("player", slotID)
    local itemLevel = 0
    local itemQuality = 1

    -- Try multiple APIs for item level
    local itemLocation = ItemLocation:CreateFromEquipmentSlot(slotID)
    if C_Item.DoesItemExist(itemLocation) then
        local ok, lvl = pcall(C_Item.GetCurrentItemLevel, itemLocation)
        if ok and lvl then
            itemLevel = lvl
        end
        local ok2, qual = pcall(C_Item.GetItemQuality, itemLocation)
        if ok2 and qual then
            itemQuality = qual
        end
    end

    -- Fallback to GetDetailedItemLevelInfo
    if itemLevel == 0 then
        local ok, lvl = pcall(GetDetailedItemLevelInfo, itemLink)
        if ok and lvl then itemLevel = lvl end
    end

    -- Scan tooltip for actual upgrade track and crafted status
    local trackName, currRank, maxRank, isCrafted = ScanUpgradeTrack(slotID)

    return {
        link = itemLink,
        ilvl = itemLevel,
        quality = itemQuality,
        icon = icon,
        track = trackName,
        rank = currRank,
        maxRank = maxRank,
        crafted = isCrafted,
    }
end

-- Check if an item can be upgraded
-- Returns: canUpgrade (bool), upgradeInfo table { currUpgrade, maxUpgrade, track, currIlvl, maxIlvl }
function ns:CanUpgradeItem(slotID)
    local info = ns:GetSlotInfo(slotID)
    if not info then return false, nil end

    local ilvl = info.ilvl
    if not ilvl or ilvl == 0 then return false, nil end

    local track, rank, maxRank

    -- Crafted items without an upgrade track cannot be upgraded with crests
    if info.crafted and not info.track then
        return false, nil
    end

    -- Use tooltip-scanned track if available (accurate, handles ilvl overlaps)
    if info.track and ns.GEAR_TRACKS[info.track] then
        track = info.track
        rank = info.rank
        maxRank = info.maxRank
    else
        -- Fallback: guess from ilvl
        track = ns:GetTrackFromIlvl(ilvl)
        if not track then return false, nil end
        rank, maxRank = ns:GetRankInTrack(track, ilvl)

        -- If we can't find the exact rank, estimate it
        if not rank then
            local levels = ns.GEAR_TRACKS[track]
            if levels then
                for i, lvl in ipairs(levels) do
                    if ilvl <= lvl then
                        rank = i
                        break
                    end
                end
                rank = rank or #levels
                maxRank = #levels
            end
        end
    end

    if not rank or not maxRank then return false, nil end

    local maxIlvl = ns:GetMaxIlvlForTrack(track)
    local canUpgrade = rank < maxRank

    return canUpgrade, {
        currUpgrade = rank,
        maxUpgrade = maxRank,
        track = track,
        currIlvl = ilvl,
        maxIlvl = maxIlvl,
    }
end

-- Send an equipped item directly into the upgrade window
function ns:SendItemToUpgrade(slotID)
    if not ns.upgradeVendorOpen then
        print("|cff00ff00YippYapp Helper|r: Upgrade vendor is not open.")
        return
    end

    local itemLocation = ItemLocation:CreateFromEquipmentSlot(slotID)
    if not C_Item.DoesItemExist(itemLocation) then return end

    if C_ItemUpgrade and C_ItemUpgrade.SetItemUpgradeFromLocation then
        C_ItemUpgrade.SetItemUpgradeFromLocation(itemLocation)
    else
        ClearCursor()
        PickupInventoryItem(slotID)
        if ItemUpgradeFrame and ItemUpgradeFrame.ItemSlot then
            ItemUpgradeFrame.ItemSlot:Click()
        elseif ItemUpgradeFrame then
            local slot = ItemUpgradeFrame.LeftItemFrame or ItemUpgradeFrame.ItemButton
            if slot then
                slot:Click()
            end
        end
        ClearCursor()
    end
end

------------------------------------------------------------
-- High-water mark: query the game for the highest ilvl ever
-- reached in a slot. Upgrades below this are free (0 crests).
-- The API may only work when the upgrade vendor is open,
-- so we cache results for use when the vendor is closed.
------------------------------------------------------------
ns.watermarkCache = {}

local function QueryWatermark(slotID)
    if not C_ItemUpgrade then return 0 end

    local itemLocation = ItemLocation:CreateFromEquipmentSlot(slotID)
    if not C_Item.DoesItemExist(itemLocation) then return 0 end

    -- Try item-based watermark first
    if C_ItemUpgrade.GetHighWatermarkForItem then
        local ok, charMark, accountMark = pcall(C_ItemUpgrade.GetHighWatermarkForItem, itemLocation)
        if ok then
            local mark = math.max(charMark or 0, accountMark or 0)
            if mark > 0 then return mark end
        end
    end

    -- Fallback: slot-based watermark
    if C_ItemUpgrade.GetHighWatermarkSlotForItem and C_ItemUpgrade.GetHighWatermarkForSlot then
        local ok1, redundancySlot = pcall(C_ItemUpgrade.GetHighWatermarkSlotForItem, itemLocation)
        if ok1 and redundancySlot then
            local ok2, charMark, accountMark = pcall(C_ItemUpgrade.GetHighWatermarkForSlot, redundancySlot)
            if ok2 then
                return math.max(charMark or 0, accountMark or 0)
            end
        end
    end

    return 0
end

-- Refresh all watermarks (call when upgrade vendor opens)
function ns:RefreshWatermarks()
    for _, slotInfo in ipairs(ns.SLOT_IDS) do
        local mark = QueryWatermark(slotInfo.slot)
        if mark > 0 then
            ns.watermarkCache[slotInfo.slot] = mark
        end
    end
end

function ns:GetFreeUpgradeIlvl(slotID)
    -- Try live query first
    local mark = QueryWatermark(slotID)
    if mark > 0 then
        ns.watermarkCache[slotID] = mark
        return mark
    end
    -- Fall back to cached value
    return ns.watermarkCache[slotID] or 0
end

-- Anchor the helper frame relative to upgrade vendor or free-floating
function ns:AnchorFrame()
    local f = ns.MainFrame
    if not f then return end

    f:ClearAllPoints()

    if ns.upgradeVendorOpen and ItemUpgradeFrame and ItemUpgradeFrame:IsShown() then
        f:SetPoint("TOPLEFT", ItemUpgradeFrame, "TOPRIGHT", 2, 0)
    else
        f:SetPoint("CENTER")
    end
end

------------------------------------------------------------
-- Initialization & Events
------------------------------------------------------------
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PLAYER_INTERACTION_MANAGER_FRAME_SHOW")
eventFrame:RegisterEvent("PLAYER_INTERACTION_MANAGER_FRAME_HIDE")
eventFrame:RegisterEvent("CURRENCY_DISPLAY_UPDATE")
eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")

local ITEM_UPGRADE_INTERACTION = Enum.PlayerInteractionType.ItemUpgrade
local characterFrameHooked = false

eventFrame:SetScript("OnEvent", function(self, event, arg1)
    if event == "ADDON_LOADED" and arg1 == addonName then
        YippYappHelperDB = YippYappHelperDB or {}

        -- Restore previous raid scan (with type validation)
        if type(YippYappHelperDB.raidInspect) == "table" then
            ns.RaidInspectData = YippYappHelperDB.raidInspect
        end

        -- Validate discounts table
        if YippYappHelperDB.discounts and type(YippYappHelperDB.discounts) ~= "table" then
            YippYappHelperDB.discounts = nil
        end

        print("|cff00ff00YippYapp Helper|r loaded \226\128\148 type |cff00ff00/yh|r to open")

        -- Create or update /yh macro
        C_Timer.After(2, function()
            if not InCombatLockdown() then
                local MACRO_ICON = 1998635
                local idx = GetMacroIndexByName("YippYapp")
                if idx == 0 then
                    local numGlobal = GetNumMacros()
                    if numGlobal < MAX_ACCOUNT_MACROS then
                        CreateMacro("YippYapp", MACRO_ICON, "/yh", false)
                    end
                else
                    -- Update icon and body in case they changed
                    EditMacro(idx, "YippYapp", MACRO_ICON, "/yh")
                end
            end
        end)

        -- Show What's New window if version changed or first install
        ns.ADDON_VERSION = "2.0.1"
        local seenVersion = YippYappHelperDB.lastSeenVersion
        if seenVersion ~= ns.ADDON_VERSION then
            C_Timer.After(1, function()
                ns:ShowWelcome()
            end)
        end

        -- Suppress CharacterFrame when upgrade vendor is open
        -- (our slot buttons handle equipping instead)
        if CharacterFrame and not characterFrameHooked then
            CharacterFrame:HookScript("OnShow", function()
                if ns.upgradeVendorOpen then
                    HideUIPanel(CharacterFrame)
                end
            end)
            characterFrameHooked = true
        end

    elseif event == "PLAYER_INTERACTION_MANAGER_FRAME_SHOW" and arg1 == ITEM_UPGRADE_INTERACTION then
        ns.upgradeVendorOpen = true

        -- Cache watermarks while vendor is open (API may require it)
        ns:RefreshWatermarks()

        if ns.MainFrame then
            -- Un-parent from the app if currently embedded
            if ns.MainFrame.inAppMode then
                ns.MainFrame.inAppMode = nil
                if ns.SetGearAppMode then ns:SetGearAppMode(false) end
                ns.MainFrame:SetParent(UIParent)
                -- Navigate the app away from the gear page
                if ns.AppFrame and ns.AppFrame:IsShown() and ns.currentAppPage == "gear" then
                    ns:ShowAppPage("home")
                end
            end
            ns:RefreshAllSlots()
            ns:RefreshCrests()
            ns:AnchorFrame()
            ns.MainFrame:Show()
        end

    elseif event == "PLAYER_INTERACTION_MANAGER_FRAME_HIDE" and arg1 == ITEM_UPGRADE_INTERACTION then
        ns.upgradeVendorOpen = false
        if ns.MainFrame then
            ns.MainFrame:Hide()
        end

    elseif event == "UNIT_INVENTORY_CHANGED" and arg1 == "player" then
        -- Always invalidate tooltip cache when gear changes
        ns:InvalidateScanCache()
        -- Delay refresh so item data is fully updated
        if ns.MainFrame and ns.MainFrame:IsShown() then
            C_Timer.After(0.3, function()
                if ns.MainFrame and ns.MainFrame:IsShown() then
                    ns:InvalidateScanCache()
                    if ns.upgradeVendorOpen then
                        ns:RefreshWatermarks()
                    end
                    ns:RefreshCrests()
                    ns:RefreshAllSlots()
                end
            end)
        end
    elseif event == "CURRENCY_DISPLAY_UPDATE" then
        if ns.MainFrame and ns.MainFrame:IsShown() then
            ns:RefreshCrests()
            ns:RefreshAllSlots()
        end
        -- Also refresh dashboard if the app home page is visible
        if ns.AppFrame and ns.AppFrame:IsShown() and ns.currentAppPage == "home" then
            if ns._refreshDashboard then ns._refreshDashboard() end
        end
    end
end)

SLASH_YIPPYAPPHELPER1 = "/yh"
SLASH_YIPPYAPPHELPER2 = "/yippyapp"
SlashCmdList["YIPPYAPPHELPER"] = function(msg)
    msg = strtrim(msg or "")
    local cmd, arg = strsplit(" ", msg, 2)
    cmd = strlower(cmd or "")

    -- /yh help
    if cmd == "help" then
        print("|cff00ff00=== YippYapp Helper ===|r")
        print("  /yh — open dashboard")
        print("  /yh raid — scan raid/party for tier pieces")
        print("  /yh mplus — open Mythic+ helper")
        print("  /yh loot — open loot browser")
        print("  /yh profile — show/set player profile")
        print("  /yh profile <name> — set profile (normal, heroic, mythic)")
        print("  /yh discount <track> — toggle crest discount for a track (adventurer, veteran, champion, hero, myth)")
        print("  /yh discounts — show current discount status")
        print("  /yh release — toggle release spirit blocker in guild raids")
        print("  /yh welcome — reset and show the welcome window")
        print("  /yh debug — dump slot data to chat")
        return
    end

    -- /yh raid
    if cmd == "raid" then
        if ns.AppFrame then
            ns:ToggleApp()
            if ns.AppFrame:IsShown() then
                ns:ShowAppPage("raid")
            end
        elseif ns.RaidFrame then
            if ns.RaidFrame:IsShown() then
                ns.RaidFrame:Hide()
            else
                ns.RaidFrame:Show()
                if ns.RefreshRaidOverview then ns:RefreshRaidOverview() end
            end
        end
        return
    end

    -- /yh profile [name]
    if cmd == "profile" then
        if arg and strtrim(arg) ~= "" then
            local profileId = strlower(strtrim(arg))
            local found = false
            for _, profile in ipairs(ns.PROFILES) do
                if profile.id == profileId then
                    YippYappHelperDB.profile = profileId
                    print("|cff00ff00YippYapp Helper|r: Profile set to |cffffffff" .. profile.name .. "|r")
                    found = true
                    break
                end
            end
            if not found then
                print("|cff00ff00YippYapp Helper|r: Unknown profile. Options:")
                for _, p in ipairs(ns.PROFILES) do
                    print("  " .. p.id .. " — " .. p.name)
                end
            else
                -- Refresh if window is open
                if ns.MainFrame and ns.MainFrame:IsShown() then
                    ns:RefreshAllSlots()
                    ns:RefreshCrests()
                end
            end
        else
            local current = ns:GetCurrentProfile()
            print("|cff00ff00YippYapp Helper|r: Current profile: |cffffffff" .. current.name .. "|r")
            print("  Spend freely: " .. table.concat(current.freeCrests, ", "))
            print("  Precious: " .. table.concat(current.preciousCrests, ", "))
            print("Available profiles:")
            for _, p in ipairs(ns.PROFILES) do
                local marker = p.id == current.id and " |cff00ff00(active)|r" or ""
                print("  " .. p.id .. " — " .. p.name .. marker)
            end
        end
        return
    end

    -- /yh discount <track>
    if cmd == "discount" then
        if not arg or strtrim(arg) == "" then
            print("|cff00ff00YippYapp Helper|r: Usage: /yh discount <track>")
            print("  Tracks: adventurer, veteran, champion, hero, myth")
            return
        end
        local trackName = strtrim(arg)
        -- Capitalize first letter
        trackName = trackName:sub(1,1):upper() .. trackName:sub(2):lower()

        if not ns.GEAR_TRACKS[trackName] then
            print("|cff00ff00YippYapp Helper|r: Unknown track '" .. trackName .. "'")
            return
        end

        YippYappHelperDB.discounts = YippYappHelperDB.discounts or {}
        if YippYappHelperDB.discounts[trackName] then
            YippYappHelperDB.discounts[trackName] = nil
            print("|cff00ff00YippYapp Helper|r: " .. trackName .. " discount |cffff0000removed|r (20 crests per upgrade)")
        else
            YippYappHelperDB.discounts[trackName] = true
            print("|cff00ff00YippYapp Helper|r: " .. trackName .. " discount |cff00ff00enabled|r (10 crests per upgrade)")
        end

        if ns.MainFrame and ns.MainFrame:IsShown() then
            ns:RefreshAllSlots()
        end
        return
    end

    -- /yh discounts
    if cmd == "discounts" then
        print("|cff00ff00=== Crest Costs ===|r")
        for _, trackName in ipairs(ns.TRACK_ORDER) do
            local cost = ns:GetCrestCost(trackName)
            local hasAchieve = ns:HasDiscountAchievement(trackName)
            local db = YippYappHelperDB or {}
            local manualOverride = db.discounts and db.discounts[trackName]
            local source = ""
            if hasAchieve then
                source = " (achievement: " .. trackName .. " of the Dawn)"
            elseif manualOverride then
                source = " (manual override)"
            end
            local discounted = cost == ns.DISCOUNTED_CREST_COST
            local status = discounted
                and "|cff00ff0010 per rank|r" .. source
                or "20 per rank"
            print("  " .. trackName .. ": " .. status)
        end
        print("Use /yh discount <track> to manually toggle discounts")
        return
    end

    -- /yh welcome (debug: reset and show welcome window)
    if cmd == "loot" then
        if ns.AppFrame then
            ns:ToggleApp()
            if ns.AppFrame:IsShown() then
                ns:ShowAppPage("loot")
            end
        else
            if not ns.LootBrowserFrame then
                ns:CreateLootBrowserFrame()
            end
            if ns.LootBrowserFrame:IsShown() then
                ns.LootBrowserFrame:Hide()
            else
                ns:LootBrowser_DetectSpec()
                ns.LootBrowserFrame:Show()
                ns:LootBrowser_ShowPage()
            end
        end
        return
    end

    if cmd == "welcome" or cmd == "whatsnew" then
        YippYappHelperDB.lastSeenVersion = nil
        if ns.ShowWelcome then ns:ShowWelcome() end
        return
    end

    -- /yh mplus
    if cmd == "mplus" or cmd == "m+" or cmd == "mythicplus" then
        if ns.AppFrame then
            ns:ToggleApp()
            if ns.AppFrame:IsShown() then
                ns:ShowAppPage("mythicplus")
            end
        elseif ns.MythicPlusFrame then
            if ns.MythicPlusFrame:IsShown() then
                ns.MythicPlusFrame:Hide()
            else
                ns.MythicPlusFrame:Show()
                ns:RefreshMythicPlus()
            end
        end
        return
    end

    -- /yh release
    if cmd == "release" then
        if ns.ToggleReleaseBlocker then
            local enabled = ns:ToggleReleaseBlocker()
            if enabled then
                print("|cff00ff00YippYapp Helper|r: Release blocker |cff00ff00enabled|r — release button hidden in guild raids")
            else
                print("|cff00ff00YippYapp Helper|r: Release blocker |cffff0000disabled|r")
            end
        end
        return
    end

    -- /yh debug
    if cmd == "debug" then
        print("|cff00ff00=== YippYapp Debug ===|r")
        local profile = ns:GetCurrentProfile()
        print("Profile: " .. profile.name)
        print("Free crests: " .. table.concat(profile.freeCrests, ", "))
        print("Precious crests: " .. table.concat(profile.preciousCrests, ", "))
        for _, trackName in ipairs(ns.TRACK_ORDER) do
            local count = ns:GetCrestCountByTrack(trackName)
            local cost = ns:GetCrestCost(trackName)
            print("  " .. trackName .. ": " .. count .. " crests, " .. cost .. " per upgrade, " .. ns:GetAffordableUpgrades(trackName) .. " upgrades affordable")
        end
        print("--- Equipped Slots ---")
        for _, slotInfo in ipairs(ns.SLOT_IDS) do
            local itemLink = GetInventoryItemLink("player", slotInfo.slot)
            if itemLink then
                local info = ns:GetSlotInfo(slotInfo.slot)
                local canUp, upInfo = ns:CanUpgradeItem(slotInfo.slot)
                local rec, reason = ns:GetRecommendation(slotInfo.slot)
                local upStr = "no data"
                if upInfo then
                    upStr = upInfo.track .. " " .. upInfo.currUpgrade .. "/" .. upInfo.maxUpgrade .. " (ilvl " .. upInfo.currIlvl .. "->" .. upInfo.maxIlvl .. ")"
                end
                local risk = ns:GetReplacementRisk(slotInfo.slot, info.ilvl)
                local watermark = ns:GetFreeUpgradeIlvl(slotInfo.slot)
                local wmStr = watermark > 0 and ("wm:" .. watermark) or "wm:none"
                print(string.format("  %s: %s | %s | risk: %s | %s | %s",
                    slotInfo.name, upStr, wmStr, risk, rec.label, reason))
            end
        end
        return
    end

    -- Default: toggle app
    if ns.ToggleApp then
        ns:ToggleApp()
    elseif ns.ToggleDashboard then
        ns:ToggleDashboard()
    end
end
