-- ============================================================================
-- Vamoose's Companion - Mascot
-- Squirrel mascot that perches on top of visible addon panels
-- Uses displayID 134 (classic squirrel), AnimationGroup for all movement
-- ============================================================================

VC = VC or {}
VC.Mascot = {}
local M = VC.Mascot

-- Animations: 0=idle, 6=attack/excited, 26=fidget, 110=sleep
local ANIM_IDLE    = 0
local ANIM_EXCITED = 6
local ANIM_FIDGET  = 26
local ANIM_SLEEP   = 110

local SQUIRREL_DISPLAY_ID = 134
local MASCOT_SIZE         = 48
local PERCH_CHECK_INTERVAL = 2    -- seconds between panel scans
local SLEEP_IDLE_TIME      = 60   -- seconds before sleep kicks in
local FIDGET_CHANCE        = 0.25 -- 25% chance each fidget roll

-- ============================================================================
-- KNOWN PANELS (checked in priority order)
-- ============================================================================

-- Returns { frame, label } or nil for each known addon panel
local function GetKnownPanels()
    local panels = {}

    -- VamoosesCompanion Display talking head
    local vc = _G.VamoosesCompanion
    if vc and vc.Display and vc.Display.talkingHead and vc.Display.talkingHead:IsShown() then
        panels[#panels + 1] = { frame = vc.Display.talkingHead, label = "vc_th" }
    end

    -- VamoosesSlipstream main panel
    local vss = _G.VamoosesSlipstream
    if vss and vss.MainPanel and vss.MainPanel.frame and vss.MainPanel.frame:IsShown() then
        panels[#panels + 1] = { frame = vss.MainPanel.frame, label = "vss" }
    end

    -- VamoosesEndeavors MainFrame
    local ve = _G.VamoosesEndeavors
    if ve and ve.MainFrame and ve.MainFrame:IsShown() then
        panels[#panels + 1] = { frame = ve.MainFrame, label = "ve" }
    end

    -- HousingDecorGuide main frame
    local hdg = _G.HousingDecorGuide
    if hdg and hdg.frame and hdg.frame:IsShown() then
        panels[#panels + 1] = { frame = hdg.frame, label = "hdg" }
    end

    return panels
end

-- ============================================================================
-- FRAME CREATION
-- ============================================================================

function M:CreateFrames()
    -- Container (moves via TranslationAnimation; model rides inside)
    local container = CreateFrame("Frame", "VC_MascotContainer", UIParent)
    container:SetSize(MASCOT_SIZE, MASCOT_SIZE)
    container:SetFrameStrata("HIGH")
    container:SetFrameLevel(100)
    container:Hide()
    self.container = container

    -- PlayerModel inside container
    local model = CreateFrame("PlayerModel", nil, container)
    model:SetAllPoints()
    -- Model loads async: SetAnimation before OnModelLoaded is a silent no-op,
    -- so the idle anim is applied from the load callback.
    model:SetScript("OnModelLoaded", function(m)
        m:SetAnimation(ANIM_IDLE)
    end)
    pcall(model.SetDisplayInfo, model, SQUIRREL_DISPLAY_ID)
    pcall(model.SetCustomCamera, model, 0)
    model:UseModelCenterToTransform(true)
    if model:HasCustomCamera() then
        model:SetCameraPosition(1.8, 0, 0.1)
        model:SetCameraTarget(0, 0, 0.1)
    end
    self.model = model

    -- Hop AnimationGroup on the container (Translation only — works on any frame)
    local hopGroup = container:CreateAnimationGroup()

    -- Phase 1: arc upward + sideways
    local trans1 = hopGroup:CreateAnimation("Translation")
    trans1:SetOrder(1)
    trans1:SetDuration(0.18)
    trans1:SetSmoothing("OUT")

    -- Phase 2: arc downward + sideways
    local trans2 = hopGroup:CreateAnimation("Translation")
    trans2:SetOrder(2)
    trans2:SetDuration(0.18)
    trans2:SetSmoothing("IN")

    self.hopGroup  = hopGroup
    self.hopTrans1 = trans1
    self.hopTrans2 = trans2

    -- Idle fidget ticker (rolls every 12-18s)
    self._fidgetTicker = nil
    self._lastActivity = GetTime()
    self._sleeping     = false
    self._currentLabel = nil  -- label of panel we're currently perched on

    -- React animation group (model-level alpha flash to signal excitement)
    local reactGroup = model:CreateAnimationGroup()
    local reactAlpha = reactGroup:CreateAnimation("Alpha")
    reactAlpha:SetOrder(1)
    reactAlpha:SetDuration(0.08)
    reactAlpha:SetFromAlpha(1)
    reactAlpha:SetToAlpha(0.3)
    reactAlpha:SetSmoothing("NONE")
    local reactAlpha2 = reactGroup:CreateAnimation("Alpha")
    reactAlpha2:SetOrder(2)
    reactAlpha2:SetDuration(0.08)
    reactAlpha2:SetFromAlpha(0.3)
    reactAlpha2:SetToAlpha(1)
    reactAlpha2:SetSmoothing("NONE")
    self.reactGroup = reactGroup

    hopGroup:SetScript("OnFinished", function()
        self:OnHopFinished()
    end)
end

-- ============================================================================
-- PERCH LOGIC
-- ============================================================================

-- Pick a random X along the top edge of the target frame
local function PickPerchX(frame)
    local w = frame:GetWidth() or MASCOT_SIZE
    local margin = MASCOT_SIZE
    local range = math.max(0, w - margin * 2)
    return margin + math.random() * range
end

-- World-space top-left of a frame (returns x, y in screen coords)
local function GetFrameTopLeft(frame)
    local left = frame:GetLeft() or 0
    local top  = frame:GetTop() or 0
    return left, top
end

function M:PerchOn(panel, animate)
    local frame = panel.frame
    local label = panel.label

    local fl, ft = GetFrameTopLeft(frame)
    local perchX = PickPerchX(frame)
    local destX  = fl + perchX - MASCOT_SIZE / 2
    local destY  = ft  -- container top aligns with frame top (mascot sits on top edge)

    local scale = UIParent:GetEffectiveScale()
    local scaledX = destX / scale
    local scaledY = destY / scale

    if animate and self.container:IsShown() and not self.hopGroup:IsPlaying() then
        local cx = self.container:GetLeft() or destX
        local cy = self.container:GetTop() or destY

        local dx = destX - cx
        local dy = destY - cy
        local arc = 30 -- pixels of arc height

        -- Phase 1: half the horizontal + arc upward
        self.hopTrans1:SetOffset(dx * 0.5, dy * 0.5 + arc)
        -- Phase 2: remaining half + arc back down
        self.hopTrans2:SetOffset(dx * 0.5, dy * 0.5 - arc)

        -- Face toward center of target
        local fw = frame:GetWidth() or 0
        local centerX = fl + fw / 2
        if (cx + MASCOT_SIZE / 2) > centerX then
            self.model:SetFacing(math.pi) -- face left
        else
            self.model:SetFacing(0)        -- face right
        end

        self._pendingPerch = { scaledX = scaledX, scaledY = scaledY, label = label }
        self.hopGroup:Play()
    else
        -- Snap immediately
        self.container:ClearAllPoints()
        self.container:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", scaledX, scaledY)
        self.container:Show()
        self._currentLabel = label
        self:WakeUp()
        self:SetAnimation(ANIM_IDLE)
    end
end

function M:OnHopFinished()
    if self._pendingPerch then
        local p = self._pendingPerch
        self.container:ClearAllPoints()
        self.container:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", p.scaledX, p.scaledY)
        self._currentLabel = p.label
        self._pendingPerch = nil
        self:WakeUp()
        self:SetAnimation(ANIM_IDLE)
    end
end

-- ============================================================================
-- ANIMATION HELPERS
-- ============================================================================

function M:SetAnimation(anim)
    if self.model then
        self.model:SetAnimation(anim)
        self._currentAnim = anim
    end
end

function M:React()
    if not self.container:IsShown() then return end
    self._lastActivity = GetTime()
    self:WakeUp()
    -- Play excited model animation then snap back to idle
    self:SetAnimation(ANIM_EXCITED)
    if self.reactGroup:IsPlaying() then self.reactGroup:Stop() end
    self.reactGroup:Play()
    -- Return to idle after a beat (use animation OnFinished loop instead of timer)
    -- AnimationGroup pulse: 2 flashes = ~0.32s, model anim will naturally loop idle
    -- We schedule idle return via a tiny ticker (1 tick only)
    if self._reactReturn then self._reactReturn:Cancel() end
    self._reactReturn = C_Timer.NewTimer(1.5, function()
        self._reactReturn = nil
        if InCombatLockdown() then return end
        if M._currentAnim == ANIM_EXCITED then
            M:SetAnimation(ANIM_IDLE)
        end
    end)
end

function M:WakeUp()
    if self._sleeping then
        self._sleeping = false
        self:SetAnimation(ANIM_IDLE)
    end
    self._lastActivity = GetTime()
end

function M:CheckSleep()
    if not self.container:IsShown() then return end
    if self._sleeping then return end
    local idle = GetTime() - (self._lastActivity or 0)
    if idle >= SLEEP_IDLE_TIME then
        self._sleeping = true
        self:SetAnimation(ANIM_SLEEP)
    end
end

-- ============================================================================
-- PERCH SCAN TICKER
-- ============================================================================

function M:StartPerchScan()
    if self._perchTicker then return end
    self._perchTicker = C_Timer.NewTicker(PERCH_CHECK_INTERVAL, function()
        M:ScanPanels()
    end)
end

function M:StopPerchScan()
    if self._perchTicker then
        self._perchTicker:Cancel()
        self._perchTicker = nil
    end
end

function M:ScanPanels()
    if not M:IsEnabled() then return end
    if InCombatLockdown() then return end

    local panels = GetKnownPanels()

    if #panels == 0 then
        -- No panels open — hide mascot
        if self.container:IsShown() then
            self.container:Hide()
            self._currentLabel = nil
        end
        return
    end

    -- Pick first visible panel (priority order)
    local target = panels[1]

    if not self.container:IsShown() then
        -- First appearance — snap on without hop
        self:PerchOn(target, false)
    elseif self._currentLabel ~= target.label then
        -- Panel changed — hop over
        self:PerchOn(target, true)
    else
        -- Same panel — check if we need to reposition (panel may have moved)
        if not self.hopGroup:IsPlaying() then
            local frame = target.frame
            local fl, ft = GetFrameTopLeft(frame)
            local perchX = PickPerchX(frame)
            local destX  = fl + perchX - MASCOT_SIZE / 2
            local destY  = ft
            -- Only nudge if significantly drifted (> 5px)
            local cx = self.container:GetLeft() or destX
            local cy = self.container:GetTop() or destY
            if math.abs(cx - destX) > 5 or math.abs(cy - destY) > 5 then
                local scale = UIParent:GetEffectiveScale()
                self.container:ClearAllPoints()
                self.container:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", destX / scale, destY / scale)
            end
        end
    end

    -- Sleep check
    self:CheckSleep()

    -- Fidget roll (only when idle + awake)
    if not self._sleeping and self._currentAnim == ANIM_IDLE then
        if math.random() < FIDGET_CHANCE / (PERCH_CHECK_INTERVAL * 3) * PERCH_CHECK_INTERVAL then
            self:SetAnimation(ANIM_FIDGET)
            if self._fidgetReturn then self._fidgetReturn:Cancel() end
            self._fidgetReturn = C_Timer.NewTimer(2.5, function()
                M._fidgetReturn = nil
                if M._currentAnim == ANIM_FIDGET then
                    M:SetAnimation(ANIM_IDLE)
                end
            end)
        end
    end
end

-- ============================================================================
-- PUBLIC API
-- ============================================================================

function M:IsEnabled()
    return VC_DB and VC_DB.mascotEnabled ~= false
end

function M:Toggle()
    if not VC_DB then return end
    VC_DB.mascotEnabled = not self:IsEnabled()
    if VC_DB.mascotEnabled then
        self:StartPerchScan()
        print("|cFFA0522D[VC]|r Mascot: shown")
    else
        self:StopPerchScan()
        if self.container then self.container:Hide() end
        print("|cFFA0522D[VC]|r Mascot: hidden")
    end
end

-- ============================================================================
-- REACT TO COMPANION EVENTS
-- ============================================================================

local REACT_EVENTS = {
    VC_QUOTE_READY = true,
    VC_ADDON_ENABLED = true,
}

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

function M:Initialize()
    if self._initialized then return end
    self._initialized = true
    self:CreateFrames()

    -- React on quote delivery
    VC.EventBus:Register("VC_QUOTE_READY", function()
        if M.container and M.container:IsShown() then
            M:React()
        end
    end)

    -- Wake on addon enable
    VC.EventBus:Register("VC_ADDON_ENABLED", function()
        if M.container and M.container:IsShown() then
            M:WakeUp()
        end
    end)

    if self:IsEnabled() then
        self:StartPerchScan()
    end
end
