--[[
    Commander 3.0 - Mounts Data Source

    Provides access to player mount collection.
    Works on Retail (Mount Journal) and Classic (spellbook/bags).

    Commands: mounts, mount
]]

local ADDON_NAME, C3 = ...

-- Use shared utilities
local Colorize = function(...) return C3.Colors.Colorize(...) end

-------------------------------------------------------------------------------
-- Helper: Get target's mount
-------------------------------------------------------------------------------

local function GetTargetMount()
    local unit = "target"
    if not UnitExists(unit) then
        return nil, "No target"
    end

    -- Scan target's buffs for mount spells
    for i = 1, 40 do
        local auraData = C_UnitAuras.GetBuffDataByIndex(unit, i)

        -- Handle WoW 12.0+ secret values
        if C3.Secrets and C3.Secrets.IsSecret(auraData) then
            return nil, "Aura data restricted during combat"
        end

        if not auraData then break end

        local mountID = C_MountJournal.GetMountFromSpell(auraData.spellId)
        if mountID then
            local name, spellID, icon, isActive, isUsable, sourceType,
                  isFavorite, isFactionSpecific, faction, shouldHideOnChar,
                  isCollected, mountTypeID = C_MountJournal.GetMountInfoByID(mountID)

            -- Check usability for current character/location
            local canUse = false
            if not shouldHideOnChar then
                local usable = C_MountJournal.GetMountUsabilityByID and
                               C_MountJournal.GetMountUsabilityByID(mountID, true)
                if usable == nil then
                    usable = isUsable
                end
                canUse = usable == true or isCollected == true
            end

            return {
                id = mountID,
                name = name,
                icon = icon,
                spellID = spellID,
                isCollected = isCollected,
                canUse = canUse,
                targetName = UnitName(unit)
            }
        end
    end

    return nil, (UnitName(unit) or "Target") .. " is not mounted"
end

-- Export for intellisense
C3.GetTargetMount = GetTargetMount

-------------------------------------------------------------------------------
-- Mount Type Registration
-------------------------------------------------------------------------------

C3.Types:Register("Mount", {
    description = "Collected mount from journal",
    source = "mounts",
    properties = {
        { "id", "Mount ID number" },
        { "name", "Mount name" },
        { "spellID", "Spell ID to summon mount" },
        { "icon", "Icon texture ID" },
        { "isUsable", "true if can be summoned now" },
        { "isCollected", "true if collected" },
        { "isFavorite", "true if marked favorite" },
        { "isActive", "true if currently summoned" },
        { "mountType", "Mount type ID" },
        { "source", "How to obtain: Drop, Vendor, Achievement, etc." },
        { "sourceText", "Detailed source description" },
        { "isSelfMount", "true if transforms player" },
        { "isFlying", "true if can fly" },
        { "isGround", "true if ground mount" },
        { "isAquatic", "true if underwater mount" },
    },
    format = function(self)
        local prefix = ""
        if self.isActive then
            prefix = Colorize("*", "accent.success") .. " "
        elseif self.isFavorite then
            prefix = Colorize("*", "accent.warning") .. " "
        end

        local icon = self.icon and (C3.Formatter:Icon(self.icon) .. " ") or ""
        local name = self.isUsable and self.name or Colorize(self.name, "status.dim")

        return string.format("%s%s%s", prefix, icon, name)
    end
})

-------------------------------------------------------------------------------
-- Mount Type Detection (for Classic compatibility)
-------------------------------------------------------------------------------

local MOUNT_TYPE_GROUND = 1
local MOUNT_TYPE_FLYING = 2
local MOUNT_TYPE_AQUATIC = 3

-- Known flying mount spell IDs (partial list for Classic detection)
local KNOWN_FLYING_MOUNTS = {
    -- TBC flying mounts
    [32235] = true, -- Golden Gryphon
    [32239] = true, -- Ebon Gryphon
    [32240] = true, -- Snowy Gryphon
    [32242] = true, -- Swift Blue Gryphon
    [32289] = true, -- Swift Red Gryphon
    [32290] = true, -- Swift Green Gryphon
    [32292] = true, -- Swift Purple Gryphon
    [32295] = true, -- Swift Green Windrider
    [32296] = true, -- Swift Yellow Windrider
    [32297] = true, -- Swift Purple Windrider
    [39798] = true, -- Green Riding Nether Ray
    [39800] = true, -- Red Riding Nether Ray
    [39801] = true, -- Purple Riding Nether Ray
    [39802] = true, -- Silver Riding Nether Ray
    [39803] = true, -- Blue Riding Nether Ray
    -- Wrath flying mounts
    [59567] = true, -- Azure Drake
    [59568] = true, -- Blue Drake
    [59569] = true, -- Bronze Drake
    [59570] = true, -- Red Drake
    [59571] = true, -- Twilight Drake
    [59650] = true, -- Black Drake
    [60021] = true, -- Plagued Proto-Drake
    [60024] = true, -- Violet Proto-Drake
    [60025] = true, -- Albino Drake
}

-- Known aquatic mount spell IDs
local KNOWN_AQUATIC_MOUNTS = {
    [64731] = true, -- Sea Turtle
    [98718] = true, -- Subdued Seahorse
    [228919] = true, -- Fathom Dweller
}

-- Detect mount type from spell ID
local function detectMountType(spellID, mountTypeID)
    -- If we have a retail mountTypeID, use it
    if mountTypeID then
        -- Mount type IDs from retail:
        -- 230 = Ground
        -- 248 = Flying
        -- 254 = Underwater
        if mountTypeID == 254 then
            return true, false, true -- isGround, isFlying, isAquatic
        elseif mountTypeID == 248 or mountTypeID == 247 or mountTypeID == 402 then
            return true, true, false -- Flying mounts can also ground mount
        else
            return true, false, false -- Ground only
        end
    end

    -- Classic fallback: check known lists
    if KNOWN_FLYING_MOUNTS[spellID] then
        return true, true, false
    elseif KNOWN_AQUATIC_MOUNTS[spellID] then
        return true, false, true
    else
        return true, false, false
    end
end

-------------------------------------------------------------------------------
-- Mounts Intellisense Provider
-------------------------------------------------------------------------------

local MAX_SUGGESTIONS = 10

C3.Intellisense:RegisterProvider("Mounts", function(partial, ctx)
    local results = {}

    -- Only show special commands when user types a matching prefix
    local partialLower = partial and partial:lower() or ""

    -- Check if "match" matches the partial (for mount match feature)
    if partialLower ~= "" and ("match"):find(partialLower, 1, true) == 1 then
        -- Get target mount info if available
        local targetMount, targetErr = C3.GetTargetMount and C3.GetTargetMount()
        if targetMount then
            local desc = string.format("Use %s's mount", targetMount.targetName)
            if not targetMount.isCollected then
                desc = desc .. " " .. Colorize("(not collected)", "accent.error")
            elseif not targetMount.canUse then
                desc = desc .. " " .. Colorize("(unusable here)", "accent.warning")
            end
            results[#results + 1] = {
                text = "match",
                description = desc,
                icon = targetMount.icon,
                priority = 100,  -- Show at top when typing "m"
            }
        else
            -- Show "match" option even without target, with helpful text
            results[#results + 1] = {
                text = "match",
                description = targetErr or "Match target's mount",
                priority = 100,
            }
        end
    end

    -- Check if "random" matches the partial
    if partialLower ~= "" and ("random"):find(partialLower, 1, true) == 1 then
        results[#results + 1] = {
            text = "random",
            description = "Summon random favorite mount",
            priority = 90,
        }
    end

    -- Use shared Lookup service for actual mounts
    local mounts = C3.Lookup:FindMounts(partial, MAX_SUGGESTIONS)

    for _, mount in ipairs(mounts) do
        local desc
        if mount.isDragonriding then
            desc = "Skyriding"
        elseif mount.isFlying then
            desc = "Flying"
        elseif mount.isAquatic then
            desc = "Aquatic"
        else
            desc = "Ground"
        end
        if mount.isFavorite then
            desc = desc .. " *"
        end
        results[#results + 1] = {
            text = mount.name,
            description = desc,
            icon = mount.icon,
        }
    end
    return results
end)

-------------------------------------------------------------------------------
-- mounts - List mount collection
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "mounts",
    description = "List available mounts",
    category = "data",
    producesType = "Mount",
    acceptsInput = false,
    flags = {
        all = { short = "a", description = "Show all mounts (including uncollected)" },
        usable = { short = "u", description = "Show only usable mounts" },
        favorites = { short = "f", description = "Show only favorite mounts" },
        flying = { description = "Show only flying mounts" },
        ground = { description = "Show only ground mounts" },
        grid = { short = "g", description = "Show as icon grid" },
    },
    execute = function(ctx)
        local showAll = ctx:HasFlag("all") or ctx:HasFlag("a")
        local usableOnly = ctx:HasFlag("usable") or ctx:HasFlag("u")
        local favoritesOnly = ctx:HasFlag("favorites") or ctx:HasFlag("f")
        local flyingOnly = ctx:HasFlag("flying")
        local groundOnly = ctx:HasFlag("ground")
        local showGrid = ctx:HasFlag("grid") or ctx:HasFlag("g")

        -- Collect mounts into array with yielding to prevent freezing
        local mounts = {}
        local processed = 0
        local YIELD_INTERVAL = C3.Constants.YIELD_INTERVAL

        -- Use Retail Mount Journal API
        local mountIDs = C_MountJournal.GetMountIDs()

        if mountIDs and #mountIDs > 0 then
            -- Retail path: iterate mount journal
            for _, mountID in ipairs(mountIDs) do repeat
                processed = processed + 1
                if processed % YIELD_INTERVAL == 0 then
                    coroutine.yield()
                end

                local name, spellID, icon, isActive, isUsable, sourceType,
                      isFavorite, isFactionSpecific, faction, shouldHideOnChar,
                      isCollected, mountTypeID, isSelfMount = C_MountJournal.GetMountInfoByID(mountID)

                -- Guard: Invalid mount
                if not name then break end

                -- Guard: Not collected (unless --all)
                if not showAll and not isCollected then break end

                -- Guard: Usable filter
                if usableOnly and not isUsable then break end

                -- Guard: Favorites filter
                if favoritesOnly and not isFavorite then break end

                local isGround, isFlying, isAquatic = detectMountType(spellID, mountTypeID)

                -- Guard: Flying filter
                if flyingOnly and not isFlying then break end

                -- Guard: Ground filter
                if groundOnly and isFlying then break end

                -- All guards passed - add result
                local creatureDisplayID, description, source, isSelfMount2,
                      mountTypeID2, uiModelSceneID, animID, spellVisualKitID,
                      disablePlayerMountPreview = C_MountJournal.GetMountInfoExtraByID(mountID)

                mounts[#mounts + 1] = {
                    _type = "Mount",
                    id = mountID,
                    mountId = mountID,
                    name = name,
                    spellID = spellID,
                    icon = icon,
                    isUsable = isUsable,
                    isCollected = isCollected,
                    isFavorite = isFavorite,
                    isActive = isActive,
                    mountType = mountTypeID,
                    source = sourceType,
                    sourceText = source or "",
                    isSelfMount = isSelfMount or false,
                    isFlying = isFlying,
                    isGround = isGround,
                    isAquatic = isAquatic,
                    command = "mount " .. name,
                }
            until true end
        else
            -- Classic path: scan spellbook for mount spells
            local bookType = "spell"
            local i = 1

            while true do
                local spellName, spellSubName = GetSpellBookItemName(i, bookType)
                if not spellName then break end

                local spellType, spellID = GetSpellBookItemInfo(i, bookType)

                if spellID then
                    local _, _, icon = GetSpellInfo(spellID)
                    local isMountSpell = false

                    local desc = GetSpellDescription(spellID)
                    if desc then
                        local descLower = desc:lower()
                        if descLower:find("mount") or descLower:find("ride") or
                           descLower:find("summon.*mount") or descLower:find("call.*mount") then
                            isMountSpell = true
                        end
                    end

                    if KNOWN_FLYING_MOUNTS[spellID] or KNOWN_AQUATIC_MOUNTS[spellID] then
                        isMountSpell = true
                    end

                    if isMountSpell then
                        local isGround, isFlying, isAquatic = detectMountType(spellID, nil)

                        local include = true
                        if flyingOnly and not isFlying then include = false end
                        if groundOnly and isFlying then include = false end

                        if include then
                            mounts[#mounts + 1] = {
                                _type = "Mount",
                                id = spellID,
                                mountId = spellID,
                                name = spellName,
                                spellID = spellID,
                                icon = icon,
                                isUsable = true,
                                isCollected = true,
                                isFavorite = false,
                                isActive = false,
                                mountType = isFlying and MOUNT_TYPE_FLYING or MOUNT_TYPE_GROUND,
                                source = "spellbook",
                                sourceText = "Spellbook",
                                isSelfMount = false,
                                isFlying = isFlying,
                                isGround = isGround,
                                isAquatic = isAquatic,
                                command = "mount " .. spellName,
                            }
                        end
                    end
                end

                i = i + 1
            end
        end

        -- If grid mode, return as IconGrid component
        if showGrid then
            return ctx:IconGrid(mounts)
        end

        -- Return array directly (already built with yielding)
        return mounts
    end
})

-------------------------------------------------------------------------------
-- mount - Summon a mount
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "mount",
    description = "Summon a mount by name",
    category = "action",
    acceptsInput = false,
    args = {
        { name = "name", required = false, description = "Mount name, 'random', or 'match' (target's mount)",
          provider = "Mounts" }
    },
    flags = {
        random = { short = "r", description = "Summon a random favorite mount" },
        flying = { short = "f", description = "Prefer flying mount" },
        ground = { short = "g", description = "Prefer ground mount" },
    },
    execute = function(ctx)
        -- Join all args to support multi-word mount names like "Garn Nighthowl"
        local query = table.concat(ctx:GetArgs(), " ")
        if query == "" then query = nil end
        local useRandom = ctx:HasFlag("random") or ctx:HasFlag("r") or query == "random"
        local useMatch = query and query:lower() == "match"
        local preferFlying = ctx:HasFlag("flying") or ctx:HasFlag("f")
        local preferGround = ctx:HasFlag("ground") or ctx:HasFlag("g")

        -- Dismount if already mounted (will remount with the requested mount)
        if IsMounted() then
            Dismount()
        end

        -- Match target's mount
        if useMatch then
            local targetMount, err = GetTargetMount()
            if not targetMount then
                return ctx:Error(err or "Target is not mounted")
            end

            if not targetMount.isCollected then
                return ctx:Error(string.format("You don't have %s's mount: %s",
                    targetMount.targetName, targetMount.name))
            end

            if not targetMount.canUse then
                return ctx:Error(string.format("Cannot use %s here", targetMount.name))
            end

            -- Track mount cast with progress bar (inline prefix)
            if C3.CastProgress and targetMount.spellID then
                local terminal = C3.Terminal
                local prefix = Colorize("Summoning:", "status.dim") .. " " .. Colorize(targetMount.name, "accent.primary") .. " " .. Colorize("(matching " .. targetMount.targetName .. ")", "status.dim")
                local completeMsg = Colorize("Summoned:", "accent.success") .. " " .. Colorize(targetMount.name, "accent.primary")

                C3.CastProgress:StartTracking(targetMount.name, targetMount.spellID, terminal, completeMsg, prefix)
            end

            C_MountJournal.SummonByID(targetMount.id)
            return nil  -- Progress bar handles output
        end

        -- Random mount
        if useRandom then
            if C_MountJournal and C_MountJournal.SummonByID then
                -- Retail: use built-in random
                C_MountJournal.SummonByID(0) -- 0 = random favorite
                return C3.Types.Create("Text", { content = "Summoning random mount..." })
            else
                -- Classic: pick from spellbook mounts
                return ctx:Error("Random mount not supported in Classic")
            end
        end

        -- Search for mount by name
        if not query or query == "" then
            return ctx:Error("Usage: mount <name> or mount -r for random")
        end

        -- Normalize apostrophes/quotes for matching (WoW uses curly quotes in names)
        local function normalizeForSearch(str)
            return str:lower()
                :gsub("\226\128\153", "'")  -- U+2019 curly apostrophe to straight
                :gsub("\226\128\152", "'")  -- U+2018 left single quote to straight
                :gsub("\226\128\156", '"')  -- U+201C left double quote
                :gsub("\226\128\157", '"')  -- U+201D right double quote
        end

        local queryNorm = normalizeForSearch(query)
        local mountIDs = C_MountJournal.GetMountIDs()

        if mountIDs and #mountIDs > 0 then
            -- Retail path with yielding to prevent freezing
            local matchedMount = nil
            local YIELD_INTERVAL = C3.Constants.YIELD_INTERVAL

            for idx, mountID in ipairs(mountIDs) do repeat
                if idx % YIELD_INTERVAL == 0 then
                    coroutine.yield()
                end

                local name, spellID, icon, isActive, isUsable, sourceType,
                      isFavorite, isFactionSpecific, faction, shouldHideOnChar,
                      isCollected = C_MountJournal.GetMountInfoByID(mountID)

                -- Guard: Invalid, not collected, or not usable
                if not name then break end
                if not isCollected then break end
                if not isUsable then break end

                local nameNorm = normalizeForSearch(name)

                -- Strict: exact match only (case-insensitive)
                if nameNorm == queryNorm then
                    matchedMount = { id = mountID, name = name, spellID = spellID }
                    break
                end
            until true end

            if matchedMount then
                -- Track mount cast with progress bar (inline prefix)
                if C3.CastProgress and matchedMount.spellID then
                    local terminal = C3.Terminal
                    local prefix = Colorize("Summoning:", "status.dim") .. " " .. Colorize(matchedMount.name, "accent.primary")
                    local completeMsg = Colorize("Summoned:", "accent.success") .. " " .. Colorize(matchedMount.name, "accent.primary")

                    C3.CastProgress:StartTracking(matchedMount.name, matchedMount.spellID, terminal, completeMsg, prefix)
                end

                C_MountJournal.SummonByID(matchedMount.id)
                return nil  -- Progress bar handles output
            else
                return ctx:Error("Mount not found: " .. query)
            end
        else
            -- Classic path: search spellbook
            local bookType = "spell"
            local i = 1

            while true do
                local spellName = GetSpellBookItemName(i, bookType)
                if not spellName then break end

                if normalizeForSearch(spellName) == queryNorm then
                    -- Check if it's a mount spell
                    local spellType, spellID = GetSpellBookItemInfo(i, bookType)
                    if spellID then
                        local desc = GetSpellDescription(spellID)
                        if desc and (desc:lower():find("mount") or desc:lower():find("ride")) then
                            -- Use SecureAction for safe execution
                            local ok, err = C3.SecureAction:SummonMount(spellID, true)
                            if ok then
                                return C3.Types.Create("Text", {
                                    content = string.format("Summoning: %s", spellName)
                                })
                            else
                                return ctx:Error(err or "Cannot summon mount")
                            end
                        end
                    end
                end

                i = i + 1
            end

            return ctx:Error("Mount not found: " .. query)
        end
    end
})

-------------------------------------------------------------------------------
-- dismount - Dismount from current mount
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "dismount",
    description = "Dismount from current mount",
    category = "action",
    acceptsInput = false,
    execute = function(ctx)
        if IsMounted() then
            Dismount()
            return C3.Types.Create("Text", { content = "Dismounted" })
        else
            return C3.Types.Create("Text", { content = "Not mounted" })
        end
    end
})

return true
