Imports System.Drawing.Drawing2D

Public Class RoomData
    Public Property GridSizeX As Integer
    Public Property GridSizeY As Integer
    Public Property wallsHatchStyleValue As Integer
    Public Property wallsColor1Argb As Integer
    Public Property wallsColor2Argb As Integer
    Public Property floorsHatchStyleValue As Integer
    Public Property floorsColor1Argb As Integer
    Public Property floorsColor2Argb As Integer

    ' Arrays to save wall states
    Public Property verticalWalls As Boolean()()
    Public Property horizontalWalls As Boolean()()

    ' Arrays to save placed items
    Public Property placedItems As PlacedItemData()()
    Public Property verticalWallItems As PlacedItemData()()
    Public Property horizontalWallItems As PlacedItemData()()

    ' Arrays to save floor edge styles
    Public Property floorsUseEdgeStyle As Boolean()()

    ' Property to save the selected music
    Public Property SelectedMusic As String

    ' Property to save the level name
    Public Property LevelName As String

    ' Property to save the useGradient value
    Public Property useGradient As Boolean

    ' Parameterless constructor for deserialization
    Public Sub New()
    End Sub

    ' Constructor: captures the current state of the frmDesigner
    Public Sub New(roomView As frmDesigner)
        ' Save colors and styles as integers
        wallsHatchStyleValue = CInt(roomView.wallsHatchStyle)
        wallsColor1Argb = roomView.wallsColor1.ToArgb()
        wallsColor2Argb = roomView.wallsColor2.ToArgb()
        floorsHatchStyleValue = CInt(roomView.floorsHatchStyle)
        floorsColor1Argb = roomView.floorsColor1.ToArgb()
        floorsColor2Argb = roomView.floorsColor2.ToArgb()

        ' Save grid size
        GridSizeX = roomView.SQCOUNTX
        GridSizeY = roomView.SQCOUNTY

        ' Save wall states
        verticalWalls = CloneJaggedArray(roomView.verticalWalls)
        horizontalWalls = CloneJaggedArray(roomView.horizontalWalls)

        ' Save placed items
        placedItems = CopyPlacedItems(roomView.placedItems)
        verticalWallItems = CopyPlacedItems(roomView.verticalWallItems)
        horizontalWallItems = CopyPlacedItems(roomView.horizontalWallItems)

        ' Save floorsUseEdgeStyle
        floorsUseEdgeStyle = CloneJaggedArray(roomView.floorsUseEdgeStyle)

        ' Save the selected music
        SelectedMusic = roomView.SelectedMusic

        ' Save the level name
        LevelName = roomView.txtName.Text

        ' Save the useGradient value
        useGradient = roomView.useGradient
    End Sub

    ' Helper method to clone jagged arrays
    Private Function CloneJaggedArray(Of T)(sourceArray As T()()) As T()()
        If sourceArray Is Nothing Then Return Nothing
        Dim lengthOuter As Integer = sourceArray.Length
        Dim newArray As T()() = New T(lengthOuter - 1)() {}
        For i As Integer = 0 To lengthOuter - 1
            If sourceArray(i) IsNot Nothing Then
                Dim lengthInner As Integer = sourceArray(i).Length
                newArray(i) = New T(lengthInner - 1) {}
                Array.Copy(sourceArray(i), newArray(i), lengthInner)
            Else
                newArray(i) = Nothing
            End If
        Next
        Return newArray
    End Function

    ' Helper method to copy placed items
    Private Function CopyPlacedItems(items As PlacedItem()()) As PlacedItemData()()
        If items Is Nothing Then Return Nothing
        Dim lengthOuter As Integer = items.Length
        Dim copiedItems As PlacedItemData()() = New PlacedItemData(lengthOuter - 1)() {}
        For x As Integer = 0 To lengthOuter - 1
            If items(x) IsNot Nothing Then
                Dim lengthInner As Integer = items(x).Length
                copiedItems(x) = New PlacedItemData(lengthInner - 1) {}
                For y As Integer = 0 To lengthInner - 1
                    If items(x)(y) IsNot Nothing Then
                        copiedItems(x)(y) = New PlacedItemData With {.ImageKey = items(x)(y).ImageKey}
                    Else
                        copiedItems(x)(y) = Nothing
                    End If
                Next
            Else
                copiedItems(x) = Nothing
            End If
        Next
        Return copiedItems
    End Function

    ' Restore the saved state back into the frmDesigner
    Public Sub RestoreState(roomView As frmDesigner)
        ' Restore the grid size
        Dim oldSQCOUNTX As Integer = roomView.SQCOUNTX
        Dim oldSQCOUNTY As Integer = roomView.SQCOUNTY
        roomView.SQCOUNTX = GridSizeX
        roomView.SQCOUNTY = GridSizeY

        ' Resize arrays to match the saved grid size and preserve data
        roomView.ResizeArrays(oldSQCOUNTX, oldSQCOUNTY, roomView.SQCOUNTX, roomView.SQCOUNTY)

        ' Restore colors and styles from stored values
        roomView.wallsHatchStyle = CType(wallsHatchStyleValue, HatchStyle)
        roomView.wallsColor1 = Color.FromArgb(wallsColor1Argb)
        roomView.wallsColor2 = Color.FromArgb(wallsColor2Argb)
        roomView.floorsHatchStyle = CType(floorsHatchStyleValue, HatchStyle)
        roomView.floorsColor1 = Color.FromArgb(floorsColor1Argb)
        roomView.floorsColor2 = Color.FromArgb(floorsColor2Argb)

        ' Restore wall states
        roomView.verticalWalls = CloneJaggedArray(verticalWalls)
        roomView.horizontalWalls = CloneJaggedArray(horizontalWalls)

        ' Restore placed items
        roomView.placedItems = RestorePlacedItems(placedItems)
        roomView.verticalWallItems = RestorePlacedItems(verticalWallItems)
        roomView.horizontalWallItems = RestorePlacedItems(horizontalWallItems)

        ' Restore floorsUseEdgeStyle
        roomView.floorsUseEdgeStyle = CloneJaggedArray(floorsUseEdgeStyle)

        ' Restore the selected music
        roomView.SelectedMusic = SelectedMusic
        roomView.lblAudio.Text = SelectedMusic

        ' Restore the level name
        roomView.txtName.Text = LevelName

        ' Restore the useGradient value
        roomView.useGradient = useGradient

        ' Update grid size-related UI elements
        roomView.UpdateGridSizeUI()

        ' Invalidate the drawing area to redraw the grid
        roomView.picGame.Invalidate()
    End Sub

    ' Helper method to restore placed items
    Private Function RestorePlacedItems(data As PlacedItemData()()) As PlacedItem()()
        If data Is Nothing Then Return Nothing
        Dim lengthOuter As Integer = data.Length
        Dim restoredItems As PlacedItem()() = New PlacedItem(lengthOuter - 1)() {}
        For x As Integer = 0 To lengthOuter - 1
            If data(x) IsNot Nothing Then
                Dim lengthInner As Integer = data(x).Length
                restoredItems(x) = New PlacedItem(lengthInner - 1) {}
                For y As Integer = 0 To lengthInner - 1
                    If data(x)(y) IsNot Nothing Then
                        restoredItems(x)(y) = New PlacedItem With {.ImageKey = data(x)(y).ImageKey}
                    Else
                        restoredItems(x)(y) = Nothing
                    End If
                Next
            Else
                restoredItems(x) = Nothing
            End If
        Next
        Return restoredItems
    End Function
End Class
' PlacedItemData class used for serialization/deserialization
Public Class PlacedItemData
    Public Property ImageKey As String
End Class

' PlacedItem class represents an item placed on the grid
Public Class PlacedItem
    Public Property ImageKey As String
    ' Include any other properties

    ' Implement a Clone method for deep copying
    Public Function Clone() As PlacedItem
        Return New PlacedItem With {
            .ImageKey = Me.ImageKey
        }
    End Function
End Class

Option Explicit On
Option Strict On

Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Text.Json
Imports System.Text.Json.Serialization

' RoomState class represents the state of the room and can save/restore it
Public Class RoomState
    ' In RoomState class
    Public Property GridSizeX As Integer
    Public Property GridSizeY As Integer
    ' Properties to save colors and styles
    Public wallsHatchStyleValue As Integer
    Public wallsColor1Argb As Integer
    Public wallsColor2Argb As Integer
    Public floorsHatchStyleValue As Integer
    Public floorsColor1Argb As Integer
    Public floorsColor2Argb As Integer

    ' Arrays to save wall states
    Public verticalWalls()() As Boolean
    Public horizontalWalls()() As Boolean

    ' Arrays to save placed items
    Public Property placedItems As PlacedItem()()
    Public Property verticalWallItems As PlacedItem()()
    Public Property horizontalWallItems As PlacedItem()()

    ' Arrays to save floor edge styles
    Public floorsUseEdgeStyle()() As Boolean

    ' Property to save the selected music
    Public SelectedMusic As String

    ' Property to save the level name
    Public LevelName As String

    ' Parameterless constructor for deserialization
    Public Sub New()
        ' This constructor is used by the deserializer
    End Sub

    ' Constructor: captures the current state of the frmDesigner
    ' Constructor: captures the current state of the frmDesigner
    Public Sub New(roomView As frmDesigner)
        ' Save colors and styles as integers
        wallsHatchStyleValue = CInt(roomView.wallsHatchStyle)
        wallsColor1Argb = roomView.wallsColor1.ToArgb()
        wallsColor2Argb = roomView.wallsColor2.ToArgb()
        floorsHatchStyleValue = CInt(roomView.floorsHatchStyle)
        floorsColor1Argb = roomView.floorsColor1.ToArgb()
        floorsColor2Argb = roomView.floorsColor2.ToArgb()

        ' Save grid size
        GridSizeX = roomView.SQCOUNTX
        GridSizeY = roomView.SQCOUNTY

        ' Save wall states
        verticalWalls = CloneJaggedArray(roomView.verticalWalls)
        horizontalWalls = CloneJaggedArray(roomView.horizontalWalls)

        ' Save placed items
        placedItems = ClonePlacedItemJaggedArray(roomView.placedItems)
        verticalWallItems = ClonePlacedItemJaggedArray(roomView.verticalWallItems)
        horizontalWallItems = ClonePlacedItemJaggedArray(roomView.horizontalWallItems)

        ' Save floorsUseEdgeStyle
        floorsUseEdgeStyle = CloneJaggedArray(roomView.floorsUseEdgeStyle)

        ' Save the selected music
        SelectedMusic = roomView.SelectedMusic

        ' Save the level name
        LevelName = roomView.txtName.Text
    End Sub
    ' Helper method to clone jagged arrays
    Private Function CloneJaggedArray(Of T)(sourceArray As T()()) As T()()
        Dim lengthOuter As Integer = sourceArray.Length
        Dim newArray As T()() = New T(lengthOuter - 1)() {}
        For i As Integer = 0 To lengthOuter - 1
            If sourceArray(i) IsNot Nothing Then
                Dim lengthInner As Integer = sourceArray(i).Length
                newArray(i) = New T(lengthInner - 1) {}
                Array.Copy(sourceArray(i), newArray(i), lengthInner)
            End If
        Next
        Return newArray
    End Function
    ' Helper method to clone jagged arrays of PlacedItem
    Private Function ClonePlacedItemJaggedArray(sourceArray As PlacedItem()()) As PlacedItem()()
        If sourceArray Is Nothing Then Return Nothing
        Dim lengthOuter As Integer = sourceArray.Length
        Dim newArray As PlacedItem()() = New PlacedItem(lengthOuter - 1)() {}
        For i As Integer = 0 To lengthOuter - 1
            If sourceArray(i) IsNot Nothing Then
                Dim lengthInner As Integer = sourceArray(i).Length
                newArray(i) = New PlacedItem(lengthInner - 1) {}
                For j As Integer = 0 To lengthInner - 1
                    If sourceArray(i)(j) IsNot Nothing Then
                        newArray(i)(j) = sourceArray(i)(j).Clone()
                    Else
                        newArray(i)(j) = Nothing
                    End If
                Next
            Else
                newArray(i) = Nothing
            End If
        Next
        Return newArray
    End Function
    ' Helper method to copy placed items
    Private Function CopyPlacedItems(items As PlacedItem()()) As PlacedItemData()()
        Dim lengthOuter As Integer = items.Length
        Dim copiedItems As PlacedItemData()() = New PlacedItemData(lengthOuter - 1)() {}
        For x As Integer = 0 To lengthOuter - 1
            If items(x) IsNot Nothing Then
                Dim lengthInner As Integer = items(x).Length
                copiedItems(x) = New PlacedItemData(lengthInner - 1) {}
                For y As Integer = 0 To lengthInner - 1
                    If items(x)(y) IsNot Nothing Then
                        copiedItems(x)(y) = New PlacedItemData With {.ImageKey = items(x)(y).ImageKey}
                    Else
                        copiedItems(x)(y) = Nothing
                    End If
                Next
            End If
        Next
        Return copiedItems
    End Function

    ' Helper method to restore placed items
    Private Function RestorePlacedItems(data As PlacedItemData()()) As PlacedItem()()
        Dim lengthOuter As Integer = data.Length
        Dim restoredItems As PlacedItem()() = New PlacedItem(lengthOuter - 1)() {}
        For x As Integer = 0 To lengthOuter - 1
            If data(x) IsNot Nothing Then
                Dim lengthInner As Integer = data(x).Length
                restoredItems(x) = New PlacedItem(lengthInner - 1) {}
                For y As Integer = 0 To lengthInner - 1
                    If data(x)(y) IsNot Nothing Then
                        restoredItems(x)(y) = New PlacedItem With {.ImageKey = data(x)(y).ImageKey}
                    Else
                        restoredItems(x)(y) = Nothing
                    End If
                Next
            End If
        Next
        Return restoredItems
    End Function

    ' Restore the saved state back into the frmDesigner
    Public Sub RestoreState(roomView As frmDesigner)
        ' Restore the grid size
        Dim oldSQCOUNTX As Integer = roomView.SQCOUNTX
        Dim oldSQCOUNTY As Integer = roomView.SQCOUNTY
        roomView.SQCOUNTX = GridSizeX
        roomView.SQCOUNTY = GridSizeY

        ' Resize arrays to match the saved grid size and preserve data
        roomView.ResizeArrays(oldSQCOUNTX, oldSQCOUNTY, roomView.SQCOUNTX, roomView.SQCOUNTY)

        ' Restore colors and styles from stored values
        roomView.wallsHatchStyle = CType(wallsHatchStyleValue, HatchStyle)
        roomView.wallsColor1 = Color.FromArgb(wallsColor1Argb)
        roomView.wallsColor2 = Color.FromArgb(wallsColor2Argb)
        roomView.floorsHatchStyle = CType(floorsHatchStyleValue, HatchStyle)
        roomView.floorsColor1 = Color.FromArgb(floorsColor1Argb)
        roomView.floorsColor2 = Color.FromArgb(floorsColor2Argb)

        ' Restore wall states
        roomView.verticalWalls = CloneJaggedArray(verticalWalls)
        roomView.horizontalWalls = CloneJaggedArray(horizontalWalls)

        ' Restore placed items
        roomView.placedItems = ClonePlacedItemJaggedArray(Me.placedItems)
        roomView.verticalWallItems = ClonePlacedItemJaggedArray(Me.verticalWallItems)
        roomView.horizontalWallItems = ClonePlacedItemJaggedArray(Me.horizontalWallItems)
        ' Restore floorsUseEdgeStyle
        roomView.floorsUseEdgeStyle = CloneJaggedArray(floorsUseEdgeStyle)

        ' Restore the selected music
        roomView.SelectedMusic = SelectedMusic

        ' Restore the level name
        roomView.txtName.Text = LevelName

        ' Update grid size-related UI elements
        roomView.UpdateGridSizeUI()

        ' Invalidate the drawing area to redraw the grid
        roomView.picGame.Invalidate()
    End Sub
End Class

' PlacedItemData class is used for serialization
Public Class SelectionData
    Public placedItems As PlacedItem()()
    Public verticalWalls As Boolean()()
    Public horizontalWalls As Boolean()()
    Public verticalWallItems As PlacedItem()()
    Public horizontalWallItems As PlacedItem()()
    Public floorsUseEdgeStyle As Boolean()()
    Public selectionWidth As Integer ' in cells
    Public selectionHeight As Integer ' in cells
End Class

Imports System.Windows.Forms
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.IO
Imports System.Text.Json
Imports System.Text.Json.Serialization
Imports System.Linq
Imports System.ComponentModel
Imports System.Reflection
Imports System.ComponentModel.Design

Public Class FrmDesigner
    Inherits Form

#Region " Variables "
    Private isCursorOnPicGame As Boolean = False
    Private SelectionType As String
    Private Const MaxRecentFiles As Integer = 5
    Private recentFiles As New List(Of String)()
    Private recentFilesFilePath As String = Path.Combine(GetProjectDirectory(), "recent_files.txt")

    Private lastMouseMoveTime As DateTime = DateTime.MinValue
    Private Const DEBOUNCE_INTERVAL As Integer = 50 ' milliseconds

    Public gameTriggers As New List(Of GameTrigger)
    Public Event ObjectSelected(selectedObject As SelectedObject)
    Public Property SelectionMode As Boolean
    Public Property SelectionRequestedByForm As Form
    Public Property SelectionRequestedFor As String

    Private isSelectionReadyToDrag As Boolean = False
    Private isDraggingSelection As Boolean = False
    Private selectionOffset As Point ' The offset between mouse position and selection rectangle origin
    Private selectionData As SelectionData
    Private selectionStartCell As Point ' The grid cell coordinates corresponding to selectionRectangle's original position

    Private Const INDENT As Integer = 20
    Friend SQCOUNTX As Integer = 16
    Friend SQCOUNTY As Integer = 11
    Private Const WALL_THICKNESS As Integer = 10

    Friend wallsHatchStyle As HatchStyle = HatchStyle.Cross
    Friend wallsColor1 As Color = Color.Gray
    Friend wallsColor2 As Color = Color.DarkGray
    Friend floorsHatchStyle As HatchStyle = HatchStyle.SmallCheckerBoard
    Friend floorsColor1 As Color = Color.LightGray
    Friend floorsColor2 As Color = Color.White

    Friend floorsUseEdgeStyle()() As Boolean ' New array to track edge style usage

    Private SQSizeX As Integer
    Private SQSizeY As Integer

    Private isRightMouseDown As Boolean = False
    Private isLeftMouseDown As Boolean = False
    Private isMouseInsideGrid As Boolean = False

    Private highlightedX As Integer
    Private highlightedY As Integer
    Private highlightedWallX As Boolean = False
    Private highlightedWallY As Boolean = False
    Private highlightColor As Color = Color.Black
    Private highlightTimer As Timer

    Private isRestoringState As Boolean = False
    Private isActionInProgress As Boolean = False
    Private undoStack As New Stack(Of RoomState)
    Private redoStack As New Stack(Of RoomState)

    Private ReadOnly projectDir As String = GetProjectDirectory()
    Private ReadOnly premadesFolder As String = Path.Combine(projectDir, "Data", "Premades")
    Private ReadOnly savesFolder As String = Path.Combine(projectDir, "Data", "Saves")
    Private ReadOnly musicFolder As String = Path.Combine(projectDir, "Data", "Sound/Music")
    Private currentFilePath As String = ""
    Friend SelectedMusic As String = ""

    Friend verticalWalls()() As Boolean
    Friend horizontalWalls()() As Boolean

    Friend placedItems()() As PlacedItem
    Friend verticalWallItems()() As PlacedItem
    Friend horizontalWallItems()() As PlacedItem

    Private animatedImages As New List(Of Image)
    Private animationTimer As Timer

    Private isDraggingItem As Boolean = False
    Private draggedItem As PlacedItem = Nothing
    Private draggedItemIsWall As Boolean = False
    Private draggedItemIsVerticalWall As Boolean = False
    Private draggedItemOriginalX As Integer
    Private draggedItemOriginalY As Integer

    Private mouseX As Integer
    Private mouseY As Integer

    Private rand As New Random()

    Private rightMouseDownPosition As Point
    Private rightMouseDownTime As DateTime
    Private Const CLICK_DRAG_THRESHOLD As Integer = 5 ' pixels
    Private Const CLICK_DRAG_THRESHOLD_SQUARED As Integer = CLICK_DRAG_THRESHOLD * CLICK_DRAG_THRESHOLD

    Private isSelecting As Boolean = False
    Private isInSelectionMode As Boolean = False
    Private selectionStartPoint As Point
    Private selectionEndPoint As Point
    Private selectionRectangle As Rectangle

    Public useGradient As Boolean = False
    Private copiedSelectionData As SelectionData
    Private copiedSelectionType As String
    Private isCopying As Boolean = False

#End Region

#Region " Constructor and Initialization "
    Public Sub New()
        InitializeComponent()
        InitializeWalls()
        InitializeTimers()
        InitializeEventHandlers()
        LoadPremades()
        LoadMusic()
        InitializeAnimationForImages()
        If Not isRestoringState Then SaveStateForUndo()
        Me.KeyPreview = True

        ' Enable double buffering for picGame
        EnableDoubleBuffering(picGame)

        ' Initialize the status bar
    End Sub

    Private Sub UpdateRecentFiles(filePath As String)
        ' Remove the file if it already exists in the recent files list
        recentFiles.Remove(filePath)

        ' Add the new file to the top of the list
        recentFiles.Insert(0, filePath)

        ' Ensure we do not exceed the maximum number of recent files
        If recentFiles.Count > MaxRecentFiles Then
            recentFiles.RemoveAt(MaxRecentFiles) ' Remove the oldest entry
        End If

        ' Update the menu with the latest recent files
        UpdateRecentFilesMenu()

        ' Save the recent files to a text file
        SaveRecentFilesToFile()
    End Sub

    Private Sub UpdateRecentFilesMenu()
        ' Remove any existing recent file items and separators
        For i As Integer = mnu.DropDownItems.Count - 1 To 0 Step -1
            Dim item As ToolStripItem = mnu.DropDownItems(i)
            If TypeOf item Is ToolStripMenuItem AndAlso (CType(item, ToolStripMenuItem).Tag IsNot Nothing AndAlso CType(item, ToolStripMenuItem).Tag.ToString() = "RecentFile") OrElse
   TypeOf item Is ToolStripSeparator Then
                mnu.DropDownItems.RemoveAt(i)
            End If
        Next

        ' Find the "New" menu item
        Dim newMenuItem As ToolStripMenuItem = Nothing
        For Each item As ToolStripItem In mnu.DropDownItems
            If TypeOf item Is ToolStripMenuItem AndAlso CType(item, ToolStripMenuItem).Text = "New" Then
                newMenuItem = CType(item, ToolStripMenuItem)
                Exit For
            End If
        Next

        If newMenuItem Is Nothing Then
            MessageBox.Show("New menu item not found.")
            Return
        End If

        ' Add the separator below the "New" menu item
        Dim separatorBelowNew As New ToolStripSeparator()
        mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(newMenuItem) + 1, separatorBelowNew)

        ' Find the "Load" menu item
        Dim loadMenuItem As ToolStripMenuItem = Nothing
        For Each item As ToolStripItem In mnu.DropDownItems
            If TypeOf item Is ToolStripMenuItem AndAlso CType(item, ToolStripMenuItem).Text = "Load" Then
                loadMenuItem = CType(item, ToolStripMenuItem)
                Exit For
            End If
        Next

        If loadMenuItem Is Nothing Then
            MessageBox.Show("Load menu item not found.")
            Return
        End If

        ' Add the separator above the "Load" menu item
        Dim separatorAboveLoad As New ToolStripSeparator()
        mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(loadMenuItem), separatorAboveLoad)

        ' Add the separator above recent files
        Dim separatorAboveRecents As New ToolStripSeparator()
        mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(loadMenuItem) + 1, separatorAboveRecents)

        ' Populate the recent files list in the menu in reverse order
        For i As Integer = recentFiles.Count - 1 To 0 Step -1
            Dim file As String = recentFiles(i)
            Dim menuItem As New ToolStripMenuItem(file) ' Create a new menu item for each file
            menuItem.Tag = "RecentFile" ' Tag the item to identify it as a recent file item
            AddHandler menuItem.Click, AddressOf RecentFileMenuItem_Click ' Attach event handler
            mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(separatorAboveRecents) + 1, menuItem) ' Add item to the dropdown after the separator
        Next

        ' Add the separator below recent files
        Dim separatorBelowRecents As New ToolStripSeparator()
        mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(separatorAboveRecents) + recentFiles.Count + 1, separatorBelowRecents)

        ' Ensure the Exit menu item is added only once
        Dim exitMenuItem As ToolStripMenuItem = Nothing
        For Each item As ToolStripItem In mnu.DropDownItems
            If TypeOf item Is ToolStripMenuItem AndAlso CType(item, ToolStripMenuItem).Text = "Exit" Then
                exitMenuItem = CType(item, ToolStripMenuItem)
                Exit For
            End If
        Next

        If exitMenuItem Is Nothing Then
            exitMenuItem = New ToolStripMenuItem("Exit")
            AddHandler exitMenuItem.Click, AddressOf ExitMenuItem_Click

            ' Load the icon
            Dim iconPath As String = Path.Combine(projectDir, "Data", "Icons", "close.png")
            If File.Exists(iconPath) Then
                exitMenuItem.Image = Image.FromFile(iconPath)
            End If

            mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(separatorBelowRecents) + 1, exitMenuItem)
        End If

        ' Hide the separators if there are no recent files
        separatorAboveRecents.Visible = recentFiles.Count > 0
        separatorBelowRecents.Visible = recentFiles.Count > 0
    End Sub

    Private Sub ExitMenuItem_Click(sender As Object, e As EventArgs)
        Me.Close()
    End Sub

    Private Sub SaveRecentFilesToFile()
        Try
            Dim lines As New List(Of String)(recentFiles)
            File.WriteAllLines(recentFilesFilePath, lines)
        Catch ex As Exception
            MessageBox.Show("Error saving recent files: " & ex.Message)
        End Try
    End Sub
    Private Sub LoadRecentFiles()
        If File.Exists(recentFilesFilePath) Then
            Try
                Dim lines As String() = File.ReadAllLines(recentFilesFilePath)
                recentFiles.AddRange(lines.Where(Function(line) Not String.IsNullOrWhiteSpace(line)).Take(MaxRecentFiles))
            Catch ex As Exception
                MessageBox.Show("Error loading recent files: " & ex.Message)
            End Try
        End If
        UpdateRecentFilesMenu()
    End Sub
    Private Sub RecentFileMenuItem_Click(sender As Object, e As EventArgs)
        Dim selectedItem As ToolStripMenuItem = CType(sender, ToolStripMenuItem)
        Dim filePath As String = selectedItem.Text

        ' Load the selected recent file
        LoadRoomFromFile(filePath)

        ' Update the recent files list with the selected file
        UpdateRecentFiles(filePath)
    End Sub

    Private Sub EnableDoubleBuffering(control As Control)
        Dim propertyInfo As PropertyInfo = control.GetType().GetProperty("DoubleBuffered", BindingFlags.NonPublic Or BindingFlags.Instance)
        propertyInfo.SetValue(control, True, Nothing)
    End Sub

    Private Sub CboStyleChoice_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboStyleChoice.SelectedIndexChanged
        useGradient = cboStyleChoice.SelectedItem.ToString() = "Gradient"
        lstHatchstyles.Enabled = Not useGradient
        picGame.Invalidate() ' Redraw the picGame control
    End Sub

    Private Sub FrmDesigner_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
        ' Check for Ctrl+Z (Undo)
        If e.Control AndAlso e.KeyCode = Keys.Z Then
            BtnUndo_Click(sender, e)
            e.SuppressKeyPress = True ' Prevent further processing
        End If

        ' Check for Ctrl+Y (Redo)
        If e.Control AndAlso e.KeyCode = Keys.Y Then
            BtnRedo_Click(sender, e)
            e.SuppressKeyPress = True
        End If
    End Sub

    Private Function GetProjectDirectory() As String
        Dim dir As DirectoryInfo = New DirectoryInfo(Application.StartupPath)
        For i As Integer = 1 To 4 ' Adjust this number based on your directory structure
            If dir.Parent IsNot Nothing Then
                dir = dir.Parent
            End If
        Next
        Return dir.FullName
    End Function

    Friend Sub InitializeWalls()
        verticalWalls = New Boolean(SQCOUNTX)() {}
        verticalWallItems = New PlacedItem(SQCOUNTX)() {}
        For x As Integer = 0 To SQCOUNTX
            verticalWalls(x) = New Boolean(SQCOUNTY - 1) {}
            verticalWallItems(x) = New PlacedItem(SQCOUNTY - 1) {}
            For y As Integer = 0 To SQCOUNTY - 1
                verticalWalls(x)(y) = True
                verticalWallItems(x)(y) = Nothing
            Next
        Next

        horizontalWalls = New Boolean(SQCOUNTX - 1)() {}
        horizontalWallItems = New PlacedItem(SQCOUNTX - 1)() {}
        For x As Integer = 0 To SQCOUNTX - 1
            horizontalWalls(x) = New Boolean(SQCOUNTY) {}
            horizontalWallItems(x) = New PlacedItem(SQCOUNTY) {}
            For y As Integer = 0 To SQCOUNTY
                horizontalWalls(x)(y) = True
                horizontalWallItems(x)(y) = Nothing
            Next
        Next

        placedItems = New PlacedItem(SQCOUNTX - 1)() {}
        For x As Integer = 0 To SQCOUNTX - 1
            placedItems(x) = New PlacedItem(SQCOUNTY - 1) {}
            For y As Integer = 0 To SQCOUNTY - 1
                placedItems(x)(y) = Nothing
            Next
        Next

        floorsUseEdgeStyle = New Boolean(SQCOUNTX - 1)() {}
        For x As Integer = 0 To SQCOUNTX - 1
            floorsUseEdgeStyle(x) = New Boolean(SQCOUNTY - 1) {}
            For y As Integer = 0 To SQCOUNTY - 1
                floorsUseEdgeStyle(x)(y) = False
            Next
        Next
    End Sub

    Private Sub InitializeTimers()
        highlightTimer = New Timer() With {.Interval = 500}
        AddHandler highlightTimer.Tick, AddressOf HighlightTimer_Tick
        highlightTimer.Start()

        animationTimer = New Timer() With {.Interval = 100} ' Update every 100 milliseconds (10 times per second)
        AddHandler animationTimer.Tick, AddressOf OnAnimationTick
        animationTimer.Start()
    End Sub

    Private Sub InitializeEventHandlers()
        AddColorPictureBoxHandlers(Me)

        ' Add handlers for item picture boxes
        AddHandler picBlock1.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picBaddy1.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picBaddy2.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picLazer1.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picLazer2.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picFlipper1.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picFlipper2.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picFlipper3.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picFlipper4.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picBaddy3.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picBaddy4.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picCoin.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picGate.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picKey.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picStart.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picFinish.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picBlockStopper1.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picBall.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picBlockSwitch.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picBomb.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picWarp.MouseDown, AddressOf PicBlock_MouseDown
        AddHandler picWarp2.MouseDown, AddressOf PicBlock_MouseDown

        ' Add handlers for form-level mouse events
        AddHandler Me.MouseMove, AddressOf Form_MouseMove
        AddHandler Me.MouseUp, AddressOf Form_MouseUp

        ' Add handlers for picGame
        picGame.AllowDrop = True
        AddHandler picGame.MouseDown, AddressOf PicGame_MouseDown
        AddHandler picGame.MouseUp, AddressOf PicGame_MouseUp
        AddHandler picGame.MouseMove, AddressOf PicGame_MouseMove
        AddHandler picGame.Paint, AddressOf PicGame_Paint

        ' Add handlers for buttons
        AddHandler btnUndo.Click, AddressOf BtnUndo_Click
        AddHandler btnRedo.Click, AddressOf BtnRedo_Click
        AddHandler BtnAll.Click, AddressOf BtnAll_Click
        AddHandler BtnWall.Click, AddressOf BtnWalls_Click
        AddHandler BtnFloor.Click, AddressOf BtnFloors_Click
        AddHandler btnSavePremade.Click, AddressOf BtnSavePremade_Click

        ' Add handler for premades list
        AddHandler lstPremades.SelectedIndexChanged, AddressOf LstPremades_SelectedIndexChanged

        ' Add handler for hatch styles list
        AddHandler lstHatchstyles.SelectedIndexChanged, AddressOf LstHatchstyles_SelectedIndexChanged

        For Each ctl As Control In tabFloor1.Controls
            If TypeOf ctl Is PictureBox Then
                AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
                AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
            End If
        Next

        For Each ctl As Control In tabFloor2.Controls
            If TypeOf ctl Is PictureBox Then
                AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
                AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
            End If
        Next

        For Each ctl As Control In tabWalls.Controls
            If TypeOf ctl Is PictureBox Then
                AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
                AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
            End If
        Next

        ' Add handler for btnSelect
        AddHandler btnSelect.Click, AddressOf BtnSelect_Click
    End Sub

    Private Sub CboX_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboX.SelectedIndexChanged
        UpdateGridSize()
    End Sub

    Private Sub CboY_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboY.SelectedIndexChanged
        UpdateGridSize()
    End Sub

    Private Sub UpdateGridSize()
        Dim newSQCOUNTX As Integer
        Dim newSQCOUNTY As Integer
        If Integer.TryParse(cboX.SelectedItem?.ToString(), newSQCOUNTX) AndAlso Integer.TryParse(cboY.SelectedItem?.ToString(), newSQCOUNTY) Then
            If Not isRestoringState Then SaveStateForUndo()
            Dim oldSQCOUNTX As Integer = SQCOUNTX
            Dim oldSQCOUNTY As Integer = SQCOUNTY
            SQCOUNTX = newSQCOUNTX
            SQCOUNTY = newSQCOUNTY
            useGradient = (cboStyleChoice.SelectedItem?.ToString() = "Gradient")
            ResizeArrays(oldSQCOUNTX, oldSQCOUNTY, SQCOUNTX, SQCOUNTY)
            CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)
            picGame.Invalidate()
            highlightedX = -1
            highlightedY = -1
            isMouseInsideGrid = False
            selectionRectangle = Rectangle.Empty
        End If
    End Sub

    Friend Sub ResizeArrays(oldX As Integer, oldY As Integer, newX As Integer, newY As Integer)
        verticalWalls = ResizeJaggedArray(verticalWalls, oldX + 1, oldY - 1, newX + 1, newY - 1, True)
        verticalWallItems = ResizeJaggedArray(verticalWallItems, oldX + 1, oldY - 1, newX + 1, newY - 1, False)
        horizontalWalls = ResizeJaggedArray(horizontalWalls, oldX - 1, oldY + 1, newX - 1, newY + 1, True)
        horizontalWallItems = ResizeJaggedArray(horizontalWallItems, oldX - 1, oldY + 1, newX - 1, newY + 1, False)
        placedItems = ResizeJaggedArray(placedItems, oldX - 1, oldY - 1, newX - 1, newY - 1, False)
        floorsUseEdgeStyle = ResizeJaggedArray(floorsUseEdgeStyle, oldX - 1, oldY - 1, newX - 1, newY - 1, False)
        FillBorderWalls()
    End Sub

    Private Function ResizeJaggedArray(Of T)(sourceArray As T()(), oldSizeX As Integer, oldSizeY As Integer, newSizeX As Integer, newSizeY As Integer, initializeValue As Boolean) As T()()
        Dim newArray As T()() = New T(newSizeX)() {}
        For x As Integer = 0 To newSizeX
            newArray(x) = New T(newSizeY) {}
            For y As Integer = 0 To newSizeY
                If x < sourceArray.Length AndAlso y < sourceArray(x).Length Then
                    newArray(x)(y) = sourceArray(x)(y)
                Else
                    If GetType(T) Is GetType(Boolean) AndAlso initializeValue Then
                        newArray(x)(y) = CType(Convert.ChangeType(True, GetType(T)), T)
                    Else
                        newArray(x)(y) = Nothing
                    End If
                End If
            Next
        Next
        Return newArray
    End Function

    Public Sub PictureBox_MouseEnter(sender As Object, e As EventArgs)
        Dim pb As PictureBox = DirectCast(sender, PictureBox)
        pb.BackColor = Color.LightBlue
    End Sub

    Public Sub PictureBox_MouseLeave(sender As Object, e As EventArgs)
        Dim pb As PictureBox = DirectCast(sender, PictureBox)
        pb.BackColor = Color.Transparent
    End Sub

    Private Sub InitializeAnimationForImages()
        animatedImages.Add(picBaddy1.Image)
        animatedImages.Add(picBaddy2.Image)
        animatedImages.Add(picBaddy3.Image)
        animatedImages.Add(picBaddy4.Image)
        animatedImages.Add(picLazer1.Image)
        animatedImages.Add(picLazer2.Image)
        animatedImages.Add(picFlipper1.Image)
        animatedImages.Add(picFlipper2.Image)
        animatedImages.Add(picFlipper3.Image)
        animatedImages.Add(picFlipper4.Image)
        animatedImages.Add(picCoin.Image)
        animatedImages.Add(picGate.Image)
        animatedImages.Add(picKey.Image)
        animatedImages.Add(picStart.Image)
        animatedImages.Add(picFinish.Image)
        animatedImages.Add(picBall.Image)
        animatedImages.Add(picBomb.Image)
        animatedImages.Add(picWarp.Image)
        animatedImages.Add(picWarp2.Image)

        For Each img In animatedImages
            If img IsNot Nothing Then
                ImageAnimator.Animate(img, AddressOf OnFrameChanged)
            End If
        Next
    End Sub

#End Region

#Region " Event Handlers "

    Private Sub FrmDesigner_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        RemoveHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
        RemoveHandler cboY.SelectedIndexChanged, AddressOf CboY_SelectedIndexChanged

        If cboX.Items.Contains(SQCOUNTX.ToString()) Then
            cboX.SelectedItem = SQCOUNTX.ToString()
        End If
        If cboY.Items.Contains(SQCOUNTY.ToString()) Then
            cboY.SelectedItem = SQCOUNTY.ToString()
        End If

        AddHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
        AddHandler cboY.SelectedIndexChanged, AddressOf CboY_SelectedIndexChanged

        LoadMusic()
        LoadPremades()
        LoadRecentFiles()

        UpdateSelections()
    End Sub

    Private Sub LoadPremades()
        If Not Directory.Exists(premadesFolder) Then Directory.CreateDirectory(premadesFolder)
        lstPremades.Items.Clear()
        For Each file As String In Directory.GetFiles(premadesFolder, "*.Premade")
            lstPremades.Items.Add(Path.GetFileNameWithoutExtension(file))
        Next
    End Sub

    Private Sub LoadMusic()
        If Not Directory.Exists(musicFolder) Then
            Directory.CreateDirectory(musicFolder)
        End If

        Dim allowedExtensions As String() = {".mp3", ".wav"}
        Dim musicFileFound As Boolean = False
        Dim defaultMusicFile As String = Path.Combine(musicFolder, "The Jolly Drummer.wav")
        Dim defaultVolume As Integer = 100
        Dim defaultGain As Integer = 5

        For Each file As String In Directory.GetFiles(musicFolder)
            Dim extension As String = Path.GetExtension(file).ToLower()
            If allowedExtensions.Contains(extension) Then
                Dim fileNameWithoutExtension As String = Path.GetFileNameWithoutExtension(file)
                Dim volume As Integer = 50
                Dim gain As Integer = 10

                SelectedMusic = String.Format("{0}, V:{1}, G:{2}", file, volume, gain)
                lblAudio.Text = String.Format("{0}, V:{1}, G:{2}", fileNameWithoutExtension, volume, gain)

                musicFileFound = True
                Exit For
            End If
        Next

        If Not musicFileFound Then
            ' Set default music file, volume, and gain
            SelectedMusic = String.Format("{0}, V:{1}, G:{2}", defaultMusicFile, defaultVolume, defaultGain)
            lblAudio.Text = String.Format("{0}, V:{1}, G:{2}", Path.GetFileNameWithoutExtension(defaultMusicFile), defaultVolume, defaultGain)
        End If
    End Sub

    Private Sub OnAnimationTick(sender As Object, e As EventArgs)
        For Each img In animatedImages
            ImageAnimator.UpdateFrames(img)
        Next
        picGame.Invalidate()
    End Sub

    Private Sub OnFrameChanged(o As Object, e As EventArgs)
    End Sub

    Private Sub HighlightTimer_Tick(sender As Object, e As EventArgs)
        highlightColor = If(highlightColor = Color.Black, Color.White, Color.Black)
        picGame.Invalidate()
    End Sub

    Private Sub LstPremades_SelectedIndexChanged(sender As Object, e As EventArgs)
        If lstPremades.SelectedIndex >= 0 Then
            If Not isRestoringState Then SaveStateForUndo()
            Dim selectedPremade As String = lstPremades.SelectedItem.ToString()
            LoadSettingsFromFile(Path.Combine(premadesFolder, selectedPremade & ".Premade"))
            UpdateSelections()
            picGame.Invalidate()
        End If
    End Sub

    Private Sub MnuNew_Click(sender As Object, e As EventArgs) Handles mnuNew.Click, btnNew.Click
        If Not isRestoringState Then SaveStateForUndo()
        wallsHatchStyle = HatchStyle.Cross
        wallsColor1 = Color.Gray
        wallsColor2 = Color.DarkGray
        floorsHatchStyle = HatchStyle.SmallCheckerBoard
        floorsColor1 = Color.LightGray
        floorsColor2 = Color.White
        InitializeWalls()
        currentFilePath = ""

        txtName.Text = ""
        SelectedMusic = ""

        UpdateSelections()

        picGame.Invalidate()
    End Sub

    Private Sub MnuSave_Click(sender As Object, e As EventArgs) Handles mnuSave.Click, btnSave.Click
        If String.IsNullOrEmpty(currentFilePath) Then
            MnuSaveAs_Click(sender, e)
        Else
            SaveRoomToFile(currentFilePath)
        End If
    End Sub

    Private Sub MnuSaveAs_Click(sender As Object, e As EventArgs) Handles mnuSaveAs.Click
        ' Create and configure the SaveFileDialog
        Dim saveDialog As New SaveFileDialog() With {
    .InitialDirectory = savesFolder, ' Set initial directory to the project directory
    .Filter = "Room Files (*.room)|*.room",
    .DefaultExt = "room",
    .AddExtension = True,
    .Title = "Save Room As"
    }

        ' Show the dialog and check if the user clicked OK
        If saveDialog.ShowDialog() = DialogResult.OK Then
            ' Save the current room data to the specified file
            SaveRoomToFile(saveDialog.FileName)

            ' Update the current file path
            currentFilePath = saveDialog.FileName

            ' Update recent files list
            UpdateRecentFiles(currentFilePath) ' Update recent files with the new save
        End If
    End Sub

    Private Sub MnuLoad_Click(sender As Object, e As EventArgs) Handles mnuLoad.Click, btnOpen.Click
        Dim openDialog As New OpenFileDialog() With {
    .InitialDirectory = savesFolder,
    .Filter = "Room Files (*.room)|*.room",
    .DefaultExt = "room",
    .AddExtension = True,
    .Title = "Load Room"
    }

        If openDialog.ShowDialog() = DialogResult.OK Then
            If Not isRestoringState Then SaveStateForUndo()
            LoadRoomFromFile(openDialog.FileName)

            UpdateRecentFiles(openDialog.FileName) ' Update recent files

            currentFilePath = openDialog.FileName
            UpdateGridSizeUI()
            UpdateSelections()
            picGame.Invalidate()
        End If
    End Sub
    Private Sub SaveStateForUndo()
        If isRestoringState Then Return
        undoStack.Push(New RoomState(Me))
        If undoStack.Count > 50 Then
            undoStack = New Stack(Of RoomState)(undoStack.Reverse().Take(50))
        End If
    End Sub

    Private Sub SaveState(isNewAction As Boolean)
        If isRestoringState Then Return
        If isNewAction Then
            redoStack.Clear()
        End If
        undoStack.Push(New RoomState(Me))
        If undoStack.Count > 50 Then
            undoStack = New Stack(Of RoomState)(undoStack.Reverse().Take(50))
        End If
    End Sub
    Private Sub BtnUndo_Click(sender As Object, e As EventArgs) Handles btnUndo.Click
        If undoStack.Count > 1 Then
            ' Save the current state for redo before performing undo
            SaveStateForRedo()

            ' Pop the current state from the undo stack
            undoStack.Pop()

            ' Restore the previous state
            isRestoringState = True
            Dim previousState As RoomState = undoStack.Peek()
            previousState.RestoreState(Me)
            isRestoringState = False

            ' Update the UI to reflect the restored state
            UpdateGridSizeUI()
            UpdateSelections()
            picGame.Invalidate()
        End If
    End Sub

    Private Sub SaveStateForRedo()
        If isRestoringState Then Return
        redoStack.Push(New RoomState(Me))
        If redoStack.Count > 50 Then
            redoStack = New Stack(Of RoomState)(redoStack.Reverse().Take(50))
        End If
    End Sub

    Private Sub BtnRedo_Click(sender As Object, e As EventArgs) Handles btnRedo.Click
        If redoStack.Count > 0 Then
            SaveStateForUndo()
            isRestoringState = True
            Dim redoState As RoomState = redoStack.Pop()
            redoState.RestoreState(Me)
            isRestoringState = False
            UpdateGridSizeUI()
            UpdateSelections()
            picGame.Invalidate()
        End If
    End Sub

    Private Sub BtnSavePremade_Click(sender As Object, e As EventArgs)
        Dim saveDialog As New SaveFileDialog() With {
            .InitialDirectory = premadesFolder,
            .Filter = "Premade Files (*.Premade)|*.Premade",
            .DefaultExt = "Premade",
            .AddExtension = True,
            .Title = "Save Premade"
        }
        If saveDialog.ShowDialog() = DialogResult.OK Then
            SaveSettingsToFile(saveDialog.FileName)
            LoadPremades()
        End If
    End Sub

    Private Sub LstHatchstyles_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstHatchstyles.SelectedIndexChanged
        If lstHatchstyles.SelectedItem IsNot Nothing Then
            Dim selectedHatchStyleName As String = lstHatchstyles.SelectedItem.ToString()
            Dim selectedHatchStyle As HatchStyle = CType([Enum].Parse(GetType(HatchStyle), selectedHatchStyleName), HatchStyle)
            If rdoFloors.Checked Then
                floorsHatchStyle = selectedHatchStyle
            ElseIf rdoWalls.Checked Then
                wallsHatchStyle = selectedHatchStyle
            End If
            If Not isRestoringState Then SaveStateForUndo()
            picGame.Invalidate()
        End If
    End Sub

    Private Sub BtnAll_Click(sender As Object, e As EventArgs)
        If Not isRestoringState Then SaveStateForUndo()
        RandomizeWalls()
        RandomizeFloors()
        picGame.Invalidate()
    End Sub

    Private Sub BtnWalls_Click(sender As Object, e As EventArgs)
        If Not isRestoringState Then SaveStateForUndo()
        RandomizeWalls()
        picGame.Invalidate()
    End Sub

    Private Sub BtnFloors_Click(sender As Object, e As EventArgs)
        If Not isRestoringState Then SaveStateForUndo()
        RandomizeFloors()
        picGame.Invalidate()
    End Sub

    Private Sub RandomizeWalls()
        wallsColor1 = Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))
        wallsColor2 = Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))
        Dim hatchStyles As Array = [Enum].GetValues(GetType(HatchStyle))
        wallsHatchStyle = CType(hatchStyles.GetValue(rand.Next(hatchStyles.Length)), HatchStyle)
        UpdateSelections()
    End Sub

    Private Sub RandomizeFloors()
        floorsColor1 = Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))
        floorsColor2 = Color.FromArgb(rand.Next(256), rand.Next(256), rand.Next(256))
        Dim hatchStyles As Array = [Enum].GetValues(GetType(HatchStyle))
        floorsHatchStyle = CType(hatchStyles.GetValue(rand.Next(hatchStyles.Length)), HatchStyle)
        UpdateSelections()
    End Sub

    Private Sub UpdateSelections()
        If rdoWalls.Checked Then
            lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
        ElseIf rdoFloors.Checked Then
            lstHatchstyles.SelectedItem = floorsHatchStyle.ToString()
        ElseIf rdoEdge.Checked Then
            lstHatchstyles.SelectedItem = wallsHatchStyle.ToString()
        End If

        cboStyleChoice.Enabled = rdoFloors.Checked
        If rdoFloors.Checked AndAlso useGradient Then
            lstHatchstyles.Enabled = False
        Else
            lstHatchstyles.Enabled = True
        End If

        If cboStyleChoice.Items.Count > 0 Then
            If useGradient Then
                cboStyleChoice.SelectedIndex = cboStyleChoice.Items.IndexOf("Gradient")
            Else
                cboStyleChoice.SelectedIndex = cboStyleChoice.Items.IndexOf("HatchStyle")
            End If
        End If
    End Sub

    Private Sub RdoWalls_CheckedChanged(sender As Object, e As EventArgs) Handles rdoWalls.CheckedChanged
        If rdoWalls.Checked Then
            UpdateSelections()
        End If
    End Sub

    Private Sub RdoFloors_CheckedChanged(sender As Object, e As EventArgs) Handles rdoFloors.CheckedChanged
        If rdoFloors.Checked Then
            UpdateSelections()
        End If
    End Sub

    Private Sub RdoEdge_CheckedChanged(sender As Object, e As EventArgs) Handles rdoEdge.CheckedChanged
        If rdoEdge.Checked Then
            UpdateSelections()
        End If
    End Sub

    Private Sub PicGame_Paint(sender As Object, e As PaintEventArgs) Handles picGame.Paint
        CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)
        e.Graphics.SmoothingMode = SmoothingMode.None

        If useGradient Then
            DrawGradientBackground(e.Graphics)
        End If

        DrawGrid(e.Graphics)

        If isDraggingItem AndAlso draggedItem IsNot Nothing Then
            Dim img As Image = GetImageByKey(draggedItem.ImageKey)
            If img IsNot Nothing Then
                ImageAnimator.UpdateFrames(img)
                Dim drawRect As Rectangle

                If draggedItemIsWall Then
                    If draggedItemIsVerticalWall Then
                        Dim wallWidth As Integer = WALL_THICKNESS
                        Dim wallHeight As Integer = SQSizeY
                        Dim xPos As Integer = mouseX - WALL_THICKNESS \ 2
                        Dim yPos As Integer = mouseY - SQSizeY \ 2
                        Dim newWidth As Integer = img.Width
                        Dim newHeight As Integer = wallHeight
                        Dim centeredX As Integer = xPos + (wallWidth - newWidth) \ 2
                        drawRect = New Rectangle(centeredX, yPos, newWidth, newHeight)
                    Else
                        Dim wallWidth As Integer = SQSizeX
                        Dim wallHeight As Integer = WALL_THICKNESS
                        Dim xPos As Integer = mouseX - SQSizeX \ 2
                        Dim yPos As Integer = mouseY - WALL_THICKNESS \ 2
                        Dim newWidth As Integer = wallWidth
                        Dim newHeight As Integer = img.Height
                        Dim centeredY As Integer = yPos + (wallHeight - newHeight) \ 2
                        drawRect = New Rectangle(xPos, centeredY, newWidth, newHeight)
                    End If
                Else
                    Dim xPos As Integer = mouseX - SQSizeX \ 2
                    Dim yPos As Integer = mouseY - SQSizeY \ 2
                    drawRect = New Rectangle(xPos, yPos, SQSizeX, SQSizeY)
                End If
                e.Graphics.DrawImage(img, drawRect)
            End If
        End If

        If (isInSelectionMode OrElse isSelectionReadyToDrag OrElse isDraggingSelection) AndAlso Not selectionRectangle.IsEmpty Then
            Using selectionBrush As New SolidBrush(Color.FromArgb(64, Color.Blue))
                e.Graphics.FillRectangle(selectionBrush, selectionRectangle)
            End Using
            Using selectionPen As New Pen(Color.Blue, 2) With {.DashStyle = DashStyle.Dash}
                e.Graphics.DrawRectangle(selectionPen, selectionRectangle)
            End Using
        End If

        If isDraggingSelection Then
            Dim drawCellX As Integer = (selectionRectangle.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
            Dim drawCellY As Integer = (selectionRectangle.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)
            DrawSelectionData(e.Graphics, drawCellX, drawCellY)
        End If

        ' Ensure DrawHighlight is called
        DrawHighlight(e.Graphics)
    End Sub

    Private Sub DrawGradientBackground(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT
        Dim gridWidth As Integer = SQCOUNTX * (SQSizeX + WALL_THICKNESS)
        Dim gridHeight As Integer = SQCOUNTY * (SQSizeY + WALL_THICKNESS)

        Dim gradientRect As New Rectangle(xOrigin, yOrigin, gridWidth, gridHeight)

        Using brush As New LinearGradientBrush(gradientRect, floorsColor1, floorsColor2, LinearGradientMode.Vertical)
            g.FillRectangle(brush, gradientRect)
        End Using
    End Sub

    Private Sub Form_MouseMove(sender As Object, e As MouseEventArgs)
        Dim screenPos As Point = Me.PointToScreen(New Point(e.X, e.Y))
        Dim picGameClientPoint As Point = picGame.PointToClient(screenPos)

        isCursorOnPicGame = picGame.ClientRectangle.Contains(picGameClientPoint)

        If isDraggingItem Then
            mouseX = picGameClientPoint.X
            mouseY = picGameClientPoint.Y

            If isCursorOnPicGame Then
                UpdateHighlight(mouseX, mouseY)
                isMouseInsideGrid = True

                ' Snap the item to the nearest wall and center it
                If draggedItemIsWall Then
                    If draggedItemIsVerticalWall Then
                        mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 4
                        mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
                    Else
                        mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                        mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 4
                    End If
                Else
                    mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                    mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
                End If
            Else
                isMouseInsideGrid = False
            End If

            picGame.Invalidate() ' Invalidate only the picGame control
        End If

        UpdateStatusBar(e.X, e.Y)
    End Sub
    Private Function IsVerticalWallOuterBorder(x As Integer) As Boolean
        Return x = 0 OrElse x = SQCOUNTX
    End Function

    Private Function IsHorizontalWallOuterBorder(y As Integer) As Boolean
        Return y = 0 OrElse y = SQCOUNTY
    End Function

    Private Sub Form_MouseUp(sender As Object, e As MouseEventArgs)
        If isDraggingItem AndAlso e.Button = MouseButtons.Left Then
            ' Before modifying the data model, save the state
            If Not isRestoringState AndAlso Not isActionInProgress Then
                SaveStateForUndo()
                isActionInProgress = True
            End If

            isDraggingItem = False
            Me.Capture = False
            Cursor.Current = Cursors.Default

            Dim screenPos As Point = Me.PointToScreen(New Point(e.X, e.Y))
            Dim picGameClientPoint As Point = picGame.PointToClient(screenPos)

            If picGame.ClientRectangle.Contains(picGameClientPoint) Then
                mouseX = picGameClientPoint.X
                mouseY = picGameClientPoint.Y
                UpdateHighlight(mouseX, mouseY)

                If isMouseInsideGrid Then
                    If draggedItemIsWall Then
                        If draggedItemIsVerticalWall Then
                            If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                           verticalWallItems(highlightedX) IsNot Nothing AndAlso
                           highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

                                If Not IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                                    verticalWallItems(highlightedX)(highlightedY) = draggedItem
                                    verticalWalls(highlightedX)(highlightedY) = False
                                Else
                                    ReturnItemToOriginalLocation()
                                End If
                            End If
                        Else
                            If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
                           horizontalWallItems(highlightedX) IsNot Nothing AndAlso
                           highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then

                                If Not IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                                    horizontalWallItems(highlightedX)(highlightedY) = draggedItem
                                    horizontalWalls(highlightedX)(highlightedY) = False
                                Else
                                    ReturnItemToOriginalLocation()
                                End If
                            End If
                        End If
                    Else
                        If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
                       placedItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

                            placedItems(highlightedX)(highlightedY) = draggedItem
                        End If
                    End If
                Else
                    ReturnItemToOriginalLocation()
                End If
            Else
                ReturnItemToOriginalLocation()
            End If

            draggedItem = Nothing

            If isActionInProgress Then
                isActionInProgress = False
                redoStack.Clear()
            End If

            picGame.Invalidate()
        End If
    End Sub
    Private Sub PicGame_MouseDown(sender As Object, e As MouseEventArgs) Handles picGame.MouseDown
        If SelectionMode Then
            ' Handle selecting a square in selection mode
            Dim clickedX As Integer = (e.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
            Dim clickedY As Integer = (e.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)

            Dim selected As New SelectedObject With {
            .Type = "Square",
            .Coordinates = New Point(clickedX, clickedY)
        }

            RaiseEvent ObjectSelected(selected)

            SelectionMode = False
            SelectionRequestedByForm = Nothing
            SelectionRequestedFor = Nothing

            ' End of action
            isActionInProgress = False
            Return
        End If

        If isSelectionReadyToDrag Then
            If selectionRectangle.Contains(e.Location) Then
                If e.Button = MouseButtons.Left Then
                    isDraggingSelection = True
                    selectionOffset = New Point(e.X - selectionRectangle.X, e.Y - selectionRectangle.Y)
                    selectionStartCell = New Point(
                    (selectionRectangle.X - INDENT) \ (SQSizeX + WALL_THICKNESS),
                    (selectionRectangle.Y - INDENT) \ (SQSizeY + WALL_THICKNESS))
                    picGame.Capture = True
                    Cursor = Cursors.SizeAll
                ElseIf e.Button = MouseButtons.Right Then
                    isSelectionReadyToDrag = False
                    selectionRectangle = Rectangle.Empty
                    Cursor = Cursors.Default
                    picGame.Invalidate()
                End If
            Else
                isSelectionReadyToDrag = False
                selectionRectangle = Rectangle.Empty
                Cursor = Cursors.Default
                picGame.Invalidate()
            End If
        ElseIf isInSelectionMode Then
            If e.Button = MouseButtons.Left Then
                isSelecting = True
                selectionStartPoint = e.Location
                selectionEndPoint = e.Location
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)
                picGame.Invalidate()
            ElseIf e.Button = MouseButtons.Right Then
                If selectionRectangle.Contains(e.Location) Then
                    Dim menuPosition As Point = e.Location
                    ctmMenu.Show(picGame, menuPosition)
                End If
            End If
        Else
            If e.Button = MouseButtons.Right Then
                isRightMouseDown = True
                rightMouseDownPosition = e.Location
                rightMouseDownTime = DateTime.Now
            ElseIf e.Button = MouseButtons.Left Then
                isLeftMouseDown = True
                UpdateHighlight(e.X, e.Y)
                Dim itemPickedUp As Boolean = False
                If isMouseInsideGrid Then
                    ' Handle picking up items for dragging
                    ' Don't need to call SaveStateForUndo() here because it was already called at the start of the action

                    ' Check if the user clicked on an item to pick up
                    If highlightedWallX Then
                        If highlightedX >= 0 AndAlso highlightedX <= verticalWallItems.Length AndAlso
                       verticalWallItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

                            If verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                                isDraggingItem = True
                                draggedItem = verticalWallItems(highlightedX)(highlightedY)
                                draggedItemIsWall = True
                                draggedItemIsVerticalWall = True
                                draggedItemOriginalX = highlightedX
                                draggedItemOriginalY = highlightedY
                                verticalWallItems(highlightedX)(highlightedY) = Nothing
                                verticalWalls(highlightedX)(highlightedY) = True
                                picGame.Capture = True
                                itemPickedUp = True
                            End If
                        End If
                    ElseIf highlightedWallY Then
                        If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
                       horizontalWallItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY <= horizontalWallItems(0).Length Then

                            If horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                                isDraggingItem = True
                                draggedItem = horizontalWallItems(highlightedX)(highlightedY)
                                draggedItemIsWall = True
                                draggedItemIsVerticalWall = False
                                draggedItemOriginalX = highlightedX
                                draggedItemOriginalY = highlightedY
                                horizontalWallItems(highlightedX)(highlightedY) = Nothing
                                horizontalWalls(highlightedX)(highlightedY) = True
                                picGame.Capture = True
                                itemPickedUp = True
                            End If
                        End If
                    Else
                        If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
                       placedItems(highlightedX) IsNot Nothing AndAlso
                       highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

                            If placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                                isDraggingItem = True
                                draggedItem = placedItems(highlightedX)(highlightedY)
                                draggedItemIsWall = False
                                draggedItemIsVerticalWall = False
                                draggedItemOriginalX = highlightedX
                                draggedItemOriginalY = highlightedY
                                placedItems(highlightedX)(highlightedY) = Nothing
                                picGame.Capture = True
                                itemPickedUp = True
                            End If
                        End If
                    End If

                    If Not itemPickedUp Then
                        ' Handle drawing walls or edges
                        If rdoEdge.Checked Then
                            isLeftMouseDown = True
                            SetEdgeStyleAtMousePosition(e.X, e.Y)
                        Else
                            isLeftMouseDown = True
                            AddWallAtMousePosition(e.X, e.Y)
                        End If
                    End If
                Else
                    isLeftMouseDown = False
                    ' Since the user clicked outside the grid, we end the action
                    isActionInProgress = False
                End If
            End If
        End If
    End Sub
    Private Sub PicGame_MouseMove(sender As Object, e As MouseEventArgs)
        isCursorOnPicGame = True

        If isDraggingItem Then
            ' Update the mouse position to the current cursor position
            mouseX = e.X
            mouseY = e.Y

            ' Snap the item to the nearest wall and center it
            UpdateHighlight(e.X, e.Y)
            If draggedItemIsWall Then
                If draggedItemIsVerticalWall Then
                    mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 3
                    mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
                Else
                    mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                    mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 3
                End If
            Else
                mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
            End If

            picGame.Invalidate()
        ElseIf isInSelectionMode Then
            If isSelecting Then
                selectionEndPoint = e.Location
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)
                picGame.Invalidate()
            End If
        Else
            If isDraggingSelection Then
                If selectionData Is Nothing Then
                    CaptureSelectionData()
                End If

                Dim newX As Integer = e.X - selectionOffset.X
                Dim newY As Integer = e.Y - selectionOffset.Y

                Dim cellX As Integer = (newX - INDENT + (SQSizeX + WALL_THICKNESS) \ 2) \ (SQSizeX + WALL_THICKNESS)
                Dim cellY As Integer = (newY - INDENT + (SQSizeY + WALL_THICKNESS) \ 2) \ (SQSizeY + WALL_THICKNESS)

                cellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, cellX))
                cellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, cellY))

                selectionRectangle.X = INDENT + cellX * (SQSizeX + WALL_THICKNESS)
                selectionRectangle.Y = INDENT + cellY * (SQSizeY + WALL_THICKNESS)
                selectionRectangle.Width = selectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS
                selectionRectangle.Height = selectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS

                picGame.Invalidate()
            Else
                If isDraggingItem Then
                    ' Snap the item to the nearest wall and center it
                    UpdateHighlight(e.X, e.Y)
                    If draggedItemIsWall Then
                        If draggedItemIsVerticalWall Then
                            mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 3
                            mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
                        Else
                            mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                            mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 3
                        End If
                    Else
                        mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                        mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
                    End If

                    picGame.Invalidate()
                Else
                    UpdateHighlight(e.X, e.Y)
                    picGame.Invalidate()
                    If isRightMouseDown Then
                        If rdoEdge.Checked Then
                            If isMouseInsideGrid Then
                                RemoveEdgeStyleAtMousePosition(e.X, e.Y)
                            End If
                        Else
                            Dim deltaX As Integer = e.X - rightMouseDownPosition.X
                            Dim deltaY As Integer = e.Y - rightMouseDownPosition.Y
                            Dim distanceSquared As Integer = deltaX * deltaX + deltaY * deltaY
                            If distanceSquared > CLICK_DRAG_THRESHOLD_SQUARED Then
                                RemoveObjectAtMousePosition(e.X, e.Y, removeWalls:=True, removeItems:=False)
                            End If
                        End If
                    End If
                    If isLeftMouseDown Then
                        If rdoEdge.Checked Then
                            If isMouseInsideGrid Then
                                SetEdgeStyleAtMousePosition(e.X, e.Y)
                            End If
                        Else
                            AddWallAtMousePosition(e.X, e.Y)
                        End If
                    End If
                End If

                If isCopying Then
                    If copiedSelectionData Is Nothing Then
                        CaptureSelectionData()
                    End If

                    Dim newX As Integer = e.X - selectionOffset.X
                    Dim newY As Integer = e.Y - selectionOffset.Y

                    Dim cellX As Integer = (newX - INDENT + (SQSizeX + WALL_THICKNESS) \ 2) \ (SQSizeX + WALL_THICKNESS)
                    Dim cellY As Integer = (newY - INDENT + (SQSizeY + WALL_THICKNESS) \ 2) \ (SQSizeY + WALL_THICKNESS)

                    cellX = Math.Max(0, Math.Min(SQCOUNTX - copiedSelectionData.selectionWidth, cellX))
                    cellY = Math.Max(0, Math.Min(SQCOUNTY - copiedSelectionData.selectionHeight, cellY))

                    selectionRectangle.X = INDENT + cellX * (SQSizeX + WALL_THICKNESS)
                    selectionRectangle.Y = INDENT + cellY * (SQSizeY + WALL_THICKNESS)
                    selectionRectangle.Width = copiedSelectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS
                    selectionRectangle.Height = copiedSelectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS

                    picGame.Invalidate()
                End If

                UpdateStatusBar(e.X, e.Y)
            End If
        End If
    End Sub
    Private Sub UpdateStatusBar(pixelX As Integer, pixelY As Integer)

        Dim squareX As Integer = (pixelX - INDENT + (SQSizeX + WALL_THICKNESS) \ 2) \ (SQSizeX + WALL_THICKNESS)
        Dim squareY As Integer = (pixelY - INDENT + (SQSizeY + WALL_THICKNESS) \ 2) \ (SQSizeY + WALL_THICKNESS)

        squareX = Math.Max(0, Math.Min(SQCOUNTX - 1, squareX))
        squareY = Math.Max(0, Math.Min(SQCOUNTY - 1, squareY))

        Dim isOverWall As Boolean = highlightedWallX OrElse highlightedWallY

        Dim content As String = "Empty"
        If isOverWall Then
            If highlightedWallX Then
                If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
           highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then
                    If verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                        content = GetItemName(verticalWallItems(highlightedX)(highlightedY).ImageKey)
                    ElseIf verticalWalls(highlightedX)(highlightedY) Then
                        content = "Wall"
                    End If
                End If
            ElseIf highlightedWallY Then
                If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
           highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then
                    If horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                        content = GetItemName(horizontalWallItems(highlightedX)(highlightedY).ImageKey)
                    ElseIf horizontalWalls(highlightedX)(highlightedY) Then
                        content = "Wall"
                    End If
                End If
            End If
        Else
            If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
       highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then
                If placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                    content = GetItemName(placedItems(highlightedX)(highlightedY).ImageKey)
                ElseIf floorsUseEdgeStyle(highlightedX)(highlightedY) Then
                    content = "Edge"
                End If
            End If
        End If

        If isOverWall Then
            stb.Text = "Location: " & pixelX & ", " & pixelY & " | Wall: " & squareX & ", " & squareY & " | " & content
        Else
            stb.Text = "Location: " & pixelX & ", " & pixelY & " | Square: " & squareX & ", " & squareY & " | " & content
        End If
    End Sub

    Private Function GetItemName(imageKey As String) As String
        Select Case imageKey
            Case "picBlock1"
                Return "Block"
            Case "picBaddy1"
                Return "Baddy1"
            Case "picBaddy2"
                Return "Baddy2"
            Case "picLazer1"
                Return "Lazer1"
            Case "picLazer2"
                Return "Lazer2"
            Case "picFlipper1"
                Return "Flipper1"
            Case "picFlipper2"
                Return "Flipper2"
            Case "picFlipper3"
                Return "Flipper3"
            Case "picFlipper4"
                Return "Flipper4"
            Case "picBaddy3"
                Return "Baddy3"
            Case "picBaddy4"
                Return "Baddy4"
            Case "picCoin"
                Return "Coin"
            Case "picGate"
                Return "Gate"
            Case "picKey"
                Return "Key"
            Case "picStart"
                Return "Start"
            Case "picFinish"
                Return "Finish"
            Case "picBlockStopper1"
                Return "BlockStopper"
            Case "picBall"
                Return "Ball"
            Case "picBlockSwitch"
                Return "BlockSwitch"
            Case "picBomb"
                Return "Bomb"
            Case "picWarp"
                Return "Warp"
            Case "picWarp2"
                Return "Warp2"
            Case Else
                Return "Unknown"
        End Select
    End Function

    Private Sub RemoveEdgeStyleAtMousePosition(mouseX As Integer, mouseY As Integer)
        UpdateHighlight(mouseX, mouseY)
        If Not highlightedWallX AndAlso Not highlightedWallY Then
            If highlightedX >= 0 AndAlso highlightedX < SQCOUNTX AndAlso highlightedY >= 0 AndAlso highlightedY < SQCOUNTY Then
                floorsUseEdgeStyle(highlightedX)(highlightedY) = False
                picGame.Invalidate()
            End If
        End If
    End Sub

    Private Function IsLazerItem(imgKey As String) As Boolean
        Return imgKey = "picLazer1" OrElse imgKey = "picLazer2"
    End Function

    Private Sub PicGame_MouseUp(sender As Object, e As MouseEventArgs) Handles picGame.MouseUp
        If isDraggingSelection Then
            If e.Button = MouseButtons.Left Then
                ' Before modifying the data model, save the state
                If Not isRestoringState AndAlso Not isActionInProgress Then
                    SaveStateForUndo()
                    isActionInProgress = True
                End If

                isDraggingSelection = False
                picGame.Capture = False
                Cursor = Cursors.Default

                If selectionRectangle.IsEmpty Then
                    ' End of action
                    isActionInProgress = False
                    Return
                End If

                Dim newCellX As Integer = (selectionRectangle.X - INDENT + WALL_THICKNESS \ 2) \ (SQSizeX + WALL_THICKNESS)
                Dim newCellY As Integer = (selectionRectangle.Y - INDENT + WALL_THICKNESS \ 2) \ (SQSizeY + WALL_THICKNESS)

                newCellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, newCellX))
                newCellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, newCellY))

                ' Move the selection data to the new position
                MoveSelectionDataTo(newCellX, newCellY)

                selectionRectangle = Rectangle.Empty
                isSelectionReadyToDrag = False
                isInSelectionMode = False
                Cursor = Cursors.Default
                picGame.Invalidate()

                ' End of action
                isActionInProgress = False

            ElseIf e.Button = MouseButtons.Right Then
                isDraggingSelection = False
                picGame.Capture = False
                Cursor = Cursors.Default

                selectionRectangle = New Rectangle(
                INDENT + selectionStartCell.X * (SQSizeX + WALL_THICKNESS),
                INDENT + selectionStartCell.Y * (SQSizeY + WALL_THICKNESS),
                selectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
                selectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)

                isSelectionReadyToDrag = False
                isInSelectionMode = False
                Cursor = Cursors.Default
                picGame.Invalidate()

                ' End of action
                isActionInProgress = False
            End If

        ElseIf isInSelectionMode Then
            If isSelecting AndAlso e.Button = MouseButtons.Left Then
                isSelecting = False
                selectionEndPoint = e.Location
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)

                ' Snap the selection rectangle to the grid cells
                Dim startCellX As Integer = (selectionRectangle.X - INDENT + WALL_THICKNESS \ 2) \ (SQSizeX + WALL_THICKNESS)
                Dim startCellY As Integer = (selectionRectangle.Y - INDENT + WALL_THICKNESS \ 2) \ (SQSizeY + WALL_THICKNESS)
                Dim endCellX As Integer = ((selectionRectangle.Right - INDENT - WALL_THICKNESS \ 2 - 1)) \ (SQSizeX + WALL_THICKNESS)
                Dim endCellY As Integer = ((selectionRectangle.Bottom - INDENT - WALL_THICKNESS \ 2 - 1)) \ (SQSizeY + WALL_THICKNESS)

                startCellX = Math.Max(0, Math.Min(SQCOUNTX - 1, startCellX))
                startCellY = Math.Max(0, Math.Min(SQCOUNTY - 1, startCellY))
                endCellX = Math.Max(0, Math.Min(SQCOUNTX - 1, endCellX))
                endCellY = Math.Max(0, Math.Min(SQCOUNTY - 1, endCellY))

                Dim selectionWidth As Integer = endCellX - startCellX + 1
                Dim selectionHeight As Integer = endCellY - startCellY + 1

                ' Adjust the selection rectangle to fill to the right and bottom edges correctly
                selectionRectangle = New Rectangle(
                INDENT + startCellX * (SQSizeX + WALL_THICKNESS),
                INDENT + startCellY * (SQSizeY + WALL_THICKNESS),
                selectionWidth * (SQSizeX + WALL_THICKNESS),
                selectionHeight * (SQSizeY + WALL_THICKNESS))

                picGame.Invalidate()

                If Not selectionRectangle.IsEmpty AndAlso Not isCopying Then
                    ' Normally, you might show a context menu here, but we are skipping that
                Else
                    isInSelectionMode = False
                    Cursor = Cursors.Default
                End If

                ' End of action
                isActionInProgress = False
            End If

        Else
            If e.Button = MouseButtons.Left Then
                ' Before modifying the data model, save the state if an action was in progress
                If (isLeftMouseDown OrElse isDraggingItem) AndAlso Not isRestoringState AndAlso Not isActionInProgress Then
                    SaveStateForUndo()
                    isActionInProgress = True
                End If

                If isDraggingItem Then
                    isDraggingItem = False
                    picGame.Capture = False
                    Cursor.Current = Cursors.Default

                    ' Snap the item to the nearest wall and center it
                    UpdateHighlight(e.X, e.Y)
                    If isMouseInsideGrid Then
                        If draggedItemIsWall Then
                            If draggedItemIsVerticalWall Then
                                If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                               verticalWallItems(highlightedX) IsNot Nothing AndAlso
                               highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

                                    If Not IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                                        verticalWallItems(highlightedX)(highlightedY) = draggedItem
                                        verticalWalls(highlightedX)(highlightedY) = False
                                    Else
                                        ReturnItemToOriginalLocation()
                                    End If
                                End If
                            Else
                                If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
                               horizontalWallItems(highlightedX) IsNot Nothing AndAlso
                               highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then

                                    If Not IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                                        horizontalWallItems(highlightedX)(highlightedY) = draggedItem
                                        horizontalWalls(highlightedX)(highlightedY) = False
                                    Else
                                        ReturnItemToOriginalLocation()
                                    End If
                                End If
                            End If
                        Else
                            If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
                           placedItems(highlightedX) IsNot Nothing AndAlso
                           highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

                                placedItems(highlightedX)(highlightedY) = draggedItem
                            End If
                        End If
                    Else
                        ReturnItemToOriginalLocation()
                    End If

                    ' Invalidate the picGame control to refresh the display
                    picGame.Invalidate()

                    ' End of action
                    isActionInProgress = False

                ElseIf isLeftMouseDown Then
                    ' The user was possibly adding walls or edges
                    ' Process any final updates if necessary

                    ' End of action
                    isActionInProgress = False
                End If

                isLeftMouseDown = False

            ElseIf e.Button = MouseButtons.Right Then
                ' Before modifying the data model, save the state if an action was in progress
                If isRightMouseDown AndAlso Not isRestoringState AndAlso Not isActionInProgress Then
                    SaveStateForUndo()
                    isActionInProgress = True
                End If

                isRightMouseDown = False

                If rdoEdge.Checked Then
                    UpdateHighlight(e.X, e.Y)
                    If isMouseInsideGrid Then
                        If Not highlightedWallX AndAlso Not highlightedWallY Then
                            If highlightedX >= 0 AndAlso highlightedX < SQCOUNTX AndAlso
                           highlightedY >= 0 AndAlso highlightedY < SQCOUNTY Then
                                floorsUseEdgeStyle(highlightedX)(highlightedY) = False
                                picGame.Invalidate()
                            End If
                        End If
                    End If
                Else
                    Dim deltaX As Integer = e.X - rightMouseDownPosition.X
                    Dim deltaY As Integer = e.Y - rightMouseDownPosition.Y
                    Dim distanceSquared As Integer = deltaX * deltaX + deltaY * deltaY
                    If distanceSquared < CLICK_DRAG_THRESHOLD_SQUARED Then
                        RemoveObjectAtMousePosition(e.X, e.Y, removeWalls:=False, removeItems:=True)
                    End If
                End If

                ' End of action
                isActionInProgress = False
            End If
        End If

        ' Handle clicking outside the selected area as Cancel
        If isInSelectionMode AndAlso Not selectionRectangle.Contains(e.Location) Then
            mnuCancel_Click(Nothing, EventArgs.Empty)
        End If

        ' Handle pasting the copied or moved items
        If isCopying OrElse Not isCopying Then
            If copiedSelectionData IsNot Nothing Then
                ' Before modifying the data model, save the state
                If Not isRestoringState AndAlso Not isActionInProgress Then
                    SaveStateForUndo()
                    isActionInProgress = True
                End If

                Dim newCellX As Integer = (selectionRectangle.X - INDENT + WALL_THICKNESS \ 2) \ (SQSizeX + WALL_THICKNESS)
                Dim newCellY As Integer = (selectionRectangle.Y - INDENT + WALL_THICKNESS \ 2) \ (SQSizeY + WALL_THICKNESS)

                newCellX = Math.Max(0, Math.Min(SQCOUNTX - copiedSelectionData.selectionWidth, newCellX))
                newCellY = Math.Max(0, Math.Min(SQCOUNTY - copiedSelectionData.selectionHeight, newCellY))

                ' Paste the copied or moved items to the new location
                PasteSelectionDataTo(newCellX, newCellY)

                isCopying = False
                isSelectionReadyToDrag = False
                Cursor = Cursors.Default
                picGame.Invalidate()

                ' End of action
                isActionInProgress = False
            End If
        End If
    End Sub
    Public Sub UpdateGridSizeUI()
        RemoveHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
        RemoveHandler cboY.SelectedIndexChanged, AddressOf CboY_SelectedIndexChanged

        If Not cboX.Items.Contains(SQCOUNTX.ToString()) Then
            cboX.Items.Add(SQCOUNTX.ToString())
        End If
        cboX.SelectedItem = SQCOUNTX.ToString()

        If Not cboY.Items.Contains(SQCOUNTY.ToString()) Then
            cboY.Items.Add(SQCOUNTY.ToString())
        End If
        cboY.SelectedItem = SQCOUNTY.ToString()

        AddHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
        AddHandler cboY.SelectedIndexChanged, AddressOf CboY_SelectedIndexChanged

        CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)

        picGame.Invalidate()
    End Sub

#End Region

#Region " Utility and Helper Methods "

    Private Function IsHorizontalWallConnectedToEdge(pt As Point) As Boolean
        Dim x As Integer = pt.X
        Dim y As Integer = pt.Y

        If y > 0 AndAlso floorsUseEdgeStyle(x)(y - 1) Then Return True
        If y < SQCOUNTY AndAlso floorsUseEdgeStyle(x)(y) Then Return True

        Return False
    End Function

    Private Function IsVerticalWallConnectedToEdge(pt As Point) As Boolean
        Dim x As Integer = pt.X
        Dim y As Integer = pt.Y

        If x > 0 AndAlso floorsUseEdgeStyle(x - 1)(y) Then Return True
        If x < SQCOUNTX AndAlso floorsUseEdgeStyle(x)(y) Then Return True

        Return False
    End Function

    Private Sub CalculateSquareSizes(clientWidth As Integer, clientHeight As Integer)
        If SQCOUNTX <= 0 OrElse SQCOUNTY <= 0 Then
            Throw New InvalidOperationException("Grid dimensions must be greater than zero.")
        End If

        SQSizeX = (clientWidth - 2 * INDENT - (SQCOUNTX + 1) * WALL_THICKNESS) \ SQCOUNTX
        SQSizeY = (clientHeight - 2 * INDENT - (SQCOUNTY + 1) * WALL_THICKNESS) \ SQCOUNTY
    End Sub

    Private Sub DrawGrid(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        DrawGradientBackground(g)

        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY - 1
                Dim floorRect As New Rectangle(
            xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
            yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
            SQSizeX,
            SQSizeY
        )

                If floorsUseEdgeStyle(x)(y) Then
                    Using floorBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                        g.FillRectangle(floorBrush, floorRect)
                    End Using
                ElseIf useGradient Then
                    Using hatchBrush As New HatchBrush(floorsHatchStyle, Color.Transparent, Color.Transparent)
                        g.FillRectangle(hatchBrush, floorRect)
                    End Using
                Else
                    Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                        g.FillRectangle(floorBrush, floorRect)
                    End Using
                End If
            Next
        Next

        For x As Integer = 0 To SQCOUNTX
            For y As Integer = 0 To SQCOUNTY
                Dim intersectionRect As New Rectangle(
            xOrigin + x * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2,
            yOrigin + y * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2,
            WALL_THICKNESS,
            WALL_THICKNESS
        )

                Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                    g.FillRectangle(wallBrush, intersectionRect)
                End Using
            Next
        Next

        For x As Integer = 0 To SQCOUNTX
            For y As Integer = 0 To SQCOUNTY - 1
                Dim wallRect As New Rectangle(
            xOrigin + x * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2,
            yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
            WALL_THICKNESS,
            SQSizeY
        )

                If x >= 0 AndAlso x < verticalWalls.Length AndAlso
           verticalWalls(x) IsNot Nothing AndAlso
           y >= 0 AndAlso y < verticalWalls(0).Length Then

                    If verticalWalls(x)(y) Then
                        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                            g.FillRectangle(wallBrush, wallRect)
                        End Using
                    ElseIf useGradient Then
                        Using hatchBrush As New HatchBrush(floorsHatchStyle, Color.Transparent, Color.Transparent)
                            g.FillRectangle(hatchBrush, wallRect)
                        End Using
                    Else
                        Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                            g.FillRectangle(floorBrush, wallRect)
                        End Using
                    End If
                End If
            Next
        Next

        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY
                Dim wallRect As New Rectangle(
            xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
            yOrigin + y * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2,
            SQSizeX,
            WALL_THICKNESS
        )

                If x >= 0 AndAlso x < horizontalWalls.Length AndAlso
           horizontalWalls(x) IsNot Nothing AndAlso
           y >= 0 AndAlso y < horizontalWalls(0).Length Then

                    If horizontalWalls(x)(y) Then
                        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                            g.FillRectangle(wallBrush, wallRect)
                        End Using
                    ElseIf useGradient Then
                        Using hatchBrush As New HatchBrush(floorsHatchStyle, Color.Transparent, Color.Transparent)
                            g.FillRectangle(hatchBrush, wallRect)
                        End Using
                    Else
                        Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                            g.FillRectangle(floorBrush, wallRect)
                        End Using
                    End If
                End If
            Next
        Next

        DrawItems(g)
        DrawWallItems(g)

        DrawGridNumbers(g)
    End Sub

    Private Sub DrawGridNumbers(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT
        Dim font As New Font("Arial", 10, FontStyle.Regular)
        Dim brush As New SolidBrush(Color.Black)

        Dim columnNumberOffsetY As Single = -20.0F
        Dim rowNumberOffsetX As Single = -5.0F

        For x As Integer = 0 To SQCOUNTX - 1
            Dim xPos As Single = xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + CSng(SQSizeX) / 2
            Dim yPos As Single = yOrigin + columnNumberOffsetY

            Dim label As String = x.ToString()
            Dim xLabelSize As SizeF = g.MeasureString(label, font)
            g.DrawString(label, font, brush, xPos - xLabelSize.Width / 2.0F, yPos)
        Next

        For y As Integer = 0 To SQCOUNTY - 1
            Dim xPos As Single = xOrigin + rowNumberOffsetX
            Dim yPos As Single = yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + CSng(SQSizeY) / 2

            Dim label As String = y.ToString()
            Dim yLabelSize As SizeF = g.MeasureString(label, font)
            g.DrawString(label, font, brush, xPos - yLabelSize.Width, yPos - yLabelSize.Height / 2.0F)
        Next

        font.Dispose()
        brush.Dispose()
    End Sub

    Friend Sub FillBorderWalls()
        For y As Integer = 0 To SQCOUNTY - 1
            verticalWalls(0)(y) = True
            verticalWalls(SQCOUNTX)(y) = True
        Next

        For x As Integer = 0 To SQCOUNTX - 1
            horizontalWalls(x)(0) = True
            horizontalWalls(x)(SQCOUNTY) = True
        Next
    End Sub

    Private Sub DrawItems(g As Graphics)
        For x As Integer = 0 To placedItems.Length - 1
            If placedItems(x) IsNot Nothing Then
                For y As Integer = 0 To placedItems(x).Length - 1
                    If placedItems(x)(y) IsNot Nothing Then
                        Dim img As Image = GetImageByKey(placedItems(x)(y).ImageKey)
                        If img IsNot Nothing Then
                            ImageAnimator.UpdateFrames(img)
                            Dim xPos As Integer = INDENT + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                            Dim yPos As Integer = INDENT + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                            Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)

                            Dim imgAttributes As New Imaging.ImageAttributes()
                            imgAttributes.SetWrapMode(WrapMode.TileFlipXY)
                            g.DrawImage(img, destRect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttributes)
                        End If
                    End If
                Next
            End If
        Next
    End Sub

    Private Sub DrawWallItems(g As Graphics)
        For x As Integer = 0 To verticalWallItems.Length - 1
            If verticalWallItems(x) IsNot Nothing Then
                For y As Integer = 0 To verticalWallItems(x).Length - 1
                    If verticalWallItems(x)(y) IsNot Nothing Then
                        Dim img As Image = GetImageByKey(verticalWallItems(x)(y).ImageKey)
                        If img IsNot Nothing Then
                            ImageAnimator.UpdateFrames(img)
                            Dim wallX As Integer = INDENT + x * (SQSizeX + WALL_THICKNESS) - (WALL_THICKNESS \ 2)
                            Dim wallY As Integer = INDENT + y * (SQSizeY + WALL_THICKNESS) + (WALL_THICKNESS \ 2)
                            Dim wallWidth As Integer = WALL_THICKNESS
                            Dim wallHeight As Integer = SQSizeY

                            Dim newWidth As Integer = img.Width
                            Dim newHeight As Integer = wallHeight

                            Dim centeredX As Integer = wallX + (wallWidth - newWidth) \ 2
                            Dim drawRect As New Rectangle(centeredX, wallY, newWidth, newHeight)
                            g.DrawImage(img, drawRect)
                        End If
                    End If
                Next
            End If
        Next

        For x As Integer = 0 To horizontalWallItems.Length - 1
            If horizontalWallItems(x) IsNot Nothing Then
                For y As Integer = 0 To horizontalWallItems(x).Length - 1
                    If horizontalWallItems(x)(y) IsNot Nothing Then
                        Dim img As Image = GetImageByKey(horizontalWallItems(x)(y).ImageKey)
                        If img IsNot Nothing Then
                            ImageAnimator.UpdateFrames(img)
                            Dim wallX As Integer = INDENT + x * (SQSizeX + WALL_THICKNESS) + (WALL_THICKNESS \ 2)
                            Dim wallY As Integer = INDENT + y * (SQSizeY + WALL_THICKNESS) - (WALL_THICKNESS \ 2)
                            Dim wallWidth As Integer = SQSizeX
                            Dim wallHeight As Integer = WALL_THICKNESS

                            Dim newWidth As Integer = wallWidth
                            Dim newHeight As Integer = img.Height

                            Dim centeredY As Integer = wallY + (wallHeight - newHeight) \ 2
                            Dim drawRect As New Rectangle(wallX, centeredY, newWidth, newHeight)
                            g.DrawImage(img, drawRect)
                        End If
                    End If
                Next
            End If
        Next
    End Sub

    Private Sub DrawHighlight(g As Graphics)
        If Not isMouseInsideGrid OrElse highlightedX < 0 OrElse highlightedY < 0 OrElse Not isCursorOnPicGame Then
            Return
        End If

        Using highlightPen As New Pen(highlightColor, 2) With {.DashStyle = DashStyle.Dash}
            If highlightedWallX Then
                Dim x As Integer = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2
                Dim y As Integer = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim rect As New Rectangle(x, y, WALL_THICKNESS, SQSizeY)
                g.DrawRectangle(highlightPen, rect)
            ElseIf highlightedWallY Then
                Dim x As Integer = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim y As Integer = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2
                Dim rect As New Rectangle(x, y, SQSizeX, WALL_THICKNESS)
                g.DrawRectangle(highlightPen, rect)
            Else
                Dim x As Integer = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim y As Integer = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim rect As New Rectangle(x, y, SQSizeX, SQSizeY)
                g.DrawRectangle(highlightPen, rect)
            End If
        End Using
    End Sub
    Private Sub UpdateHighlight(mouseX As Integer, mouseY As Integer)
        mouseX -= INDENT
        mouseY -= INDENT

        isMouseInsideGrid = False
        highlightedWallX = False
        highlightedWallY = False
        highlightedX = -1
        highlightedY = -1

        Dim cellSizeX As Integer = SQSizeX + WALL_THICKNESS
        Dim cellSizeY As Integer = SQSizeY + WALL_THICKNESS
        Dim cellX As Integer = mouseX \ cellSizeX
        Dim cellY As Integer = mouseY \ cellSizeY

        If cellX >= 0 AndAlso cellX <= SQCOUNTX AndAlso
       cellY >= 0 AndAlso cellY <= SQCOUNTY Then

            Dim offsetX As Integer = mouseX Mod cellSizeX
            Dim offsetY As Integer = mouseY Mod cellSizeY

            highlightedWallX = offsetX < WALL_THICKNESS
            highlightedWallY = offsetY < WALL_THICKNESS
            highlightedX = cellX
            highlightedY = cellY

            isMouseInsideGrid = True
        End If

        If highlightedX < 0 Then highlightedX = 0
        If highlightedX > SQCOUNTX Then highlightedX = SQCOUNTX
        If highlightedY < 0 Then highlightedY = 0
        If highlightedY > SQCOUNTY Then highlightedY = SQCOUNTY

        If highlightedWallX AndAlso (highlightedX = 0 OrElse highlightedX = SQCOUNTX) Then
            highlightedWallX = True
        End If

        If highlightedWallY AndAlso (highlightedY = 0 OrElse highlightedY = SQCOUNTY) Then
            highlightedWallY = True
        End If
    End Sub

    Private Function GetRectangle(p1 As Point, p2 As Point) As Rectangle
        Dim x As Integer = Math.Min(p1.X, p2.X)
        Dim y As Integer = Math.Min(p1.Y, p2.Y)
        Dim width As Integer = Math.Abs(p1.X - p2.X)
        Dim height As Integer = Math.Abs(p1.Y - p2.Y)
        Return New Rectangle(x, y, width, height)
    End Function

    Private Sub SetEdgeStyleAtMousePosition(mouseX As Integer, mouseY As Integer)
        UpdateHighlight(mouseX, mouseY)
        If Not highlightedWallX AndAlso Not highlightedWallY Then
            If highlightedX >= 0 AndAlso highlightedX < SQCOUNTX AndAlso highlightedY >= 0 AndAlso highlightedY < SQCOUNTY Then
                floorsUseEdgeStyle(highlightedX)(highlightedY) = True

                If highlightedX >= 0 AndAlso highlightedX < verticalWalls.Length AndAlso
           verticalWalls(highlightedX) IsNot Nothing AndAlso
           highlightedY >= 0 AndAlso highlightedY < verticalWalls(0).Length Then
                    verticalWallItems(highlightedX)(highlightedY) = Nothing
                    verticalWalls(highlightedX)(highlightedY) = True
                End If

                If highlightedX + 1 >= 0 AndAlso highlightedX + 1 < verticalWalls.Length AndAlso
           verticalWalls(highlightedX + 1) IsNot Nothing AndAlso
           highlightedY >= 0 AndAlso highlightedY < verticalWalls(0).Length Then
                    verticalWallItems(highlightedX + 1)(highlightedY) = Nothing
                    verticalWalls(highlightedX + 1)(highlightedY) = True
                End If

                If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length AndAlso
           horizontalWalls(highlightedX) IsNot Nothing AndAlso
           highlightedY >= 0 AndAlso highlightedY < horizontalWalls(0).Length Then
                    horizontalWallItems(highlightedX)(highlightedY) = Nothing
                    horizontalWalls(highlightedX)(highlightedY) = True
                End If

                If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length AndAlso
           horizontalWalls(highlightedX) IsNot Nothing AndAlso
           highlightedY + 1 >= 0 AndAlso highlightedY + 1 < horizontalWalls(0).Length Then
                    horizontalWallItems(highlightedX)(highlightedY + 1) = Nothing
                    horizontalWalls(highlightedX)(highlightedY + 1) = True
                End If

                picGame.Invalidate()
            End If
        End If
    End Sub

    Private Function GetCellsInRectangle(rect As Rectangle) As List(Of Point)
        Dim selectedCells As New List(Of Point)
        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY - 1
                Dim cellRect As Rectangle = GetCellRectangle(x, y)
                If rect.IntersectsWith(cellRect) Then
                    selectedCells.Add(New Point(x, y))
                End If
            Next
        Next
        Return selectedCells
    End Function

    Private Function GetCellRectangle(x As Integer, y As Integer) As Rectangle
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT
        Return New Rectangle(
        xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
        yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
        SQSizeX,
        SQSizeY)
    End Function

    Private Function GetVerticalWallsInRectangle(rect As Rectangle) As List(Of Point)
        Dim selectedWalls As New List(Of Point)
        For x As Integer = 0 To SQCOUNTX
            For y As Integer = 0 To SQCOUNTY - 1
                Dim wallRect As Rectangle = GetVerticalWallRectangle(x, y)
                If rect.IntersectsWith(wallRect) Then
                    selectedWalls.Add(New Point(x, y))
                End If
            Next
        Next
        Return selectedWalls
    End Function

    Private Function GetVerticalWallRectangle(x As Integer, y As Integer) As Rectangle
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT
        Return New Rectangle(
        xOrigin + x * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS \ 2,
        yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
        WALL_THICKNESS, SQSizeY)
    End Function

    Private Function GetHorizontalWallsInRectangle(rect As Rectangle) As List(Of Point)
        Dim selectedWalls As New List(Of Point)
        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY
                Dim wallRect As Rectangle = GetHorizontalWallRectangle(x, y)
                If rect.IntersectsWith(wallRect) Then
                    selectedWalls.Add(New Point(x, y))
                End If
            Next
        Next
        Return selectedWalls
    End Function

    Private Function GetHorizontalWallRectangle(x As Integer, y As Integer) As Rectangle
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT
        Return New Rectangle(
        xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
        yOrigin + y * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2,
        SQSizeX, WALL_THICKNESS)
    End Function

    Private Sub AddColorPictureBoxHandlers(parent As Control)
        For Each ctrl As Control In parent.Controls
            If TypeOf ctrl Is PictureBox AndAlso ctrl.Name.StartsWith("col") Then
                AddHandler ctrl.Click, AddressOf ColorPictureBox_Click
            End If
            If ctrl.HasChildren Then
                AddColorPictureBoxHandlers(ctrl)
            End If
        Next
    End Sub

    Private Sub ColorPictureBox_Click(sender As Object, e As EventArgs)
        If Not isRestoringState Then SaveStateForUndo()
        Dim pb As PictureBox = CType(sender, PictureBox)
        Dim selectedColor As Color = pb.BackColor

        If pb.Name.StartsWith("col") Then
            Dim numStr As String = pb.Name.Substring(3)
            Dim num As Integer
            If Integer.TryParse(numStr, num) Then
                If num >= 1 AndAlso num <= 48 Then
                    If rdoFloors.Checked Then
                        floorsColor1 = selectedColor
                    ElseIf rdoWalls.Checked Then
                        wallsColor1 = selectedColor
                    ElseIf rdoEdge.Checked Then
                        wallsColor1 = selectedColor
                    End If
                ElseIf num >= 51 AndAlso num <= 98 Then
                    If rdoFloors.Checked Then
                        floorsColor2 = selectedColor
                    ElseIf rdoWalls.Checked Then
                        wallsColor2 = selectedColor
                    ElseIf rdoEdge.Checked Then
                        wallsColor2 = selectedColor
                    End If
                End If
            End If
        End If

        picGame.Invalidate()
    End Sub

    Private Sub PicBlock_MouseDown(sender As Object, e As MouseEventArgs)
        Dim pic As PictureBox = CType(sender, PictureBox)

        isDraggingItem = True

        ' Create a new PlacedItem with the ImageKey
        draggedItem = New PlacedItem() With {.ImageKey = pic.Name}

        ' Determine if it's a wall item
        draggedItemIsWall = IsVerticalItem(pic.Name) OrElse IsHorizontalItem(pic.Name)
        draggedItemIsVerticalWall = IsVerticalItem(pic.Name)

        ' Since it's being dragged from the toolbox, there is no original position
        draggedItemOriginalX = -1
        draggedItemOriginalY = -1

        ' Capture the mouse events at the form level
        Me.Capture = True

        Cursor.Current = Cursors.Hand

        ' Initialize the mouse position to the current cursor position
        Dim mousePos As Point = picGame.PointToClient(Cursor.Position)
        mouseX = mousePos.X
        mouseY = mousePos.Y
        picGame.Invalidate()
    End Sub

    Private Sub ReturnItemToOriginalLocation()
        If draggedItemIsWall Then
            If draggedItemOriginalX >= 0 AndAlso draggedItemOriginalY >= 0 Then
                If draggedItemIsVerticalWall Then
                    verticalWallItems(draggedItemOriginalX)(draggedItemOriginalY) = draggedItem
                    verticalWalls(draggedItemOriginalX)(draggedItemOriginalY) = False
                Else
                    horizontalWallItems(draggedItemOriginalX)(draggedItemOriginalY) = draggedItem
                    horizontalWalls(draggedItemOriginalX)(draggedItemOriginalY) = False
                End If
            End If
        Else
            If draggedItemOriginalX >= 0 AndAlso draggedItemOriginalY >= 0 Then
                placedItems(draggedItemOriginalX)(draggedItemOriginalY) = draggedItem
            End If
        End If
    End Sub

    Private Function GetImageByKey(imageKey As String) As Image
        Select Case imageKey
            Case "picBlock1"
                Return picBlock1.Image
            Case "picBaddy1"
                Return picBaddy1.Image
            Case "picBaddy2"
                Return picBaddy2.Image
            Case "picBaddy3"
                Return picBaddy3.Image
            Case "picBaddy4"
                Return picBaddy4.Image
            Case "picLazer1"
                Return picLazer1.Image
            Case "picLazer2"
                Return picLazer2.Image
            Case "picFlipper1"
                Return picFlipper1.Image
            Case "picFlipper2"
                Return picFlipper2.Image
            Case "picFlipper3"
                Return picFlipper3.Image
            Case "picFlipper4"
                Return picFlipper4.Image
            Case "picCoin"
                Return picCoin.Image
            Case "picGate"
                Return picGate.Image
            Case "picKey"
                Return picKey.Image
            Case "picStart"
                Return picStart.Image
            Case "picFinish"
                Return picFinish.Image
            Case "picBlockStopper1"
                Return picBlockStopper1.Image
            Case "picBall"
                Return picBall.Image
            Case "picBlockSwitch"
                Return picBlockSwitch.Image
            Case "picBomb"
                Return picBomb.Image
            Case "picWarp"
                Return picWarp.Image
            Case "picWarp2"
                Return picWarp2.Image
            Case Else
                Return Nothing
        End Select
    End Function

    Private Function IsVerticalItem(imgKey As String) As Boolean
        Return imgKey = "picLazer1" OrElse imgKey = "picFlipper1" OrElse imgKey = "picFlipper2"
    End Function

    Private Function IsHorizontalItem(imgKey As String) As Boolean
        Return imgKey = "picLazer2" OrElse imgKey = "picFlipper3" OrElse imgKey = "picFlipper4"
    End Function

    Private Function IsFlipperItem(imgKey As String) As Boolean
        Return imgKey = "picFlipper1" OrElse imgKey = "picFlipper2" OrElse imgKey = "picFlipper3" OrElse imgKey = "picFlipper4"
    End Function

    Private Sub AddWallAtMousePosition(mouseX As Integer, mouseY As Integer)
        UpdateHighlight(mouseX, mouseY)

        If highlightedWallX Then
            If highlightedX >= 0 AndAlso highlightedX < verticalWalls.Length AndAlso
   highlightedY >= 0 AndAlso highlightedY < verticalWalls(0).Length Then
                verticalWalls(highlightedX)(highlightedY) = True
                picGame.Invalidate()
            End If
        ElseIf highlightedWallY Then
            If highlightedX >= 0 AndAlso highlightedX < horizontalWalls.Length AndAlso
   highlightedY >= 0 AndAlso highlightedY < horizontalWalls(0).Length Then
                horizontalWalls(highlightedX)(highlightedY) = True
                picGame.Invalidate()
            End If
        End If
    End Sub

    Private Sub RemoveObjectAtMousePosition(mouseX As Integer, mouseY As Integer, removeWalls As Boolean, removeItems As Boolean)
        UpdateHighlight(mouseX, mouseY)

        If Not isMouseInsideGrid Then
            Return
        End If

        If highlightedWallX Then
            If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
   verticalWallItems(highlightedX) IsNot Nothing AndAlso
   highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

                If removeItems AndAlso verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                    verticalWallItems(highlightedX)(highlightedY) = Nothing
                    verticalWalls(highlightedX)(highlightedY) = True
                    picGame.Invalidate()

                    If IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                        verticalWalls(highlightedX)(highlightedY) = True
                    End If
                ElseIf removeWalls AndAlso verticalWalls(highlightedX)(highlightedY) Then
                    If Not IsVerticalWallOuterBorder(highlightedX) AndAlso Not IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                        verticalWalls(highlightedX)(highlightedY) = False
                        picGame.Invalidate()
                    End If
                End If
            End If
        ElseIf highlightedWallY Then
            If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
   horizontalWallItems(highlightedX) IsNot Nothing AndAlso
   highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then

                If removeItems AndAlso horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                    horizontalWallItems(highlightedX)(highlightedY) = Nothing
                    horizontalWalls(highlightedX)(highlightedY) = True
                    picGame.Invalidate()

                    If IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                        horizontalWalls(highlightedX)(highlightedY) = True
                    End If
                ElseIf removeWalls AndAlso horizontalWalls(highlightedX)(highlightedY) Then
                    If Not IsHorizontalWallOuterBorder(highlightedY) AndAlso Not IsHorizontalWallConnectedToEdge(New Point(highlightedX, highlightedY)) Then
                        horizontalWalls(highlightedX)(highlightedY) = False
                        picGame.Invalidate()
                    End If
                End If
            End If
        Else
            If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
   placedItems(highlightedX) IsNot Nothing AndAlso
   highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

                If removeItems AndAlso placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                    placedItems(highlightedX)(highlightedY) = Nothing
                    picGame.Invalidate()
                End If
            End If
        End If
    End Sub

    Private Sub LoadSettingsFromFile(filePath As String)
        Try
            If Not File.Exists(filePath) Then Return
            Dim lines As String() = File.ReadAllLines(filePath)
            Dim section As String = ""
            For Each line As String In lines
                line = line.Trim()
                If String.IsNullOrEmpty(line) OrElse line.StartsWith(";"c) Then Continue For
                If line.StartsWith("[") AndAlso line.EndsWith("]") Then
                    section = line.Substring(1, line.Length - 2)
                Else
                    Dim parts() As String = line.Split(New Char() {":"c}, 2)
                    If parts.Length = 2 Then
                        LoadProperty(parts(0).Trim(), parts(1).Trim(), section)
                    End If
                End If
            Next

            UpdateSelections()
        Catch ex As Exception
            MessageBox.Show("Error loading settings: " & ex.Message)
        End Try
    End Sub

    Private Sub LoadProperty(key As String, value As String, section As String)
        Dim isWall As Boolean = (section = "Walls")
        Select Case key
            Case "HatchStyle"
                Dim hatchStyle As HatchStyle
                If [Enum].TryParse(value, hatchStyle) Then
                    If isWall Then wallsHatchStyle = hatchStyle Else floorsHatchStyle = hatchStyle
                End If
            Case "Color1"
                Dim color As Color = ParseColor(value)
                If isWall Then wallsColor1 = color Else floorsColor1 = color
            Case "Color2"
                Dim color As Color = ParseColor(value)
                If isWall Then wallsColor2 = color Else floorsColor2 = color
        End Select
    End Sub

    Private Function ParseColor(colorString As String) As Color
        Dim parts() As String = colorString.Split(","c)
        If parts.Length = 4 Then
            Dim a, r, g, b As Integer
            If Integer.TryParse(parts(0), a) AndAlso Integer.TryParse(parts(1), r) AndAlso
       Integer.TryParse(parts(2), g) AndAlso Integer.TryParse(parts(3), b) Then
                Return Color.FromArgb(a, r, g, b)
            End If
        End If
        Return Color.Black
    End Function

    Private Sub SaveSettingsToFile(filePath As String)
        Try
            Using sw As New StreamWriter(filePath)
                sw.WriteLine("[Walls]")
                sw.WriteLine("HatchStyle: " & wallsHatchStyle.ToString())
                sw.WriteLine("Color1: " & ColorToString(wallsColor1))
                sw.WriteLine("Color2: " & ColorToString(wallsColor2))
                sw.WriteLine()
                sw.WriteLine("[Floors]")
                sw.WriteLine("HatchStyle: " & floorsHatchStyle.ToString())
                sw.WriteLine("Color1: " & ColorToString(floorsColor1))
                sw.WriteLine("Color2: " & ColorToString(floorsColor2))
            End Using
        Catch ex As Exception
            MessageBox.Show("Error saving settings: " & ex.Message)
        End Try
    End Sub

    Private Function ColorToString(color As Color) As String
        Return $"{color.A},{color.R},{color.G},{color.B}"
    End Function

    Private Sub BtnTest_Click(sender As Object, e As EventArgs) Handles btnTest.Click
        Dim tempFilePath As String = Path.Combine(Path.GetTempPath(), "temp.room")
        SaveRoomToFile(tempFilePath)

        PauseBackgroundTasks()

        Dim gameForm As New frmGame()
        gameForm.RoomFilePath = tempFilePath
        AddHandler gameForm.FormClosed, AddressOf OnGameFormClosed
        gameForm.Show()
    End Sub

    Private Sub PauseBackgroundTasks()
        animationTimer.Stop()
        highlightTimer.Stop()
    End Sub

    Private Sub ResumeBackgroundTasks()
        animationTimer.Start()
        highlightTimer.Start()
    End Sub

    Private Sub OnGameFormClosed(sender As Object, e As FormClosedEventArgs)
        ResumeBackgroundTasks()
    End Sub

    Private Function CloneJaggedArray(Of T)(sourceArray As T()()) As T()()
        Dim lengthOuter As Integer = sourceArray.Length
        Dim newArray As T()() = New T(lengthOuter - 1)() {}
        For i As Integer = 0 To lengthOuter - 1
            If sourceArray(i) IsNot Nothing Then
                Dim lengthInner As Integer = sourceArray(i).Length
                newArray(i) = New T(lengthInner - 1) {}
                Array.Copy(sourceArray(i), newArray(i), lengthInner)
            End If
        Next
        Return newArray
    End Function

    Private Function ConvertPlacedItemsToStrings(items As PlacedItem()()) As String()()
        Dim lengthOuter As Integer = items.Length
        Dim stringItems As String()() = New String(lengthOuter - 1)() {}
        For x As Integer = 0 To lengthOuter - 1
            If items(x) IsNot Nothing Then
                Dim lengthInner As Integer = items(x).Length
                stringItems(x) = New String(lengthInner - 1) {}
                For y As Integer = 0 To lengthInner - 1
                    If items(x)(y) IsNot Nothing Then
                        stringItems(x)(y) = items(x)(y).ImageKey
                    Else
                        stringItems(x)(y) = Nothing
                    End If
                Next
            End If
        Next
        Return stringItems
    End Function

    Public Sub SaveRoomToFile(filePath As String)
        Try
            Dim currentState As New RoomData(Me)

            Dim options As New JsonSerializerOptions With {
    .IncludeFields = True,
    .WriteIndented = True
}
            Dim jsonString As String = JsonSerializer.Serialize(currentState, options)

            File.WriteAllText(filePath, jsonString)
        Catch ex As Exception
            MessageBox.Show("Error saving room: " & ex.Message)
        End Try
    End Sub

    Private Sub LoadRoomFromFile(filePath As String)
        Try
            If Not File.Exists(filePath) Then Return

            Dim options As New JsonSerializerOptions With {
    .IncludeFields = True
}
            Dim jsonString As String = File.ReadAllText(filePath)
            Dim roomData As RoomData = JsonSerializer.Deserialize(Of RoomData)(jsonString, options)

            If roomData Is Nothing Then
                MessageBox.Show("Error: Failed to deserialize room data.")
                Return
            End If

            isRestoringState = True
            roomData.RestoreState(Me)
            isRestoringState = False

            UpdateSelections()

            If roomData.useGradient Then
                cboStyleChoice.SelectedIndex = cboStyleChoice.Items.IndexOf("Gradient")
            Else
                cboStyleChoice.SelectedIndex = cboStyleChoice.Items.IndexOf("HatchStyle")
            End If

            Me.Text = filePath

            ' Update the audio label with the selected music
            Dim selectedMusicParts As String() = SelectedMusic.Split(New String() {", V:", ", G:"}, StringSplitOptions.None)
            If selectedMusicParts.Length = 3 Then
                Dim audioFile As String = selectedMusicParts(0).Trim()
                Dim volume As Integer = Integer.Parse(selectedMusicParts(1).Trim())
                Dim gain As Integer = Integer.Parse(selectedMusicParts(2).Trim())
                Dim fileNameWithoutExtension As String = Path.GetFileNameWithoutExtension(audioFile)
                lblAudio.Text = String.Format("{0}, V:{1}, G:{2}", fileNameWithoutExtension, volume, gain)
            End If

        Catch ex As Exception
            MessageBox.Show("Error loading room: " & ex.Message)
        End Try
    End Sub

    Private Sub BtnTriggerEditor_Click(sender As Object, e As EventArgs) Handles btnTriggerEditor.Click
        PauseBackgroundTasks()
        Dim frm As New frmTriggerEditor()
        frm.ShowDialog()
    End Sub

    Private Sub BtnAudio_Click(sender As Object, e As EventArgs) Handles btnAudio.Click, lblAudio.Click
        Dim selectedMusicParts As String() = SelectedMusic.Split(New String() {", V:", ", G:"}, StringSplitOptions.None)
        If selectedMusicParts.Length = 3 Then
            Dim audioFile As String = selectedMusicParts(0).Trim()
            Dim volume As Integer = Integer.Parse(selectedMusicParts(1).Trim())
            Dim gain As Integer = Integer.Parse(selectedMusicParts(2).Trim())
            PauseBackgroundTasks()
            Using frm As New frmMusic With {
    .InitialVolume = volume,
    .InitialGain = gain,
    .InitialSelectedTrack = audioFile
}
                If frm.ShowDialog() = DialogResult.OK Then
                    Dim returnedVolume As Integer = frm.VolumeLevel
                    Dim returnedGain As Integer = frm.GainLevel
                    Dim returnedAudioFile As String = frm.AudioFile

                    SelectedMusic = String.Format("{0}, V:{1}, G:{2}", returnedAudioFile, returnedVolume, returnedGain)

                    Dim fileNameWithoutExtension As String = Path.GetFileNameWithoutExtension(returnedAudioFile)
                    lblAudio.Text = String.Format("{0}, V:{1}, G:{2}", fileNameWithoutExtension, returnedVolume, returnedGain)
                End If
            End Using
        Else
            MessageBox.Show("SelectedMusic string is not in the expected format.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Sub

#End Region

    Private Sub BtnFillDeadEnds_Click(sender As Object, e As EventArgs) Handles btnFillDeadEnds.Click
        If Not isRestoringState Then SaveStateForUndo()
        FillDeadEnds()
    End Sub
    Private Sub FillDeadEnds()
        If Not isRestoringState Then SaveStateForUndo()
        Dim wallCount As Integer = 0

        ' Loop through each square in the grid
        For y As Integer = 0 To SQCOUNTY - 1
            For x As Integer = 0 To SQCOUNTX - 1
                wallCount = 0
                Dim isFlipperPresent As Boolean = False ' To track if any flippers are present

                ' Count the walls around the square at (x, y)
                If x = 0 OrElse (x > 0 AndAlso (verticalWalls(x)(y) OrElse IsFlipperItem(verticalWallItems(x)(y)))) Then
                    wallCount += 1  ' Check vertical wall on the left
                    If IsFlipperItem(verticalWallItems(x)(y)) Then isFlipperPresent = True
                End If
                If x = SQCOUNTX - 1 OrElse (x < SQCOUNTX AndAlso (verticalWalls(x + 1)(y) OrElse IsFlipperItem(verticalWallItems(x + 1)(y)))) Then
                    wallCount += 1  ' Check vertical wall on the right
                    If IsFlipperItem(verticalWallItems(x + 1)(y)) Then isFlipperPresent = True
                End If
                If y = 0 OrElse (y > 0 AndAlso (horizontalWalls(x)(y) OrElse IsFlipperItem(horizontalWallItems(x)(y)))) Then
                    wallCount += 1  ' Check horizontal wall on the top
                    If IsFlipperItem(horizontalWallItems(x)(y)) Then isFlipperPresent = True
                End If
                If y = SQCOUNTY - 1 OrElse (y < SQCOUNTY AndAlso (horizontalWalls(x)(y + 1) OrElse IsFlipperItem(horizontalWallItems(x)(y + 1)))) Then
                    wallCount += 1  ' Check horizontal wall on the bottom
                    If IsFlipperItem(horizontalWallItems(x)(y + 1)) Then isFlipperPresent = True
                End If

                ' Check if the square is not occupied, has three walls, and does not contain a flipper
                If wallCount = 3 AndAlso Not isFlipperPresent AndAlso placedItems(x)(y) Is Nothing Then
                    placedItems(x)(y) = New PlacedItem() With {.ImageKey = "picCoin"}
                End If
            Next
        Next

        ' Refresh the game panel to show changes
        picGame.Invalidate()
    End Sub
    Private Function IsFlipperItem(item As PlacedItem) As Boolean
        If item Is Nothing Then Return False
        Return item.ImageKey = "picFlipper1" OrElse item.ImageKey = "picFlipper2" OrElse item.ImageKey = "picFlipper3" OrElse item.ImageKey = "picFlipper4"
    End Function
#Region " Menu "

    Private Sub MnuSelectAll_Click(sender As Object, e As EventArgs) Handles mnuSelectAll.Click
        SelectAllItems()
        ShowMenu()
    End Sub

    Private Sub SelectAllItemsOfType(ParamArray itemKeys As String())
        Dim selectedPoints As New List(Of Point)

        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY - 1
                Dim localX As Integer = x
                Dim localY As Integer = y
                If placedItems(localX)(localY) IsNot Nothing AndAlso Array.Exists(itemKeys, Function(key) placedItems(localX)(localY).ImageKey = key) Then
                    selectedPoints.Add(New Point(localX, localY))
                End If
            Next
        Next

        If selectedPoints.Count > 0 Then
            Dim minX = selectedPoints.Min(Function(p) p.X)
            Dim minY = selectedPoints.Min(Function(p) p.Y)
            Dim maxX = selectedPoints.Max(Function(p) p.X)
            Dim maxY = selectedPoints.Max(Function(p) p.Y)

            selectionRectangle = New Rectangle(
        INDENT + minX * (SQSizeX + WALL_THICKNESS),
        INDENT + minY * (SQSizeY + WALL_THICKNESS),
        (maxX - minX + 1) * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
        (maxY - minY + 1) * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)

            isInSelectionMode = True
            Cursor = Cursors.Cross
            picGame.Invalidate()
        End If
    End Sub

    Private Sub SelectAllWalls()
        selectionRectangle = New Rectangle(INDENT, INDENT, SQCOUNTX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS, SQCOUNTY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)
        isInSelectionMode = True
        Cursor = Cursors.Cross
        picGame.Invalidate()
    End Sub

    Private Sub SelectAllEdges()
        selectionRectangle = New Rectangle(INDENT, INDENT, SQCOUNTX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS, SQCOUNTY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)
        isInSelectionMode = True
        Cursor = Cursors.Cross
        picGame.Invalidate()
    End Sub

    Private Sub SelectAllItems()
        selectionRectangle = New Rectangle(INDENT, INDENT, SQCOUNTX * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS, SQCOUNTY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)
        isInSelectionMode = True
        Cursor = Cursors.Cross
        picGame.Invalidate()
    End Sub

    Private Sub ShowMenu()
        Dim menuPosition As Point = picGame.PointToClient(MousePosition)
        ctmMenu.Show(picGame, menuPosition)
    End Sub

    Private Function GetItemKeysForType(type As String) As String()
        Select Case type
            Case "Baddies"
                Return New String() {"picBaddy1", "picBaddy2", "picBaddy3", "picBaddy4"}
            Case "BlockStoppers"
                Return New String() {"picBlockStopper1"}
            Case "Blocks"
                Return New String() {"picBlock1"}
            Case "Coins"
                Return New String() {"picCoin"}
            Case "Keys"
                Return New String() {"picKey"}
            Case "Warps"
                Return New String() {"picWarp", "picWarp2"}
            Case "Walls"
                Return New String() {}
            Case "Edge"
                Return New String() {}
            Case Else
                Return New String() {}
        End Select
    End Function

#End Region

#Region " ctmMenu "
    Private Sub BtnSelect_Click(sender As Object, e As EventArgs) Handles btnSelect.Click
        SelectionMode = True
        isInSelectionMode = True
        Cursor = Cursors.Cross
        picGame.Invalidate()
    End Sub
    Private Sub ctmMenu_Opened(sender As Object, e As EventArgs)
        AddHandler ctmMenu.Closed, AddressOf ctmMenu_Closed
    End Sub

    Private Sub ctmMenu_Closed(sender As Object, e As ToolStripDropDownClosedEventArgs)
        If isInSelectionMode Then
            Dim menuPosition = picGame.PointToClient(MousePosition)
            ctmMenu.Show(picGame, menuPosition)
        End If
    End Sub
    Private Sub mnuCancel_Click(sender As Object, e As EventArgs)
        isInSelectionMode = False
        isSelectionReadyToDrag = False
        Cursor = Cursors.Default
        picGame.Invalidate()
    End Sub

    ' **** FILL ****
    Private Sub mnuFill_Click(sender As Object, e As EventArgs) Handles mnuFillBlocks.Click, mnuFillBlockStoppers.Click, mnuFillCoins.Click, mnuFIllEdge.Click, mnuFillKeys.Click, mnuFillWalls.Click
        If Not isRestoringState Then SaveStateForUndo()
        If selectionRectangle.IsEmpty Then
            MessageBox.Show("Please select an area before using the fill operation.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        ' Extract the selection type from the sender's name
        Dim menuItem = CType(sender, ToolStripMenuItem)
        Dim selectionType = menuItem.Name.Substring(7) ' Remove the "mnuFill" prefix

        ' Call the appropriate fill method based on the selection type
        Select Case selectionType
            Case "Edge"
                FillSelectedAreaWithEdge()
            Case "Walls"
                FillSelectedAreaWithWalls()
            Case "BlockStoppers"
                FillSelectedAreaWithItems("picBlockStopper1")
            Case "Blocks"
                FillSelectedAreaWithItems("picBlock1")
            Case "Coins"
                FillSelectedAreaWithItems("picCoin")
            Case "Keys"
                FillSelectedAreaWithItems("picKey")
            Case Else
                MessageBox.Show($"Invalid selection type for fill operation: {selectionType}.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Select
    End Sub

    Private Sub mnuClear_Click(sender As Object, e As EventArgs) Handles mnuClearAll.Click, mnuClearBaddies.Click, mnuClearBlocks.Click, mnuClearBlockStoppers.Click, mnuClearCoins.Click, mnuClearEdge.Click, mnuClearKeys.Click, mnuClearWalls.Click, mnuClearWarps.Click
        If Not isRestoringState Then SaveStateForUndo()
        If selectionRectangle.IsEmpty Then
            Return
        End If

        ' Extract the selection type from the sender's name
        Dim menuItem = CType(sender, ToolStripMenuItem)
        Dim selectionType = menuItem.Name.Substring(8) ' Remove the "mnuClear" prefix

        ' Call the appropriate clear method based on the selection type
        Select Case selectionType
            Case "All"
                ClearSelectedArea()
            Case "Edge"
                ClearSelectedAreaEdge()
            Case "Walls"
                ClearSelectedAreaWalls()
            Case Else
                ClearSelectedAreaItems(GetItemKeysForType(selectionType))
        End Select
    End Sub

    Private Sub mnuCopy_Click(sender As Object, e As EventArgs) Handles mnuCopyAll.Click, mnuCopyBaddies.Click, mnuCopyBlocks.Click, mnuCopyBlockStoppers.Click, mnuCopyCoins.Click, mnuCopyEdge.Click, mnuCopyKeys.Click, mnuCopyWalls.Click, mnuCopyWarps.Click
        If Not isRestoringState Then SaveStateForUndo()
        If selectionRectangle.IsEmpty Then
            Return
        End If

        CaptureSelectionData()
        isCopying = True

        ' Extract the selection type from the sender's name
        Dim menuItem = CType(sender, ToolStripMenuItem)
        copiedSelectionType = menuItem.Name.Substring(6) ' Remove the "mnuCopy" prefix

        isSelectionReadyToDrag = True
        Cursor = Cursors.SizeAll
        picGame.Invalidate()

        ' Remove the code that shows the context menu
        ' ShowMenu()
    End Sub

    Private Sub mnuMove_Click(sender As Object, e As EventArgs) Handles mnuMoveAll.Click, mnuMoveBaddies.Click, mnuMoveBlocks.Click, mnuMoveBlockStoppers.Click, mnuMoveCoins.Click, mnuMoveEdge.Click, mnuMoveKeys.Click, mnuMoveWalls.Click, mnuMoveWarps.Click
        If Not isRestoringState Then SaveStateForUndo()
        If selectionRectangle.IsEmpty Then
            Return
        End If

        CaptureSelectionData()
        isCopying = False

        ' Extract the selection type from the sender's name
        Dim menuItem = CType(sender, ToolStripMenuItem)
        copiedSelectionType = menuItem.Name.Substring(6) ' Remove the "mnuMove" prefix

        isSelectionReadyToDrag = True
        Cursor = Cursors.SizeAll
        picGame.Invalidate()

        ' Remove the code that shows the context menu
        ' ShowMenu()
    End Sub
    ' **** Helper Methods ****
    Private Sub CaptureSelectionData()
        If selectionRectangle.IsEmpty Then
            Return
        End If

        Dim startCellX As Integer = (selectionRectangle.X - INDENT + WALL_THICKNESS \ 2) \ (SQSizeX + WALL_THICKNESS)
        Dim startCellY As Integer = (selectionRectangle.Y - INDENT + WALL_THICKNESS \ 2) \ (SQSizeY + WALL_THICKNESS)
        Dim endCellX As Integer = ((selectionRectangle.Right - INDENT - WALL_THICKNESS \ 2 - 1)) \ (SQSizeX + WALL_THICKNESS)
        Dim endCellY As Integer = ((selectionRectangle.Bottom - INDENT - WALL_THICKNESS \ 2 - 1)) \ (SQSizeY + WALL_THICKNESS)

        startCellX = Math.Max(0, Math.Min(SQCOUNTX - 1, startCellX))
        startCellY = Math.Max(0, Math.Min(SQCOUNTY - 1, startCellY))
        endCellX = Math.Max(0, Math.Min(SQCOUNTX - 1, endCellX))
        endCellY = Math.Max(0, Math.Min(SQCOUNTY - 1, endCellY))

        Dim selectionWidth As Integer = endCellX - startCellX + 1
        Dim selectionHeight As Integer = endCellY - startCellY + 1

        selectionData = New SelectionData()
        selectionData.selectionWidth = selectionWidth
        selectionData.selectionHeight = selectionHeight

        selectionData.placedItems = New PlacedItem(selectionWidth - 1)() {}
        selectionData.floorsUseEdgeStyle = New Boolean(selectionWidth - 1)() {}
        For x As Integer = 0 To selectionWidth - 1
            selectionData.placedItems(x) = New PlacedItem(selectionHeight - 1) {}
            selectionData.floorsUseEdgeStyle(x) = New Boolean(selectionHeight - 1) {}
        Next

        Dim verticalWallsWidth As Integer = selectionWidth + 1
        Dim verticalWallsHeight As Integer = selectionHeight
        selectionData.verticalWalls = New Boolean(verticalWallsWidth - 1)() {}
        selectionData.verticalWallItems = New PlacedItem(verticalWallsWidth - 1)() {}
        For x As Integer = 0 To verticalWallsWidth - 1
            selectionData.verticalWalls(x) = New Boolean(verticalWallsHeight - 1) {}
            selectionData.verticalWallItems(x) = New PlacedItem(verticalWallsHeight - 1) {}
        Next

        Dim horizontalWallsWidth As Integer = selectionWidth
        Dim horizontalWallsHeight As Integer = selectionHeight + 1
        selectionData.horizontalWalls = New Boolean(horizontalWallsWidth - 1)() {}
        selectionData.horizontalWallItems = New PlacedItem(horizontalWallsWidth - 1)() {}
        For x As Integer = 0 To horizontalWallsWidth - 1
            selectionData.horizontalWalls(x) = New Boolean(horizontalWallsHeight - 1) {}
            selectionData.horizontalWallItems(x) = New PlacedItem(horizontalWallsHeight - 1) {}
        Next

        For x As Integer = 0 To selectionWidth - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionHeight - 1
                Dim localY As Integer = y
                If placedItems(startCellX + localX)(startCellY + localY) IsNot Nothing Then
                    selectionData.placedItems(localX)(localY) = placedItems(startCellX + localX)(startCellY + localY)
                    selectionData.floorsUseEdgeStyle(localX)(localY) = floorsUseEdgeStyle(startCellX + localX)(startCellY + localY)
                End If
            Next
        Next

        For x As Integer = 0 To verticalWallsWidth - 1
            Dim localX As Integer = x
            For y As Integer = 0 To verticalWallsHeight - 1
                Dim localY As Integer = y
                selectionData.verticalWalls(localX)(localY) = verticalWalls(startCellX + localX)(startCellY + localY)
                selectionData.verticalWallItems(localX)(localY) = verticalWallItems(startCellX + localX)(startCellY + localY)
            Next
        Next

        For x As Integer = 0 To horizontalWallsWidth - 1
            Dim localX As Integer = x
            For y As Integer = 0 To horizontalWallsHeight - 1
                Dim localY As Integer = y
                selectionData.horizontalWalls(localX)(localY) = horizontalWalls(startCellX + localX)(startCellY + localY)
                selectionData.horizontalWallItems(localX)(localY) = horizontalWallItems(startCellX + localX)(startCellY + localY)
            Next
        Next

        selectionStartCell = New Point(startCellX, startCellY)
    End Sub

    Private Sub PasteSelectionDataTo(newCellX As Integer, newCellY As Integer)
        If copiedSelectionData Is Nothing Then
            Return
        End If

        ' Paste the copied or moved items to the new location
        If copiedSelectionType = "All" Then
            ' Paste all items
            For x As Integer = 0 To copiedSelectionData.selectionWidth - 1
                Dim localX As Integer = x
                For y As Integer = 0 To copiedSelectionData.selectionHeight - 1
                    Dim localY As Integer = y
                    If newCellX + localX < SQCOUNTX AndAlso newCellY + localY < SQCOUNTY Then
                        placedItems(newCellX + localX)(newCellY + localY) = copiedSelectionData.placedItems(localX)(localY)
                        floorsUseEdgeStyle(newCellX + localX)(newCellY + localY) = copiedSelectionData.floorsUseEdgeStyle(localX)(localY)
                    End If
                Next
            Next
            For x As Integer = 0 To copiedSelectionData.verticalWalls.Length - 1
                Dim localX As Integer = x
                For y As Integer = 0 To copiedSelectionData.verticalWalls(0).Length - 1
                    Dim localY As Integer = y
                    If newCellX + localX < verticalWalls.Length AndAlso newCellY + localY < verticalWalls(0).Length Then
                        verticalWalls(newCellX + localX)(newCellY + localY) = copiedSelectionData.verticalWalls(localX)(localY)
                        verticalWallItems(newCellX + localX)(newCellY + localY) = copiedSelectionData.verticalWallItems(localX)(localY)
                    End If
                Next
            Next
            For x As Integer = 0 To copiedSelectionData.horizontalWalls.Length - 1
                Dim localX As Integer = x
                For y As Integer = 0 To copiedSelectionData.horizontalWalls(0).Length - 1
                    Dim localY As Integer = y
                    If newCellX + localX < horizontalWalls.Length AndAlso newCellY + localY < horizontalWalls(0).Length Then
                        horizontalWalls(newCellX + localX)(newCellY + localY) = copiedSelectionData.horizontalWalls(localX)(localY)
                        horizontalWallItems(newCellX + localX)(newCellY + localY) = copiedSelectionData.horizontalWallItems(localX)(localY)
                    End If
                Next
            Next
        ElseIf copiedSelectionType = "Edge" Then
            ' Paste edges
            For x As Integer = 0 To copiedSelectionData.selectionWidth - 1
                Dim localX As Integer = x
                For y As Integer = 0 To copiedSelectionData.selectionHeight - 1
                    Dim localY As Integer = y
                    If newCellX + localX < SQCOUNTX AndAlso newCellY + localY < SQCOUNTY Then
                        floorsUseEdgeStyle(newCellX + localX)(newCellY + localY) = copiedSelectionData.floorsUseEdgeStyle(localX)(localY)
                    End If
                Next
            Next
        ElseIf copiedSelectionType = "Walls" Then
            ' Paste walls
            For x As Integer = 0 To copiedSelectionData.verticalWalls.Length - 1
                Dim localX As Integer = x
                For y As Integer = 0 To copiedSelectionData.verticalWalls(0).Length - 1
                    Dim localY As Integer = y
                    If newCellX + localX < verticalWalls.Length AndAlso newCellY + localY < verticalWalls(0).Length Then
                        verticalWalls(newCellX + localX)(newCellY + localY) = copiedSelectionData.verticalWalls(localX)(localY)
                        verticalWallItems(newCellX + localX)(newCellY + localY) = copiedSelectionData.verticalWallItems(localX)(localY)
                    End If
                Next
            Next
            For x As Integer = 0 To copiedSelectionData.horizontalWalls.Length - 1
                Dim localX As Integer = x
                For y As Integer = 0 To copiedSelectionData.horizontalWalls(0).Length - 1
                    Dim localY As Integer = y
                    If newCellX + localX < horizontalWalls.Length AndAlso newCellY + localY < horizontalWalls(0).Length Then
                        horizontalWalls(newCellX + localX)(newCellY + localY) = copiedSelectionData.horizontalWalls(localX)(localY)
                        horizontalWallItems(newCellX + localX)(newCellY + localY) = copiedSelectionData.horizontalWallItems(localX)(localY)
                    End If
                Next
            Next
        Else
            ' Paste specific items
            For x As Integer = 0 To copiedSelectionData.selectionWidth - 1
                Dim localX As Integer = x
                For y As Integer = 0 To copiedSelectionData.selectionHeight - 1
                    Dim localY As Integer = y
                    If newCellX + localX < SQCOUNTX AndAlso newCellY + localY < SQCOUNTY Then
                        If copiedSelectionData.placedItems(localX)(localY) IsNot Nothing AndAlso Array.Exists(GetItemKeysForType(copiedSelectionType), Function(key) copiedSelectionData.placedItems(localX)(localY).ImageKey = key) Then
                            placedItems(newCellX + localX)(newCellY + localY) = copiedSelectionData.placedItems(localX)(localY)
                        End If
                    End If
                Next
            Next
        End If

        ' Clear the original selection area if moving
        If Not isCopying Then
            ClearSelectionDataAt(selectionStartCell.X, selectionStartCell.Y)
        End If

        ' Update the selection rectangle to the new position
        selectionRectangle = New Rectangle(
    INDENT + newCellX * (SQSizeX + WALL_THICKNESS),
    INDENT + newCellY * (SQSizeY + WALL_THICKNESS),
    copiedSelectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
    copiedSelectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)

        ' Invalidate the picGame control to refresh the display
        picGame.Invalidate()
    End Sub

    Private Sub MoveSelectionDataTo(newCellX As Integer, newCellY As Integer)
        ' Ensure the new position is within bounds
        newCellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, newCellX))
        newCellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, newCellY))

        ' Move placed items
        For x As Integer = 0 To selectionData.selectionWidth - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim localY As Integer = y
                If newCellX + localX >= 0 AndAlso newCellX + localX < SQCOUNTX AndAlso newCellY + localY >= 0 AndAlso newCellY + localY < SQCOUNTY Then
                    placedItems(newCellX + localX)(newCellY + localY) = selectionData.placedItems(localX)(localY)
                    floorsUseEdgeStyle(newCellX + localX)(newCellY + localY) = selectionData.floorsUseEdgeStyle(localX)(localY)
                End If
            Next
        Next

        ' Move vertical walls
        For x As Integer = 0 To selectionData.verticalWalls.Length - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionData.verticalWalls(0).Length - 1
                Dim localY As Integer = y
                If newCellX + localX >= 0 AndAlso newCellX + localX < verticalWalls.Length AndAlso newCellY + localY >= 0 AndAlso newCellY + localY < verticalWalls(0).Length Then
                    verticalWalls(newCellX + localX)(newCellY + localY) = selectionData.verticalWalls(localX)(localY)
                    verticalWallItems(newCellX + localX)(newCellY + localY) = selectionData.verticalWallItems(localX)(localY)
                End If
            Next
        Next

        ' Move horizontal walls
        For x As Integer = 0 To selectionData.horizontalWalls.Length - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionData.horizontalWalls(0).Length - 1
                Dim localY As Integer = y
                If newCellX + localX >= 0 AndAlso newCellX + localX < horizontalWalls.Length AndAlso newCellY + localY >= 0 AndAlso newCellY + localY < horizontalWalls(0).Length Then
                    horizontalWalls(newCellX + localX)(newCellY + localY) = selectionData.horizontalWalls(localX)(localY)
                    horizontalWallItems(newCellX + localX)(newCellY + localY) = selectionData.horizontalWallItems(localX)(localY)
                End If
            Next
        Next

        ' Clear the original selection area
        ClearSelectionDataAt(selectionStartCell.X, selectionStartCell.Y)

        ' Update the selection rectangle to the new position
        selectionRectangle = New Rectangle(
INDENT + newCellX * (SQSizeX + WALL_THICKNESS),
INDENT + newCellY * (SQSizeY + WALL_THICKNESS),
selectionData.selectionWidth * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
selectionData.selectionHeight * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS)

        ' Invalidate the picGame control to refresh the display
        picGame.Invalidate()
    End Sub

    Private Sub ClearSelectionDataAt(oldCellX As Integer, oldCellY As Integer)
        For x As Integer = 0 To selectionData.selectionWidth - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim localY As Integer = y
                If oldCellX + localX >= 0 AndAlso oldCellX + localX < SQCOUNTX AndAlso oldCellY + localY >= 0 AndAlso oldCellY + localY < SQCOUNTY Then
                    placedItems(oldCellX + localX)(oldCellY + localY) = Nothing
                    floorsUseEdgeStyle(oldCellX + localX)(oldCellY + localY) = False
                End If
            Next
        Next

        For x As Integer = 0 To selectionData.verticalWalls.Length - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionData.verticalWalls(0).Length - 1
                Dim localY As Integer = y
                If oldCellX + localX >= 0 AndAlso oldCellX + localX < verticalWalls.Length AndAlso oldCellY + localY >= 0 AndAlso oldCellY + localY < verticalWalls(0).Length Then
                    verticalWalls(oldCellX + localX)(oldCellY + localY) = True
                    verticalWallItems(oldCellX + localX)(oldCellY + localY) = Nothing
                End If
            Next
        Next

        For x As Integer = 0 To selectionData.horizontalWalls.Length - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionData.horizontalWalls(0).Length - 1
                Dim localY As Integer = y
                If oldCellX + localX >= 0 AndAlso oldCellX + localX < horizontalWalls.Length AndAlso oldCellY + localY >= 0 AndAlso oldCellY + localY < horizontalWalls(0).Length Then
                    horizontalWalls(oldCellX + localX)(oldCellY + localY) = True
                    horizontalWallItems(oldCellX + localX)(oldCellY + localY) = Nothing
                End If
            Next
        Next
    End Sub

    Private Sub DrawSelectionData(g As Graphics, baseCellX As Integer, baseCellY As Integer)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        Dim colorMatrix As New Imaging.ColorMatrix()
        colorMatrix.Matrix33 = 0.5F
        Dim imgAttributes As New Imaging.ImageAttributes()
        imgAttributes.SetColorMatrix(colorMatrix, Imaging.ColorMatrixFlag.Default, Imaging.ColorAdjustType.Bitmap)

        For x As Integer = 0 To selectionData.selectionWidth - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim localY As Integer = y
                If selectionData.placedItems(localX)(localY) IsNot Nothing Then
                    Dim img As Image = GetImageByKey(selectionData.placedItems(localX)(localY).ImageKey)
                    If img IsNot Nothing Then
                        ImageAnimator.UpdateFrames(img)
                        Dim xPos As Integer = xOrigin + (baseCellX + localX) * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                        Dim yPos As Integer = yOrigin + (baseCellY + localY) * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                        Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)
                        g.DrawImage(img, destRect, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttributes)
                    End If
                End If
            Next
        Next
    End Sub

    Private Sub FillSelectedAreaWithEdge()
        CaptureSelectionData()
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionStartCell.X + x < SQCOUNTX AndAlso selectionStartCell.Y + y < SQCOUNTY Then
                    floorsUseEdgeStyle(selectionStartCell.X + x)(selectionStartCell.Y + y) = True
                End If
            Next
        Next
        For x As Integer = 0 To selectionData.selectionWidth
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionStartCell.X + x < verticalWalls.Length AndAlso selectionStartCell.Y + y < verticalWalls(0).Length Then
                    verticalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = True
                End If
            Next
        Next
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight
                If selectionStartCell.X + x < horizontalWalls.Length AndAlso selectionStartCell.Y + y < horizontalWalls(0).Length Then
                    horizontalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = True
                End If
            Next
        Next
        picGame.Invalidate()
    End Sub
    Private Sub FillSelectedAreaWithWalls()
        CaptureSelectionData()
        For x As Integer = 0 To selectionData.selectionWidth
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionStartCell.X + x < verticalWalls.Length AndAlso selectionStartCell.Y + y < verticalWalls(0).Length Then
                    verticalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = True
                End If
            Next
        Next
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight
                If selectionStartCell.X + x < horizontalWalls.Length AndAlso selectionStartCell.Y + y < horizontalWalls(0).Length Then
                    horizontalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = True
                End If
            Next
        Next
        picGame.Invalidate()
    End Sub

    Private Sub FillSelectedAreaWithItems(itemKey As String)
        CaptureSelectionData()
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionStartCell.X + x < placedItems.Length AndAlso selectionStartCell.Y + y < placedItems(0).Length Then
                    placedItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = New PlacedItem() With {.ImageKey = itemKey}
                End If
            Next
        Next
        picGame.Invalidate()
    End Sub

    Private Sub ClearSelectedArea()
        If selectionData Is Nothing Then
            CaptureSelectionData()
        End If

        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionStartCell.X + x < SQCOUNTX AndAlso selectionStartCell.Y + y < SQCOUNTY Then
                    floorsUseEdgeStyle(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
                    placedItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = Nothing
                End If
            Next
        Next
        For x As Integer = 0 To selectionData.selectionWidth
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionStartCell.X + x < verticalWalls.Length AndAlso selectionStartCell.Y + y < verticalWalls(0).Length Then
                    verticalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
                    verticalWallItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = Nothing
                End If
            Next
        Next
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight
                If selectionStartCell.X + x < horizontalWalls.Length AndAlso selectionStartCell.Y + y < horizontalWalls(0).Length Then
                    horizontalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
                    horizontalWallItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = Nothing
                End If
            Next
        Next
        picGame.Invalidate()
    End Sub

    Private Sub ClearSelectedAreaEdge()
        CaptureSelectionData()
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionStartCell.X + x < SQCOUNTX AndAlso selectionStartCell.Y + y < SQCOUNTY Then
                    floorsUseEdgeStyle(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
                End If
            Next
        Next
        picGame.Invalidate()
    End Sub

    Private Sub ClearSelectedAreaWalls()
        CaptureSelectionData()
        For x As Integer = 0 To selectionData.selectionWidth
            For y As Integer = 0 To selectionData.selectionHeight - 1
                If selectionStartCell.X + x < verticalWalls.Length AndAlso selectionStartCell.Y + y < verticalWalls(0).Length Then
                    verticalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
                    verticalWallItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = Nothing
                End If
            Next
        Next
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight
                If selectionStartCell.X + x < horizontalWalls.Length AndAlso selectionStartCell.Y + y < horizontalWalls(0).Length Then
                    horizontalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
                    horizontalWallItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = Nothing
                End If
            Next
        Next
        picGame.Invalidate()
    End Sub

    Private Sub ClearSelectedAreaItems(ParamArray itemKeys As String())
        CaptureSelectionData()
        For x As Integer = 0 To selectionData.selectionWidth - 1
            Dim localX As Integer = x
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim localY As Integer = y
                If selectionStartCell.X + localX < placedItems.Length AndAlso selectionStartCell.Y + localY < placedItems(0).Length Then
                    If placedItems(selectionStartCell.X + localX)(selectionStartCell.Y + localY) IsNot Nothing AndAlso Array.Exists(itemKeys, Function(key) placedItems(selectionStartCell.X + localX)(selectionStartCell.Y + localY).ImageKey = key) Then
                        placedItems(selectionStartCell.X + localX)(selectionStartCell.Y + localY) = Nothing
                    End If
                End If
            Next
        Next
        picGame.Invalidate()
    End Sub

#End Region

End Class

Can you look at my undo code. It seems to undo multiple things at once. For example. when I drag something from the toolbar (picBlock_MouseDown) onto picGame a few times, undo will does the lot in 1 undo. Instead of 1 at a time. 

I believe the problem is in picGame_Mouseup. Once we let go of leftclick SaveStateForUndo shuold be called.