Imports System.Drawing.Drawing2D
Imports System.IO
Imports System.Text.Json
Public Class FrmDesigner
    Inherits Form

#Region " Variables "
    Public SnapToFloor As Boolean
    Public Property HoveredTrigger As Triggers = Nothing
    Private regions As New List(Of RegionInfo)
    Public Property TriggerData As New List(Of Triggers)
    Private triggerImage2 As Image
    Private triggerImage As Image
    Private triggerImageSmall As Image
    Private triggerImage2Small As Image
    Private IsOnGate As Boolean = False
    Private IsOnBaddy As Boolean = False
    Private xOrigin As Integer = INDENT
    Private yOrigin As Integer = INDENT

    Private selectedTriggerSquare As New Point(-1, -1)
    Private selectedActionSquare As New Point(-1, -1)
    Private selectedItemToRemove As String = ""

    Private invalidateTimer As Timer
    Private draggedSelectionCellX As Integer
    Private draggedSelectionCellY As Integer

    Private Const MaxRecentFiles As Integer = 10
    Private recentFiles As New List(Of String)()
    Private recentFilesFilePath As String = Path.Combine(GetProjectDirectory(), "recent_files.txt")

    Private lastMouseMoveTime As Date = Date.MinValue
    Private Const DEBOUNCE_INTERVAL As Integer = 50 ' milliseconds
    Public Property SelectionMode As Boolean = False
    Public Property SelectionRequestedByForm As Form = Nothing
    Public Property SelectionRequestedFor As String = ""

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

    Friend wallsHatchStyle As HatchStyle = HatchStyle.LargeConfetti
    Friend wallsColor1 As Color = Color.FromArgb(255, 0, 0, 0)
    Friend wallsColor2 As Color = Color.FromArgb(255, 64, 0, 0)
    Friend floorsHatchStyle As HatchStyle = HatchStyle.Divot
    Friend floorsColor1 As Color = Color.FromArgb(255, 64, 0, 0)
    Friend floorsColor2 As Color = Color.FromArgb(255, 0, 64, 0)

    Friend floorsUseEdgeStyle()() As Boolean 'track edge style usage

    Private SQSizeX As Integer
    Private SQSizeY As Integer

    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 RoomData)
    Private redoStack As New Stack(Of RoomData)

    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 ReadOnly tempFilePath As String = Path.Combine(Path.GetTempPath(), "temp.room")
    Private ReadOnly ImagesFolder As String = Path.Combine(projectDir, "Data", "Images")

    Private currentFilePath As String = ""
    Friend SelectedMusic As String = ""

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

    Friend placedItems()() As ItemInfo
    Friend verticalWallItems()() As ItemInfo
    Friend horizontalWallItems()() As ItemInfo

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

    Private isDraggingItem As Boolean = False
    Private draggedItem As ItemInfo = 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 Date
    Private Const CLICK_DRAG_THRESHOLD As Integer = 5 ' pixels
    Private Const CLICK_DRAG_THRESHOLD_SQUARED As Integer = CLICK_DRAG_THRESHOLD * CLICK_DRAG_THRESHOLD

    Public useGradient As Boolean = False
    Private itemsDictionary As Dictionary(Of String, List(Of ItemInfo))

    Private regionOverlayColors As New Dictionary(Of String, Color) From {
    {"Region1", Color.FromArgb(128, 255, 255, 0)},   ' Yellow
    {"Region2", Color.FromArgb(128, 255, 165, 0)},   ' Orange
    {"Region3", Color.FromArgb(128, 173, 255, 47)},  ' Light Green
    {"Region4", Color.FromArgb(128, 173, 216, 230)}, ' Light Blue
    {"Region5", Color.FromArgb(128, 211, 211, 211)}  ' Light Gray
}

#End Region

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

        ' Load the Trigger.png image
        triggerImage = Image.FromFile(Path.Combine(ImagesFolder, "Trigger.png"))

        ' Load the Trigger2.png image
        triggerImage2 = Image.FromFile(Path.Combine(ImagesFolder, "Trigger2.png"))

        ' Create a smaller version of the image (70% smaller)
        Dim originalWidth As Integer = triggerImage2.Width
        Dim originalHeight As Integer = triggerImage2.Height
        Dim newWidth As Integer = originalWidth * 0.3
        Dim newHeight As Integer = originalHeight * 0.3
        triggerImage2Small = New Bitmap(triggerImage2, New Size(newWidth, newHeight))

        ' Create a smaller version of the Trigger.png image (80% smaller)
        Dim originalWidthTrigger As Integer = triggerImage.Width
        Dim originalHeightTrigger As Integer = triggerImage.Height
        Dim newWidthTrigger As Integer = originalWidthTrigger * 1
        Dim newHeightTrigger As Integer = originalHeightTrigger * 1
        triggerImageSmall = New Bitmap(triggerImage, New Size(newWidthTrigger, newHeightTrigger))
    End Sub

    Private Sub FrmDesigner_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        RemoveHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
        RemoveHandler cboY.SelectedIndexChanged, AddressOf CboX_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 CboX_SelectedIndexChanged

        ' Ensure one of the radio buttons is checked
        rdoWalls.Checked = True

        ' Initialize the color pickers with the current colors
        colPattern.SelectedColor = wallsColor1
        colBackground.SelectedColor = wallsColor2

        LoadMusic()
        LoadPremades()
        LoadRecentFiles()

        UpdateSelections()

        ' Load the state from the temporary file
        If File.Exists(tempFilePath) Then
            Try
                LoadRoomFromFile(tempFilePath)
                UpdateGridSizeUI()
                UpdateSelections()
            Catch ex As Exception
                Dim unused = MessageBox.Show("Error loading temporary file: " & ex.Message)
            End Try
        End If

        ' Reset currentFilePath to treat it as a new file
        currentFilePath = ""

        ' Reset the window title
        Text = "New Room"

        ' Initialize trigger colors
        Dim rand As New Random()
        picGame.Image = Nothing
    End Sub
    Friend Sub InitializeWalls()
        verticalWalls = New Boolean(SQCOUNTX)() {}
        verticalWallItems = New ItemInfo(SQCOUNTX)() {}
        For x As Integer = 0 To SQCOUNTX
            verticalWalls(x) = New Boolean(SQCOUNTY - 1) {}
            verticalWallItems(x) = New ItemInfo(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 ItemInfo(SQCOUNTX - 1)() {}
        For x As Integer = 0 To SQCOUNTX - 1
            horizontalWalls(x) = New Boolean(SQCOUNTY) {}
            horizontalWallItems(x) = New ItemInfo(SQCOUNTY) {}
            For y As Integer = 0 To SQCOUNTY
                horizontalWalls(x)(y) = True
                horizontalWallItems(x)(y) = Nothing
            Next
        Next

        placedItems = New ItemInfo(SQCOUNTX - 1)() {}
        For x As Integer = 0 To SQCOUNTX - 1
            placedItems(x) = New ItemInfo(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 = 250}
        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()

        invalidateTimer = New Timer() With {.Interval = 30} ' Update every 50 milliseconds (20 times per second)
        AddHandler invalidateTimer.Tick, AddressOf InvalidateTimer_Tick
        invalidateTimer.Start()
    End Sub

    Private Sub InitializeEventHandlers()
        picGame.AllowDrop = True

        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 tabWalls.Controls
            If TypeOf ctl Is PictureBox Then
                AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
                AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
            End If
        Next
    End Sub

    Private Sub InitializeAnimationForImages()
        animatedImages.Add(picBaddy1.Image)
        animatedImages.Add(picBaddy2.Image)
        animatedImages.Add(picBaddy3.Image)
        animatedImages.Add(picBaddy4.Image)
        animatedImages.Add(picLaser1.Image)
        animatedImages.Add(picLaser2.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)
        animatedImages.Add(picWarp3.Image)
        animatedImages.Add(picVolcano.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 LoadPremades()
        If Not Directory.Exists(premadesFolder) Then Dim unused1 = Directory.CreateDirectory(premadesFolder)
        lstPremades.Items.Clear()
        For Each file As String In Directory.GetFiles(premadesFolder, "*.Premade")
            Dim unused = lstPremades.Items.Add(Path.GetFileNameWithoutExtension(file))
        Next
    End Sub

    Private Sub LoadMusic()
        If Not Directory.Exists(musicFolder) Then
            Dim unused = 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
    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.Gray, Color.White, Color.Gray)
    End Sub
    Private Sub InvalidateTimer_Tick(sender As Object, e As EventArgs)
        picGame.Invalidate()
    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 Function GetItemName(imageKey As String) As String
        Select Case imageKey
            Case "picBlock1"
                Return "Block"
            Case "picBlock2"
                Return "Block2"
            Case "picBaddy1"
                Return "Baddy1"
            Case "picBaddy2"
                Return "Baddy2"
            Case "picLaser1"
                Return "Laser1"
            Case "picLaser2"
                Return "Laser2"
            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 "picWarp3"
                Return "Warp3"
            Case "picVolcano"
                Return "Volcano"
            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

            End If
        End If
    End Sub

    Public Sub UpdateGridSizeUI()
        RemoveHandler cboX.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged
        RemoveHandler cboY.SelectedIndexChanged, AddressOf CboX_SelectedIndexChanged

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

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

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

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

    End Sub
    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

#End Region

#Region " File "
    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)

            ' Update the window title to reflect the saved file path
            Text = filePath
        Catch ex As Exception
            Dim unused = 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
                Dim unused1 = MessageBox.Show("Error: Failed to deserialize room data.")
                Return
            End If

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

            UpdateSelections()

            cboStyleChoice.SelectedIndex = If(roomData.useGradient, cboStyleChoice.Items.IndexOf("Gradient"), cboStyleChoice.Items.IndexOf("HatchStyle"))

            ' Update the window title to reflect the loaded file path
            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 volumePart As String = selectedMusicParts(1).Trim()
                Dim gainPart As String = selectedMusicParts(2).Trim()

                Dim volume As Integer
                Dim gain As Integer

                If Not Integer.TryParse(volumePart, volume) Then
                    Dim unused2 = MessageBox.Show("Error: Invalid volume value in selected music.")
                    Return
                End If

                If Not Integer.TryParse(gainPart, gain) Then
                    Dim unused3 = MessageBox.Show("Error: Invalid gain value in selected music.")
                    Return
                End If

                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
            Dim unused = MessageBox.Show("Error loading room: " & ex.Message)
        End Try
    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 = ""

        ' Reset the window title
        Text = "New Room"

        UpdateSelections()
    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()
        End If
    End Sub


    Private Sub UpdateRecentFiles(filePath As String)
        ' Remove the file if it already exists in the recent files list to avoid duplicates
        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()
        ' Clear 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

        ' Add separator below the New menu item if items exist
        Dim newMenuItem As ToolStripMenuItem = mnu.DropDownItems.OfType(Of ToolStripMenuItem)().FirstOrDefault(Function(m) m.Text = "New")
        If newMenuItem IsNot Nothing Then
            mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(newMenuItem) + 1, New ToolStripSeparator())
        End If

        ' Add separator above Load menu item if it exists
        Dim loadMenuItem As ToolStripMenuItem = mnu.DropDownItems.OfType(Of ToolStripMenuItem)().FirstOrDefault(Function(m) m.Text = "Load")
        If loadMenuItem IsNot Nothing Then
            mnu.DropDownItems.Insert(mnu.DropDownItems.IndexOf(loadMenuItem), New ToolStripSeparator())
        End If

        ' Populate the recent files list in the menu, ensuring the file exists
        For Each tfile As String In recentFiles
            If File.Exists(tfile) Then
                Dim menuItem As New ToolStripMenuItem(tfile) With {
                .Tag = "RecentFile" ' Tag the item to identify it as a recent file item
            }
                AddHandler menuItem.Click, AddressOf RecentFileMenuItem_Click ' Attach event handler

                ' Insert the menu item at the end (before current last separator)
                mnu.DropDownItems.Insert(mnu.DropDownItems.Count - 1, menuItem)
            End If
        Next

        ' Ensure the Exit menu item is added only once. Handling similar to previous instructions.
        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
            mnu.DropDownItems.Insert(mnu.DropDownItems.Count, exitMenuItem)
        End If

        ' Hide separators if there are no recent files
        Dim hasRecentFiles As Boolean = recentFiles.Any(Function(f) File.Exists(f))
        For Each item As ToolStripItem In mnu.DropDownItems
            If TypeOf item Is ToolStripSeparator Then
                item.Visible = hasRecentFiles
            End If
        Next
    End Sub
    Private Sub ExitMenuItem_Click(sender As Object, e As EventArgs)
        Close()
    End Sub

    Private Sub SaveRecentFilesToFile()
        Try
            Dim lines As New List(Of String)(recentFiles)
            File.WriteAllLines(recentFilesFilePath, lines)
        Catch ex As Exception
            Dim unused = 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
                Dim unused = 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 current file path
        currentFilePath = filePath

        ' Update the recent files list with the selected file
        UpdateRecentFiles(filePath)
    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
            Dim unused = 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 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
            Dim unused = MessageBox.Show("Error saving settings: " & ex.Message)
        End Try
    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 Function ColorToString(color As Color) As String
        Return $"{color.A},{color.R},{color.G},{color.B}"
    End Function

#End Region

#Region " Design "
    Private Sub LstPremades_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstPremades.SelectedIndexChanged
        TurnOffSelectionMode()
        If lstPremades.SelectedIndex >= 0 Then
            If Not isRestoringState Then SaveStateForUndo()
            Dim selectedPremade As String = lstPremades.SelectedItem.ToString()
            LoadSettingsFromFile(Path.Combine(premadesFolder, selectedPremade & ".Premade"))
            UpdateSelections()

        End If
    End Sub

    Private Sub BtnSavePremade_Click(sender As Object, e As EventArgs) Handles btnSavePremade.Click
        TurnOffSelectionMode()
        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
        TurnOffSelectionMode()
        If lstHatchstyles.SelectedItem IsNot Nothing Then
            Dim selectedHatchStyleName As String = lstHatchstyles.SelectedItem.ToString()
            Dim selectedHatchStyle As HatchStyle = [Enum].Parse(GetType(HatchStyle), selectedHatchStyleName)
            If rdoFloors.Checked Then
                floorsHatchStyle = selectedHatchStyle
            ElseIf rdoWalls.Checked Then
                wallsHatchStyle = selectedHatchStyle
            End If
            If Not isRestoringState Then SaveStateForUndo()

        End If
    End Sub
    Private Sub btnRandom_Click(sender As Object, e As EventArgs) Handles btnRandom.Click
        If lstHatchstyles.Items.Count > 0 Then
            Dim random As New Random()
            Dim randomIndex As Integer = random.Next(lstHatchstyles.Items.Count)
            lstHatchstyles.SelectedIndex = randomIndex
        End If
        If Not isRestoringState Then SaveStateForUndo()
    End Sub

    Private Sub BtnAll_Click(sender As Object, e As EventArgs) Handles BtnAll.Click
        TurnOffSelectionMode()
        If Not isRestoringState Then SaveStateForUndo()
        RandomizeWalls()
        RandomizeFloors()
    End Sub

    Private Sub BtnWalls_Click(sender As Object, e As EventArgs) Handles BtnWall.Click
        TurnOffSelectionMode()
        If Not isRestoringState Then SaveStateForUndo()
        RandomizeWalls()
    End Sub

    Private Sub BtnFloors_Click(sender As Object, e As EventArgs) Handles BtnFloor.Click
        TurnOffSelectionMode()
        If Not isRestoringState Then SaveStateForUndo()
        RandomizeFloors()

    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
        lstHatchstyles.Enabled = Not rdoFloors.Checked OrElse Not useGradient

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

        ' Update the color pickers to reflect the current colors
        UpdateColorPickers()
    End Sub
    Private Sub RdoWalls_CheckedChanged(sender As Object, e As EventArgs) Handles rdoWalls.CheckedChanged
        TurnOffSelectionMode()
        If rdoWalls.Checked Then
            UpdateSelections()
        End If
    End Sub

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

    Private Sub RdoEdge_CheckedChanged(sender As Object, e As EventArgs) Handles rdoEdge.CheckedChanged
        TurnOffSelectionMode()
        If rdoEdge.Checked Then
            UpdateSelections()
        End If
    End Sub
    Private Sub CboStyleChoice_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboStyleChoice.SelectedIndexChanged
        TurnOffSelectionMode()
        useGradient = cboStyleChoice.SelectedItem.ToString() = "Gradient"
        lstHatchstyles.Enabled = Not useGradient
        ' Redraw the picGame control
    End Sub

    Private Sub CboX_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboX.SelectedIndexChanged, cboY.SelectedIndexChanged
        TurnOffSelectionMode()
        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)

            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
                newArray(x)(y) = If(x < sourceArray.Length AndAlso y < sourceArray(x).Length,
                    sourceArray(x)(y),
                    If(GetType(T) Is GetType(Boolean) AndAlso initializeValue, CType(Convert.ChangeType(True, GetType(T)), T), Nothing))
            Next
        Next
        Return newArray
    End Function

#End Region

#Region " Mouse Up, Down, Move "
    Private Sub PicGame_MouseDown(sender As Object, e As MouseEventArgs) Handles picGame.MouseDown
        If SelectionMode Then
            HandleSelectionMode(e)
            Return
        End If

        If e.Button = MouseButtons.Left Then
            ' Handle actions when the left mouse button is clicked
            HandleGateClicked(e.X, e.Y)
            HandleBaddyClicked(e.X, e.Y) ' Ensure this line is added

            ' Start the selection process
            isSelecting = True
            selectionStartPoint = e.Location
            selectionEndPoint = e.Location
            selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)

            ' Reset selection data
            selectionData = Nothing
            selectionStartCell = New Point(-1, -1)
            copiedSelectionData = Nothing
            copiedSelectionType = ""

            ' Check if selection starts from a wall or the space between walls
            Dim offsetX As Integer = (selectionStartPoint.X - INDENT) Mod (SQSizeX + WALL_THICKNESS)
            Dim offsetY As Integer = (selectionStartPoint.Y - INDENT) Mod (SQSizeY + WALL_THICKNESS)
            selectionStartedOnWall = (offsetX < WALL_THICKNESS) OrElse (offsetY < WALL_THICKNESS)

            ' Adjust the selection start point to snap to the grid
            selectionStartPoint = GetGridAlignedPoint(selectionStartPoint)

            ' Determine if the user starts the selection on a floor or a wall
            Dim clickedX As Integer = (e.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
            Dim clickedY As Integer = (e.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)

            If clickedX >= 0 AndAlso clickedX < SQCOUNTX AndAlso clickedY >= 0 AndAlso clickedY < SQCOUNTY Then
                If Not IsMouseOverWall(e.X, e.Y) Then
                    ' The starting selection is on the floor
                    SnapToFloor = True
                Else
                    ' Starting on a wall
                    SnapToFloor = False
                End If
            End If
        ElseIf e.Button = MouseButtons.Right Then
            ' Hide properties displays
            UcBaddyProps.Visible = False
            krKeys.Visible = False
        End If

        If isSelectionReadyToDrag Then
            If selectionRectangle.Contains(e.Location) Then
                If e.Button = MouseButtons.Left Then
                    SaveStateForUndo()
                    isDraggingSelection = True
                    selectionOffset = New Point(e.X - selectionRectangle.Location.X, e.Y - selectionRectangle.Location.Y)
                    selectionStartCell = New Point(
                (selectionRectangle.Location.X - INDENT) \ (SQSizeX + WALL_THICKNESS),
                (selectionRectangle.Location.Y - INDENT) \ (SQSizeY + WALL_THICKNESS))
                    picGame.Capture = True
                    Cursor = Cursors.SizeAll
                ElseIf e.Button = MouseButtons.Right Then
                    ' Show context menu if right-clicking inside the selection
                    Dim menuPosition As Point = e.Location
                    ctmMenu.Show(picGame, menuPosition)
                End If
            End If
        ElseIf isInSelectionMode Then
            ' Start selecting a new area
            If e.Button = MouseButtons.Left Then
                isSelecting = True
                selectionStartPoint = e.Location
                selectionEndPoint = e.Location
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)

                ' Reset selection data
                selectionData = Nothing
                selectionStartCell = New Point(-1, -1)
                copiedSelectionData = Nothing
                copiedSelectionType = ""

                ' Reset the cycle
                isSelectionExpandedToWalls = False
                isSelectionExpandedToFloors = False

                ' Adjust the selection start point to snap to the grid
                selectionStartPoint = GetGridAlignedPoint(selectionStartPoint)
            ElseIf e.Button = MouseButtons.Right Then
                If selectionRectangle.Contains(e.Location) Then
                    Dim menuPosition As Point = e.Location
                    ctmMenu.Show(picGame, menuPosition)
                End If
            Else
                TurnOffSelectionMode()
            End If
        Else
            ' Normal mouse down processing
            If e.Button = MouseButtons.Right Then
                isRightMouseDown = True
                rightMouseDownPosition = e.Location
                rightMouseDownTime = Date.Now
                If Not isActionInProgress Then
                    isActionInProgress = True
                    SaveStateForUndo()
                End If
            ElseIf e.Button = MouseButtons.Left Then
                isLeftMouseDown = True
                UpdateHighlight(e.X, e.Y)
                Dim itemPickedUp As Boolean = False

                If isMouseInsideGrid Then
                    If Not isActionInProgress Then
                        isActionInProgress = True
                        SaveStateForUndo()
                    End If

                    If highlightedWallX Then
                        ' Handle vertical wall item pickup
                        If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length 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
                        ' Handle horizontal wall item pickup
                        If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length 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
                        ' Handle floor item pickup
                        If highlightedX >= 0 AndAlso highlightedX < placedItems.Length 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 itemPickedUp Then
                        redoStack.Clear()
                    Else
                        ' Add wall or edge style handling
                        If rdoEdge.Checked Then
                            isLeftMouseDown = True
                            SetEdgeStyleAtMousePosition(e.X, e.Y)
                        Else
                            isLeftMouseDown = True
                            AddWallAtMousePosition(e.X, e.Y)
                        End If
                    End If
                End If
            End If
        End If

        ' Check if the krKeys control is visible and the click is outside the cell with the gate
        If krKeys.Visible And IsOnGate = False Then
            Dim clickedX As Integer = (e.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
            Dim clickedY As Integer = (e.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)

            If clickedX >= 0 AndAlso clickedX < placedItems.Length AndAlso
       clickedY >= 0 AndAlso clickedY < placedItems(0).Length Then
                Dim clickedItem As ItemInfo = placedItems(clickedX)(clickedY)
                If clickedItem Is Nothing OrElse clickedItem.ImageKey <> "picGate" Then
                    krKeys.Visible = False
                End If
            Else
                krKeys.Visible = False
            End If
        End If

        ' Check if the UcBaddyProps control is visible and the click is outside the cell with the Baddy
        If UcBaddyProps.Visible AndAlso Not IsOnBaddy Then
            Dim clickedX As Integer = (e.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
            Dim clickedY As Integer = (e.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)

            If clickedX >= 0 AndAlso clickedX < placedItems.Length AndAlso
       clickedY >= 0 AndAlso clickedY < placedItems(0).Length Then
                Dim clickedItem As ItemInfo = placedItems(clickedX)(clickedY)
                If Not clickedItem.ImageKey.StartsWith("picBaddy") Then
                    UcBaddyProps.Visible = False
                End If
            Else
                UcBaddyProps.Visible = False
            End If
        End If
    End Sub
    Private Sub PicGame_MouseMove(sender As Object, e As MouseEventArgs) Handles picGame.MouseMove
        isCursorOnPicGame = True

        If isDraggingSelection Then
            ' Existing code for dragging selection
            If isCopying Then
                If copiedSelectionData Is Nothing Then
                    Return
                End If
            Else
                If selectionData Is Nothing Then
                    CaptureSelectionData()
                End If
            End If

            ' Calculate new cell positions
            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)
            Dim cellY As Integer = (newY - INDENT) \ (SQSizeY + WALL_THICKNESS)

            ' Ensure cellX and cellY are within bounds
            If isCopying Then
                cellX = Math.Max(0, Math.Min(SQCOUNTX - copiedSelectionData.selectionWidth, cellX))
                cellY = Math.Max(0, Math.Min(SQCOUNTY - copiedSelectionData.selectionHeight, cellY))
                selectionRectangle.Width = (copiedSelectionData.selectionWidth * (SQSizeX + WALL_THICKNESS)) - WALL_THICKNESS
                selectionRectangle.Height = (copiedSelectionData.selectionHeight * (SQSizeY + WALL_THICKNESS)) - WALL_THICKNESS
            Else
                cellX = Math.Max(0, Math.Min(SQCOUNTX - selectionData.selectionWidth, cellX))
                cellY = Math.Max(0, Math.Min(SQCOUNTY - selectionData.selectionHeight, cellY))
                selectionRectangle.Width = (selectionData.selectionWidth * (SQSizeX + WALL_THICKNESS)) - WALL_THICKNESS
                selectionRectangle.Height = (selectionData.selectionHeight * (SQSizeY + WALL_THICKNESS)) - WALL_THICKNESS
            End If

            ' Update selection rectangle position
            selectionRectangle.X = INDENT + (cellX * (SQSizeX + WALL_THICKNESS))
            selectionRectangle.Y = INDENT + (cellY * (SQSizeY + WALL_THICKNESS))

            ' Store cell positions for use in MouseUp
            draggedSelectionCellX = cellX
            draggedSelectionCellY = cellY

        ElseIf 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) + 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

        ElseIf isInSelectionMode Then
            If isSelecting Then
                Dim newEndPoint = e.Location

                ' Adjust the snap behavior based on where we started the selection
                If SnapToFloor Then
                    selectionStartPoint = GetGridAlignedPoint(selectionStartPoint)
                    newEndPoint = GetGridAlignedPoint(newEndPoint, isFloor:=True)
                Else
                    selectionStartPoint = GetGridAlignedPoint(selectionStartPoint, isWall:=True)
                    newEndPoint = GetGridAlignedPoint(newEndPoint, isWall:=True)
                End If

                selectionRectangle = GetRectangle(selectionStartPoint, newEndPoint)
            End If
        Else
            UpdateHighlight(e.X, e.Y)

            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

        UpdateStatusBar(e.X, e.Y)
    End Sub
    Private Sub PicGame_MouseUp(sender As Object, e As MouseEventArgs) Handles picGame.MouseUp
        If isDraggingSelection Then
            ' Completing selection dragging
            If e.Button = MouseButtons.Left Then
                ' Save the state before modifying the data model
                If Not isRestoringState Then
                    SaveStateForUndo()
                End If

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

                If selectionRectangle.IsEmpty Then
                    Return
                End If

                ' When the mouse is released, we don’t change the selection rectangle. Use the stored
                ' selection data and the adjusted rectangle from when it was being drawn.
                ' Use the captured selection data to snap to the grid.
                Dim startCell As Point = New Point(
            (selectionRectangle.X - INDENT) \ (SQSizeX + WALL_THICKNESS),
            (selectionRectangle.Y - INDENT) \ (SQSizeY + WALL_THICKNESS))

                PasteSelectionDataTo(startCell, False, copiedSelectionType = "Edge")

                isCopying = False
                isSelectionReadyToDrag = False

                ' Reset selection-related variables to prevent issues with future operations
                selectionData = Nothing
                selectionStartCell = New Point(-1, -1)
                copiedSelectionData = Nothing
                copiedSelectionType = ""
                isCopying = False
                isSelectionReadyToDrag = False
                isDraggingSelection = False

                ' Clear the redo stack since we've made a new change
                redoStack.Clear()
            End If
        ElseIf isInSelectionMode AndAlso isSelecting Then
            If e.Button = MouseButtons.Left Then
                ' Finalizing the selection rectangle
                isSelecting = False
                selectionEndPoint = e.Location

                ' Finalize selection snapping based on the type of click
                If SnapToFloor Then
                    selectionEndPoint = GetGridAlignedPoint(selectionEndPoint, isFloor:=True)
                Else
                    selectionEndPoint = GetGridAlignedPoint(selectionEndPoint, isWall:=True)
                End If

                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)

                ' Check if the selection needs to be adjusted
                selectionRectangle = AdjustSelectionRectangleToWalls(selectionRectangle)

                ' Reset selection-related variables
                selectionData = Nothing
                selectionStartCell = New Point(-1, -1)
                copiedSelectionData = Nothing
                copiedSelectionType = ""
                isCopying = False
                isSelectionReadyToDrag = False
                isDraggingSelection = False

                If selectionRectangle.IsEmpty Then
                    TurnOffSelectionMode()
                End If
            End If
        Else
            ' Normal mouse up processing
            If e.Button = MouseButtons.Left Then
                If isDraggingItem Then
                    ' Save the state before modifying the data model
                    If Not isRestoringState Then
                        SaveStateForUndo()
                    End If

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

                    ' Snap the item to the grid position
                    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
                   highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then

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

                    ' Clear redo stack since we've made a new change
                    redoStack.Clear()

                    ' Invalidate the picGame control to refresh the display

                End If

                isLeftMouseDown = False
                isActionInProgress = False

            ElseIf e.Button = MouseButtons.Right Then
                If isRightMouseDown Then
                    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

                                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

                    isActionInProgress = False
                End If

                ' Check if the right-click is outside the selected area
                If isInSelectionMode AndAlso Not selectionRectangle.Contains(e.Location) Then
                    TurnOffSelectionMode()
                End If
            End If
        End If
    End Sub

    Private Sub Form_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
        Dim screenPos As Point = 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

            ' Invalidate only the picGame control
        End If

        ' Force the highlight color to toggle based on mouse movement
        Dim currentTime As Date = Date.Now
        If (currentTime - lastMouseMoveTime).TotalMilliseconds >= DEBOUNCE_INTERVAL Then
            highlightColor = If(highlightColor = Color.Black, Color.White, Color.Black)
            lastMouseMoveTime = currentTime
        End If

        UpdateStatusBar(e.X, e.Y)
    End Sub

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

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

            Dim screenPos As Point = 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)) OrElse IsFlipperItem(draggedItem.ImageKey) 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)) OrElse IsFlipperItem(draggedItem.ImageKey) Then
                                    horizontalWallItems(highlightedX)(highlightedY) = draggedItem
                                    horizontalWalls(highlightedX)(highlightedY) = False
                                Else
                                    ReturnItemToOriginalLocation()
                                End If
                            End If
                        End If
                    Else
                        ' Existing code for placing non-wall items
                        If highlightedX >= 0 AndAlso highlightedX < placedItems.Length 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

            redoStack.Clear()
        End If
    End Sub

    Private Sub PicBlock_MouseDown(sender As Object, e As MouseEventArgs) Handles picBlock1.MouseDown, picBlock2.MouseDown, picBaddy1.MouseDown, picBaddy2.MouseDown, picLaser1.MouseDown, picLaser2.MouseDown, picFlipper1.MouseDown, picFlipper2.MouseDown, picFlipper3.MouseDown, picFlipper4.MouseDown, picBaddy3.MouseDown, picBaddy4.MouseDown, picCoin.MouseDown, picGate.MouseDown, picKey.MouseDown, picStart.MouseDown, picFinish.MouseDown, picBlockStopper1.MouseDown, picBall.MouseDown, picBlockSwitch.MouseDown, picBomb.MouseDown, picWarp.MouseDown, picWarp2.MouseDown, picWarp3.MouseDown, picVolcano.MouseDown
        TurnOffSelectionMode
        Dim pic = CType(sender, PictureBox)

        isDraggingItem = True

        ' Create a new PlacedItem with the ImageKey
        draggedItem = New ItemInfo(pic.Name, New Point(0, 0), Guid.NewGuid.ToString, isDoodad:=IsDoodadItem(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
        Capture = True

        Cursor.Current = Cursors.Hand

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

#Region " Mouse Helpers "
    Private Function GetGridAlignedPoint(point As Point, Optional isFloor As Boolean = False, Optional isWall As Boolean = False) As Point
        Dim cellX As Integer = (point.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
        Dim cellY As Integer = (point.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)

        ' Ensure we align correctly based on the type of snapping
        If isFloor Then
            cellX = Math.Max(0, Math.Min(SQCOUNTX - 1, cellX))
            cellY = Math.Max(0, Math.Min(SQCOUNTY - 1, cellY))
        ElseIf isWall Then
            ' To get wall or wall intersection points, adjust this computation
            cellX = Math.Max(0, Math.Min(SQCOUNTX, cellX)) ' Adjust bounds for wall snapping
            cellY = Math.Max(0, Math.Min(SQCOUNTY, cellY)) ' Adjust bounds for wall snapping
        End If

        Dim alignedX As Integer = INDENT + (cellX * (SQSizeX + WALL_THICKNESS))
        Dim alignedY As Integer = INDENT + (cellY * (SQSizeY + WALL_THICKNESS))

        Return New Point(alignedX, alignedY)
    End Function

    Private Function IsMouseOverWall(mouseX As Integer, mouseY As Integer) As Boolean
        ' You can iterate over the vertical and horizontal walls to determine if the mouse is over a wall
        UpdateHighlight(mouseX, mouseY)
        Return highlightedWallX OrElse highlightedWallY
    End Function

    Private Function AdjustSelectionRectangleToWalls(rect As Rectangle) As Rectangle
        ' Determine starting and ending points for cell snapping
        Dim startCellX As Integer = (rect.X - INDENT + (WALL_THICKNESS \ 2)) \ (SQSizeX + WALL_THICKNESS)
        Dim startCellY As Integer = (rect.Y - INDENT + (WALL_THICKNESS \ 2)) \ (SQSizeY + WALL_THICKNESS)
        Dim endCellX As Integer = (rect.Right - INDENT - (WALL_THICKNESS \ 2) - 1) \ (SQSizeX + WALL_THICKNESS)
        Dim endCellY As Integer = (rect.Bottom - INDENT - (WALL_THICKNESS \ 2) - 1) \ (SQSizeY + WALL_THICKNESS)

        ' Make sure to stay within grid bounds
        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))

        ' Adjust rectangle to snap to walls' positions
        If highlightedWallX OrElse highlightedWallY Then
            ' Adjust snap position for walls
            Dim adjustedRect As New Rectangle(
            INDENT + (startCellX * (SQSizeX + WALL_THICKNESS)) + (WALL_THICKNESS \ 2),
            INDENT + (startCellY * (SQSizeY + WALL_THICKNESS)) + (WALL_THICKNESS \ 2),
            ((endCellX - startCellX + 1) * (SQSizeX + WALL_THICKNESS)) - WALL_THICKNESS,
            ((endCellY - startCellY + 1) * (SQSizeY + WALL_THICKNESS)) - WALL_THICKNESS
        )
            Return adjustedRect
        Else
            ' Otherwise, follow the standard floor area snapping.
            Dim adjustedRect As New Rectangle(
            INDENT + (startCellX * (SQSizeX + WALL_THICKNESS)) + (WALL_THICKNESS \ 2),
            INDENT + (startCellY * (SQSizeY + WALL_THICKNESS)) + (WALL_THICKNESS \ 2),
            (endCellX - startCellX + 1) * (SQSizeX + WALL_THICKNESS) - WALL_THICKNESS,
            (endCellY - startCellY + 1) * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS
        )
            Return adjustedRect
        End If
    End Function
    Private Sub HandleBaddyClicked(mouseX As Integer, mouseY As Integer)
        ' Calculate the cell coordinates based on the mouse position
        Dim clickedX As Integer = (mouseX - INDENT) \ (SQSizeX + WALL_THICKNESS)
        Dim clickedY As Integer = (mouseY - INDENT) \ (SQSizeY + WALL_THICKNESS)

        ' Check if the clicked cell contains a Baddy
        If clickedX >= 0 AndAlso clickedX < placedItems.Length AndAlso clickedY >= 0 AndAlso clickedY < placedItems(0).Length Then
            Dim clickedItem As ItemInfo = placedItems(clickedX)(clickedY)
            If clickedItem IsNot Nothing AndAlso clickedItem.ImageKey.StartsWith("picBaddy") Then
                IsOnBaddy = True

                ' Calculate the top-right corner of the clicked cell
                Dim cellTopRightX As Integer = INDENT + (clickedX * (SQSizeX + WALL_THICKNESS)) + SQSizeX + 9
                Dim cellTopRightY As Integer = INDENT + (clickedY * (SQSizeY + WALL_THICKNESS)) + (WALL_THICKNESS * 2) + 38

                ' Set the position and make the control visible
                UcBaddyProps.Location = New Point(cellTopRightX, cellTopRightY)
                UcBaddyProps.Visible = True

                ' Set properties of UcBaddyProps based on the clicked Baddy item
                UcBaddyProps.ItemInfo = clickedItem

                UcBaddyProps.BringToFront() ' Bring the control to the front
            Else
                ' Hide the control if the clicked item is not a Baddy
                UcBaddyProps.Visible = False
            End If
        End If
    End Sub

    Private Sub HandleGateClicked(mouseX As Integer, mouseY As Integer)
        ' Calculate the cell coordinates based on the mouse position
        Dim clickedX As Integer = (mouseX - INDENT) \ (SQSizeX + WALL_THICKNESS)
        Dim clickedY As Integer = (mouseY - INDENT) \ (SQSizeY + WALL_THICKNESS)

        ' Check if the clicked cell contains a gate
        If clickedX >= 0 AndAlso clickedX < placedItems.Length AndAlso
   clickedY >= 0 AndAlso clickedY < placedItems(0).Length Then

            Dim clickedItem As ItemInfo = placedItems(clickedX)(clickedY)
            If clickedItem IsNot Nothing AndAlso clickedItem.ImageKey = "picGate" Then
                IsOnGate = True
                ' Calculate the top-right corner of the clicked cell
                Dim cellTopRightX As Integer = INDENT + (clickedX * (SQSizeX + WALL_THICKNESS)) + SQSizeX + 7
                Dim cellTopRightY As Integer = INDENT + (clickedY * (SQSizeY + WALL_THICKNESS)) + (WALL_THICKNESS * 2) + 38

                ' Set the position and make the control visible
                krKeys.Location = New Point(cellTopRightX, cellTopRightY)
                krKeys.SelectedGate = clickedItem ' Set the SelectedGate property
                krKeys.Grid = picGame ' Set the Grid property
                krKeys.SetNumKeysValue(clickedItem.KeysRequiredToOpen) ' Set the NumKeys value to the gate's value
                krKeys.Visible = True
            Else
                IsOnGate = False
            End If
        End If
    End Sub

    Private Sub HandleSelectionMode(e As MouseEventArgs)
        ' 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)

        ' Determine if the user clicked on a wall or a floor
        Dim isWall As Boolean = highlightedWallX OrElse highlightedWallY
        Dim itemType As String = If(isWall, If(highlightedWallX, "VerticalWall", "HorizontalWall"), "Floor")

        ' Determine the item at the selected point
        Dim item As String
        If isWall Then
            If highlightedWallX Then
                ' Check vertical wall items
                If clickedX >= 0 AndAlso clickedX < verticalWallItems.Length AndAlso
       clickedY >= 0 AndAlso clickedY < verticalWallItems(clickedX).Length Then
                    item = If(verticalWallItems(clickedX)(clickedY) IsNot Nothing, verticalWallItems(clickedX)(clickedY).ImageKey, "Nothing")
                End If
            ElseIf highlightedWallY Then
                ' Check horizontal wall items
                If clickedX >= 0 AndAlso clickedX < horizontalWallItems.Length AndAlso
       clickedY >= 0 AndAlso clickedY < horizontalWallItems(clickedX).Length Then
                    item = If(horizontalWallItems(clickedX)(clickedY) IsNot Nothing, horizontalWallItems(clickedX)(clickedY).ImageKey, "Nothing")
                End If
            End If
        Else
            ' Check floor items
            If clickedX >= 0 AndAlso clickedX < placedItems.Length AndAlso
   clickedY >= 0 AndAlso clickedY < placedItems(clickedX).Length Then
                item = If(placedItems(clickedX)(clickedY) IsNot Nothing, placedItems(clickedX)(clickedY).ImageKey, "Nothing")
            End If
        End If

        ' Check selection constraints
        Dim triggerEditor As frmTriggerEditor = CType(SelectionRequestedByForm, frmTriggerEditor)
        If SelectionRequestedFor = "TriggeringPoint" Then
            If triggerEditor.AllowFloorSelectionOnly AndAlso isWall Then
                Return
            End If
        ElseIf SelectionRequestedFor = "TriggerActionPoint" Then
            ' Allow clicking anything for TriggerActionPoint
        ElseIf SelectionRequestedFor = "TriggerItemToRemove" Then
            If triggerEditor.AllowWallSelectionOnly AndAlso Not isWall Then
                Return
            End If
        End If

        ' Pass the selected point, item, and item type back to the requesting form
        If TypeOf SelectionRequestedByForm Is frmTriggerEditor Then
            If SelectionRequestedFor = "TriggeringPoint" Then
                triggerEditor.SetTriggeringPoint(New Point(clickedX, clickedY))
            ElseIf SelectionRequestedFor = "TriggerActionPoint" Then
                triggerEditor.SetTriggerActionPoint(New Point(clickedX, clickedY), itemType)
            End If
        End If

        TurnOffSelectionMode()

        ' Bring FrmTriggerEditor to the foreground
        If TypeOf SelectionRequestedByForm Is frmTriggerEditor Then
            triggerEditor.BringToFront() ' Bring FrmTriggerEditor to the front
            Dim unused = triggerEditor.Focus() ' Focus on FrmTriggerEditor
        End If
    End Sub

    ' Create a new method to reset the cursor
    Private Sub ResetTheCursor()
        picGame.Cursor = Cursors.Default
        Cursor = Cursors.Default
    End Sub
    ' Call this method in TurnOffSelectionMode
    Private Sub TurnOffSelectionMode()
        isInSelectionMode = False
        selectionRectangle = Rectangle.Empty
        SelectionMode = False
        SelectionRequestedByForm = Nothing
        SelectionRequestedFor = Nothing
        ResetTheCursor()

        ' Reset the cycle
        isSelectionExpandedToWalls = False
        isSelectionExpandedToFloors = False
    End Sub

    Private Function IsDoodadItem(imageKey As String) As Boolean
        Return imageKey = "picBlockSwitch" OrElse imageKey.StartsWith("picDoodad")
    End Function
    Private Sub UpdateStatusBar(pixelX As Integer, pixelY As Integer)
        ' Calculate the square coordinates based on the mouse position
        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)
        Dim triggerInfo As String = ""

        ' Determine if the mouse is over a wall
        Dim isOverWall As Boolean = highlightedWallX OrElse highlightedWallY

        ' Determine the content of the square or wall
        Dim content As String = "Empty"
        Dim uniqueId As String = ""

        If isOverWall Then
            If highlightedWallX Then
                ' Check vertical wall items or walls
                If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
   highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then
                    If verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                        content = GetItemFullName(verticalWallItems(highlightedX)(highlightedY).ImageKey)
                        uniqueId = verticalWallItems(highlightedX)(highlightedY).UniqueId
                    ElseIf verticalWalls(highlightedX)(highlightedY) Then
                        content = "Wall"
                    End If
                End If
            ElseIf highlightedWallY Then
                ' Check horizontal wall items or walls
                If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
   highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then
                    If horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                        content = GetItemFullName(horizontalWallItems(highlightedX)(highlightedY).ImageKey)
                        uniqueId = horizontalWallItems(highlightedX)(highlightedY).UniqueId
                    ElseIf horizontalWalls(highlightedX)(highlightedY) Then
                        content = "Wall"
                    End If
                End If
            End If
        Else
            ' Check floor items
            If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
   highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then
                If placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                    content = GetItemFullName(placedItems(highlightedX)(highlightedY).ImageKey)
                    uniqueId = placedItems(highlightedX)(highlightedY).UniqueId
                ElseIf floorsUseEdgeStyle(highlightedX)(highlightedY) Then
                    content = "Edge"
                End If
            End If
        End If

        ' Update the status bar labels
        stbMouseCoords.Text = $"{pixelX}, {pixelY}"

        If isOverWall Then
            If highlightedWallX Then
                stbGridCoords.Text = $"Vertical Wall: {highlightedX}, {highlightedY}"
            ElseIf highlightedWallY Then
                stbGridCoords.Text = $"Horizontal Wall: {highlightedX}, {highlightedY}"
            End If
        Else
            stbGridCoords.Text = $"Floor: {highlightedX}, {highlightedY}"
        End If

        stbItemType.Text = content
        stbID.Text = uniqueId

        ' Look for triggers associated with this point using GRID COORDINATES
        For Each trigger As Triggers In TriggerData
            ' Check floor triggers
            If trigger.TriggeringPoint.Item1 = highlightedX AndAlso trigger.TriggeringPoint.Item2 = highlightedY Then
                triggerInfo = trigger.TriggerFull
                Exit For
            End If

            ' Check if the TriggerActionPoint is on a wall
            If trigger.TriggerActionPoint.Item1 = highlightedX AndAlso trigger.TriggerActionPoint.Item2 = highlightedY Then
                If trigger.TriggerActionIsWall Then
                    If trigger.TriggerActionIsVerticalWall AndAlso highlightedWallX Then
                        triggerInfo = trigger.TriggerFull
                    ElseIf trigger.TriggerActionIsHorizontalWall AndAlso highlightedWallY Then
                        triggerInfo = trigger.TriggerFull
                    End If
                Else
                    triggerInfo = trigger.TriggerFull
                End If
                Exit For
            End If
        Next

        ' Only update stbTrigger if not over a wall or if the trigger action point is on the correct wall type
        If Not isOverWall Then
            stbTrigger.Text = triggerInfo ' Display the trigger information in the status bar
        Else
            For Each trigger As Triggers In TriggerData
                If trigger.TriggerActionPoint.Item1 = highlightedX AndAlso trigger.TriggerActionPoint.Item2 = highlightedY Then
                    If trigger.TriggerActionIsWall AndAlso
           ((trigger.TriggerActionIsVerticalWall AndAlso highlightedWallX) OrElse
            (trigger.TriggerActionIsHorizontalWall AndAlso highlightedWallY)) Then
                        stbTrigger.Text = triggerInfo ' Display the trigger information in the status bar
                        Exit For
                    End If
                End If
            Next
            If stbTrigger.Text = "" Then
                stbTrigger.Text = ""
            End If
        End If

        ' Apply formatting to the RichTextBox
        ApplyFormatting(stbTrigger, triggerInfo)
    End Sub
    Private Function GetItemTypeAtPosition(x As Integer, y As Integer) As String
        ' Determine if the selected item is a wall, wall item, or floor item
        If highlightedWallX Then
            Return If(verticalWallItems(x)(y) IsNot Nothing, "VerticalWallItem", "VerticalWall")
        ElseIf highlightedWallY Then
            Return If(horizontalWallItems(x)(y) IsNot Nothing, "HorizontalWallItem", "HorizontalWall")
        Else
            Return If(placedItems(x)(y) IsNot Nothing, "FloorItem", "Floor")
        End If
    End Function

    Private Sub HideUcBaddyProps()
        UcBaddyProps.Visible = False
    End Sub

    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 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

            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

            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

                    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

                    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

                    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

                    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

                End If
            End If
        End If
    End Sub

    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

            End If
        End If
    End Sub
    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 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 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 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 IsVerticalItem(imgKey As String) As Boolean
        Return imgKey = "picLaser1" OrElse imgKey = "picFlipper1" OrElse imgKey = "picFlipper2"
    End Function

    Private Function IsHorizontalItem(imgKey As String) As Boolean
        Return imgKey = "picLaser2" OrElse imgKey = "picFlipper3" OrElse imgKey = "picFlipper4"
    End Function
    Private Function IsFlipperItem(item As ItemInfo) As Boolean
        If item Is Nothing Then Return False

        Dim imgKey = item.ImageKey
        Return imgKey = "picFlipper1" OrElse imgKey = "picFlipper2" _
        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

#End Region

#Region " Draw "
    Private Sub PicGame_Paint(sender As Object, e As PaintEventArgs) Handles picGame.Paint
        ' Calculate the sizes of the squares based on the client size of picGame
        CalculateSquareSizes(picGame.ClientSize.Width, picGame.ClientSize.Height)

        ' Set the smoothing mode to None for pixel-perfect drawing
        e.Graphics.SmoothingMode = SmoothingMode.None

        ' Draw the gradient background if useGradient is true
        If useGradient Then
            DrawGradientBackground(e.Graphics)
        End If

        ' Draw the grid
        DrawGrid(e.Graphics)

        ' Draw any dragged item
        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

        ' Draw selection rectangle
        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

        ' Draw selection data during drag
        If isDraggingSelection Then
            Dim baseCellX As Integer = (selectionRectangle.X - INDENT) \ (SQSizeX + WALL_THICKNESS)
            Dim baseCellY As Integer = (selectionRectangle.Y - INDENT) \ (SQSizeY + WALL_THICKNESS)
            If isCopying Then
                DrawSelectionData(e.Graphics, baseCellX, baseCellY, copiedSelectionData)
            Else
                DrawSelectionData(e.Graphics, baseCellX, baseCellY, selectionData)
            End If
        End If

        ' Ensure DrawHighlight is called
        DrawHighlight(e.Graphics)

        ' Draw triggers
        DrawTriggers(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 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)

        ' Draw triggers
        DrawTriggers(g)
    End Sub

    Private Sub DrawTriggers(g As Graphics)
        If triggerImage Is Nothing OrElse triggerImage2Small Is Nothing Then Return ' Ensure the images are loaded

        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT

        For Each trigger As Triggers In TriggerData
            ' Draw the trigger icon for the TriggeringPoint (always on floor)
            If trigger.TriggeringPoint.Item1 >= 0 AndAlso trigger.TriggeringPoint.Item1 < SQCOUNTX AndAlso
       trigger.TriggeringPoint.Item2 >= 0 AndAlso trigger.TriggeringPoint.Item2 < SQCOUNTY Then

                ' Move the TriggeringPoint icon to the top-left of the floor
                Dim triggerX = xOrigin + (trigger.TriggeringPoint.Item1 * (SQSizeX + WALL_THICKNESS)) + (WALL_THICKNESS \ 2) + 4 ' Slightly right of the top-left
                Dim triggerY = yOrigin + (trigger.TriggeringPoint.Item2 * (SQSizeY + WALL_THICKNESS)) + (WALL_THICKNESS \ 2) + 4 ' Slightly down from the top-left
                g.DrawImage(triggerImageSmall, triggerX, triggerY)
            End If

            ' Draw the trigger icon for the TriggerActionPoint
            If trigger.TriggerActionPoint.Item1 >= 0 AndAlso trigger.TriggerActionPoint.Item1 <= SQCOUNTX AndAlso
       trigger.TriggerActionPoint.Item2 >= 0 AndAlso trigger.TriggerActionPoint.Item2 <= SQCOUNTY Then

                Dim actionX As Integer = 0
                Dim actionY As Integer = 0

                If trigger.TriggerActionIsWall Then
                    ' Handle drawing for walls (horizontal or vertical)
                    If trigger.TriggerActionIsVerticalWall Then
                        ' Move to the top of the vertical wall
                        actionX = xOrigin + (trigger.TriggerActionPoint.Item1 * (SQSizeX + WALL_THICKNESS)) - (WALL_THICKNESS \ 2) - 2
                        actionY = yOrigin + (trigger.TriggerActionPoint.Item2 * (SQSizeY + WALL_THICKNESS)) - (triggerImage2Small.Height \ 2) + 12 ' Slightly above the center
                    ElseIf trigger.TriggerActionIsHorizontalWall Then
                        ' Move to the center of the horizontal wall
                        actionX = xOrigin + (trigger.TriggerActionPoint.Item1 * (SQSizeX + WALL_THICKNESS)) + (SQSizeX \ 2) - 20
                        actionY = yOrigin + (trigger.TriggerActionPoint.Item2 * (SQSizeY + WALL_THICKNESS)) - (WALL_THICKNESS \ 2) - (triggerImage2Small.Height \ 2) + 2
                    End If
                Else
                    ' Move the action point icon to the top-left of the floor
                    If trigger.TriggerActionPoint.Item1 >= 0 AndAlso trigger.TriggerActionPoint.Item1 < SQCOUNTX AndAlso
           trigger.TriggerActionPoint.Item2 >= 0 AndAlso trigger.TriggerActionPoint.Item2 < SQCOUNTY Then
                        actionX = xOrigin + (trigger.TriggerActionPoint.Item1 * (SQSizeX + WALL_THICKNESS)) + (WALL_THICKNESS \ 2) + 4 ' Slightly right of the top-left
                        actionY = yOrigin + (trigger.TriggerActionPoint.Item2 * (SQSizeY + WALL_THICKNESS)) + (WALL_THICKNESS \ 2) + 4 ' Slightly down from the top-left
                    End If
                End If

                ' Check if the trigger action is "Flip Horizontal" or "Flip Vertical"
                If trigger.TriggerAction = "Flip Horizontal" OrElse trigger.TriggerAction = "Flip Vertical" Then
                    ' Skip drawing the trigger icon for these actions
                    Continue For
                End If

                ' Draw the action trigger icon using triggerImage2Small
                g.DrawImage(triggerImage2Small, actionX, actionY)

                ' Draw a line between the triggering point and the action point if hovered
                If HoveredTrigger IsNot Nothing AndAlso HoveredTrigger.Equals(trigger) Then
                    Dim pen As New Pen(Color.Red, 2)

                    ' Calculate the center of the triggering point icon
                    Dim startX As Integer = xOrigin + (trigger.TriggeringPoint.Item1 * (SQSizeX + WALL_THICKNESS)) + (WALL_THICKNESS \ 2) + (triggerImageSmall.Width \ 2)
                    Dim startY As Integer = yOrigin + (trigger.TriggeringPoint.Item2 * (SQSizeY + WALL_THICKNESS)) + (WALL_THICKNESS \ 2) + (triggerImageSmall.Height \ 2)

                    ' Calculate the center of the action point icon
                    Dim endX As Integer = actionX + (triggerImage2Small.Width \ 2)
                    Dim endY As Integer = actionY + (triggerImage2Small.Height \ 2)

                    g.DrawLine(pen, startX, startY, endX, endY)
                End If
            End If
        Next
    End Sub
    Private Sub DrawGridNumbers(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT
        Dim font As New Font("Arial", 12, FontStyle.Regular)
        Dim brush As New SolidBrush(Color.Black)
        Dim brush2 As New SolidBrush(Color.Green)
        Dim smallFont As New Font("Arial", 6, FontStyle.Regular)

        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

        ' Draw smaller numbers for vertical walls
        For x As Integer = 0 To SQCOUNTX
            Dim smallLabel As String = x.ToString()
            Dim smallXPos As Single = xOrigin + (x * (SQSizeX + WALL_THICKNESS)) - (WALL_THICKNESS \ 2) + 4
            Dim smallYPos As Single = yOrigin + columnNumberOffsetY + 4
            Dim smallXLabelSize As SizeF = g.MeasureString(smallLabel, smallFont)
            g.DrawString(smallLabel, smallFont, brush2, smallXPos - (smallXLabelSize.Width / 2.0F), smallYPos)
        Next

        ' Draw smaller numbers for horizontal walls
        For y As Integer = 0 To SQCOUNTY
            Dim smallLabel As String = y.ToString()
            Dim smallYPos As Single = yOrigin + (y * (SQSizeY + WALL_THICKNESS)) - (WALL_THICKNESS \ 2) + 3
            Dim smallXPos As Single = xOrigin + rowNumberOffsetX
            Dim smallYLabelSize As SizeF = g.MeasureString(smallLabel, smallFont)
            g.DrawString(smallLabel, smallFont, brush2, smallXPos - smallYLabelSize.Width, smallYPos - (smallYLabelSize.Height / 2.0F))
        Next

        font.Dispose()
        brush.Dispose()
        smallFont.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)
        ' Font and brush for key count text
        Dim keyFont As New Font("Arial", 14, FontStyle.Bold) ' Text will now be white

        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)

                            ' Draw the key count in the center of the gate (only for gates)
                            If placedItems(x)(y).ImageKey = "picGate" Then
                                Dim keysRequired = placedItems(x)(y).KeysRequiredToOpen
                                Dim numKeyText As String = keysRequired.ToString()

                                ' Center the text in the middle of the gate
                                Dim textSize As SizeF = g.MeasureString(numKeyText, keyFont)
                                Dim textX As Integer = xPos + (SQSizeX \ 2) - (textSize.Width \ 2)
                                Dim textY As Integer = yPos + (SQSizeY \ 2) - (textSize.Height \ 2)

                                ' Draw the number of keys on the gate
                                g.DrawString(numKeyText, keyFont, Brushes.White, textX, textY)
                            End If
                        End If
                    End If
                Next
            End If
        Next

        ' Draw region overlays if chkShowRegions is checked
        If chkShowRegions.Checked Then
            For Each region As RegionInfo In regions
                If regionOverlayColors.ContainsKey(region.Name) Then
                    Dim overlayColor As Color = regionOverlayColors(region.Name)
                    Using brush As New SolidBrush(overlayColor)
                        g.FillRectangle(brush, region.Rectangle)
                    End Using
                End If
            Next
        End If
    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

        ' Determine the highlight color
        Dim highlightColorToUse As Color = highlightColor
        If SelectionMode Then
            Dim triggerEditor As frmTriggerEditor = CType(SelectionRequestedByForm, frmTriggerEditor)
            If (triggerEditor.AllowFloorSelectionOnly AndAlso (highlightedWallX OrElse highlightedWallY)) OrElse
           (triggerEditor.AllowWallSelectionOnly AndAlso Not (highlightedWallX OrElse highlightedWallY)) Then
                highlightColorToUse = Color.Red
            End If
        End If

        Using highlightPen As New Pen(highlightColorToUse, 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

        Dim itemName As String

        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
                    itemName = GetItemName(verticalWallItems(highlightedX)(highlightedY).ImageKey)
                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
                    itemName = GetItemName(horizontalWallItems(highlightedX)(highlightedY).ImageKey)
                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
                    itemName = GetItemName(placedItems(highlightedX)(highlightedY).ImageKey)
                End If
            End If
        End If
        ' Reset hoveredTrigger
        HoveredTrigger = Nothing

        ' Check if the mouse is over a triggering point or action point
        For Each trigger As Triggers In TriggerData
            If trigger.TriggeringPoint.Item1 = highlightedX AndAlso trigger.TriggeringPoint.Item2 = highlightedY Then
                If Not highlightedWallX AndAlso Not highlightedWallY Then
                    HoveredTrigger = trigger
                    Exit For
                End If
            End If
            If trigger.TriggerActionPoint.Item1 = highlightedX AndAlso trigger.TriggerActionPoint.Item2 = highlightedY Then
                If trigger.TriggerActionIsWall Then
                    If trigger.TriggerActionIsVerticalWall AndAlso highlightedWallX Then
                        HoveredTrigger = trigger
                        Exit For
                    ElseIf trigger.TriggerActionIsHorizontalWall AndAlso highlightedWallY Then
                        HoveredTrigger = trigger
                        Exit For
                    End If
                ElseIf Not highlightedWallX AndAlso Not highlightedWallY Then
                    HoveredTrigger = trigger
                    Exit For
                End If
            End If
        Next
    End Sub

    Private Function GetImageByKey(imageKey As String) As Image
        Select Case imageKey
            Case "picBlock1"
                Return picBlock1.Image
            Case "picBlock2"
                Return picBlock2.Image
            Case "picBaddy1"
                Return picBaddy1.Image
            Case "picBaddy2"
                Return picBaddy2.Image
            Case "picBaddy3"
                Return picBaddy3.Image
            Case "picBaddy4"
                Return picBaddy4.Image
            Case "picLaser1"
                Return picLaser1.Image
            Case "picLaser2"
                Return picLaser2.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 "picWarp3"
                Return picWarp3.Image
            Case "picVolcano"
                Return picVolcano.Image
            Case Else
                Return Nothing
        End Select
    End Function

#End Region

#Region " Colours "
    Private Sub UpdateColorPickers()
        If rdoWalls.Checked Then
            ' Set color pickers to wall colors
            colPattern.SelectedColor = wallsColor1
            colBackground.SelectedColor = wallsColor2
        ElseIf rdoFloors.Checked Then
            ' Set color pickers to floor colors
            colPattern.SelectedColor = floorsColor1
            colBackground.SelectedColor = floorsColor2
        End If
    End Sub

    Public Sub UpdateGameColors(senderObject As Object, newColor As Color)
        If rdoWalls.Checked Then
            ' Update wall colors
            If senderObject Is colPattern Then
                wallsColor1 = newColor
            ElseIf senderObject Is colBackground Then
                wallsColor2 = newColor
            End If
        ElseIf rdoFloors.Checked Then
            ' Update floor colors
            If senderObject Is colPattern Then
                floorsColor1 = newColor
            ElseIf senderObject Is colBackground Then
                floorsColor2 = newColor
            End If
        End If

        ' Redraw the game area to reflect changes

    End Sub

    Private Sub ColPattern_ColorChanged(sender As Object, e As EventArgs) Handles colPattern.ColorChanged
        TurnOffSelectionMode()
        UpdateGameColors(colPattern, colPattern.SelectedColor)
    End Sub

    Private Sub ColBackground_ColorChanged(sender As Object, e As EventArgs) Handles colBackground.ColorChanged
        TurnOffSelectionMode()
        UpdateGameColors(colBackground, colBackground.SelectedColor)
    End Sub
#End Region

#Region " Undo, Redo "
    Private Sub SaveStateForUndo()
        If isRestoringState Then Return
        undoStack.Push(New RoomData(Me))
        If undoStack.Count > 50 Then
            undoStack = New Stack(Of RoomData)(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 RoomData(Me))
        If undoStack.Count > 50 Then
            undoStack = New Stack(Of RoomData)(undoStack.Reverse().Take(50))
        End If
    End Sub
    Private Sub BtnUndo_Click(sender As Object, e As EventArgs) Handles btnUndo.Click
        TurnOffSelectionMode()
        If undoStack.Count > 1 Then
            ' Save the current state for redo before performing undo
            SaveStateForRedo()

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

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

            ' Update the UI to reflect the restored state
            UpdateGridSizeUI()
            UpdateSelections()

        End If
    End Sub

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

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

        End If
    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
#End Region

#Region " ToolStrip Buttons "
    Private Sub BtnClearEdge_Click(sender As Object, e As EventArgs) Handles btnClearEdge.Click
        TurnOffSelectionMode()
        If Not isRestoringState Then SaveStateForUndo()

        ' Loop through each square in the grid
        For y As Integer = 0 To SQCOUNTY - 1
            For x As Integer = 0 To SQCOUNTX - 1
                floorsUseEdgeStyle(x)(y) = False
            Next
        Next
    End Sub
    Private Sub BtnFill_Click(sender As Object, e As EventArgs) Handles btnFillEdge.Click, btnFillKeys.Click, btnFillCoins.Click
        TurnOffSelectionMode()
        If Not isRestoringState Then SaveStateForUndo()

        ' Get the name of the button clicked
        Dim itemtype As String = CType(sender, ToolStripButton).Name

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

                ' Count the walls around the square at (x, y)
                ' Count vertical walls
                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 - 1 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

                ' Count horizontal walls
                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 - 1 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

                ' Logic to place keys or coins
                If itemtype = "btnFillCoins" AndAlso wallCount = 3 AndAlso Not isFlipperPresent AndAlso placedItems(x)(y) Is Nothing Then
                    placedItems(x)(y) = New ItemInfo("picCoin", New Point(x, y), Guid.NewGuid().ToString())
                ElseIf itemtype = "btnFillKeys" AndAlso wallCount = 3 AndAlso Not isFlipperPresent AndAlso placedItems(x)(y) Is Nothing Then
                    placedItems(x)(y) = New ItemInfo("picKey", New Point(x, y), Guid.NewGuid().ToString())
                End If

                ' Logic to fill edge style
                If itemtype = "btnFillEdge" AndAlso wallCount = 4 Then
                    floorsUseEdgeStyle(x)(y) = True
                End If
            Next
        Next
    End Sub
    Private Sub BtnTriggerEditor_Click(sender As Object, e As EventArgs) Handles btnTriggerEditor.Click
        TurnOffSelectionMode()
        Dim frm As New frmTriggerEditor(TriggerData)
        AddHandler frm.FormClosed, AddressOf OnTriggerEditorFormClosed
        frm.Show()
    End Sub

    Private Sub OnTriggerEditorFormClosed(sender As Object, e As FormClosedEventArgs)
        ' Update the triggerData in FrmDesigner with the current state from FrmTriggerEditor
        Dim frmTriggerEditor As FrmTriggerEditor = CType(sender, FrmTriggerEditor)
        triggerData = frmTriggerEditor.triggerData
    End Sub

    Private Sub BtnFlipHorizontal_Click(sender As Object, e As EventArgs) Handles btnFlipHorizontal.Click
        TurnOffSelectionMode()
        If Not isRestoringState Then SaveStateForUndo()
        Utilities.FlipGridHorizontally(Me)
    End Sub

    Private Sub BtnFlipVertical_Click(sender As Object, e As EventArgs) Handles btnFlipVertical.Click
        TurnOffSelectionMode()
        If Not isRestoringState Then SaveStateForUndo()
        Utilities.FlipGridVertically(Me)
    End Sub

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

        PauseBackgroundTasks()

        Dim gameForm As New frmGame With {
            .RoomFilePath = tempFilePath
        }
        AddHandler gameForm.FormClosed, AddressOf OnGameFormClosed
        gameForm.Show()
    End Sub
    Private Sub PauseBackgroundTasks()
        animationTimer.Stop()
        highlightTimer.Stop()
    End Sub
    Private Sub OnGameFormClosed(sender As Object, e As FormClosedEventArgs)
        'When frmGame is closed
        ResumeBackgroundTasks()
    End Sub

    Public Function CollectItems() As Dictionary(Of String, List(Of ItemInfo))
        Dim itemsDictionary As New Dictionary(Of String, List(Of ItemInfo))

        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 itemKey As String = placedItems(x)(y).ImageKey
                        Dim itemName As String = GetItemName(itemKey)
                        Dim location As New Point(x, y)
                        Dim uniqueId As String = $"{itemName}_{x}_{y}"

                        If Not itemsDictionary.ContainsKey(itemName) Then
                            itemsDictionary(itemName) = New List(Of ItemInfo)
                        End If

                        itemsDictionary(itemName).Add(New ItemInfo(itemKey, location, uniqueId))
                    End If
                Next
            End If
        Next

        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 itemKey As String = verticalWallItems(x)(y).ImageKey
                        Dim itemName As String = GetItemName(itemKey)
                        Dim location As New Point(x, y)
                        Dim uniqueId As String = $"{itemName}_{x}_{y}"

                        If Not itemsDictionary.ContainsKey(itemName) Then
                            itemsDictionary(itemName) = New List(Of ItemInfo)
                        End If

                        itemsDictionary(itemName).Add(New ItemInfo(itemKey, location, uniqueId))
                    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 itemKey As String = horizontalWallItems(x)(y).ImageKey
                        Dim itemName As String = GetItemName(itemKey)
                        Dim location As New Point(x, y)
                        Dim uniqueId As String = $"{itemName}_{x}_{y}"

                        If Not itemsDictionary.ContainsKey(itemName) Then
                            itemsDictionary(itemName) = New List(Of ItemInfo)
                        End If

                        itemsDictionary(itemName).Add(New ItemInfo(itemKey, location, uniqueId))
                    End If
                Next
            End If
        Next

        Return itemsDictionary
    End Function

    Private Sub BtnProperties_Click(sender As Object, e As EventArgs) Handles btnProperties.Click
        TurnOffSelectionMode()
        Dim itemsDictionary = CollectItems()
        Dim propertiesForm As New frmProperties(itemsDictionary, Me)
        Dim unused = propertiesForm.ShowDialog()
        frmGame.Dispose()
    End Sub

    Private Sub BtnAudio_Click(sender As Object, e As EventArgs) Handles btnAudio.Click, lblAudio.Click
        TurnOffSelectionMode()
        Dim audioFile As String = ""
        Dim volume As Integer = 50 ' Default volume
        Dim gain As Integer = 10 ' Default gain

        If Not String.IsNullOrWhiteSpace(SelectedMusic) Then
            Dim selectedMusicParts As String() = SelectedMusic.Split(New String() {", V:", ", G:"}, StringSplitOptions.None)
            If selectedMusicParts.Length = 3 Then
                audioFile = selectedMusicParts(0).Trim()
                If Integer.TryParse(selectedMusicParts(1).Trim(), volume) AndAlso Integer.TryParse(selectedMusicParts(2).Trim(), gain) Then
                    ' Valid format, proceed with the values
                Else
                    Dim unused1 = MessageBox.Show("Error parsing volume or gain values.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                    Return
                End If
            Else
                Dim unused = MessageBox.Show("SelectedMusic string is not in the expected format.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            End If
        End If

        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
    End Sub
#End Region

#Region " Closing "
    Private Sub FrmDesigner_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        SaveRoomToFile(tempFilePath)
    End Sub
    Private Sub ResumeBackgroundTasks()
        animationTimer.Start()
        highlightTimer.Start()
    End Sub

#End Region

End Class
Partial Public Class FrmDesigner
    ' Variables related to selection and ctmMenu
    Private isCursorOnPicGame As Boolean = False
    Private selectionStartedOnWall As Boolean
    Private SelectionType 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 isRightMouseDown As Boolean = False
    Private isLeftMouseDown As Boolean = False
    Private isMouseInsideGrid As Boolean = False

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

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

    Private isSelectionExpandedToWalls As Boolean = False
    Private isSelectionExpandedToFloors As Boolean = False


#Region " Select "
    ' Methods related to ctmMenu
    Private Sub BtnSelect_Click(sender As Object, e As EventArgs) Handles btnSelect.Click
        If isInSelectionMode Then
            ' Exiting selection mode
            isInSelectionMode = False
            isSelecting = False
            isSelectionReadyToDrag = False
            selectionRectangle = Rectangle.Empty
            picGame.Cursor = Cursors.Default
            picGame.Invalidate()
        Else
            ' Entering selection mode
            isInSelectionMode = True
            isSelecting = False
            isSelectionReadyToDrag = False
            selectionRectangle = Rectangle.Empty
            picGame.Cursor = Cursors.Cross
        End If
    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
#End Region

#Region " ctmMenu Opened, ctmMenu Closed "
    Private Sub ctmMenu_Opened(sender As Object, e As EventArgs) Handles ctmMenu.Opened
        AddHandler ctmMenu.Closed, AddressOf ctmMenu_Closed
        ' Check the selection area
        If selectionRectangle.IsEmpty Then
            ' Disable the Clear, Copy, Move menus if there's no selection
            mnuClear.Enabled = False
            mnuCopy.Enabled = False
            Return
        Else
            mnuClear.Enabled = True
            mnuCopy.Enabled = True
        End If

        ' Capture the selection data if not already captured
        If selectionData Is Nothing Then
            CaptureSelectionData()
        End If

        ' Initialize booleans for item categories
        Dim hasEdge As Boolean = False
        Dim hasBaddies As Boolean = False
        Dim hasBlockStoppers As Boolean = False
        Dim hasBlocks As Boolean = False
        Dim hasCoins As Boolean = False
        Dim hasKeys As Boolean = False
        Dim hasWarps As Boolean = False
        Dim hasFlippers As Boolean = False
        Dim hasLasers As Boolean = False
        Dim hasWalls As Boolean = False

        ' Loop through the selection data
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                ' Check Edge
                If selectionData.floorsUseEdgeStyle(x)(y) Then
                    hasEdge = True
                End If

                ' Check placedItems
                Dim placedItem As ItemInfo = selectionData.placedItems(x)(y)
                If placedItem IsNot Nothing Then
                    Dim imageKey As String = placedItem.ImageKey
                    If IsBaddieItem(imageKey) Then
                        hasBaddies = True
                    ElseIf IsBlockStopperItem(imageKey) Then
                        hasBlockStoppers = True
                    ElseIf IsBlockItem(imageKey) Then
                        hasBlocks = True
                    ElseIf IsCoinItem(imageKey) Then
                        hasCoins = True
                    ElseIf IsKeyItem(imageKey) Then
                        hasKeys = True
                    ElseIf IsWarpItem(imageKey) Then
                        hasWarps = True
                    ElseIf isFlipperItems(imageKey) Then
                        hasFlippers = True
                    ElseIf IsLaserItem(imageKey) Then
                        hasLasers = True
                    End If
                End If
            Next
        Next

        ' Check vertical walls and wall items
        If selectionData.verticalWalls IsNot Nothing Then
            For x As Integer = 0 To selectionData.verticalWalls.Length - 1
                For y As Integer = 0 To selectionData.verticalWalls(x).Length - 1
                    If selectionData.verticalWalls(x)(y) Then
                        hasWalls = True
                    End If
                    Dim wallItem As ItemInfo = selectionData.verticalWallItems(x)(y)
                    If wallItem IsNot Nothing Then
                        Dim imageKey As String = wallItem.ImageKey
                        If isFlipperItems(imageKey) Then
                            hasFlippers = True
                        ElseIf IsLaserItem(imageKey) Then
                            hasLasers = True
                        End If
                    End If
                Next
            Next
        End If

        ' Check horizontal walls and wall items
        If selectionData.horizontalWalls IsNot Nothing Then
            For x As Integer = 0 To selectionData.horizontalWalls.Length - 1
                For y As Integer = 0 To selectionData.horizontalWalls(x).Length - 1
                    If selectionData.horizontalWalls(x)(y) Then
                        hasWalls = True
                    End If
                    Dim wallItem As ItemInfo = selectionData.horizontalWallItems(x)(y)
                    If wallItem IsNot Nothing Then
                        Dim imageKey As String = wallItem.ImageKey
                        If isFlipperItems(imageKey) Then
                            hasFlippers = True
                        ElseIf IsLaserItem(imageKey) Then
                            hasLasers = True
                        End If
                    End If
                Next
            Next
        End If

        ' Set enabled state of menu items in Clear menu
        mnuClearEdge.Enabled = hasEdge
        mnuClearBaddies.Enabled = hasBaddies
        mnuClearBlockStoppers.Enabled = hasBlockStoppers
        mnuClearBlocks.Enabled = hasBlocks
        mnuClearCoins.Enabled = hasCoins
        mnuClearKeys.Enabled = hasKeys
        mnuClearWarps.Enabled = hasWarps
        mnuClearFlippers.Enabled = hasFlippers
        mnuClearLasers.Enabled = hasLasers
        mnuClearWalls.Enabled = hasWalls
        mnuClearAll.Enabled = True ' "Clear All" is always enabled

        ' Set enabled state of menu items in Copy menu
        mnuCopyEdge.Enabled = hasEdge
        mnuCopyBaddies.Enabled = hasBaddies
        mnuCopyBlockStoppers.Enabled = hasBlockStoppers
        mnuCopyBlocks.Enabled = hasBlocks
        mnuCopyCoins.Enabled = hasCoins
        mnuCopyKeys.Enabled = hasKeys
        mnuCopyWarps.Enabled = hasWarps
        mnuCopyFlippers.Enabled = hasFlippers
        mnuCopyLasers.Enabled = hasLasers
        mnuCopyWalls.Enabled = hasWalls
        mnuCopyAll.Enabled = True ' "Copy All" is always enabled
    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
#End Region

#Region " 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
            Dim unused1 = 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
                Dim unused = MessageBox.Show($"Invalid selection type for fill operation: {selectionType}.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End Select
    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()
        If selectionData Is Nothing Then
            CaptureSelectionData()
        End If

        ' Fill internal vertical walls
        For x As Integer = 0 To selectionData.selectionWidth - 2
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim wallX As Integer = selectionStartCell.X + x + 1
                Dim wallY As Integer = selectionStartCell.Y + y

                If wallX >= 0 AndAlso wallX < verticalWalls.Length AndAlso wallY >= 0 AndAlso wallY < verticalWalls(0).Length Then
                    verticalWalls(wallX)(wallY) = True
                End If
            Next
        Next

        ' Fill internal horizontal walls
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 2
                Dim wallX As Integer = selectionStartCell.X + x
                Dim wallY As Integer = selectionStartCell.Y + y + 1

                If wallX >= 0 AndAlso wallX < horizontalWalls.Length AndAlso wallY >= 0 AndAlso wallY < horizontalWalls(0).Length Then
                    horizontalWalls(wallX)(wallY) = 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 ItemInfo(itemKey, New Point(selectionStartCell.X + x, selectionStartCell.Y + y), Guid.NewGuid().ToString())
                End If
            Next
        Next
        picGame.Invalidate()
    End Sub


#End Region

#Region " Clear "
    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, mnuClearFlippers.Click, mnuClearLasers.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 "Flippers"
                ClearSelectedAreaItems("picFlipper1", "picFlipper2", "picFlipper3", "picFlipper4")
            Case "Lasers"
                ClearSelectedAreaItems("picLaser1", "picLaser2")
            Case Else
                ClearSelectedAreaItems(GetItemKeysForType(selectionType))
        End Select
    End Sub
    Private Sub ClearSelectedArea()
        If selectionData Is Nothing Then
            CaptureSelectionData()
        End If

        ' Clear placed items and floor edge styles
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                placedItems(selectionStartCell.X + x)(selectionStartCell.Y + y) = Nothing
                floorsUseEdgeStyle(selectionStartCell.X + x)(selectionStartCell.Y + y) = False
            Next
        Next

        ' Clear internal vertical walls
        For x As Integer = 0 To selectionData.selectionWidth - 2
            For y As Integer = 0 To selectionData.selectionHeight - 1
                verticalWalls(selectionStartCell.X + x + 1)(selectionStartCell.Y + y) = False
                verticalWallItems(selectionStartCell.X + x + 1)(selectionStartCell.Y + y) = Nothing
            Next
        Next

        ' Clear internal horizontal walls
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 2
                horizontalWalls(selectionStartCell.X + x)(selectionStartCell.Y + y + 1) = False
                horizontalWallItems(selectionStartCell.X + x)(selectionStartCell.Y + y + 1) = Nothing
            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()
        If selectionData Is Nothing Then
            CaptureSelectionData()
        End If

        ' Clear internal vertical walls
        For x As Integer = 0 To selectionData.selectionWidth - 2
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim wallX As Integer = selectionStartCell.X + x + 1
                Dim wallY As Integer = selectionStartCell.Y + y

                ' Ensure indices are within bounds
                If wallX >= 0 AndAlso wallX < verticalWalls.Length AndAlso wallY >= 0 AndAlso wallY < verticalWalls(0).Length Then
                    verticalWalls(wallX)(wallY) = False
                    verticalWallItems(wallX)(wallY) = Nothing
                End If
            Next
        Next

        ' Clear internal horizontal walls
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 2
                Dim wallX As Integer = selectionStartCell.X + x
                Dim wallY As Integer = selectionStartCell.Y + y + 1

                ' Ensure indices are within bounds
                If wallX >= 0 AndAlso wallX < horizontalWalls.Length AndAlso wallY >= 0 AndAlso wallY < horizontalWalls(0).Length Then
                    horizontalWalls(wallX)(wallY) = False
                    horizontalWallItems(wallX)(wallY) = Nothing
                End If
            Next
        Next

        picGame.Invalidate()
    End Sub

    Private Sub ClearSelectedAreaItems(ParamArray itemKeys As String())
        CaptureSelectionData()

        ' Clear placedItems
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim globalX As Integer = selectionStartCell.X + x
                Dim globalY As Integer = selectionStartCell.Y + y
                If globalX < placedItems.Length AndAlso globalY < placedItems(0).Length Then
                    If placedItems(globalX)(globalY) IsNot Nothing AndAlso itemKeys.Contains(placedItems(globalX)(globalY).ImageKey) Then
                        placedItems(globalX)(globalY) = Nothing
                    End If
                End If
            Next
        Next

        ' Clear verticalWallItems
        If selectionData.verticalWallItems IsNot Nothing Then
            For x As Integer = 0 To selectionData.verticalWallItems.Length - 1
                For y As Integer = 0 To selectionData.verticalWallItems(x).Length - 1
                    Dim globalX As Integer = selectionStartCell.X + x + 1
                    Dim globalY As Integer = selectionStartCell.Y + y
                    If globalX >= 0 AndAlso globalX < verticalWallItems.Length AndAlso globalY >= 0 AndAlso globalY < verticalWallItems(0).Length Then
                        If verticalWallItems(globalX)(globalY) IsNot Nothing AndAlso itemKeys.Contains(verticalWallItems(globalX)(globalY).ImageKey) Then
                            verticalWallItems(globalX)(globalY) = Nothing
                        End If
                    End If
                Next
            Next
        End If

        ' Clear horizontalWallItems
        If selectionData.horizontalWallItems IsNot Nothing Then
            For x As Integer = 0 To selectionData.horizontalWallItems.Length - 1
                For y As Integer = 0 To selectionData.horizontalWallItems(x).Length - 1
                    Dim globalX As Integer = selectionStartCell.X + x
                    Dim globalY As Integer = selectionStartCell.Y + y + 1
                    If globalX >= 0 AndAlso globalX < horizontalWallItems.Length AndAlso globalY >= 0 AndAlso globalY < horizontalWallItems(0).Length Then
                        If horizontalWallItems(globalX)(globalY) IsNot Nothing AndAlso itemKeys.Contains(horizontalWallItems(globalX)(globalY).ImageKey) Then
                            horizontalWallItems(globalX)(globalY) = Nothing
                        End If
                    End If
                Next
            Next
        End If

        picGame.Invalidate()
    End Sub

#End Region

#Region " Copy "
    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, mnuCopyFlippers.Click, mnuCopyLasers.Click

        If Not isRestoringState Then SaveStateForUndo()
        If selectionRectangle.IsEmpty Then
            Return
        End If

        CaptureSelectionData()
        copiedSelectionData = CloneSelectionData(selectionData)
        isCopying = True

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

        ' Filter the selection data based on the selection type
        Select Case copiedSelectionType
            Case "All"
        ' Do nothing, keep all items
            Case "Baddies"
                FilterSelectionDataForItemTypes(selectionData, GetItemKeysForType("Baddies"))
            Case "BlockStoppers"
                FilterSelectionDataForItemTypes(selectionData, GetItemKeysForType("BlockStoppers"))
            Case "Blocks"
                FilterSelectionDataForItemTypes(selectionData, GetItemKeysForType("Blocks"))
            Case "Coins"
                FilterSelectionDataForItemTypes(selectionData, GetItemKeysForType("Coins"))
            Case "Keys"
                FilterSelectionDataForItemTypes(selectionData, GetItemKeysForType("Keys"))
            Case "Warps"
                FilterSelectionDataForItemTypes(selectionData, GetItemKeysForType("Warps"))
            Case "Flippers"
                FilterSelectionDataForItemTypes(selectionData, GetItemKeysForType("Flippers"))
            Case "Lasers"
                FilterSelectionDataForItemTypes(selectionData, GetItemKeysForType("Lasers"))
            Case "Edge"
        ' Handle edge separately if needed
            Case "Walls"
                ' Handle walls separately if needed
            Case Else
                ' Handle other cases
        End Select

        ' Proceed with copying the filtered selection data
        copiedSelectionData = CloneSelectionData(selectionData)

        ' Get cursor position in picGame coordinates
        Dim cursorPosInPicGame = picGame.PointToClient(Cursor.Position)

        ' Adjust selection rectangle to center on the cursor
        selectionRectangle.Location = New Point(
    cursorPosInPicGame.X - (selectionRectangle.Width \ 2),
    cursorPosInPicGame.Y - (selectionRectangle.Height \ 2)
)

        ' Recalculate selectionOffset based on new rectangle position
        selectionOffset = New Point(
    Cursor.Position.X - picGame.PointToScreen(selectionRectangle.Location).X,
    Cursor.Position.Y - picGame.PointToScreen(selectionRectangle.Location).Y
)

        ' Automatically start dragging the selection
        isSelectionReadyToDrag = True
        isDraggingSelection = True
        picGame.Capture = True
        Cursor = Cursors.SizeAll
        picGame.Invalidate()

        ' Handle pasting logic
        AddHandler picGame.MouseUp, Sub(mouseSender As Object, mouseEventArgs As MouseEventArgs)
                                        ' Paste data
                                        PasteSelectionDataTo(New Point(cursorPosInPicGame.X, cursorPosInPicGame.Y), False, copiedSelectionType = "Edge")
                                        isSelectionReadyToDrag = False
                                        isDraggingSelection = False
                                        picGame.Capture = False
                                        Cursor = Cursors.Default
                                        picGame.Invalidate()
                                    End Sub
    End Sub
    Private Sub PasteSelectionDataTo(newCell As Point, removeOldData As Boolean, copyEdge As Boolean)
        If copiedSelectionData Is Nothing Then
            Return
        End If

        ' Ensure the new position is within bounds
        newCell.X = Math.Max(0, Math.Min(SQCOUNTX - copiedSelectionData.selectionWidth, newCell.X))
        newCell.Y = Math.Max(0, Math.Min(SQCOUNTY - copiedSelectionData.selectionHeight, newCell.Y))

        ' Paste placed items and floor edge styles
        For x As Integer = 0 To copiedSelectionData.selectionWidth - 1
            For y As Integer = 0 To copiedSelectionData.selectionHeight - 1
                If newCell.X + x < SQCOUNTX AndAlso newCell.Y + y < SQCOUNTY Then
                    If copiedSelectionData.placedItems(x)(y) IsNot Nothing Then
                        placedItems(newCell.X + x)(newCell.Y + y) = copiedSelectionData.placedItems(x)(y)
                    End If
                    If copyEdge AndAlso copiedSelectionData.floorsUseEdgeStyle(x)(y) Then
                        floorsUseEdgeStyle(newCell.X + x)(newCell.Y + y) = True
                    End If
                End If
            Next
        Next

        ' Paste internal vertical walls, if any
        If copiedSelectionData.verticalWalls IsNot Nothing AndAlso Not removeOldData Then
            For x As Integer = 0 To copiedSelectionData.verticalWalls.Length - 1
                For y As Integer = 0 To copiedSelectionData.verticalWalls(x).Length - 1
                    Dim targetX As Integer = newCell.X + x + 1
                    Dim targetY As Integer = newCell.Y + y

                    If targetX >= 0 AndAlso targetX < verticalWalls.Length AndAlso targetY >= 0 AndAlso targetY < verticalWalls(0).Length Then
                        If copiedSelectionData.verticalWalls(x)(y) Then
                            verticalWalls(targetX)(targetY) = True
                        End If
                        If copiedSelectionData.verticalWallItems(x)(y) IsNot Nothing Then
                            verticalWallItems(targetX)(targetY) = copiedSelectionData.verticalWallItems(x)(y)
                        End If
                    End If
                Next
            Next
        End If

        ' Paste internal horizontal walls, if any
        If copiedSelectionData.horizontalWalls IsNot Nothing AndAlso Not removeOldData Then
            For x As Integer = 0 To copiedSelectionData.horizontalWalls.Length - 1
                For y As Integer = 0 To copiedSelectionData.horizontalWalls(x).Length - 1
                    Dim targetX As Integer = newCell.X + x
                    Dim targetY As Integer = newCell.Y + y + 1

                    If targetX >= 0 AndAlso targetX < horizontalWalls.Length AndAlso targetY >= 0 AndAlso targetY < horizontalWalls(0).Length Then
                        If copiedSelectionData.horizontalWalls(x)(y) Then
                            horizontalWalls(targetX)(targetY) = True
                        End If
                        If copiedSelectionData.horizontalWallItems(x)(y) IsNot Nothing Then
                            horizontalWallItems(targetX)(targetY) = copiedSelectionData.horizontalWallItems(x)(y)
                        End If
                    End If
                Next
            Next
        End If

        ' Update the selection rectangle to the new position
        selectionRectangle = New Rectangle(
    INDENT + (newCell.X * (SQSizeX + WALL_THICKNESS)) + (WALL_THICKNESS \ 2),
    INDENT + (newCell.Y * (SQSizeY + WALL_THICKNESS)) + (WALL_THICKNESS \ 2),
    (copiedSelectionData.selectionWidth * (SQSizeX + WALL_THICKNESS)) - WALL_THICKNESS,
    (copiedSelectionData.selectionHeight * (SQSizeY + WALL_THICKNESS)) - WALL_THICKNESS
)

        ' Remove old data if moving
        If removeOldData Then
            ClearSelectedArea()
        End If

        picGame.Invalidate()
    End Sub
    Private Sub CaptureSelectionData()
        If selectionRectangle.IsEmpty Then
            Return
        End If

        ' Get start and end cell indices from selection rectangle
        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)

        ' Allow negative indices to handle selections starting outside the grid
        Dim selectionCellWidth As Integer = endCellX - startCellX + 1
        Dim selectionCellHeight As Integer = endCellY - startCellY + 1

        ' Adjust startCellX and startCellY if negative
        Dim adjustedStartCellX As Integer = Math.Max(0, startCellX)
        Dim adjustedStartCellY As Integer = Math.Max(0, startCellY)

        ' Adjust selection dimensions if start cells were negative
        selectionCellWidth -= adjustedStartCellX - startCellX
        selectionCellHeight -= adjustedStartCellY - startCellY

        ' Adjust end cells if they exceed grid boundaries
        adjustedStartCellX = Math.Min(adjustedStartCellX, SQCOUNTX - 1)
        adjustedStartCellY = Math.Min(adjustedStartCellY, SQCOUNTY - 1)
        selectionCellWidth = Math.Min(selectionCellWidth, SQCOUNTX - adjustedStartCellX)
        selectionCellHeight = Math.Min(selectionCellHeight, SQCOUNTY - adjustedStartCellY)

        ' Initialize selectionData
        selectionData = New SelectionData With {
        .selectionWidth = selectionCellWidth,
        .selectionHeight = selectionCellHeight,
        .selectedWalls = New List(Of Point)(),
        .selectedCells = New List(Of Point)()
    }

        ' Initialize arrays
        selectionData.placedItems = New ItemInfo(selectionData.selectionWidth - 1)() {}
        selectionData.floorsUseEdgeStyle = New Boolean(selectionData.selectionWidth - 1)() {}
        For x As Integer = 0 To selectionData.selectionWidth - 1
            selectionData.placedItems(x) = New ItemInfo(selectionData.selectionHeight - 1) {}
            selectionData.floorsUseEdgeStyle(x) = New Boolean(selectionData.selectionHeight - 1) {}
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim srcX As Integer = adjustedStartCellX + x
                Dim srcY As Integer = adjustedStartCellY + y
                If srcX >= 0 AndAlso srcX < SQCOUNTX AndAlso srcY >= 0 AndAlso srcY < SQCOUNTY Then
                    selectionData.placedItems(x)(y) = placedItems(srcX)(srcY)
                    selectionData.floorsUseEdgeStyle(x)(y) = floorsUseEdgeStyle(srcX)(srcY)
                Else
                    selectionData.placedItems(x)(y) = Nothing
                    selectionData.floorsUseEdgeStyle(x)(y) = False
                End If
            Next
        Next

        ' Capture internal vertical walls (excluding boundary walls)
        If selectionData.selectionWidth > 1 Then
            Dim numVertWalls As Integer = selectionData.selectionWidth - 2
            selectionData.verticalWalls = New Boolean(numVertWalls)() {}
            selectionData.verticalWallItems = New ItemInfo(numVertWalls)() {}
            For x As Integer = 0 To numVertWalls
                selectionData.verticalWalls(x) = New Boolean(selectionData.selectionHeight - 1) {}
                selectionData.verticalWallItems(x) = New ItemInfo(selectionData.selectionHeight - 1) {}
                For y As Integer = 0 To selectionData.selectionHeight - 1
                    Dim srcX As Integer = adjustedStartCellX + x + 1
                    Dim srcY As Integer = adjustedStartCellY + y
                    If srcX >= 0 AndAlso srcX < verticalWalls.Length AndAlso srcY >= 0 AndAlso srcY < verticalWalls(0).Length Then
                        selectionData.verticalWalls(x)(y) = verticalWalls(srcX)(srcY)
                        selectionData.verticalWallItems(x)(y) = verticalWallItems(srcX)(srcY)
                    Else
                        selectionData.verticalWalls(x)(y) = False
                        selectionData.verticalWallItems(x)(y) = Nothing
                    End If
                Next
            Next
        Else
            ' No internal vertical walls to capture
            selectionData.verticalWalls = Nothing
            selectionData.verticalWallItems = Nothing
        End If

        ' Capture internal horizontal walls (excluding boundary walls)
        If selectionData.selectionHeight > 1 Then
            Dim numHorizWalls As Integer = selectionData.selectionHeight - 2
            selectionData.horizontalWalls = New Boolean(selectionData.selectionWidth - 1)() {}
            selectionData.horizontalWallItems = New ItemInfo(selectionData.selectionWidth - 1)() {}
            For x As Integer = 0 To selectionData.selectionWidth - 1
                selectionData.horizontalWalls(x) = New Boolean(numHorizWalls) {}
                selectionData.horizontalWallItems(x) = New ItemInfo(numHorizWalls) {}
                For y As Integer = 0 To numHorizWalls
                    Dim srcX As Integer = adjustedStartCellX + x
                    Dim srcY As Integer = adjustedStartCellY + y + 1
                    If srcX >= 0 AndAlso srcX < horizontalWalls.Length AndAlso srcY >= 0 AndAlso srcY < horizontalWalls(0).Length Then
                        selectionData.horizontalWalls(x)(y) = horizontalWalls(srcX)(srcY)
                        selectionData.horizontalWallItems(x)(y) = horizontalWallItems(srcX)(srcY)
                    Else
                        selectionData.horizontalWalls(x)(y) = False
                        selectionData.horizontalWallItems(x)(y) = Nothing
                    End If
                Next
            Next
        Else
            ' No internal horizontal walls to capture
            selectionData.horizontalWalls = Nothing
            selectionData.horizontalWallItems = Nothing
        End If

        ' Store the selection start cell
        selectionData.originalSelectionStartCell = New Point(adjustedStartCellX, adjustedStartCellY)
    End Sub
    Private Function CloneSelectionData(original As SelectionData) As SelectionData
        Dim clone As New SelectionData With {
            .selectionWidth = original.selectionWidth,
            .selectionHeight = original.selectionHeight
        }

        ' Clone placedItems
        If original.placedItems IsNot Nothing Then
            clone.placedItems = original.placedItems.Select(Function(row) row.ToArray()).ToArray()
        End If

        ' Clone floorsUseEdgeStyle
        If original.floorsUseEdgeStyle IsNot Nothing Then
            clone.floorsUseEdgeStyle = original.floorsUseEdgeStyle.Select(Function(row) row.ToArray()).ToArray()
        End If

        ' Clone verticalWalls and verticalWallItems
        If original.verticalWalls IsNot Nothing Then
            clone.verticalWalls = original.verticalWalls.Select(Function(row) CType(row.Clone(), Boolean())).ToArray()
        End If

        If original.verticalWallItems IsNot Nothing Then
            clone.verticalWallItems = original.verticalWallItems.Select(Function(row) row.ToArray()).ToArray()
        End If

        ' Clone horizontalWalls and horizontalWallItems
        If original.horizontalWalls IsNot Nothing Then
            clone.horizontalWalls = original.horizontalWalls.Select(Function(row) CType(row.Clone(), Boolean())).ToArray()
        End If

        If original.horizontalWallItems IsNot Nothing Then
            clone.horizontalWallItems = original.horizontalWallItems.Select(Function(row) row.ToArray()).ToArray()
        End If

        Return clone
    End Function

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

        Dim colorMatrix As New Imaging.ColorMatrix With {
        .Matrix33 = 0.5F  ' Set opacity to 50%
    }
        Dim imgAttributes As New Imaging.ImageAttributes()
        imgAttributes.SetColorMatrix(colorMatrix, Imaging.ColorMatrixFlag.Default, Imaging.ColorAdjustType.Bitmap)

        ' Draw placed items
        If data.placedItems IsNot Nothing Then
            For x As Integer = 0 To data.selectionWidth - 1
                For y As Integer = 0 To data.selectionHeight - 1
                    If data.placedItems(x)(y) IsNot Nothing Then
                        Dim img As Image = GetImageByKey(data.placedItems(x)(y).ImageKey)
                        If img IsNot Nothing Then
                            ImageAnimator.UpdateFrames(img)
                            Dim xPos As Integer = xOrigin + ((baseCellX + x) * (SQSizeX + WALL_THICKNESS)) + (WALL_THICKNESS \ 2)
                            Dim yPos As Integer = yOrigin + ((baseCellY + y) * (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 If

        ' Draw vertical wall items
        If data.verticalWallItems IsNot Nothing Then
            For x As Integer = 0 To data.verticalWallItems.Length - 1
                For y As Integer = 0 To data.verticalWallItems(x).Length - 1
                    If data.verticalWallItems(x)(y) IsNot Nothing Then
                        Dim img As Image = GetImageByKey(data.verticalWallItems(x)(y).ImageKey)
                        If img IsNot Nothing Then
                            ImageAnimator.UpdateFrames(img)
                            Dim wallX As Integer = xOrigin + ((baseCellX + x + 1) * (SQSizeX + WALL_THICKNESS)) - (WALL_THICKNESS \ 2)
                            Dim wallY As Integer = yOrigin + ((baseCellY + 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, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttributes)
                        End If
                    End If
                Next
            Next
        End If

        ' Draw horizontal wall items
        If data.horizontalWallItems IsNot Nothing Then
            For x As Integer = 0 To data.horizontalWallItems.Length - 1
                For y As Integer = 0 To data.horizontalWallItems(x).Length - 1
                    If data.horizontalWallItems(x)(y) IsNot Nothing Then
                        Dim img As Image = GetImageByKey(data.horizontalWallItems(x)(y).ImageKey)
                        If img IsNot Nothing Then
                            ImageAnimator.UpdateFrames(img)
                            Dim wallX As Integer = xOrigin + ((baseCellX + x) * (SQSizeX + WALL_THICKNESS)) + (WALL_THICKNESS \ 2)
                            Dim wallY As Integer = yOrigin + ((baseCellY + y + 1) * (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, 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttributes)
                        End If
                    End If
                Next
            Next
        End If
    End Sub
#End Region

#Region " Convert to Region "
    Private Sub mnuRegion_Click(sender As Object, e As EventArgs) Handles mnuRegion1.Click, mnuRegion2.Click, mnuRegion3.Click, mnuRegion4.Click, mnuRegion5.Click
        ' Get the selected region menu item
        Dim menuItem As ToolStripMenuItem = CType(sender, ToolStripMenuItem)
        Dim regionName As String = menuItem.Name.Substring(9) ' Remove the "mnuRegion" prefix

        ' Convert the selected area to the region
        ConvertSelectedAreaToRegion("Region" & regionName)
    End Sub

    Private Sub ConvertSelectedAreaToRegion(regionName As String)
        If selectionRectangle.IsEmpty Then
            Dim unused = MessageBox.Show("Please select an area before converting to a region.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        ' Check for existing region of the same type
        Dim existingRegion As RegionInfo = regions.FirstOrDefault(Function(r) r.Name = regionName)
        If existingRegion IsNot Nothing Then
            ' Replace the existing region with the new one
            existingRegion.Rectangle = selectionRectangle
        Else
            ' Add the new region
            Dim regionInfo As New RegionInfo(regionName, selectionRectangle)
            regions.Add(regionInfo)
        End If

        ' Debugging information
        Debug.WriteLine($"Region {regionName} created at {selectionRectangle}")
    End Sub
    Public Function GetRegions() As List(Of RegionInfo)
        Return regions
    End Function

#End Region

    Private Function IsBaddieItem(imageKey As String) As Boolean
        Return imageKey = "picBaddy1" OrElse imageKey = "picBaddy2" OrElse imageKey = "picBaddy3" OrElse imageKey = "picBaddy4"
    End Function

    Private Function IsBlockStopperItem(imageKey As String) As Boolean
        Return imageKey = "picBlockStopper1"
    End Function

    Private Function IsBlockItem(imageKey As String) As Boolean
        Return imageKey = "picBlock1"
    End Function

    Private Function IsCoinItem(imageKey As String) As Boolean
        Return imageKey = "picCoin"
    End Function

    Private Function IsKeyItem(imageKey As String) As Boolean
        Return imageKey = "picKey"
    End Function

    Private Function IsWarpItem(imageKey As String) As Boolean
        Return imageKey = "picWarp" OrElse imageKey = "picWarp2"
    End Function

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

    Private Function IsLaserItem(imageKey As String) As Boolean
        Return imageKey = "picLaser1" OrElse imageKey = "picLaser2"
    End Function

    Private Sub mnuCancel_Click(sender As Object, e As EventArgs) Handles mnuCancel.Click
        TurnOffSelectionMode()
    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 "Flippers"
                Return New String() {"picFlipper1", "picFlipper2", "picFlipper3", "picFlipper4"}
            Case "Lasers"
                Return New String() {"picLaser1", "picLaser2"}
            Case Else
                Return New String() {}
        End Select
    End Function
    Private Sub FilterSelectionDataForItemTypes(selectionData As SelectionData, allowedItemKeys As String())
        ' Filter placedItems
        For x As Integer = 0 To selectionData.selectionWidth - 1
            For y As Integer = 0 To selectionData.selectionHeight - 1
                Dim placedItem As ItemInfo = selectionData.placedItems(x)(y)
                If placedItem IsNot Nothing AndAlso Not allowedItemKeys.Contains(placedItem.ImageKey) Then
                    selectionData.placedItems(x)(y) = Nothing
                End If
            Next
        Next

        ' Filter verticalWallItems
        If selectionData.verticalWallItems IsNot Nothing Then
            For x As Integer = 0 To selectionData.verticalWallItems.Length - 1
                For y As Integer = 0 To selectionData.verticalWallItems(x).Length - 1
                    Dim wallItem As ItemInfo = selectionData.verticalWallItems(x)(y)
                    If wallItem IsNot Nothing AndAlso Not allowedItemKeys.Contains(wallItem.ImageKey) Then
                        selectionData.verticalWallItems(x)(y) = Nothing
                    End If
                Next
            Next
        End If

        ' Filter horizontalWallItems
        If selectionData.horizontalWallItems IsNot Nothing Then
            For x As Integer = 0 To selectionData.horizontalWallItems.Length - 1
                For y As Integer = 0 To selectionData.horizontalWallItems(x).Length - 1
                    Dim wallItem As ItemInfo = selectionData.horizontalWallItems(x)(y)
                    If wallItem IsNot Nothing AndAlso Not allowedItemKeys.Contains(wallItem.ImageKey) Then
                        selectionData.horizontalWallItems(x)(y) = Nothing
                    End If
                Next
            Next
        End If

        ' Filter verticalWalls
        If selectionData.verticalWalls IsNot Nothing Then
            For x As Integer = 0 To selectionData.verticalWalls.Length - 1
                For y As Integer = 0 To selectionData.verticalWalls(x).Length - 1
                    If Not allowedItemKeys.Contains("Walls") Then
                        selectionData.verticalWalls(x)(y) = False
                    End If
                Next
            Next
        End If

        ' Filter horizontalWalls
        If selectionData.horizontalWalls IsNot Nothing Then
            For x As Integer = 0 To selectionData.horizontalWalls.Length - 1
                For y As Integer = 0 To selectionData.horizontalWalls(x).Length - 1
                    If Not allowedItemKeys.Contains("Walls") Then
                        selectionData.horizontalWalls(x)(y) = False
                    End If
                Next
            Next
        End If
    End Sub

End Class


System.NullReferenceException
  HResult=0x80004003
  Message=Object reference not set to an instance of an object.
  Source=Enigma
  StackTrace:
   at Enigma.FrmDesigner.DrawSelectionData(Graphics g, Int32 baseCellX, Int32 baseCellY, SelectionData data) in D:\01 - Grags\Visual Studio\Enigma\Enigma\FrmDesigner\FrmDesigner Partials\frmMenu.vb:line 801