local _, ns = ...
local L = ns.L or {}

local DATA = ns.Data or {}
local MIDNIGHT_RARE_ZONES = DATA.MIDNIGHT_RARE_ZONES or {}

local FEATURE_ID = "midnight_rare_map_pins"
local WORLD_MAP_PIN_TEMPLATE = "VaultloomMidnightRareWorldMapPinTemplate"
local RARE_ATLAS = "VignetteKill"
local RARE_FALLBACK_TEXTURE = "Interface\\TargetingFrame\\UI-TargetingFrame-Rare"
local PIN_SIZE = 21

local Quest = ns.Quest or {}
local isQuestCompleted = Quest.IsQuestCompleted or function(questID)
    if C_QuestLog and type(C_QuestLog.IsQuestFlaggedCompleted) == "function" then
        local ok, completed = pcall(C_QuestLog.IsQuestFlaggedCompleted, questID)
        return ok and completed == true
    end
    return false
end

local MidnightRareMapPinsRuntime = {
    enabled = false,
    providerAdded = false,
    worldMapHooked = false,
    mapDirty = false,
    refreshToken = 0,
    resetToken = 0,
}

local basePinMixin = type(MapCanvasPinMixin) == "table" and MapCanvasPinMixin or {}
local WorldMapPinMixin = type(CreateFromMixins) == "function" and CreateFromMixins(basePinMixin) or {}
_G.VaultloomMidnightRareWorldMapPinMixin = WorldMapPinMixin

local WorldMapDataProvider = type(CreateFromMixins) == "function"
    and type(MapCanvasDataProviderMixin) == "table"
    and CreateFromMixins(MapCanvasDataProviderMixin)
    or nil

local function getRareDisplayName(rare)
    if type(rare) ~= "table" then
        return ""
    end

    local fallback = rare.name or rare.label or rare.key or ""
    if ns.GetLocalizedMidnightRareName then
        return ns:GetLocalizedMidnightRareName(rare.npcID, fallback)
    end

    return fallback
end

local function getZoneDisplayName(zone)
    if type(zone) ~= "table" then
        return ""
    end

    local fallback = zone.label or zone.key or ""
    if ns.GetLocalizedMidnightRareZoneName then
        return ns:GetLocalizedMidnightRareZoneName(zone.mapID, fallback)
    end

    return fallback
end

local function isRareCompleted(rare)
    if ns.IsMidnightRareCompleted then
        return ns:IsMidnightRareCompleted(rare)
    end

    local questID = tonumber(rare and rare.questID)
    return questID and isQuestCompleted(questID) == true or false
end

local function buildPinData(zone, rare, mapID)
    local x = tonumber(rare and rare.x)
    local y = tonumber(rare and rare.y)
    if not x or not y then
        return nil
    end

    return {
        key = rare.key or tostring(rare.questID or rare.npcID or x),
        name = rare.name,
        label = rare.label or rare.name,
        npcID = rare.npcID,
        questID = rare.questID,
        mapID = mapID,
        x = x,
        y = y,
        zoneName = getZoneDisplayName(zone),
    }
end

local function collectVisibleRaresForMap(mapID)
    mapID = tonumber(mapID)
    if not mapID then
        return {}
    end

    local pins = {}
    for _, zone in ipairs(MIDNIGHT_RARE_ZONES) do
        local zoneMapID = tonumber(zone and zone.mapID)
        for _, rare in ipairs(type(zone) == "table" and zone.rares or {}) do
            local rareMapID = tonumber(rare and rare.mapID) or zoneMapID
            if rareMapID == mapID and isRareCompleted(rare) ~= true then
                local pin = buildPinData(zone, rare, rareMapID)
                if pin then
                    pins[#pins + 1] = pin
                end
            end
        end
    end

    return pins
end

local function applyBlizzardRareIcon(texture)
    if not texture then
        return
    end

    if texture.ClearAllPoints then
        texture:ClearAllPoints()
    end
    if texture.SetPoint then
        texture:SetPoint("CENTER", 0, 0)
    end
    if texture.SetSize then
        texture:SetSize(PIN_SIZE, PIN_SIZE)
    end

    local usedAtlas = false
    if texture.SetAtlas then
        local ok = pcall(texture.SetAtlas, texture, RARE_ATLAS, true)
        usedAtlas = ok == true
        if usedAtlas and texture.SetSize then
            texture:SetSize(PIN_SIZE, PIN_SIZE)
        end
    end

    if not usedAtlas and texture.SetTexture then
        texture:SetTexture(RARE_FALLBACK_TEXTURE)
        if texture.SetTexCoord then
            texture:SetTexCoord(0, 1, 0, 1)
        end
    end

    if texture.SetVertexColor then
        texture:SetVertexColor(1, 1, 1, 1)
    end
end

function WorldMapPinMixin:SetPassThroughButtons()
end

function WorldMapPinMixin:CheckMouseButtonPassthrough()
    return false
end

function WorldMapPinMixin:OnLoad()
    self:SetSize(PIN_SIZE, PIN_SIZE)
    if self.RegisterForClicks then
        self:RegisterForClicks("LeftButtonUp")
    end
    if self.UseFrameLevelType then
        self:UseFrameLevelType("PIN_FRAME_LEVEL_AREA_POI")
    end
    if self.SetScalingLimits then
        self:SetScalingLimits(1, 1.0, 1.35)
    end
end

function WorldMapPinMixin:OnAcquired(rare)
    self.rare = rare
    self:SetSize(PIN_SIZE, PIN_SIZE)
    if self.SetScalingLimits then
        self:SetScalingLimits(1, 1.0, 1.35)
    end
    if self.SetAlpha then
        self:SetAlpha(1)
    end
    applyBlizzardRareIcon(self.Icon)

    local x = (tonumber(rare and rare.x) or 0) / 100
    local y = (tonumber(rare and rare.y) or 0) / 100
    if self.SetPosition then
        self:SetPosition(x, y)
    end
end

function WorldMapPinMixin:OnReleased()
    self.rare = nil
end

function WorldMapPinMixin:OnMouseEnter()
    local rare = self.rare
    if type(rare) ~= "table" then
        return
    end

    GameTooltip:SetOwner(self, "ANCHOR_CURSOR_RIGHT")
    GameTooltip:AddLine(getRareDisplayName(rare), 1, 0.82, 0.12, true)
    if rare.zoneName and rare.zoneName ~= "" then
        GameTooltip:AddLine(rare.zoneName, 0.78, 0.78, 0.72, true)
    end
    GameTooltip:AddLine(
        string.format(L.PVE_RARES_COORDS_FORMAT or "Coordinates: %.1f, %.1f", tonumber(rare.x) or 0, tonumber(rare.y) or 0),
        0.88,
        0.88,
        0.86,
        true
    )
    GameTooltip:AddLine(L.FEATURE_MIDNIGHT_RARE_MAP_PINS_TOOLTIP_HINT or "Left-click to set a Blizzard waypoint.", 0.64, 0.74, 1, true)
    GameTooltip:AddLine(L.FEATURE_MIDNIGHT_RARE_MAP_PINS_HIDE_DONE_HINT or "Pins disappear after the daily kill credit is earned.", 0.68, 0.82, 0.68, true)
    GameTooltip:Show()
end

function WorldMapPinMixin:OnMouseLeave()
    GameTooltip:Hide()
end

function WorldMapPinMixin:OnMouseDown()
end

function WorldMapPinMixin:OnMouseUp()
end

function WorldMapPinMixin:OnClick(button)
    if button ~= "LeftButton" then
        return
    end

    if ns.SetMidnightRareWaypoint and type(self.rare) == "table" then
        ns:SetMidnightRareWaypoint(self.rare)
    end
end

if WorldMapDataProvider then
    function WorldMapDataProvider:RemoveAllData()
        local map = self.GetMap and self:GetMap() or nil
        if map and type(map.RemoveAllPinsByTemplate) == "function" then
            map:RemoveAllPinsByTemplate(WORLD_MAP_PIN_TEMPLATE)
        end
    end

    function WorldMapDataProvider:RefreshAllData()
        self:RemoveAllData()
        if MidnightRareMapPinsRuntime.enabled ~= true then
            return
        end

        local map = self.GetMap and self:GetMap() or nil
        local mapID = map and map.GetMapID and map:GetMapID() or nil
        if not mapID or not map or type(map.AcquirePin) ~= "function" then
            return
        end

        for _, rare in ipairs(collectVisibleRaresForMap(mapID)) do
            map:AcquirePin(WORLD_MAP_PIN_TEMPLATE, rare)
        end
    end
end

local function safeRegisterEvent(frame, eventName)
    if frame and type(eventName) == "string" and eventName ~= "" then
        pcall(frame.RegisterEvent, frame, eventName)
    end
end

local function isWorldMapShown()
    local worldMap = _G.WorldMapFrame
    return worldMap and (type(worldMap.IsShown) ~= "function" or worldMap:IsShown())
end

function MidnightRareMapPinsRuntime:RefreshNow()
    self.mapDirty = true
    if not isWorldMapShown() then
        return
    end
    if not self:EnsureWorldMapProvider() then
        return
    end
    if WorldMapDataProvider and WorldMapDataProvider.RefreshAllData then
        WorldMapDataProvider:RefreshAllData()
        self.mapDirty = false
    end
end

function MidnightRareMapPinsRuntime:QueueRefresh(delay)
    if self.enabled ~= true then
        return
    end

    self.refreshToken = (tonumber(self.refreshToken) or 0) + 1
    local token = self.refreshToken
    local function refreshIfCurrent()
        if MidnightRareMapPinsRuntime.enabled == true and token == MidnightRareMapPinsRuntime.refreshToken then
            MidnightRareMapPinsRuntime:RefreshNow()
        end
    end

    delay = tonumber(delay) or 0
    if delay > 0 and C_Timer and type(C_Timer.After) == "function" then
        C_Timer.After(delay, refreshIfCurrent)
    else
        refreshIfCurrent()
    end
end

function MidnightRareMapPinsRuntime:RequestRefresh(delay)
    if self.enabled ~= true then
        return
    end

    if not isWorldMapShown() then
        self.mapDirty = true
        return
    end

    self:QueueRefresh(delay)
end

function MidnightRareMapPinsRuntime:ScheduleDailyResetRefresh()
    if self.enabled ~= true or not C_Timer or type(C_Timer.After) ~= "function" then
        return
    end

    self.resetToken = (tonumber(self.resetToken) or 0) + 1
    local token = self.resetToken
    local seconds = ns.GetDailyResetSeconds and ns:GetDailyResetSeconds() or 0
    seconds = math.max(0, tonumber(seconds) or 0)
    if seconds <= 0 then
        return
    end

    C_Timer.After(seconds + 5, function()
        if MidnightRareMapPinsRuntime.enabled ~= true or token ~= MidnightRareMapPinsRuntime.resetToken then
            return
        end
        MidnightRareMapPinsRuntime:RequestRefresh(0.05)
        MidnightRareMapPinsRuntime:ScheduleDailyResetRefresh()
    end)
end

function MidnightRareMapPinsRuntime:EnsureEventFrame()
    if self.eventFrame then
        return
    end

    self.eventFrame = CreateFrame("Frame")
    self.eventFrame:SetScript("OnEvent", function(_, event, ...)
        if MidnightRareMapPinsRuntime.enabled ~= true then
            return
        end

        if event == "ADDON_LOADED" then
            local loadedName = ...
            if loadedName ~= ns.ADDON_NAME and loadedName ~= "Blizzard_WorldMap" then
                return
            end
            MidnightRareMapPinsRuntime:HookWorldMap(_G.WorldMapFrame)
            MidnightRareMapPinsRuntime:RequestRefresh(0.10)
            return
        end

        if event == "QUEST_TURNED_IN" then
            local questID = tonumber((...))
            if questID and ns.IsMidnightRareQuestID and ns:IsMidnightRareQuestID(questID) then
                MidnightRareMapPinsRuntime:RequestRefresh(0.05)
            end
            return
        end

        local delay = event == "QUEST_LOG_UPDATE" and 0.30 or 0.10
        MidnightRareMapPinsRuntime:RequestRefresh(delay)
    end)
end

function MidnightRareMapPinsRuntime:RegisterEvents()
    self:EnsureEventFrame()
    if not self.eventFrame then
        return
    end

    self.eventFrame:UnregisterAllEvents()
    safeRegisterEvent(self.eventFrame, "ADDON_LOADED")
    safeRegisterEvent(self.eventFrame, "PLAYER_ENTERING_WORLD")
    safeRegisterEvent(self.eventFrame, "QUEST_LOG_UPDATE")
    safeRegisterEvent(self.eventFrame, "QUEST_TURNED_IN")
    safeRegisterEvent(self.eventFrame, "ZONE_CHANGED_NEW_AREA")
    safeRegisterEvent(self.eventFrame, "AREA_POIS_UPDATED")
    safeRegisterEvent(self.eventFrame, "VIGNETTES_UPDATED")
end

function MidnightRareMapPinsRuntime:EnsureWorldMapProvider()
    if not WorldMapDataProvider or not _G.WorldMapFrame then
        return false
    end

    local worldMap = _G.WorldMapFrame
    if self.providerAdded ~= true then
        if not (type(worldMap.dataProviders) == "table" and worldMap.dataProviders[WorldMapDataProvider]) then
            worldMap:AddDataProvider(WorldMapDataProvider)
        end
        self.providerAdded = true
    end
    self:HookWorldMap(worldMap)

    return true
end

function MidnightRareMapPinsRuntime:HookWorldMap(worldMap)
    if self.worldMapHooked == true or not worldMap then
        return
    end

    self.worldMapHooked = true
    if worldMap.HookScript then
        worldMap:HookScript("OnShow", function()
            if MidnightRareMapPinsRuntime.enabled == true then
                MidnightRareMapPinsRuntime:EnsureWorldMapProvider()
                MidnightRareMapPinsRuntime:QueueRefresh(0.05)
            end
        end)
    end
    if type(hooksecurefunc) == "function" then
        pcall(hooksecurefunc, worldMap, "OnMapChanged", function()
            if MidnightRareMapPinsRuntime.enabled == true then
                MidnightRareMapPinsRuntime:QueueRefresh(0.05)
            end
        end)
    end
end

function MidnightRareMapPinsRuntime:RemoveWorldMapProvider()
    if WorldMapDataProvider and WorldMapDataProvider.RemoveAllData then
        WorldMapDataProvider:RemoveAllData()
    end

    local worldMap = _G.WorldMapFrame
    if worldMap
        and type(worldMap.dataProviders) == "table"
        and worldMap.dataProviders[WorldMapDataProvider]
        and type(worldMap.RemoveDataProvider) == "function"
    then
        worldMap:RemoveDataProvider(WorldMapDataProvider)
    end
    self.providerAdded = false
end

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

    if not enabled then
        if self.eventFrame then
            self.eventFrame:UnregisterAllEvents()
        end
        self:RemoveWorldMapProvider()
        return
    end

    self:RegisterEvents()
    self:HookWorldMap(_G.WorldMapFrame)
    self:RequestRefresh(0.05)
    self:ScheduleDailyResetRefresh()
end

function ns:RefreshMidnightRareMapPins()
    MidnightRareMapPinsRuntime:RequestRefresh(0.05)
end

ns:RegisterFeatureDefinition(FEATURE_ID, {
    id = FEATURE_ID,
    order = 35,
    category = "world_map",
    title = function() return L.FEATURE_MIDNIGHT_RARE_MAP_PINS_TITLE or "Midnight Rare Map Pins" end,
    description = function() return L.FEATURE_MIDNIGHT_RARE_MAP_PINS_DESCRIPTION or "Shows unfinished Midnight rare mobs on the Blizzard world map." end,
    note = function() return L.FEATURE_MIDNIGHT_RARE_MAP_PINS_NOTE or "Uses Blizzard's rare vignette icon. Pins vanish after the character earns the daily kill credit and return after the daily reset." end,
    previewLabel = function() return L.FEATURE_MIDNIGHT_RARE_MAP_PINS_PREVIEW_LABEL or "World Map" end,
    previewNote = function() return L.FEATURE_MIDNIGHT_RARE_MAP_PINS_PREVIEW_NOTE or "Midnight rare locations with daily completion hiding and Blizzard waypoints." end,
    requiresReload = false,
    apply = function(isEnabled)
        MidnightRareMapPinsRuntime:SetEnabled(isEnabled == true)
    end,
})
