-- CursorEffects - Main Addon Core
-- WoW Midnight 12.0.1 Compatible

local CE = {}
_G.CursorEffects = CE

-- Default settings
local DEFAULT_COLOR = { r = 0, g = 1, b = 1 }
local DEFAULT_SETTINGS = {
    enabled = true,
    effects = { glow = true },
    glowIntensity = 1.0,
    pulseSpeed = 1.0,
    colors = {
        glow      = { r = 0, g = 1, b = 1 },
        pulse     = { r = 0, g = 1, b = 1 },
        trail     = { r = 0, g = 1, b = 1 },
        sparkle   = { r = 0, g = 1, b = 1 },
        crosshair = { r = 0, g = 1, b = 1 },
    },
    rainbow = {
        glow      = false,
        pulse     = false,
        trail     = false,
        sparkle   = false,
        crosshair = false,
    },
    crosshairEnabled = true,
    crosshairSensitivity = 1.0,
    scale = 1.5,
}

-- Rainbow color cycling time tracker
local rainbowTime = 0

-- Initialize database
function CE:Initialize()
    CE:LoadSettings()
    CE:SetupFrames()
    CE:PrintWelcome()
end

-- Initialize options panel
function CE:InitializeOptions()
    -- Options now self-initializes via its own PLAYER_LOGIN handler
    -- This is kept as a fallback
    C_Timer.After(1.0, function()
        if CursorEffectsOptions and not CursorEffectsOptions.initialized then
            CursorEffectsOptions:Initialize()
        end
    end)
end

-- Load settings into memory
function CE:LoadSettings()
    if not CursorEffectsDB then
        CursorEffectsDB = {}
    end
    
    -- Migrate old effectType string to effects table
    if CursorEffectsDB.effectType ~= nil then
        if not CursorEffectsDB.effects then
            CursorEffectsDB.effects = {}
            local old = CursorEffectsDB.effectType
            if old == "rachel" then
                CursorEffectsDB.effects.rachel = true
            elseif old and old ~= "none" then
                CursorEffectsDB.effects[old] = true
            end
        end
        CursorEffectsDB.effectType = nil
    end

    -- Migrate old single color to per-effect colors table
    if CursorEffectsDB.color and not CursorEffectsDB.colors then
        local c = CursorEffectsDB.color
        CursorEffectsDB.colors = {
            glow      = { r = c.r, g = c.g, b = c.b },
            pulse     = { r = c.r, g = c.g, b = c.b },
            trail     = { r = c.r, g = c.g, b = c.b },
            sparkle   = { r = c.r, g = c.g, b = c.b },
            crosshair = { r = c.r, g = c.g, b = c.b },
        }
        CursorEffectsDB.color = nil
    end

    -- Ensure all default settings exist (shallow)
    for key, value in pairs(DEFAULT_SETTINGS) do
        if CursorEffectsDB[key] == nil then
            if type(value) == "table" then
                CursorEffectsDB[key] = {}
                for k, v in pairs(value) do
                    if type(v) == "table" then
                        CursorEffectsDB[key][k] = {}
                        for k2, v2 in pairs(v) do
                            CursorEffectsDB[key][k][k2] = v2
                        end
                    else
                        CursorEffectsDB[key][k] = v
                    end
                end
            else
                CursorEffectsDB[key] = value
            end
        end
    end

    -- Ensure all per-effect color keys exist
    if CursorEffectsDB.colors then
        for effect, defColor in pairs(DEFAULT_SETTINGS.colors) do
            if not CursorEffectsDB.colors[effect] then
                CursorEffectsDB.colors[effect] = { r = defColor.r, g = defColor.g, b = defColor.b }
            end
        end
    end

    CE.settings = CursorEffectsDB
end

-- Frame pool for recycling trail/sparkle frames
local framePool = {}
local function AcquireFrame()
    local frame = table.remove(framePool)
    if not frame then
        frame = CreateFrame("Frame", nil, UIParent)
        frame:EnableMouse(false)
    end
    frame:Show()
    return frame
end

local function ReleaseFrame(frame)
    frame:Hide()
    frame:ClearAllPoints()
    table.insert(framePool, frame)
end

-- Setup visual frames for cursor effects
function CE:SetupFrames()
    -- Main cursor glow frame
    if not CE.glowFrame then
        CE.glowFrame = CreateFrame("Frame", "CursorEffectsGlowFrame", UIParent)
        CE.glowFrame:SetSize(128, 128)
        CE.glowFrame:SetFrameStrata("TOOLTIP")
        CE.glowFrame:EnableMouse(false)
        
        -- Create outer glow layer using star4 for a soft glow look
        local outerGlow = CE.glowFrame:CreateTexture(nil, "BACKGROUND")
        outerGlow:SetPoint("CENTER")
        outerGlow:SetSize(128, 128)
        outerGlow:SetTexture("Interface\\Cooldown\\star4")
        outerGlow:SetVertexColor(0, 1, 1, 0.15)
        outerGlow:SetBlendMode("ADD")
        CE.glowFrame.outerGlow = outerGlow
        
        -- Create middle glow layer
        local midGlow = CE.glowFrame:CreateTexture(nil, "BACKGROUND")
        midGlow:SetPoint("CENTER")
        midGlow:SetSize(80, 80)
        midGlow:SetTexture("Interface\\Cooldown\\star4")
        midGlow:SetVertexColor(0, 1, 1, 0.25)
        midGlow:SetBlendMode("ADD")
        CE.glowFrame.midGlow = midGlow
        
        -- Create inner bright core
        local innerGlow = CE.glowFrame:CreateTexture(nil, "BACKGROUND")
        innerGlow:SetPoint("CENTER")
        innerGlow:SetSize(40, 40)
        innerGlow:SetTexture("Interface\\Cooldown\\star4")
        innerGlow:SetVertexColor(0, 1, 1, 0.4)
        innerGlow:SetBlendMode("ADD")
        CE.glowFrame.innerGlow = innerGlow
        
        CE.glowFrame:Hide()
    end
    
    -- Trail frame (for movement trails)
    if not CE.trailFrame then
        CE.trailFrame = CreateFrame("Frame", "CursorEffectsTrailFrame", UIParent)
        CE.trailFrame:SetFrameStrata("TOOLTIP")
        CE.trailPoints = {}
    end
    
    -- Update loop
    if not CE.updateFrame then
        CE.updateFrame = CreateFrame("Frame", "CursorEffectsUpdateFrame")
        CE.updateFrame:SetScript("OnUpdate", function(self, elapsed)
            CE:OnUpdate(elapsed)
        end)
    end
end

-- Particle cleanup
function CE:CleanupTrailParticles()
    if not CE.trailParticles then return end
    for i = #CE.trailParticles, 1, -1 do
        ReleaseFrame(CE.trailParticles[i].frame)
        table.remove(CE.trailParticles, i)
    end
end

function CE:CleanupSparkleParticles()
    if not CE.sparklePixels then return end
    for i = #CE.sparklePixels, 1, -1 do
        ReleaseFrame(CE.sparklePixels[i].frame)
        table.remove(CE.sparklePixels, i)
    end
end

function CE:CleanupAllParticles()
    CE:CleanupTrailParticles()
    CE:CleanupSparkleParticles()
end

-- Check if any effect is active
function CE:HasActiveEffect()
    if not CE.settings or not CE.settings.effects then return false end
    for _, v in pairs(CE.settings.effects) do
        if v then return true end
    end
    return false
end

-- Update function called every frame
function CE:OnUpdate(elapsed)
    -- Update rainbow time for smooth color cycling
    rainbowTime = rainbowTime + elapsed
    
    -- Shake detection + crosshair always runs (find-my-cursor utility)
    CE:DetectShake(elapsed)
    CE:UpdateCrosshair(elapsed)

    if not CE.settings or not CE.settings.enabled then
        if CE.glowFrame then CE.glowFrame:Hide() end
        CE:CleanupAllParticles()
        return
    end
    
    if CE:HasActiveEffect() then
        CE:UpdateCursorPosition()
        CE:UpdateEffects(elapsed)
    else
        CE.glowFrame:Hide()
        CE:CleanupAllParticles()
    end
end

-- Update cursor glow position
function CE:UpdateCursorPosition()
    local x, y = GetCursorPosition()
    local scale = UIParent:GetEffectiveScale()
    x = x / scale
    y = y / scale
    
    CE.glowFrame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y)
    CE.glowFrame:Show()
end

-- Rainbow color generation (HSV to RGB conversion)
local function HSVtoRGB(h, s, v)
    local r, g, b
    local i = math.floor(h * 6)
    local f = h * 6 - i
    local p = v * (1 - s)
    local q = v * (1 - f * s)
    local t = v * (1 - (1 - f) * s)
    i = i % 6
    if i == 0 then r, g, b = v, t, p
    elseif i == 1 then r, g, b = q, v, p
    elseif i == 2 then r, g, b = p, v, t
    elseif i == 3 then r, g, b = p, q, v
    elseif i == 4 then r, g, b = t, p, v
    elseif i == 5 then r, g, b = v, p, q
    end
    return { r = r, g = g, b = b }
end

local function GetRainbowColor()
    -- Cycle hue from 0 to 1 over 3 seconds for smooth rainbow
    local hue = (rainbowTime % 3) / 3
    return HSVtoRGB(hue, 1, 1)
end

-- Helper: get per-effect color, fallback to glow color
local function GetEffectColor(effect)
    -- Check if rainbow mode is enabled for this effect
    if CE.settings.rainbow and CE.settings.rainbow[effect] then
        return GetRainbowColor()
    end
    
    local colors = CE.settings.colors
    if colors and colors[effect] then
        return colors[effect]
    end
    return DEFAULT_COLOR
end

-- Update visual effects (multi-select)
function CE:UpdateEffects(elapsed)
    local effects = CE.settings.effects

    -- Rachel mode: all effects with pink
    if effects.rachel then
        local pink = { r = 1, g = 0.4, b = 0.7 }
        CE:UpdateGlowEffect(pink)
        CE:UpdatePulseEffect(pink, elapsed)
        CE:UpdateTrailEffect(pink)
        CE:UpdateSparkleEffect(pink, elapsed)
        return
    end

    -- Glow layers used by glow and pulse
    if effects.glow or effects.pulse then
        if effects.glow then CE:UpdateGlowEffect(GetEffectColor("glow")) end
        if effects.pulse then CE:UpdatePulseEffect(GetEffectColor("pulse"), elapsed) end
    else
        CE.glowFrame.outerGlow:SetVertexColor(0, 0, 0, 0)
        CE.glowFrame.midGlow:SetVertexColor(0, 0, 0, 0)
        CE.glowFrame.innerGlow:SetVertexColor(0, 0, 0, 0)
    end

    if effects.trail then
        CE:UpdateTrailEffect(GetEffectColor("trail"))
    else
        CE:CleanupTrailParticles()
    end

    if effects.sparkle then
        CE:UpdateSparkleEffect(GetEffectColor("sparkle"), elapsed)
    else
        CE:CleanupSparkleParticles()
    end
end

-- Update glow effect
function CE:UpdateGlowEffect(color)
    local alpha = 0.15 + (CE.settings.glowIntensity * 0.25)
    local s = CE.settings.scale
    
    CE.glowFrame.outerGlow:SetSize(128 * s, 128 * s)
    CE.glowFrame.outerGlow:SetVertexColor(color.r, color.g, color.b, alpha)
    CE.glowFrame.midGlow:SetSize(80 * s, 80 * s)
    CE.glowFrame.midGlow:SetVertexColor(color.r, color.g, color.b, alpha + 0.1)
    CE.glowFrame.innerGlow:SetSize(40 * s, 40 * s)
    CE.glowFrame.innerGlow:SetVertexColor(color.r, color.g, color.b, alpha + 0.2)
end

-- Update pulse effect
local pulseTime = 0
function CE:UpdatePulseEffect(color, elapsed)
    pulseTime = pulseTime + (elapsed * CE.settings.pulseSpeed * 2)
    if pulseTime > 2 then pulseTime = 0 end
    
    local pulse = math.abs(math.sin(pulseTime * math.pi))
    local alpha = 0.05 + (pulse * 0.35)
    local ps = 0.8 + (pulse * 0.4)
    local s = CE.settings.scale
    
    CE.glowFrame.outerGlow:SetVertexColor(color.r, color.g, color.b, alpha)
    CE.glowFrame.outerGlow:SetSize(128 * ps * s, 128 * ps * s)
    CE.glowFrame.midGlow:SetVertexColor(color.r, color.g, color.b, alpha + 0.1)
    CE.glowFrame.midGlow:SetSize(80 * ps * s, 80 * ps * s)
    CE.glowFrame.innerGlow:SetVertexColor(color.r, color.g, color.b, alpha + 0.2)
    CE.glowFrame.innerGlow:SetSize(40 * ps * s, 40 * ps * s)
end

-- Update trail effect
local lastTrailX, lastTrailY = 0, 0
local trailCounter = 0
local MAX_TRAIL_PARTICLES = 30

function CE:UpdateTrailEffect(color)
    local x, y = GetCursorPosition()
    local scale = UIParent:GetEffectiveScale()
    x = x / scale
    y = y / scale
    
    -- Only add trail point every 2 frames
    trailCounter = trailCounter + 1
    if trailCounter >= 2 then
        trailCounter = 0
        
        local dx = (x - lastTrailX) ^ 2
        local dy = (y - lastTrailY) ^ 2
        local distance = math.sqrt(dx + dy)
        
        -- Only add trail if cursor moved enough
        if distance > 5 then
            CE:CreateTrailParticle(x, y, color)
            lastTrailX = x
            lastTrailY = y
        end
    end
    
    -- Update and remove old trail particles
    if not CE.trailParticles then CE.trailParticles = {} end
    for i = #CE.trailParticles, 1, -1 do
        local particle = CE.trailParticles[i]
        particle.lifetime = particle.lifetime - 0.016
        
        if particle.lifetime <= 0 then
            ReleaseFrame(particle.frame)
            table.remove(CE.trailParticles, i)
        else
            -- Fade out and shrink
            local pct = particle.lifetime / particle.maxLifetime
            local alpha = pct * 0.7
            local sz = particle.size * pct
            particle.texture:SetVertexColor(color.r, color.g, color.b, alpha)
            particle.texture:SetSize(sz, sz)
            particle.glowTexture:SetVertexColor(color.r, color.g, color.b, alpha * 0.4)
            particle.glowTexture:SetSize(sz * 1.6, sz * 1.6)
            
            -- Gentle rotation
            particle.rotation = particle.rotation + 0.04
            particle.texture:SetRotation(particle.rotation)
        end
    end
end

-- Create a trail particle using WoW's built-in star4 texture
function CE:CreateTrailParticle(x, y, color)
    if not CE.trailParticles then CE.trailParticles = {} end
    
    -- Cap particles to avoid performance issues
    if #CE.trailParticles >= MAX_TRAIL_PARTICLES then
        local oldest = table.remove(CE.trailParticles, 1)
        ReleaseFrame(oldest.frame)
    end
    
    local particle = AcquireFrame()
    local sz = 20 + math.random(0, 12)
    particle:SetSize(sz, sz)
    particle:SetFrameStrata("TOOLTIP")
    particle:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y)
    
    -- Reuse or create the star texture
    local texture = particle.ceTrailTex
    if not texture then
        texture = particle:CreateTexture(nil, "ARTWORK")
        particle.ceTrailTex = texture
    end
    texture:SetAllPoints()
    texture:SetTexture("Interface\\Cooldown\\star4")
    texture:SetBlendMode("ADD")
    texture:SetVertexColor(color.r, color.g, color.b, 0.7)
    texture:Show()
    
    -- Reuse or create glow behind the star
    local glowTexture = particle.ceTrailGlow
    if not glowTexture then
        glowTexture = particle:CreateTexture(nil, "BACKGROUND")
        particle.ceTrailGlow = glowTexture
    end
    glowTexture:SetPoint("CENTER")
    glowTexture:SetSize(sz * 1.6, sz * 1.6)
    glowTexture:SetTexture("Interface\\Cooldown\\star4")
    glowTexture:SetBlendMode("ADD")
    glowTexture:SetVertexColor(color.r, color.g, color.b, 0.3)
    glowTexture:Show()
    
    local particleData = {
        frame = particle,
        texture = texture,
        glowTexture = glowTexture,
        size = sz,
        lifetime = 0.5 + (math.random() * 0.3),
        maxLifetime = 0.8,
        rotation = math.random() * 6.28,
    }
    
    table.insert(CE.trailParticles, particleData)
end

-- Update sparkle effect (twinkling star particles around cursor)
local sparkleTime = 0
local sparkleCounter = 0
local MAX_SPARKLE_PARTICLES = 40

function CE:UpdateSparkleEffect(color, elapsed)
    sparkleTime = sparkleTime + elapsed
    
    -- Create sparkle particles periodically
    sparkleCounter = sparkleCounter + elapsed
    if sparkleCounter >= 0.06 then
        sparkleCounter = 0
        local numPixels = math.random(2, 4)
        for i = 1, numPixels do
            CE:CreateSparklePixel(color)
        end
    end
    
    -- Update existing sparkle pixels
    if not CE.sparklePixels then CE.sparklePixels = {} end
    for i = #CE.sparklePixels, 1, -1 do
        local pixel = CE.sparklePixels[i]
        pixel.lifetime = pixel.lifetime - elapsed
        
        if pixel.lifetime <= 0 then
            ReleaseFrame(pixel.frame)
            table.remove(CE.sparklePixels, i)
        else
            -- Fade and twinkle
            local pct = pixel.lifetime / pixel.maxLifetime
            -- Twinkle: fade in fast then fade out
            local twinkle = math.sin(pct * math.pi)
            local alpha = twinkle * pixel.brightness
            
            -- Color shimmer
            local r = pixel.baseColor.r + (math.sin(pixel.phase + sparkleTime * 5) * 0.2)
            local g = pixel.baseColor.g + (math.cos(pixel.phase + sparkleTime * 3) * 0.2)
            local b = pixel.baseColor.b + (math.sin(pixel.phase + sparkleTime * 7) * 0.2)
            r = math.max(0, math.min(1, r))
            g = math.max(0, math.min(1, g))
            b = math.max(0, math.min(1, b))
            
            pixel.texture:SetVertexColor(r, g, b, alpha)
            
            -- Shrink as it fades
            local sz = pixel.size * (0.3 + pct * 0.7)
            pixel.texture:SetSize(sz, sz)
            
            -- Slow rotation
            pixel.rotation = pixel.rotation + 0.03
            pixel.texture:SetRotation(pixel.rotation)
        end
    end
end

-- Create a sparkle pixel using star4 texture
function CE:CreateSparklePixel(baseColor)
    if not CE.sparklePixels then CE.sparklePixels = {} end
    
    -- Cap particles
    if #CE.sparklePixels >= MAX_SPARKLE_PARTICLES then
        local oldest = table.remove(CE.sparklePixels, 1)
        ReleaseFrame(oldest.frame)
    end
    
    local x, y = GetCursorPosition()
    local scale = UIParent:GetEffectiveScale()
    x = x / scale
    y = y / scale
    
    -- Random position around cursor
    local angle = math.random() * math.pi * 2
    local distance = 10 + math.random() * 30
    local pixelX = x + math.cos(angle) * distance
    local pixelY = y + math.sin(angle) * distance
    
    local pixel = AcquireFrame()
    local sz = 6 + math.random(0, 10)
    pixel:SetSize(sz, sz)
    pixel:SetFrameStrata("TOOLTIP")
    pixel:SetPoint("CENTER", UIParent, "BOTTOMLEFT", pixelX, pixelY)
    
    -- Reuse or create texture
    local texture = pixel.ceSparkleTex
    if not texture then
        texture = pixel:CreateTexture(nil, "ARTWORK")
        pixel.ceSparkleTex = texture
    end
    texture:SetAllPoints()
    texture:SetTexture("Interface\\Cooldown\\star4")
    texture:SetBlendMode("ADD")
    texture:SetVertexColor(baseColor.r, baseColor.g, baseColor.b, 0.8)
    texture:Show()
    
    local brightness = 0.7 + math.random() * 0.5
    local lifetime = 0.3 + math.random() * 0.5
    
    local pixelData = {
        frame = pixel,
        texture = texture,
        baseColor = baseColor,
        phase = math.random() * 6.28,
        brightness = brightness,
        size = sz,
        rotation = math.random() * 6.28,
        lifetime = lifetime,
        maxLifetime = lifetime,
    }
    
    table.insert(CE.sparklePixels, pixelData)
end

---------------------------------------------------------------------------
-- Shake Detection & Crosshair Animation ("find my cursor")
---------------------------------------------------------------------------
local shakeHistory = {}
local shakeCooldown = 0
local SHAKE_WINDOW = 0.5           -- seconds of history to analyze
local SHAKE_COOLDOWN_TIME = 2.0    -- seconds between triggers
local SHAKE_REVERSAL_THRESHOLD = 3 -- direction changes needed

-- Crosshair state
local crosshairActive = false
local crosshairPhase = 0      -- 1=converge, 2=flash, 0=inactive
local crosshairTimer = 0
local CROSSHAIR_CONVERGE_TIME = 0.7
local CROSSHAIR_FLASH_TIME = 0.35  -- on/off duration per half-flash
local CROSSHAIR_START_OFFSET = 200 -- pixels away at start
local CROSSHAIR_ARM_LENGTH = 36
local CROSSHAIR_ARM_WIDTH = 3
local CROSSHAIR_GAP = 6

function CE:SetupCrosshair()
    if CE.crosshairFrame then return end

    local parent = CreateFrame("Frame", nil, UIParent)
    parent:SetSize(1, 1)
    parent:SetFrameStrata("TOOLTIP")
    parent:SetFrameLevel(100)
    parent:EnableMouse(false)
    parent:Hide()
    CE.crosshairFrame = parent

    local arms = {}
    for _, name in ipairs({"top", "bottom", "left", "right"}) do
        local arm = CreateFrame("Frame", nil, parent)
        arm:EnableMouse(false)
        arm:SetFrameStrata("TOOLTIP")
        arm:SetFrameLevel(100)

        local tex = arm:CreateTexture(nil, "OVERLAY")
        tex:SetAllPoints()
        tex:SetColorTexture(1, 1, 1, 1)
        tex:SetBlendMode("ADD")
        arm.texture = tex

        local glow = arm:CreateTexture(nil, "ARTWORK")
        glow:SetPoint("CENTER")
        glow:SetTexture("Interface\\Cooldown\\star4")
        glow:SetBlendMode("ADD")
        arm.glow = glow

        arms[name] = arm
    end

    arms.top:SetSize(CROSSHAIR_ARM_WIDTH, CROSSHAIR_ARM_LENGTH)
    arms.bottom:SetSize(CROSSHAIR_ARM_WIDTH, CROSSHAIR_ARM_LENGTH)
    arms.left:SetSize(CROSSHAIR_ARM_LENGTH, CROSSHAIR_ARM_WIDTH)
    arms.right:SetSize(CROSSHAIR_ARM_LENGTH, CROSSHAIR_ARM_WIDTH)

    for _, arm in pairs(arms) do
        arm.glow:SetSize(CROSSHAIR_ARM_LENGTH * 2.2, CROSSHAIR_ARM_LENGTH * 2.2)
    end

    CE.crosshairArms = arms
end

function CE:DetectShake(elapsed)
    if CE.settings and not CE.settings.crosshairEnabled then return end
    shakeCooldown = math.max(0, shakeCooldown - elapsed)
    if shakeCooldown > 0 then return end

    local x, y = GetCursorPosition()
    local scale = UIParent:GetEffectiveScale()
    x = x / scale
    y = y / scale

    local now = GetTime()
    table.insert(shakeHistory, { x = x, y = y, t = now })

    -- Trim old entries
    while #shakeHistory > 0 and (now - shakeHistory[1].t) > SHAKE_WINDOW do
        table.remove(shakeHistory, 1)
    end

    if #shakeHistory < 6 then return end

    -- Count direction reversals and total travel distance
    local reversals = 0
    local totalDist = 0
    for i = 3, #shakeHistory do
        local dx1 = shakeHistory[i-1].x - shakeHistory[i-2].x
        local dy1 = shakeHistory[i-1].y - shakeHistory[i-2].y
        local dx2 = shakeHistory[i].x   - shakeHistory[i-1].x
        local dy2 = shakeHistory[i].y   - shakeHistory[i-1].y

        local dot  = dx1 * dx2 + dy1 * dy2
        local mag1 = math.sqrt(dx1*dx1 + dy1*dy1)
        local mag2 = math.sqrt(dx2*dx2 + dy2*dy2)

        totalDist = totalDist + mag2

        if dot < 0 and mag1 > 3 and mag2 > 3 then
            reversals = reversals + 1
        end
    end

    -- Apply sensitivity scaling (higher sensitivity = easier to trigger)
    local sensitivity = (CE.settings and CE.settings.crosshairSensitivity) or 1.0
    local reversalThreshold = SHAKE_REVERSAL_THRESHOLD / sensitivity
    local distanceThreshold = 60 / sensitivity
    
    if reversals >= reversalThreshold and totalDist > distanceThreshold then
        CE:TriggerCrosshair()
        shakeHistory = {}
        shakeCooldown = SHAKE_COOLDOWN_TIME
    end
end

function CE:TriggerCrosshair()
    CE:SetupCrosshair()

    crosshairActive = true
    crosshairPhase = 1
    crosshairTimer = 0

    CE.crosshairFrame:Show()

    local color
    if CE.settings and CE.settings.effects and CE.settings.effects.rachel then
        color = { r = 1, g = 0, b = 0 }
    else
        color = GetEffectColor("crosshair")
    end
    for _, arm in pairs(CE.crosshairArms) do
        arm.texture:SetColorTexture(color.r, color.g, color.b, 1)
        arm.glow:SetVertexColor(color.r, color.g, color.b, 0.5)
        arm:Show()
    end
end

function CE:UpdateCrosshair(elapsed)
    if not crosshairActive then return end

    crosshairTimer = crosshairTimer + elapsed

    -- Follow cursor
    local x, y = GetCursorPosition()
    local scale = UIParent:GetEffectiveScale()
    x = x / scale
    y = y / scale

    CE.crosshairFrame:ClearAllPoints()
    CE.crosshairFrame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x, y)

    local arms = CE.crosshairArms

    if crosshairPhase == 1 then
        -----------------------------------------------------------------
        -- Phase 1: Converge — arms fly in from far away
        -----------------------------------------------------------------
        local progress = math.min(crosshairTimer / CROSSHAIR_CONVERGE_TIME, 1)
        local ease = 1 - (1 - progress) * (1 - progress)  -- ease-out
        local offset = CROSSHAIR_START_OFFSET * (1 - ease) + CROSSHAIR_GAP * ease

        arms.top:ClearAllPoints()
        arms.top:SetPoint("BOTTOM", CE.crosshairFrame, "CENTER", 0, offset)
        arms.bottom:ClearAllPoints()
        arms.bottom:SetPoint("TOP", CE.crosshairFrame, "CENTER", 0, -offset)
        arms.left:ClearAllPoints()
        arms.left:SetPoint("RIGHT", CE.crosshairFrame, "CENTER", -offset, 0)
        arms.right:ClearAllPoints()
        arms.right:SetPoint("LEFT", CE.crosshairFrame, "CENTER", offset, 0)

        for _, arm in pairs(arms) do
            arm.texture:SetAlpha(ease)
            arm.glow:SetAlpha(ease * 0.5)
        end

        if progress >= 1 then
            crosshairPhase = 2
            crosshairTimer = 0
        end

    elseif crosshairPhase == 2 then
        -----------------------------------------------------------------
        -- Phase 2: Flash twice, then fade out
        -----------------------------------------------------------------
        arms.top:ClearAllPoints()
        arms.top:SetPoint("BOTTOM", CE.crosshairFrame, "CENTER", 0, CROSSHAIR_GAP)
        arms.bottom:ClearAllPoints()
        arms.bottom:SetPoint("TOP", CE.crosshairFrame, "CENTER", 0, -CROSSHAIR_GAP)
        arms.left:ClearAllPoints()
        arms.left:SetPoint("RIGHT", CE.crosshairFrame, "CENTER", -CROSSHAIR_GAP, 0)
        arms.right:ClearAllPoints()
        arms.right:SetPoint("LEFT", CE.crosshairFrame, "CENTER", CROSSHAIR_GAP, 0)

        local flashCycle = CROSSHAIR_FLASH_TIME * 2   -- on + off = 1 flash
        local totalFlashTime = flashCycle * 2          -- 2 full flashes

        if crosshairTimer < totalFlashTime then
            local pos = crosshairTimer % flashCycle
            local alpha = pos < CROSSHAIR_FLASH_TIME and 1.0 or 0.15
            for _, arm in pairs(arms) do
                arm.texture:SetAlpha(alpha)
                arm.glow:SetAlpha(alpha * 0.5)
            end
        else
            -- Fade out
            local fadeProgress = (crosshairTimer - totalFlashTime) / 0.9
            if fadeProgress >= 1 then
                crosshairActive = false
                CE.crosshairFrame:Hide()
                return
            end
            local alpha = 1 - fadeProgress
            for _, arm in pairs(arms) do
                arm.texture:SetAlpha(alpha)
                arm.glow:SetAlpha(alpha * 0.5)
            end
        end
    end
end

-- Print welcome message
function CE:PrintWelcome()
    print("|cff00ffffCursor Effects|r v1.0.0 - Loaded!")
    print("Type |cff00ffff/ce|r for options")
end

-- Export functions for console commands
function CE:ToggleEffects()
    CE.settings.enabled = not CE.settings.enabled
    if not CE.settings.enabled then
        CE:CleanupAllParticles()
    end
    local status = CE.settings.enabled and "|cff00ff00Enabled|r" or "|cffff0000Disabled|r"
    print("Cursor Effects: " .. status)
end

function CE:SetEffectType(effectType)
    if effectType == "none" then
        CE.settings.effects = {}
        CE:CleanupAllParticles()
        print("All effects disabled")
    elseif effectType == "rachel" then
        CE.settings.effects = { rachel = true }
        print("|cffff66b2Effect type set to: rachel|r")
    elseif effectType == "glow" or effectType == "pulse" or effectType == "trail" or effectType == "sparkle" then
        if CE.settings.effects[effectType] then
            CE.settings.effects[effectType] = nil
            print(effectType .. " |cffff0000disabled|r")
        else
            CE.settings.effects[effectType] = true
            CE.settings.effects.rachel = nil
            print(effectType .. " |cff00ff00enabled|r")
        end
    else
        print("Unknown effect. Valid: glow, pulse, trail, sparkle, none")
    end
end

function CE:SetMouseSize(size)
    size = tonumber(size) or 1.0
    CE.settings.scale = math.max(0.5, math.min(size, 3.0))
    print(string.format("Mouse size set to: %.1f", CE.settings.scale))
end

function CE:SetGlowIntensity(intensity)
    intensity = tonumber(intensity) or 1.0
    CE.settings.glowIntensity = math.max(0, math.min(intensity, 2.0))
    print(string.format("Glow intensity set to: %.1f", CE.settings.glowIntensity))
end

-- Named color lookup table
local COLOR_NAMES = {
    red         = { r = 1,    g = 0,    b = 0    },
    green       = { r = 0,    g = 1,    b = 0    },
    blue        = { r = 0,    g = 0,    b = 1    },
    cyan        = { r = 0,    g = 1,    b = 1    },
    magenta     = { r = 1,    g = 0,    b = 1    },
    yellow      = { r = 1,    g = 1,    b = 0    },
    orange      = { r = 1,    g = 0.5,  b = 0    },
    pink        = { r = 1,    g = 0.4,  b = 0.7  },
    purple      = { r = 0.6,  g = 0,    b = 1    },
    white       = { r = 1,    g = 1,    b = 1    },
    gold        = { r = 1,    g = 0.84, b = 0    },
    lime        = { r = 0.5,  g = 1,    b = 0    },
    teal        = { r = 0,    g = 0.8,  b = 0.8  },
    coral       = { r = 1,    g = 0.5,  b = 0.31 },
    lavender    = { r = 0.7,  g = 0.5,  b = 1    },
    mint        = { r = 0.4,  g = 1,    b = 0.6  },
    crimson     = { r = 0.86, g = 0.08, b = 0.24 },
    skyblue     = { r = 0.4,  g = 0.75, b = 1    },
    peach       = { r = 1,    g = 0.8,  b = 0.6  },
    violet      = { r = 0.5,  g = 0,    b = 0.8  },
}

-- Valid effect keys for per-effect color assignment
local EFFECT_KEYS = { glow = true, pulse = true, trail = true, sparkle = true, crosshair = true }

function CE:SetColor(args)
    -- Parse: "/ce color <name>" sets all, "/ce color <effect> <name>" sets one
    local parts = {}
    for word in args:gmatch("%S+") do table.insert(parts, word:lower()) end

    local effectKey, colorName
    if #parts == 2 and EFFECT_KEYS[parts[1]] then
        effectKey = parts[1]
        colorName = parts[2]
    elseif #parts == 1 then
        colorName = parts[1]
    else
        print("Usage: /ce color <name>  or  /ce color <effect> <name>")
        print("Effects: glow, pulse, trail, sparkle, crosshair")
        return
    end

    local colorData = COLOR_NAMES[colorName]
    if not colorData then
        print("Unknown color. Available colors:")
        local names = {}
        for k in pairs(COLOR_NAMES) do table.insert(names, k) end
        table.sort(names)
        print("|cff00ffff" .. table.concat(names, ", ") .. "|r")
        return
    end

    local hex = string.format("%02x%02x%02x", colorData.r * 255, colorData.g * 255, colorData.b * 255)
    if effectKey then
        CE.settings.colors[effectKey] = { r = colorData.r, g = colorData.g, b = colorData.b }
        print(effectKey .. " color set to: |cff" .. hex .. colorName .. "|r")
    else
        for k in pairs(EFFECT_KEYS) do
            CE.settings.colors[k] = { r = colorData.r, g = colorData.g, b = colorData.b }
        end
        print("All colors set to: |cff" .. hex .. colorName .. "|r")
    end
end

-- Slash command handler
SLASH_CURSOREFFECTS1 = "/ce"
SLASH_CURSOREFFECTS2 = "/cursoreffects"
SlashCmdList["CURSOREFFECTS"] = function(msg)
    -- Initialize if not already done
    if not CE.settings then
        CE:Initialize()
    end
    
    local cmd, arg = msg:match("^(%S*)%s*(.*)$")
    cmd = cmd:lower()
    
    if cmd == "toggle" or cmd == "" then
        CE:ToggleEffects()
    elseif cmd == "effect" then
        CE:SetEffectType(arg)
    elseif cmd == "size" then
        CE:SetMouseSize(arg)
    elseif cmd == "intensity" then
        CE:SetGlowIntensity(arg)
    elseif cmd == "color" then
        if arg and arg ~= "" then
            CE:SetColor(arg)
        else
            print("Usage: /ce color <name> (e.g., /ce color cyan)")
        end
    elseif cmd == "crosshair" then
        CE.settings.crosshairEnabled = not CE.settings.crosshairEnabled
        local status = CE.settings.crosshairEnabled and "|cff00ff00Enabled|r" or "|cffff0000Disabled|r"
        print("Shake-to-find crosshair: " .. status)
    elseif cmd == "rainbow" then
        -- Toggle rainbow mode for effect(s)
        if not CE.settings.rainbow then CE.settings.rainbow = {} end
        local effect = arg and arg:lower() or nil
        if effect and EFFECT_KEYS[effect] then
            -- Toggle rainbow for specific effect
            CE.settings.rainbow[effect] = not CE.settings.rainbow[effect]
            local status = CE.settings.rainbow[effect] and "|cff00ff00Enabled|r" or "|cffff0000Disabled|r"
            print("Rainbow mode for " .. effect .. ": " .. status)
        elseif effect == "all" or not effect or effect == "" then
            -- Toggle rainbow for all effects
            local anyEnabled = false
            for k in pairs(EFFECT_KEYS) do
                if CE.settings.rainbow[k] then
                    anyEnabled = true
                    break
                end
            end
            local newState = not anyEnabled
            for k in pairs(EFFECT_KEYS) do
                CE.settings.rainbow[k] = newState
            end
            local status = newState and "|cff00ff00Enabled|r" or "|cffff0000Disabled|r"
            print("Rainbow mode for all effects: " .. status)
        else
            print("Usage: /ce rainbow [effect|all] - Toggle rainbow color cycling")
            print("Valid effects: glow, pulse, trail, sparkle, crosshair, all")
        end
    elseif cmd == "help" then
        CE:PrintHelp()
    elseif cmd == "options" or cmd == "settings" then
        if CursorEffectsOptions and CursorEffectsOptions.OpenOptions then
            CursorEffectsOptions:OpenOptions()
        elseif Settings and Settings.OpenToCategory then
            Settings.OpenToCategory("Cursor Effects")
        else
            print("Cursor Effects: Options panel not available.")
        end
    else
        print("Unknown command. Type /ce help for options")
    end
end

function CE:PrintHelp()
    print("|cff00ffffCursor Effects Help|r")
    print("/ce toggle - Toggle effects on/off")
    print("/ce effect <type> - Toggle an effect (glow, pulse, trail, sparkle, none)")
    print("/ce size <scale> - Set cursor size (0.5-3.0)")
    print("/ce intensity <value> - Set glow intensity (0-2.0)")
    print("/ce color <name> - Set all colors (e.g., /ce color cyan)")
    print("/ce color <effect> <name> - Set one effect's color (glow, pulse, trail, sparkle, crosshair)")
    print("/ce rainbow [effect|all] - Toggle rainbow cycling (glow, pulse, trail, sparkle, crosshair, all)")
    print("/ce crosshair - Toggle shake-to-find crosshair on/off")
    print("/ce options - Open the settings panel")
    print("/ce help - Show this help message")
end

-- Register events at file scope (ensures ADDON_LOADED is caught)
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:SetScript("OnEvent", function(self, event, ...)
    if event == "ADDON_LOADED" then
        local addonName = ...
        if addonName == "CursorEffects" then
            CE:Initialize()
        end
    elseif event == "PLAYER_LOGIN" then
        CE:InitializeOptions()
    end
end)
