local _, ns = ...
local L = ns.L or {}
local ASSET_ROOT = "Interface\\AddOns\\Vaultloom\\Assets\\"

local PREY_HUNT_ICON_TEXTURE = ASSET_ROOT .. "prey_hunt_icons.tga"
local PREY_HUNT_ICON_SIZE = 38

local PREY_HUNT_PIN_TEMPLATES = {
    "AdventureMap_QuestOfferPinTemplate",
    "AdventureMap_QuestChoicePinTemplate",
}

local PREY_HUNT_ACHIEVEMENT_IDS = {
    [1] = 42701,
    [2] = 42702,
    [3] = 42703,
}

local PREY_HUNT_ICON_COORDS = {
    [1] = {
        ready = { 0.00, 0.25, 0.00, 0.25 },
        needed = { 0.00, 0.25, 0.25, 0.50 },
    },
    [2] = {
        ready = { 0.25, 0.50, 0.00, 0.25 },
        needed = { 0.25, 0.50, 0.25, 0.50 },
    },
    [3] = {
        ready = { 0.50, 0.75, 0.00, 0.25 },
        needed = { 0.50, 0.75, 0.25, 0.50 },
    },
}

local PREY_HUNT_QUEST_DATA = {
    [91095] = { 1, 105912 },
    [91096] = { 1, 105913 },
    [91097] = { 1, 105914 },
    [91098] = { 1, 105915 },
    [91099] = { 1, 105916 },
    [91100] = { 1, 105917 },
    [91101] = { 1, 105918 },
    [91102] = { 1, 105919 },
    [91103] = { 1, 105920 },
    [91104] = { 1, 105921 },
    [91105] = { 1, 105922 },
    [91106] = { 1, 105923 },
    [91107] = { 1, 105924 },
    [91108] = { 1, 105925 },
    [91109] = { 1, 105926 },
    [91110] = { 1, 105927 },
    [91111] = { 1, 105928 },
    [91112] = { 1, 105929 },
    [91113] = { 1, 105930 },
    [91114] = { 1, 105931 },
    [91115] = { 1, 105932 },
    [91116] = { 1, 105933 },
    [91117] = { 1, 105934 },
    [91118] = { 1, 105935 },
    [91119] = { 1, 105936 },
    [91120] = { 1, 105937 },
    [91121] = { 1, 105938 },
    [91122] = { 1, 105939 },
    [91123] = { 1, 105940 },
    [91124] = { 1, 105941 },
    [91210] = { 2, 105942 },
    [91212] = { 2, 105943 },
    [91214] = { 2, 105944 },
    [91216] = { 2, 105945 },
    [91218] = { 2, 105946 },
    [91220] = { 2, 105947 },
    [91222] = { 2, 105948 },
    [91224] = { 2, 105949 },
    [91226] = { 2, 105950 },
    [91228] = { 2, 105951 },
    [91230] = { 2, 105952 },
    [91232] = { 2, 105953 },
    [91234] = { 2, 105954 },
    [91236] = { 2, 105955 },
    [91238] = { 2, 105956 },
    [91240] = { 2, 105957 },
    [91242] = { 2, 105958 },
    [91243] = { 2, 105959 },
    [91244] = { 2, 105960 },
    [91245] = { 2, 105961 },
    [91246] = { 2, 105962 },
    [91247] = { 2, 105963 },
    [91248] = { 2, 105964 },
    [91249] = { 2, 105965 },
    [91250] = { 2, 105966 },
    [91251] = { 2, 105967 },
    [91252] = { 2, 105968 },
    [91253] = { 2, 105969 },
    [91254] = { 2, 105970 },
    [91255] = { 2, 105971 },
    [91211] = { 3, 105972 },
    [91213] = { 3, 105973 },
    [91215] = { 3, 105974 },
    [91217] = { 3, 105975 },
    [91219] = { 3, 105976 },
    [91221] = { 3, 105977 },
    [91223] = { 3, 105978 },
    [91225] = { 3, 105979 },
    [91227] = { 3, 105980 },
    [91229] = { 3, 105981 },
    [91231] = { 3, 105982 },
    [91233] = { 3, 105983 },
    [91235] = { 3, 105984 },
    [91237] = { 3, 105985 },
    [91239] = { 3, 105986 },
    [91241] = { 3, 105987 },
    [91256] = { 3, 105988 },
    [91257] = { 3, 105989 },
    [91258] = { 3, 105990 },
    [91259] = { 3, 105991 },
    [91260] = { 3, 105992 },
    [91261] = { 3, 105993 },
    [91262] = { 3, 105994 },
    [91263] = { 3, 105995 },
    [91264] = { 3, 105996 },
    [91265] = { 3, 105997 },
    [91266] = { 3, 105998 },
    [91267] = { 3, 105999 },
    [91268] = { 3, 106000 },
    [91269] = { 3, 106001 },
}

local PreyHuntIconsRuntime = {
    enabled = false,
    hooksReady = false,
    dataProviders = nil,
    hookedDataProviders = setmetatable({}, { __mode = "k" }),
    modifiedPins = setmetatable({}, { __mode = "k" }),
    watchElapsed = 0,
    refreshToken = 0,
}

local function captureTextureState(textureObject)
    if not textureObject then
        return nil
    end

    local left, right, top, bottom = 0, 1, 0, 1
    if textureObject.GetTexCoord then
        local rawLeft, rawRight, rawTop, rawBottom = textureObject:GetTexCoord()
        left = tonumber(rawLeft) or 0
        right = tonumber(rawRight) or 1
        top = tonumber(rawTop) or 0
        bottom = tonumber(rawBottom) or 1
    end

    return {
        texture = textureObject.GetTexture and textureObject:GetTexture() or nil,
        left = left,
        right = right,
        top = top,
        bottom = bottom,
        width = textureObject.GetWidth and textureObject:GetWidth() or nil,
        height = textureObject.GetHeight and textureObject:GetHeight() or nil,
        alpha = textureObject.GetAlpha and textureObject:GetAlpha() or 1,
        shown = textureObject.IsShown and textureObject:IsShown() or nil,
    }
end

local function restoreTextureState(textureObject, state)
    if not textureObject or type(state) ~= "table" then
        return
    end

    if textureObject.SetTexture then
        textureObject:SetTexture(state.texture)
    end
    if textureObject.SetTexCoord then
        textureObject:SetTexCoord(state.left or 0, state.right or 1, state.top or 0, state.bottom or 1)
    end
    if textureObject.SetSize and state.width and state.height then
        textureObject:SetSize(state.width, state.height)
    end
    if textureObject.SetAlpha and state.alpha ~= nil then
        textureObject:SetAlpha(state.alpha)
    end
    if state.shown == false and textureObject.Hide then
        textureObject:Hide()
    elseif state.shown == true and textureObject.Show then
        textureObject:Show()
    end
end

local function getPreyHuntMap()
    local covenantMissionFrame = _G.CovenantMissionFrame
    local map = covenantMissionFrame and covenantMissionFrame.MapTab or nil
    if map and type(map.EnumeratePinsByTemplate) == "function" then
        return map
    end
    return nil
end

local function findPreyHuntDataProviders(map)
    if not map or type(map.dataProviders) ~= "table" then
        return {}
    end

    local providers = {}
    for dataProvider in pairs(map.dataProviders) do
        if type(dataProvider) == "table"
            and type(dataProvider.RefreshAllData) == "function"
            and type(dataProvider.RemoveAllData) == "function"
        then
            providers[#providers + 1] = dataProvider
        end
    end

    return providers
end

local function getPreyHuntIconCoords(difficulty, needsAchievement)
    local difficultyCoords = PREY_HUNT_ICON_COORDS[tonumber(difficulty) or 0]
    if not difficultyCoords then
        return 0, 1, 0, 1
    end

    local coords = needsAchievement and difficultyCoords.needed or difficultyCoords.ready
    return coords[1], coords[2], coords[3], coords[4]
end

function PreyHuntIconsRuntime:StoreOriginalPinState(pin)
    if not pin or pin.VaultloomPreyPinState then
        return
    end

    pin.VaultloomPreyPinState = {
        icon = captureTextureState(pin.Icon),
        highlight = captureTextureState(pin.IconHighlight),
        description = pin.description,
    }
end

function PreyHuntIconsRuntime:RestorePin(pin)
    if not pin or not pin.VaultloomPreyPinState then
        self.modifiedPins[pin] = nil
        return
    end

    restoreTextureState(pin.Icon, pin.VaultloomPreyPinState.icon)
    restoreTextureState(pin.IconHighlight, pin.VaultloomPreyPinState.highlight)
    pin.description = pin.VaultloomPreyPinState.description
    pin.VaultloomPreyPinState = nil
    self.modifiedPins[pin] = nil
end

function PreyHuntIconsRuntime:ApplyPin(pin, difficulty, criteriaID)
    if not pin or not pin.Icon then
        return
    end

    self:StoreOriginalPinState(pin)

    local achievementID = PREY_HUNT_ACHIEVEMENT_IDS[tonumber(difficulty) or 0]
    local needsAchievement = false
    if achievementID and type(GetAchievementCriteriaInfoByID) == "function" then
        local _, _, completed = GetAchievementCriteriaInfoByID(achievementID, criteriaID)
        needsAchievement = completed == false
    end

    local left, right, top, bottom = getPreyHuntIconCoords(difficulty, needsAchievement)
    pin.Icon:SetTexture(PREY_HUNT_ICON_TEXTURE)
    pin.Icon:SetTexCoord(left, right, top, bottom)
    pin.Icon:SetSize(PREY_HUNT_ICON_SIZE, PREY_HUNT_ICON_SIZE)

    if pin.IconHighlight then
        pin.IconHighlight:Hide()
    end

    self.modifiedPins[pin] = true
end

function PreyHuntIconsRuntime:RefreshPin(pin)
    local questID = pin and tonumber(pin.questID) or nil
    local questData = questID and PREY_HUNT_QUEST_DATA[questID] or nil
    if self.enabled ~= true or type(questData) ~= "table" then
        self:RestorePin(pin)
        return
    end

    self:ApplyPin(pin, questData[1], questData[2])
end

function PreyHuntIconsRuntime:RestoreAllPins()
    for pin in pairs(self.modifiedPins) do
        self:RestorePin(pin)
    end
end

function PreyHuntIconsRuntime:RefreshAllPins()
    local map = getPreyHuntMap()
    if not map then
        if self.enabled ~= true then
            self:RestoreAllPins()
        end
        return
    end

    local seenPins = {}
    for _, templateName in ipairs(PREY_HUNT_PIN_TEMPLATES) do
        for pin in map:EnumeratePinsByTemplate(templateName) do
            if not seenPins[pin] then
                seenPins[pin] = true
                self:RefreshPin(pin)
            end
        end
    end
end

function PreyHuntIconsRuntime:QueueRefreshPasses()
    self.refreshToken = (tonumber(self.refreshToken) or 0) + 1
    local token = self.refreshToken

    local function refreshIfCurrent()
        if PreyHuntIconsRuntime.enabled ~= true or token ~= PreyHuntIconsRuntime.refreshToken then
            return
        end
        PreyHuntIconsRuntime:RefreshAllPins()
    end

    refreshIfCurrent()

    if C_Timer and type(C_Timer.After) == "function" then
        for _, delay in ipairs({ 0.10, 0.30, 0.65 }) do
            C_Timer.After(delay, refreshIfCurrent)
        end
    end
end

function PreyHuntIconsRuntime:RequestMapRefresh(forceReset)
    local map = getPreyHuntMap()
    local dataProviders = self.dataProviders or findPreyHuntDataProviders(map)
    if type(dataProviders) == "table" then
        for _, dataProvider in ipairs(dataProviders) do
            if forceReset and type(dataProvider.RemoveAllData) == "function" then
                dataProvider:RemoveAllData()
            end
        end
    end

    local refreshed = false
    if type(dataProviders) == "table" then
        for _, dataProvider in ipairs(dataProviders) do
            if type(dataProvider.RefreshAllData) == "function" then
                dataProvider:RefreshAllData()
                refreshed = true
            end
        end
    end

    return refreshed
end

function PreyHuntIconsRuntime:EnsureHooks()
    local map = getPreyHuntMap()
    if self.hooksReady and self.map == map then
        return true
    end

    local dataProviders = findPreyHuntDataProviders(map)
    if not map or #dataProviders == 0 or type(hooksecurefunc) ~= "function" then
        return false
    end

    self.hooksReady = true
    self.map = map
    self.dataProviders = dataProviders

    for _, dataProvider in ipairs(dataProviders) do
        if not self.hookedDataProviders[dataProvider] then
            self.hookedDataProviders[dataProvider] = true

            hooksecurefunc(dataProvider, "RefreshAllData", function(_, fromOnShow)
                if PreyHuntIconsRuntime.enabled ~= true then
                    PreyHuntIconsRuntime:RestoreAllPins()
                    return
                end
                if fromOnShow then
                    PreyHuntIconsRuntime:QueueRefreshPasses()
                    return
                end
                PreyHuntIconsRuntime:QueueRefreshPasses()
            end)

            hooksecurefunc(dataProvider, "RemoveAllData", function()
                if PreyHuntIconsRuntime.enabled ~= true then
                    PreyHuntIconsRuntime:RestoreAllPins()
                end
            end)
        end
    end

    if map.HookScript then
        map:HookScript("OnShow", function()
            if PreyHuntIconsRuntime.enabled == true then
                PreyHuntIconsRuntime:QueueRefreshPasses()
            end
        end)
    end

    return true
end

function PreyHuntIconsRuntime:StartWatch()
    if not self.eventFrame then
        return
    end

    self.watchElapsed = 0
    self.eventFrame:SetScript("OnUpdate", function(_, elapsed)
        if PreyHuntIconsRuntime.enabled ~= true then
            PreyHuntIconsRuntime:StopWatch()
            return
        end

        if PreyHuntIconsRuntime.hooksReady then
            PreyHuntIconsRuntime:StopWatch()
            return
        end

        PreyHuntIconsRuntime.watchElapsed = (PreyHuntIconsRuntime.watchElapsed or 0) + (tonumber(elapsed) or 0)
        if PreyHuntIconsRuntime.watchElapsed < 0.50 then
            return
        end

        PreyHuntIconsRuntime.watchElapsed = 0
        if PreyHuntIconsRuntime:EnsureHooks() then
            PreyHuntIconsRuntime:QueueRefreshPasses()
            PreyHuntIconsRuntime:StopWatch()
        end
    end)
end

function PreyHuntIconsRuntime:StopWatch()
    if self.eventFrame then
        self.eventFrame:SetScript("OnUpdate", nil)
    end
    self.watchElapsed = 0
end

function PreyHuntIconsRuntime:SetEnabled(enabled)
    enabled = enabled == true
    self.enabled = enabled
    self.refreshToken = (tonumber(self.refreshToken) or 0) + 1

    if not self.eventFrame then
        self.eventFrame = CreateFrame("Frame")
        self.eventFrame:SetScript("OnEvent", function(_, event, ...)
            if event == "ADDON_LOADED" then
                local loadedName = ...
                if loadedName == "Blizzard_GarrisonUI" then
                    if PreyHuntIconsRuntime:EnsureHooks() then
                        PreyHuntIconsRuntime:QueueRefreshPasses()
                    else
                        PreyHuntIconsRuntime:StartWatch()
                    end
                end
            end
        end)
    end

    self.eventFrame:UnregisterAllEvents()
    self:StopWatch()

    if not enabled then
        self:RestoreAllPins()
        if ns and ns.Print then
            ns:Print(L.FEATURE_PREY_HUNT_ICONS_RELOAD_HINT or "If Blizzard map pins do not return immediately, use /reload once.")
        end
        return
    end

    self.eventFrame:RegisterEvent("ADDON_LOADED")
    if (type(IsAddOnLoaded) == "function" and IsAddOnLoaded("Blizzard_GarrisonUI")) or getPreyHuntMap() then
        if self:EnsureHooks() then
            self:QueueRefreshPasses()
        else
            self:StartWatch()
        end
    else
        self:StartWatch()
    end
end

ns:RegisterFeatureDefinition("prey_hunt_icons", {
    id = "prey_hunt_icons",
    order = 30,
    category = "world_map",
    title = function() return L.FEATURE_PREY_HUNT_ICONS_TITLE or "Prey Hunt Icons" end,
    description = function() return L.FEATURE_PREY_HUNT_ICONS_DESCRIPTION or "Replaces prey map exclamation marks with custom icons. Turn off and /reload if Blizzard pins do not return immediately." end,
    note = function() return L.FEATURE_PREY_HUNT_ICONS_NOTE or "Shows Normal, Hard, Nightmare, and whether a target still matters for achievements." end,
    previewLabel = function() return L.FEATURE_PREY_HUNT_ICONS_PREVIEW_LABEL or "Hunt Map" end,
    previewNote = function() return L.FEATURE_PREY_HUNT_ICONS_PREVIEW_NOTE or "Custom prey icons for the hunt table with difficulty and achievement context." end,
    requiresReload = false,
    apply = function(isEnabled, source)
        PreyHuntIconsRuntime:SetEnabled(isEnabled == true)
    end,
})
