--[[
    Commander 3.0 - Secure Action System

    Handles combat-safe execution of protected actions:
    - Using items
    - Casting spells
    - Targeting

    Uses SecureActionButtonTemplate with SetOverrideBindingClick pattern.
    Key insight: Clear editbox focus on Enter so binding catches key release.
]]

local ADDON_NAME, C3 = ...

-------------------------------------------------------------------------------
-- Secure Action Manager
-------------------------------------------------------------------------------

local SecureAction = {}
C3.SecureAction = SecureAction

-- Use shared utilities (lazy lookup handled by Colors module)
local Colorize = function(...) return C3.Colors.Colorize(...) end

-- State
SecureAction._button = nil          -- The secure button
SecureAction._activeAction = nil    -- Currently active action config
SecureAction._activeTrigger = nil   -- The trigger text that activated
SecureAction._activeSession = nil   -- Session that activated the action
SecureAction._activeInputCtrl = nil -- Input controller reference
SecureAction._inClickAction = false -- Prevent deactivation during click
SecureAction._inCombat = false
SecureAction._queue = {}            -- Actions queued for after combat

-------------------------------------------------------------------------------
-- PostClick Helpers
-------------------------------------------------------------------------------

---Handle adding command to history and echoing to output after secure action executes
---@param self table SecureAction instance
local function handlePendingHistory(self)
    if not self._pendingHistoryText then return end

    local text = self._pendingHistoryText
    local session = self._activeSession
    local inputCtrl = self._activeInputCtrl
    local terminal = inputCtrl and inputCtrl.terminal

    -- Add to session history (for up-arrow recall)
    if session and session.AddHistory then
        session:AddHistory(text)
    end

    -- Echo command to output display (Playername> fs)
    if terminal and terminal.output and inputCtrl.GetPromptString then
        local promptText = inputCtrl:GetPromptString()
        terminal.output:AddInput(text, promptText)
    end

    self._pendingHistoryText = nil
end

---Handle cast/item/toy progress tracking after secure action executes
---@param self table SecureAction instance
local function handleCastProgress(self)
    if not self._activeAction then return end
    if not C3.CastProgress then return end

    local terminal = self._activeInputCtrl and self._activeInputCtrl.terminal
    local trackName = self._activeAction.spellName
        or self._activeAction.itemName
        or self._activeAction.toyName
    local trackID = self._activeAction.spellID
        or self._activeAction.toyItemID

    if not trackName then return end

    -- Pass startMessage as prefix (will be shown inline with progress bar)
    local prefix = self._activeAction.startMessage
    local completeMessage = self._activeAction.completeMessage
    C3.CastProgress:StartTracking(trackName, trackID, terminal, completeMessage, prefix)
end

---Handle logout/quit progress tracking after secure action executes
---@param self table SecureAction instance
local function handleLogoutProgress(self)
    if not self._activeAction then return end
    if not self._activeAction.logoutType then return end
    if not C3.CastProgress then return end

    local terminal = self._activeInputCtrl and self._activeInputCtrl.terminal

    -- If resting, logout is instant - just show message
    if IsResting() then
        if not terminal or not terminal.output then return end

        local msg = self._activeAction.logoutType == "quit"
            and "Quitting..."
            or "Logging out..."
        local line = C3.BufferLine.Text(Colorize(msg, "status.dim"))
        terminal.output.buffer:AddLine(line)
        terminal.output:ScrollToBottom()
        return
    end

    -- Not resting, show 20-second countdown
    C3.CastProgress:StartLogoutProgress(self._activeAction.logoutType, terminal)
end

---Refocus editbox after secure action completes
---@param inputCtrl table Input controller reference
local function refocusEditBox(inputCtrl)
    if not inputCtrl then return end
    if not inputCtrl.editBox then return end

    C_Timer.After(0, function()
        if InCombatLockdown() then return end
        if not inputCtrl.editBox:IsVisible() then return end
        inputCtrl.editBox:SetFocus()
    end)
end

-------------------------------------------------------------------------------
-- Initialization
-------------------------------------------------------------------------------

function SecureAction:Init()
    -- Create the secure button
    self._button = CreateFrame("Button", "Commander3SecureButton", UIParent, "SecureActionButtonTemplate")
    self._button:RegisterForClicks("AnyUp", "AnyDown")
    self._button:Hide()

    -- PreClick: Set up cast tracking BEFORE spell starts
    -- This is critical because UNIT_SPELLCAST_START fires during the click action
    self._button:SetScript("PreClick", function()
        handleCastProgress(self)
    end)

    -- PostClick: Clean up after action executes
    self._button:SetScript("PostClick", function()
        self._inClickAction = false

        handlePendingHistory(self)
        handleLogoutProgress(self)
        refocusEditBox(self._activeInputCtrl)

        -- Clean up - preserve who continuation for WHO_LIST_UPDATE handler
        local isWhoCommand = self._activeAction and self._activeAction.whoQuery ~= nil
        self:Deactivate(not isWhoCommand)
    end)

    -- Register for combat events
    local eventFrame = CreateFrame("Frame")
    eventFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
    eventFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
    eventFrame:SetScript("OnEvent", function(_, event)
        if event == "PLAYER_REGEN_DISABLED" then
            self:_onCombatStart()
        elseif event == "PLAYER_REGEN_ENABLED" then
            self:_onCombatEnd()
        end
    end)

    C3:Debug("SecureAction: Initialized")
end

-------------------------------------------------------------------------------
-- Combat Safety
-------------------------------------------------------------------------------

function SecureAction:_onCombatStart()
    self._inCombat = true
    C3:Debug("SecureAction: Combat started")

    -- CRITICAL: Clear bindings immediately to prevent stale action execution
    -- ClearOverrideBindings is NOT a protected function - it can be called during combat
    -- Only SetOverrideBindingClick (setting new bindings) is restricted
    if self._button then
        ClearOverrideBindings(self._button)
    end

    -- Clear any pending who pipeline continuation
    if C3.Social_ClearWhoContinuation then
        C3.Social_ClearWhoContinuation()
    end

    if self._activeAction then
        -- Clear focus to prevent accidental triggers
        local inputCtrl = self._activeInputCtrl
        if inputCtrl and inputCtrl.editBox then
            inputCtrl.editBox:ClearFocus()
        end

        -- Clear Lua state
        self._activeAction = nil
        self._activeTrigger = nil
        self._activeSession = nil
        self._activeInputCtrl = nil
    end
end

function SecureAction:_onCombatEnd()
    self._inCombat = false
    C3:Debug("SecureAction: Combat ended")

    -- Bindings already cleared on combat start, no need to clear again
    -- Process queued actions
    self:_processQueue()
end

function SecureAction:InCombat()
    return self._inCombat or InCombatLockdown()
end

-------------------------------------------------------------------------------
-- Activation / Deactivation
-------------------------------------------------------------------------------

---Activate a secure action (set up binding for Enter key)
---@param trigger string The trigger text (e.g., "cast feign death")
---@param config table Action configuration with macroText or spell
---@param inputController table Reference to input controller
---@return boolean success True if activation succeeded, false if blocked by combat
function SecureAction:Activate(trigger, config, inputController)
    if InCombatLockdown() then
        return false  -- Combat blocked the activation
    end
    if self._activeAction == config then
        return true   -- Already active with this config
    end

    -- Deactivate any previous action
    -- Preserve who continuation if we're activating a who command
    local isWhoCommand = config and config.whoQuery ~= nil
    self:Deactivate(not isWhoCommand)  -- false = don't clear continuation for who commands

    -- Configure button
    if config.macroText then
        self._button:SetAttribute("type", "macro")
        self._button:SetAttribute("macrotext", config.macroText)
        self._button:SetAttribute("spell", nil)
    elseif config.spellName then
        self._button:SetAttribute("type", "spell")
        self._button:SetAttribute("spell", config.spellName)
        self._button:SetAttribute("macrotext", nil)
    elseif config.itemName then
        self._button:SetAttribute("type", "item")
        self._button:SetAttribute("item", config.itemName)
        self._button:SetAttribute("macrotext", nil)
    elseif config.unit then
        self._button:SetAttribute("type", "target")
        self._button:SetAttribute("unit", config.unit)
        self._button:SetAttribute("macrotext", nil)
    elseif config.actionSlot then
        self._button:SetAttribute("type", "action")
        self._button:SetAttribute("action", config.actionSlot)
        self._button:SetAttribute("macrotext", nil)
    end

    -- Set up override binding for Enter key
    SetOverrideBindingClick(self._button, true, "ENTER", "Commander3SecureButton")

    self._activeAction = config
    self._activeTrigger = trigger
    self._activeInputCtrl = inputController
    self._activeSession = inputController and inputController.session

    return true  -- Activation succeeded
end

---Deactivate current secure action (clear binding)
---@param clearWhoContinuation boolean|nil Whether to clear who pipeline continuation (default: true, but false after who command executes)
function SecureAction:Deactivate(clearWhoContinuation)
    if self._inClickAction then return end
    if InCombatLockdown() then return end

    if self._button then
        ClearOverrideBindings(self._button)
    end

    -- Clear who pipeline continuation unless told not to
    -- (who commands preserve continuation for WHO_LIST_UPDATE handler)
    if clearWhoContinuation ~= false then
        if C3.Social_ClearWhoContinuation then
            C3.Social_ClearWhoContinuation()
        end
    end

    self._activeAction = nil
    self._activeTrigger = nil
    self._activeSession = nil
    self._activeInputCtrl = nil
end

---Check if a secure action is currently active
---@return boolean
function SecureAction:IsActive()
    return self._activeAction ~= nil
end

---Get the current active trigger text
---@return string|nil
function SecureAction:GetActiveTrigger()
    return self._activeTrigger
end

-------------------------------------------------------------------------------
-- Input Handling
-------------------------------------------------------------------------------

---Check input and activate matching secure action
---Called from terminal's OnTextChanged
---@param inputText string Current editbox text
---@param inputController table Input controller reference
---@return boolean|nil success True if activated, false if combat blocked, nil if no action matched
function SecureAction:CheckAndActivate(inputText, inputController)
    if self._inClickAction then return nil end

    -- Use WoW's strtrim for reliable whitespace handling
    local text = strtrim(inputText)
    -- Unescape WoW's || back to single | for pipeline detection
    text = text:gsub("||", "|")

    -- Strip chain operators (&& or ;) and everything after them
    -- so the pre-armer only sees the first command in a chain
    local chainPos = text:find("%s&&%s") or text:find("%s;%s") or text:find(";$")
    if chainPos then
        text = strtrim(text:sub(1, chainPos - 1))
    end

    local textLower = text:lower()
    if not text or text == "" then
        self:Deactivate()
        return nil
    end

    -- Early combat check - return false to signal combat blocking
    local inCombat = InCombatLockdown()

    -- Check for cast command (use original case for spell name)
    local spellName = text:match("^[Cc][Aa][Ss][Tt]%s+(.+)$")
    if spellName then
        local config = self:BuildCastConfig(spellName)
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for use command (use original case for item name)
    local itemName = text:match("^[Uu][Ss][Ee]%s+(.+)$")
    if itemName then
        local config = self:BuildUseConfig(itemName)
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for target command
    local unit = textLower:match("^settarget%s+(.+)$") or textLower:match("^targetunit%s+(.+)$")
    if unit then
        local config = self:BuildTargetConfig(unit)
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for toy command (use original case for toy name)
    local toyName = text:match("^[Tt][Oo][Yy]%s+(.+)$")
    if toyName then
        local config = self:BuildToyConfig(toyName)
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for outfit command (use original case for outfit name)
    local outfitQuery = text:match("^[Oo][Uu][Tt][Ff][Ii][Tt]%s+(.+)$")
    if outfitQuery then
        local config = self:BuildOutfitConfig(outfitQuery)
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for who command with pipeline continuation
    -- Pattern: "who <query> | <continuation>"
    -- Use simpler pattern: find "who" at start, then look for "|" anywhere
    local hasWho = text:match("^[Ww][Hh][Oo]%s")
    local pipePos = hasWho and text:find("|", 1, true)  -- plain search for |

    if hasWho and pipePos then
        -- Has pipeline continuation - extract base who and continuation
        local baseWhoQuery = strtrim(text:sub(4, pipePos - 1))  -- "who " is 4 chars
        local continuation = strtrim(text:sub(pipePos + 1))

        local config = self:BuildWhoConfig(baseWhoQuery)
        if config then
            -- Store continuation for deferred execution after WHO_LIST_UPDATE
            if continuation and continuation ~= "" and C3.Social_SetWhoContinuation then
                C3.Social_SetWhoContinuation(continuation)
            end
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for simple who command (no pipe)
    local whoQuery = text:match("^[Ww][Hh][Oo]%s*(.*)$")
    if whoQuery ~= nil then  -- Match even empty query (just "who")
        -- Clear any stale continuation
        if C3.Social_ClearWhoContinuation then
            C3.Social_ClearWhoContinuation()
        end
        local config = self:BuildWhoConfig(whoQuery)
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for logout command
    if textLower == "logout" or textLower == "camp" then
        local config = {
            macroText = "/logout",
            logoutType = "logout",
        }
        if inCombat then return false end
        return self:Activate(textLower, config, inputController)
    end

    -- Check for quit command
    if textLower == "quit" then
        local config = {
            macroText = "/quit",
            logoutType = "quit",
        }
        if inCombat then return false end
        return self:Activate(textLower, config, inputController)
    end

    -- Check for flightstyle command
    if textLower == "flightstyle" or textLower == "fs" then
        local config = self:BuildFlightStyleConfig()
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for teleport command (Mage only)
    local teleportDest = text:match("^[Tt][Ee][Ll][Ee][Pp][Oo][Rr][Tt]%s+(.+)$")
        or text:match("^[Tt][Pp]%s+(.+)$")
        or text:match("^[Tt][Ee][Ll][Ee]%s+(.+)$")
    if teleportDest then
        local config = self:BuildTeleportConfig(teleportDest)
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- Check for portal command (Mage only)
    local portalDest = text:match("^[Pp][Oo][Rr][Tt][Aa][Ll]%s+(.+)$")
        or text:match("^[Pp][Oo][Rr][Tt]%s+(.+)$")
    if portalDest then
        local config = self:BuildPortalConfig(portalDest)
        if config then
            if inCombat then return false end
            return self:Activate(textLower, config, inputController)
        end
    end

    -- No match, deactivate
    self:Deactivate()
    return nil
end

---Handle Enter key down when secure action is active
---Called from terminal's OnKeyDown
---@param editbox frame The editbox frame
---@return boolean True if handled (should prevent normal Enter), false otherwise
function SecureAction:HandleEnterKeyDown(editbox)
    -- CRITICAL: Keep this function as minimal as possible to avoid taint!
    -- No print statements, no complex operations in the secure path.

    if not self._activeAction then return false end

    -- Trust that CheckAndActivate (called on every text change) keeps the action
    -- state correct. If we have an active action, it's valid for the current text.
    -- Skip string comparison to avoid whitespace normalization issues.

    -- Mark as in-click to prevent deactivation during cleanup
    self._inClickAction = true

    -- Store text for history (will be added in PostClick)
    self._pendingHistoryText = editbox:GetText()

    -- CRITICAL: Clear text and focus IMMEDIATELY
    -- This allows the override binding to catch the key release
    editbox:SetText("")
    editbox:ClearFocus()

    return true
end

-------------------------------------------------------------------------------
-- Config Builders
-------------------------------------------------------------------------------

---Build config for cast command
---@param spellName string
---@return table|nil config
function SecureAction:BuildCastConfig(spellName)
    if not spellName or spellName == "" then return nil end

    -- Strip quotes if present
    spellName = spellName:gsub('^"', ''):gsub('"$', '')
    spellName = spellName:gsub("^'", ''):gsub("'$", '')

    if spellName == "" then return nil end

    -- Use shared Lookup service
    local foundName, foundID = C3.Lookup:FindSpell(spellName)

    if foundName then
        return {
            macroText = "/cast " .. foundName,
            spellName = foundName,
            spellID = foundID,
            startMessage = Colorize("Casting:", "status.dim") .. " " .. Colorize(foundName, "accent.primary"),
            completeMessage = Colorize("Cast:", "accent.success") .. " " .. Colorize(foundName, "accent.primary"),
        }
    end

    return nil
end

---Build config for use command
---@param itemName string
---@return table|nil config
function SecureAction:BuildUseConfig(itemName)
    if not itemName or itemName == "" then return nil end

    -- Try to get item info
    local itemId = tonumber(itemName)
    local resolvedName

    if itemId then
        local itemInfo = C_Item.GetItemInfo(itemId)
        resolvedName = itemInfo and itemInfo.itemName or nil
    else
        resolvedName = itemName
    end

    if not resolvedName then return nil end

    return {
        macroText = "/use " .. resolvedName,
        itemName = resolvedName,
        startMessage = Colorize("Using:", "status.dim") .. " " .. Colorize(resolvedName, "accent.primary"),
        completeMessage = Colorize("Used:", "accent.success") .. " " .. Colorize(resolvedName, "accent.primary"),
    }
end

---Build config for target command
---@param unit string
---@return table|nil config
function SecureAction:BuildTargetConfig(unit)
    if not unit or unit == "" then return nil end
    return { unit = unit }
end

---Build config for toy command
---@param toyName string
---@return table|nil config
function SecureAction:BuildToyConfig(toyName)
    if not toyName or toyName == "" then return nil end
    if not C_ToyBox then return nil end

    -- Use shared Lookup service
    local toy = C3.Lookup:FindToy(toyName)

    if not toy then
        return nil
    end

    -- Toys are used via /use with the toy name
    return {
        macroText = "/use " .. toy.name,
        toyName = toy.name,
        toyItemID = toy.itemID,
        startMessage = Colorize("Using:", "status.dim") .. " " .. Colorize(toy.name, "accent.primary"),
        completeMessage = Colorize("Used:", "accent.success") .. " " .. Colorize(toy.name, "accent.primary"),
    }
end

---Build config for outfit command
---Scans action bars for the outfit and uses /click to activate it
---@param outfitQuery string
---@return table|nil config
function SecureAction:BuildOutfitConfig(outfitQuery)
    if not outfitQuery or outfitQuery == "" then return nil end
    if not C3.Transmog then return nil end

    -- Strip quotes if present
    outfitQuery = outfitQuery:gsub('^"', ''):gsub('"$', '')
    outfitQuery = outfitQuery:gsub("^'", ''):gsub("'$", '')
    if outfitQuery == "" then return nil end

    local outfitID, outfitName = C3.Transmog.FindOutfitByName(outfitQuery)
    if not outfitID then return nil end

    local slotID = C3.Transmog.FindOutfitOnActionBar(outfitID)
    if not slotID then return nil end

    return {
        actionSlot = slotID,
        startMessage = Colorize("Applying:", "status.dim") .. " " .. Colorize(outfitName, "accent.primary"),
        completeMessage = Colorize("Applied:", "accent.success") .. " " .. Colorize(outfitName, "accent.primary"),
    }
end

---Get the current zone name suitable for /who queries
---@return string Zone name
local function getCurrentZoneName()
    -- Walk up the map hierarchy to find Zone-level map (not subzone/floor)
    local mapID = C_Map.GetBestMapForUnit("player")
    while mapID do
        local mapInfo = C_Map.GetMapInfo(mapID)
        if mapInfo then
            -- mapType 3 = Zone (not Dungeon, Micro, etc.)
            if mapInfo.mapType == Enum.UIMapType.Zone then
                return mapInfo.name
            end
            -- Walk up to parent map
            mapID = mapInfo.parentMapID
        else
            break
        end
    end
    -- Fallback to zone text APIs
    return GetZoneText() or GetRealZoneText() or ""
end

---Build config for who command
---@param query string
---@return table config
function SecureAction:BuildWhoConfig(query)
    -- Process query shortcuts (class names, zones, level ranges)
    local processedQuery = query or ""

    -- Normalize: remove spaces around comparison operators
    processedQuery = processedQuery:gsub("<%s+", "<"):gsub(">%s+", ">")
    processedQuery = processedQuery:gsub("<=%s+", "<="):gsub(">=%s+", ">=")

    -- Class shortcuts
    local CLASS_SHORTCUTS = {
        warrior = "Warrior", war = "Warrior", warr = "Warrior",
        paladin = "Paladin", pal = "Paladin", pala = "Paladin",
        hunter = "Hunter", hunt = "Hunter",
        rogue = "Rogue", rog = "Rogue",
        priest = "Priest", pri = "Priest",
        dk = "Death Knight", deathknight = "Death Knight",
        shaman = "Shaman", sham = "Shaman",
        mage = "Mage",
        warlock = "Warlock", lock = "Warlock",
        monk = "Monk",
        druid = "Druid", dru = "Druid",
        dh = "Demon Hunter", demonhunter = "Demon Hunter",
        evoker = "Evoker", evok = "Evoker",
    }

    -- Zone shortcuts
    local ZONE_SHORTCUTS = {
        org = "Orgrimmar",
        sw = "Stormwind",
        stormwind = "Stormwind",
        dal = "Dalaran",
        valdrakken = "Valdrakken",
        vald = "Valdrakken",
        dorn = "Dornogal",
        dornogal = "Dornogal",
        ["if"] = "Ironforge",
        ironforge = "Ironforge",
        tb = "Thunder Bluff",
        uc = "Undercity",
    }

    -- Process each word
    local parts = {}
    for word in processedQuery:gmatch("%S+") do
        local lower = word:lower()

        -- "." means current zone
        if word == "." then
            table.insert(parts, 'z-"' .. getCurrentZoneName() .. '"')
        elseif CLASS_SHORTCUTS[lower] then
            table.insert(parts, 'c-"' .. CLASS_SHORTCUTS[lower] .. '"')
        elseif ZONE_SHORTCUTS[lower] then
            table.insert(parts, 'z-"' .. ZONE_SHORTCUTS[lower] .. '"')
        elseif word:match("^%+%-%d+$") then
            local range = tonumber(word:match("%d+"))
            local playerLevel = UnitLevel("player")
            local maxLevel = GetMaxPlayerLevel and GetMaxPlayerLevel() or 80
            table.insert(parts, string.format("%d-%d",
                math.max(1, playerLevel - range),
                math.min(maxLevel, playerLevel + range)
            ))
        -- Level comparison shortcuts: <80, >70, <=79, >=70
        elseif word:match("^<=%d+$") then
            local level = tonumber(word:match("%d+"))
            table.insert(parts, string.format("1-%d", level))
        elseif word:match("^>=%d+$") then
            local level = tonumber(word:match("%d+"))
            local maxLevel = GetMaxPlayerLevel and GetMaxPlayerLevel() or 80
            table.insert(parts, string.format("%d-%d", level, maxLevel))
        elseif word:match("^<%d+$") then
            local level = tonumber(word:match("%d+"))
            table.insert(parts, string.format("1-%d", level - 1))
        elseif word:match("^>%d+$") then
            local level = tonumber(word:match("%d+"))
            local maxLevel = GetMaxPlayerLevel and GetMaxPlayerLevel() or 80
            table.insert(parts, string.format("%d-%d", level + 1, maxLevel))
        else
            table.insert(parts, word)
        end
    end

    local finalQuery = table.concat(parts, " ")

    -- Redirect results to addon
    C_FriendList.SetWhoToUi(true)

    -- Mark that Commander triggered this who search
    -- This tells social.lua to intercept and hide the default UI
    if C3.Social_MarkWhoFromCommander then
        C3.Social_MarkWhoFromCommander()
    end

    return {
        macroText = "/who " .. finalQuery,
        whoQuery = finalQuery,
    }
end

---Build config for flightstyle command
---@return table|nil config
function SecureAction:BuildFlightStyleConfig()
    -- Check if Flight API is available
    if not C3.Flight or not C3.Flight.IsAvailable() then
        return nil
    end

    -- Get spell info for the switch flight style spell
    local spellInfo = C_Spell.GetSpellInfo(C3.Flight.SWITCH_SPELL_ID)
    if not spellInfo then
        return nil
    end

    -- Get target style for custom messages
    local targetStyle = C3.Flight.GetTargetStyle()
    local targetName = C3.Flight.GetTargetStyleName()

    -- The actual cast bar uses different spell IDs depending on target style
    local castSpellID = targetStyle == "steady"
        and C3.Flight.CAST_TO_STEADY
        or C3.Flight.CAST_TO_SKYRIDING

    return {
        macroText = "/cast " .. spellInfo.name,
        spellName = spellInfo.name,
        spellID = castSpellID,  -- Use the actual cast spell ID for progress tracking
        -- Custom messages for flightstyle
        startMessage = Colorize("Switching to:", "status.dim") .. " " .. Colorize(targetName, "accent.primary"),
        completeMessage = Colorize("Switched to:", "accent.success") .. " " .. Colorize(targetName, "accent.primary"),
    }
end

-------------------------------------------------------------------------------
-- Mage Teleport/Portal Config Builders
-------------------------------------------------------------------------------

-- Teleport spell IDs by destination (destination name -> {Alliance ID, Horde ID, Neutral ID})
local TELEPORT_SPELLS = {
    -- Alliance
    stormwind = { 3561, nil, nil },
    ironforge = { 3562, nil, nil },
    darnassus = { 3565, nil, nil },
    exodar = { 32271, nil, nil },
    theramore = { 49359, nil, nil },
    -- Horde
    orgrimmar = { nil, 3567, nil },
    undercity = { nil, 3563, nil },
    thunderbluff = { nil, 3566, nil },
    silvermoon = { nil, 32272, nil },
    stonard = { nil, 49358, nil },
    -- Neutral (available to both factions)
    shattrath = { 33690, 35715, nil },
    dalaran = { nil, nil, 53140 },
    dalaranbroken = { nil, nil, 224869 },
    tol = { nil, nil, 88342 },
    tolbarad = { nil, nil, 88342 },
    vale = { nil, nil, 132621 },
    valeofeternal = { nil, nil, 132621 },
    stormshield = { 176248, nil, nil },
    warspear = { nil, 176242, nil },
    dalarannorthrend = { nil, nil, 53140 },
    hall = { nil, nil, 193759 },
    classorder = { nil, nil, 193759 },
    boralus = { 281403, nil, nil },
    dazaralor = { nil, 281404, nil },
    oribos = { nil, nil, 344587 },
    valdrakken = { nil, nil, 395277 },
    dornogal = { nil, nil, 446540 },
}

-- Portal spell IDs (same structure as teleports)
local PORTAL_SPELLS = {
    -- Alliance
    stormwind = { 10059, nil, nil },
    ironforge = { 11416, nil, nil },
    darnassus = { 11419, nil, nil },
    exodar = { 32266, nil, nil },
    theramore = { 49360, nil, nil },
    -- Horde
    orgrimmar = { nil, 11417, nil },
    undercity = { nil, 11418, nil },
    thunderbluff = { nil, 11420, nil },
    silvermoon = { nil, 32267, nil },
    stonard = { nil, 49361, nil },
    -- Neutral
    shattrath = { 33691, 35717, nil },
    dalaran = { nil, nil, 53142 },
    dalaranbroken = { nil, nil, 224871 },
    tol = { nil, nil, 88345 },
    tolbarad = { nil, nil, 88345 },
    vale = { nil, nil, 132620 },
    valeofeternal = { nil, nil, 132620 },
    stormshield = { 176246, nil, nil },
    warspear = { nil, 176244, nil },
    dalarannorthrend = { nil, nil, 53142 },
    boralus = { 281400, nil, nil },
    dazaralor = { nil, 281402, nil },
    oribos = { nil, nil, 344597 },
    valdrakken = { nil, nil, 395289 },
    dornogal = { nil, nil, 446534 },
}

-- Destination aliases for fuzzy matching
local DESTINATION_ALIASES = {
    sw = "stormwind",
    storm = "stormwind",
    if_ = "ironforge",
    iron = "ironforge",
    darn = "darnassus",
    exo = "exodar",
    thera = "theramore",
    org = "orgrimmar",
    uc = "undercity",
    under = "undercity",
    tb = "thunderbluff",
    thunder = "thunderbluff",
    sm = "silvermoon",
    silver = "silvermoon",
    ston = "stonard",
    shat = "shattrath",
    shatt = "shattrath",
    dal = "dalaran",
    dalbroken = "dalaranbroken",
    dalnorth = "dalarannorthrend",
    tol = "tolbarad",
    tolb = "tolbarad",
    vale = "valeofeternal",
    eternal = "valeofeternal",
    shield = "stormshield",
    spear = "warspear",
    hall = "classorder",
    order = "classorder",
    bor = "boralus",
    boral = "boralus",
    daz = "dazaralor",
    dazar = "dazaralor",
    ori = "oribos",
    vald = "valdrakken",
    dorn = "dornogal",
}

---Get the appropriate spell ID for a destination based on player faction
---@param spellTable table The spell ID table {Alliance, Horde, Neutral}
---@return number|nil spellID
local function getSpellForFaction(spellTable)
    if not spellTable then return nil end

    local faction = UnitFactionGroup("player")
    local allianceID, hordeID, neutralID = spellTable[1], spellTable[2], spellTable[3]

    -- Try faction-specific first, then neutral
    if faction == "Alliance" then
        return allianceID or neutralID
    elseif faction == "Horde" then
        return hordeID or neutralID
    else
        return neutralID or allianceID or hordeID
    end
end

---Resolve destination name to canonical form
---@param input string User input destination
---@return string|nil canonicalName
local function resolveDestination(input)
    if not input then return nil end
    local lower = input:lower():gsub("%s+", "")

    -- Check direct match first
    if TELEPORT_SPELLS[lower] then
        return lower
    end

    -- Check aliases
    if DESTINATION_ALIASES[lower] then
        return DESTINATION_ALIASES[lower]
    end

    -- Fuzzy match against all destinations
    for dest, _ in pairs(TELEPORT_SPELLS) do
        if dest:find(lower, 1, true) then
            return dest
        end
    end

    return nil
end

---Shared helper to build teleport/portal config
---@param destination string User input destination
---@param spellTable table Spell IDs table (TELEPORT_SPELLS or PORTAL_SPELLS)
---@param startPrefix string Message prefix for cast start
---@param completePrefix string Message prefix for cast complete
---@return table|nil config
local function buildMageTransportConfig(destination, spellTable, startPrefix, completePrefix)
    -- Check if player is a Mage
    local _, class = UnitClass("player")
    if class ~= "MAGE" then
        return nil
    end

    local dest = resolveDestination(destination)
    if not dest then
        return nil
    end

    local destSpells = spellTable[dest]
    local spellID = getSpellForFaction(destSpells)
    if not spellID then
        return nil
    end

    -- Check if spell is known
    if not IsSpellKnown(spellID) then
        return nil
    end

    -- Get spell name
    local spellInfo = C_Spell.GetSpellInfo(spellID)
    if not spellInfo then
        return nil
    end

    -- Capitalize destination for display
    local displayDest = dest:sub(1, 1):upper() .. dest:sub(2)

    return {
        macroText = "/cast " .. spellInfo.name,
        spellName = spellInfo.name,
        spellID = spellID,
        startMessage = startPrefix .. Colorize(displayDest, "accent.primary"),
        completeMessage = completePrefix .. Colorize(displayDest, "accent.primary"),
    }
end

---Build config for teleport command
---@param destination string
---@return table|nil config
function SecureAction:BuildTeleportConfig(destination)
    return buildMageTransportConfig(
        destination,
        TELEPORT_SPELLS,
        Colorize("Teleporting to:", "status.dim") .. " ",
        Colorize("Teleported to:", "accent.success") .. " "
    )
end

---Build config for portal command
---@param destination string
---@return table|nil config
function SecureAction:BuildPortalConfig(destination)
    return buildMageTransportConfig(
        destination,
        PORTAL_SPELLS,
        Colorize("Portal", "accent.special") .. " " .. Colorize("to:", "status.dim") .. " ",
        Colorize("Opened portal to:", "accent.success") .. " "
    )
end

-- Export destination lists for intellisense
SecureAction.TELEPORT_DESTINATIONS = TELEPORT_SPELLS
SecureAction.PORTAL_DESTINATIONS = PORTAL_SPELLS
SecureAction.DESTINATION_ALIASES = DESTINATION_ALIASES

-------------------------------------------------------------------------------
-- Fallback Commands (when not using pre-armed pattern)
-------------------------------------------------------------------------------

---Use an item by ID or name (shows button if not pre-armed)
---@param itemIdOrName number|string
---@param target? string Unit to target (optional)
---@return boolean success
---@return string? message
function SecureAction:UseItem(itemIdOrName, target)
    if self:InCombat() then
        table.insert(self._queue, { type = "item", item = itemIdOrName, target = target })
        return false, "Queued for after combat"
    end

    local itemId = tonumber(itemIdOrName)
    local itemName

    if itemId then
        local itemInfo = C_Item.GetItemInfo(itemId)
        itemName = itemInfo and itemInfo.itemName or nil
    else
        itemName = itemIdOrName
        itemId = C_Item.GetItemInfoInstant(itemIdOrName)
    end

    if not itemId then
        return false, "Item not found: " .. tostring(itemIdOrName)
    end

    -- Configure button
    self._button:SetAttribute("type", "item")
    self._button:SetAttribute("item", itemName or itemIdOrName)
    if target then
        self._button:SetAttribute("unit", target)
    else
        self._button:SetAttribute("unit", nil)
    end

    SetOverrideBindingClick(self._button, true, "ENTER", "Commander3SecureButton")

    return true, "Type 'use " .. tostring(itemIdOrName) .. "' and press Enter"
end

---Cast a spell by ID or name (shows button if not pre-armed)
---@param spellIdOrName number|string
---@param target? string Unit to target (optional)
---@return boolean success
---@return string? message
function SecureAction:CastSpell(spellIdOrName, target)
    if self:InCombat() then
        table.insert(self._queue, { type = "spell", spell = spellIdOrName, target = target })
        return false, "Queued for after combat"
    end

    local spellInfo = C_Spell.GetSpellInfo(spellIdOrName)
    if not spellInfo then
        return false, "Spell not found: " .. tostring(spellIdOrName)
    end

    -- Check if known
    local spellId = spellInfo.spellID
    if spellId then
        local isKnown = IsSpellKnown and IsSpellKnown(spellId)
        if isKnown == false then
            return false, "Spell not known: " .. spellInfo.name
        end
    end

    -- Configure button using macro for reliability
    self._button:SetAttribute("type", "macro")
    self._button:SetAttribute("macrotext", "/cast " .. spellInfo.name)
    if target then
        self._button:SetAttribute("unit", target)
    else
        self._button:SetAttribute("unit", nil)
    end

    SetOverrideBindingClick(self._button, true, "ENTER", "Commander3SecureButton")

    return true, "Type 'cast " .. tostring(spellIdOrName) .. "' and press Enter"
end

---Target a unit
---@param unit string
---@return boolean success
---@return string? message
function SecureAction:Target(unit)
    if self:InCombat() then
        table.insert(self._queue, { type = "target", unit = unit })
        return false, "Queued for after combat"
    end

    if not UnitExists(unit) then
        return false, "Unit not found: " .. unit
    end

    self._button:SetAttribute("type", "target")
    self._button:SetAttribute("unit", unit)

    SetOverrideBindingClick(self._button, true, "ENTER", "Commander3SecureButton")

    return true, "Type 'settarget " .. unit .. "' and press Enter"
end

---Execute a macro
---@param macroText string
---@return boolean success
---@return string? message
function SecureAction:Macro(macroText)
    if self:InCombat() then
        table.insert(self._queue, { type = "macro", macro = macroText })
        return false, "Queued for after combat"
    end

    self._button:SetAttribute("type", "macro")
    self._button:SetAttribute("macrotext", macroText)

    SetOverrideBindingClick(self._button, true, "ENTER", "Commander3SecureButton")

    return true, "Press Enter to execute macro"
end

---Summon a mount by ID or spell name
---@param mountIdOrSpellId number Mount ID (Retail) or Spell ID (Classic)
---@param useSpellId? boolean If true, treat as spell ID instead of mount ID
---@return boolean success
---@return string? error
function SecureAction:SummonMount(mountIdOrSpellId, useSpellId)
    if self:InCombat() then
        return false, "Cannot summon mount during combat"
    end

    -- Retail: use C_MountJournal API (this works without secure button)
    if C_MountJournal and C_MountJournal.SummonByID and not useSpellId then
        C_MountJournal.SummonByID(mountIdOrSpellId)
        return true
    end

    -- Classic fallback: cast mount spell via secure button
    local spellName = C_Spell.GetSpellName(mountIdOrSpellId)
    if not spellName then
        return false, "Mount spell not found"
    end

    return self:CastSpell(spellName)
end

-------------------------------------------------------------------------------
-- Queue Management
-------------------------------------------------------------------------------

function SecureAction:_processQueue()
    if #self._queue == 0 then return end

    local action = table.remove(self._queue, 1)

    if action.type == "item" then
        self:UseItem(action.item, action.target)
    elseif action.type == "spell" then
        self:CastSpell(action.spell, action.target)
    elseif action.type == "target" then
        self:Target(action.unit)
    elseif action.type == "macro" then
        self:Macro(action.macro)
    end

    if #self._queue > 0 then
        C_Timer.After(0.1, function()
            if not self:InCombat() then
                self:_processQueue()
            end
        end)
    end
end

function SecureAction:GetQueueLength()
    return #self._queue
end

function SecureAction:ClearQueue()
    self._queue = {}
end

-------------------------------------------------------------------------------
-- Commands
-------------------------------------------------------------------------------

C3.Command:Define({
    name = "use",
    description = "Use an item",
    category = "action",
    acceptsInput = false,
    args = {
        { name = "item", required = true, description = "Item name or ID",
          provider = "Items" },
        { name = "target", required = false, description = "Target unit" },
    },
    execute = function(ctx)
        local item = ctx:GetArg(1)
        local target = ctx:GetArg(2)

        if not item then
            return ctx:Error("Usage: use <item> [target]")
        end

        -- If we're called, secure action wasn't pre-armed (user typed too fast)
        -- Just give them instructions
        return ctx:Text("Type 'use " .. item .. "' and press Enter to use")
    end
})

C3.Command:Define({
    name = "cast",
    description = "Cast a spell",
    category = "action",
    acceptsInput = false,
    args = {
        { name = "spell", required = true, description = "Spell name or ID",
          provider = "Spells" },
        { name = "target", required = false, description = "Target unit" },
    },
    execute = function(ctx)
        local spell = ctx:GetArg(1)
        local target = ctx:GetArg(2)

        if not spell then
            return ctx:Error("Usage: cast <spell> [target]")
        end

        -- If we're called, secure action wasn't pre-armed
        return ctx:Text("Type 'cast " .. spell .. "' and press Enter to cast")
    end
})

C3.Command:Define({
    name = "settarget",
    description = "Target a unit (use 'target' to show current target info)",
    category = "action",
    acceptsInput = false,
    args = {
        { name = "unit", required = true, description = "Unit to target (player, focus, party1, etc.)" },
    },
    execute = function(ctx)
        local unit = ctx:GetArg(1)

        if not unit then
            return ctx:Error("Usage: settarget <unit>")
        end

        return ctx:Text("Type 'settarget " .. unit .. "' and press Enter to target")
    end
})

C3.Command:Define({
    name = "logout",
    description = "Logout to character select",
    category = "action",
    acceptsInput = false,
    execute = function(ctx)
        if InCombatLockdown() then
            return ctx:Error("Cannot logout during combat")
        end
        return ctx:Text("Type 'logout' and press Enter to logout")
    end
})

C3.Command:Define({
    name = "quit",
    description = "Quit the game",
    category = "action",
    acceptsInput = false,
    execute = function(ctx)
        if InCombatLockdown() then
            return ctx:Error("Cannot quit during combat")
        end
        return ctx:Text("Type 'quit' and press Enter to quit")
    end
})

C3.Command:Define({
    name = "clear",
    description = "Clear terminal output",
    category = "terminal",
    acceptsInput = false,
    execute = function(ctx)
        -- Get terminal reference from session
        local session = ctx.session
        local terminal = session and session.terminal
        if terminal and terminal.output then
            terminal.output:Clear()
            return nil  -- No output
        end
        return ctx:Text("Terminal cleared")
    end
})

C3.Command:Define({
    name = "exit",
    description = "Close Commander window",
    category = "terminal",
    acceptsInput = false,
    execute = function(ctx)
        -- Get terminal reference from session
        local session = ctx.session
        local terminal = session and session.terminal
        if terminal then
            terminal:Hide()
            return nil  -- No output (window is closing)
        end
        return ctx:Error("No terminal window found")
    end
})

-------------------------------------------------------------------------------
-- Initialize on Load
-------------------------------------------------------------------------------

C3:OnInit(function()
    SecureAction:Init()
end)

return SecureAction
