--[[
    Commander 3.0 - Talents Data Source

    Provides access to player talent configuration.
    Commands: talents, spec, specs, build, builds

    Supports Retail (Hero Talents, Class Talents)
]]

local ADDON_NAME, C3 = ...

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

-------------------------------------------------------------------------------
-- Talent Type Registration
-------------------------------------------------------------------------------

C3.Types:Register("Talent", {
    description = "Talent from talent tree",
    source = "talents",
    properties = {
        { "id", "Talent/Entry ID" },
        { "name", "Talent name" },
        { "icon", "Icon texture ID" },
        { "rank", "Current rank invested" },
        { "maxRank", "Maximum rank available" },
        { "nodeID", "Node ID in talent tree" },
        { "isSelected", "true if talent is selected" },
        { "isAvailable", "true if can be selected" },
        { "type", "Talent type" },
    },
})

C3.Formatter:Register("Talent", {
    format = function(obj)
        local icon = obj.icon and (C3.Formatter:Icon(obj.icon) .. " ") or ""
        local name = obj.name or "Unknown Talent"

        if obj.isSelected then
            name = Colorize(name, "accent.success")
        else
            name = Colorize(name, "status.dim")
        end

        local rankStr = ""
        if obj.maxRank and obj.maxRank > 1 then
            rankStr = string.format(" |cffffd700(%d/%d)|r", obj.rank or 0, obj.maxRank)
        end

        return icon .. name .. rankStr
    end,
    list = function(obj)
        local icon = obj.icon and (C3.Formatter:Icon(obj.icon) .. " ") or ""
        local status = obj.isSelected and Colorize("+", "accent.success") or Colorize("-", "status.dim")
        return string.format("%s %s%s", status, icon, obj.name or "?")
    end,
    compact = function(obj)
        return obj.name or "?"
    end
})

-------------------------------------------------------------------------------
-- Build Type Registration
-------------------------------------------------------------------------------

C3.Types:Register("Build", {
    description = "Talent loadout/build",
    source = "builds",
    properties = {
        { "id", "Config ID" },
        { "name", "Build name" },
        { "specId", "Specialization ID" },
        { "specName", "Specialization name" },
        { "specIndex", "Spec index (1-4)" },
        { "specIcon", "Spec icon" },
        { "isActive", "true if currently active build" },
        { "isActiveSpec", "true if this is the active spec" },
    },
})

C3.Formatter:Register("Build", {
    format = function(obj)
        local specIcon = obj.specIcon and (C3.Formatter:Icon(obj.specIcon) .. " ") or ""
        local name = obj.name or "Unnamed Build"

        if obj.isActive then
            name = Colorize(name, "accent.success") .. " " .. Colorize("[Active]", "status.gold")
        elseif not obj.isActiveSpec then
            name = Colorize(name, "status.dim")
        end

        local specLabel = obj.specName and (" - " .. Colorize(obj.specName, "accent.primary")) or ""

        return specIcon .. name .. specLabel
    end,
})

-------------------------------------------------------------------------------
-- Spec Type Registration
-------------------------------------------------------------------------------

C3.Types:Register("Spec", {
    description = "Specialization",
    source = "specs",
    properties = {
        { "id", "Spec ID number" },
        { "name", "Spec name" },
        { "icon", "Icon texture ID" },
        { "role", "Role: TANK, HEALER, DAMAGER" },
        { "isActive", "true if current spec" },
        { "index", "Spec index (1-4)" },
    },
})

C3.Formatter:Register("Spec", {
    format = function(obj)
        local icon = obj.icon and (C3.Formatter:Icon(obj.icon) .. " ") or ""
        local name = obj.name or "Unknown Spec"

        if obj.isActive then
            name = Colorize(name, "accent.success") .. " |cffffd700[Active]|r"
        end

        local roleColors = {
            TANK = "5599FF",
            HEALER = "00FF00",
            DAMAGER = "FF5555",
        }
        local roleStr = ""
        if obj.role then
            local color = roleColors[obj.role] or "FFFFFF"
            roleStr = string.format(" |cff%s(%s)|r", color, obj.role)
        end

        return icon .. name .. roleStr
    end,
})

-------------------------------------------------------------------------------
-- Specs Intellisense Provider
-------------------------------------------------------------------------------

local MAX_SUGGESTIONS = C3.Constants.MAX_SUGGESTIONS

C3.Intellisense:RegisterProvider("Specs", function(partial, ctx)
    if not GetNumSpecializations then
        return {}
    end

    local numSpecs = GetNumSpecializations()
    local activeSpec = GetSpecialization()

    -- Collect specs
    local specs = {}
    for i = 1, numSpecs do
        local id, name, description, icon, role = GetSpecializationInfo(i)
        if name and fuzzyMatch(partial, name) then
            specs[#specs + 1] = {
                name = name,
                role = role,
                icon = icon,
                isActive = (i == activeSpec),
            }
        end
    end

    -- Sort: active spec goes to bottom
    table.sort(specs, function(a, b)
        if a.isActive ~= b.isActive then
            return not a.isActive  -- non-active first
        end
        return a.name < b.name
    end)

    local results = {}
    for _, spec in ipairs(specs) do
        local desc = spec.role or "Spec"
        if spec.isActive then
            desc = desc .. " " .. Colorize("[Active]", "accent.success")
        end
        results[#results + 1] = {
            text = spec.name,
            description = desc,
            icon = spec.icon,
        }
    end
    return results
end)

-------------------------------------------------------------------------------
-- Builds Intellisense Provider
-------------------------------------------------------------------------------

C3.Intellisense:RegisterProvider("Builds", function(partial, ctx)
    if not C_ClassTalents or not C_ClassTalents.GetConfigIDsBySpecID then return {} end

    local results = {}
    local numSpecs = GetNumSpecializations()
    local activeSpecIndex = GetSpecialization()

    for specIdx = 1, numSpecs do
        local specID, specName, _, specIcon = GetSpecializationInfo(specIdx)
        local configIDs = C_ClassTalents.GetConfigIDsBySpecID(specID) or {}
        local activeConfigID = C_ClassTalents.GetLastSelectedSavedConfigID(specID)
        local isActiveSpec = (specIdx == activeSpecIndex)

        for _, configID in ipairs(configIDs) do repeat
            local configInfo = C_Traits.GetConfigInfo(configID)
            if not configInfo or not configInfo.name then break end

            local buildName = configInfo.name
            if not fuzzyMatch(partial, buildName) and not fuzzyMatch(partial, specName) then break end

            local isActive = isActiveSpec and (configID == activeConfigID)

            -- Just use build name - icon distinguishes spec
            local text = buildName

            -- Description shows status
            local desc
            if isActive then
                desc = Colorize("[Current Build]", "accent.success")
            elseif not isActiveSpec then
                desc = Colorize("(switch spec)", "status.dim")
            else
                desc = ""
            end

            -- Priority: non-current active spec builds highest, current build next, other specs lowest
            local priority = 0
            if isActiveSpec then
                priority = isActive and 50 or 100
            end

            results[#results + 1] = {
                text = text,
                description = desc,
                icon = specIcon,
                priority = priority,
                specName = specName,
            }
        until true end
    end

    -- Sort: priority desc, then spec name, then build name
    table.sort(results, function(a, b)
        if a.priority ~= b.priority then return a.priority > b.priority end
        if a.specName ~= b.specName then return a.specName < b.specName end
        return a.text < b.text
    end)

    -- Limit results
    local limited = {}
    for i = 1, math.min(#results, MAX_SUGGESTIONS) do
        limited[i] = results[i]
    end
    return limited
end)

-------------------------------------------------------------------------------
-- specs - List specializations
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "specs",
    aliases = { "specializations" },
    description = "List available specializations",
    category = "data",
    producesType = "Spec",
    acceptsInput = false,
    execute = function(ctx)
        if not GetNumSpecializations then
            return ctx:Error("Specializations not available in this game version")
        end

        return ctx:Produce(function(emit)
            local numSpecs = GetNumSpecializations()
            local activeSpec = GetSpecialization()

            for i = 1, numSpecs do
                local id, name, description, icon, role = GetSpecializationInfo(i)

                if name then
                    emit(C3.Types.Create("Spec", {
                        id = id,
                        name = name,
                        description = description,
                        icon = icon,
                        role = role,
                        isActive = (i == activeSpec),
                        index = i,
                    }))
                end
            end
        end)
    end
})

-------------------------------------------------------------------------------
-- spec - Show or switch spec
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "spec",
    description = "Show current spec or switch to another",
    category = "data",
    acceptsInput = false,
    args = {
        { name = "spec", required = false, description = "Spec number or name to switch to",
          provider = "Specs" }
    },
    execute = function(ctx)
        if not GetSpecialization then
            return ctx:Error("Specializations not available in this game version")
        end

        local query = table.concat(ctx:GetArgs(), " ")

        if query == "" then
            -- Show current spec
            local currentSpec = GetSpecialization()
            if currentSpec then
                local id, name, desc, icon, role = GetSpecializationInfo(currentSpec)
                return C3.Types.Create("Spec", {
                    id = id,
                    name = name,
                    description = desc,
                    icon = icon,
                    role = role,
                    isActive = true,
                    index = currentSpec,
                })
            else
                return ctx:Error("No specialization selected")
            end
        end

        -- Try to switch spec
        if InCombatLockdown() then
            return ctx:Error("Cannot switch spec during combat")
        end

        local specIndex = tonumber(query)
        if not specIndex then
            -- Search by name
            local queryLower = query:lower()
            local numSpecs = GetNumSpecializations()
            for i = 1, numSpecs do
                local _, name = GetSpecializationInfo(i)
                if name and name:lower() == queryLower then
                    specIndex = i
                    break
                end
            end
        end

        if not specIndex or specIndex < 1 or specIndex > GetNumSpecializations() then
            return ctx:Error("Spec not found: " .. query)
        end

        local _, name = GetSpecializationInfo(specIndex)

        -- Track spec switch with progress bar (inline prefix)
        local CHANGE_SPEC_SPELL_ID = 200749
        if C3.CastProgress then
            local terminal = C3.Terminal
            local prefix = Colorize("Switching to:", "status.dim") .. " " .. name
            local completeMsg = Colorize("Switched to:", "accent.success") .. " " .. name

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

        -- Use correct API for spec switching
        if C_SpecializationInfo and C_SpecializationInfo.SetSpecialization then
            C_SpecializationInfo.SetSpecialization(specIndex)
        elseif SetSpecialization then
            SetSpecialization(specIndex)
        else
            return ctx:Error("Spec switching not available")
        end

        return nil  -- Progress bar handles output
    end
})

-------------------------------------------------------------------------------
-- talents - List current talent configuration
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "talents",
    description = "List current talents",
    category = "data",
    producesType = "Talent",
    acceptsInput = false,
    flags = {
        all = { short = "a", description = "Show all talents (including unselected)" },
        selected = { short = "s", description = "Show only selected talents" },
    },
    execute = function(ctx)
        local showAll = ctx:HasFlag("all") or ctx:HasFlag("a")
        local selectedOnly = ctx:HasFlag("selected") or ctx:HasFlag("s")

        -- Check for modern talent API (Dragonflight+)
        if not C_ClassTalents then
            return ctx:Error("Talent system not available")
        end

        return ctx:Produce(function(emit)
            local configID = C_ClassTalents.GetActiveConfigID()
            if not configID then
                return
            end

            local configInfo = C_Traits.GetConfigInfo(configID)
            if not configInfo or not configInfo.treeIDs then
                return
            end

            -- Get tree ID from configInfo.treeIDs (it's a table)
            local treeID = nil
            for _, id in pairs(configInfo.treeIDs) do
                treeID = id
                break
            end

            if not treeID then
                return
            end

            local nodes = C_Traits.GetTreeNodes(treeID)
            if not nodes then
                return
            end

            for _, nodeID in ipairs(nodes) do
                local nodeInfo = C_Traits.GetNodeInfo(configID, nodeID)

                if nodeInfo and nodeInfo.entryIDs then
                    for _, entryID in ipairs(nodeInfo.entryIDs) do
                        local entryInfo = C_Traits.GetEntryInfo(configID, entryID)

                        if entryInfo and entryInfo.definitionID then
                            local defInfo = C_Traits.GetDefinitionInfo(entryInfo.definitionID)

                            if defInfo then
                                local isSelected = nodeInfo.activeEntry and
                                                  nodeInfo.activeEntry.entryID == entryID and
                                                  nodeInfo.activeEntry.rank > 0

                                -- Apply filters
                                local include = true
                                if selectedOnly and not isSelected then
                                    include = false
                                end
                                if not showAll and not isSelected then
                                    include = false
                                end

                                if include then
                                    local spellID = defInfo.spellID
                                    local name = defInfo.overrideName
                                    local icon = defInfo.overrideIcon

                                    -- Get spell info if we have spellID
                                    if spellID and C_Spell and C_Spell.GetSpellInfo then
                                        local spellInfo = C_Spell.GetSpellInfo(spellID)
                                        if spellInfo then
                                            name = name or spellInfo.name
                                            icon = icon or spellInfo.iconID
                                        end
                                    end

                                    if name then
                                        emit(C3.Types.Create("Talent", {
                                            id = entryID,
                                            nodeID = nodeID,
                                            name = name,
                                            icon = icon,
                                            rank = nodeInfo.activeEntry and nodeInfo.activeEntry.rank or 0,
                                            maxRank = nodeInfo.maxRanks or 1,
                                            isSelected = isSelected,
                                            isAvailable = nodeInfo.meetsEdgeRequirements,
                                            type = nodeInfo.type,
                                        }))
                                    end
                                end
                            end
                        end
                    end
                end
            end
        end)
    end
})

-------------------------------------------------------------------------------
-- builds - List all talent builds across all specs
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "builds",
    aliases = { "loadouts" },
    description = "List all talent builds across all specs",
    category = "data",
    producesType = "Build",
    acceptsInput = false,
    execute = function(ctx)
        if not C_ClassTalents or not C_ClassTalents.GetConfigIDsBySpecID then
            return ctx:Error("Talent loadouts not available")
        end

        return ctx:Produce(function(emit)
            local numSpecs = GetNumSpecializations()
            local activeSpecIndex = GetSpecialization()

            for specIdx = 1, numSpecs do
                local specID, specName, _, specIcon = GetSpecializationInfo(specIdx)
                local configIDs = C_ClassTalents.GetConfigIDsBySpecID(specID) or {}
                local activeConfigID = C_ClassTalents.GetLastSelectedSavedConfigID(specID)
                local isActiveSpec = (specIdx == activeSpecIndex)

                for _, configID in ipairs(configIDs) do repeat
                    local configInfo = C_Traits.GetConfigInfo(configID)
                    if not configInfo or not configInfo.name then break end

                    emit(C3.Types.Create("Build", {
                        id = configID,
                        name = configInfo.name,
                        specId = specID,
                        specName = specName,
                        specIndex = specIdx,
                        specIcon = specIcon,
                        isActive = isActiveSpec and (configID == activeConfigID),
                        isActiveSpec = isActiveSpec,
                    }))
                until true end
            end
        end)
    end
})

-------------------------------------------------------------------------------
-- build - Show current build or switch to another
-------------------------------------------------------------------------------

-- Pending build to apply after spec change
local pendingBuild = nil

-- Pending config update after LoadInProgress
local pendingConfigUpdate = nil

-- Helper: Get the talent frame if available
local function GetTalentFrame()
    return PlayerSpellsFrame and PlayerSpellsFrame.TalentsFrame
end

-- Helper: Update the selected loadout tracking and refresh UI
-- Blizzard's API doesn't fire SELECTED_LOADOUT_CHANGED when we call UpdateLastSelectedSavedConfigID,
-- so we need to manually update the dropdown and regenerate its menu for the checkmark to appear.
local function UpdateSelectedLoadout(configID)
    local specID = PlayerUtil.GetCurrentSpecID()
    C_ClassTalents.UpdateLastSelectedSavedConfigID(specID, configID)

    -- Update dropdown if talent frame exists
    local talentsTab = GetTalentFrame()
    if not talentsTab then return end

    local dropdown = talentsTab.LoadSystem
    if not dropdown then return end

    pcall(function()
        -- SetSelectionID updates the collapsed button display
        if dropdown.SetSelectionID then
            dropdown:SetSelectionID(configID)
        end
        -- GenerateMenu regenerates the dropdown items with correct checkmarks
        if dropdown.GenerateMenu then
            dropdown:GenerateMenu()
        end
    end)
end

-- Helper: Find a build by name across all specs
-- Searches non-active specs first (user likely wants to switch)
local function FindBuildByName(query)
    local queryLower = query:lower()
    local numSpecs = GetNumSpecializations()
    local activeSpecIndex = GetSpecialization()

    local function searchSpec(specIdx)
        local specID, specName, _, specIcon = GetSpecializationInfo(specIdx)
        local configIDs = C_ClassTalents.GetConfigIDsBySpecID(specID) or {}

        for _, configID in ipairs(configIDs) do
            local configInfo = C_Traits.GetConfigInfo(configID)
            if not configInfo or not configInfo.name then
                -- continue
            elseif configInfo.name:lower() == queryLower then
                return {
                    configID = configID,
                    name = configInfo.name,
                    specID = specID,
                    specName = specName,
                    specIndex = specIdx,
                    specIcon = specIcon,
                    isActiveSpec = (specIdx == activeSpecIndex),
                }
            end
        end
        return nil
    end

    -- Search non-active specs first, then active
    for specIdx = 1, numSpecs do
        if specIdx ~= activeSpecIndex then
            local result = searchSpec(specIdx)
            if result then return result end
        end
    end
    return searchSpec(activeSpecIndex)
end

-- Helper: Finalize a pending config update (called when SELECTED_LOADOUT_CHANGED fires)
local function FinalizePendingConfigUpdate()
    if not pendingConfigUpdate then return end

    local pending = pendingConfigUpdate
    pendingConfigUpdate = nil

    UpdateSelectedLoadout(pending.configID)
    if not pending.silent and C3.Terminal then
        C3.Terminal:Print(Colorize("Applied build:", "accent.success") .. " " .. pending.name)
    end
end

-- Helper: Apply a build and print result
-- LoadConfigResult: 0=Error, 1=NoChangesNecessary, 2=LoadInProgress, 3=Ready
-- Returns: success, error, isPending (isPending=true means cast in progress, don't output yet)
local function ApplyBuild(build, silent)
    -- Check if already on this config
    local currentConfigID = C_ClassTalents.GetLastSelectedSavedConfigID(PlayerUtil.GetCurrentSpecID())
    if currentConfigID == build.configID then
        if not silent and C3.Terminal then
            C3.Terminal:Print(Colorize("Already using build:", "accent.success") .. " " .. build.name)
        end
        return true, nil, false
    end

    local result, err = C_ClassTalents.LoadConfig(build.configID, true)

    if result == Enum.LoadConfigResult.Ready or result == Enum.LoadConfigResult.NoChangesNecessary then
        -- Immediate success - update tracking now
        UpdateSelectedLoadout(build.configID)
        if not silent and C3.Terminal then
            C3.Terminal:Print(Colorize("Applied build:", "accent.success") .. " " .. build.name)
        end
        return true, nil, false

    elseif result == Enum.LoadConfigResult.LoadInProgress then
        -- Async load - involves a cast in non-rested areas
        -- Set up pending update, events will handle completion
        pendingConfigUpdate = { configID = build.configID, name = build.name, silent = silent }

        -- Show progress bar if casting starts
        -- (handled in UNIT_SPELLCAST_START event)

        return true, nil, true  -- isPending=true, caller should not output

    else
        -- Error
        if not silent and C3.Terminal then
            C3.Terminal:Print(Colorize("Failed to apply build:", "accent.error") .. " " .. (err or "unknown"))
        end
        return false, err, false
    end
end

-- Track if we're waiting for spec to become fully active
local pendingBuildAfterSpecChange = nil

local function getPlayerCastingInfo()
    local name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellID = UnitCastingInfo("player")
    if C3.Secrets and C3.Secrets.IsSecret and C3.Secrets.IsSecret(name) then
        return nil
    end
    return name, text, texture, startTimeMS, endTimeMS, isTradeSkill, castID, notInterruptible, spellID
end

-- Event frame for handling spec change and config updates
local buildEventFrame = CreateFrame("Frame")
buildEventFrame:RegisterEvent("PLAYER_SPECIALIZATION_CHANGED")
buildEventFrame:RegisterEvent("ACTIVE_PLAYER_SPECIALIZATION_CHANGED")
buildEventFrame:RegisterEvent("TRAIT_CONFIG_UPDATED")
buildEventFrame:RegisterEvent("CONFIG_COMMIT_FAILED")
buildEventFrame:RegisterEvent("UNIT_SPELLCAST_START")
buildEventFrame:SetScript("OnEvent", function(self, event, arg1, ...)

    if event == "PLAYER_SPECIALIZATION_CHANGED" then
        if not pendingBuild then return end
        pendingBuildAfterSpecChange = pendingBuild
        pendingBuild = nil

    elseif event == "ACTIVE_PLAYER_SPECIALIZATION_CHANGED" then
        if not pendingBuildAfterSpecChange then return end
        local build = pendingBuildAfterSpecChange
        pendingBuildAfterSpecChange = nil
        ApplyBuild(build, false)
        -- Mark that this came after a spec switch (cast may take longer to start)
        if pendingConfigUpdate then
            pendingConfigUpdate.afterSpecSwitch = true
        end

    elseif event == "TRAIT_CONFIG_UPDATED" then
        if not pendingConfigUpdate then return end
        if getPlayerCastingInfo() then return end  -- Still casting, wait

        -- If we saw a cast start, it's now done. Otherwise we're in rested area.
        if pendingConfigUpdate.trackingCast then
            FinalizePendingConfigUpdate()
            return
        end

        -- No cast tracked yet - might be early event or rested area
        -- Wait briefly for cast to potentially start
        if pendingConfigUpdate.checkedForCast then return end
        pendingConfigUpdate.checkedForCast = true

        C_Timer.After(0.1, function()
            if not pendingConfigUpdate then return end
            if getPlayerCastingInfo() then return end
            -- If cast started, wait for it to finish via next TRAIT_CONFIG_UPDATED
            if pendingConfigUpdate.trackingCast then return end
            -- After spec switch, wait longer for cast to start (then use fallback)
            if pendingConfigUpdate.afterSpecSwitch and not pendingConfigUpdate.specSwitchFallback then
                pendingConfigUpdate.specSwitchFallback = true
                C_Timer.After(1.0, function()
                    if not pendingConfigUpdate then return end
                    if getPlayerCastingInfo() then return end
                    if pendingConfigUpdate.trackingCast then return end
                    -- Still no cast after 1s, assume rested area
                    FinalizePendingConfigUpdate()
                end)
                return
            end
            FinalizePendingConfigUpdate()
        end)

    elseif event == "CONFIG_COMMIT_FAILED" then
        if not pendingConfigUpdate then return end
        local pending = pendingConfigUpdate
        pendingConfigUpdate = nil
        if not pending.silent and C3.Terminal then
            C3.Terminal:Print(Colorize("Build change cancelled", "accent.warning"))
        end

    elseif event == "UNIT_SPELLCAST_START" then
        if arg1 ~= "player" then return end
        if not pendingConfigUpdate then return end
        if pendingConfigUpdate.trackingCast then return end

        pendingConfigUpdate.trackingCast = true

        if pendingConfigUpdate.silent then return end
        if not C3.CastProgress or not C3.Terminal then return end

        local _, _, _, _, _, _, _, _, spellID = getPlayerCastingInfo()
        if not spellID then return end

        local prefix = Colorize("Applying:", "status.dim") .. " " .. pendingConfigUpdate.name
        C3.CastProgress:StartTracking(pendingConfigUpdate.name, spellID, C3.Terminal, nil, prefix)
    end
end)

C3.Command:Define({
    name = "build",
    aliases = { "loadout" },
    description = "Show current build or switch to another",
    category = "data",
    acceptsInput = false,
    args = {
        { name = "name", required = false, description = "Build name to switch to",
          provider = "Builds" }
    },
    execute = function(ctx)
        if not C_ClassTalents then return ctx:Error("Talent system not available") end

        local query = table.concat(ctx:GetArgs(), " ")

        -- No argument: show current build
        if query == "" then
            local specID = PlayerUtil.GetCurrentSpecID()
            local configID = C_ClassTalents.GetLastSelectedSavedConfigID(specID)
            if not configID then return ctx:Text(Colorize("No build selected", "status.dim")) end

            local configInfo = C_Traits.GetConfigInfo(configID)
            if not configInfo then return ctx:Text(Colorize("No build selected", "status.dim")) end

            local _, specName, _, specIcon = GetSpecializationInfo(GetSpecialization())
            return C3.Types.Create("Build", {
                id = configID,
                name = configInfo.name,
                specId = specID,
                specName = specName,
                specIcon = specIcon,
                isActive = true,
                isActiveSpec = true,
            })
        end

        -- Has argument: switch to build
        if InCombatLockdown() then return ctx:Error("Cannot switch build during combat") end

        local targetBuild = FindBuildByName(query)
        if not targetBuild then return ctx:Error("Build not found: " .. query) end

        -- Check if we need to switch spec (compare indices directly, fresh check)
        local currentSpecIndex = GetSpecialization()
        local needsSpecSwitch = (targetBuild.specIndex ~= currentSpecIndex)

        -- Same spec: apply directly
        if not needsSpecSwitch then
            local currentConfigID = C_ClassTalents.GetLastSelectedSavedConfigID(PlayerUtil.GetCurrentSpecID())
            if currentConfigID == targetBuild.configID then
                return ctx:Text(Colorize("Already using:", "status.dim") .. " " .. targetBuild.name)
            end

            local success, err, isPending = ApplyBuild(targetBuild, false)
            if success then
                -- If pending (cast in progress), return nil - events will handle output
                if isPending then
                    return nil
                end
                return ctx:Text(Colorize("Applied build:", "accent.success") .. " " .. targetBuild.name)
            end
            return ctx:Error("Failed to apply build: " .. (err or "unknown error"))
        end

        -- Different spec: check if we can switch
        if C_SpecializationInfo and C_SpecializationInfo.CanPlayerUseTalentSpecUI then
            local canUse, failureReason = C_SpecializationInfo.CanPlayerUseTalentSpecUI()
            if not canUse then
                return ctx:Error("Cannot switch spec: " .. (failureReason or "unknown reason"))
            end
        end

        -- Store pending build for after spec change
        pendingBuild = targetBuild

        -- Track spec switch with progress bar (same as spec command)
        local CHANGE_SPEC_SPELL_ID = 200749
        if C3.CastProgress then
            local prefix = Colorize("Switching to:", "status.dim") .. " " .. targetBuild.specName
            local completeMsg = Colorize("Switched to:", "accent.success") .. " " .. targetBuild.specName
            C3.CastProgress:StartTracking(targetBuild.specName, CHANGE_SPEC_SPELL_ID, C3.Terminal, completeMsg, prefix)
        end

        -- Use correct API for spec switching - check return value
        local specSwitchStarted = false
        if C_SpecializationInfo and C_SpecializationInfo.SetSpecialization then
            specSwitchStarted = C_SpecializationInfo.SetSpecialization(targetBuild.specIndex)
        elseif SetSpecialization then
            SetSpecialization(targetBuild.specIndex)
            specSwitchStarted = true  -- Legacy API doesn't return value
        else
            pendingBuild = nil
            return ctx:Error("Spec switching not available")
        end

        if not specSwitchStarted then
            pendingBuild = nil
            return ctx:Error("Failed to start spec switch to " .. targetBuild.specName)
        end

        return nil
    end
})

return true
