-- WoWTetris Game Logic
-- Core gameplay mechanics

local addonName, addon = ...

-- Affix definitions (harder = more bonus)
addon.AFFIXES = {
    -- Easy affixes (+50%)
    {id = "volatile", name = "Volatile", description = "Random block drops every 15 sec (despawns after 15s)", scoreBonus = 0.50, tier = 1},
    {id = "quaking", name = "Quaking", description = "All blocks shift randomly every 25 sec", scoreBonus = 0.50, tier = 1},
    -- Medium affixes (+50%)
    {id = "necrotic", name = "Necrotic", description = "1 block spawns on bottom every 20 sec (despawns after 15s)", scoreBonus = 0.50, tier = 2},
    {id = "bolstering", name = "Bolstering", description = "Every 8 line clears, add 1 garbage row", scoreBonus = 0.50, tier = 2},
    {id = "tyrannical", name = "Tyrannical", description = "Pieces lock 25% faster", scoreBonus = 0.50, tier = 2},
    -- Hard affixes (+50%)
    {id = "sanguine", name = "Sanguine", description = "Cleared rows leave 1 block behind (despawns after 12s)", scoreBonus = 0.50, tier = 3},
    {id = "raging", name = "Raging", description = "Speed +15% when board above row 10", scoreBonus = 0.50, tier = 3},
    {id = "bursting", name = "Bursting", description = "Clearing 3+ lines adds 1 random block (despawns after 15s)", scoreBonus = 0.50, tier = 3},
    -- Nightmare affixes (+50%)
    {id = "grievous", name = "Grievous", description = "Board rises 1 row every 45 seconds", scoreBonus = 0.50, tier = 4},
    {id = "explosive", name = "Explosive", description = "Random block EXPLODES (destroys adjacent) every 20 sec", scoreBonus = 0.50, tier = 4},
}

-- Class power-up definitions
addon.CLASS_POWERUPS = {
    WARRIOR = {name = "Battle Shout", icon = "Interface\\Icons\\Ability_Warrior_BattleShout", description = "+15% score for 10 seconds", duration = 10},
    PALADIN = {name = "Divine Shield", icon = "Interface\\Icons\\Spell_Holy_DivineIntervention", description = "Block next garbage row or sabotage", duration = 0},
    HUNTER = {name = "Eagle Eye", icon = "Interface\\Icons\\Ability_Hunter_SniperShot", description = "See 1 extra preview piece for 30 seconds", duration = 30},
    ROGUE = {name = "Shadow Dance", icon = "Interface\\Icons\\Ability_Rogue_ShadowDance", description = "Next 2 line clears give double points", charges = 2},
    PRIEST = {name = "Renew", icon = "Interface\\Icons\\Spell_Holy_Renew", description = "Remove 3 random blocks from board", duration = 0},
    DEATHKNIGHT = {name = "Death Strike", icon = "Interface\\Icons\\Spell_DeathKnight_ButcherPresence", description = "Remove bottom incomplete row", duration = 0},
    SHAMAN = {name = "Earthbind", icon = "Interface\\Icons\\Spell_Nature_StrengthOfEarthTotem02", description = "Slow drop speed 20% for 8 seconds", duration = 8},
    MAGE = {name = "Ice Block", icon = "Interface\\Icons\\Spell_Frost_Frost", description = "Pause drop timer for 3 seconds", duration = 3},
    WARLOCK = {name = "Drain Life", icon = "Interface\\Icons\\Spell_Shadow_LifeDrain02", description = "Convert 2 garbage blocks to regular", duration = 0},
    DRUID = {name = "Barkskin", icon = "Interface\\Icons\\Spell_Nature_StoneClawTotem", description = "Next piece ignores collision once", duration = 0},
    -- Premium classes
    MONK = {name = "Fortifying Brew", icon = "Interface\\Icons\\Ability_Monk_FortifyingAle", description = "Slow drop 30% for 12 seconds", duration = 12},
    DEMONHUNTER = {name = "Blur", icon = "Interface\\Icons\\Ability_DemonHunter_Blur", description = "Next 3 pieces can pass through 1 block", charges = 3},
    EVOKER = {name = "Obsidian Scales", icon = "Interface\\Icons\\Ability_Evoker_ObsidianScales", description = "Block next 2 garbage or sabotage", charges = 2},
}

-- Active affixes for current game
addon.activeAffixes = {}
addon.affixTimers = {}

-- Power-up state
addon.activePowerUps = {}  -- {effectName = endTime or charges}
addon.powerUpBlock = nil   -- Location of power-up block on board {x, y}

-- Game state
addon.gameState = {
    board = {},           -- 2D array of the game board
    currentPiece = nil,   -- Current falling piece
    nextPieces = {},      -- Queue of next pieces (show 3-6)
    heldPiece = nil,      -- Held piece
    canHold = true,       -- Can swap with held piece
    score = 0,
    level = 1,
    linesCleared = 0,
    totalLines = 0,
    totalLinesForAffixes = 0, -- Track for bolstering
    gameTime = 0,
    isPlaying = false,
    isPaused = false,
    isGameOver = false,
    dropTimer = 0,
    lockTimer = 0,
    lockDelay = 0.5,      -- Seconds before piece locks
    lockDelayBonus = 0,   -- Bonus from ultimates
    lockMoves = 0,        -- Moves made during lock delay
    maxLockMoves = 8,     -- Max moves before forced lock (prevents infinite stalling)
    speedMultiplier = 1.0,
    scoreMultiplier = 1.0,
    lastBoardState = nil, -- For undo
}

-- Scoring values
local SCORE_VALUES = {
    softDrop = 1,         -- Per cell
    hardDrop = 2,         -- Per cell
    single = 100,         -- 1 line
    double = 300,         -- 2 lines
    triple = 500,         -- 3 lines
    tetris = 800,         -- 4 lines
    tSpin = 400,          -- T-spin no lines
    tSpinSingle = 800,    -- T-spin 1 line
    tSpinDouble = 1200,   -- T-spin 2 lines
    tSpinTriple = 1600,   -- T-spin 3 lines
    backToBack = 1.5,     -- Multiplier for back-to-back tetris/t-spin
    combo = 50,           -- Per combo
}

-- Calculate drop speed based on time played
function addon:GetDropSpeed()
    local state = addon.gameState
    
    -- Base speed: 1 second drop time
    local speed = 1.0
    
    -- Time-based speed increase: +20% faster every 30 seconds
    local gameTime = state.gameTime or 0
    local intervals = math.floor(gameTime / 30)  -- Number of 30-second intervals
    local speedMultFromTime = 1 + (intervals * 0.20)  -- +20% faster per 30 seconds
    speed = speed / speedMultFromTime
    
    -- Minimum speed cap (don't go below 0.05 seconds)
    speed = math.max(0.05, speed)
    
    -- Apply speed multiplier from ultimates
    local multiplier = state.speedMultiplier or 1.0
    speed = speed / multiplier  -- Higher multiplier = slower (inverted for slow effects)
    
    -- Apply PvP effects
    local effects = addon.pvpEffects or {}
    if effects.speedMultiplier then
        speed = speed / effects.speedMultiplier  -- 2x speed = half the drop time
    end
    
    -- Affix: Raging - +15% speed when board above row 10
    if addon:HasAffix("raging") then
        local highestBlock = addon:GetHighestBlockRow()
        if highestBlock and highestBlock <= 10 then
            speed = speed * 0.85  -- 15% faster
        end
    end
    
    -- Affix: Tyrannical - 25% faster lock (handled in lock delay, not drop speed)
    
    -- Power-up: Earthbind (Shaman) - 20% slower
    if addon.activePowerUps.earthbind and addon.activePowerUps.earthbind > GetTime() then
        speed = speed * 1.20  -- 20% slower
    end
    
    -- Power-up: Ice Block (Mage) - pause drop timer
    if addon.activePowerUps.iceblock and addon.activePowerUps.iceblock > GetTime() then
        speed = 999  -- Effectively paused
    end
    
    -- Apply timed effects for slow/fast drops
    if addon.HasTimedEffect and addon:HasTimedEffect("slowDrop") then
        speed = speed * 2  -- 50% slower
    end
    if addon.HasTimedEffect and addon:HasTimedEffect("slowDrop75") then
        speed = speed * 4  -- 75% slower
    end
    if addon.HasTimedEffect and addon:HasTimedEffect("pause") then
        speed = 999  -- Paused
    end
    if addon.HasTimedEffect and addon:HasTimedEffect("immune") then
        speed = 999  -- No blocks land
    end
    
    return speed
end

-- Get time-based score multiplier: +20% every 15 seconds (survival bonus)
function addon:GetTimeScoreMultiplier()
    local gameTime = addon.gameState.gameTime or 0
    local intervals = math.floor(gameTime / 15)  -- Number of 15-second intervals
    return 1 + (intervals * 0.20)  -- +20% per 15 seconds
end

-- Get speed-based score multiplier: +20% every 30 seconds (matches drop speed increase)
function addon:GetSpeedScoreMultiplier()
    local gameTime = addon.gameState.gameTime or 0
    local intervals = math.floor(gameTime / 30)  -- Number of 30-second intervals
    return 1 + (intervals * 0.20)  -- +20% per 30 seconds (reward for faster drops)
end

-- Get affix score multiplier
function addon:GetAffixScoreMultiplier()
    local multiplier = 1.0
    local affixCount = 0
    
    if addon.activeAffixes then
        for _, affixId in ipairs(addon.activeAffixes) do
            for _, affix in ipairs(addon.AFFIXES) do
                if affix.id == affixId then
                    multiplier = multiplier + affix.scoreBonus
                    affixCount = affixCount + 1
                    break
                end
            end
        end
    end
    
    -- Power-up: Battle Shout (Warrior) - +15% score
    if addon.activePowerUps and addon.activePowerUps.battleshout and addon.activePowerUps.battleshout > GetTime() then
        multiplier = multiplier + 0.15
    end
    
    return multiplier
end

-- Check if affix is active
function addon:HasAffix(affixId)
    for _, id in ipairs(addon.activeAffixes) do
        if id == affixId then return true end
    end
    return false
end

-- Get highest block row (lowest Y value with blocks)
function addon:GetHighestBlockRow()
    local state = addon.gameState
    for y = 1, addon.BOARD_HEIGHT do
        for x = 1, addon.BOARD_WIDTH do
            if state.board[y] and state.board[y][x] then
                return y
            end
        end
    end
    return nil
end

-- Initialize/reset the game board
function addon:InitializeBoard()
    addon.gameState.board = {}
    for y = 1, addon.BOARD_HEIGHT do
        addon.gameState.board[y] = {}
        for x = 1, addon.BOARD_WIDTH do
            addon.gameState.board[y][x] = nil
        end
    end
    
    -- Force UI refresh to clear visual artifacts
    if addon.mainFrame and addon.mainFrame:IsShown() then
        addon:UpdateUI()
    end
end

-- Start a new game
function addon:StartGame()
    if not addon.selectedClass then
        addon:ShowClassSelection()
        return
    end
    
    addon:InitializeBoard()
    addon:ResetPieceBag()
    
    local state = addon.gameState
    state.score = 0
    state.linesCleared = 0
    state.totalLines = 0
    state.gameTime = 0
    state.isPlaying = true
    state.isPaused = false
    state.isGameOver = false
    state.dropTimer = 0
    state.lockTimer = 0
    state.lockMoves = 0
    state.heldPiece = nil
    state.canHold = true
    state.combo = 0
    state.lastClearWasDifficult = false
    state.speedMultiplier = 1.0
    state.scoreMultiplier = 1.0
    state.lockDelayBonus = 0
    state.lastBoardState = nil
    
    -- Reset ultimate system
    addon.ultimateCooldown = 0
    addon.ultimateChargeUsed = 0  -- Reset charge tracking
    addon.ultimateEffects = {}
    
    -- Reset PvP effects
    addon.pvpEffects = {}
    addon.lastMoveTime = nil
    
    -- Reset racial for new round
    addon.racialUsedThisRound = false
    
    -- Validate ultimate belongs to current class (check both base and shop)
    if addon.selectedUltimate then
        local available = addon:GetAvailableUltimates()
        local valid = false
        for _, ult in ipairs(available) do
            if ult.name == addon.selectedUltimate.name then
                valid = true
                -- Update execute function
                if ult.execute then
                    addon.selectedUltimate.execute = ult.execute
                end
                break
            end
        end
        if not valid then
            addon.selectedUltimate = nil  -- Clear invalid ultimate
        end
    end
    
    -- If no valid ultimate, select first available for current class
    if not addon.selectedUltimate then
        local available = addon:GetAvailableUltimates()
        for _, ult in ipairs(available) do
            if not ult.locked then
                addon.selectedUltimate = ult
                break
            end
        end
    end
    
    -- Lock the ultimate for this game
    addon.ultimateLocked = true
    
    -- Reset affix state
    state.totalLinesForAffixes = 0
    addon.activePowerUps = {}
    addon.powerUpBlock = nil
    
    -- Start affix timers if any active
    addon:StartAffixTimers()
    
    -- Fill next piece queue (6 for extended vision)
    state.nextPieces = {}
    for i = 1, 6 do
        table.insert(state.nextPieces, addon:GetNextPiece())
    end
    
    -- Spawn first piece
    addon:SpawnNextPiece()
    
    -- Update global stats
    addon.db.gamesPlayed = addon.db.gamesPlayed + 1
    
    -- Initialize class stats if needed
    addon:InitClassStats(addon.selectedClass)
    
    -- Reset quest counters for this game
    if addon.ResetGameQuestCounters then addon:ResetGameQuestCounters() end
    
    -- Update class-themed background
    addon:UpdateClassBackground()
    
    -- Show game frame, hide menu
    addon:ShowGame()
    
    -- Update UI
    addon:UpdateUI()
    
    -- Start background music
    addon:StartMusic()
end

-- Spawn the next piece
function addon:SpawnNextPiece()
    local state = addon.gameState
    
    -- Get next piece from queue
    local pieceType = table.remove(state.nextPieces, 1)
    table.insert(state.nextPieces, addon:GetNextPiece())
    
    state.currentPiece = addon:CreatePiece(pieceType)
    state.canHold = true
    state.lockTimer = 0
    state.lockMoves = 0
    
    -- Check if spawn position is valid (game over check)
    if not addon:IsValidPosition(state.currentPiece) then
        addon:GameOver()
    end
    
    addon:UpdateUI()
end

-- Check if a piece position is valid
function addon:IsValidPosition(piece, offsetX, offsetY, rotation)
    offsetX = offsetX or 0
    offsetY = offsetY or 0
    rotation = rotation or piece.rotation
    
    local blocks = addon:GetPieceBlocks(piece.type, rotation)
    local state = addon.gameState
    
    for _, block in ipairs(blocks) do
        local x = piece.x + block[1] + offsetX + 1  -- +1 for 1-indexed board
        local y = piece.y + block[2] + offsetY + 1
        
        -- Check bounds
        if x < 1 or x > addon.BOARD_WIDTH then
            return false
        end
        if y > addon.BOARD_HEIGHT then
            return false
        end
        
        -- Check collision with placed blocks (ignore blocks above the board)
        if y >= 1 and state.board[y] and state.board[y][x] then
            return false
        end
    end
    
    return true
end

-- Move piece left
function addon:MovePieceLeft()
    local state = addon.gameState
    if not state.isPlaying or state.isPaused or not state.currentPiece then return end
    
    -- Check for slow movement effect
    local effects = addon.pvpEffects or {}
    if effects.slowMovement then
        local now = GetTime()
        if addon.lastMoveTime and (now - addon.lastMoveTime) < 0.2 then
            return false  -- Too fast, slow down!
        end
        addon.lastMoveTime = now
    end
    
    if addon:IsValidPosition(state.currentPiece, -1, 0) then
        state.currentPiece.x = state.currentPiece.x - 1
        addon:ResetLockTimer()
        addon:PlaySound("move")
        addon:UpdateUI()
        return true
    end
    return false
end

-- Move piece right
function addon:MovePieceRight()
    local state = addon.gameState
    if not state.isPlaying or state.isPaused or not state.currentPiece then return end
    
    -- Check for slow movement effect
    local effects = addon.pvpEffects or {}
    if effects.slowMovement then
        local now = GetTime()
        if addon.lastMoveTime and (now - addon.lastMoveTime) < 0.2 then
            return false  -- Too fast, slow down!
        end
        addon.lastMoveTime = now
    end
    
    if addon:IsValidPosition(state.currentPiece, 1, 0) then
        state.currentPiece.x = state.currentPiece.x + 1
        addon:ResetLockTimer()
        addon:PlaySound("move")
        addon:UpdateUI()
        return true
    end
    return false
end

-- Soft drop (move down faster)
function addon:SoftDrop()
    local state = addon.gameState
    if not state.isPlaying or state.isPaused or not state.currentPiece then return end
    
    if addon:IsValidPosition(state.currentPiece, 0, 1) then
        state.currentPiece.y = state.currentPiece.y + 1
        local totalMult = addon:GetAffixScoreMultiplier() * addon:GetTimeScoreMultiplier() * addon:GetSpeedScoreMultiplier()
        state.score = state.score + math.floor(SCORE_VALUES.softDrop * totalMult)
        state.dropTimer = 0
        addon:UpdateUI()
        return true
    end
    return false
end

-- Hard drop (instant drop)
function addon:HardDrop()
    local state = addon.gameState
    if not state.isPlaying or state.isPaused or not state.currentPiece then return end
    
    local dropDistance = 0
    while addon:IsValidPosition(state.currentPiece, 0, 1) do
        state.currentPiece.y = state.currentPiece.y + 1
        dropDistance = dropDistance + 1
    end
    
    local totalMult = addon:GetAffixScoreMultiplier() * addon:GetTimeScoreMultiplier() * addon:GetSpeedScoreMultiplier()
    state.score = state.score + math.floor(dropDistance * SCORE_VALUES.hardDrop * totalMult)
    addon:PlaySound("drop")
    addon:LockPiece()
end

-- Rotate piece clockwise
function addon:RotateClockwise()
    local state = addon.gameState
    if not state.isPlaying or state.isPaused or not state.currentPiece then return end
    
    local piece = state.currentPiece
    local newRotation = (piece.rotation + 1) % 4
    
    -- Try wall kicks
    local kickTable = addon:GetWallKickTable(piece.type)
    local kickKey = piece.rotation .. ">" .. newRotation
    local kicks = kickTable[kickKey]
    
    for _, kick in ipairs(kicks) do
        if addon:IsValidPosition(piece, kick[1], kick[2], newRotation) then
            piece.x = piece.x + kick[1]
            piece.y = piece.y + kick[2]
            piece.rotation = newRotation
            addon:ResetLockTimer()
            addon:PlaySound("rotate")
            addon:UpdateUI()
            return true
        end
    end
    
    return false
end

-- Rotate piece counter-clockwise
function addon:RotateCounterClockwise()
    local state = addon.gameState
    if not state.isPlaying or state.isPaused or not state.currentPiece then return end
    
    local piece = state.currentPiece
    local newRotation = (piece.rotation - 1 + 4) % 4
    
    -- Try wall kicks
    local kickTable = addon:GetWallKickTable(piece.type)
    local kickKey = piece.rotation .. ">" .. newRotation
    local kicks = kickTable[kickKey]
    
    for _, kick in ipairs(kicks) do
        if addon:IsValidPosition(piece, kick[1], kick[2], newRotation) then
            piece.x = piece.x + kick[1]
            piece.y = piece.y + kick[2]
            piece.rotation = newRotation
            addon:ResetLockTimer()
            addon:PlaySound("rotate")
            addon:UpdateUI()
            return true
        end
    end
    
    return false
end

-- Hold piece
function addon:HoldPiece()
    local state = addon.gameState
    if not state.isPlaying or state.isPaused or not state.currentPiece or not state.canHold then return end
    
    local currentType = state.currentPiece.type
    
    if state.heldPiece then
        -- Swap with held piece
        state.currentPiece = addon:CreatePiece(state.heldPiece)
    else
        -- No held piece, get next piece
        addon:SpawnNextPiece()
    end
    
    state.heldPiece = currentType
    state.canHold = false
    addon:PlaySound("hold")
    addon:UpdateUI()
end

-- Reset lock timer when piece moves
function addon:ResetLockTimer()
    local state = addon.gameState
    if state.lockTimer > 0 then
        state.lockTimer = 0
        state.lockMoves = state.lockMoves + 1
    end
end

-- Lock the current piece in place
function addon:LockPiece()
    local state = addon.gameState
    local piece = state.currentPiece
    if not piece then return end
    
    -- Save board state for potential undo
    state.lastBoardState = {}
    for y = 1, addon.BOARD_HEIGHT do
        state.lastBoardState[y] = {}
        for x = 1, addon.BOARD_WIDTH do
            state.lastBoardState[y][x] = state.board[y][x]
        end
    end
    
    local blocks = addon:GetPieceBlocks(piece.type, piece.rotation)
    local texture = addon:GetPieceTexture(piece.type)
    local r, g, b = addon:GetPieceColor(piece.type)
    
    -- Place blocks on board
    for _, block in ipairs(blocks) do
        local x = piece.x + block[1] + 1
        local y = piece.y + block[2] + 1
        
        if y >= 1 and y <= addon.BOARD_HEIGHT then
            state.board[y][x] = {r = r, g = g, b = b, type = piece.type, texture = texture}
        end
    end
    
    -- Check for line clears
    local linesCleared = addon:ClearLines()
    
    -- Avatar effect: extra row clear on every drop
    if addon:HasEffect("avatar") and linesCleared == 0 then
        addon:Ultimate_ClearBottomRow()
    end
    
    -- Update score
    addon:UpdateScore(linesCleared)
    
    -- Try to spawn power-up block (5% chance)
    addon:TrySpawnPowerUp()
    
    -- Reset lock delay bonus counter if applicable
    if state.lockDelayBonus > 0 then
        local hasEffect = addon:HasEffect("extendedLock")
        if hasEffect then
            local remaining = addon:GetEffectTime("extendedLock")
            if remaining <= 1 then
                state.lockDelayBonus = 0
            end
        end
    end
    
    -- Spawn next piece
    addon:SpawnNextPiece()
end

-- Check and clear completed lines
function addon:ClearLines()
    local state = addon.gameState
    local linesCleared = 0
    local linesToClear = {}
    local powerUpCleared = false
    
    -- Find completed lines
    for y = addon.BOARD_HEIGHT, 1, -1 do
        local complete = true
        for x = 1, addon.BOARD_WIDTH do
            if not state.board[y][x] then
                complete = false
                break
            end
        end
        if complete then
            -- Check if power-up block is in this line
            if addon.powerUpBlock and addon.powerUpBlock.y == y then
                powerUpCleared = true
            end
            table.insert(linesToClear, y)
            linesCleared = linesCleared + 1
        end
    end
    
    -- Affix: Sanguine - leave 1 random block behind (before clearing)
    local sanguineBlocks = {}
    if addon:HasAffix("sanguine") and #linesToClear > 0 then
        for _, lineY in ipairs(linesToClear) do
            table.insert(sanguineBlocks, {x = math.random(addon.BOARD_WIDTH), y = lineY})
        end
    end
    
    -- Clear the lines (from bottom to top)
    for _, lineY in ipairs(linesToClear) do
        -- Remove the line and shift everything down
        for y = lineY, 2, -1 do
            state.board[y] = state.board[y - 1]
        end
        state.board[1] = {}
        for x = 1, addon.BOARD_WIDTH do
            state.board[1][x] = nil
        end
        
        -- Adjust other line indices
        for i, otherY in ipairs(linesToClear) do
            if otherY < lineY then
                linesToClear[i] = otherY + 1
            end
        end
        
        -- Adjust sanguine block positions
        for i, block in ipairs(sanguineBlocks) do
            if block.y < lineY then
                sanguineBlocks[i].y = block.y + 1
            end
        end
    end
    
    -- Apply Sanguine blocks after clearing (temporary, 12 sec)
    for _, block in ipairs(sanguineBlocks) do
        if not state.board[block.y] then state.board[block.y] = {} end
        addon:AddTemporaryBlock(block.x, block.y, {0.6, 0.2, 0.2}, 12)
    end
    
    -- Activate power-up if cleared
    if powerUpCleared then
        addon:ActivatePowerUp()
    end
    
    -- Clear power-up tracking if it was cleared
    if powerUpCleared then
        addon.powerUpBlock = nil
    end
    
    if linesCleared > 0 then
        state.linesCleared = state.linesCleared + linesCleared
        state.totalLines = state.totalLines + linesCleared
        addon.db.totalLinesCleared = addon.db.totalLinesCleared + linesCleared
        
        -- Play appropriate sound
        if linesCleared >= 4 then
            addon:PlaySound("tetris")
            -- Track tetris for quests
            if addon.TrackTetris then addon:TrackTetris() end
        else
            addon:PlaySound("lineClear")
        end
    end
    
    return linesCleared
end

-- Update score based on lines cleared
function addon:UpdateScore(linesCleared)
    local state = addon.gameState
    local baseScore = 0
    
    if linesCleared == 1 then
        baseScore = SCORE_VALUES.single
    elseif linesCleared == 2 then
        baseScore = SCORE_VALUES.double
    elseif linesCleared == 3 then
        baseScore = SCORE_VALUES.triple
    elseif linesCleared >= 4 then
        baseScore = SCORE_VALUES.tetris
    end
    
    -- Build up total multiplier first, then apply once
    local totalMultiplier = 1.0
    
    -- Score multiplier from ultimates
    totalMultiplier = totalMultiplier * (state.scoreMultiplier or 1.0)
    
    -- Apply timed effects for score bonuses
    if addon.HasTimedEffect and addon:HasTimedEffect("doubleScore") then
        totalMultiplier = totalMultiplier * 2
    end
    if addon.HasTimedEffect and addon:HasTimedEffect("tripleScore") then
        totalMultiplier = totalMultiplier * 3
    end
    if addon.HasTimedEffect and addon:HasTimedEffect("bonusScore50") then
        totalMultiplier = totalMultiplier * 1.5
    end
    
    -- Time-based score multiplier (+20% every 15 seconds survival)
    totalMultiplier = totalMultiplier * addon:GetTimeScoreMultiplier()
    
    -- Speed-based score multiplier (+20% every 30 seconds, matches speed increase)
    totalMultiplier = totalMultiplier * addon:GetSpeedScoreMultiplier()
    
    -- Affix score multiplier (+50% per affix, so 3 affixes = 2.5x)
    totalMultiplier = totalMultiplier * addon:GetAffixScoreMultiplier()
    
    -- Calculate final score (floor only once at the end)
    local score = math.floor(baseScore * totalMultiplier)
    
    -- Power-up: Shadow Dance (Rogue) - double points for next 2 clears
    if addon.activePowerUps.shadowdance and addon.activePowerUps.shadowdance > 0 and linesCleared > 0 then
        score = score * 2
        addon.activePowerUps.shadowdance = addon.activePowerUps.shadowdance - 1
        if addon.activePowerUps.shadowdance <= 0 then
            addon.activePowerUps.shadowdance = nil
            print("|cff8800ff[Tetris]|r Shadow Dance expired!")
        end
    end
    
    -- Combo bonus
    if linesCleared > 0 then
        state.combo = (state.combo or 0) + 1
        if state.combo > 1 then
            score = score + math.floor(SCORE_VALUES.combo * (state.combo - 1) * totalMultiplier)
        end
        
        -- Track lines for Bolstering affix
        state.totalLinesForAffixes = (state.totalLinesForAffixes or 0) + linesCleared
        
        -- Affix: Bolstering - every 8 lines, add 1 garbage row
        if addon:HasAffix("bolstering") and state.totalLinesForAffixes >= 8 then
            state.totalLinesForAffixes = state.totalLinesForAffixes - 8
            addon:AddGarbageRow()
        end
        
        -- Affix: Bursting - clearing 3+ lines adds 1 random block (temporary)
        if addon:HasAffix("bursting") and linesCleared >= 3 then
            addon:AddRandomBlock()
        end
    else
        state.combo = 0
    end
    
    state.score = state.score + score
    
    -- Update high score
    if state.score > addon.db.highScore then
        addon.db.highScore = state.score
    end
    
    -- Check for PvP win condition
    if addon.mpState and addon.mpState.inMatch then
        addon:CheckPvPWinCondition()
    end
    
    addon:UpdateUI()
end

-- Game over
function addon:GameOver()
    local state = addon.gameState
    
    -- Check for immunity effect
    if addon:HasEffect("immunity") then
        -- Clear top rows to make space
        addon:Ultimate_ClearTopRows(4)
        return
    end
    
    -- Track quest progress BEFORE setting isPlaying to false
    if addon.TrackQuestProgress then
        addon:TrackQuestProgress()
    end
    
    state.isPlaying = false
    state.isGameOver = true
    
    -- Stop affix timers
    addon:StopAffixTimers()
    
    -- Save class-specific stats
    addon:SaveClassStats()
    
    -- Stop background music
    addon:StopMusic(true)
    
    addon:PlaySound("gameOver")
    addon:UpdateUI()
    
    -- In PvP mode, send game over to opponent (we lost - board filled)
    if addon.mpState and addon.mpState.inMatch then
        addon:SendGameOver(state.score)
        addon:ShowPvPMatchResults(false, state.score)
    else
        addon:ShowGameOver()
    end
end

-- Pause/unpause game
function addon:TogglePause()
    local state = addon.gameState
    if not state.isPlaying then return end
    
    -- Don't allow manual pause during stun
    if addon:HasEffect("stun") then return end
    
    state.isPaused = not state.isPaused
    addon:UpdateUI()
end

function addon:PauseGame()
    local state = addon.gameState
    if state.isPlaying and not state.isPaused then
        state.isPaused = true
        addon:UpdateUI()
    end
end

-- Get ghost piece Y position (where piece would land)
function addon:GetGhostY()
    local state = addon.gameState
    local piece = state.currentPiece
    if not piece then return nil end
    
    local ghostY = piece.y
    while addon:IsValidPosition(piece, 0, ghostY - piece.y + 1) do
        ghostY = ghostY + 1
    end
    
    return ghostY
end

-- Show new ultimate unlocked notification
function addon:ShowNewUltimateUnlocked(level)
    local available = addon:GetAvailableUltimates()
    local newUltimate = available[#available]
    
    if newUltimate and addon.ultimateUnlockFrame then
        local frame = addon.ultimateUnlockFrame
        frame.icon:SetTexture(newUltimate.icon)
        frame.name:SetText("|cffffd700NEW ULTIMATE!|r\n" .. newUltimate.name)
        frame:SetAlpha(1)
        frame:Show()
        
        addon:PlaySound("ultimateReady")
        
        -- Fade out
        local elapsed = 0
        frame:SetScript("OnUpdate", function(self, delta)
            elapsed = elapsed + delta
            if elapsed > 3 then
                self:SetAlpha(math.max(0, 1 - (elapsed - 3) / 1))
                if elapsed > 4 then
                    self:Hide()
                    self:SetScript("OnUpdate", nil)
                end
            end
        end)
    end
end

-- Game update loop
function addon:UpdateGame(elapsed)
    local state = addon.gameState
    if not state.isPlaying or state.isPaused then return end
    
    -- Update ultimate cooldown and effects
    addon:UpdateUltimateCooldown(elapsed)
    
    -- Update game time
    state.gameTime = state.gameTime + elapsed
    
    -- Update temporary affix blocks (despawn expired ones)
    addon:UpdateTemporaryBlocks()
    
    -- Check freeze effect
    if addon:HasEffect("freeze") then
        addon:UpdateUI()
        return
    end
    
    -- Update drop timer
    state.dropTimer = state.dropTimer + elapsed
    local dropSpeed = addon:GetDropSpeed()
    
    -- Check if piece is on the ground
    local onGround = not addon:IsValidPosition(state.currentPiece, 0, 1)
    
    if onGround then
        -- Increment lock timer
        state.lockTimer = state.lockTimer + elapsed
        
        -- Calculate effective lock delay
        local effectiveLockDelay = state.lockDelay + (state.lockDelayBonus or 0)
        
        -- Affix: Tyrannical - 25% faster lock
        if addon:HasAffix("tyrannical") then
            effectiveLockDelay = effectiveLockDelay * 0.75
        end
        
        -- Lock if timer exceeded or max moves reached
        if state.lockTimer >= effectiveLockDelay or state.lockMoves >= state.maxLockMoves then
            addon:LockPiece()
        end
    else
        state.lockTimer = 0
        state.lockMoves = 0
        
        -- Normal drop
        if state.dropTimer >= dropSpeed then
            state.dropTimer = state.dropTimer - dropSpeed
            if addon:IsValidPosition(state.currentPiece, 0, 1) then
                state.currentPiece.y = state.currentPiece.y + 1
            end
        end
    end
    
    addon:UpdateUI()
end

-- Format time as MM:SS
function addon:FormatTime(seconds)
    local mins = math.floor(seconds / 60)
    local secs = math.floor(seconds % 60)
    return string.format("%02d:%02d", mins, secs)
end

-- =====================
-- AFFIX SYSTEM
-- =====================

-- Start affix timers when game starts
function addon:StartAffixTimers()
    addon:StopAffixTimers()  -- Clear any existing
    
    -- Volatile: Random block every 15 seconds (despawns after 15s)
    if addon:HasAffix("volatile") then
        addon.affixTimers.volatile = C_Timer.NewTicker(15, function()
            if addon.gameState.isPlaying and not addon.gameState.isPaused then
                addon:AddRandomBlock()
            end
        end)
    end
    
    -- Quaking: Shift blocks every 25 seconds
    if addon:HasAffix("quaking") then
        addon.affixTimers.quaking = C_Timer.NewTicker(25, function()
            if addon.gameState.isPlaying and not addon.gameState.isPaused then
                addon:ShiftBoardRandomly()
            end
        end)
    end
    
    -- Necrotic: 1 block on bottom every 20 seconds (despawns after 15s)
    if addon:HasAffix("necrotic") then
        addon.affixTimers.necrotic = C_Timer.NewTicker(20, function()
            if addon.gameState.isPlaying and not addon.gameState.isPaused then
                addon:AddNecroticBlock()
            end
        end)
    end
    
    -- Grievous: Board rises 1 row every 45 seconds
    if addon:HasAffix("grievous") then
        addon.affixTimers.grievous = C_Timer.NewTicker(45, function()
            if addon.gameState.isPlaying and not addon.gameState.isPaused then
                addon:RiseBoardOneRow()
            end
        end)
    end
    
    -- Explosive: Random block explodes every 20 seconds
    if addon:HasAffix("explosive") then
        addon.affixTimers.explosive = C_Timer.NewTicker(20, function()
            if addon.gameState.isPlaying and not addon.gameState.isPaused then
                addon:ExplodeRandomBlock()
            end
        end)
    end
end

-- Stop all affix timers
function addon:StopAffixTimers()
    for name, ticker in pairs(addon.affixTimers) do
        if ticker then ticker:Cancel() end
    end
    addon.affixTimers = {}
    addon.temporaryBlocks = {}  -- Reset temporary blocks
end

-- Track temporary blocks for despawn
addon.temporaryBlocks = {}

-- Add a temporary block that despawns after duration
function addon:AddTemporaryBlock(x, y, color, duration)
    local state = addon.gameState
    if not state.board[y] then state.board[y] = {} end
    
    -- Only place if empty
    if state.board[y][x] then return end
    
    state.board[y][x] = {color = color, affix = true, temporary = true}
    
    -- Schedule despawn
    local despawnTime = GetTime() + (duration or 15)
    table.insert(addon.temporaryBlocks, {x = x, y = y, despawnTime = despawnTime})
end

-- Check and despawn expired temporary blocks (called in game update)
function addon:UpdateTemporaryBlocks()
    local state = addon.gameState
    if not state or not state.isPlaying then return end
    
    local currentTime = GetTime()
    local i = 1
    while i <= #addon.temporaryBlocks do
        local block = addon.temporaryBlocks[i]
        if currentTime >= block.despawnTime then
            -- Remove block if it still exists and is temporary
            if state.board[block.y] and state.board[block.y][block.x] then
                local b = state.board[block.y][block.x]
                if b.temporary then
                    state.board[block.y][block.x] = nil
                end
            end
            table.remove(addon.temporaryBlocks, i)
        else
            i = i + 1
        end
    end
end

-- Add a random block anywhere on the board (temporary, despawns after 15 sec)
function addon:AddRandomBlock()
    local state = addon.gameState
    local emptySpots = {}
    
    for y = 5, addon.BOARD_HEIGHT do  -- Don't spawn in top 4 rows
        for x = 1, addon.BOARD_WIDTH do
            if not state.board[y] then state.board[y] = {} end
            if not state.board[y][x] then
                table.insert(emptySpots, {x = x, y = y})
            end
        end
    end
    
    if #emptySpots > 0 then
        local spot = emptySpots[math.random(#emptySpots)]
        addon:AddTemporaryBlock(spot.x, spot.y, {0.5, 0.5, 0.5}, 15)
    end
end

-- Add a block on the bottom row (Necrotic) - temporary, 15 sec
function addon:AddNecroticBlock()
    local state = addon.gameState
    local y = addon.BOARD_HEIGHT
    local emptyX = {}
    
    if not state.board[y] then state.board[y] = {} end
    for x = 1, addon.BOARD_WIDTH do
        if not state.board[y][x] then
            table.insert(emptyX, x)
        end
    end
    
    if #emptyX > 0 then
        local x = emptyX[math.random(#emptyX)]
        addon:AddTemporaryBlock(x, y, {0.3, 0.5, 0.3}, 15)
    end
end

-- Shift entire board left or right (Quaking)
function addon:ShiftBoardRandomly()
    local state = addon.gameState
    local direction = math.random(2) == 1 and 1 or -1  -- 1 = right, -1 = left
    
    for y = 1, addon.BOARD_HEIGHT do
        if state.board[y] then
            local newRow = {}
            for x = 1, addon.BOARD_WIDTH do
                local newX = x + direction
                if newX >= 1 and newX <= addon.BOARD_WIDTH then
                    newRow[newX] = state.board[y][x]
                end
                -- Blocks that would go off the edge are lost
            end
            state.board[y] = newRow
        end
    end
    addon:UpdateUI()
end

-- Add a garbage row with one gap
function addon:AddGarbageRow()
    local state = addon.gameState
    
    -- Check if blocked by Divine Shield
    if addon.activePowerUps.divineshield then
        addon.activePowerUps.divineshield = nil
        print("|cffffff00[Power-Up]|r Divine Shield blocked garbage row!")
        return
    end
    
    -- Shift all rows up
    for y = 1, addon.BOARD_HEIGHT - 1 do
        state.board[y] = state.board[y + 1]
    end
    
    -- Create garbage row at bottom with one random gap
    local gapX = math.random(addon.BOARD_WIDTH)
    state.board[addon.BOARD_HEIGHT] = {}
    for x = 1, addon.BOARD_WIDTH do
        if x ~= gapX then
            state.board[addon.BOARD_HEIGHT][x] = {color = {0.4, 0.4, 0.4}, garbage = true}
        end
    end
    
    addon:UpdateUI()
end

-- Rise entire board up by 1 row (Grievous affix)
function addon:RiseBoardOneRow()
    local state = addon.gameState
    
    -- Check if top row has blocks (would cause game over)
    for x = 1, addon.BOARD_WIDTH do
        if state.board[1] and state.board[1][x] then
            -- Can't rise, game over check will handle
            return
        end
    end
    
    -- Shift all rows up
    for y = 1, addon.BOARD_HEIGHT - 1 do
        state.board[y] = state.board[y + 1]
    end
    
    -- Create partial garbage row at bottom
    local gapX = math.random(addon.BOARD_WIDTH)
    local gapX2 = math.random(addon.BOARD_WIDTH)
    state.board[addon.BOARD_HEIGHT] = {}
    for x = 1, addon.BOARD_WIDTH do
        if x ~= gapX and x ~= gapX2 then
            state.board[addon.BOARD_HEIGHT][x] = {color = {0.5, 0.3, 0.3}, affix = true}
        end
    end
    
    addon:UpdateUI()
end

-- Explode a random block and destroy adjacent (Explosive affix)
function addon:ExplodeRandomBlock()
    local state = addon.gameState
    local filledSpots = {}
    
    for y = 5, addon.BOARD_HEIGHT do
        for x = 1, addon.BOARD_WIDTH do
            if state.board[y] and state.board[y][x] then
                table.insert(filledSpots, {x = x, y = y})
            end
        end
    end
    
    if #filledSpots > 0 then
        local spot = filledSpots[math.random(#filledSpots)]
        
        -- Destroy the block and adjacent
        for dy = -1, 1 do
            for dx = -1, 1 do
                local nx, ny = spot.x + dx, spot.y + dy
                if ny >= 1 and ny <= addon.BOARD_HEIGHT and nx >= 1 and nx <= addon.BOARD_WIDTH then
                    if state.board[ny] then
                        state.board[ny][nx] = nil
                    end
                end
            end
        end
        
        addon:UpdateUI()
    end
end

-- =====================
-- POWER-UP SYSTEM  
-- =====================

-- Spawn a power-up block (5% chance per piece)
function addon:TrySpawnPowerUp()
    if not addon.selectedClass then return end
    if addon.powerUpBlock then return end  -- Only one at a time
    
    if math.random(100) <= 5 then  -- 5% chance
        -- Find a random empty spot (not too high)
        local state = addon.gameState
        local emptySpots = {}
        
        for y = 8, addon.BOARD_HEIGHT do
            for x = 1, addon.BOARD_WIDTH do
                if not state.board[y] then state.board[y] = {} end
                if not state.board[y][x] then
                    table.insert(emptySpots, {x = x, y = y})
                end
            end
        end
        
        if #emptySpots > 0 then
            local spot = emptySpots[math.random(#emptySpots)]
            state.board[spot.y][spot.x] = {color = {1, 0.84, 0}, powerUp = true}  -- Gold color
            addon.powerUpBlock = {x = spot.x, y = spot.y}
            print("|cffffff00[Tetris]|r Power-Up block spawned!")
        end
    end
end

-- Activate power-up when its row is cleared
function addon:ActivatePowerUp()
    if not addon.selectedClass or not addon.CLASS_POWERUPS[addon.selectedClass] then return end
    
    local powerUp = addon.CLASS_POWERUPS[addon.selectedClass]
    addon.powerUpBlock = nil
    
    print("|cffffff00[Power-Up]|r " .. powerUp.name .. " activated!")
    
    local className = addon.selectedClass
    
    if className == "WARRIOR" then
        -- Battle Shout: +15% score for 10 seconds
        addon.activePowerUps.battleshout = GetTime() + 10
        
    elseif className == "PALADIN" then
        -- Divine Shield: Block next garbage/sabotage
        addon.activePowerUps.divineshield = true
        
    elseif className == "HUNTER" then
        -- Eagle Eye: See 1 extra preview for 30 seconds
        addon.activePowerUps.eagleeye = GetTime() + 30
        
    elseif className == "ROGUE" then
        -- Shadow Dance: Next 2 clears double points
        addon.activePowerUps.shadowdance = 2
        
    elseif className == "PRIEST" then
        -- Renew: Remove 3 random blocks
        addon:RemoveRandomBlocks(3)
        
    elseif className == "DEATHKNIGHT" then
        -- Death Strike: Remove bottom incomplete row
        addon:RemoveBottomIncompleteRow()
        
    elseif className == "SHAMAN" then
        -- Earthbind: Slow drop 20% for 8 seconds
        addon.activePowerUps.earthbind = GetTime() + 8
        
    elseif className == "MAGE" then
        -- Ice Block: Pause drop for 3 seconds
        addon.activePowerUps.iceblock = GetTime() + 3
        
    elseif className == "WARLOCK" then
        -- Drain Life: Convert 2 garbage to regular
        addon:ConvertGarbageBlocks(2)
        
    elseif className == "DRUID" then
        -- Barkskin: Next piece ignores collision once
        addon.activePowerUps.barkskin = true
    end
    
    addon:UpdateUI()
end

-- Remove random blocks (Priest - Renew)
function addon:RemoveRandomBlocks(count)
    local state = addon.gameState
    local blocks = {}
    
    for y = 1, addon.BOARD_HEIGHT do
        for x = 1, addon.BOARD_WIDTH do
            if state.board[y] and state.board[y][x] then
                table.insert(blocks, {x = x, y = y})
            end
        end
    end
    
    for i = 1, math.min(count, #blocks) do
        local idx = math.random(#blocks)
        local block = blocks[idx]
        state.board[block.y][block.x] = nil
        table.remove(blocks, idx)
    end
end

-- Remove bottom incomplete row (DK - Death Strike)
function addon:RemoveBottomIncompleteRow()
    local state = addon.gameState
    
    for y = addon.BOARD_HEIGHT, 1, -1 do
        local filled = 0
        local hasBlock = false
        for x = 1, addon.BOARD_WIDTH do
            if state.board[y] and state.board[y][x] then
                filled = filled + 1
                hasBlock = true
            end
        end
        
        if hasBlock and filled < addon.BOARD_WIDTH then
            -- Clear this row and shift down
            for shiftY = y, 2, -1 do
                state.board[shiftY] = state.board[shiftY - 1]
            end
            state.board[1] = {}
            for x = 1, addon.BOARD_WIDTH do state.board[1][x] = nil end
            return
        end
    end
end

-- Convert garbage blocks to regular (Warlock - Drain Life)
function addon:ConvertGarbageBlocks(count)
    local state = addon.gameState
    local garbageBlocks = {}
    
    for y = 1, addon.BOARD_HEIGHT do
        for x = 1, addon.BOARD_WIDTH do
            if state.board[y] and state.board[y][x] and state.board[y][x].garbage then
                table.insert(garbageBlocks, {x = x, y = y})
            end
        end
    end
    
    for i = 1, math.min(count, #garbageBlocks) do
        local idx = math.random(#garbageBlocks)
        local block = garbageBlocks[idx]
        state.board[block.y][block.x] = {color = {0.6, 0.6, 0.8}}  -- Convert to regular
        table.remove(garbageBlocks, idx)
    end
end

-- Affix: Sanguine - leave random block behind after clear
function addon:ApplySanguineAffix(clearedRows)
    if not addon:HasAffix("sanguine") then return end
    
    local state = addon.gameState
    for _, y in ipairs(clearedRows) do
        local x = math.random(addon.BOARD_WIDTH)
        if not state.board[y] then state.board[y] = {} end
        state.board[y][x] = {color = {0.6, 0.2, 0.2}, affix = true}
    end
end
