local pluginName, pluginShared = ...
local Tracking = {}
pluginShared.Tracking = Tracking

local guarded = pluginShared.guarded
local reported = pluginShared.reported or guarded

Tracking.APPEARANCE = (Enum and Enum.ContentTrackingType and Enum.ContentTrackingType.Appearance) or 0
Tracking.MOUNT = (Enum and Enum.ContentTrackingType and Enum.ContentTrackingType.Mount) or 1

local function stopType()
    return (Enum and Enum.ContentTrackingStopType and Enum.ContentTrackingStopType.Manual) or 2
end

function Tracking:IsEnabled()
    if type(C_ContentTracking) ~= "table" then
        return false
    end
    if type(ContentTrackingUtil) == "table" and type(ContentTrackingUtil.IsContentTrackingEnabled) == "function" then
        local ok, enabled = pcall(ContentTrackingUtil.IsContentTrackingEnabled)
        if ok then
            return enabled and true or false
        end
    end
    return true
end

function Tracking:CanTrack(trackType, id)
    if type(id) ~= "number" or not self:IsEnabled() then
        return false
    end
    local ok, trackable = guarded(C_ContentTracking.IsTrackable, trackType, id)
    return (ok and trackable) and true or false
end

function Tracking:IsTracking(trackType, id)
    if type(id) ~= "number" or type(C_ContentTracking) ~= "table" then
        return false
    end
    local ok, active = guarded(C_ContentTracking.IsTracking, trackType, id)
    return (ok and active) and true or false
end

function Tracking:NextWaypoint(trackType, id)
    if type(id) ~= "number" or type(C_ContentTracking) ~= "table"
        or type(C_ContentTracking.GetNextWaypointForTrackable) ~= "function"
        or type(C_Map) ~= "table" or type(C_Map.GetBestMapForUnit) ~= "function" then
        return nil
    end
    local okMap, uiMapID = pcall(C_Map.GetBestMapForUnit, "player")
    if not (okMap and type(uiMapID) == "number") then
        return nil
    end
    local candidate, hops = uiMapID, 0
    local lastResult
    local zoneMap = pluginShared.Route and pluginShared.Route.ZoneMap and pluginShared.Route:ZoneMap(uiMapID) or uiMapID
    while candidate and hops < 6 do
        local ok, result, mapInfo = pcall(C_ContentTracking.GetNextWaypointForTrackable, trackType, id, candidate)
        lastResult = ok and result or nil
        if ok and type(mapInfo) == "table" and type(mapInfo.x) == "number" and type(mapInfo.y) == "number" then
            local text = type(mapInfo.waypointText) == "string" and mapInfo.waypointText:lower() or ""
            local leaves = text:find("portal", 1, true) or text:find("fly to", 1, true) or text:find("boat", 1, true) or text:find("zeppelin", 1, true)
            if leaves and candidate ~= zoneMap and zoneMap then
                -- from a city sub-map Blizzard sometimes routes out through a portal even when the
                -- item is in this zone; the parent zone map answers correctly, prefer it when it stays here
                local okZ, resultZ, zoneInfo = pcall(C_ContentTracking.GetNextWaypointForTrackable, trackType, id, zoneMap)
                if okZ and type(zoneInfo) == "table" and type(zoneInfo.x) == "number" and type(zoneInfo.y) == "number" then
                    local zText = type(zoneInfo.waypointText) == "string" and zoneInfo.waypointText:lower() or ""
                    local zLeaves = zText:find("portal", 1, true) or zText:find("fly to", 1, true) or zText:find("boat", 1, true) or zText:find("zeppelin", 1, true)
                    if not zLeaves then
                        return zoneMap, zoneInfo.x, zoneInfo.y, zoneInfo.waypointText, resultZ
                    end
                end
            end
            return candidate, mapInfo.x, mapInfo.y, mapInfo.waypointText, result
        end
        if ok and result == 1 then
            return nil, nil, nil, nil, result
        end
        local okInfo, info = pcall(C_Map.GetMapInfo, candidate)
        candidate = okInfo and type(info) == "table" and info.parentMapID or nil
        hops = hops + 1
    end
    return nil, nil, nil, nil, lastResult
end

-- Untrack every appearance and mount in Blizzard's tracker. Returns how many were cleared.
function Tracking:StopAll()
    if type(C_ContentTracking) ~= "table" or type(C_ContentTracking.GetTrackedIDs) ~= "function"
        or type(C_ContentTracking.StopTracking) ~= "function" then
        return 0
    end
    local cleared = 0
    for _, trackType in ipairs({ self.APPEARANCE or 0, self.MOUNT or 1 }) do
        local ok, ids = pcall(C_ContentTracking.GetTrackedIDs, trackType)
        for _, id in ipairs(ok and type(ids) == "table" and ids or {}) do
            if reported(C_ContentTracking.StopTracking, trackType, id, stopType()) then
                cleared = cleared + 1
            end
        end
    end
    return cleared
end

function Tracking:Toggle(trackType, id)
    if type(id) ~= "number" or type(C_ContentTracking) ~= "table" then
        return false
    end
    if self:IsTracking(trackType, id) then
        if type(C_ContentTracking.StopTracking) ~= "function" then
            return false
        end
        -- the answer was thrown away here and the caller told it worked either way; now a real
        -- failure is both reported and handed back
        return reported(C_ContentTracking.StopTracking, trackType, id, stopType()) and true or false
    end
    if type(C_ContentTracking.StartTracking) ~= "function" then
        return false
    end
    local ok, trackError = pcall(C_ContentTracking.StartTracking, trackType, id)
    self.lastStartError = trackError
    if not ok or trackError ~= nil then
        return false
    end
    if type(C_SuperTrack) == "table" and type(C_SuperTrack.SetSuperTrackedContent) == "function" then
        pcall(C_SuperTrack.SetSuperTrackedContent, trackType, id)
    end
    return true
end
