local pluginName, pluginShared = ...
local Favorites = pluginShared.Favorites

local guarded = pluginShared.guarded
local reported = pluginShared.reported or guarded
local plain = pluginShared.plain
-- A withheld value is dropped, and the panel is told the game held something back so it says so and
-- asks again once the fight is over. Lockouts loads later than this file; it is looked up when used.
local function withheld(value)
    local lockouts = pluginShared.Lockouts
    if lockouts and lockouts.Plain then
        return lockouts:Plain(value)
    end
    return plain(value)
end
local L = LibStub("AceLocale-3.0"):GetLocale("AppearanceMatic")

function Favorites:EnsureMountDB()
    AppearanceMaticDB = AppearanceMaticDB or {}
    AppearanceMaticDB.mountFavorites = AppearanceMaticDB.mountFavorites or {}
    return AppearanceMaticDB.mountFavorites
end

function Favorites:MountIsFavorite(mountID)
    if not mountID then
        return false
    end
    return self:EnsureMountDB()[mountID] == true
end

function Favorites:MountCollected(mountID)
    if not (mountID and C_MountJournal and type(C_MountJournal.GetMountInfoByID) == "function") then
        return false
    end
    local info = { guarded(C_MountJournal.GetMountInfoByID, mountID) }
    return (info[1] and info[12]) and true or false
end

-- A star only ever marks work left to do: starring a mount already in the journal leaves it unstarred
-- (and clears a stale star) and says so.
function Favorites:MountSet(mountID, on)
    if not mountID then
        return false
    end
    local set = self:EnsureMountDB()
    if on and self:MountCollected(mountID) then
        local had = set[mountID] ~= nil
        set[mountID] = nil
        self:SayCollected()
        if had then
            self:Notify()
        end
        return false
    end
    if on then
        set[mountID] = true
    else
        set[mountID] = nil
    end
    self:Notify()
    return on and true or false
end

function Favorites:MountToggle(mountID)
    if not mountID then
        return false
    end
    self:MountSet(mountID, not self:MountIsFavorite(mountID))
    return self:MountIsFavorite(mountID)
end

-- A mount was starred or learned: the starred list is walked out of the journal again next time.
function Favorites:InvalidateMountScan()
    self.mountScan = nil
end

-- Every mount in the game is walked to find the starred ones, so this is built once and kept until a
-- star moves or a mount is learned. The panel asked for it on every rebuild, and the route asked for
-- it again on every check with the panel closed.
function Favorites:FavoritedUncollectedMounts()
    if self.mountScan then
        return self.mountScan
    end
    local out = {}
    if not (C_MountJournal and type(C_MountJournal.GetMountIDs) == "function") then
        return out
    end
    local ok, ids = guarded(C_MountJournal.GetMountIDs)
    if not ok or type(ids) ~= "table" then
        return out
    end
    local incomplete = false
    for _, mountID in ipairs(ids) do
        if self:MountIsFavorite(mountID) then
            local info = { guarded(C_MountJournal.GetMountInfoByID, mountID) }
            -- the name is joined into a row later on, so a secret one is dropped here rather than
            -- raising at the point it is drawn
            local name, spellID, icon = withheld(info[2]), withheld(info[3]), withheld(info[4])
            if info[1] and not withheld(info[12]) then
                out[#out + 1] = { mountID = mountID, name = name, spellID = spellID, icon = icon }
                if type(name) ~= "string" then
                    -- a row without its name is the game holding back, not the answer: this scan is
                    -- not kept, so the next build asks again
                    incomplete = true
                end
            end
        end
    end
    self.mountScan = (not incomplete) and out or nil
    return out
end

function Favorites:MountPassesFilter(mountID)
    if not (AppearanceMaticDB and AppearanceMaticDB.preview and AppearanceMaticDB.preview.mountFavoritesOnly) then
        return true
    end
    if not (mountID and self:MountIsFavorite(mountID)) then
        return false
    end
    if C_MountJournal and type(C_MountJournal.GetMountInfoByID) == "function" then
        local info = { guarded(C_MountJournal.GetMountInfoByID, mountID) }
        if info[1] and info[12] then
            return false
        end
    end
    return true
end

function Favorites:PruneMountList()
    if self.mountPruning then
        return
    end
    if not (AppearanceMaticDB and AppearanceMaticDB.preview and AppearanceMaticDB.preview.mountFavoritesOnly) then
        return
    end
    local sb = MountJournal and MountJournal.ScrollBox
    if not (sb and type(sb.SetDataProvider) == "function" and type(CreateDataProvider) == "function") then
        return
    end
    if not (C_MountJournal and type(C_MountJournal.GetNumDisplayedMounts) == "function" and type(C_MountJournal.GetDisplayedMountInfo) == "function") then
        return
    end
    self.mountPruning = true
    pcall(function()
        local provider = CreateDataProvider()
        for index = 1, C_MountJournal.GetNumDisplayedMounts() do
            local mountID = select(12, C_MountJournal.GetDisplayedMountInfo(index))
            if self:MountPassesFilter(mountID) then
                provider:Insert({ index = index, mountID = mountID })
            end
        end
        local retain = ScrollBoxConstants and ScrollBoxConstants.RetainScrollPosition
        sb:SetDataProvider(provider, retain)
    end)
    self.mountPruning = false
end

function Favorites:InstallMountFilterMenu()
    if self.mountFilterHooked or type(Menu) ~= "table" or type(Menu.ModifyMenu) ~= "function" then
        return
    end
    self.mountFilterHooked = true
    Menu.ModifyMenu("MENU_MOUNT_COLLECTION_FILTER", function(owner, rootDescription)
        rootDescription:CreateCheckbox(L["fav_menu_favorites"], function()
            return AppearanceMaticDB and AppearanceMaticDB.preview and AppearanceMaticDB.preview.mountFavoritesOnly or false
        end, function()
            AppearanceMaticDB = AppearanceMaticDB or {}
            AppearanceMaticDB.preview = AppearanceMaticDB.preview or {}
            AppearanceMaticDB.preview.mountFavoritesOnly = not AppearanceMaticDB.preview.mountFavoritesOnly
            if type(MountJournal_UpdateMountList) == "function" then
                MountJournal_UpdateMountList()
            end
            return MenuResponse and MenuResponse.Refresh
        end)
    end)
end

function Favorites:InstallMountListHook()
    if self.mountListHooked or type(hooksecurefunc) ~= "function" then
        return
    end
    if type(MountJournal_UpdateMountList) ~= "function" then
        return
    end
    self.mountListHooked = true
    hooksecurefunc("MountJournal_UpdateMountList", function()
        Favorites:PruneMountList()
        Favorites:RefreshMountStars()
    end)
end

function Favorites:EnsureMountStar(button)
    if type(button) ~= "table" or type(CreateFrame) ~= "function" then
        return
    end
    if not button.amStar then
        local ok, star = pcall(function()
            local s = CreateFrame("Button", nil, button)
            s:SetSize(22, 22)
            s:SetPoint("RIGHT", button, "RIGHT", -6, 0)
            s:SetFrameLevel((button:GetFrameLevel() or 1) + 5)
            local tex = s:CreateTexture(nil, "OVERLAY")
            tex:SetAllPoints()
            tex:SetAtlas("PetJournal-FavoritesIcon")
            s.tex = tex
            s:SetScript("OnClick", function()
                local mid = button.mountID
                if mid then
                    Favorites:MountToggle(mid)
                    Favorites:RefreshMountStars()
                end
            end)
            return s
        end)
        if ok then
            button.amStar = star
        else
            return
        end
    end
    local mountID = button.mountID
    if not mountID then
        button.amStar:Hide()
        return
    end
    local fav = self:MountIsFavorite(mountID)
    -- the journal is polled several times a second while it is open, and the state almost never moves:
    -- a row already drawn this way is left alone rather than re-dressed every poll
    if button.amStar.tex and (button.amStarDrawn ~= fav or button.amStarFor ~= mountID) then
        reported(button.amStar.tex.SetDesaturated, button.amStar.tex, not fav)
        reported(button.amStar.tex.SetAlpha, button.amStar.tex, fav and 1 or 0.3)
        button.amStarDrawn, button.amStarFor = fav, mountID
    end
    button.amStar:Show()
end

-- The route icon left of the mount star
function Favorites:EnsureMountRoute(button)
    local routeList = pluginShared.RouteList
    if type(button) ~= "table" or type(CreateFrame) ~= "function" or not (routeList and button.amStar) then
        return
    end
    if not button.amRoute then
        local ok, made = pcall(function()
            local r = CreateFrame("Button", nil, button)
            r:SetSize(22, 22)
            r:SetPoint("RIGHT", button.amStar, "LEFT", -2, 0)
            r:SetFrameLevel((button:GetFrameLevel() or 1) + 5)
            local tex = r:CreateTexture(nil, "OVERLAY")
            tex:SetAllPoints()
            tex:SetTexture(routeList.ICON_DIM)
            r.tex = tex
            r:SetScript("OnClick", function()
                local mid = button.mountID
                if mid then
                    local lockouts = pluginShared.Lockouts
                    if lockouts and lockouts.ToggleRouteItem then
                        lockouts:ToggleRouteItem(true, mid)
                    else
                        routeList:Toggle(true, mid)
                    end
                    Favorites:RefreshMountStars()
                end
            end)
            r:SetScript("OnEnter", function()
                if GameTooltip then
                    GameTooltip:SetOwner(r, "ANCHOR_RIGHT")
                    local onRoute = button.mountID and routeList:Has(true, button.mountID)
                    GameTooltip:SetText(onRoute and L["route_tooltip_remove"] or L["route_tooltip_add"])
                    GameTooltip:AddLine(L["route_tooltip_desc"], 0.8, 0.8, 0.8, true)
                    GameTooltip:Show()
                end
            end)
            r:SetScript("OnLeave", function()
                if GameTooltip then GameTooltip:Hide() end
            end)
            return r
        end)
        if ok then
            button.amRoute = made
        else
            return
        end
    end
    local mountID = button.mountID
    if not mountID then
        button.amRoute:Hide()
        return
    end
    local onRoute = routeList:Has(true, mountID) and true or false
    -- same as the star: the poll redraws nothing that already looks right
    if button.amRoute.tex and (button.amRouteDrawn ~= onRoute or button.amRouteFor ~= mountID) then
        reported(button.amRoute.tex.SetTexture, button.amRoute.tex, onRoute and routeList.ICON or routeList.ICON_DIM)
        button.amRouteDrawn, button.amRouteFor = onRoute, mountID
    end
    button.amRoute:Show()
end

function Favorites:RefreshMountStars()
    if not (MountJournal and MountJournal.ScrollBox and type(MountJournal.ScrollBox.GetFrames) == "function") then
        return
    end
    local ok, frames = guarded(MountJournal.ScrollBox.GetFrames, MountJournal.ScrollBox)
    if not ok or type(frames) ~= "table" then
        return
    end
    for i = 1, #frames do
        self:EnsureMountStar(frames[i])
        self:EnsureMountRoute(frames[i])
    end
end

function Favorites:InstallMountStarPoller()
    if self.mountStarPoller or type(CreateFrame) ~= "function" then
        return
    end
    if not (MountJournal and type(MountJournal.HookScript) == "function") then
        return
    end
    local f = CreateFrame("Frame")
    f:Hide()
    f:SetScript("OnUpdate", function(_, elapsed)
        Favorites.mountStarTimer = (Favorites.mountStarTimer or 0) + (elapsed or 0)
        if Favorites.mountStarTimer < 0.15 then
            return
        end
        Favorites.mountStarTimer = 0
        Favorites:RefreshMountStars()
    end)
    self.mountStarPoller = f
    MountJournal:HookScript("OnShow", function()
        f:Show()
        Favorites:RefreshMountStars()
    end)
    MountJournal:HookScript("OnHide", function()
        f:Hide()
    end)
    if type(MountJournal.IsShown) == "function" and MountJournal:IsShown() then
        f:Show()
    end
end

function Favorites:InstallMountHooks()
    self:InstallMountFilterMenu()
    self:InstallMountListHook()
    self:InstallMountStarPoller()
end

local isLoaded = C_AddOns and C_AddOns.IsAddOnLoaded or IsAddOnLoaded
if type(isLoaded) == "function" then
    local ok, loaded = guarded(isLoaded, "Blizzard_Collections")
    if ok and loaded then
        Favorites:InstallWardrobeHooks()
        Favorites:InstallMountHooks()
    end
end
