-- ============================================================================
-- Vamoose's Companion - Engine
-- Core pipeline: enrich → prioritize → cooldown → select quote → emit
-- ============================================================================

VC = VC or {}
VC.Engine = {}

-- Internal cooldown tracking (not persisted — resets each session)
VC.Engine._lastGlobalQuote = 0
VC.Engine._lastCategoryQuote = {}
VC.Engine._cachedExpansion = nil

-- ============================================================================
-- INITIALIZATION
-- ============================================================================

function VC.Engine:Initialize()
    -- Build history lookup set for O(1) checks
    self:RebuildHistorySet()

    -- Listen for bond level-up events to fire special quotes
    VC.EventBus:Register("VC_BOND_LEVEL_UP", function(data)
        if not data or not data.newLevel then return end
        local quote = self:FindQuoteByTag("bondlevelup", data.newLevel)
        if not quote then return end
        self:ProcessEvent({
            eventType = "bondlevelup",
            intensity = VC.Constants.INTENSITY.EPIC,
            context = { oldLevel = data.oldLevel, newLevel = data.newLevel },
            timestamp = GetTime(),
            source = "VC_MILESTONE",
            milestoneQuoteId = quote.id,
        })
    end)
end

-- ============================================================================
-- MAIN PIPELINE
-- ============================================================================

function VC.Engine:ProcessEvent(event)
    local state = VC.Store:GetState()
    local isTest = event.source == "SLASH_TEST"
    local isMilestone = event.source == "VC_MILESTONE"

    -- 0. Pre-selected milestone quote (bypasses all filtering)
    if event.milestoneQuoteId then
        local quote = self:FindQuoteById(event.milestoneQuoteId)
        if quote then
            local companionKey = state.config.activeCompanion
            VC.Store:Dispatch("ADD_QUOTE_HISTORY", {
                entry = {
                    id = quote.id, text = quote.text,
                    companionKey = companionKey, eventType = event.eventType,
                    quality = quote.quality, timestamp = GetTime(),
                }
            })
            self._historySet[quote.id] = true
            local companion = VC_CompanionProfiles[companionKey]
            VC.EventBus:Trigger("VC_QUOTE_READY", {
                quote = quote, companion = companion, event = event,
            })
            if state.config.debug then
                print("|cFFA0522D[VC Engine]|r Milestone:", quote.id, "(" .. quote.quality .. ")")
            end
            return
        end
    end

    -- 1. Global mute check
    if state.config.muted and not isTest then return end

    -- 1b. Activity-based muting (instance type)
    if not isTest then
        local _, instanceType = IsInInstance()
        if instanceType == "party" and state.config.muteInDungeons then return end
        if instanceType == "raid" and state.config.muteInRaids then return end
        if (instanceType == "pvp" or instanceType == "arena") and state.config.muteInPvP then return end
    end

    -- 2. Combat suppression (EPIC events and tests bypass)
    if not isTest and event.intensity < VC.Constants.INTENSITY.EPIC then
        if InCombatLockdown() then
            if state.config.debug then
                print("|cFFA0522D[VC Engine]|r Dropped (in combat):", event.eventType)
            end
            return
        end
    end

    -- 3. Event toggle check
    if not isTest and state.config.eventToggles[event.eventType] == false then return end

    -- 3. Enrich context
    self:EnrichContext(event)

    -- 4. Priority check (test quotes bypass; threshold scales with chattiness)
    if not isTest then
        local score = self:ScorePriority(event)
        local tier = VC.Constants.CHATTINESS[state.config.chattiness]
        local threshold = tier and tier.priorityThreshold or VC.Constants.PRIORITY_THRESHOLD
        if score < threshold then
            if state.config.debug then
                print("|cFFA0522D[VC Engine]|r Dropped (low priority):", event.eventType, "score=" .. score)
            end
            return
        end
    end

    -- 5. Cooldown check (test quotes bypass; EPIC bypasses except gold; skipCooldown flag)
    local epicBypass = event.intensity >= VC.Constants.INTENSITY.EPIC and event.eventType ~= "gold"
    if not isTest and not epicBypass and not event.skipCooldown then
        if not self:CheckCooldowns(event) then
            if state.config.debug then
                print("|cFFA0522D[VC Engine]|r Dropped (cooldown):", event.eventType)
            end
            return
        end
    end

    -- 6. Select quote (returns quote + companionKey used, which may differ in random mode)
    local quote, usedKey = self:SelectQuote(event)
    if not quote then
        if state.config.debug then
            print("|cFFA0522D[VC Engine]|r No quote available for:", event.eventType)
        end
        return
    end

    -- 6b. Check milestones (may fire follow-up milestone quote)
    if VC.Memory and not isMilestone then
        VC.Memory:CheckMilestones(event.eventType)
    end

    -- 7. Record to history and update cooldowns
    VC.Store:Dispatch("ADD_QUOTE_HISTORY", {
        entry = {
            id = quote.id,
            text = quote.text,
            companionKey = usedKey,
            eventType = event.eventType,
            quality = quote.quality,
            timestamp = GetTime(),
        }
    })
    self._historySet[quote.id] = true
    self._lastGlobalQuote = GetTime()
    self._lastCategoryQuote[event.eventType] = GetTime()

    -- 8. Bonding gain on quote delivery
    if VC.Bonding then
        VC.Bonding:AddRapport(VC.Constants.BONDING.GAINS.QUOTE_DELIVERED, "quote")
    end

    -- 9. Emit to display (use the companion that actually spoke)
    local companion = VC_CompanionProfiles[usedKey]
    VC.EventBus:Trigger("VC_QUOTE_READY", {
        quote = quote,
        companion = companion,
        event = event,
    })

    if state.config.debug then
        print("|cFFA0522D[VC Engine]|r Quote:", quote.id, "(" .. quote.quality .. ")")
    end
end

-- ============================================================================
-- CONTEXT ENRICHMENT
-- ============================================================================

function VC.Engine:EnrichContext(event)
    event.context = event.context or {}

    -- Time of day (real clock)
    local hour = tonumber(date("%H"))
    if hour >= 5 and hour < 12 then
        event.context.timeOfDay = "morning"
    elseif hour >= 12 and hour < 17 then
        event.context.timeOfDay = "afternoon"
    elseif hour >= 17 and hour < 21 then
        event.context.timeOfDay = "evening"
    else
        event.context.timeOfDay = "night"
    end

    -- Session duration
    if VC._sessionStart then
        event.context.sessionMinutes = math.floor((GetTime() - VC._sessionStart) / 60)
    end

    -- Zone name
    event.context.zoneName = GetRealZoneText() or ""

    -- Expansion context
    event.context.expansion = self:GetCurrentExpansion()
end

-- Get current expansion from zone map parent chain
function VC.Engine:GetCurrentExpansion()
    if self._cachedExpansion then return self._cachedExpansion end
    local mapID = C_Map.GetBestMapForUnit("player")
    if not mapID then return nil end
    local id = mapID
    for _ = 1, 10 do
        local exp = VC.Constants.EXPANSION_ZONES[id]
        if exp then
            self._cachedExpansion = exp
            return exp
        end
        local info = C_Map.GetMapInfo(id)
        if not info or not info.parentMapID or info.parentMapID == 0 then break end
        id = info.parentMapID
    end
    return nil
end

-- ============================================================================
-- PRIORITY SCORING
-- ============================================================================

function VC.Engine:ScorePriority(event)
    local score = (event.intensity or 1) * 2

    -- Farming: bonus for item quality
    if event.eventType == "farming" and event.context.itemQuality then
        if event.context.itemQuality >= 3 then score = score + 2 end
        if event.context.itemQuality >= 4 then score = score + 2 end
    end

    -- Gold: bonus for large amounts
    if event.eventType == "gold" and event.context.goldDelta then
        if math.abs(event.context.goldDelta) > 100000 then score = score + 2 end
    end

    -- Achievements and boss kills are always interesting
    if event.eventType == "achieve" then score = score + 3 end
    if event.eventType == "boss" then score = score + 3 end

    -- Level up and login always fire
    if event.eventType == "levelup" then score = score + 5 end
    if event.eventType == "login" then score = score + 5 end

    -- Zone changes, resurrections, and rep ranks are noteworthy
    if event.eventType == "zone" then score = score + 2 end
    if event.eventType == "resurrection" then score = score + 2 end
    if event.eventType == "reprank" then score = score + 2 end

    -- Milestones and bond level-ups always fire
    if event.eventType == "milestone" then score = score + 10 end
    if event.eventType == "bondlevelup" then score = score + 10 end

    return score
end

-- ============================================================================
-- COOLDOWN CHECK
-- ============================================================================

function VC.Engine:CheckCooldowns(event)
    local now = GetTime()
    local state = VC.Store:GetState()
    local tier = VC.Constants.CHATTINESS[state.config.chattiness]
    if not tier then return true end

    -- Global cooldown
    local globalCD = tier.globalCD
    if (now - self._lastGlobalQuote) < globalCD then
        return false
    end

    -- Category cooldown
    local baseCD = VC.Constants.CATEGORY_COOLDOWNS[event.eventType]
    if baseCD then
        local categoryCD = baseCD * tier.categoryMult
        local lastCategory = self._lastCategoryQuote[event.eventType] or 0
        if (now - lastCategory) < categoryCD then
            return false
        end
    end

    return true
end

-- ============================================================================
-- QUOTE SELECTION
-- ============================================================================

function VC.Engine:SelectQuote(event)
    local state = VC.Store:GetState()
    local companionKey = state.config.activeCompanion

    -- Random companion mode: pick from all available companions
    if state.config.randomCompanion and VC_QuoteDB then
        local available = {}
        for k in pairs(VC_QuoteDB) do available[#available + 1] = k end
        if #available > 0 then
            companionKey = available[math.random(#available)]
        end
    end

    -- Get quote pool for this companion + event type
    local db = VC_QuoteDB and VC_QuoteDB[companionKey]
    if not db then return nil, companionKey end

    local pool = db[event.eventType]
    if not pool or #pool == 0 then return nil, companionKey end

    -- Filter by context tags (e.g., gold gain vs loss)
    local filtered = self:FilterByTags(pool, event)

    -- Exclude recently used quotes
    local available = {}
    for _, quote in ipairs(filtered) do
        if not self._historySet[quote.id] then
            table.insert(available, quote)
        end
    end

    -- If all quotes exhausted, reset history for this category and retry
    if #available == 0 then
        self:ClearCategoryHistory(companionKey, event.eventType)
        available = filtered
    end

    if #available == 0 then return nil, companionKey end

    -- Weighted random selection by quality tier
    return self:WeightedSelect(available), companionKey
end

-- Find a quote by ID across all categories for the active companion
function VC.Engine:FindQuoteById(quoteId)
    local state = VC.Store:GetState()
    local db = VC_QuoteDB and VC_QuoteDB[state.config.activeCompanion]
    if not db then return nil end
    for _, pool in pairs(db) do
        if type(pool) == "table" then
            for _, quote in ipairs(pool) do
                if quote.id == quoteId then return quote end
            end
        end
    end
    return nil
end

-- Find a quote by tag within a specific category for the active companion
function VC.Engine:FindQuoteByTag(category, tag)
    local db = VC_QuoteDB and VC_QuoteDB[VC.Store:GetState().config.activeCompanion]
    if not db or not db[category] then return nil end
    for _, q in ipairs(db[category]) do
        if q.tags then
            for _, t in ipairs(q.tags) do
                if t == tag then return q end
            end
        end
    end
    return nil
end

-- Expansion tag set for O(1) lookup
local EXPANSION_TAGS = {}
for _, exp in pairs(VC.Constants.EXPANSION_ZONES) do
    EXPANSION_TAGS[exp] = true
end

-- Home-presence tags: a home quote matches only the active presence tag
-- (and, for house entries, the first-of-session vs return sub-tag).
local HOME_TAGS = {
    enter_neighborhood = true, enter_plot = true, enter_house = true,
    idle_neighborhood  = true, idle_plot  = true, idle_house  = true,
}

-- Filter quotes by context tags (gold gain/loss + expansion)
function VC.Engine:FilterByTags(pool, event)
    local tagged = {}
    local untagged = {}
    local expansion = event.context and event.context.expansion

    for _, quote in ipairs(pool) do
        if quote.tags and #quote.tags > 0 then
            local matches = true
            for _, tag in ipairs(quote.tags) do
                if tag == "gain" and not event.context.isGain then matches = false end
                if tag == "loss" and event.context.isGain then matches = false end
                -- Expansion tag: must match current expansion
                if EXPANSION_TAGS[tag] and tag ~= expansion then matches = false end
                -- Home presence: quote must carry the active presence tag,
                -- and house entries must match first-of-session vs return.
                if HOME_TAGS[tag] and tag ~= event.context.homeTag then matches = false end
                if tag == "first"  and event.context.homeEntry ~= "first"  then matches = false end
                if tag == "return" and event.context.homeEntry ~= "return" then matches = false end
            end
            if matches then
                table.insert(tagged, quote)
            end
        else
            table.insert(untagged, quote)
        end
    end

    -- Prefer tagged quotes if any match, otherwise fall back to untagged
    if #tagged > 0 then
        for _, q in ipairs(untagged) do
            table.insert(tagged, q)
        end
        return tagged
    end

    return untagged
end

-- Weighted random selection based on quality tier (adjusted by mood + bonding)
function VC.Engine:WeightedSelect(pool)
    local baseWeights = VC.Constants.QUALITY_WEIGHTS
    local totalWeight = 0
    local entries = {}

    -- Mood adjustments
    local moodMult = { common = 1, uncommon = 1, rare = 1, epic = 1, legendary = 1 }
    if VC.Mood then
        local mood = VC.Mood:GetMood()
        if mood == "FRUSTRATED" or mood == "STRESSED" then
            moodMult.common = 1.5 -- safer, warmer quotes when stressed
        elseif mood == "EXCITED" or mood == "TRIUMPHANT" then
            moodMult.rare = 2.0
            moodMult.epic = 2.0
            moodMult.legendary = 2.0
        end
    end

    -- Bonding boost (higher bonding = better quality chances)
    local bondBoost = 1.0
    if VC.Bonding then
        local level = VC.Bonding:GetLevel()
        bondBoost = VC.Constants.BONDING.QUALITY_BOOST[level] or 1.0
    end

    for _, quote in ipairs(pool) do
        local quality = quote.quality or "common"
        local w = baseWeights[quality] or baseWeights.common
        w = w * (moodMult[quality] or 1)
        -- Bonding boost applies to rare+ only
        if quality == "rare" or quality == "epic" or quality == "legendary" then
            w = w * bondBoost
        end
        totalWeight = totalWeight + w
        table.insert(entries, { quote = quote, cumWeight = totalWeight })
    end

    local roll = math.random() * totalWeight
    for _, entry in ipairs(entries) do
        if roll <= entry.cumWeight then
            return entry.quote
        end
    end

    return pool[1] -- fallback
end

-- ============================================================================
-- HISTORY HELPERS
-- ============================================================================

-- Build O(1) lookup set from stored history array (supports rich entries)
function VC.Engine:RebuildHistorySet()
    self._historySet = {}
    local history = VC.Store:GetState().quoteHistory
    for _, entry in ipairs(history) do
        local id = type(entry) == "table" and entry.id or entry
        self._historySet[id] = true
    end
end

-- Clear history entries for a specific companion + category (when pool exhausted)
function VC.Engine:ClearCategoryHistory(companionKey, eventType)
    local db = VC_QuoteDB and VC_QuoteDB[companionKey]
    if not db or not db[eventType] then return end

    -- Build set of IDs in this category
    local categoryIds = {}
    for _, quote in ipairs(db[eventType]) do
        categoryIds[quote.id] = true
    end

    -- Remove from history set
    for id in pairs(categoryIds) do
        self._historySet[id] = nil
    end

    -- Also trim from persisted history array
    local history = VC.Store:GetState().quoteHistory
    local newHistory = {}
    for _, entry in ipairs(history) do
        local id = type(entry) == "table" and entry.id or entry
        if not categoryIds[id] then
            table.insert(newHistory, entry)
        end
    end
    VC.Store:Dispatch("SET_QUOTE_HISTORY", { history = newHistory })
end
