local _, Addon = ...

local Markers = {}
Addon.Markers = Markers

local state = Addon.state
local Coordinates = Addon.Coordinates
local canvas
local viewport
local mapFrame
local statusText
local playerMarker
local waypointMarker
local nativePlayerFrame
local nativeMapID
local groupMarkers = {}
local contentMarkers = {}
local bossMarkers = {}

local function PositionMarker(marker, x, y)
    Addon.MapView:PositionMarker(marker, x, y)
end

local function CreatePrimaryMarkers()
    playerMarker = CreateFrame("Frame", nil, canvas)
    playerMarker:SetSize(Addon.db.playerArrowSize, Addon.db.playerArrowSize)
    playerMarker:SetFrameLevel(canvas:GetFrameLevel() + 20)

    local arrow = playerMarker:CreateTexture(nil, "OVERLAY")
    arrow:SetAllPoints()
    arrow:SetTexture("Interface\\Minimap\\MinimapArrow")
    playerMarker.arrow = arrow

    waypointMarker = CreateFrame("Frame", nil, canvas)
    waypointMarker:SetSize(24, 24)
    waypointMarker:SetFrameLevel(canvas:GetFrameLevel() + 19)

    local waypointTexture = waypointMarker:CreateTexture(nil, "OVERLAY")
    waypointTexture:SetAllPoints()
    waypointTexture:SetAtlas("Waypoint-MapPin-Untracked", false)

    -- El mapa oficial usa este widget nativo para las posiciones protegidas.
    -- Puede dibujar al jugador en instancias aunque las APIs de coordenadas
    -- devuelvan nil al código Lua del addon.
    local ok, frame = pcall(
        CreateFrame,
        "UnitPositionFrame",
        nil,
        canvas,
        "UnitPositionFrameTemplate"
    )
    if ok and frame then
        nativePlayerFrame = frame
        nativePlayerFrame:SetAllPoints(canvas)
        nativePlayerFrame:SetFrameLevel(canvas:GetFrameLevel() + 21)
        nativePlayerFrame:EnableMouse(false)

        local configured = pcall(function()
            nativePlayerFrame:SetPinSize("player", Addon.db.playerArrowSize)
            nativePlayerFrame:SetPinTexture("player", "UI-WorldMapArrow")
            nativePlayerFrame:SetUseClassColor("player", false)
            nativePlayerFrame:SetPinSize("party", 11)
            nativePlayerFrame:SetPinSize("raid", 9)
            nativePlayerFrame:SetUseClassColor("party", true)
            nativePlayerFrame:SetUseClassColor("raid", true)
            nativePlayerFrame:SetShouldShowUnits("player", true)
            nativePlayerFrame:SetShouldShowUnits("party", true)
            nativePlayerFrame:SetShouldShowUnits("raid", true)
            nativePlayerFrame:SetMouseOverUnitExcluded("player", true)
            nativePlayerFrame:SetScript("OnUpdate", function(self)
                self:UpdatePlayerPins()
            end)
        end)
        if not configured then
            nativePlayerFrame:Hide()
            nativePlayerFrame = nil
        else
            nativePlayerFrame:Hide()
        end
    end
end

local function AcquireGroupMarker(index)
    if not groupMarkers[index] then
        local marker = CreateFrame("Frame", nil, canvas, "BackdropTemplate")
        marker:SetSize(10, 10)
        marker:SetFrameLevel(canvas:GetFrameLevel() + 18)
        marker:SetBackdrop({
            bgFile = "Interface\\Buttons\\WHITE8X8",
            edgeFile = "Interface\\Buttons\\WHITE8X8",
            edgeSize = 1,
        })
        marker:SetBackdropBorderColor(0, 0, 0, 1)
        groupMarkers[index] = marker
    end

    return groupMarkers[index]
end

local function HideGroupMarkers(firstUnused)
    for index = firstUnused, #groupMarkers do
        groupMarkers[index]:Hide()
    end
end

local function RestoreShapeVisibility(marker)
    if not marker or not marker._mzmShapeHidden then
        return
    end
    marker:SetAlpha(marker._mzmShapeAlpha or 1)
    if marker.EnableMouse and marker._mzmShapeMouseEnabled ~= nil then
        marker:EnableMouse(marker._mzmShapeMouseEnabled)
    end
    marker._mzmShapeHidden = nil
    marker._mzmShapeAlpha = nil
    marker._mzmShapeMouseEnabled = nil
end

local function SetShapeHidden(marker)
    if not marker._mzmShapeHidden then
        marker._mzmShapeHidden = true
        marker._mzmShapeAlpha = marker:GetAlpha()
        if marker.IsMouseEnabled then
            marker._mzmShapeMouseEnabled = marker:IsMouseEnabled()
        end
    end
    marker:SetAlpha(0)
    if marker.EnableMouse then
        marker:EnableMouse(false)
    end
end

local function ApplyShapeVisibility(marker)
    if not marker then
        return
    end
    if Addon.db.shape ~= "circle" then
        RestoreShapeVisibility(marker)
        return
    end
    if not marker:IsShown() then
        return
    end

    local markerX, markerY = marker:GetCenter()
    local viewX, viewY = viewport:GetCenter()
    if Addon:IsSecret(markerX)
        or Addon:IsSecret(markerY)
        or Addon:IsSecret(viewX)
        or Addon:IsSecret(viewY)
    then
        return
    end
    if not markerX or not markerY or not viewX or not viewY then
        return
    end

    local markerRadius = math.max(marker:GetWidth() or 0, marker:GetHeight() or 0) * 0.35
    local radius = (math.min(viewport:GetWidth(), viewport:GetHeight()) / 2) - markerRadius
    local deltaX = markerX - viewX
    local deltaY = markerY - viewY
    if (deltaX * deltaX) + (deltaY * deltaY) <= radius * radius then
        RestoreShapeVisibility(marker)
    else
        SetShapeHidden(marker)
    end
end

local function ApplyShapeToChildren(frame, depth)
    if not frame or depth <= 0 then
        return
    end
    for _, child in ipairs({ frame:GetChildren() }) do
        if math.max(child:GetWidth() or 0, child:GetHeight() or 0) <= 80 then
            ApplyShapeVisibility(child)
        else
            RestoreShapeVisibility(child)
        end
        ApplyShapeToChildren(child, depth - 1)
    end
end

local function SetMarkerAtlas(texture, atlasName, textureIndex, textureKit)
    texture:SetTexCoord(0, 1, 0, 1)

    if type(atlasName) == "string" and atlasName ~= "" then
        local resolvedAtlas = atlasName
        if type(textureKit) == "string" and textureKit ~= "" then
            local textureKitAtlas = string.format("%s-%s", textureKit, atlasName)
            local kitOK, kitAtlasInfo = pcall(C_Texture.GetAtlasInfo, textureKitAtlas)
            if kitOK and kitAtlasInfo then
                resolvedAtlas = textureKitAtlas
            end
        end

        local ok, atlasInfo = pcall(C_Texture.GetAtlasInfo, resolvedAtlas)
        if ok and atlasInfo then
            texture:SetAtlas(resolvedAtlas, false)
            return
        end
    end

    if type(textureIndex) == "number" and C_Minimap.GetPOITextureCoords then
        local ok, x1, x2, y1, y2 = pcall(C_Minimap.GetPOITextureCoords, textureIndex)
        if ok and x1 and x2 and y1 and y2 then
            texture:SetTexture("Interface\\Minimap\\POIIcons")
            texture:SetTexCoord(x1, x2, y1, y2)
            return
        end
    end

    texture:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
    texture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
end

local function GetQuestTitle(questID)
    local title = C_QuestLog.GetTitleForQuestID(questID)
    if type(title) == "string" and title ~= "" and not Addon:IsSecret(title) then
        return title
    end

    if C_TaskQuest.GetQuestInfoByQuestID then
        local taskTitle = C_TaskQuest.GetQuestInfoByQuestID(questID)
        if type(taskTitle) == "string" and taskTitle ~= "" and not Addon:IsSecret(taskTitle) then
            return taskTitle
        end
    end

    return "Misión " .. tostring(questID)
end

local function GetActiveQuestAtlas(questID)
    if C_QuestLog.IsComplete(questID) then
        return "UI-QuestIcon-TurnIn-Normal"
    end

    local classification = C_QuestInfoSystem.GetQuestClassification(questID)
    if classification == Enum.QuestClassification.Campaign then
        return "UI-QuestPoiCampaign-QuestNumber"
    elseif classification == Enum.QuestClassification.Legendary then
        return "UI-QuestPoiLegendary-QuestNumber"
    elseif classification == Enum.QuestClassification.Important then
        return "UI-QuestPoiImportant-QuestNumber"
    elseif classification == Enum.QuestClassification.Meta then
        return "UI-QuestPoiWrapper-QuestNumber"
    elseif classification == Enum.QuestClassification.Recurring then
        return "UI-QuestPoiRecurring-QuestNumber"
    end

    return "UI-QuestPoi-QuestNumber"
end

local function GetAvailableQuestAtlas(info)
    if info.isLegendary then
        return "UI-QuestPoiLegendary-QuestBang"
    elseif info.isCampaign then
        return "Quest-Campaign-Available"
    elseif info.isImportant then
        return "importantavailablequesticon"
    elseif info.isMeta then
        return "quest-wrapper-available"
    elseif info.isDaily then
        return "UI-QuestPoiRecurring-QuestBang"
    end

    return "QuestNormal"
end

local function GetWorldQuestAtlas(questID, inProgress)
    local tagInfo = C_QuestLog.GetQuestTagInfo(questID)
    if QuestUtil and QuestUtil.GetWorldQuestAtlasInfo and tagInfo then
        local ok, atlasName = pcall(QuestUtil.GetWorldQuestAtlasInfo, questID, tagInfo, inProgress)
        if ok and atlasName then
            return atlasName
        end
    end

    return "Worldquest-icon"
end

local function ShowContentMarkerTooltip(marker)
    GameTooltip:SetOwner(marker, "ANCHOR_LEFT")
    GameTooltip:SetText(marker.title or "Punto del mapa", 1, 0.82, 0)

    if marker.kindLabel then
        GameTooltip:AddLine(marker.kindLabel, 0.45, 0.8, 1)
    end
    if marker.description and marker.description ~= "" then
        GameTooltip:AddLine(marker.description, 1, 1, 1, true)
    end

    if marker.questID then
        local tagInfo = C_QuestLog.GetQuestTagInfo(marker.questID)
        if tagInfo and type(tagInfo.tagName) == "string" and not Addon:IsSecret(tagInfo.tagName) then
            GameTooltip:AddLine(tagInfo.tagName, 0.75, 0.75, 0.75)
        end

        local objectives = C_QuestLog.GetQuestObjectives(marker.questID)
        if objectives then
            for index, objective in ipairs(objectives) do
                if index > 6 then
                    break
                end
                if type(objective.text) == "string" and not Addon:IsSecret(objective.text) then
                    local color = objective.finished and GREEN_FONT_COLOR or HIGHLIGHT_FONT_COLOR
                    GameTooltip:AddLine(objective.text, color.r, color.g, color.b, true)
                end
            end
        end

        if C_QuestLog.IsWorldQuest(marker.questID) and C_TaskQuest.GetQuestTimeLeftMinutes then
            local minutes = C_TaskQuest.GetQuestTimeLeftMinutes(marker.questID)
            if type(minutes) == "number" and minutes > 0 and not Addon:IsSecret(minutes) then
                local hours = math.floor(minutes / 60)
                local remainingMinutes = math.floor(minutes % 60)
                if hours > 0 then
                    GameTooltip:AddLine(string.format("Tiempo restante: %dh %02dm", hours, remainingMinutes), 1, 0.55, 0.2)
                else
                    GameTooltip:AddLine(string.format("Tiempo restante: %dm", remainingMinutes), 1, 0.55, 0.2)
                end
            end
        end
    end

    if marker.trackable or marker.areaPoiID then
        GameTooltip:AddLine("Clic izquierdo para seguir", 0.6, 0.8, 1)
    end
    if marker.linkedUiMapID then
        GameTooltip:AddLine("Clic derecho para abrir esa zona en el mapa grande", 0.6, 0.8, 1)
    end
    GameTooltip:Show()
end

local function TrackContentMarker(marker, button)
    if button == "RightButton" and marker.linkedUiMapID then
        pcall(C_Map.OpenWorldMap, marker.linkedUiMapID)
        return
    end

    if marker.questID and marker.trackable then
        pcall(C_SuperTrack.SetSuperTrackedQuestID, marker.questID)
    elseif marker.superTrackType and marker.superTrackID then
        pcall(C_SuperTrack.SetSuperTrackedMapPin, marker.superTrackType, marker.superTrackID)
    elseif marker.areaPoiID and Enum.SuperTrackingMapPinType.AreaPOI then
        pcall(C_SuperTrack.SetSuperTrackedMapPin, Enum.SuperTrackingMapPinType.AreaPOI, marker.areaPoiID)
    end
end

local function ShowBossMarkerTooltip(marker)
    GameTooltip:SetOwner(marker, "ANCHOR_LEFT")
    GameTooltip:SetText(marker.title or "Jefe de mazmorra", 1, 0.82, 0)
    if marker.completed then
        GameTooltip:AddLine(DUNGEON_ENCOUNTER_DEFEATED or "Derrotado", 1, 0.2, 0.2)
    else
        GameTooltip:AddLine("Jefe de mazmorra o banda", 0.45, 0.8, 1)
    end
    if marker.description and marker.description ~= "" then
        GameTooltip:AddLine(marker.description, 1, 1, 1, true)
    end
    GameTooltip:AddLine("Clic para abrir la Guía de aventuras", 0.6, 0.8, 1)
    GameTooltip:Show()
end

local function OpenBossJournal(marker)
    pcall(function()
        EncounterJournal_LoadUI()
        EncounterJournal_OpenJournal(nil, marker.instanceID, marker.encounterID)
    end)
end

local function AcquireBossMarker(index)
    if not bossMarkers[index] then
        local marker = CreateFrame("Button", nil, canvas)
        marker:SetSize(32, 32)
        marker:RegisterForClicks("LeftButtonUp")

        local portrait = marker:CreateTexture(nil, "BACKGROUND")
        portrait:SetPoint("CENTER")
        portrait:SetSize(23, 23)
        marker.portrait = portrait

        local portraitMask = marker:CreateMaskTexture(nil, "ARTWORK")
        portraitMask:SetTexture("Interface\\CharacterFrame\\TempPortraitAlphaMask")
        portraitMask:SetAllPoints(portrait)
        portrait:AddMaskTexture(portraitMask)

        local border = marker:CreateTexture(nil, "ARTWORK")
        border:SetAllPoints()
        border:SetTexture("Interface\\EncounterJournal\\UI-EncounterJournalTextures")
        border:SetTexCoord(0.84960938, 0.97070313, 0.42871094, 0.48828125)

        local highlight = marker:CreateTexture(nil, "HIGHLIGHT")
        highlight:SetAllPoints()
        highlight:SetTexture("Interface\\EncounterJournal\\UI-EncounterJournalTextures")
        highlight:SetTexCoord(0.68945313, 0.81054688, 0.33300781, 0.39257813)
        marker:SetHighlightTexture(highlight)

        local defeatedCross = marker:CreateTexture(nil, "OVERLAY")
        defeatedCross:SetPoint("BOTTOMRIGHT", 2, -2)
        defeatedCross:SetAtlas("Map-MarkedDefeated", true)
        defeatedCross:SetScale(0.65)
        marker.defeatedCross = defeatedCross

        marker:SetScript("OnEnter", ShowBossMarkerTooltip)
        marker:SetScript("OnLeave", GameTooltip_Hide)
        marker:SetScript("OnClick", OpenBossJournal)
        bossMarkers[index] = marker
    end

    return bossMarkers[index]
end

local function HideBossMarkers(firstUnused)
    for index = firstUnused, #bossMarkers do
        bossMarkers[index]:Hide()
    end
end

local function RefreshBossMarkers(mapID)
    if not Addon.db.showBosses then
        HideBossMarkers(1)
        return
    end

    local markerIndex = 1
    if C_EncounterJournal.GetEncountersOnMap
        and (not CanShowEncounterJournal or CanShowEncounterJournal()) then
        local ok, encounters = pcall(C_EncounterJournal.GetEncountersOnMap, mapID)
        if ok then
            for _, encounterInfo in ipairs(encounters or {}) do
                local encounterID = encounterInfo.encounterID
                local x = encounterInfo.mapX
                local y = encounterInfo.mapY
                if encounterID and Addon:IsValidMapCoordinate(x, y) then
                    local name, description, _, _, _, instanceID = EJ_GetEncounterInfo(encounterID)
                    local displayInfo = select(4, EJ_GetCreatureInfo(1, encounterID))
                    local completed = false
                    local completionOK, isComplete = pcall(
                        C_EncounterJournal.IsEncounterComplete,
                        encounterID
                    )
                    if completionOK then
                        completed = not not isComplete
                    end

                    local marker = AcquireBossMarker(markerIndex)
                    marker.mapX = x
                    marker.mapY = y
                    marker.encounterID = encounterID
                    marker.instanceID = instanceID
                    marker.title = name or "Jefe de mazmorra"
                    marker.description = description
                    marker.completed = completed
                    marker:SetFrameLevel(canvas:GetFrameLevel() + 17)
                    marker.portrait:SetTexture("Interface\\Icons\\INV_Misc_QuestionMark")
                    marker.portrait:SetTexCoord(0.08, 0.92, 0.08, 0.92)
                    if displayInfo then
                        pcall(SetPortraitTextureFromCreatureDisplayID, marker.portrait, displayInfo)
                    end
                    marker.portrait:SetDesaturated(completed)
                    marker.portrait:SetAlpha(completed and 0.6 or 1)
                    marker.defeatedCross:SetShown(completed)
                    PositionMarker(marker, x, y)
                    markerIndex = markerIndex + 1
                end
            end
        end
    end
    HideBossMarkers(markerIndex)
end

function Markers:SetBossesShown(shown)
    Addon.db.showBosses = not not shown
    if Addon.db.showBosses then
        self:RefreshContentMarkers()
    else
        HideBossMarkers(1)
    end
    if Addon.AddonButtons and Addon.AddonButtons.UpdateBossButton then
        Addon.AddonButtons:UpdateBossButton()
    end
end

local function AcquireContentMarker(index)
    if not contentMarkers[index] then
        local marker = CreateFrame("Button", nil, canvas)
        marker:SetSize(22, 22)
        marker:RegisterForClicks("LeftButtonUp", "RightButtonUp")

        local glow = marker:CreateTexture(nil, "BACKGROUND")
        glow:SetPoint("CENTER")
        glow:SetSize(28, 28)
        glow:SetAtlas("UI-QuestPoi-OuterGlow", false)
        glow:SetAlpha(0.45)
        marker.glow = glow

        local icon = marker:CreateTexture(nil, "ARTWORK")
        icon:SetAllPoints()
        marker.icon = icon

        marker:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
        marker:SetScript("OnEnter", ShowContentMarkerTooltip)
        marker:SetScript("OnLeave", GameTooltip_Hide)
        marker:SetScript("OnClick", TrackContentMarker)
        contentMarkers[index] = marker
    end

    return contentMarkers[index]
end

local function HideContentMarkers(firstUnused)
    for index = firstUnused, #contentMarkers do
        contentMarkers[index]:Hide()
    end
end

function Markers:RefreshContentMarkers()
    if not state.mapID or not state.layer then
        HideContentMarkers(1)
        HideBossMarkers(1)
        return
    end

    local markerIndex = 1
    local usedKeys = {}

    RefreshBossMarkers(state.mapID)

    local function AddMarker(key, x, y, atlasName, title, kindLabel, description, data)
        if usedKeys[key] or not Addon:IsValidMapCoordinate(x, y) then
            return
        end

        usedKeys[key] = true
        local marker = AcquireContentMarker(markerIndex)
        marker.mapX = x
        marker.mapY = y
        marker.title = title
        marker.kindLabel = kindLabel
        marker.description = description
        marker.questID = data and data.questID or nil
        marker.areaPoiID = data and data.areaPoiID or nil
        marker.linkedUiMapID = data and data.linkedUiMapID or nil
        marker.superTrackType = data and data.superTrackType or nil
        marker.superTrackID = data and data.superTrackID or nil
        marker.trackable = data and data.trackable or false
        marker:SetFrameLevel(canvas:GetFrameLevel() + (data and data.level or 10))
        marker:SetSize(data and data.size or 22, data and data.size or 22)
        marker.glow:SetShown(not data or data.showGlow ~= false)
        SetMarkerAtlas(marker.icon, atlasName, data and data.textureIndex, data and data.textureKit)
        PositionMarker(marker, x, y)
        markerIndex = markerIndex + 1
    end

    local dungeonEntrances = C_EncounterJournal.GetDungeonEntrancesForMap(state.mapID) or {}
    for _, info in ipairs(dungeonEntrances) do
        local x, y = Coordinates:SafeGetXY(info.position)
        if x and y then
            AddMarker(
                "areapoi:" .. tostring(info.areaPoiID), x, y, info.atlasName, info.name,
                "Entrada de mazmorra o banda", info.description,
                { areaPoiID = info.areaPoiID, level = 11, size = 22 }
            )
        end
    end

    local mapLinks = C_Map.GetMapLinksForMap(state.mapID) or {}
    for _, info in ipairs(mapLinks) do
        local x, y = Coordinates:SafeGetXY(info.position)
        if x and y then
            AddMarker(
                "areapoi:" .. tostring(info.areaPoiID), x, y, info.atlasName, info.name,
                "Acceso a otra zona", nil,
                {
                    areaPoiID = info.areaPoiID,
                    linkedUiMapID = info.linkedUiMapID,
                    level = 11,
                    size = 22,
                    showGlow = false,
                }
            )
        end
    end

    if C_TaxiMap.ShouldMapShowTaxiNodes(state.mapID) then
        local factionGroup = UnitFactionGroup("player")
        local taxiNodes = C_TaxiMap.GetTaxiNodesForMap(state.mapID) or {}
        for _, info in ipairs(taxiNodes) do
            local correctFaction = info.faction == Enum.FlightPathFaction.Neutral
                or (info.faction == Enum.FlightPathFaction.Horde and factionGroup == "Horde")
                or (info.faction == Enum.FlightPathFaction.Alliance and factionGroup == "Alliance")
            local x, y = Coordinates:SafeGetXY(info.position)
            if correctFaction and x and y then
                AddMarker(
                    "taxi:" .. tostring(info.nodeID), x, y, info.atlasName, info.name,
                    info.isUndiscovered and "Punto de vuelo sin descubrir" or "Punto de vuelo", nil,
                    {
                        superTrackType = Enum.SuperTrackingMapPinType.TaxiNode,
                        superTrackID = info.nodeID,
                        trackable = true,
                        level = 11,
                        size = 20,
                        showGlow = false,
                        textureKit = info.textureKit,
                    }
                )
            end
        end
    end

    if GetCVarBool("digSites") then
        local digSites = C_ResearchInfo.GetDigSitesForMap(state.mapID) or {}
        for _, info in ipairs(digSites) do
            local x, y = Coordinates:SafeGetXY(info.position)
            if x and y then
                AddMarker(
                    "digsite:" .. tostring(info.researchSiteID), x, y, nil, info.name,
                    "Excavación arqueológica", nil,
                    {
                        superTrackType = Enum.SuperTrackingMapPinType.DigSite,
                        superTrackID = info.researchSiteID,
                        trackable = true,
                        level = 10,
                        size = 20,
                        showGlow = false,
                        textureIndex = info.textureIndex,
                    }
                )
            end
        end
    end

    local function AddAreaPOIList(areaPoiIDs, kindLabel, level, size, fallbackAtlas)
        for _, areaPoiID in ipairs(areaPoiIDs or {}) do
            local info = C_AreaPoiInfo.GetAreaPOIInfo(state.mapID, areaPoiID)
            if info then
                local x, y = Coordinates:SafeGetXY(info.position)
                if x and y then
                    AddMarker(
                        "areapoi:" .. tostring(areaPoiID), x, y, info.atlasName or fallbackAtlas,
                        info.name, kindLabel, info.description,
                        {
                            areaPoiID = areaPoiID,
                            linkedUiMapID = info.linkedUiMapID,
                            level = level,
                            size = size,
                            showGlow = info.shouldGlow,
                            textureIndex = info.textureIndex,
                            textureKit = info.uiTextureKit,
                        }
                    )
                end
            end
        end
    end

    AddAreaPOIList(C_AreaPoiInfo.GetEventsForMap(state.mapID), "Evento", 12, 23, "poi-workorders")
    AddAreaPOIList(C_AreaPoiInfo.GetDelvesForMap(state.mapID), "Abismo", 12, 23, "delves-bountiful")
    AddAreaPOIList(C_AreaPoiInfo.GetDragonridingRacesForMap(state.mapID), "Carrera de surcacielos", 11, 22, "racing")
    AddAreaPOIList(C_AreaPoiInfo.GetQuestHubsForMap(state.mapID), "Centro de misiones", 11, 22, "QuestNormal")

    local areaPOIs = C_AreaPoiInfo.GetAreaPOIForMap(state.mapID) or {}
    for _, areaPoiID in ipairs(areaPOIs) do
        local info = C_AreaPoiInfo.GetAreaPOIInfo(state.mapID, areaPoiID)
        if info then
            local x, y = Coordinates:SafeGetXY(info.position)
            if x and y then
                AddMarker(
                    "areapoi:" .. tostring(areaPoiID), x, y, info.atlasName, info.name,
                    info.isCurrentEvent and "Evento actual" or "Punto de interés", info.description,
                    {
                        areaPoiID = areaPoiID,
                        linkedUiMapID = info.linkedUiMapID,
                        level = 10,
                        size = 21,
                        showGlow = info.shouldGlow,
                        textureIndex = info.textureIndex,
                        textureKit = info.uiTextureKit,
                    }
                )
            end
        end
    end

    local vignetteGUIDs = C_VignetteInfo.GetVignettes() or {}
    for _, vignetteGUID in ipairs(vignetteGUIDs) do
        local info = C_VignetteInfo.GetVignetteInfo(vignetteGUID)
        if info and info.onWorldMap and not info.isDead then
            local position = C_VignetteInfo.GetVignettePosition(vignetteGUID, state.mapID)
            local x, y = Coordinates:SafeGetXY(position)
            if x and y then
                local label = info.type == Enum.VignetteType.Treasure and "Tesoro" or "Raro o punto destacado"
                AddMarker(
                    "vignette:" .. tostring(vignetteGUID), x, y, info.atlasName, info.name,
                    label, nil, { level = 12, size = 20, showGlow = false }
                )
            end
        end
    end

    C_QuestLine.RequestQuestLinesForMap(state.mapID)
    local availableQuestLines = C_QuestLine.GetAvailableQuestLines(state.mapID) or {}
    for _, info in ipairs(availableQuestLines) do
        if not info.inProgress and not C_QuestLog.IsWorldQuest(info.questID) then
            AddMarker(
                "quest:" .. tostring(info.questID), info.x, info.y, GetAvailableQuestAtlas(info),
                info.questName or info.questLineName or GetQuestTitle(info.questID),
                info.isCampaign and "Misión de campaña disponible" or "Misión disponible",
                info.questLineName, { questID = info.questID, level = 14, size = 21 }
            )
        end
    end

    local tasks = C_TaskQuest.GetQuestsOnMap(state.mapID) or {}
    for _, info in ipairs(tasks) do
        local questID = info.questID
        if questID and C_QuestLog.IsWorldQuest(questID) then
            AddMarker(
                "quest:" .. tostring(questID), info.x, info.y, GetWorldQuestAtlas(questID, info.inProgress),
                GetQuestTitle(questID), "Misión de mundo", nil,
                { questID = questID, trackable = true, level = 16, size = 24 }
            )
        elseif questID then
            AddMarker(
                "quest:" .. tostring(questID), info.x, info.y, "Bonus-Objective-Star",
                GetQuestTitle(questID), "Objetivo adicional", nil,
                { questID = questID, trackable = true, level = 13, size = 20 }
            )
        end
    end

    local activeQuests = C_QuestLog.GetQuestsOnMap(state.mapID) or {}
    for _, info in ipairs(activeQuests) do
        local questID = info.questID
        if questID and not C_QuestLog.IsWorldQuest(questID) then
            local isComplete = C_QuestLog.IsComplete(questID)
            AddMarker(
                "quest:" .. tostring(questID), info.x, info.y, GetActiveQuestAtlas(questID),
                GetQuestTitle(questID), isComplete and "Misión lista para entregar" or "Misión activa", nil,
                { questID = questID, trackable = true, level = 15, size = 22 }
            )
        end
    end

    HideContentMarkers(markerIndex)
    self:UpdateShapeClipping()
end

function Markers:ScheduleContentRefresh()
    if state.contentRefreshPending then
        return
    end

    state.contentRefreshPending = true
    C_Timer.After(0.15, function()
        state.contentRefreshPending = false
        if mapFrame and mapFrame:IsShown() then
            self:RefreshContentMarkers()
        end
    end)
end

function Markers:UpdateGroupMarkers()
    local markerIndex = 1
    local units = {}

    if IsInRaid() then
        for index = 1, GetNumGroupMembers() do
            units[#units + 1] = "raid" .. index
        end
    elseif IsInGroup() then
        for index = 1, GetNumSubgroupMembers() do
            units[#units + 1] = "party" .. index
        end
    end

    for _, unit in ipairs(units) do
        if UnitExists(unit) and not UnitIsUnit(unit, "player") then
            local x, y = Coordinates:GetUnitMapPosition(state.mapID, unit)
            if x and y then
                local marker = AcquireGroupMarker(markerIndex)
                local _, class = UnitClass(unit)
                if Addon:IsSecret(class) or type(class) ~= "string" then
                    class = nil
                end
                local color = class and RAID_CLASS_COLORS[class]
                if color then
                    marker:SetBackdropColor(color.r, color.g, color.b, 1)
                else
                    marker:SetBackdropColor(0.3, 0.8, 1, 1)
                end
                PositionMarker(marker, x, y)
                markerIndex = markerIndex + 1
            end
        end
    end

    HideGroupMarkers(markerIndex)
end

function Markers:UpdateMarkers()
    if not state.mapID or not state.layer then
        playerMarker:Hide()
        waypointMarker:Hide()
        if nativePlayerFrame then
            nativePlayerFrame:Hide()
        end
        HideGroupMarkers(1)
        return
    end

    local playerX, playerY = Coordinates:GetUnitMapPosition(state.mapID, "player")
    if nativePlayerFrame then
        if nativeMapID ~= state.mapID then
            local ok = pcall(nativePlayerFrame.SetUiMapID, nativePlayerFrame, state.mapID)
            if ok then
                nativeMapID = state.mapID
            end
        end

        nativePlayerFrame:Show()
        playerMarker:Hide()
        HideGroupMarkers(1)

        if playerX and playerY then
            statusText:SetFormattedText(
                "%.1f, %.1f  |  Zoom %d/%d",
                playerX * 100,
                playerY * 100,
                Addon.db.zoom,
                #Addon.ZOOM_LEVELS
            )
        else
            statusText:SetFormattedText(
                "Posición protegida  |  Zoom %d/%d",
                Addon.db.zoom,
                #Addon.ZOOM_LEVELS
            )
        end
    elseif playerX and playerY then
        PositionMarker(playerMarker, playerX, playerY)
        local facing = GetPlayerFacing()
        if not Addon:IsSecret(facing) and type(facing) == "number" then
            playerMarker.arrow:SetRotation(facing)
        end
        statusText:SetFormattedText(
            "%.1f, %.1f  |  Zoom %d/%d",
            playerX * 100,
            playerY * 100,
            Addon.db.zoom,
            #Addon.ZOOM_LEVELS
        )
    else
        playerMarker:Hide()
        statusText:SetFormattedText(
            "Posición no disponible  |  Zoom %d/%d",
            Addon.db.zoom,
            #Addon.ZOOM_LEVELS
        )
    end

    local waypointX
    local waypointY
    if C_Map.HasUserWaypoint() then
        local ok, position = pcall(C_Map.GetUserWaypointPositionForMap, state.mapID)
        if ok then
            waypointX, waypointY = Coordinates:SafeGetXY(position)
        end
    end

    if waypointX and waypointY then
        PositionMarker(waypointMarker, waypointX, waypointY)
    else
        waypointMarker:Hide()
    end

    if not nativePlayerFrame then
        self:UpdateGroupMarkers()
    end
    self:UpdateShapeClipping()
end

function Markers:RepositionContentMarkers()
    for _, marker in ipairs(contentMarkers) do
        if marker:IsShown() and marker.mapX and marker.mapY then
            PositionMarker(marker, marker.mapX, marker.mapY)
        end
    end
    for _, marker in ipairs(bossMarkers) do
        if marker:IsShown() and marker.mapX and marker.mapY then
            PositionMarker(marker, marker.mapX, marker.mapY)
        end
    end
    self:UpdateShapeClipping()
end

function Markers:UpdateShapeClipping()
    ApplyShapeVisibility(playerMarker)
    ApplyShapeVisibility(waypointMarker)
    for _, marker in ipairs(groupMarkers) do
        ApplyShapeVisibility(marker)
    end
    for _, marker in ipairs(contentMarkers) do
        ApplyShapeVisibility(marker)
    end
    for _, marker in ipairs(bossMarkers) do
        ApplyShapeVisibility(marker)
    end
    ApplyShapeToChildren(nativePlayerFrame, 3)
end

function Markers:HideAll()
    HideContentMarkers(1)
    HideBossMarkers(1)
    HideGroupMarkers(1)
    if playerMarker then
        playerMarker:Hide()
    end
    if nativePlayerFrame then
        nativePlayerFrame:Hide()
    end
    if waypointMarker then
        waypointMarker:Hide()
    end
end

function Markers:IsNativePlayerFrameAvailable()
    return nativePlayerFrame ~= nil
end

function Markers:IsPlayerUnderMouse()
    if not nativePlayerFrame or not nativePlayerFrame:IsShown() then
        return false
    end

    local results = { pcall(nativePlayerFrame.GetMouseOverUnits, nativePlayerFrame) }
    if not results[1] then
        return false
    end

    for index = 2, #results do
        local unit = results[index]
        if unit == "player" then
            return true
        end
        local ok, isPlayer = pcall(UnitIsUnit, unit, "player")
        if ok and isPlayer then
            return true
        end
    end
    return false
end

function Markers:HighlightPlayer()
    if not nativePlayerFrame or not state.mapID then
        return
    end

    pcall(function()
        if nativeMapID ~= state.mapID then
            nativePlayerFrame:SetUiMapID(state.mapID)
            nativeMapID = state.mapID
        end
        nativePlayerFrame:UpdatePlayerPins()
        nativePlayerFrame:SetPlayerPingScale(math.max(1, Addon.db.playerArrowSize / 18))
        nativePlayerFrame:StartPlayerPing(2.5, 0.6)
    end)
end

function Markers:SetPlayerArrowSize(size)
    size = Addon:Clamp(
        math.floor((tonumber(size) or Addon.defaults.playerArrowSize) + 0.5),
        Addon.MIN_ARROW_SIZE,
        Addon.MAX_ARROW_SIZE
    )

    if playerMarker then
        playerMarker:SetSize(size, size)
    end
    if nativePlayerFrame then
        pcall(nativePlayerFrame.SetPinSize, nativePlayerFrame, "player", size)
    end
end

function Markers:Initialize(ui)
    canvas = ui.canvas
    viewport = ui.viewport
    mapFrame = ui.mapFrame
    statusText = ui.statusText
    CreatePrimaryMarkers()
end
