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

Public Class FrmDesigner
    Inherits Form

#Region " Variables "
    Public Property TriggersList As List(Of Trigger) = New List(Of Trigger)()

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

    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 DateTime = DateTime.MinValue
    Private Const DEBOUNCE_INTERVAL As Integer = 50 ' milliseconds

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

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

    Friend wallsHatchStyle As HatchStyle = HatchStyle.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 RoomState)
    Private redoStack As New Stack(Of RoomState)

    Private ReadOnly projectDir As String = GetProjectDirectory()
    Private ReadOnly premadesFolder As String = Path.Combine(projectDir, "Data", "Premades")
    Private ReadOnly savesFolder As String = Path.Combine(projectDir, "Data", "Saves")
    Private ReadOnly musicFolder As String = Path.Combine(projectDir, "Data", "Sound/Music")
    Private tempFilePath As String = Path.Combine(Path.GetTempPath(), "temp.room")


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

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

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

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

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

    Private mouseX As Integer
    Private mouseY As Integer

    Private rand As New Random()

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

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

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

        ' Enable double buffering for picGame
        EnableDoubleBuffering(picGame)
    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
                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
        Me.Text = "New Room"
    End Sub
    Friend Sub InitializeWalls()
        verticalWalls = New Boolean(SQCOUNTX)() {}
        verticalWallItems = New PlacedItem(SQCOUNTX)() {}
        For x As Integer = 0 To SQCOUNTX
            verticalWalls(x) = New Boolean(SQCOUNTY - 1) {}
            verticalWallItems(x) = New PlacedItem(SQCOUNTY - 1) {}
            For y As Integer = 0 To SQCOUNTY - 1
                verticalWalls(x)(y) = True
                verticalWallItems(x)(y) = Nothing
            Next
        Next

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

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

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

    Private Sub InitializeTimers()
        highlightTimer = New Timer() With {.Interval = 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 tabFloor2.Controls
            If TypeOf ctl Is PictureBox Then
                AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
                AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
            End If
        Next

        For Each ctl As Control In tabWalls.Controls
            If TypeOf ctl Is PictureBox Then
                AddHandler ctl.MouseEnter, AddressOf PictureBox_MouseEnter
                AddHandler ctl.MouseLeave, AddressOf PictureBox_MouseLeave
            End If
        Next
    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)

        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 Directory.CreateDirectory(premadesFolder)
        lstPremades.Items.Clear()
        For Each file As String In Directory.GetFiles(premadesFolder, "*.Premade")
            lstPremades.Items.Add(Path.GetFileNameWithoutExtension(file))
        Next
    End Sub

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

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

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

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

                musicFileFound = True
                Exit For
            End If
        Next

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

    Private Sub OnAnimationTick(sender As Object, e As EventArgs)
        For Each img In animatedImages
            ImageAnimator.UpdateFrames(img)
        Next
    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 "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 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
            cboX.Items.Add(SQCOUNTX.ToString())
        End If
        cboX.SelectedItem = SQCOUNTX.ToString()

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

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

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


    End Sub

#End Region

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

        ' Clear the triggers list
        TriggersList.Clear()

        ' Reset the window title
        Me.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
        recentFiles.Remove(filePath)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        ' Load the selected recent file
        LoadRoomFromFile(filePath)

        ' Update the 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
            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
            MessageBox.Show("Error saving settings: " & ex.Message)
        End Try
    End Sub

    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
            Me.Text = filePath
        Catch ex As Exception
            MessageBox.Show("Error saving room: " & ex.Message)
        End Try
    End Sub

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

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

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

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

            ' Ensure TriggersList is initialized
            If roomData.Triggers Is Nothing Then
                TriggersList = New List(Of Trigger)()
            Else
                TriggersList = roomData.Triggers
            End If

            UpdateSelections()

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

            ' Update the window title to reflect the loaded file path
            Me.Text = filePath

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

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

#End Region

#Region " Design "
    Private Sub LstPremades_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstPremades.SelectedIndexChanged
        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
        Dim saveDialog As New SaveFileDialog() With {
            .InitialDirectory = premadesFolder,
            .Filter = "Premade Files (*.Premade)|*.Premade",
            .DefaultExt = "Premade",
            .AddExtension = True,
            .Title = "Save Premade"
        }
        If saveDialog.ShowDialog() = DialogResult.OK Then
            SaveSettingsToFile(saveDialog.FileName)
            LoadPremades()
        End If
    End Sub

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

        End If
    End Sub

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

    End Sub

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

    End Sub

    Private Sub BtnFloors_Click(sender As Object, e As EventArgs) Handles BtnFloor.Click
        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
        If rdoFloors.Checked AndAlso useGradient Then
            lstHatchstyles.Enabled = False
        Else
            lstHatchstyles.Enabled = True
        End If

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

        ' 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
        If rdoWalls.Checked Then
            UpdateSelections()
        End If
    End Sub

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

    Private Sub RdoEdge_CheckedChanged(sender As Object, e As EventArgs) Handles rdoEdge.CheckedChanged
        If rdoEdge.Checked Then
            UpdateSelections()
        End If
    End Sub
    Private Sub CboStyleChoice_SelectedIndexChanged(sender As Object, e As EventArgs) Handles cboStyleChoice.SelectedIndexChanged
        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
        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
                If x < sourceArray.Length AndAlso y < sourceArray(x).Length Then
                    newArray(x)(y) = sourceArray(x)(y)
                Else
                    If GetType(T) Is GetType(Boolean) AndAlso initializeValue Then
                        newArray(x)(y) = CType(Convert.ChangeType(True, GetType(T)), T)
                    Else
                        newArray(x)(y) = Nothing
                    End If
                End If
            Next
        Next
        Return newArray
    End Function

#End Region

#Region " Mouse Controls "
    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 Form_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
        Dim screenPos As Point = Me.PointToScreen(New Point(e.X, e.Y))
        Dim picGameClientPoint As Point = picGame.PointToClient(screenPos)

        isCursorOnPicGame = picGame.ClientRectangle.Contains(picGameClientPoint)

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

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

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

            ' Invalidate only the picGame control
        End If

        ' Force the highlight color to toggle based on mouse movement
        Dim currentTime As DateTime = DateTime.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
            Me.Capture = False
            Cursor.Current = Cursors.Default

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

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

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

                                If Not IsVerticalWallConnectedToEdge(New Point(highlightedX, highlightedY)) 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, 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

        Dim pic As PictureBox = CType(sender, PictureBox)

        isDraggingItem = True

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

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

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

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

        Cursor.Current = Cursors.Hand

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

    End Sub
    Private Sub SelectTriggerSquare(mouseX As Integer, mouseY As Integer)
        UpdateHighlight(mouseX, mouseY)
        If isMouseInsideGrid Then
            currentTriggerForm.SetSelectedTriggerSquare(New Point(highlightedX, highlightedY))
        End If
    End Sub

    Private Sub SelectActionSquare(mouseX As Integer, mouseY As Integer)
        UpdateHighlight(mouseX, mouseY)
        If isMouseInsideGrid Then
            currentTriggerForm.SetSelectedActionSquare(New Point(highlightedX, highlightedY))
        End If
    End Sub

    Private Sub SelectItemToRemove(mouseX As Integer, mouseY As Integer)
        UpdateHighlight(mouseX, mouseY)
        If isMouseInsideGrid Then
            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
                        Dim itemInfo As New ItemInfo(
                        verticalWallItems(highlightedX)(highlightedY).ImageKey,
                        New Point(highlightedX, highlightedY),
                        $"{GetItemName(verticalWallItems(highlightedX)(highlightedY).ImageKey)}_{highlightedX}_{highlightedY}"
                    )
                        currentTriggerForm.UpdateSelectedItemToRemove(itemInfo.UniqueId)
                    Else
                        currentTriggerForm.UpdateSelectedItemToRemove($"VerticalWall_{highlightedX}_{highlightedY}")
                    End If
                    currentTriggerForm.Show() ' Show the frmTriggerEditor form
                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
                        Dim itemInfo As New ItemInfo(
                        horizontalWallItems(highlightedX)(highlightedY).ImageKey,
                        New Point(highlightedX, highlightedY),
                        $"{GetItemName(horizontalWallItems(highlightedX)(highlightedY).ImageKey)}_{highlightedX}_{highlightedY}"
                    )
                        currentTriggerForm.UpdateSelectedItemToRemove(itemInfo.UniqueId)
                    Else
                        currentTriggerForm.UpdateSelectedItemToRemove($"HorizontalWall_{highlightedX}_{highlightedY}")
                    End If
                    currentTriggerForm.Show() ' Show the frmTriggerEditor form
                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
                        Dim itemInfo As New ItemInfo(
                        placedItems(highlightedX)(highlightedY).ImageKey,
                        New Point(highlightedX, highlightedY),
                        $"{GetItemName(placedItems(highlightedX)(highlightedY).ImageKey)}_{highlightedX}_{highlightedY}"
                    )
                        currentTriggerForm.UpdateSelectedItemToRemove(itemInfo.UniqueId)
                    End If
                    currentTriggerForm.Show() ' Show the frmTriggerEditor form
                End If
            End If
        End If
    End Sub
    Private Sub SelectItem(mouseX As Integer, mouseY As Integer)
        UpdateHighlight(mouseX, mouseY)
        If isMouseInsideGrid Then
            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
                        Dim itemInfo As New ItemInfo(
                        verticalWallItems(highlightedX)(highlightedY).ImageKey,
                        New Point(highlightedX, highlightedY),
                        $"{GetItemName(verticalWallItems(highlightedX)(highlightedY).ImageKey)}_{highlightedX}_{highlightedY}"
                    )
                        currentTriggerForm.UpdateSelectedItemInfo(itemInfo)
                    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
                        Dim itemInfo As New ItemInfo(
                        horizontalWallItems(highlightedX)(highlightedY).ImageKey,
                        New Point(highlightedX, highlightedY),
                        $"{GetItemName(horizontalWallItems(highlightedX)(highlightedY).ImageKey)}_{highlightedX}_{highlightedY}"
                    )
                        currentTriggerForm.UpdateSelectedItemInfo(itemInfo)
                    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
                        Dim itemInfo As New ItemInfo(
                        placedItems(highlightedX)(highlightedY).ImageKey,
                        New Point(highlightedX, highlightedY),
                        $"{GetItemName(placedItems(highlightedX)(highlightedY).ImageKey)}_{highlightedX}_{highlightedY}"
                    )
                        currentTriggerForm.UpdateSelectedItemInfo(itemInfo)
                    End If
                End If
            End If
        End If
    End Sub

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

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

            RaiseEvent ObjectSelected(selected)

            SelectionMode = False
            SelectionRequestedByForm = Nothing
            SelectionRequestedFor = Nothing

            Return
        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.X, e.Y - selectionRectangle.Y)
                    selectionStartCell = New Point(
                    (selectionRectangle.X - INDENT) \ (SQSizeX + WALL_THICKNESS),
                    (selectionRectangle.Y - INDENT) \ (SQSizeY + WALL_THICKNESS))
                    picGame.Capture = True
                    Cursor = Cursors.SizeAll
                ElseIf e.Button = MouseButtons.Right Then
                    ' Show context menu
                    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 = ""

            ElseIf e.Button = MouseButtons.Right Then
                If selectionRectangle.Contains(e.Location) Then
                    Dim menuPosition As Point = e.Location
                    ctmMenu.Show(picGame, menuPosition)
                End If
            End If
        Else
            ' Normal mouse down processing
            If e.Button = MouseButtons.Right Then
                isRightMouseDown = True
                rightMouseDownPosition = e.Location
                rightMouseDownTime = DateTime.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
                        If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
                   verticalWallItems(highlightedX) IsNot Nothing AndAlso
                   highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then

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

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

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

                    If itemPickedUp Then
                        redoStack.Clear()

                    Else
                        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
    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 + 3
                    mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
                Else
                    mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                    mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) - WALL_THICKNESS \ 2 + 3
                End If
            Else
                mouseX = INDENT + highlightedX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeX \ 2
                mouseY = INDENT + highlightedY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2 + SQSizeY \ 2
            End If


        ElseIf isInSelectionMode Then
            If isSelecting Then
                selectionEndPoint = e.Location
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)

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

        ' Ensure the coordinates are within the grid bounds
        squareX = Math.Max(0, Math.Min(SQCOUNTX - 1, squareX))
        squareY = Math.Max(0, Math.Min(SQCOUNTY - 1, squareY))

        ' 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"
        If isOverWall Then
            If highlightedWallX Then
                If highlightedX >= 0 AndAlso highlightedX < verticalWallItems.Length AndAlso
           highlightedY >= 0 AndAlso highlightedY < verticalWallItems(0).Length Then
                    If verticalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                        content = GetItemName(verticalWallItems(highlightedX)(highlightedY).ImageKey)
                    ElseIf verticalWalls(highlightedX)(highlightedY) Then
                        content = "Wall"
                    End If
                End If
            ElseIf highlightedWallY Then
                If highlightedX >= 0 AndAlso highlightedX < horizontalWallItems.Length AndAlso
           highlightedY >= 0 AndAlso highlightedY < horizontalWallItems(0).Length Then
                    If horizontalWallItems(highlightedX)(highlightedY) IsNot Nothing Then
                        content = GetItemName(horizontalWallItems(highlightedX)(highlightedY).ImageKey)
                    ElseIf horizontalWalls(highlightedX)(highlightedY) Then
                        content = "Wall"
                    End If
                End If
            End If
        Else
            If highlightedX >= 0 AndAlso highlightedX < placedItems.Length AndAlso
       highlightedY >= 0 AndAlso highlightedY < placedItems(0).Length Then
                If placedItems(highlightedX)(highlightedY) IsNot Nothing Then
                    content = GetItemName(placedItems(highlightedX)(highlightedY).ImageKey)
                ElseIf floorsUseEdgeStyle(highlightedX)(highlightedY) Then
                    content = "Edge"
                End If
            End If
        End If

        ' Update the status bar text
        If isOverWall Then
            stb.Text = "Location: " & pixelX & ", " & pixelY & " | Wall: " & squareX & ", " & squareY & " | " & content
        Else
            stb.Text = "Location: " & pixelX & ", " & pixelY & " | Square: " & squareX & ", " & squareY & " | " & content
        End If
    End Sub

    Private 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

                ' Use stored cell positions from MouseMove
                Dim newCellX As Integer = draggedSelectionCellX
                Dim newCellY As Integer = draggedSelectionCellY

                ' Ensure the new position is within bounds
                newCellX = Math.Max(0, Math.Min(SQCOUNTX - copiedSelectionData.selectionWidth, newCellX))
                newCellY = Math.Max(0, Math.Min(SQCOUNTY - copiedSelectionData.selectionHeight, newCellY))
                PasteSelectionDataTo(New Point(newCellX, newCellY), 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
                selectionRectangle = GetRectangle(selectionStartPoint, selectionEndPoint)

                ' Snap the selection rectangle to grid cells
                Dim adjustedSelectionRectangle As Rectangle = AdjustSelectionRectangleToGrid(selectionRectangle)

                ' Update selectionRectangle with adjusted values
                selectionRectangle = adjustedSelectionRectangle

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



                If selectionRectangle.IsEmpty Then
                    isInSelectionMode = False
                    Cursor = Cursors.Default
                    selectionRectangle = Rectangle.Empty
                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
                           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
                           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
                    Else
                        ReturnItemToOriginalLocation()
                    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
                    ' Cancel selection mode
                    isInSelectionMode = False
                    isSelecting = False
                    isSelectionReadyToDrag = False
                    selectionRectangle = Rectangle.Empty
                    picGame.Cursor = Cursors.Default

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

#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)
    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)
    End Sub
    Private Sub DrawGridNumbers(g As Graphics)
        Dim xOrigin As Integer = INDENT
        Dim yOrigin As Integer = INDENT
        Dim font As New Font("Arial", 10, FontStyle.Regular)
        Dim brush As New SolidBrush(Color.Black)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            isMouseInsideGrid = True
        End If

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

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

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

        Dim itemName As String = "None"
        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
    End Sub
#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
        UpdateGameColors(colPattern, colPattern.SelectedColor)
    End Sub

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

#Region " Undo, Redo "
    Private Sub SaveStateForUndo()
        If isRestoringState Then Return
        undoStack.Push(New RoomState(Me))
        If undoStack.Count > 50 Then
            undoStack = New Stack(Of RoomState)(undoStack.Reverse().Take(50))
        End If
    End Sub

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

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

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

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

        End If
    End Sub

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

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

        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 BtnFillDeadEnds_Click(sender As Object, e As EventArgs) Handles btnFillDeadEnds.Click
        If Not isRestoringState Then SaveStateForUndo()
        FillDeadEnds()
    End Sub
    Private Sub FillDeadEnds()
        If Not isRestoringState Then SaveStateForUndo()
        Dim wallCount As Integer = 0

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

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

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

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

        PauseBackgroundTasks()

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


    Private Sub btnTriggerEditor_Click(sender As Object, e As EventArgs) Handles btnTriggerEditor.Click
        Dim itemsDictionary As Dictionary(Of String, List(Of ItemInfo)) = CollectItems()
        Dim gridData As New RoomData With {
        .SQCOUNTX = SQCOUNTX,
        .SQCOUNTY = SQCOUNTY
    }
        currentTriggerForm = New frmTriggerEditor(itemsDictionary, gridData, Me, TriggersList)
        currentTriggerForm.ShowDialog(Me) ' Use ShowDialog to ensure it opens centered on the parent form
    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
        Dim itemsDictionary = CollectItems()
        Dim propertiesForm As New frmProperties(itemsDictionary, Me, currentTriggerForm)
        propertiesForm.ShowDialog()
        frmGame.Dispose()
    End Sub


    Private Sub BtnAudio_Click(sender As Object, e As EventArgs) Handles btnAudio.Click, lblAudio.Click
        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
                    MessageBox.Show("Error parsing volume or gain values.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                    Return
                End If
            Else
                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 " Calculations "
    Private Function IsHorizontalWallConnectedToEdge(pt As Point) As Boolean
        Dim x As Integer = pt.X
        Dim y As Integer = pt.Y

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

        Return False
    End Function

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

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

        Return False
    End Function

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

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

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

    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(imgKey As String) As Boolean
        Return imgKey = "picFlipper1" OrElse imgKey = "picFlipper2" OrElse imgKey = "picFlipper3" OrElse imgKey = "picFlipper4"
    End Function

    Private Function IsFlipperItem(item As PlacedItem) As Boolean
        If item Is Nothing Then Return False
        Return item.ImageKey = "picFlipper1" OrElse item.ImageKey = "picFlipper2" OrElse item.ImageKey = "picFlipper3" OrElse item.ImageKey = "picFlipper4"
    End Function

    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

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

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

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

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

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

    Private Sub FrmDesigner_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        SaveRoomToFile(tempFilePath)
    End Sub
#End Region

End Class

' SelectedObject class
Public Class SelectedObject
    Public Property Type As String ' "Square" or "Item"
    Public Property Coordinates As Point? ' Use Point? to allow for Nothing
    Public Property ImageKey As String
End Class

' GameTrigger class
Public Class GameTrigger
    Public Property Condition As TriggerCondition
    Public Property Action As TriggerAction
End Class

' TriggerCondition class
Public Class TriggerCondition
    Public Property Subject As SelectedObject
    Public Property Action As String ' For example, "Enters"
    Public Property ObjectRef As SelectedObject
End Class

' TriggerAction class
Public Class TriggerAction
    Public Property ActionType As String ' "Create" or "Remove"
    Public Property Item As String
    Public Property Target As SelectedObject
End Class

Public Class Trigger
    Public Property ItemType As String
    Public Property TriggerAction As String
    Public Property Action As String
    Public Property ItemToCreate As String
    Public Property TriggerSquare As Point
    Public Property ActionSquare As Point
    Public Property ItemToRemove As String

    Public Overrides Function ToString() As String
        If Action = "Remove" Then
            Return $"When {ItemType} {TriggerAction} Square ({TriggerSquare.X}, {TriggerSquare.Y}), Remove {ItemToRemove} from the game."
        Else
            Return $"When {ItemType} {TriggerAction} Square ({TriggerSquare.X}, {TriggerSquare.Y}), {Action} a {ItemToCreate} at Square ({ActionSquare.X}, {ActionSquare.Y})."
        End If
    End Function
End Class

Imports System.IO
Imports System.Text.Json
Imports System.Text.Json.Serialization
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.ComponentModel
Imports System.Resources
Imports System.Drawing.Imaging
Imports System.Reflection

Public Class frmGame
    Private baddyMovementTimer As Timer
    Private triggers As New List(Of Trigger)()
    Private baddies As New List(Of Baddy)
    Private lastDirection As String = "Left" ' Default to left
    Private lastMoveTime As DateTime
    Private idleTimer As Timer

    Private doorImage As Image
    Private doorAnimationTimer As Timer
    Private doorX As Integer
    Private doorY As Integer
    Private doorAnimationStartTime As DateTime

    Private movingObjects As New List(Of MovingObject)()
    Private movingObjectsTimer As Timer

    Private CollectedCoins As Integer = 0
    Private totalCoins As Integer = 0

    Private playerX As Integer
    Private playerY As Integer
    Private hasKey As Boolean = False
    Private justWarped As Boolean = False
    Public Property RoomFilePath As String
    Private SelectedMusic As String

    Private Const INDENT As Integer = 20
    Private Const WALL_THICKNESS As Integer = 10

    Private SQCOUNTX As Integer
    Private SQCOUNTY As Integer

    Private SQSizeX As Integer
    Private SQSizeY As Integer

    Private wallsHatchStyle As HatchStyle
    Private wallsColor1 As Color
    Private wallsColor2 As Color
    Private floorsHatchStyle As HatchStyle
    Private floorsColor1 As Color
    Private floorsColor2 As Color

    Private verticalWalls()() As Boolean
    Private horizontalWalls()() As Boolean
    Private placedItems()() As String
    Private verticalWallItems()() As String
    Private horizontalWallItems()() As String
    Private floorsUseEdgeStyle()() As Boolean

    Private imageDictionary As New Dictionary(Of String, Image)
    Private animatedImages As New List(Of Image)

    Private animationTimer As Timer
    Private isRoomDataLoaded As Boolean = False

    Private ReadOnly projectDir As String = GetProjectDirectory()
    Private ReadOnly ImagesFolder As String = Path.Combine(projectDir, "Data", "Images")
    Private ReadOnly musicFolder As String = Path.Combine(projectDir, "Data", "Sound/Music")
    Private ReadOnly SFXFolder As String = Path.Combine(projectDir, "Data", "Sound/Sound Effects")
    Private ReadOnly musicManager As New MusicManager()
    Private ReadOnly sfxManager As New SFXManager(SFXFolder)

    Private totalKeys As Integer = 0
    Private collectedKeys As Integer = 0

    Private resourceManager As ResourceManager

    Private itemAnimations As New List(Of ItemAnimation)()
    Private itemAnimationTimer As Timer

    ' New property to determine if gradient background should be used
    Public Property useGradient As Boolean = False

    ' Timer for redrawing
    Private redrawTimer As Timer

    Private Class MovingObject
        Public Property X As Integer
        Public Property Y As Integer
        Public Property DeltaX As Integer
        Public Property DeltaY As Integer
        Public Property IsBall As Boolean
        Public Property IsBlock As Boolean
        Public Property MoveInterval As Integer
        Public Property NextMoveTime As DateTime
    End Class

    Private Class ItemAnimation
        Public Property ItemImage As Image
        Public Property StartX As Integer
        Public Property StartY As Integer
        Public Property EndX As Integer
        Public Property EndY As Integer
        Public Property CurrentX As Integer
        Public Property CurrentY As Integer
        Public Property AnimationDuration As Integer
        Public Property StartTime As DateTime

        Public Sub New(itemImage As Image, startX As Integer, startY As Integer, endX As Integer, endY As Integer, animationDuration As Integer)
            Me.ItemImage = itemImage
            Me.StartX = startX
            Me.StartY = startY
            Me.EndX = endX
            Me.EndY = endY
            Me.CurrentX = startX
            Me.CurrentY = startY
            Me.AnimationDuration = animationDuration
            Me.StartTime = DateTime.Now
        End Sub

        Public Function IsAnimationComplete() As Boolean
            Return (DateTime.Now - StartTime).TotalMilliseconds >= AnimationDuration
        End Function

        Public Sub UpdatePosition()
            Dim elapsedTime As Integer = CInt((DateTime.Now - StartTime).TotalMilliseconds)
            Dim progress As Double = elapsedTime / AnimationDuration
            CurrentX = StartX + CInt((EndX - StartX) * progress)
            CurrentY = StartY + CInt((EndY - StartY) * progress)
        End Sub
    End Class

    Friend Class Baddy
        Public Property X As Integer
        Public Property Y As Integer
        Public Property CurrentDirection As String
        Public Property ImageKey As String

        Public Sub New(startX As Integer, startY As Integer, imageKey As String)
            Me.X = startX
            Me.Y = startY
            Me.CurrentDirection = "Right" ' Start moving to the right
            Me.ImageKey = imageKey
        End Sub
    End Class

#Region "Initialization"
    Public Sub New()
        InitializeComponent()
        resourceManager = New ResourceManager("Enigma.frmGame", GetType(frmGame).Assembly)
        InitializeSoundEffects()

        ' Enable double buffering for the form
        Me.DoubleBuffered = True

        ' Enable double buffering for the PictureBox
        EnableDoubleBuffering(picTest)

        ' Initialize the idle timer
        idleTimer = New Timer() With {.Interval = 250} ' 1 second
        AddHandler idleTimer.Tick, AddressOf OnIdleTick
        idleTimer.Start()
    End Sub

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

    Private Sub frmGame_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        InitializeImages()
        LoadRoomData()
        StartAnimationTimer()
        FindPlayerStartPosition()
        AddObjectsOnFlippersToMovingObjects()
        PlaySelectedMusic()
        InitializeTriggers() ' Initialize triggers

        ' Initialize the redraw timer
        redrawTimer = New Timer() With {.Interval = 50} ' 20 times a second
        AddHandler redrawTimer.Tick, AddressOf OnRedrawTick
        redrawTimer.Start()

        itemAnimationTimer = New Timer() With {.Interval = 16} ' Approximately 60 FPS
        AddHandler itemAnimationTimer.Tick, AddressOf OnItemAnimationTick
        itemAnimationTimer.Start()
    End Sub
    Private Sub InitializeTriggers()
        ' Example trigger for testing
        Dim trigger As New Trigger With {
        .ItemType = "Any Block",
        .TriggerAction = "Enters",
        .TriggerSquare = New Point(2, 4),
        .Action = "Remove",
        .ItemToRemove = "BlockStopper_4_8"
    }
        triggers.Add(trigger)
    End Sub
    Private Sub InitializeImages()
        Try
            ' Load images into the imageDictionary
            imageDictionary("picBlock1") = Image.FromFile(Path.Combine(ImagesFolder, "Block.png"))
            imageDictionary("picBlockSwitch") = Image.FromFile(Path.Combine(ImagesFolder, "BlockSwitch.png"))
            imageDictionary("picBlockSwitch2") = Image.FromFile(Path.Combine(ImagesFolder, "BlockSwitch2.png"))
            imageDictionary("picBall") = Image.FromFile(Path.Combine(ImagesFolder, "Ball.gif"))
            imageDictionary("picCoin") = Image.FromFile(Path.Combine(ImagesFolder, "Coin.gif"))
            imageDictionary("picFinish") = Image.FromFile(Path.Combine(ImagesFolder, "Finish.gif"))
            imageDictionary("picFlipper1") = Image.FromFile(Path.Combine(ImagesFolder, "Flipper1.gif"))
            imageDictionary("picFlipper2") = Image.FromFile(Path.Combine(ImagesFolder, "Flipper2.gif"))
            imageDictionary("picFlipper3") = Image.FromFile(Path.Combine(ImagesFolder, "Flipper3.gif"))
            imageDictionary("picFlipper4") = Image.FromFile(Path.Combine(ImagesFolder, "Flipper4.gif"))
            imageDictionary("picGate") = Image.FromFile(Path.Combine(ImagesFolder, "Gate.gif"))
            imageDictionary("picKey") = Image.FromFile(Path.Combine(ImagesFolder, "Key.gif"))
            imageDictionary("picLaser1") = Image.FromFile(Path.Combine(ImagesFolder, "Laser1.gif"))
            imageDictionary("picLaser2") = Image.FromFile(Path.Combine(ImagesFolder, "Laser2.gif"))
            imageDictionary("picWarp") = Image.FromFile(Path.Combine(ImagesFolder, "Warp.gif"))
            imageDictionary("picWarp2") = Image.FromFile(Path.Combine(ImagesFolder, "Warp2.gif"))
            imageDictionary("picStart") = Image.FromFile(Path.Combine(ImagesFolder, "Guy.gif"))
            imageDictionary("picStartLeft") = Image.FromFile(Path.Combine(ImagesFolder, "Guy Left.gif"))
            imageDictionary("picStartRight") = Image.FromFile(Path.Combine(ImagesFolder, "Guy Right.gif"))
            imageDictionary("picBlockStopper1") = Image.FromFile(Path.Combine(ImagesFolder, "Debris1.png"))
            imageDictionary("picBaddy1") = Image.FromFile(Path.Combine(ImagesFolder, "Baddy1.gif"))
            imageDictionary("picBaddy2") = Image.FromFile(Path.Combine(ImagesFolder, "Baddy2.gif"))
            imageDictionary("picBaddy3") = Image.FromFile(Path.Combine(ImagesFolder, "Baddy3.gif"))
            imageDictionary("picBaddy4") = Image.FromFile(Path.Combine(ImagesFolder, "Baddy4.gif"))
            imageDictionary("picExplosion") = Image.FromFile(Path.Combine(ImagesFolder, "Explosion.gif")) ' Load explosion image

            ' Extract the first frame of the door image
            doorImage = ExtractFirstFrame(imageDictionary("picGate"))
            Dim animatedImageKeys As String() = {
    "picLaser1", "picLaser2", "picFlipper1", "picFlipper2",
    "picFlipper3", "picFlipper4", "picCoin", "picGate",
    "picKey", "picFinish", "picBall", "picWarp", "picWarp2", "picStart", "picStartLeft", "picStartRight",
    "picBaddy1", "picBaddy2", "picBaddy3", "picBaddy4", "picExplosion" ' Add explosion image to animated images
}

            For Each imgKey In animatedImageKeys
                If imageDictionary.ContainsKey(imgKey) Then
                    Dim img As Image = imageDictionary(imgKey)
                    animatedImages.Add(img)
                    ImageAnimator.Animate(img, AddressOf OnFrameChanged)
                End If
            Next
        Catch ex As Exception
            MessageBox.Show("Error loading images: " & ex.Message)
        End Try
    End Sub

    Private Function ExtractFirstFrame(animatedImage As Image) As Image
        Dim frameDimensions As New FrameDimension(animatedImage.FrameDimensionsList(0))
        animatedImage.SelectActiveFrame(frameDimensions, 0)
        Return New Bitmap(animatedImage)
    End Function

    Private Sub InitializeSoundEffects()
        ' sfxManager is already initialized in the constructor
    End Sub

    Private Sub LoadRoomData()
        If Not String.IsNullOrEmpty(RoomFilePath) AndAlso File.Exists(RoomFilePath) Then
            LoadRoomFromFile(RoomFilePath)
            AddBaddiesOnGrid() ' Add this method to place baddies
            StartBaddyMovementTimer() ' Start moving baddies
        Else
            MessageBox.Show("Room file not found.")
            Me.Close()
        End If
    End Sub

    Private Sub StartAnimationTimer()
        animationTimer = New Timer() With {.Interval = 33} ' Approximately 30 FPS
        AddHandler animationTimer.Tick, AddressOf OnAnimationTick
        animationTimer.Start()
    End Sub

    Private Sub FindPlayerStartPosition()
        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
                    Dim imageKey As String = placedItems(x)(y)
                    If imageKey = "picStart" Then
                        playerX = x
                        playerY = y
                        Exit Sub
                    End If
                Next
            End If
        Next
    End Sub

    Private Sub AddObjectsOnFlippersToMovingObjects()
        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY - 1
                Dim item As String = placedItems(x)(y)
                If item = "picBlock1" OrElse item = "picBall" Then
                    Dim movingObject As New MovingObject With {
                .X = x,
                .Y = y,
                .IsBlock = (item = "picBlock1"),
                .IsBall = (item = "picBall"),
                .DeltaX = 0,
                .DeltaY = 0
            }

                    CheckForFlipper(movingObject)

                    If movingObject.DeltaX <> 0 OrElse movingObject.DeltaY <> 0 Then
                        placedItems(x)(y) = Nothing
                        movingObject.MoveInterval = If(movingObject.IsBall, 100, 50)
                        movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
                        movingObjects.Add(movingObject)
                    End If
                End If
            Next
        Next

        If movingObjects.Count > 0 AndAlso movingObjectsTimer Is Nothing Then
            movingObjectsTimer = New Timer()
            AddHandler movingObjectsTimer.Tick, AddressOf OnMovingObjectsTimerTick
            movingObjectsTimer.Interval = 50 ' Ensure the interval is appropriate
            movingObjectsTimer.Start()
        End If
    End Sub

    Private Sub PlaySelectedMusic()
        If Not String.IsNullOrEmpty(SelectedMusic) Then
            Dim musicDetails As String() = SelectedMusic.Split(", ")
            Dim musicFileName As String = musicDetails(0)
            Dim volumeLevel As Single = Integer.Parse(musicDetails(1).Split(":")(1)) / 100.0F
            Dim gainLevel As Single = Integer.Parse(musicDetails(2).Split(":")(1)) / 10.0F

            Dim musicFilePath As String = Path.Combine(musicFolder, musicFileName)

            musicManager.PlaySelectedMusic(musicFilePath, volumeLevel, gainLevel)
        End If
    End Sub

#End Region

#Region " Baddies "
    Private Sub AddBaddiesOnGrid()
        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY - 1
                If placedItems(x)(y) IsNot Nothing Then
                    Dim imageKey As String = placedItems(x)(y)
                    If imageKey.StartsWith("picBaddy") Then
                        Dim baddy As New Baddy(x, y, imageKey) ' Pass the imageKey here
                        baddies.Add(baddy) ' Add baddy to the list
                        placedItems(x)(y) = Nothing ' Clear the starting point
                        Debug.WriteLine($"Baddy added at ({x}, {y})")
                    End If
                End If
            Next
        Next
    End Sub

    Private Sub StartBaddyMovementTimer()
        baddyMovementTimer = New Timer() With {.Interval = 250} ' Adjust speed as needed
        AddHandler baddyMovementTimer.Tick, AddressOf OnBaddyMovementTick
        baddyMovementTimer.Start()
    End Sub

    Private Sub OnBaddyMovementTick(sender As Object, e As EventArgs)
        ' Iterate over the baddies list in reverse order to safely remove baddies
        For i As Integer = baddies.Count - 1 To 0 Step -1
            Try
                Dim baddy As Baddy = baddies(i)

                ' Check if the baddy can move
                If Not MoveBaddy(baddy) Then
                    ' Baddy is trapped, remove it from the list
                    Console.WriteLine($"Removing baddy at index: {i}, Current count: {baddies.Count}") ' Log for debugging
                    baddies.RemoveAt(i) ' Remove by index
                End If
            Catch ex As ArgumentOutOfRangeException
                Console.WriteLine($"Caught ArgumentOutOfRangeException! Index: {i}, Current count: {baddies.Count}. Message: {ex.Message}")
            End Try
        Next

        picTest.Invalidate() ' Refresh the display to show updated baddy positions
    End Sub
    Private Function MoveBaddy(baddy As Baddy) As Boolean
        Dim nextX As Integer = baddy.X
        Dim nextY As Integer = baddy.Y

        ' Attempt to move in the current direction
        Select Case baddy.CurrentDirection
            Case "Right"
                nextX += 1
                If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                    ' Try down if right is blocked
                    nextX = baddy.X
                    nextY += 1
                    If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                        ' Try up if down is blocked
                        nextY = baddy.Y - 1
                        If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                            ' Finally try left
                            nextX = baddy.X - 1
                            nextY = baddy.Y
                            baddy.CurrentDirection = "Left" ' Update direction
                        Else
                            baddy.CurrentDirection = "Up" ' Update direction
                        End If
                    Else
                        baddy.CurrentDirection = "Down" ' Update direction
                    End If
                End If

            Case "Down"
                nextY += 1
                If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                    ' Try left if down is blocked
                    nextY = baddy.Y
                    nextX -= 1
                    If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                        ' Try right if left is blocked
                        nextX = baddy.X + 1
                        If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                            ' Finally try up
                            nextY = baddy.Y - 1
                            nextX = baddy.X
                            baddy.CurrentDirection = "Up" ' Update direction
                        Else
                            baddy.CurrentDirection = "Right" ' Update direction
                        End If
                    Else
                        baddy.CurrentDirection = "Left" ' Update direction
                    End If
                End If

            Case "Left"
                nextX -= 1
                If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                    ' Try up if left is blocked
                    nextX = baddy.X
                    nextY -= 1
                    If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                        ' Try down if up is blocked
                        nextY = baddy.Y + 1
                        If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                            ' Finally try right
                            nextX = baddy.X + 1
                            nextY = baddy.Y
                            baddy.CurrentDirection = "Right" ' Update direction
                        Else
                            baddy.CurrentDirection = "Down" ' Update direction
                        End If
                    Else
                        baddy.CurrentDirection = "Up" ' Update direction
                    End If
                End If

            Case "Up"
                nextY -= 1
                If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                    ' Try right if up is blocked
                    nextY = baddy.Y
                    nextX += 1
                    If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                        ' Try left if right is blocked
                        nextX = baddy.X - 1
                        If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
                            ' Finally try down
                            nextY = baddy.Y + 1
                            nextX = baddy.X
                            baddy.CurrentDirection = "Down" ' Update direction
                        Else
                            baddy.CurrentDirection = "Left" ' Update direction
                        End If
                    Else
                        baddy.CurrentDirection = "Right" ' Update direction
                    End If
                End If
        End Select

        ' Now perform the move if the next position is valid
        If CanBaddyMoveTo(baddy, nextX, nextY) Then
            baddy.X = nextX
            baddy.Y = nextY
            Return True ' Return true to indicate that the baddy is still active
        Else
            ' If baddy cannot move, it might be trapped
            If trapped(baddy) Then
                Return False ' This indicates the baddy should be removed
            End If
        End If

        Return True ' Return true to indicate that the baddy is still active
    End Function

    Private Function CanBaddyMoveTo(baddy As Baddy, newX As Integer, newY As Integer) As Boolean
        ' Check boundaries
        If newX < 0 OrElse newX >= SQCOUNTX OrElse newY < 0 OrElse newY >= SQCOUNTY Then
            Return False
        End If

        ' Check for walls in the way
        If newX > baddy.X Then
            ' Moving right
            If verticalWalls(newX)(newY) Then
                Return False
            End If
        ElseIf newX < baddy.X Then
            ' Moving left
            If verticalWalls(newX + 1)(newY) Then
                Return False
            End If
        End If

        If newY > baddy.Y Then
            ' Moving down
            If horizontalWalls(newX)(newY) Then
                Return False
            End If
        ElseIf newY < baddy.Y Then
            ' Moving up
            If horizontalWalls(newX)(newY + 1) Then
                Return False
            End If
        End If

        ' Check for wall items (lasers and flippers)
        If newX > baddy.X Then
            ' Moving right
            If verticalWallItems(newX)(newY) IsNot Nothing Then
                Return False
            End If
        ElseIf newX < baddy.X Then
            ' Moving left
            If verticalWallItems(newX + 1)(newY) IsNot Nothing Then
                Return False
            End If
        End If

        If newY > baddy.Y Then
            ' Moving down
            If horizontalWallItems(newX)(newY) IsNot Nothing Then
                Return False
            End If
        ElseIf newY < baddy.Y Then
            ' Moving up
            If horizontalWallItems(newX)(newY + 1) IsNot Nothing Then
                Return False
            End If
        End If

        ' Check for other baddies occupying the new space
        If baddies.Any(Function(b) b.X = newX AndAlso b.Y = newY) Then
            Return False
        End If

        ' Check if the new position is clear (no items or other obstacles)
        If placedItems(newX)(newY) IsNot Nothing Then
            Return False
        End If

        ' If all checks passed, return true
        Return True
    End Function

    Private Function trapped(baddy As Baddy) As Boolean
        ' Simple definition of trapped
        Dim nextX As Integer = baddy.X
        Dim nextY As Integer = baddy.Y
        Select Case baddy.CurrentDirection
            Case "Right"
                nextX += 1
            Case "Down"
                nextY += 1
            Case "Left"
                nextX -= 1
            Case "Up"
                nextY -= 1
        End Select

        If Not CanBaddyMoveTo(baddy, nextX, nextY) Then
            ' Baddy is trapped
            sfxManager.PlaySoundEffect("Explosion") ' Play explosion sound

            ' Remove the baddy from the list
            baddies.Remove(baddy)

            ' Place a key where the baddy was trapped
            placedItems(baddy.X)(baddy.Y) = "picKey"

            ' Start explosion animation
            Dim explosionImage As Image = imageDictionary("picExplosion")
            Dim startX As Integer = INDENT + baddy.X * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim startY As Integer = INDENT + baddy.Y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim animationDuration As Integer = 1000 ' 1 second animation
            Dim explosionAnimation As New ItemAnimation(explosionImage, startX, startY, startX, startY, animationDuration)
            itemAnimations.Add(explosionAnimation)

            Return True
        End If

        Return False
    End Function
#End Region


#Region "Movement"
    Private Sub frmGame_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown
        Dim moved As Boolean = False
        Select Case e.KeyCode
            Case Keys.Up, Keys.P
                moved = AttemptMove("Up")
            Case Keys.Down, Keys.L
                moved = AttemptMove("Down")
            Case Keys.Left, Keys.Z
                moved = AttemptMove("Left")
            Case Keys.Right, Keys.X
                moved = AttemptMove("Right")
        End Select
        If moved Then
            lastMoveTime = DateTime.Now
            ' No need to invalidate here, the redraw timer will handle it
        End If

        ' Stop the idle timer when a key is pressed
        If idleTimer IsNot Nothing Then
            idleTimer.Stop()
        End If
    End Sub
    Private Sub frmGame_KeyUp(sender As Object, e As KeyEventArgs) Handles Me.KeyUp
        ' Start the idle timer immediately when a key is released
        idleTimer.Start()
    End Sub

    Private Function AttemptMove(direction As String) As Boolean
        Dim deltaX As Integer = 0
        Dim deltaY As Integer = 0
        Select Case direction
            Case "Up"
                deltaY = -1
            Case "Down"
                deltaY = 1
            Case "Left"
                deltaX = -1
                lastDirection = "Left"
            Case "Right"
                deltaX = 1
                lastDirection = "Right"
        End Select

        Dim newX As Integer = playerX + deltaX
        Dim newY As Integer = playerY + deltaY

        If newX < 0 OrElse newX >= SQCOUNTX OrElse newY < 0 OrElse newY >= SQCOUNTY Then
            Return False
        End If

        If Not CanMoveTo(newX, newY, deltaX, deltaY) Then
            Return False
        End If

        Dim targetItem As String = placedItems(newX)(newY)

        If String.IsNullOrEmpty(targetItem) Then
            ' Remove the starting point once the player moves
            If placedItems(playerX)(playerY) <> "picBlockStopper1" AndAlso placedItems(playerX)(playerY) <> "picWarp" AndAlso placedItems(playerX)(playerY) <> "picWarp2" Then
                placedItems(playerX)(playerY) = Nothing
            End If

            playerX = newX
            playerY = newY
            PlayRandomMoveSound() ' Play one of the move sounds randomly.
            CheckForWarp()
            Return True
        ElseIf targetItem = "picBlock1" Then
            Dim blockMoved As Boolean = AttemptPushBlock(newX, newY, deltaX, deltaY)
            If blockMoved Then
                ' Remove the starting point once the player moves
                If placedItems(playerX)(playerY) <> "picBlockStopper1" AndAlso placedItems(playerX)(playerY) <> "picWarp" AndAlso placedItems(playerX)(playerY) <> "picWarp2" Then
                    placedItems(playerX)(playerY) = Nothing
                End If

                playerX = newX
                playerY = newY
                PlayRandomMoveSound() ' Play one of the move sounds randomly.
                CheckForWarp()
                Return True
            Else
                Return False
            End If
        ElseIf targetItem = "picBlockStopper1" Then
            ' Do not remove the block stopper
            playerX = newX
            playerY = newY
            PlayRandomMoveSound() ' Play one of the move sounds randomly.
            CheckForWarp()
            Return True
        ElseIf targetItem = "picBall" Then
            Dim ballMoved As Boolean = AttemptPushBall(newX, newY, deltaX, deltaY)
            If ballMoved Then
                ' Remove the starting point once the player moves
                If placedItems(playerX)(playerY) <> "picBlockStopper1" AndAlso placedItems(playerX)(playerY) <> "picWarp" AndAlso placedItems(playerX)(playerY) <> "picWarp2" Then
                    placedItems(playerX)(playerY) = Nothing
                End If

                playerX = newX
                playerY = newY
                PlayRandomMoveSound() ' Play one of the move sounds randomly.
                CheckForWarp()
                Return True
            Else
                Return False
            End If
        ElseIf targetItem = "picCoin" Then
            CollectedCoins += 1
            placedItems(newX)(newY) = Nothing
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Coin")
            PlayRandomMoveSound() ' Play one of the move sounds randomly.
            UpdateCoinLabel()

            ' Start coin animation
            Dim coinImage As Image = imageDictionary("picCoin")
            Dim startX As Integer = INDENT + newX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim startY As Integer = INDENT + newY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim endX As Integer = lblCoinCount.Bounds.Left + lblCoinCount.Bounds.Width \ 2 - coinImage.Width \ 2
            Dim endY As Integer = lblCoinCount.Bounds.Top + lblCoinCount.Bounds.Height \ 2 - coinImage.Height \ 2
            Dim animationDuration As Integer = 1000 ' 1 second animation
            Dim coinAnimation As New ItemAnimation(coinImage, startX, startY, endX, endY, animationDuration)
            itemAnimations.Add(coinAnimation)

            ' Remove the starting point once the player moves
            If placedItems(playerX)(playerY) <> "picBlockStopper1" AndAlso placedItems(playerX)(playerY) <> "picWarp" AndAlso placedItems(playerX)(playerY) <> "picWarp2" Then
                placedItems(playerX)(playerY) = Nothing
            End If

            CheckForWarp()
            Return True
        ElseIf targetItem = "picKey" Then
            collectedKeys += 1
            placedItems(newX)(newY) = Nothing
            playerX = newX
            playerY = newY
            sfxManager.PlaySoundEffect("Key")
            PlayRandomMoveSound() ' Play one of the move sounds randomly.
            UpdateKeyLabel()

            ' Start key animation
            Dim keyImage As Image = imageDictionary("picKey")
            Dim startX As Integer = INDENT + newX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim startY As Integer = INDENT + newY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim endX As Integer = lblKeyCount.Bounds.Left + lblKeyCount.Bounds.Width \ 2 - keyImage.Width \ 2
            Dim endY As Integer = lblKeyCount.Bounds.Top + lblKeyCount.Bounds.Height \ 2 - keyImage.Height \ 2
            Dim animationDuration As Integer = 1000 ' 1 second animation
            Dim keyAnimation As New ItemAnimation(keyImage, startX, startY, endX, endY, animationDuration)
            itemAnimations.Add(keyAnimation)

            ' Remove the starting point once the player moves
            If placedItems(playerX)(playerY) <> "picBlockStopper1" AndAlso placedItems(playerX)(playerY) <> "picWarp" AndAlso placedItems(playerX)(playerY) <> "picWarp2" Then
                placedItems(playerX)(playerY) = Nothing
            End If

            CheckForWarp()
            Return True
        ElseIf targetItem = "picGate" Then
            If collectedKeys = totalKeys Then
                UnlockDoor(newX, newY)
                PlayRandomMoveSound() ' Play one of the move sounds randomly.
                Return False
            Else
                Return False
            End If
        ElseIf targetItem = "picFinish" Then
            playerX = newX
            playerY = newY
            PlayRandomMoveSound() ' Play one of the move sounds randomly.
            GameWon()
            Return True
        ElseIf targetItem = "picWarp" Then
            playerX = newX
            playerY = newY
            PlayRandomMoveSound() ' Play one of the move sounds randomly.
            HandleWarp("picWarp")
            Return True
        ElseIf targetItem = "picWarp2" Then
            playerX = newX
            playerY = newY
            PlayRandomMoveSound() ' Play one of the move sounds randomly.
            HandleWarp("picWarp2")
            Return True
        ElseIf targetItem = "picBlockSwitch" OrElse targetItem = "picBlockSwitch2" Then
            ' Handle BlockSwitch logic
            HandleBlockSwitch(newX, newY, targetItem)
            playerX = newX
            playerY = newY
            PlayRandomMoveSound() ' Play one of the move sounds randomly.
            CheckForWarp()
            Return True
        Else
            Return False
        End If

        ' Remove the starting point once the player moves, if applicable
        If placedItems(playerX)(playerY) <> "picBlockStopper1" AndAlso placedItems(playerX)(playerY) <> "picWarp" AndAlso placedItems(playerX)(playerY) <> "picWarp2" Then
            placedItems(playerX)(playerY) = Nothing
        End If
    End Function

    Private Sub HandleBlockSwitch(x As Integer, y As Integer, switchType As String)
        ' Implement the logic for handling BlockSwitch
        ' For example, you can toggle walls, open doors, etc.
        If switchType = "picBlockSwitch" Then
            ' Example: Toggle vertical walls
            If x > 0 AndAlso verticalWalls(x - 1)(y) Then
                verticalWalls(x - 1)(y) = False
            ElseIf x < SQCOUNTX - 1 AndAlso verticalWalls(x + 1)(y) Then
                verticalWalls(x + 1)(y) = False
            End If
        ElseIf switchType = "picBlockSwitch2" Then
            ' Example: Toggle horizontal walls
            If y > 0 AndAlso horizontalWalls(x)(y - 1) Then
                horizontalWalls(x)(y - 1) = False
            ElseIf y < SQCOUNTY - 1 AndAlso horizontalWalls(x)(y + 1) Then
                horizontalWalls(x)(y + 1) = False
            End If
        End If
        ' Add more logic as needed
    End Sub
    Private Function AttemptPushBlock(blockX As Integer, blockY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Dim blockNewX As Integer = blockX + deltaX
        Dim blockNewY As Integer = blockY + deltaY

        If blockNewX < 0 OrElse blockNewX >= SQCOUNTX OrElse blockNewY < 0 OrElse blockNewY >= SQCOUNTY Then
            Return False
        End If

        If Not CanBlockMoveTo(blockX, blockY, deltaX, deltaY) Then
            Return False
        End If

        Dim blockTargetItem As String = placedItems(blockNewX)(blockNewY)
        Dim playerAtNextPos As Boolean = (blockNewX = playerX AndAlso blockNewY = playerY)

        ' Check for baddies at the new position
        If baddies.Any(Function(b) b.X = blockNewX AndAlso b.Y = blockNewY) Then
            Return False
        End If

        If String.IsNullOrEmpty(blockTargetItem) AndAlso Not playerAtNextPos Then
            placedItems(blockX)(blockY) = Nothing
            placedItems(blockNewX)(blockNewY) = "picBlock1"

            sfxManager.PlaySoundEffect("Push Block")

            Dim newBlock As New MovingObject With {
    .X = blockNewX,
    .Y = blockNewY,
    .IsBlock = True,
    .IsBall = False,
    .DeltaX = 0,
    .DeltaY = 0
}

            CheckForFlipper(newBlock)
            If newBlock.DeltaX <> 0 OrElse newBlock.DeltaY <> 0 Then
                placedItems(blockNewX)(blockNewY) = Nothing
                newBlock.MoveInterval = 50
                newBlock.NextMoveTime = DateTime.Now.AddMilliseconds(50)
                movingObjects.Add(newBlock)

                If movingObjectsTimer Is Nothing Then
                    movingObjectsTimer = New Timer()
                    AddHandler movingObjectsTimer.Tick, AddressOf OnMovingObjectsTimerTick
                    movingObjectsTimer.Interval = 50
                    movingObjectsTimer.Start()
                End If
            End If

            Return True
        ElseIf blockTargetItem = "picBlockSwitch" OrElse blockTargetItem = "picBlockSwitch2" Then
            ' Handle BlockSwitch logic
            HandleBlockSwitch(blockNewX, blockNewY, blockTargetItem)
            placedItems(blockX)(blockY) = Nothing
            placedItems(blockNewX)(blockNewY) = "picBlock1"

            sfxManager.PlaySoundEffect("Push Block")

            Dim newBlock As New MovingObject With {
    .X = blockNewX,
    .Y = blockNewY,
    .IsBlock = True,
    .IsBall = False,
    .DeltaX = 0,
    .DeltaY = 0
}

            CheckForFlipper(newBlock)
            If newBlock.DeltaX <> 0 OrElse newBlock.DeltaY <> 0 Then
                placedItems(blockNewX)(blockNewY) = Nothing
                newBlock.MoveInterval = 50
                newBlock.NextMoveTime = DateTime.Now.AddMilliseconds(50)
                movingObjects.Add(newBlock)

                If movingObjectsTimer Is Nothing Then
                    movingObjectsTimer = New Timer()
                    AddHandler movingObjectsTimer.Tick, AddressOf OnMovingObjectsTimerTick
                    movingObjectsTimer.Interval = 50
                    movingObjectsTimer.Start()
                End If
            End If

            Return True
        Else
            Return False
        End If
    End Function

    Private Sub PlayRandomMoveSound()
        Dim moveSounds As String() = {"Move 1", "Move 2", "Move 3"}
        Dim randomIndex As Integer = New Random().Next(moveSounds.Length)
        Dim selectedSound As String = moveSounds(randomIndex)

        sfxManager.PlaySoundEffect(selectedSound)
    End Sub

    Private Sub UnlockDoor(doorX As Integer, doorY As Integer)
        ' Store the door position
        Me.doorX = doorX
        Me.doorY = doorY

        ' Start the door animation timer
        If doorAnimationTimer Is Nothing Then
            doorAnimationTimer = New Timer()
            AddHandler doorAnimationTimer.Tick, AddressOf OnDoorAnimationTick
            doorAnimationTimer.Interval = 50 ' Adjust the interval as needed
        End If
        doorAnimationTimer.Start()

        ' Record the start time of the door animation
        doorAnimationStartTime = DateTime.Now

        ' Play the door open sound effect
        sfxManager.PlaySoundEffect("Door Open")
    End Sub

    Private Sub OnDoorAnimationTick(sender As Object, e As EventArgs)
        ' Update the door animation frame
        Dim doorImage As Image = imageDictionary("picGate")
        ImageAnimator.UpdateFrames(doorImage)

        ' Check if one second has passed since the animation started
        If (DateTime.Now - doorAnimationStartTime).TotalSeconds >= 1 Then
            ' Stop the timer
            doorAnimationTimer.Stop()

            ' Remove the door from the grid
            placedItems(doorX)(doorY) = Nothing
            ' No need to invalidate here, the redraw timer will handle it
        End If
    End Sub

    Private Sub CheckForWarp()
        Dim currentItem As String = placedItems(playerX)(playerY)
        If currentItem = "picWarp" AndAlso Not justWarped Then
            HandleWarp("picWarp")
        ElseIf currentItem = "picWarp2" AndAlso Not justWarped Then
            HandleWarp("picWarp2")
        ElseIf currentItem <> "picWarp" AndAlso currentItem <> "picWarp2" Then
            justWarped = False
        End If
    End Sub

    Private Sub HandleWarp(warpType As String)
        If Not justWarped Then
            Dim warpPositions As New List(Of Point)
            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) = warpType Then
                            warpPositions.Add(New Point(x, y))
                        End If
                    Next
                End If
            Next
            warpPositions.Remove(New Point(playerX, playerY))
            If warpPositions.Count = 1 Then
                justWarped = True
                sfxManager.PlaySoundEffect("Warp")
                playerX = warpPositions(0).X
                playerY = warpPositions(0).Y
                CheckForWarp()
            End If
        End If
    End Sub

    Private Function AttemptPushBall(ballX As Integer, ballY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Dim nextX As Integer = ballX + deltaX
        Dim nextY As Integer = ballY + deltaY

        If nextX < 0 OrElse nextX >= SQCOUNTX OrElse nextY < 0 OrElse nextY >= SQCOUNTY Then
            Return False
        End If

        If Not CanBallMoveTo(ballX, ballY, deltaX, deltaY) Then
            Return False
        End If

        Dim targetItem As String = placedItems(nextX)(nextY)
        Dim playerAtNextPos As Boolean = (nextX = playerX AndAlso nextY = playerY)

        If String.IsNullOrEmpty(targetItem) AndAlso Not playerAtNextPos Then
            placedItems(ballX)(ballY) = Nothing

            Dim newBall As New MovingObject With {
            .X = ballX,
            .Y = ballY,
            .DeltaX = deltaX,
            .DeltaY = deltaY,
            .IsBall = True,
            .IsBlock = False,
            .MoveInterval = 100,
            .NextMoveTime = DateTime.Now.AddMilliseconds(100)
        }

            movingObjects.Add(newBall)

            If movingObjectsTimer Is Nothing Then
                movingObjectsTimer = New Timer()
                AddHandler movingObjectsTimer.Tick, AddressOf OnMovingObjectsTimerTick
                movingObjectsTimer.Interval = 50
                movingObjectsTimer.Start()
            End If

            sfxManager.PlaySoundEffect("Push Ball")

            Return True
        Else
            Return False
        End If
    End Function

    Private Sub OnMovingObjectsTimerTick(sender As Object, e As EventArgs)
        Dim currentTime As DateTime = DateTime.Now

        For i As Integer = movingObjects.Count - 1 To 0 Step -1
            Dim movingObject = movingObjects(i)

            If movingObject.NextMoveTime > currentTime Then
                Continue For
            End If

            Dim deltaX As Integer = movingObject.DeltaX
            Dim deltaY As Integer = movingObject.DeltaY

            Dim currentX As Integer = movingObject.X
            Dim currentY As Integer = movingObject.Y
            Dim objectStopped As Boolean = False

            Dim nextX As Integer = currentX + deltaX
            Dim nextY As Integer = currentY + deltaY

            If nextX < 0 OrElse nextX >= SQCOUNTX OrElse nextY < 0 OrElse nextY >= SQCOUNTY Then
                objectStopped = True
            Else
                If Not CanObjectMoveTo(movingObject, currentX, currentY, deltaX, deltaY) Then
                    objectStopped = True
                Else
                    Dim targetItem As String = placedItems(nextX)(nextY)
                    Dim playerAtNextPos As Boolean = (nextX = playerX AndAlso nextY = playerY)
                    Dim objectAtNextPos As Boolean = movingObjects.Any(Function(o) o.X = nextX AndAlso o.Y = nextY AndAlso o IsNot movingObject)

                    If String.IsNullOrEmpty(targetItem) AndAlso Not playerAtNextPos AndAlso Not objectAtNextPos Then
                        movingObject.X = nextX
                        movingObject.Y = nextY
                        movingObject.NextMoveTime = currentTime.AddMilliseconds(movingObject.MoveInterval)

                        CheckForFlipper(movingObject)
                    Else
                        objectStopped = True
                    End If
                End If
            End If

            If objectStopped Then
                If IsObjectOnFlipper(movingObject) Then
                    movingObject.NextMoveTime = currentTime.AddMilliseconds(movingObject.MoveInterval)
                    CheckForFlipper(movingObject)
                Else
                    If movingObject.IsBall Then
                        placedItems(movingObject.X)(movingObject.Y) = "picBall"
                    ElseIf movingObject.IsBlock Then
                        placedItems(movingObject.X)(movingObject.Y) = "picBlock1"
                    End If
                    sfxManager.PlaySoundEffect("Block Stop")
                    movingObjects.RemoveAt(i)
                End If
            End If
        Next
    End Sub
    Private Function IsObjectOnFlipper(movingObject As MovingObject) As Boolean
        Dim x As Integer = movingObject.X
        Dim y As Integer = movingObject.Y

        ' Left Flipper Check (Flipper2)
        If x >= 0 AndAlso x < verticalWallItems.Length AndAlso
       y >= 0 AndAlso y < verticalWallItems(x).Length AndAlso
       verticalWallItems(x)(y) = "picFlipper2" Then
            Debug.WriteLine($"Object on Flipper2 at ({x}, {y})")
            Return True
        End If

        ' Right Flipper Check (Flipper1)
        If x + 1 < verticalWallItems.Length AndAlso
       y >= 0 AndAlso y < verticalWallItems(x + 1).Length AndAlso
       verticalWallItems(x + 1)(y) = "picFlipper1" Then
            Debug.WriteLine($"Object on Flipper1 at ({x + 1}, {y})")
            Return True
        End If

        ' Up Flipper Check (Flipper4)
        If x >= 0 AndAlso x < horizontalWallItems.Length AndAlso
       y >= 0 AndAlso y < horizontalWallItems(x).Length AndAlso
       horizontalWallItems(x)(y) = "picFlipper4" Then
            Debug.WriteLine($"Object on Flipper4 at ({x}, {y})")
            Return True
        End If

        ' Down Flipper Check (Flipper3)
        If x >= 0 AndAlso x < horizontalWallItems.Length AndAlso
       y + 1 < horizontalWallItems(x).Length AndAlso
       horizontalWallItems(x)(y + 1) = "picFlipper3" Then
            Debug.WriteLine($"Object on Flipper3 at ({x}, {y + 1})")
            Return True
        End If

        Return False
    End Function
    Private Sub CheckForFlipper(movingObject As MovingObject)
        Dim x As Integer = movingObject.X
        Dim y As Integer = movingObject.Y

        Debug.WriteLine($"Checking flipper for moving object at ({x}, {y})")

        ' Left Flipper Check (Flipper2)
        If x >= 0 AndAlso x < verticalWallItems.Length AndAlso
       y >= 0 AndAlso y < verticalWallItems(x).Length AndAlso
       verticalWallItems(x)(y) = "picFlipper2" Then
            Debug.WriteLine($"Flipper2 detected at ({x}, {y})")
            movingObject.DeltaX = 1 ' Move right
            movingObject.DeltaY = 0
            movingObject.MoveInterval = If(movingObject.IsBall, 100, 12)
            movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
            sfxManager.PlaySoundEffect("Flipper")
            Return
        End If

        ' Right Flipper Check (Flipper1)
        If x + 1 < verticalWallItems.Length AndAlso
       y >= 0 AndAlso y < verticalWallItems(x + 1).Length AndAlso
       verticalWallItems(x + 1)(y) = "picFlipper1" Then
            Debug.WriteLine($"Flipper1 detected at ({x + 1}, {y})")
            movingObject.DeltaX = -1 ' Move left
            movingObject.DeltaY = 0
            movingObject.MoveInterval = If(movingObject.IsBall, 100, 12)
            movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
            sfxManager.PlaySoundEffect("Flipper")
            Return
        End If

        ' Up Flipper Check (Flipper4)
        If x >= 0 AndAlso x < horizontalWallItems.Length AndAlso
       y >= 0 AndAlso y < horizontalWallItems(x).Length AndAlso
       horizontalWallItems(x)(y) = "picFlipper4" Then
            Debug.WriteLine($"Flipper4 detected at ({x}, {y})")
            movingObject.DeltaX = 0
            movingObject.DeltaY = 1 ' Move down
            movingObject.MoveInterval = If(movingObject.IsBall, 100, 12)
            movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
            sfxManager.PlaySoundEffect("Flipper")
            Return
        End If

        ' Down Flipper Check (Flipper3)
        If x >= 0 AndAlso x < horizontalWallItems.Length AndAlso
       y + 1 < horizontalWallItems(x).Length AndAlso
       horizontalWallItems(x)(y + 1) = "picFlipper3" Then
            Debug.WriteLine($"Flipper3 detected at ({x}, {y + 1})")
            movingObject.DeltaX = 0
            movingObject.DeltaY = -1 ' Move up
            movingObject.MoveInterval = If(movingObject.IsBall, 100, 12)
            movingObject.NextMoveTime = DateTime.Now.AddMilliseconds(movingObject.MoveInterval)
            sfxManager.PlaySoundEffect("Flipper")
            Return
        End If
    End Sub
    Private Function CanObjectMoveTo(movingObject As MovingObject, currentX As Integer, currentY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Dim wallExists As Boolean = False
        Dim laserExists As Boolean = False
        Dim laserX As Integer = -1
        Dim laserY As Integer = -1
        Dim laserType As String = ""

        If deltaX = 1 Then
            wallExists = verticalWalls(currentX + 1)(currentY)
            If verticalWallItems(currentX + 1)(currentY) IsNot Nothing Then
                laserExists = True
                laserX = currentX + 1
                laserY = currentY
                laserType = verticalWallItems(laserX)(laserY)
            End If
        ElseIf deltaX = -1 Then
            wallExists = verticalWalls(currentX)(currentY)
            If verticalWallItems(currentX)(currentY) IsNot Nothing Then
                laserExists = True
                laserX = currentX
                laserY = currentY
                laserType = verticalWallItems(laserX)(laserY)
            End If
        ElseIf deltaY = 1 Then
            wallExists = horizontalWalls(currentX)(currentY + 1)
            If horizontalWallItems(currentX)(currentY + 1) IsNot Nothing Then
                laserExists = True
                laserX = currentX
                laserY = currentY + 1
                laserType = horizontalWallItems(laserX)(laserY)
            End If
        ElseIf deltaY = -1 Then
            wallExists = horizontalWalls(currentX)(currentY)
            If horizontalWallItems(currentX)(currentY) IsNot Nothing Then
                laserExists = True
                laserX = currentX
                laserY = currentY
                laserType = horizontalWallItems(laserX)(laserY)
            End If
        End If

        If wallExists Then
            Return False
        End If

        If laserExists Then
            Dim laserCanBeDestroyed As Boolean = False

            If (deltaX <> 0 AndAlso laserType = "picLaser1") Then
                laserCanBeDestroyed = True
            ElseIf (deltaY <> 0 AndAlso laserType = "picLaser2") Then
                laserCanBeDestroyed = True
            End If

            If laserCanBeDestroyed Then
                If deltaX <> 0 Then
                    verticalWallItems(laserX)(laserY) = Nothing
                ElseIf deltaY <> 0 Then
                    horizontalWallItems(laserX)(laserY) = Nothing
                End If
                sfxManager.PlaySoundEffect("Break Laser")
            Else
                Return False
            End If
        End If

        Return True
    End Function

    Private Function CanBallMoveTo(currentX As Integer, currentY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Return CanObjectMoveTo(New MovingObject With {.IsBall = True}, currentX, currentY, deltaX, deltaY)
    End Function

    Private Function IsLaserOnVerticalWall(x As Integer, y As Integer) As Boolean
        If verticalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = verticalWallItems(x)(y)
            Return item = "picLaser1" OrElse item = "picLaser2"
        End If
        Return False
    End Function

    Private Function IsLaserOnHorizontalWall(x As Integer, y As Integer) As Boolean
        If horizontalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = horizontalWallItems(x)(y)
            Return item = "picLaser1" OrElse item = "picLaser2"
        End If
        Return False
    End Function

    Private Function CanBlockMoveTo(blockX As Integer, blockY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        Return CanObjectMoveTo(New MovingObject With {.IsBlock = True}, blockX, blockY, deltaX, deltaY)
    End Function

    Private Function IsFlipperOnVerticalWall(x As Integer, y As Integer) As Boolean
        If verticalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = verticalWallItems(x)(y)
            Return item = "picFlipper1" OrElse item = "picFlipper2"
        End If
        Return False
    End Function

    Private Function IsFlipperOnHorizontalWall(x As Integer, y As Integer) As Boolean
        If horizontalWallItems(x)(y) IsNot Nothing Then
            Dim item As String = horizontalWallItems(x)(y)
            Return item = "picFlipper3" OrElse item = "picFlipper4"
        End If
        Return False
    End Function

    Private Function CanMoveTo(newX As Integer, newY As Integer, deltaX As Integer, deltaY As Integer) As Boolean
        ' Check for walls and lasers based on the direction of movement
        If deltaX = 1 Then
            If verticalWalls(playerX + 1)(playerY) Then Return False
            If IsLaserOnVerticalWall(playerX + 1, playerY) Then Return False
            If IsFlipperOnVerticalWall(playerX + 1, playerY) Then Return False
        ElseIf deltaX = -1 Then
            If verticalWalls(playerX)(playerY) Then Return False
            If IsLaserOnVerticalWall(playerX, playerY) Then Return False
            If IsFlipperOnVerticalWall(playerX, playerY) Then Return False
        ElseIf deltaY = 1 Then
            If horizontalWalls(playerX)(playerY + 1) Then Return False
            If IsLaserOnHorizontalWall(playerX, playerY + 1) Then Return False
            If IsFlipperOnHorizontalWall(playerX, playerY + 1) Then Return False
        ElseIf deltaY = -1 Then
            If horizontalWalls(playerX)(playerY) Then Return False
            If IsLaserOnHorizontalWall(playerX, playerY) Then Return False
            If IsFlipperOnHorizontalWall(playerX, playerY) Then Return False
        End If

        ' Check for moving objects at the new position
        If movingObjects.Any(Function(b) b.X = newX AndAlso b.Y = newY) Then
            Return False
        End If

        ' Allow movement onto BlockSwitch
        Dim targetItem As String = placedItems(newX)(newY)
        If targetItem = "picBlockSwitch" OrElse targetItem = "picBlockSwitch2" Then
            Return True
        End If

        Return True
    End Function
    Private Sub GameWon()
        sfxManager.PlaySoundEffect("Finish")
        MessageBox.Show($"Congratulations! You have completed the level.{Environment.NewLine}Coins Collected: {CollectedCoins}")
        Me.Close()
    End Sub

    Private Sub UpdateCoinLabel()
        lblCoinCount.Text = $"{CollectedCoins} of {totalCoins}"
    End Sub

    Private Sub UpdateKeyLabel()
        If totalKeys > 0 Then
            lblKeyCount.Text = $"{collectedKeys} of {totalKeys}"
        Else
            lblKeyCount.Text = "X"
        End If
    End Sub

#End Region

#Region "Drawing"
    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)
        ' Required but can be left empty
    End Sub

    Private Sub picTest_Paint(sender As Object, e As PaintEventArgs) Handles picTest.Paint
        If Not isRoomDataLoaded Then
            Return
        End If

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

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

        e.Graphics.SmoothingMode = SmoothingMode.None

        DrawGrid(e.Graphics)

        ' Draw item animations
        For Each itemAnimation In itemAnimations
            Dim destRect As New Rectangle(itemAnimation.CurrentX, itemAnimation.CurrentY, itemAnimation.ItemImage.Width, itemAnimation.ItemImage.Height)
            e.Graphics.DrawImage(itemAnimation.ItemImage, destRect)
        Next
    End Sub

    Private Sub InvalidateMovingObjects()
        For Each movingObject In movingObjects
            Dim rect As New Rectangle(
        INDENT + movingObject.X * (SQSizeX + WALL_THICKNESS),
        INDENT + movingObject.Y * (SQSizeY + WALL_THICKNESS),
        SQSizeX + WALL_THICKNESS,
        SQSizeY + WALL_THICKNESS
    )
            picTest.Invalidate(rect)
        Next
    End Sub

    Private Sub DrawHatchStyleBackground(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)

        ' Calculate the hatch style rectangle for the entire grid
        Dim hatchRect As New Rectangle(xOrigin, yOrigin, gridWidth, gridHeight)

        ' Draw the hatch style background
        Using brush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
            g.FillRectangle(brush, hatchRect)
        End Using
    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)

        ' Calculate the gradient rectangle for the entire grid
        Dim gradientRect As New Rectangle(xOrigin, yOrigin, gridWidth, gridHeight)

        ' Draw the gradient background
        Using brush As New LinearGradientBrush(gradientRect, floorsColor1, floorsColor2, LinearGradientMode.Vertical)
            g.FillRectangle(brush, gradientRect)
        End Using
    End Sub

    Private Sub CalculateSquareSizes(clientWidth As Integer, clientHeight As Integer)
        SQSizeX = (clientWidth - 2 * INDENT - (SQCOUNTX + 1) * WALL_THICKNESS) \ SQCOUNTX
        SQSizeY = (clientHeight - 2 * INDENT - (SQCOUNTY + 1) * WALL_THICKNESS) \ SQCOUNTY
    End Sub

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

        ' Draw the gradient background if useGradient is true
        If useGradient Then
            DrawGradientBackground(g)
        End If

        ' Draw floor squares with the chosen style only if not using gradient
        If Not useGradient Then
            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 IsNot Nothing AndAlso floorsUseEdgeStyle.Length > x AndAlso
           floorsUseEdgeStyle(x) IsNot Nothing AndAlso floorsUseEdgeStyle(x).Length > y AndAlso
           floorsUseEdgeStyle(x)(y) Then
                        Using floorBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                            g.FillRectangle(floorBrush, floorRect)
                        End Using
                    Else
                        Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                            g.FillRectangle(floorBrush, floorRect)
                        End Using
                    End If
                Next
            Next
        End If

        ' Draw wall intersections
        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

        ' Draw vertical walls or floor
        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(x).Length Then

                    If verticalWalls(x)(y) Then
                        ' Draw wall
                        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                            g.FillRectangle(wallBrush, wallRect)
                        End Using
                    ElseIf Not useGradient Then
                        Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                            g.FillRectangle(floorBrush, wallRect)
                        End Using
                    End If
                End If
            Next
        Next

        ' Draw horizontal walls or floor
        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(x).Length Then

                    If horizontalWalls(x)(y) Then
                        ' Draw wall
                        Using wallBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                            g.FillRectangle(wallBrush, wallRect)
                        End Using
                    ElseIf Not useGradient Then
                        Using floorBrush As New HatchBrush(floorsHatchStyle, floorsColor1, floorsColor2)
                            g.FillRectangle(floorBrush, wallRect)
                        End Using
                    End If
                End If
            Next
        Next

        ' Draw edge squares with the wall hatch style and colors
        For x As Integer = 0 To SQCOUNTX - 1
            For y As Integer = 0 To SQCOUNTY - 1
                If floorsUseEdgeStyle IsNot Nothing AndAlso floorsUseEdgeStyle.Length > x AndAlso
       floorsUseEdgeStyle(x) IsNot Nothing AndAlso floorsUseEdgeStyle(x).Length > y AndAlso
       floorsUseEdgeStyle(x)(y) Then
                    Dim edgeRect As New Rectangle(
            xOrigin + x * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2,
            yOrigin + y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2,
            SQSizeX,
            SQSizeY
        )
                    Using edgeBrush As New HatchBrush(wallsHatchStyle, wallsColor1, wallsColor2)
                        g.FillRectangle(edgeBrush, edgeRect)
                    End Using
                End If
            Next
        Next

        ' Draw items
        DrawItems(g)
        DrawWallItems(g)
    End Sub

    Private Sub DrawItems(g As Graphics)
        If placedItems Is Nothing Then Return

        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
                    Dim imageKey As String = placedItems(x)(y)
                    If Not String.IsNullOrEmpty(imageKey) AndAlso imageDictionary.ContainsKey(imageKey) Then
                        If imageKey = "picStart" Then
                            Continue For
                        End If
                        Dim img As Image = Nothing
                        If imageKey = "picGate" Then
                            If doorAnimationTimer IsNot Nothing AndAlso doorAnimationTimer.Enabled Then
                                img = imageDictionary(imageKey) ' Use the animated door image
                            Else
                                img = doorImage ' Use the static door image
                            End If
                        Else
                            img = imageDictionary(imageKey)
                        End If
                        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)

                        g.DrawImage(img, destRect)

                        ' Draw the number of remaining keys on the door
                        If imageKey = "picGate" Then
                            Dim remainingKeys As Integer = totalKeys - collectedKeys
                            Dim font As New Font("Arial", 12, FontStyle.Bold)
                            Dim textSize As SizeF = g.MeasureString(remainingKeys.ToString(), font)
                            Dim textX As Integer = xPos + (SQSizeX - textSize.Width) \ 2
                            Dim textY As Integer = yPos + (SQSizeY - textSize.Height) \ 2
                            g.DrawString(remainingKeys.ToString(), font, Brushes.White, textX, textY)
                        End If
                    End If
                Next
            End If
        Next

        For Each movingObject In movingObjects
            Dim img As Image = Nothing
            If movingObject.IsBall Then
                img = imageDictionary("picBall")
            ElseIf movingObject.IsBlock Then
                img = imageDictionary("picBlock1")
            End If

            If img IsNot Nothing Then
                Dim xPos As Integer = INDENT + movingObject.X * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim yPos As Integer = INDENT + movingObject.Y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
                Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)

                g.DrawImage(img, destRect)
            End If
        Next

        ' Draw baddies
        For Each baddy In baddies
            Dim img As Image = imageDictionary(baddy.ImageKey)
            Dim xPos As Integer = INDENT + baddy.X * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim yPos As Integer = INDENT + baddy.Y * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
            Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)
            g.DrawImage(img, destRect)
        Next

        DrawPlayer(g)
    End Sub
    Private Sub DrawPlayer(g As Graphics)
        Dim img As Image
        Select Case lastDirection
            Case "Left"
                img = imageDictionary("picStartLeft")
            Case "Right"
                img = imageDictionary("picStartRight")
            Case Else
                img = imageDictionary("picStart")
        End Select

        Dim xPos As Integer = INDENT + playerX * (SQSizeX + WALL_THICKNESS) + WALL_THICKNESS \ 2
        Dim yPos As Integer = INDENT + playerY * (SQSizeY + WALL_THICKNESS) + WALL_THICKNESS \ 2
        Dim destRect As New Rectangle(xPos, yPos, SQSizeX, SQSizeY)

        g.DrawImage(img, destRect)
    End Sub

    Private Sub DrawWallItems(g As Graphics)
        If verticalWallItems IsNot Nothing Then
            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
                        Dim imageKey As String = verticalWallItems(x)(y)
                        If Not String.IsNullOrEmpty(imageKey) AndAlso imageDictionary.ContainsKey(imageKey) Then
                            Dim img As Image = imageDictionary(imageKey)
                            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
                    Next
                End If
            Next
        End If

        If horizontalWallItems IsNot Nothing Then
            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
                        Dim imageKey As String = horizontalWallItems(x)(y)
                        If Not String.IsNullOrEmpty(imageKey) AndAlso imageDictionary.ContainsKey(imageKey) Then
                            Dim img As Image = imageDictionary(imageKey)
                            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
                    Next
                End If
            Next
        End If
    End Sub
    Private Sub OnRedrawTick(sender As Object, e As EventArgs)
        picTest.Invalidate()
        CheckTriggers() ' Check triggers on every redraw tick
    End Sub

    Private Sub OnIdleTick(sender As Object, e As EventArgs)
        lastDirection = "Idle"
    End Sub

#End Region

#Region " Triggers "

    Private Sub CheckTriggers()
        For Each trigger In triggers
            If CheckTriggerCondition(trigger) Then
                ExecuteTriggerAction(trigger)
            End If
        Next
    End Sub
    Private Function CheckTriggerCondition(trigger As Trigger) As Boolean
        If trigger.TriggerAction = "Enters" Then
            If trigger.ItemType = "Any Block" Then
                ' Check for moving blocks
                For Each movingObject In movingObjects
                    If movingObject.IsBlock AndAlso movingObject.X = trigger.TriggerSquare.X AndAlso movingObject.Y = trigger.TriggerSquare.Y Then
                        Return True
                    End If
                Next

                ' Check for stationary blocks
                For x As Integer = 0 To placedItems.Length - 1
                    For y As Integer = 0 To placedItems(x).Length - 1
                        If placedItems(x)(y) = "picBlock1" AndAlso x = trigger.TriggerSquare.X AndAlso y = trigger.TriggerSquare.Y Then
                            Return True
                        End If
                    Next
                Next
            End If
        End If
        ' Add more conditions as needed
        Return False
    End Function
    Private Sub ExecuteTriggerAction(trigger As Trigger)
        If trigger.Action = "Remove" Then
            ' Implement the logic to remove an item
            Dim itemToRemove As String = trigger.ItemToRemove
            Dim coordinates As String() = itemToRemove.Split("_")
            If coordinates.Length = 3 Then
                Dim x As Integer = Integer.Parse(coordinates(1))
                Dim y As Integer = Integer.Parse(coordinates(2))
                placedItems(x)(y) = Nothing
            End If
        End If
        ' Add more actions as needed
    End Sub

#End Region


#Region "Closing"
    Private Sub frmGame_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
        StopAndDisposeAnimationTimer()
        StopAndDisposeMovingObjectsTimer()
        ClearMovingObjectsList()
        StopMusicPlayback()
        StopSoundEffects()
        StopAndDisposeRedrawTimer()
        StopAndDisposeIdleTimer()
    End Sub

    Private Sub StopAndDisposeIdleTimer()
        If idleTimer IsNot Nothing Then
            idleTimer.Stop()
            idleTimer.Dispose()
            idleTimer = Nothing
        End If
    End Sub
    Private Sub StopAndDisposeAnimationTimer()
        If animationTimer IsNot Nothing Then
            animationTimer.Stop()
            animationTimer.Dispose()
            animationTimer = Nothing
        End If
    End Sub

    Private Sub StopAndDisposeMovingObjectsTimer()
        If movingObjectsTimer IsNot Nothing Then
            movingObjectsTimer.Stop()
            movingObjectsTimer.Dispose()
            movingObjectsTimer = Nothing
        End If
    End Sub

    Private Sub ClearMovingObjectsList()
        movingObjects.Clear()
    End Sub

    Private Sub StopMusicPlayback()
        musicManager.StopMusic()
    End Sub

    Private Sub StopSoundEffects()
        sfxManager.StopSoundEffects()
    End Sub

    Private Sub StopAndDisposeRedrawTimer()
        If redrawTimer IsNot Nothing Then
            redrawTimer.Stop()
            redrawTimer.Dispose()
            redrawTimer = Nothing
        End If
    End Sub

    Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnCLose.Click
        Close()
    End Sub

#End Region

#Region "Utility Methods"
    Private Function GetProjectDirectory() As String
        Dim dir As DirectoryInfo = New DirectoryInfo(Application.StartupPath)
        For i As Integer = 1 To 4
            If dir.Parent IsNot Nothing Then
                dir = dir.Parent
            End If
        Next
        Return dir.FullName
    End Function

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

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

            RestoreRoomData(roomData)
            CountKeysInGrid(roomData)
            CountCoinsInGrid(roomData) ' Count the total number of coins available

            ' Invalidate the picTest control to redraw the grid
            picTest.Invalidate()
        Catch ex As Exception
            MessageBox.Show("Error loading room: " & ex.ToString())
        End Try
    End Sub

    Private Sub CountCoinsInGrid(roomData As RoomData)
        totalCoins = 0
        For x As Integer = 0 To roomData.placedItems.Length - 1
            If roomData.placedItems(x) IsNot Nothing Then
                For y As Integer = 0 To roomData.placedItems(x).Length - 1
                    If roomData.placedItems(x)(y) IsNot Nothing AndAlso roomData.placedItems(x)(y).ImageKey = "picCoin" Then
                        totalCoins += 1
                    End If
                Next
            End If
        Next
        UpdateCoinLabel()
    End Sub

    Private Sub CountKeysInGrid(roomData As RoomData)
        totalKeys = 0
        For x As Integer = 0 To roomData.placedItems.Length - 1
            If roomData.placedItems(x) IsNot Nothing Then
                For y As Integer = 0 To roomData.placedItems(x).Length - 1
                    If roomData.placedItems(x)(y) IsNot Nothing AndAlso roomData.placedItems(x)(y).ImageKey = "picKey" Then
                        totalKeys += 1
                    End If
                Next
            End If
        Next
        UpdateKeyLabel()
    End Sub

    Private Sub RestoreRoomData(roomData As RoomData)
        Try
            SQCOUNTX = roomData.GridSizeX
            SQCOUNTY = roomData.GridSizeY

            wallsHatchStyle = CType(roomData.wallsHatchStyleValue, HatchStyle)
            wallsColor1 = Color.FromArgb(roomData.wallsColor1Argb)
            wallsColor2 = Color.FromArgb(roomData.wallsColor2Argb)
            floorsHatchStyle = CType(roomData.floorsHatchStyleValue, HatchStyle)
            floorsColor1 = Color.FromArgb(roomData.floorsColor1Argb)
            floorsColor2 = Color.FromArgb(roomData.floorsColor2Argb)

            verticalWalls = roomData.verticalWalls
            horizontalWalls = roomData.horizontalWalls
            floorsUseEdgeStyle = roomData.floorsUseEdgeStyle

            placedItems = RestorePlacedItems(roomData.placedItems)
            verticalWallItems = RestorePlacedItems(roomData.verticalWallItems)
            horizontalWallItems = RestorePlacedItems(roomData.horizontalWallItems)

            SelectedMusic = roomData.SelectedMusic
            lblName.Text = roomData.LevelName
            PlaySelectedMusic()

            ' Ensure the useGradient property is restored
            useGradient = roomData.useGradient

            isRoomDataLoaded = True
            picTest.Invalidate()
        Catch ex As Exception
            MessageBox.Show("Error restoring room data: " & ex.ToString())
        End Try
    End Sub

    Private Function RestorePlacedItems(data As PlacedItemData()()) As String()()
        If data Is Nothing Then Return Nothing
        Dim lengthOuter As Integer = data.Length
        Dim restoredItems As String()() = New String(lengthOuter - 1)() {}
        For x As Integer = 0 To lengthOuter - 1
            If data(x) IsNot Nothing Then
                Dim lengthInner As Integer = data(x).Length
                restoredItems(x) = New String(lengthInner - 1) {}
                For y As Integer = 0 To lengthInner - 1
                    If data(x)(y) IsNot Nothing Then
                        restoredItems(x)(y) = data(x)(y).ImageKey
                    Else
                        restoredItems(x)(y) = Nothing
                    End If
                Next
            Else
                restoredItems(x) = Nothing
            End If
        Next
        Return restoredItems
    End Function

    Private Sub OnItemAnimationTick(sender As Object, e As EventArgs)
        For i As Integer = itemAnimations.Count - 1 To 0 Step -1
            Dim itemAnimation = itemAnimations(i)
            itemAnimation.UpdatePosition()
            If itemAnimation.IsAnimationComplete() Then
                itemAnimations.RemoveAt(i)
            End If
        Next
        ' No need to invalidate here, the redraw timer will handle it
    End Sub

    Private Sub btnRetry_Click(sender As Object, e As EventArgs) Handles btnRetry.Click
        RestartLevel()
    End Sub

    Private Sub RestartLevel()
        ' Reload the room data to reset the placedItems array
        LoadRoomData()

        ' Reset player position
        FindPlayerStartPosition()

        ' Reset collected items
        CollectedCoins = 0
        collectedKeys = 0
        UpdateCoinLabel()
        UpdateKeyLabel()

        ' Reset moving objects
        movingObjects.Clear()
        AddObjectsOnFlippersToMovingObjects()

        ' Reset animations
        itemAnimations.Clear()
    End Sub

    Private Sub btnMinimize_Click(sender As Object, e As EventArgs) Handles btnMinimize.Click
        Me.WindowState = FormWindowState.Minimized
    End Sub

    Protected Overrides ReadOnly Property CreateParams() As CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            cp.ExStyle = cp.ExStyle Or &H2000000 ' WS_EX_COMPOSITED
            Return cp
        End Get
    End Property

#End Region

End Class

Public Class DoubleBufferedPictureBox
    Inherits PictureBox

    Public Sub New()
        Me.DoubleBuffered = True
    End Sub
End Class

Public Class NoBorderToolStripRenderer
    Inherits ToolStripRenderer

    Protected Overrides Sub OnRenderToolStripBorder(e As ToolStripRenderEventArgs)
        ' Do nothing to prevent the border from being drawn
    End Sub

    Protected Overrides Sub OnRenderButtonBackground(e As ToolStripItemRenderEventArgs)
        ' Do nothing to prevent the button background from being drawn
    End Sub

    Protected Overrides Sub OnRenderItemBackground(e As ToolStripItemRenderEventArgs)
        ' Do nothing to prevent the item background from being drawn
    End Sub
End Class

Public Class frmTriggerEditor
    Private itemsDictionary As Dictionary(Of String, List(Of ItemInfo))
    Private gridData As RoomData
    Private designerForm As FrmDesigner
    Private highlightedItem As ItemInfo

    ' Variables for frmTrigger
    Private selectedItemInfo As ItemInfo
    Private selectedTriggerSquare As Point = New Point(-1, -1)
    Private selectedActionSquare As Point = New Point(-1, -1)
    Private selectedItemToRemove As String = ""
    Private isSpecificItemSelected As Boolean = False
    Private triggers As New List(Of Trigger)()

    Public Property TriggersList As List(Of Trigger)
        Get
            Return triggers
        End Get
        Set(value As List(Of Trigger))
            triggers = value
        End Set
    End Property

    Public Sub New(itemsDictionary As Dictionary(Of String, List(Of ItemInfo)), gridData As RoomData, designerForm As FrmDesigner, triggersList As List(Of Trigger))
        InitializeComponent()
        Me.itemsDictionary = itemsDictionary
        Me.gridData = gridData
        Me.designerForm = designerForm
        Me.triggers = triggersList
        If Me.triggers Is Nothing Then
            Me.triggers = New List(Of Trigger)()
        End If
        InitializeTriggerList()
        PopulateTreeView()
    End Sub

    Private Sub InitializeTriggerList()
        lstTrigger.Items.Add("Enters Cell")
        lstTrigger.SelectedIndex = 0 ' Ensure "Enters Cell" is selected by default
    End Sub

    Private Sub PopulateTreeView()
        trvTriggers.Nodes.Clear()
        If triggers Is Nothing Then
            triggers = New List(Of Trigger)()
        End If
        For Each trigger As Trigger In triggers
            Dim newNode As New TreeNode(trigger.ToString())
            trvTriggers.Nodes.Add(newNode)
        Next
    End Sub

    Private Sub btnSpecificItem_Click(sender As Object, e As EventArgs) Handles btnSpecificItem.Click
        Hide()
        designerForm.picGame.Tag = "SelectItem"
    End Sub

    Public Sub UpdateSelectedItemInfo(itemInfo As ItemInfo)
        selectedItemInfo = itemInfo
        isSpecificItemSelected = True
        lblType.Text = $"Item Type: {GetRenamedItem(selectedItemInfo.ImageKey)}"
        PopulateTriggerList(selectedItemInfo.ImageKey)
        UpdateTriggerDescription()
        Me.Show() ' Ensure the form is shown
    End Sub

    Public Sub UpdateSelectedItemToRemove(item As String)
        selectedItemToRemove = item
        Me.Show() ' Ensure the form is shown
        UpdateTriggerDescription()
    End Sub

    Private Sub PopulateTriggerList(itemType As String)
        ' No need to populate lstTrigger as it is always "Enters Cell"
    End Sub

    Private Sub lstTrigger_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstTrigger.SelectedIndexChanged
        If lstTrigger.SelectedItem IsNot Nothing AndAlso lstTrigger.SelectedItem.ToString() = "Enters Cell" Then
            ' Indicate that the user needs to select a square
            Me.Hide()
            designerForm.picGame.Tag = "SelectTriggerSquare"
        End If
        UpdateTriggerDescription()
    End Sub

    Private Sub lstAction_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstAction.SelectedIndexChanged
        If lstAction.SelectedItem IsNot Nothing Then
            lstItem.Enabled = lstAction.SelectedItem.ToString() = "Create"
            If lstAction.SelectedItem.ToString() = "Remove" Then
                ' Indicate that the user needs to select an item to remove
                Me.Hide()
                designerForm.picGame.Tag = "SelectItemToRemove"
            End If
        End If
        UpdateTriggerDescription()
    End Sub

    Private Sub lstItem_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstItem.SelectedIndexChanged
        If lstItem.SelectedItem IsNot Nothing Then
            ' Indicate that the user needs to select a square
            Me.Hide()
            designerForm.picGame.Tag = "SelectActionSquare"
        End If
        UpdateTriggerDescription()
    End Sub

    Private Sub lstItemOfType_SelectedIndexChanged(sender As Object, e As EventArgs) Handles lstItemOfType.SelectedIndexChanged
        If lstItemOfType.SelectedItem IsNot Nothing Then
            isSpecificItemSelected = lstItemOfType.SelectedItem.ToString() = "Specific Item"
            UpdateTriggerDescription()
        End If
    End Sub
    Private Sub UpdateTriggerDescription()
        Dim itemType As String = If(isSpecificItemSelected AndAlso selectedItemInfo IsNot Nothing, selectedItemInfo.UniqueId, lstItemOfType.SelectedItem?.ToString())
        Dim triggerAction As String = If(lstTrigger.SelectedItem IsNot Nothing, lstTrigger.SelectedItem.ToString(), "")
        Dim action As String = If(lstAction.SelectedItem IsNot Nothing, lstAction.SelectedItem.ToString(), "")
        Dim itemToCreate As String = If(lstItem.SelectedItem IsNot Nothing, lstItem.SelectedItem.ToString(), "")
        Dim triggerSquare As String = If(selectedTriggerSquare <> New Point(-1, -1), $"Square ({selectedTriggerSquare.X}, {selectedTriggerSquare.Y})", "")
        Dim actionSquare As String = If(selectedActionSquare <> New Point(-1, -1), $"Square ({selectedActionSquare.X}, {selectedActionSquare.Y})", "")
        Dim itemToRemove As String = selectedItemToRemove

        Dim description As String
        If action = "Remove" Then
            description = $"When {itemType} {triggerAction} {triggerSquare}, remove {itemToRemove} from the game."
        Else
            description = $"When {itemType} {triggerAction} {triggerSquare}, {action} a {itemToCreate} at {actionSquare}."
        End If

        rtbTrigger.Rtf = GetRichText(description, itemType, triggerAction, triggerSquare, action, itemToCreate, actionSquare, itemToRemove)

        ' Enable the create button if the description is complete
        btnCreate.Enabled = ValidateTrigger()
    End Sub

    Private Function GetRichText(description As String, itemType As String, triggerAction As String, triggerSquare As String, action As String, itemToCreate As String, actionSquare As String, itemToRemove As String) As String
        Dim rtf As String = "{\rtf1\ansi "

        If itemType <> "" Then
            rtf &= description.Replace(itemType, $"{{\b\cf2 {itemType}}}")
        End If

        If triggerAction <> "" Then
            rtf = rtf.Replace(triggerAction, $"{{\i {triggerAction}}}")
        End If

        If triggerSquare <> "" Then
            rtf = rtf.Replace(triggerSquare, $"{{\i\cf4 {triggerSquare}}}")
        End If

        If action <> "" Then
            rtf = rtf.Replace(action, $"{{\i {action}}}")
        End If

        If itemToCreate <> "" Then
            rtf = rtf.Replace(itemToCreate, $"{{\b\cf2 {itemToCreate}}}")
        End If

        If actionSquare <> "" Then
            rtf = rtf.Replace(actionSquare, $"{{\i\cf4 {actionSquare}}}")
        End If

        If itemToRemove <> "" Then
            rtf = rtf.Replace(itemToRemove, $"{{\b\cf2 {itemToRemove}}}")
        End If

        rtf &= "}"
        Return rtf
    End Function

    Private Function ValidateTrigger() As Boolean
        Return lstTrigger.SelectedItem IsNot Nothing AndAlso lstAction.SelectedItem IsNot Nothing AndAlso
               (lstAction.SelectedItem.ToString() = "Remove" OrElse (lstItem.SelectedItem IsNot Nothing AndAlso selectedActionSquare <> New Point(-1, -1))) AndAlso
               selectedTriggerSquare <> New Point(-1, -1)
    End Function

    Public Sub SetSelectedTriggerSquare(square As Point)
        selectedTriggerSquare = square
        UpdateTriggerDescription()
        Me.Show()
    End Sub

    Public Sub SetSelectedActionSquare(square As Point)
        selectedActionSquare = square
        UpdateTriggerDescription()
        Me.Show()
    End Sub

    Public Sub SetSelectedItemToRemove(item As String)
        selectedItemToRemove = item
        UpdateTriggerDescription()
        Me.Show()
    End Sub

    Private Function GetRenamedItem(itemKey As String) As String
        Select Case itemKey
            Case "picCoin"
                Return "Coin"
            Case "picFlipper1"
                Return "Flipper Left"
            Case "picFlipper2"
                Return "Flipper Right"
            Case "picFlipper3"
                Return "Flipper Up"
            Case "picFlipper4"
                Return "Flipper Down"
            Case "picLaser1"
                Return "Vertical Laser"
            Case "picLaser2"
                Return "Horizontal Laser"
            Case Else
                Return itemKey.Replace("pic", "")
        End Select
    End Function

    Private Sub ClearForm()
        lstTrigger.SelectedIndex = 0 ' Ensure "Enters Cell" is selected by default
        lstAction.SelectedIndex = -1
        lstItem.SelectedIndex = -1
        lstItemOfType.SelectedIndex = -1
        selectedTriggerSquare = New Point(-1, -1)
        selectedActionSquare = New Point(-1, -1)
        selectedItemToRemove = ""
        rtbTrigger.Clear()
        isSpecificItemSelected = False
    End Sub
    Private Sub AddTriggerToTreeView()
        Dim trigger As New Trigger With {
        .ItemType = If(isSpecificItemSelected AndAlso selectedItemInfo IsNot Nothing, GetRenamedItem(selectedItemInfo.ImageKey), lstItemOfType.SelectedItem?.ToString()),
        .TriggerAction = lstTrigger.SelectedItem?.ToString(),
        .Action = lstAction.SelectedItem?.ToString(),
        .ItemToCreate = lstItem.SelectedItem?.ToString(),
        .TriggerSquare = selectedTriggerSquare,
        .ActionSquare = selectedActionSquare,
        .ItemToRemove = selectedItemToRemove
    }
        triggers.Add(trigger)

        Dim newNode As New TreeNode(trigger.ToString())
        trvTriggers.Nodes.Add(newNode)
    End Sub

    Private Sub mnuDelete_Click(sender As Object, e As EventArgs) Handles mnuDelete.Click
        ' Get the selected node
        Dim selectedNode As TreeNode = trvTriggers.SelectedNode

        If selectedNode IsNot Nothing Then
            ' Find the trigger associated with the selected node
            Dim triggerToRemove As Trigger = Nothing
            For Each trigger As Trigger In triggers
                If trigger.ToString() = selectedNode.Text Then
                    triggerToRemove = trigger
                    Exit For
                End If
            Next

            ' Remove the trigger from the list
            If triggerToRemove IsNot Nothing Then
                triggers.Remove(triggerToRemove)

                ' Remove the node from the TreeView
                trvTriggers.Nodes.Remove(selectedNode)
            End If
        End If
    End Sub
    Private Sub btnCreate_Click(sender As Object, e As EventArgs) Handles btnCreate.Click
        If ValidateTrigger() Then
            AddTriggerToTreeView()
            ClearForm()
            PopulateTreeView() ' Refresh the TreeView to show the new trigger
        Else
            MessageBox.Show("Please fill in all required fields.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Sub

End Class

Public Class frmProperties
    Private itemsDictionary As Dictionary(Of String, List(Of ItemInfo))
    Private designerForm As FrmDesigner
    Private triggerEditorForm As frmTriggerEditor

    Public Sub New(itemsDictionary As Dictionary(Of String, List(Of ItemInfo)), designerForm As FrmDesigner, triggerEditorForm As frmTriggerEditor)
        InitializeComponent()
        Me.itemsDictionary = itemsDictionary
        Me.designerForm = designerForm
        Me.triggerEditorForm = triggerEditorForm
        PopulateTreeView()
    End Sub

    Private Function GetItemInfoFromNode(node As TreeNode) As ItemInfo
        For Each itemType In itemsDictionary.Keys
            For Each item In itemsDictionary(itemType)
                If node.Text.Contains(item.UniqueId) Then
                    Return item
                End If
            Next
        Next
        Return Nothing
    End Function

    Private Sub PopulateTreeView()
        trvControls.Nodes.Clear()

        ' Dictionary to map item keys to their categories
        Dim itemCategories As New Dictionary(Of String, String) From {
        {"picFlipper1", "Flippers"},
        {"picFlipper2", "Flippers"},
        {"picFlipper3", "Flippers"},
        {"picFlipper4", "Flippers"},
        {"picLaser1", "Lasers"},
        {"picLaser2", "Lasers"}
    }

        ' Dictionary to hold nodes for each category
        Dim categoryNodes As New Dictionary(Of String, TreeNode)

        ' First pass: Create category nodes and add items to them
        For Each itemType In itemsDictionary.Keys
            For Each item In itemsDictionary(itemType)
                Dim category As String = Nothing
                If itemCategories.TryGetValue(item.ImageKey, category) Then
                    If Not categoryNodes.ContainsKey(category) Then
                        categoryNodes(category) = New TreeNode(category)
                    End If
                    Dim parentNode As TreeNode = categoryNodes(category)
                    Dim itemName As String = GetRenamedItem(item.ImageKey)
                    Dim subCategory As String = GetSubCategory(item.ImageKey)

                    Dim subCategoryNode As TreeNode = Nothing
                    For Each node As TreeNode In parentNode.Nodes
                        If node.Text = subCategory Then
                            subCategoryNode = node
                            Exit For
                        End If
                    Next

                    If subCategoryNode Is Nothing Then
                        subCategoryNode = New TreeNode(subCategory)
                        parentNode.Nodes.Add(subCategoryNode)
                    End If

                    Dim childNode As New TreeNode($"{itemName} at ({item.Location.X}, {item.Location.Y}) - {item.UniqueId}")
                    subCategoryNode.Nodes.Add(childNode)
                Else
                    If Not categoryNodes.ContainsKey(itemType) Then
                        categoryNodes(itemType) = New TreeNode(itemType)
                    End If
                    Dim parentNode As TreeNode = categoryNodes(itemType)
                    Dim itemName As String = GetRenamedItem(item.ImageKey)
                    Dim childNode As New TreeNode($"{itemName} at ({item.Location.X}, {item.Location.Y}) - {item.UniqueId}")
                    parentNode.Nodes.Add(childNode)
                End If
            Next
        Next

        ' Sort the category nodes
        Dim sortedCategoryNodes As New List(Of TreeNode)(categoryNodes.Values)
        sortedCategoryNodes.Sort(Function(x, y) x.Text.CompareTo(y.Text))

        ' Now sort each parent node and its children
        For Each categoryNode In sortedCategoryNodes
            SortNode(categoryNode)
            trvControls.Nodes.Add(categoryNode)
        Next

        ' Update the count in the category and subcategory names
        For Each category In categoryNodes.Keys
            Dim parentNode As TreeNode = categoryNodes(category)
            Dim totalCount As Integer = 0

            ' Count items directly under the category node
            For Each itemNode As TreeNode In parentNode.Nodes
                If itemNode.Nodes.Count > 0 Then
                    ' Count items in subcategories
                    itemNode.Text = $"{itemNode.Text} ({itemNode.Nodes.Count})"
                    totalCount += itemNode.Nodes.Count
                Else
                    totalCount += 1
                End If
            Next

            parentNode.Text = $"{category} ({totalCount})"
        Next

    End Sub

    Private Sub SortNode(node As TreeNode)
        ' Sort child nodes recursively
        Dim nodesList As New List(Of TreeNode)()
        For Each childNode As TreeNode In node.Nodes
            nodesList.Add(childNode)
        Next

        ' Sort the list of child nodes
        nodesList.Sort(Function(x, y) x.Text.CompareTo(y.Text))

        ' Clear the existing nodes and re-add them in sorted order
        node.Nodes.Clear()
        For Each sortedNode As TreeNode In nodesList
            node.Nodes.Add(sortedNode)
            ' Recursively sort the sub-nodes
            SortNode(sortedNode)
        Next
    End Sub
    Private Function GetRenamedItem(itemKey As String) As String
        Select Case itemKey
            Case "picCoin"
                Return "Coin"
            Case "picFlipper1"
                Return "Flipper Left"
            Case "picFlipper2"
                Return "Flipper Right"
            Case "picFlipper3"
                Return "Flipper Up"
            Case "picFlipper4"
                Return "Flipper Down"
            Case "picLaser1"
                Return "Vertical Laser"
            Case "picLaser2"
                Return "Horizontal Laser"
            Case Else
                Return itemKey.Replace("pic", "")
        End Select
    End Function

    Private Function GetSubCategory(itemKey As String) As String
        Select Case itemKey
            Case "picFlipper1"
                Return "Flipper Left"
            Case "picFlipper2"
                Return "Flipper Right"
            Case "picFlipper3"
                Return "Flipper Up"
            Case "picFlipper4"
                Return "Flipper Down"
            Case "picLaser1"
                Return "Vertical Laser"
            Case "picLaser2"
                Return "Horizontal Laser"
            Case Else
                Return itemKey.Replace("pic", "")
        End Select
    End Function
End Class

Imports System.Drawing.Drawing2D

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

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

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

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

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

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

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

    Public Property Triggers As List(Of Trigger)

    ' Parameterless constructor for deserialization
    Public Sub New()

    End Sub

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

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

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

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

        ' Save floorsUseEdgeStyle
        floorsUseEdgeStyle = CloneJaggedArray(roomView.floorsUseEdgeStyle)

        ' Save the selected music
        SelectedMusic = roomView.SelectedMusic

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

        ' Save the useGradient value
        useGradient = roomView.useGradient

        ' Save triggers
        Triggers = roomView.TriggersList
    End Sub

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

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

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

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

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

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

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

        ' Restore floorsUseEdgeStyle
        roomView.floorsUseEdgeStyle = CloneJaggedArray(floorsUseEdgeStyle)

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

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

        ' Restore the useGradient value
        roomView.useGradient = useGradient

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

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

        ' Restore triggers
        roomView.TriggersList = Triggers
    End Sub

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

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

    ' Add Clone method
    Public Function Clone() As PlacedItem
        Return New PlacedItem() With {
            .ImageKey = Me.ImageKey
        }
    End Function
End Class

Public Class ItemInfo
    Public Property ImageKey As String
    Public Property Location As Point
    Public Property UniqueId As String

    Public Sub New(imageKey As String, location As Point, uniqueId As String)
        Me.ImageKey = imageKey
        Me.Location = location
        Me.UniqueId = uniqueId
    End Sub
End Class

I need to rewind the above back. Sadly I don't have any backups past the above. 

The changes I made were, I added my trigger editor. Which is working. And I love it. However. The button btnSpecificItem is no longer needed. And neither is the uniqueid. I attempted to set up a Unique Identifier for ALL the items in the game, but, I've decided the best foot forward is to remove these completely. The tirggers work perfectly with "Any Block, Any Baddy or Player (Start)" 

Can you find and remove all references to UniqueID