local addonName, privateDB = ...
local HDG = _G.HousingDecorGuide

-- ============================================================================
-- HDG_Waypoints.lua - Waypoint Facade
-- Centralized waypoint system: TomTom (optional) or Blizzard native
-- ============================================================================

HDG.Waypoints = {}

-- ============================================================================
-- TRAVEL HINTS (expansion-level portal directions)
-- ============================================================================

local TRAVEL_HINTS = {
    ["The Burning Crusade"] = {
        default = "Travel through the Dark Portal in Blasted Lands, or use Shattrath portal in portal room",
    },
    ["Wrath of the Lich King"] = {
        default = "Portal to Dalaran (Northrend) in Stormwind/Orgrimmar portal room",
    },
    ["Cataclysm"] = {
        default = "Cataclysm portals in Stormwind/Orgrimmar portal room",
    },
    ["Mists of Pandaria"] = {
        A = "Portal to Jade Forest in Stormwind portal room",
        H = "Portal to Jade Forest in Orgrimmar portal room",
    },
    ["Warlords of Draenor"] = {
        A = "Portal to Stormshield (Ashran) in Stormwind portal room",
        H = "Portal to Warspear (Ashran) in Orgrimmar portal room",
    },
    ["Legion"] = {
        default = "Portal to Dalaran (Broken Isles) in Stormwind/Orgrimmar portal room",
    },
    ["Battle for Azeroth"] = {
        A = "Portal to Boralus in Stormwind portal room",
        H = "Portal to Dazar'alor in Orgrimmar portal room",
    },
    ["Shadowlands"] = {
        default = "Portal to Oribos in Stormwind/Orgrimmar portal room",
    },
    ["Dragonflight"] = {
        default = "Portal to Valdrakken in Stormwind/Orgrimmar portal room",
    },
    ["The War Within"] = {
        default = "Portal to Dornogal in Stormwind/Orgrimmar portal room",
    },
    ["Midnight"] = {
        default = "Portal to Midnight zones in Stormwind/Orgrimmar portal room",
    },
}

local function GetPlayerExpansion()
    local mapID = C_Map.GetBestMapForUnit("player")
    if not mapID then return nil end
    return HDG_ExpansionData.GetExpansionForMapID(mapID)
end

local function GetTravelHint(destMapID)
    local playerExp = GetPlayerExpansion()
    if not playerExp then return nil end
    local destExp = HDG_ExpansionData.GetExpansionForMapID(destMapID)
    if not destExp or destExp == "Other" then return nil end
    if playerExp == destExp then return nil end
    local hints = TRAVEL_HINTS[destExp]
    if not hints then return nil end
    local faction = UnitFactionGroup("player")
    if faction == "Alliance" and hints.A then return hints.A end
    if faction == "Horde" and hints.H then return hints.H end
    return hints.default
end

-- ============================================================================
-- PROVIDER SELECTION
-- ============================================================================

local function GetWaypointPref()
    local state = HDG.Store and HDG.Store:GetState()
    return state and state.config and state.config.waypointProvider or "auto"
end

local function ShouldUseTomTom()
    local pref = GetWaypointPref()
    if pref == "native" then return false end
    if pref == "tomtom" then return true end
    return TomTom and TomTom.AddWaypoint and true or false
end

-- ============================================================================
-- SINGLE WAYPOINT
-- ============================================================================

function HDG.Waypoints:Set(mapID, x, y, title)
    if InCombatLockdown() then return false, "combat" end

    local uiMapID, mapX, mapY = HDG_VendorResolver.ZonePctToMap(mapID, x, y)
    if not uiMapID then return false, "no_map" end

    -- TomTom path
    if ShouldUseTomTom() then
        if TomTom and TomTom.AddWaypoint then
            local ok, uid = pcall(function()
                return TomTom:AddWaypoint(uiMapID, mapX, mapY, {
                    title = title or "Vendor",
                    persistent = false,
                    from = "HDG",
                })
            end)
            if ok and uid then
                pcall(function()
                    TomTom:SetCrazyArrow(uid, TomTom.profile.arrow.arrival or 5, title)
                end)
                return true, "tomtom"
            end
        end
        -- Strict TomTom mode but TomTom not available
        local pref = GetWaypointPref()
        if pref == "tomtom" then return false, "no_tomtom" end
    end

    -- Native waypoint (with map pin fallback for unsupported zones)
    if C_Map.CanSetUserWaypointOnMap(uiMapID) then
        local point = UiMapPoint.CreateFromCoordinates(uiMapID, mapX, mapY)
        C_Map.SetUserWaypoint(point)
        C_SuperTrack.SetSuperTrackedUserWaypoint(true)
    end
    -- Always add a map pin and open the world map. OpenWorldMap is
    -- protected in combat; pcall absorbs the rare cross-addon-taint case.
    HDG.Waypoints:AddMapPin(uiMapID, mapX, mapY, title or "Vendor")
    if not InCombatLockdown() then pcall(OpenWorldMap, uiMapID) end
    return true, "native"
end

-- ============================================================================
-- MULTI WAYPOINT
-- ============================================================================

-- waypoints = { { mapID, x, y, title }, ... }
function HDG.Waypoints:SetMultiple(waypoints)
    if InCombatLockdown() then return 0, "combat" end
    if not waypoints or #waypoints == 0 then return 0, "empty" end

    -- TomTom path: one waypoint per vendor
    if ShouldUseTomTom() and TomTom and TomTom.AddWaypoint then
        local count = 0
        for _, wp in ipairs(waypoints) do
            local uiMapID, mapX, mapY = HDG_VendorResolver.ZonePctToMap(wp[1], wp[2], wp[3])
            if uiMapID then
                local ok = pcall(function()
                    TomTom:AddWaypoint(uiMapID, mapX, mapY, {
                        title = wp[4] or "Vendor",
                        persistent = false,
                        from = "HDG",
                    })
                end)
                if ok then count = count + 1 end
            end
        end
        return count, "tomtom"
    end

    -- Strict TomTom mode but TomTom not available
    local pref = GetWaypointPref()
    if pref == "tomtom" then return 0, "no_tomtom" end

    -- Native fallback: show all as map pins, set first as active waypoint
    local count = 0
    local firstSet = false
    for _, wp in ipairs(waypoints) do
        local uiMapID, mapX, mapY = HDG_VendorResolver.ZonePctToMap(wp[1], wp[2], wp[3])
        if uiMapID then
            HDG.Waypoints:AddMapPin(uiMapID, mapX, mapY, wp[4] or "Vendor")
            count = count + 1
            if not firstSet and C_Map.CanSetUserWaypointOnMap(uiMapID) then
                local point = UiMapPoint.CreateFromCoordinates(uiMapID, mapX, mapY)
                C_Map.SetUserWaypoint(point)
                C_SuperTrack.SetSuperTrackedUserWaypoint(true)
                firstSet = true
            end
        end
    end

    -- Open world map if we placed pins. OpenWorldMap is protected in
    -- combat; pcall absorbs the rare cross-addon-taint case.
    if count > 0 and not InCombatLockdown() then
        local firstMapID = HDG_VendorResolver.ZonePctToMap(waypoints[1][1], waypoints[1][2], waypoints[1][3])
        if firstMapID then pcall(OpenWorldMap, firstMapID) end
    end

    return count, "native"
end

-- ============================================================================
-- CLEAR
-- ============================================================================

function HDG.Waypoints:Clear()
    if TomTom and TomTom.RemoveWaypoint then
        pcall(function()
            local waypoints = TomTom.waypoints
            if not waypoints then return end
            -- Collect HDG waypoints first, then remove (avoid mutating during iteration)
            local toRemove = {}
            for mapID, wps in pairs(waypoints) do
                for key, wp in pairs(wps) do
                    if type(wp) == "table" and wp.from == "HDG" then
                        toRemove[#toRemove + 1] = wp
                    end
                end
            end
            for _, wp in ipairs(toRemove) do
                TomTom:RemoveWaypoint(wp)
            end
        end)
    end
    pcall(C_Map.ClearUserWaypoint)
    HDG.Waypoints:ClearAllMapPins()
end

-- ============================================================================
-- FEEDBACK MESSAGES
-- ============================================================================

function HDG.Waypoints:PrintResult(ok, provider, title, count, destMapID, vendorFaction)
    if not ok then
        if provider == "combat" then
            print("|cFFFF0000[HDG]|r Cannot set waypoints during combat.")
        elseif provider == "no_tomtom" then
            print("|cFFFFAA00[HDG]|r TomTom is not installed. Change Waypoint System to Auto in HDG settings.")
        elseif provider == "unsupported_map" then
            print("|cFFFFAA00[HDG]|r Cannot set waypoint for this zone.")
        elseif provider == "no_map" then
            print("|cFFFFAA00[HDG]|r No valid coordinates for this vendor.")
        else
            print("|cFFFFAA00[HDG]|r Could not set waypoint.")
        end
        return
    end
    if count then
        if provider == "tomtom" then
            print(string.format("|cFF00FF00[HDG]|r Set %d TomTom waypoints.", count))
        else
            print(string.format("|cFF00FF00[HDG]|r %d vendor pins added to map. Navigating to closest.", count))
        end
    else
        local name = title or "vendor"
        if provider == "tomtom" then
            print("|cFF00FF00[HDG]|r TomTom waypoint set for " .. name)
        else
            print("|cFF00FF00[HDG]|r Waypoint set for " .. name)
        end
    end
    -- Opposite-faction vendor warning
    if vendorFaction and vendorFaction ~= "N" then
        local playerFaction = UnitFactionGroup("player")
        local vendorIsAlliance = vendorFaction == "A"
        local vendorIsHorde = vendorFaction == "H"
        if (playerFaction == "Alliance" and vendorIsHorde) or (playerFaction == "Horde" and vendorIsAlliance) then
            local label = vendorIsAlliance and "Alliance" or "Horde"
            print("|cFFFFAA00[HDG]|r Note: This is a " .. label .. "-only vendor.")
        end
    end
    -- Travel hint for cross-expansion destinations
    if destMapID then
        local hint = GetTravelHint(destMapID)
        if hint then
            print("|cFF88BBDD[HDG]|r Travel: " .. hint)
        end
    end
end

-- ============================================================================
-- CUSTOM MAP PIN DATAPROVIDER (show multiple vendor pins on world map)
-- ============================================================================

HDG_VendorPinMixin = CreateFromMixins(MapCanvasPinMixin)

function HDG_VendorPinMixin:OnLoad()
    self:UseFrameLevelType("PIN_FRAME_LEVEL_AREA_POI")
    self:SetScalingLimits(1, 1.0, 1.2)
end

function HDG_VendorPinMixin:OnAcquired(data)
    self:SetPosition(data.x, data.y)
    self:SetSize(20, 20)
    if not self.icon then
        self.icon = self:CreateTexture(nil, "ARTWORK")
        self.icon:SetAllPoints()
    end
    self.icon:SetAtlas("housing-map-plot-occupied")
    self.data = data
    self:EnableMouse(true)
    self:SetScript("OnEnter", function(pin)
        GameTooltip:SetOwner(pin, "ANCHOR_RIGHT")
        GameTooltip:AddLine(pin.data.title, 1, 1, 1)
        GameTooltip:AddLine("Click to navigate", 0.78, 0.78, 0.78)
        GameTooltip:Show()
    end)
    self:SetScript("OnLeave", GameTooltip_Hide)
    self:SetScript("OnMouseDown", function(pin)
        local pt = UiMapPoint.CreateFromCoordinates(pin.data.mapID, pin.data.x, pin.data.y)
        C_Map.SetUserWaypoint(pt)
        C_SuperTrack.SetSuperTrackedUserWaypoint(true)
    end)
end

local VendorPinProvider = CreateFromMixins(MapCanvasDataProviderMixin)
VendorPinProvider.pendingPins = {}

function VendorPinProvider:RemoveAllData()
    self:GetMap():RemoveAllPinsByTemplate("HDGVendorPinTemplate")
end

function VendorPinProvider:RefreshAllData()
    self:RemoveAllData()
    local mapID = self:GetMap():GetMapID()
    local pins = self.pendingPins[mapID]
    if not pins then return end
    for _, data in ipairs(pins) do
        self:GetMap():AcquirePin("HDGVendorPinTemplate", data)
    end
end

function HDG.Waypoints:AddMapPin(mapID, x, y, title)
    if not VendorPinProvider.pendingPins[mapID] then
        VendorPinProvider.pendingPins[mapID] = {}
    end
    table.insert(VendorPinProvider.pendingPins[mapID], {
        mapID = mapID, x = x, y = y, title = title
    })
    if WorldMapFrame and WorldMapFrame:IsShown() then
        VendorPinProvider:RefreshAllData()
    end
end

function HDG.Waypoints:ClearAllMapPins()
    wipe(VendorPinProvider.pendingPins)
    if WorldMapFrame and WorldMapFrame:IsShown() and VendorPinProvider.GetMap then
        pcall(function() VendorPinProvider:RemoveAllData() end)
    end
end

-- ============================================================================
-- INIT (call from HDG init after WorldMapFrame is available)
-- ============================================================================

local mapPinsInitialized = false

function HDG.Waypoints:InitMapPins()
    if mapPinsInitialized then return end
    if not WorldMapFrame then return end
    mapPinsInitialized = true

    local canvas = WorldMapFrame:GetCanvas()
    -- Tier 2 reset (see Reference/MIDNIGHT_SECRET_VALUES.md): SetToDefaults
    -- on release clears any Secret state the pin inherited from a tainted
    -- acquisition. Without this, "if one secret object enters a pool, all
    -- future acquisitions end up being secret too" (Blizzard's Pools.lua).
    -- Matches the canonical Pool_HideAndSetToDefaults reset.
    local resetFunc = function(_, pin)
        if pin.SetToDefaults then pin:SetToDefaults() end
        pin:Hide()
        pin:ClearAllPoints()
    end
    local createFunc = function()
        local f = CreateFrame("Frame", nil, canvas)
        Mixin(f, HDG_VendorPinMixin)
        f:OnLoad()
        return f
    end

    local pool
    if CreateUnsecuredRegionPoolInstance then
        pool = CreateUnsecuredRegionPoolInstance("HDGVendorPinTemplate", createFunc, resetFunc)
    else
        pool = CreateFramePool("FRAME", canvas, nil, resetFunc, false, createFunc)
    end
    WorldMapFrame.pinPools["HDGVendorPinTemplate"] = pool
    WorldMapFrame:AddDataProvider(VendorPinProvider)
end
