--[[
    Commander 3.0 - Universal Find Command

    Searches across multiple data types: items, spells, mounts, toys, pets,
    achievements, quests, macros.

    Returns typed objects for pipeline operations.
    Usage:
        find fireball              -- Search all types
        find sword --type items    -- Search only items
        find dragon -t mounts      -- Search only mounts
]]

local ADDON_NAME, C3 = ...

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

-------------------------------------------------------------------------------
-- Search Providers
-------------------------------------------------------------------------------

local MAX_RESULTS_PER_TYPE = 10
local YIELD_INTERVAL = C3.Constants.YIELD_INTERVAL

-- Alias for local use
local fuzzyMatch = fuzzyMatchWithScore

-- Search items in bags
local function searchItems(query, results, yielder)
    local count = 0
    local processed = 0

    for bag = 0, 4 do
        local numSlots = C_Container.GetContainerNumSlots(bag)
        for slot = 1, numSlots do
            processed = processed + 1
            if processed % YIELD_INTERVAL == 0 then yielder() end

            local itemInfo = C_Container.GetContainerItemInfo(bag, slot)
            if itemInfo and itemInfo.itemID then
                local name = C_Item.GetItemInfo(itemInfo.itemID)
                if name then
                    local matches, score = fuzzyMatch(query, name)
                    if matches and count < MAX_RESULTS_PER_TYPE then
                        local _, link, quality, ilvl = C_Item.GetItemInfo(itemInfo.itemID)
                        results[#results + 1] = {
                            obj = C3.Types.Create("Item", {
                                id = itemInfo.itemID,
                                name = name,
                                link = link,
                                quality = quality or 1,
                                ilvl = ilvl or 0,
                                stackCount = itemInfo.stackCount or 1,
                                bag = bag,
                                slot = slot,
                                icon = itemInfo.iconFileID,
                            }),
                            score = score,
                            source = "items",
                        }
                        count = count + 1
                    end
                end
            end
        end
    end
end

-- Search spellbook
local function searchSpells(query, results, yielder)
    local count = 0
    local processed = 0

    local numSpellTabs = C_SpellBook.GetNumSpellBookSkillLines()
    for tab = 1, numSpellTabs do
        local skillLineInfo = C_SpellBook.GetSpellBookSkillLineInfo(tab)
        if skillLineInfo then
            local offset = skillLineInfo.itemIndexOffset
            local numSpells = skillLineInfo.numSpellBookItems

            for i = offset + 1, offset + numSpells do
                processed = processed + 1
                if processed % YIELD_INTERVAL == 0 then yielder() end

                local spellInfo = C_SpellBook.GetSpellBookItemInfo(i, Enum.SpellBookSpellBank.Player)
                if spellInfo and spellInfo.spellID then
                    local info = C_Spell.GetSpellInfo(spellInfo.spellID)
                    if info and info.name then
                        local matches, score = fuzzyMatch(query, info.name)
                        if matches and count < MAX_RESULTS_PER_TYPE then
                            results[#results + 1] = {
                                obj = C3.Types.Create("Spell", {
                                    id = spellInfo.spellID,
                                    name = info.name,
                                    icon = info.iconID,
                                    castTime = info.castTime,
                                    range = info.minRange,
                                    isPassive = spellInfo.isPassive,
                                }),
                                score = score,
                                source = "spells",
                            }
                            count = count + 1
                        end
                    end
                end
            end
        end
    end
end

-- Search mounts
local function searchMounts(query, results, yielder)
    local count = 0
    local processed = 0

    local mountIDs = C_MountJournal.GetMountIDs()
    for _, mountID in ipairs(mountIDs) do
        processed = processed + 1
        if processed % YIELD_INTERVAL == 0 then yielder() end

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

        if isCollected and name then
            local matches, score = fuzzyMatch(query, name)
            if matches and count < MAX_RESULTS_PER_TYPE then
                local _, _, _, _, mountTypeID = C_MountJournal.GetMountInfoExtraByID(mountID)
                local isFlying = mountTypeID == 248 or mountTypeID == 247

                results[#results + 1] = {
                    obj = C3.Types.Create("Mount", {
                        id = mountID,
                        name = name,
                        spellID = spellID,
                        icon = icon,
                        isUsable = isUsable,
                        isFavorite = isFavorite,
                        isFlying = isFlying,
                        isCollected = isCollected,
                    }),
                    score = score,
                    source = "mounts",
                }
                count = count + 1
            end
        end
    end
end

-- Search toys
local function searchToys(query, results, yielder)
    local count = 0
    local processed = 0

    C_ToyBox.SetAllSourceTypeFilters(true)
    C_ToyBox.SetFilterCollected(true)
    C_ToyBox.SetFilterUncollected(false)

    local numToys = C_ToyBox.GetNumFilteredToys()
    for i = 1, numToys do
        processed = processed + 1
        if processed % YIELD_INTERVAL == 0 then yielder() end

        local toyID = C_ToyBox.GetToyFromIndex(i)
        if toyID then
            local itemID, toyName, icon = C_ToyBox.GetToyInfo(toyID)
            if toyName then
                local matches, score = fuzzyMatch(query, toyName)
                if matches and count < MAX_RESULTS_PER_TYPE then
                    results[#results + 1] = {
                        obj = C3.Types.Create("Toy", {
                            id = toyID,
                            itemID = itemID,
                            name = toyName,
                            icon = icon,
                        }),
                        score = score,
                        source = "toys",
                    }
                    count = count + 1
                end
            end
        end
    end
end

-- Search pets
local function searchPets(query, results, yielder)
    local count = 0
    local processed = 0

    local numPets = C_PetJournal.GetNumPets()
    for i = 1, numPets do
        processed = processed + 1
        if processed % YIELD_INTERVAL == 0 then yielder() end

        local petID, speciesID, owned, customName, level, favorite, isRevoked, speciesName, icon = C_PetJournal.GetPetInfoByIndex(i)

        if owned and speciesName then
            local displayName = customName or speciesName
            local matches, score = fuzzyMatch(query, displayName)
            if matches and count < MAX_RESULTS_PER_TYPE then
                results[#results + 1] = {
                    obj = C3.Types.Create("Pet", {
                        id = petID,
                        speciesID = speciesID,
                        name = displayName,
                        speciesName = speciesName,
                        customName = customName,
                        level = level,
                        icon = icon,
                        isFavorite = favorite,
                    }),
                    score = score,
                    source = "pets",
                }
                count = count + 1
            end
        end
    end
end

-- Search achievements
local function searchAchievements(query, results, yielder)
    local count = 0

    -- Use native search API
    SetAchievementSearchString(query)

    -- Wait a frame for results (search is async in some cases)
    yielder()

    local numResults = GetNumFilteredAchievements()
    for i = 1, math.min(numResults, MAX_RESULTS_PER_TYPE) do
        local achievementID = GetFilteredAchievementID(i)
        if achievementID then
            local id, name, points, completed, month, day, year, description, flags, icon = GetAchievementInfo(achievementID)
            if name then
                results[#results + 1] = {
                    obj = C3.Types.Create("Achievement", {
                        id = achievementID,
                        name = name,
                        description = description,
                        points = points,
                        icon = icon,
                        isComplete = completed,
                        completedDate = completed and string.format("%d/%d/%d", month, day, year) or nil,
                    }),
                    score = 80,  -- Native search already ranked
                    source = "achievements",
                }
                count = count + 1
            end
        end
    end

    -- Clear search
    ClearAchievementSearchString()
end

-- Search quests
local function searchQuests(query, results, yielder)
    local count = 0
    local processed = 0

    local numQuests = C_QuestLog.GetNumQuestLogEntries()
    for i = 1, numQuests do
        processed = processed + 1
        if processed % YIELD_INTERVAL == 0 then yielder() end

        local info = C_QuestLog.GetInfo(i)
        if info and not info.isHeader and info.title then
            local matches, score = fuzzyMatch(query, info.title)
            if matches and count < MAX_RESULTS_PER_TYPE then
                results[#results + 1] = {
                    obj = C3.Types.Create("Quest", {
                        id = info.questID,
                        name = info.title,
                        level = info.difficultyLevel,
                        isComplete = info.isComplete,
                        isFailed = info.isFailed,
                        frequency = info.frequency,
                    }),
                    score = score,
                    source = "quests",
                }
                count = count + 1
            end
        end
    end
end

-- Search macros
local function searchMacros(query, results, yielder)
    local count = 0
    local MAX_MACROS = 138

    for i = 1, MAX_MACROS do
        if i % YIELD_INTERVAL == 0 then yielder() end

        local name, iconTexture, body = GetMacroInfo(i)
        if name then
            local matches, score = fuzzyMatch(query, name)
            if matches and count < MAX_RESULTS_PER_TYPE then
                results[#results + 1] = {
                    obj = C3.Types.Create("Macro", {
                        id = i,
                        name = name,
                        icon = iconTexture,
                        body = body or "",
                        isCharacter = i > 120,
                    }),
                    score = score,
                    source = "macros",
                }
                count = count + 1
            end
        end
    end
end

-------------------------------------------------------------------------------
-- Provider Registry
-------------------------------------------------------------------------------

local SEARCH_PROVIDERS = {
    items = { name = "Items", fn = searchItems },
    spells = { name = "Spells", fn = searchSpells },
    mounts = { name = "Mounts", fn = searchMounts },
    toys = { name = "Toys", fn = searchToys },
    pets = { name = "Pets", fn = searchPets },
    achievements = { name = "Achievements", fn = searchAchievements },
    quests = { name = "Quests", fn = searchQuests },
    macros = { name = "Macros", fn = searchMacros },
}

-- Valid type names for --type flag
local VALID_TYPES = {}
for k in pairs(SEARCH_PROVIDERS) do
    VALID_TYPES[k] = true
end

-------------------------------------------------------------------------------
-- find - Universal search command
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "find",
    description = "Search across items, spells, mounts, toys, pets, achievements, quests, macros",
    category = "data",
    acceptsInput = false,
    args = {
        { name = "query", required = true, vararg = true, description = "Search query" }
    },
    flags = {
        type = { short = "t", hasValue = true, description = "Limit to type: items, spells, mounts, toys, pets, achievements, quests, macros" },
    },
    execute = function(ctx)
        -- Build query from args
        local args = ctx:GetArgs()
        local query = table.concat(args, " ")

        if not query or query == "" then
            return ctx:Error("Usage: find <query> [--type <type>]")
        end

        -- Get type filter
        local typeFilter = ctx:GetFlagValue("type") or ctx:GetFlagValue("t")
        if typeFilter then
            typeFilter = typeFilter:lower()
            if not VALID_TYPES[typeFilter] then
                local validList = {}
                for k in pairs(VALID_TYPES) do
                    validList[#validList + 1] = k
                end
                table.sort(validList)
                return ctx:Error("Invalid type: " .. typeFilter .. "\nValid types: " .. table.concat(validList, ", "))
            end
        end

        -- Collect results
        local results = {}
        local yielder = function() coroutine.yield() end

        -- Run providers
        if typeFilter then
            -- Single provider
            local provider = SEARCH_PROVIDERS[typeFilter]
            if provider then
                provider.fn(query, results, yielder)
            end
        else
            -- All providers
            for _, provider in pairs(SEARCH_PROVIDERS) do
                provider.fn(query, results, yielder)
                yielder()  -- Yield between providers
            end
        end

        -- Sort by score (highest first)
        table.sort(results, function(a, b)
            return a.score > b.score
        end)

        -- Extract just the objects (drop metadata)
        local output = {}
        for _, r in ipairs(results) do
            output[#output + 1] = r.obj
        end

        if #output == 0 then
            return ctx:Text(Colorize("No results found for:", "status.dim") .. " " .. query)
        end

        return output
    end
})

-------------------------------------------------------------------------------
-- Alias: f
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "f",
    description = "Search (alias for find)",
    category = "data",
    acceptsInput = false,
    args = {
        { name = "query", required = true, vararg = true, description = "Search query" }
    },
    flags = {
        type = { short = "t", hasValue = true, description = "Limit to type" },
    },
    execute = function(ctx)
        -- Forward to find command
        local args = ctx:GetArgs()
        local query = table.concat(args, " ")
        local typeFilter = ctx:GetFlagValue("type") or ctx:GetFlagValue("t")

        -- Build command string
        local cmd = "find " .. query
        if typeFilter then
            cmd = cmd .. " --type " .. typeFilter
        end

        -- Execute via C3
        return C3:Execute(cmd)
    end
})

return true
