// MARK: - Supporting types

struct GridSize: Equatable {
    let width: Int
    let height: Int

    func contains(_ pos: GridPosition) -> Bool {
        pos.x >= 0 && pos.x < width && pos.y >= 0 && pos.y < height
    }
}

enum ObjectiveType: Equatable {
    case clearAll
    case clearColor(BlockColor)
    case reduceTo(Int)
}

// MARK: - GameState

/// Immutable. Every move produces a new instance.
struct GameState: Equatable {
    let grid: GridSize
    let blocks: [Block]
    let walls: Set<GridPosition>
    let moveCount: Int
    let par: Int
    let objective: ObjectiveType
    /// World 2+ enables primary+primary merging into secondary colors. World 1 leaves
    /// different-color collisions as a hard stop, matching the original mechanic.
    let allowsMerging: Bool
    /// Previous states for undo. Stored WITHOUT their own history to avoid memory explosion.
    let history: [GameState]

    // MARK: Derived helpers

    var isWon: Bool {
        switch objective {
        case .clearAll:
            return blocks.isEmpty
        case .clearColor(let color):
            return !blocks.contains(where: { $0.color == color })
        case .reduceTo(let count):
            return blocks.count <= count
        }
    }

    func stars(for moveCount: Int) -> Int {
        if moveCount <= par       { return 3 }
        if moveCount <= par + 2   { return 2 }
        return 1
    }

    var currentStars: Int { stars(for: moveCount) }

    func block(at pos: GridPosition) -> Block? {
        blocks.first(where: { $0.position == pos })
    }

    func isOccupied(_ pos: GridPosition) -> Bool {
        walls.contains(pos) || blocks.contains(where: { $0.position == pos })
    }

    // MARK: State factory used by GameEngine

    func withBlocks(_ newBlocks: [Block], incrementMove: Bool) -> GameState {
        // Strip history from self before storing as a history entry
        let stripped = GameState(
            grid: grid, blocks: blocks, walls: walls,
            moveCount: moveCount, par: par, objective: objective,
            allowsMerging: allowsMerging, history: []
        )
        return GameState(
            grid: grid,
            blocks: newBlocks,
            walls: walls,
            moveCount: incrementMove ? moveCount + 1 : moveCount,
            par: par,
            objective: objective,
            allowsMerging: allowsMerging,
            history: history + [stripped]
        )
    }

    // MARK: Undo

    var canUndo: Bool { !history.isEmpty }

    func undoState() -> GameState? {
        guard var prev = history.last else { return nil }
        // Restore the history slice that was current before this move
        let newHistory = Array(history.dropLast())
        prev = GameState(
            grid: prev.grid, blocks: prev.blocks, walls: prev.walls,
            moveCount: prev.moveCount, par: prev.par,
            objective: prev.objective, allowsMerging: prev.allowsMerging,
            history: newHistory
        )
        return prev
    }
}
