Imports System.IO
Imports System.Globalization
Imports System.Collections.Generic
Imports System.ComponentModel

Public Class frmMain
#Region "Variables and Constants"
    ' Column names constants
    Private Const ID_COLUMN_NAME As String = "idColumn"
    Private Const TOTALS_FLAG_COLUMN_NAME As String = "totalsFlagColumn"

    ' Column indices
    Private idColumnIndex As Integer
    Private totalsFlagColumnIndex As Integer
    Private dateColumnIndex As Integer

    ' Variables
    Private clickedRowIndex As Integer

    ' Undo and Redo stacks
    Private undoStack As New Stack(Of DataGridViewAction)()
    Private redoStack As New Stack(Of DataGridViewAction)()

    ' List to preserve original data for filtering and sorting
    Private originalRows As New List(Of Object())()

    ' Unique ID counter
    Private nextId As Integer = 1

    ' Variables to store the last sorted column and sort direction
    Private lastSortColumn As DataGridViewColumn = Nothing
    Private lastSortDirection As ListSortDirection = ListSortDirection.Ascending
#End Region

#Region "Form Initialization"
    ' Form constructor
    Public Sub New()
        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        InitializeContextMenu()
        InitializeDataGridView()

        ' Set DataGridView Anchor
        DataGridView1.Anchor = AnchorStyles.Top Or AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Bottom

        ' Handle CellEndEdit event
        AddHandler DataGridView1.CellEndEdit, AddressOf DataGridView1_CellEndEdit

        ' Set DateTimePicker properties
        DateTimePicker01.MaxDate = DateTime.Now.AddDays(1)

        ' Load data when the form loads
        AddHandler Me.Load, AddressOf frmMain_Load
    End Sub

    ' Event handler for form load
    Private Sub frmMain_Load(sender As Object, e As EventArgs)
        ' Temporarily remove the DateTimePicker ValueChanged event handler
        RemoveHandler DateTimePicker01.ValueChanged, AddressOf DateTimePicker01_ValueChanged

        ' Load data
        LoadData()

        ' Display all data
        DisplayAllData()

        UpdateUndoRedoMenuItems()

        ' Set the DateTimePicker to the current date
        DateTimePicker01.Value = DateTime.Now

        ' Attach the ValueChanged event handler after initialization
        AddHandler DateTimePicker01.ValueChanged, AddressOf DateTimePicker01_ValueChanged
    End Sub
#End Region

#Region "DataGridView Initialization"
    ' Method to initialize DataGridView properties and events
    Private Sub InitializeDataGridView()
        ' Assume columns are already added via the designer.

        ' Add the ID column if not already added
        If Not DataGridView1.Columns.Contains(ID_COLUMN_NAME) Then
            Dim idColumn As New DataGridViewTextBoxColumn()
            idColumn.Name = ID_COLUMN_NAME
            idColumn.HeaderText = "ID"
            idColumn.Visible = False ' Hide the column
            DataGridView1.Columns.Insert(0, idColumn)
        End If

        ' Add the totalsFlagColumn if not already added
        If Not DataGridView1.Columns.Contains(TOTALS_FLAG_COLUMN_NAME) Then
            Dim totalsFlagColumn As New DataGridViewTextBoxColumn()
            totalsFlagColumn.Name = TOTALS_FLAG_COLUMN_NAME
            totalsFlagColumn.HeaderText = "IsTotalsRow"
            totalsFlagColumn.Visible = False ' Hide the column
            DataGridView1.Columns.Insert(1, totalsFlagColumn)
        End If

        ' Get column indices
        idColumnIndex = DataGridView1.Columns(ID_COLUMN_NAME).Index
        totalsFlagColumnIndex = DataGridView1.Columns(TOTALS_FLAG_COLUMN_NAME).Index
        dateColumnIndex = DataGridView1.Columns("dateColumn").Index

        ' Set AutoSizeColumnsMode to Fill
        DataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill

        ' Set the FillWeight to control the width of each column
        DataGridView1.Columns("nameColumn").FillWeight = 10
        DataGridView1.Columns("typeColumn").FillWeight = 10
        DataGridView1.Columns("dateColumn").FillWeight = 10
        DataGridView1.Columns("startTimeColumn").FillWeight = 10
        DataGridView1.Columns("finishTimeColumn").FillWeight = 10
        DataGridView1.Columns("totalTimeColumn").FillWeight = 10
        DataGridView1.Columns("amountColumn").FillWeight = 10
        DataGridView1.Columns("commentColumn").FillWeight = 30 ' Comment section should be the longest

        ' Make Total Time column read-only
        DataGridView1.Columns("totalTimeColumn").ReadOnly = True

        ' Disable automatic addition of new rows
        DataGridView1.AllowUserToAddRows = False

        ' Enable sorting for all columns
        For Each column As DataGridViewColumn In DataGridView1.Columns
            column.SortMode = DataGridViewColumnSortMode.Automatic
        Next

        ' Handle events
        AddHandler DataGridView1.CellValueChanged, AddressOf DataGridView1_CellValueChanged
        AddHandler DataGridView1.Sorted, AddressOf DataGridView1_Sorted
        AddHandler DataGridView1.UserDeletingRow, AddressOf DataGridView1_UserDeletingRow
        AddHandler DataGridView1.SelectionChanged, AddressOf DataGridView1_SelectionChanged
        AddHandler DataGridView1.CellBeginEdit, AddressOf DataGridView1_CellBeginEdit
        AddHandler DataGridView1.SortCompare, AddressOf DataGridView1_SortCompare
        AddHandler DataGridView1.ColumnHeaderMouseClick, AddressOf DataGridView1_ColumnHeaderMouseClick ' Add this line
    End Sub
#End Region

#Region "Context Menu Initialization"
    ' Assign the context menu to the DataGridView
    Private Sub InitializeContextMenu()
        ' Assign context menu to DataGridView
        DataGridView1.ContextMenuStrip = contextMenuMain

        ' Initially disable move up and move down menu items
        menuItemMoveUp.Enabled = False
        menuItemMoveDown.Enabled = False

        ' Initially disable Undo and Redo menu items
        menuItemUndo.Enabled = False
        menuItemRedo.Enabled = False
    End Sub
#End Region

#Region "DateTimePicker Event"
    ' Event handler for DateTimePicker value changed
    Private Sub DateTimePicker01_ValueChanged(sender As Object, e As EventArgs)
        ' Extract the selected date from DateTimePicker01
        Dim selectedDate As DateTime = DateTimePicker01.Value
        Dim selectedMonth As Integer = selectedDate.Month
        Dim selectedYear As Integer = selectedDate.Year

        ' Filter the DataGridView based on the selected month and year
        FilterDataGrid(selectedMonth, selectedYear)
    End Sub
#End Region

#Region "DataGridView Events"
    ' Handle the DataGridView Mouse Down event to get the row index on right-click
    Private Sub DataGridView1_MouseDown(sender As Object, e As MouseEventArgs) Handles DataGridView1.MouseDown
        If e.Button = MouseButtons.Right Then
            Dim hitTestInfo As DataGridView.HitTestInfo = DataGridView1.HitTest(e.X, e.Y)
            If hitTestInfo.Type = DataGridViewHitTestType.Cell Then
                clickedRowIndex = hitTestInfo.RowIndex
                DataGridView1.ClearSelection()
                DataGridView1.Rows(clickedRowIndex).Selected = True
            End If
        End If
    End Sub

    ' Event handler for cell value changes (handled in CellEndEdit)
    Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs)
        ' No implementation needed since editing is handled in CellEndEdit
    End Sub

    ' Store the original value before editing
    Private Sub DataGridView1_CellBeginEdit(sender As Object, e As DataGridViewCellCancelEventArgs) Handles DataGridView1.CellBeginEdit
        If e.RowIndex >= 0 AndAlso e.ColumnIndex >= 0 Then
            Dim cell = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex)
            cell.Tag = cell.Value ' Store the original value in the Tag property
        End If
    End Sub

    ' Event handler for CellEndEdit to update Total Time
    Private Sub DataGridView1_CellEndEdit(sender As Object, e As DataGridViewCellEventArgs)
        Try
            ' Ensure the edited cell is valid
            If e.RowIndex >= 0 AndAlso e.ColumnIndex >= 0 Then
                Dim cell = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex)
                Dim oldValue = cell.Tag ' Retrieve the old value stored in Tag
                Dim newValue = cell.Value

                Dim currentRow As DataGridViewRow = DataGridView1.Rows(e.RowIndex)
                If IsTotalsRow(currentRow) Then
                    ' Do not process totals row
                    Return
                End If

                ' Record the cell edit action
                Dim action As New DataGridViewAction With {
                    .Type = DataGridViewAction.ActionType.CellEdit,
                    .RowIndex = e.RowIndex,
                    .ColumnIndex = e.ColumnIndex,
                    .OldValue = oldValue,
                    .NewValue = newValue,
                    .RowId = currentRow.Cells(idColumnIndex).Value.ToString()
                }
                undoStack.Push(action)
                redoStack.Clear() ' Clear redo stack when a new action is performed
                UpdateUndoRedoMenuItems()

                ' Update Total Time if Start Time or Finish Time is edited
                If e.ColumnIndex = DataGridView1.Columns("startTimeColumn").Index OrElse e.ColumnIndex = DataGridView1.Columns("finishTimeColumn").Index Then
                    UpdateTotalTimeForRow(currentRow)
                End If

                ' Update the corresponding data in originalRows
                UpdateOriginalRowFromDataGridViewRow(currentRow)

                ' Update totals and save data
                UpdateTotals()
                SaveData()
            End If
        Catch ex As Exception
            MessageBox.Show("An error occurred while editing a cell: " & ex.Message)
        End Try
    End Sub

    ' Event handler for DataGridView CellClick to update move menu items
    Private Sub DataGridView1_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellClick
        If e.RowIndex >= 0 AndAlso e.RowIndex < DataGridView1.Rows.Count Then
            UpdateMoveMenuItems()
        End If
    End Sub

    ' Event handler for UserDeletingRow to prevent deletion of the totals row
    Private Sub DataGridView1_UserDeletingRow(sender As Object, e As DataGridViewRowCancelEventArgs)
        If IsTotalsRow(e.Row) Then
            MessageBox.Show("The totals row cannot be deleted.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
            e.Cancel = True ' Cancel the deletion
        Else
            ' Record the delete action for Undo
            Dim rowData As Object() = New Object(DataGridView1.Columns.Count - 1) {}
            For i As Integer = 0 To DataGridView1.Columns.Count - 1
                rowData(i) = e.Row.Cells(i).Value
            Next

            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.RowDeleted,
                .RowIndex = e.Row.Index,
                .RowData = rowData,
                .RowId = e.Row.Cells(idColumnIndex).Value.ToString()
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack
            UpdateUndoRedoMenuItems()

            ' Remove from originalRows
            Dim originalRow = originalRows.FirstOrDefault(Function(row) row(idColumnIndex).ToString() = action.RowId)
            If originalRow IsNot Nothing Then
                originalRows.Remove(originalRow)
            End If
        End If
    End Sub

    ' Event handler for SelectionChanged to deselect the totals row
    Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) Handles DataGridView1.SelectionChanged
        If DataGridView1.CurrentRow IsNot Nothing AndAlso IsTotalsRow(DataGridView1.CurrentRow) Then
            ' Deselect the totals row
            DataGridView1.CurrentCell = Nothing
        End If
    End Sub

    ' Event handler for Sorted event
    Private Sub DataGridView1_Sorted(sender As Object, e As EventArgs)
        ' Move totals row to the bottom
        Dim totalsRow As DataGridViewRow = GetTotalsRow()
        If totalsRow IsNot Nothing Then
            DataGridView1.Rows.Remove(totalsRow)
            DataGridView1.Rows.Add(totalsRow)
        End If
    End Sub

    ' Custom sorting logic for the DataGridView
    Private Sub DataGridView1_SortCompare(sender As Object, e As DataGridViewSortCompareEventArgs)
        Dim dgv As DataGridView = CType(sender, DataGridView)
        Dim row1 As DataGridViewRow = dgv.Rows(e.RowIndex1)
        Dim row2 As DataGridViewRow = dgv.Rows(e.RowIndex2)

        ' Check if one of the rows is the totals row
        Dim isRow1Totals As Boolean = IsTotalsRow(row1)
        Dim isRow2Totals As Boolean = IsTotalsRow(row2)

        If isRow1Totals AndAlso Not isRow2Totals Then
            ' Totals row should always come after data rows
            e.SortResult = 1
            e.Handled = True
            Return
        ElseIf Not isRow1Totals AndAlso isRow2Totals Then
            e.SortResult = -1
            e.Handled = True
            Return
        ElseIf isRow1Totals AndAlso isRow2Totals Then
            ' Both rows are totals rows; treat as equal
            e.SortResult = 0
            e.Handled = True
            Return
        End If

        ' Now proceed with custom comparison for specific columns
        If e.Column.Name = "dateColumn" Then
            Dim date1 As DateTime
            Dim date2 As DateTime
            If DateTime.TryParse(e.CellValue1?.ToString(), date1) AndAlso DateTime.TryParse(e.CellValue2?.ToString(), date2) Then
                e.SortResult = date1.CompareTo(date2)
            Else
                e.SortResult = String.Compare(e.CellValue1?.ToString(), e.CellValue2?.ToString())
            End If
            e.Handled = True
        ElseIf e.Column.Name = "startTimeColumn" OrElse e.Column.Name = "finishTimeColumn" Then
            Dim time1 As TimeSpan
            Dim time2 As TimeSpan
            If TimeSpan.TryParse(e.CellValue1?.ToString(), time1) AndAlso TimeSpan.TryParse(e.CellValue2?.ToString(), time2) Then
                e.SortResult = time1.CompareTo(time2)
            Else
                e.SortResult = String.Compare(e.CellValue1?.ToString(), e.CellValue2?.ToString())
            End If
            e.Handled = True
        ElseIf e.Column.Name = "amountColumn" Then
            Dim amount1 As Decimal
            Dim amount2 As Decimal
            Dim amountStr1 As String = e.CellValue1?.ToString().Replace("£", "")
            Dim amountStr2 As String = e.CellValue2?.ToString().Replace("£", "")
            If Decimal.TryParse(amountStr1, amount1) AndAlso Decimal.TryParse(amountStr2, amount2) Then
                e.SortResult = amount1.CompareTo(amount2)
            Else
                e.SortResult = String.Compare(e.CellValue1?.ToString(), e.CellValue2?.ToString())
            End If
            e.Handled = True
        Else
            ' For other columns, use default sorting
            e.Handled = False
        End If
    End Sub

    ' Event handler for ColumnHeaderMouseClick to track sort column and direction
    Private Sub DataGridView1_ColumnHeaderMouseClick(sender As Object, e As DataGridViewCellMouseEventArgs)
        Dim clickedColumn As DataGridViewColumn = DataGridView1.Columns(e.ColumnIndex)

        ' Determine the new sort direction
        If lastSortColumn Is clickedColumn Then
            ' Toggle sort direction if the same column is clicked
            If lastSortDirection = ListSortDirection.Ascending Then
                lastSortDirection = ListSortDirection.Descending
            Else
                lastSortDirection = ListSortDirection.Ascending
            End If
        Else
            ' Default to ascending when a new column is clicked
            lastSortDirection = ListSortDirection.Ascending
        End If

        lastSortColumn = clickedColumn

        ' Apply the sort
        DataGridView1.Sort(clickedColumn, lastSortDirection)
    End Sub
#End Region

#Region "Context Menu Event Handlers"
    ' Event handler for adding a row
    Private Sub menuItemAddRow_Click(sender As Object, e As EventArgs) Handles menuItemAddRow.Click
        AddRow()
        UpdateMoveMenuItems() ' Ensure move menu items are updated

        ' Record the add row action
        Dim action As New DataGridViewAction With {
            .Type = DataGridViewAction.ActionType.RowAdded,
            .RowId = (nextId - 1).ToString()
        }
        undoStack.Push(action)
        redoStack.Clear() ' Clear redo stack when a new action is performed
        UpdateUndoRedoMenuItems()
    End Sub

    ' Event handler for deleting a row
    Private Sub menuItemDeleteRow_Click(sender As Object, e As EventArgs) Handles menuItemDeleteRow.Click
        If clickedRowIndex >= 0 AndAlso clickedRowIndex < DataGridView1.Rows.Count Then
            Dim selectedRow As DataGridViewRow = DataGridView1.Rows(clickedRowIndex)
            If IsTotalsRow(selectedRow) Then
                MessageBox.Show("The totals row cannot be deleted.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                Return
            End If

            ' Get the ID of the row to delete
            Dim idToDelete As String = selectedRow.Cells(idColumnIndex).Value.ToString()

            ' Record the delete row action
            Dim rowData As Object() = New Object(DataGridView1.Columns.Count - 1) {}
            For i As Integer = 0 To DataGridView1.Columns.Count - 1
                rowData(i) = selectedRow.Cells(i).Value
            Next

            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.RowDeleted,
                .RowData = rowData,
                .RowId = idToDelete
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack when a new action is performed
            UpdateUndoRedoMenuItems()

            ' Remove the data from originalRows
            Dim originalRow = originalRows.FirstOrDefault(Function(row) row(idColumnIndex).ToString() = idToDelete)
            If originalRow IsNot Nothing Then
                originalRows.Remove(originalRow)
            End If

            ' Refresh the DataGridView display
            DisplayAllData()

            UpdateTotals() ' Update totals after deletion
            UpdateMoveMenuItems()
            SaveData()
        End If
    End Sub

    ' Event handler for moving a row up
    Private Sub menuItemMoveUp_Click(sender As Object, e As EventArgs)
        ' Since the rows are sorted by date, manual moving is not applicable.
        MessageBox.Show("Rows are sorted by date and cannot be moved manually.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information)
    End Sub

    ' Event handler for moving a row down
    Private Sub menuItemMoveDown_Click(sender As Object, e As EventArgs)
        ' Since the rows are sorted by date, manual moving is not applicable.
        MessageBox.Show("Rows are sorted by date and cannot be moved manually.", "Information", MessageBoxButtons.OK, MessageBoxIcon.Information)
    End Sub

    ' Event handler for duplicating a row
    Private Sub menuItemDuplicateRow_Click(sender As Object, e As EventArgs) Handles menuItemDuplicateRow.Click
        If clickedRowIndex >= 0 AndAlso clickedRowIndex < DataGridView1.Rows.Count Then
            Dim selectedRow As DataGridViewRow = DataGridView1.Rows(clickedRowIndex)
            If IsTotalsRow(selectedRow) Then
                MessageBox.Show("The totals row cannot be duplicated.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
                Return
            End If

            ' Duplicate the selected row
            Dim rowValues As Object() = New Object(selectedRow.Cells.Count - 1) {}
            For i As Integer = 0 To selectedRow.Cells.Count - 1
                rowValues(i) = selectedRow.Cells(i).Value
            Next

            ' Assign a new unique ID to the duplicated row
            rowValues(idColumnIndex) = nextId.ToString()
            nextId += 1

            ' Add the duplicated row to originalRows
            originalRows.Add(rowValues)

            ' Refresh the DataGridView display
            DisplayAllData()

            UpdateTotals() ' Update totals after duplication
            UpdateMoveMenuItems() ' Update move menu items

            ' Record the duplicate row action
            Dim action As New DataGridViewAction With {
                .Type = DataGridViewAction.ActionType.RowDuplicated,
                .RowId = rowValues(idColumnIndex).ToString(),
                .RowData = rowValues
            }
            undoStack.Push(action)
            redoStack.Clear() ' Clear redo stack when a new action is performed
            UpdateUndoRedoMenuItems()

            SaveData()
        End If
    End Sub

    ' Event handler for the Undo menu item
    Private Sub menuItemUndo_Click(sender As Object, e As EventArgs) Handles menuItemUndo.Click
        If undoStack.Count > 0 Then
            Dim action = undoStack.Pop
            PerformUndo(action)
            redoStack.Push(action)
            UpdateUndoRedoMenuItems()
        End If
    End Sub

    ' Event handler for the Redo menu item
    Private Sub menuItemRedo_Click(sender As Object, e As EventArgs) Handles menuItemRedo.Click
        If redoStack.Count > 0 Then
            Dim action = redoStack.Pop
            PerformRedo(action)
            undoStack.Push(action)
            UpdateUndoRedoMenuItems()
        End If
    End Sub
#End Region

#Region "Data Management Methods"
    ' Method to load data
    Private Sub LoadData()
        ' Clear existing rows
        originalRows.Clear()
        nextId = 1 ' Reset ID counter

        ' Check if the file exists
        If File.Exists("data.csv") Then
            Using reader As New StreamReader("data.csv")
                ' Read header line
                Dim header As String = reader.ReadLine()

                ' Read rows
                While Not reader.EndOfStream
                    Dim line As String = reader.ReadLine()
                    Dim values As String() = line.Split(","c)
                    If values.Length < 8 Then
                        Continue While ' Skip invalid rows
                    End If

                    ' Initialize an array with the correct number of elements
                    Dim totalColumns As Integer = DataGridView1.Columns.Count
                    Dim extendedValues As Object() = New Object(totalColumns - 1) {}

                    ' Map columns by name
                    For Each column As DataGridViewColumn In DataGridView1.Columns
                        Select Case column.Name
                            Case "nameColumn"
                                extendedValues(column.Index) = values(0)
                            Case "typeColumn"
                                extendedValues(column.Index) = values(1)
                            Case "dateColumn"
                                extendedValues(column.Index) = values(2)
                            Case "startTimeColumn"
                                extendedValues(column.Index) = values(3)
                            Case "finishTimeColumn"
                                extendedValues(column.Index) = values(4)
                            Case "totalTimeColumn"
                                extendedValues(column.Index) = values(5)
                            Case "amountColumn"
                                extendedValues(column.Index) = values(6)
                            Case "commentColumn"
                                extendedValues(column.Index) = values(7)
                            Case TOTALS_FLAG_COLUMN_NAME
                                extendedValues(column.Index) = ""
                            Case ID_COLUMN_NAME
                                extendedValues(column.Index) = nextId.ToString()
                                nextId += 1
                            Case Else
                                extendedValues(column.Index) = ""
                        End Select
                    Next

                    ' Add to originalRows
                    originalRows.Add(extendedValues)
                End While
            End Using
        End If
    End Sub

    ' Method to save data
    Private Sub SaveData()
        Try
            ' Save data to a CSV file
            Using writer As New StreamWriter("data.csv")
                ' Write header
                writer.WriteLine("Name,Type,Date,Start Time,Finish Time,Total Time,Amount,Comment")

                ' Write rows from originalRows
                For Each rowValues As Object() In originalRows
                    Dim name = If(rowValues(DataGridView1.Columns("nameColumn").Index), "").ToString()
                    Dim type = If(rowValues(DataGridView1.Columns("typeColumn").Index), "").ToString()
                    Dim dateValue = If(rowValues(DataGridView1.Columns("dateColumn").Index), "").ToString()
                    Dim startTime = If(rowValues(DataGridView1.Columns("startTimeColumn").Index), "").ToString()
                    Dim finishTime = If(rowValues(DataGridView1.Columns("finishTimeColumn").Index), "").ToString()
                    Dim totalTime = If(rowValues(DataGridView1.Columns("totalTimeColumn").Index), "").ToString()
                    Dim amount = If(rowValues(DataGridView1.Columns("amountColumn").Index), "").ToString()
                    Dim comment = If(rowValues(DataGridView1.Columns("commentColumn").Index), "").ToString()

                    writer.WriteLine($"{name},{type},{dateValue},{startTime},{finishTime},{totalTime},{amount},{comment}")
                Next
            End Using
        Catch ex As Exception
            MessageBox.Show("An error occurred while saving data: " & ex.Message)
        End Try
    End Sub

    ' Method to display all data in the DataGridView
    Private Sub DisplayAllData()
        Try
            ' Temporarily remove event handlers to prevent unwanted triggers during loading
            RemoveHandler DataGridView1.CellValueChanged, AddressOf DataGridView1_CellValueChanged
            RemoveHandler DataGridView1.CellEndEdit, AddressOf DataGridView1_CellEndEdit

            DataGridView1.Rows.Clear()

            ' Add the data from originalRows to the DataGridView
            For Each rowValues As Object() In originalRows
                DataGridView1.Rows.Add(rowValues)
            Next

            ' Add totals row after loading
            AddTotalsRow()
            UpdateTotals()

            ' Re-add event handlers after loading is complete
            AddHandler DataGridView1.CellValueChanged, AddressOf DataGridView1_CellValueChanged
            AddHandler DataGridView1.CellEndEdit, AddressOf DataGridView1_CellEndEdit

            ' Reapply the last sort
            ReapplyLastSort()

            UpdateMoveMenuItems()
        Catch ex As Exception
            MessageBox.Show("An error occurred while displaying data: " & ex.Message)
        End Try
    End Sub

    ' Method to reapply the last sort
    Private Sub ReapplyLastSort()
        If lastSortColumn IsNot Nothing Then
            ' Apply the sort
            DataGridView1.Sort(lastSortColumn, lastSortDirection)
        End If
    End Sub

    ' Method to update totals in the totals row
    Private Sub UpdateTotals()
        Try
            Dim totalDuration As TimeSpan = TimeSpan.Zero
            Dim totalAmount As Decimal = 0D

            For Each row As DataGridViewRow In DataGridView1.Rows
                If IsTotalsRow(row) Then
                    Continue For
                End If

                ' Calculate total time
                Dim totalTimeStr As String = row.Cells("totalTimeColumn").Value?.ToString()
                Dim totalTime As TimeSpan
                If TimeSpan.TryParseExact(totalTimeStr, "%h\:mm", CultureInfo.InvariantCulture, TimeSpanStyles.None, totalTime) Then
                    totalDuration += totalTime
                End If

                ' Calculate total amount
                Dim amountStr As String = row.Cells("amountColumn").Value?.ToString().Replace("£", "")
                Dim amount As Decimal
                If Decimal.TryParse(amountStr, amount) Then
                    totalAmount += amount
                End If
            Next

            ' Update totals row
            Dim totalsRow As DataGridViewRow = GetTotalsRow()
            If totalsRow IsNot Nothing Then
                Dim totalHours As Integer = CInt(Math.Floor(totalDuration.TotalHours))
                Dim totalMinutes As Integer = totalDuration.Minutes

                ' Display total hours and minutes, including hours over 24
                totalsRow.Cells("totalTimeColumn").Value = $"{totalHours}:{totalMinutes:D2}"
                totalsRow.Cells("amountColumn").Value = "£" & totalAmount.ToString("0.00")
            End If
        Catch ex As Exception
            MessageBox.Show("An error occurred while updating totals: " & ex.Message)
        End Try
    End Sub

    ' Method to add a totals row to the DataGridView
    Private Sub AddTotalsRow()
        ' Remove existing totals row if present
        Dim existingTotalsRow As DataGridViewRow = GetTotalsRow()
        If existingTotalsRow IsNot Nothing Then
            DataGridView1.Rows.Remove(existingTotalsRow)
        End If

        ' Initialize an array with the correct number of elements
        Dim totalColumns As Integer = DataGridView1.Columns.Count
        Dim totalsRowValues As Object() = New Object(totalColumns - 1) {}

        ' Assign values based on column names
        For i As Integer = 0 To totalColumns - 1
            Select Case DataGridView1.Columns(i).Name
                Case "nameColumn"
                    totalsRowValues(i) = "Total"
                Case "totalTimeColumn"
                    totalsRowValues(i) = "0:00"
                Case "amountColumn"
                    totalsRowValues(i) = "£0.00"
                Case TOTALS_FLAG_COLUMN_NAME
                    totalsRowValues(i) = "True"
                Case Else
                    totalsRowValues(i) = ""
            End Select
        Next

        DataGridView1.Rows.Add(totalsRowValues)

        Dim totalsRowView As DataGridViewRow = DataGridView1.Rows(DataGridView1.Rows.Count - 1)

        ' Set every cell in the totals row as read-only
        For Each cell As DataGridViewCell In totalsRowView.Cells
            cell.ReadOnly = True
        Next

        totalsRowView.DefaultCellStyle.BackColor = Color.LightGray
        totalsRowView.DefaultCellStyle.Font = New Font(DataGridView1.Font, FontStyle.Bold)

        ' Set the totals row flag in the hidden column
        totalsRowView.Cells(totalsFlagColumnIndex).Value = "True"
    End Sub

    ' Method to add a row to the DataGridView
    Private Sub AddRow()
        ' Example data to add
        Dim name As String = " "
        Dim type As String = " "
        Dim dateValue As DateTime = DateTime.Now ' Use current date
        Dim startTime As DateTime = DateTime.Now
        Dim finishTime As DateTime = DateTime.Now.AddHours(1)
        Dim totalTime As TimeSpan = finishTime - startTime
        Dim amount As Decimal = 0
        Dim comment As String = " "

        ' Create a row with the data
        Dim totalHours As Integer = CInt(Math.Floor(totalTime.TotalHours))
        Dim totalMinutes As Integer = totalTime.Minutes
        Dim totalTimeStr As String = $"{totalHours}:{totalMinutes:D2}"

        ' Initialize an array with the correct number of elements
        Dim totalColumns As Integer = DataGridView1.Columns.Count
        Dim rowValues As Object() = New Object(totalColumns - 1) {}

        ' Map columns by name
        For Each column As DataGridViewColumn In DataGridView1.Columns
            Select Case column.Name
                Case "nameColumn"
                    rowValues(column.Index) = name
                Case "typeColumn"
                    rowValues(column.Index) = type
                Case "dateColumn"
                    rowValues(column.Index) = dateValue.ToString("d")
                Case "startTimeColumn"
                    rowValues(column.Index) = startTime.ToString("HH:mm")
                Case "finishTimeColumn"
                    rowValues(column.Index) = finishTime.ToString("HH:mm")
                Case "totalTimeColumn"
                    rowValues(column.Index) = totalTimeStr
                Case "amountColumn"
                    rowValues(column.Index) = "£" & amount.ToString("0.00")
                Case "commentColumn"
                    rowValues(column.Index) = comment
                Case TOTALS_FLAG_COLUMN_NAME
                    rowValues(column.Index) = ""
                Case ID_COLUMN_NAME
                    rowValues(column.Index) = nextId.ToString()
                    nextId += 1
                Case Else
                    rowValues(column.Index) = ""
            End Select
        Next

        ' Add the new row data to originalRows
        originalRows.Add(rowValues)

        ' Refresh the DataGridView display
        DisplayAllData()

        ' Update totals
        UpdateTotals()
        SaveData()
    End Sub

    ' Method to update Total Time for a row
    Private Sub UpdateTotalTimeForRow(currentRow As DataGridViewRow)
        Dim startTimeStr As String = currentRow.Cells("startTimeColumn").Value?.ToString()
        Dim finishTimeStr As String = currentRow.Cells("finishTimeColumn").Value?.ToString()
        Dim startTime As DateTime
        Dim finishTime As DateTime

        ' Try parsing Start Time and Finish Time cell values
        If DateTime.TryParseExact(startTimeStr, "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, startTime) AndAlso
           DateTime.TryParseExact(finishTimeStr, "HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, finishTime) Then
            ' Calculate Total Time
            Dim totalTime As TimeSpan = finishTime - startTime
            If totalTime.TotalMinutes < 0 Then
                ' If totalTime is negative, add 24 hours (handles times past midnight)
                totalTime = totalTime.Add(TimeSpan.FromHours(24))
            End If
            ' Update Total Time cell value
            Dim totalHours As Integer = CInt(Math.Floor(totalTime.TotalHours))
            Dim totalMinutes As Integer = totalTime.Minutes
            currentRow.Cells("totalTimeColumn").Value = $"{totalHours}:{totalMinutes:D2}"
        Else
            ' If parsing fails, clear the Total Time cell
            currentRow.Cells("totalTimeColumn").Value = ""
        End If
    End Sub
#End Region

#Region "Undo and Redo Functionality"
    ' Perform the Undo action
    Private Sub PerformUndo(action As DataGridViewAction)
        Select Case action.Type
            Case DataGridViewAction.ActionType.CellEdit
                ' Find the row in originalRows
                Dim originalRow = originalRows.FirstOrDefault(Function(row) row(idColumnIndex).ToString() = action.RowId)
                If originalRow IsNot Nothing Then
                    originalRow(action.ColumnIndex) = action.OldValue
                End If

            Case DataGridViewAction.ActionType.RowAdded
                ' Remove the row from originalRows
                Dim originalRow = originalRows.FirstOrDefault(Function(row) row(idColumnIndex).ToString() = action.RowId)
                If originalRow IsNot Nothing Then
                    originalRows.Remove(originalRow)
                End If

            Case DataGridViewAction.ActionType.RowDeleted
                ' Re-add the row to originalRows
                originalRows.Add(action.RowData)

            Case DataGridViewAction.ActionType.RowDuplicated
                ' Remove the duplicated row from originalRows
                Dim originalRow = originalRows.FirstOrDefault(Function(row) row(idColumnIndex).ToString() = action.RowId)
                If originalRow IsNot Nothing Then
                    originalRows.Remove(originalRow)
                End If
        End Select

        ' Refresh the DataGridView display
        DisplayAllData()
        SaveData()
    End Sub

    ' Perform the Redo action
    Private Sub PerformRedo(action As DataGridViewAction)
        Select Case action.Type
            Case DataGridViewAction.ActionType.CellEdit
                ' Find the row in originalRows
                Dim originalRow = originalRows.FirstOrDefault(Function(row) row(idColumnIndex).ToString() = action.RowId)
                If originalRow IsNot Nothing Then
                    originalRow(action.ColumnIndex) = action.NewValue
                End If

            Case DataGridViewAction.ActionType.RowAdded
                ' Re-add the row to originalRows
                originalRows.Add(action.RowData)

            Case DataGridViewAction.ActionType.RowDeleted
                ' Remove the row from originalRows
                Dim originalRow = originalRows.FirstOrDefault(Function(row) row(idColumnIndex).ToString() = action.RowId)
                If originalRow IsNot Nothing Then
                    originalRows.Remove(originalRow)
                End If

            Case DataGridViewAction.ActionType.RowDuplicated
                ' Re-add the duplicated row to originalRows
                originalRows.Add(action.RowData)
        End Select

        ' Refresh the DataGridView display
        DisplayAllData()
        SaveData()
    End Sub

    ' Method to update Undo and Redo menu items
    Private Sub UpdateUndoRedoMenuItems()
        menuItemUndo.Enabled = undoStack.Count > 0
        menuItemRedo.Enabled = redoStack.Count > 0
    End Sub
#End Region

#Region "Helper Methods"
    ' Helper method to update a row in originalRows from a DataGridViewRow
    Private Sub UpdateOriginalRowFromDataGridViewRow(dgvRow As DataGridViewRow)
        Dim idValue As String = dgvRow.Cells(idColumnIndex).Value.ToString()
        Dim originalRow = originalRows.FirstOrDefault(Function(row) row(idColumnIndex).ToString() = idValue)
        If originalRow IsNot Nothing Then
            For i As Integer = 0 To DataGridView1.Columns.Count - 1
                originalRow(i) = dgvRow.Cells(i).Value
            Next
        End If
    End Sub

    ' Helper method to check if a row is the totals row
    Private Function IsTotalsRow(row As DataGridViewRow) As Boolean
        Return row.Cells(totalsFlagColumnIndex).Value IsNot Nothing AndAlso row.Cells(totalsFlagColumnIndex).Value.ToString() = "True"
    End Function

    ' Helper method to get the totals row
    Private Function GetTotalsRow() As DataGridViewRow
        For Each row As DataGridViewRow In DataGridView1.Rows
            If IsTotalsRow(row) Then
                Return row
            End If
        Next
        Return Nothing
    End Function

    ' Method to update the status of move menu items
    Private Sub UpdateMoveMenuItems()
        menuItemMoveUp.Enabled = False
        menuItemMoveDown.Enabled = False
    End Sub
#End Region

#Region "Filter DataGridView"
    ' Method to filter DataGridView rows based on selected month and year
    Private Sub FilterDataGrid(selectedMonth As Integer, selectedYear As Integer)
        Try
            ' Temporarily remove event handlers to prevent unwanted triggers during filtering
            RemoveHandler DataGridView1.CellValueChanged, AddressOf DataGridView1_CellValueChanged
            RemoveHandler DataGridView1.CellEndEdit, AddressOf DataGridView1_CellEndEdit

            DataGridView1.Rows.Clear()

            For Each rowValues As Object() In originalRows
                Dim dateValue As DateTime
                If DateTime.TryParse(rowValues(dateColumnIndex)?.ToString(), dateValue) Then
                    ' Check if the row's date matches the selected month and year
                    If dateValue.Month = selectedMonth And dateValue.Year = selectedYear Then
                        DataGridView1.Rows.Add(rowValues)
                    End If
                End If
            Next

            ' Add totals row after filtering
            AddTotalsRow()
            UpdateTotals()

            ' Re-add event handlers after filtering is complete
            AddHandler DataGridView1.CellValueChanged, AddressOf DataGridView1_CellValueChanged
            AddHandler DataGridView1.CellEndEdit, AddressOf DataGridView1_CellEndEdit

            ' Reapply the last sort
            ReapplyLastSort()

            UpdateMoveMenuItems()
        Catch ex As Exception
            MessageBox.Show("An error occurred during filtering: " & ex.Message)
        End Try
    End Sub
#End Region

#Region "Button Click Events"
    ' Event handler for resetting the DateTimePicker to current date
    Private Sub CTMReset_Click(sender As Object, e As EventArgs) Handles CTMReset.Click
        DateTimePicker01.Value = DateTime.Now
    End Sub

    ' Event handler for the btnGo button click to reload data
    Private Sub btnGo_Click(sender As Object, e As EventArgs) Handles btnGo.Click
        ' Temporarily remove the DateTimePicker ValueChanged event handler
        RemoveHandler DateTimePicker01.ValueChanged, AddressOf DateTimePicker01_ValueChanged

        ' Load data
        LoadData()

        ' Display all data
        DisplayAllData()

        ' Reset DateTimePicker to current date
        DateTimePicker01.Value = DateTime.Now

        ' Re-attach the DateTimePicker ValueChanged event handler
        AddHandler DateTimePicker01.ValueChanged, AddressOf DateTimePicker01_ValueChanged
    End Sub
#End Region
End Class

#Region "DataGridViewAction Class"
' Class to represent actions for undo and redo
Public Class DataGridViewAction
    Public Enum ActionType
        CellEdit
        RowAdded
        RowDeleted
        RowDuplicated
    End Enum

    ' Properties for the action
    Public Property Type As ActionType

    ' For cell edits
    Public Property RowIndex As Integer
    Public Property ColumnIndex As Integer
    Public Property OldValue As Object
    Public Property NewValue As Object

    ' For row addition and deletion
    Public Property RowData As Object() ' Required for RowDeleted and RowAdded actions
    Public Property RowId As String ' To identify the row in originalRows
End Class
#End Region


This is my code. 

I have removed menuItemMoveUp & menuItemMoveDown from my context menu as they no longer serve any purpose. Can you go through my code and tidy it up, reoving all references and code for menuItemMoveUp & menuItemMoveDown