Here is my python code, identify code logic errors in it, data parsing errors, data handling, data management, synchronization, update and similar related errors. "from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QTabWidget, QHBoxLayout,
    QToolBar, QMenu, QToolButton, QListWidget, QFileDialog, QTableView,
    QSplitter, QLineEdit, QLabel, QComboBox, QMessageBox, QListWidgetItem,
    QPushButton, QInputDialog, QSpinBox, QScrollArea
)
from PySide6.QtGui import QIcon, QFont, QBrush, QColor
from PySide6.QtCore import Qt, QSize, QAbstractTableModel, Signal, QModelIndex
import sys
import pandas as pd
import numpy as np
import pyEDM
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas


class PandasModel(QAbstractTableModel):
    def data(self, index, role=Qt.DisplayRole):
        if index.isValid():
            value = self._data.iloc[index.row(), index.column()]
            if role == Qt.DisplayRole:
                if pd.api.types.is_datetime64_any_dtype(self._data.dtypes[index.column()]):
                    # Check if the value has a custom string format (indicating it's been formatted)
                    if isinstance(value, str):
                        return value
                    # If it's a datetime object, format it as 'YYYY-MM-DD'
                    return value.strftime('%Y-%m-%d') if pd.notnull(value) else ''
                return str(value)
            elif role == Qt.BackgroundRole and pd.isna(value):
                return QBrush(QColor(255, 111, 97))  # Light Coral for NaN values
        return None

    def setData(self, index, value, role=Qt.EditRole):
        if index.isValid() and role == Qt.EditRole:
            column_dtype = self._data.dtypes[index.column()]
            try:
                if pd.api.types.is_integer_dtype(column_dtype):
                    cast_value = int(value)
                elif pd.api.types.is_float_dtype(column_dtype):
                    cast_value = float(value)
                elif pd.api.types.is_datetime64_any_dtype(column_dtype):
                    cast_value = pd.to_datetime(value)
                else:
                    cast_value = str(value)
                self._data.iloc[index.row(), index.column()] = cast_value
                self.dataChanged.emit(index, index, [Qt.DisplayRole])
                return True
            except ValueError:
                self._handle_error("Invalid data type entered.")
                return False
        elif role == Qt.BackgroundRole:
            # Handle background color changes (e.g., for error highlighting)
            return True
        return False

    def flags(self, index):
        return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable

    def headerData(self, col, orientation, role):
        if orientation == Qt.Horizontal and role == Qt.DisplayRole:
            return self._data.columns[col]
        return None

    def update_column_dtype(self, column_index, new_dtype):
        # Instead of directly modifying the DataFrame, emit a signal
        self.column_dtype_changed.emit(column_index, new_dtype)

    def _handle_error(self, message):
        print(message)
        self.error_occurred.emit(message)


class VariableViewer(QWidget):
    variable_changed = Signal(int, str, str, str)  # Added a parameter for date_format
    variable_added = Signal()
    variable_deleted = Signal(int)
    row_added = Signal()
    row_deleted = Signal()

    def __init__(self):
        super().__init__()
        self.layout = QVBoxLayout(self)
        self.variable_list = QListWidget()
        self.layout.addWidget(self.variable_list)

        self._add_buttons()
        self._add_row_buttons()

    def _add_buttons(self):
        button_layout = QHBoxLayout()
        self.add_variable_button = QPushButton("Add Variable")
        self.add_variable_button.clicked.connect(self.variable_added.emit)
        self.delete_variable_button = QPushButton("Delete Variable")
        self.delete_variable_button.clicked.connect(self._on_delete_variable)
        button_layout.addWidget(self.add_variable_button)
        button_layout.addWidget(self.delete_variable_button)
        self.layout.addLayout(button_layout)

    def _add_row_buttons(self):
        row_button_layout = QHBoxLayout()
        self.add_row_button = QPushButton("Add Row")
        self.add_row_button.clicked.connect(self.row_added.emit)
        self.delete_row_button = QPushButton("Delete Row")
        self.delete_row_button.clicked.connect(self.row_deleted.emit)
        row_button_layout.addWidget(self.add_row_button)
        row_button_layout.addWidget(self.delete_row_button)
        self.layout.addLayout(row_button_layout)

    def populate_variables(self, column_names, data_types):
        self.variable_list.clear()
        for i, col in enumerate(column_names):
            item = QListWidgetItem()
            widget = self._create_variable_widget(i, col, data_types[col])
            item.setSizeHint(widget.sizeHint())
            self.variable_list.addItem(item)
            self.variable_list.setItemWidget(item, widget)

    def _create_variable_widget(self, index, name, dtype):
        widget = QWidget()
        layout = QHBoxLayout(widget)

        name_edit = QLineEdit(name)
        name_edit.setObjectName(f"name_edit_{index}")
        name_edit.editingFinished.connect(
            lambda idx=index, widget=name_edit: self._emit_variable_changed(idx, "name", widget.text())
        )

        type_combo = QComboBox()
        type_combo.setObjectName(f"type_combo_{index}")
        type_combo.addItems(["datetime", "float", "int", "str"])
        type_combo.setCurrentText(self._convert_dtype_to_str(dtype))

        date_format_combo = QComboBox()
        date_format_combo.setObjectName(f"date_format_combo_{index}")
        date_format_combo.addItems(["Auto", "YYYY-MM-DD", "DD-MM-YYYY", "MM-DD-YYYY", "YYYY", "YYYY-MM", "Month YYYY"])
        date_format_combo.setVisible(self._convert_dtype_to_str(dtype) == "datetime")

        type_combo.currentTextChanged.connect(
            lambda text, idx=index, date_combo=date_format_combo: self._on_type_combo_changed(idx, text, date_combo)
        )

        date_format_combo.currentTextChanged.connect(
            lambda text, idx=index: self._emit_variable_changed(idx, "date_format", text)
        )

        layout.addWidget(name_edit)
        layout.addWidget(type_combo)
        layout.addWidget(date_format_combo)
        return widget

    def _on_type_combo_changed(self, index, new_type, date_combo):
        date_combo.setVisible(new_type == "datetime")
        self._emit_variable_changed(index, "type", new_type)

    def _emit_variable_changed(self, index, property_name, new_value):
        date_format = None
        if property_name == "type" and new_value == "datetime":
            date_format_combo = self.variable_list.itemWidget(self.variable_list.item(index)).findChild(QComboBox, f"date_format_combo_{index}")
            date_format = date_format_combo.currentText()
        self.variable_changed.emit(index, property_name, new_value, date_format)

    def _convert_dtype_to_str(self, dtype):
        dtype_str = str(dtype)
        if dtype_str.startswith("datetime"):
            return "datetime"
        elif dtype_str.startswith("float"):
            return "float"
        elif dtype_str.startswith("int"):
            return "int"
        elif dtype_str.startswith("object"):
            return "str"
        return dtype_str

    def _on_delete_variable(self):
        selected_items = self.variable_list.selectedItems()
        if selected_items:
            indices_to_delete = [self.variable_list.row(item) for item in selected_items]
            for index in sorted(indices_to_delete, reverse=True):
                self.variable_deleted.emit(index)
        else:
            QMessageBox.warning(self, "No Selection", "Please select one or more variables to delete.")

    def update_dtype_in_viewer(self, column_index, new_dtype):
        item = self.variable_list.item(column_index)
        widget = self.variable_list.itemWidget(item)
        if widget:
            type_combo = widget.findChild(QComboBox, f"type_combo_{column_index}")
            if type_combo:
                type_combo.setCurrentText(new_dtype)

class Command:
    def execute(self):
        raise NotImplementedError("Subclasses must implement execute method")

    def undo(self):
        raise NotImplementedError("Subclasses must implement undo method")


class AddRowCommand(Command):
    def __init__(self, df, model):
        self.df = df
        self.model = model
        self.new_row_index = None

    def execute(self):
        new_index = self.df.index.max() + 1 if not self.df.index.empty else 0  # Determine a suitable new index
        self.model.beginInsertRows(QModelIndex(), self.df.shape[0], self.df.shape[0])
        self.df.loc[new_index] = np.nan  # Add a new row with NaN values
        self.model._data = self.df
        self.model.endInsertRows()

    def undo(self):
        self.model.beginRemoveRows(QModelIndex(), self.df.shape[0] - 1, self.df.shape[0] - 1)
        self.df.drop(self.df.index[-1], inplace=True)  # Drop the last row (which was the added row)
        self.model._data = self.df
        self.model.endRemoveRows()


class DeleteRowCommand(Command):
    def __init__(self, df, model, row_index):
        self.df = df
        self.model = model
        self.row_index = row_index
        self.deleted_row = None
        self.deleted_index = None

    def execute(self):
        self.deleted_index = self.df.index[self.row_index]
        self.deleted_row = self.df.loc[[self.deleted_index]].copy()  # Use loc to get the row by label
        self.model.beginRemoveRows(QModelIndex(), self.row_index, self.row_index)
        self.df.drop(self.deleted_index, inplace=True)
        self.model._data = self.df
        self.model.endRemoveRows()

    def undo(self):
        self.model.beginInsertRows(QModelIndex(), self.row_index, self.row_index)
        self.df = pd.concat([self.df.iloc[:self.row_index], self.deleted_row, self.df.iloc[self.row_index:]])
        self.df.sort_index(inplace=True)  # Sort to restore original order if needed
        self.model._data = self.df
        self.model.endInsertRows()


class AddColumnCommand(Command):
    def __init__(self, df, model, column_name):
        self.df = df
        self.model = model
        self.column_name = column_name

    def execute(self):
        self.model.beginInsertColumns(QModelIndex(), self.df.shape[1], self.df.shape[1])  # Signal before change
        self.df[self.column_name] = np.nan  # Initialize with NaN
        self.model._data = self.df
        self.model.endInsertColumns()  # Signal after change

    def undo(self):
        self.model.beginRemoveColumns(QModelIndex(), self.df.shape[1] - 1, self.df.shape[1] - 1)  # Signal before change
        self.df.drop(columns=[self.column_name], inplace=True)
        self.model._data = self.df
        self.model.endRemoveColumns()  # Signal after change


class DeleteColumnCommand(Command):
    def __init__(self, df, model, column_index):
        self.df = df
        self.model = model
        self.column_index = column_index
        self.deleted_column = None
        self.deleted_column_name = None  # Store the column name

    def execute(self):
        self.deleted_column_name = self.df.columns[self.column_index]  # Store the column name
        self.deleted_column = self.df[self.deleted_column_name].copy()
        self.model.beginRemoveColumns(QModelIndex(), self.column_index, self.column_index)  # Signal before change
        self.df.drop(self.deleted_column_name, axis=1, inplace=True)
        self.model._data = self.df
        self.model.endRemoveColumns()  # Signal after change

    def undo(self):
        self.model.beginInsertColumns(QModelIndex(), self.column_index, self.column_index)  # Signal before change
        self.df.insert(self.column_index, self.deleted_column_name, self.deleted_column)
        self.model._data = self.df
        self.model.endInsertColumns()  # Signal after change


class DeleteMultipleRowsCommand(Command):
    def __init__(self, df, model, row_indices):
        self.df = df
        self.model = model
        self.row_indices = sorted(row_indices)
        self.deleted_rows = self.df.iloc[self.row_indices].copy()
        self.deleted_indices = self.df.index[self.row_indices]

    def execute(self):
        self.model.beginResetModel()  # Use beginResetModel for bulk changes
        self.df.drop(self.deleted_indices, inplace=True)
        self.model._data = self.df
        self.model.endResetModel()

    def undo(self):
        self.model.beginResetModel()
        # Insert deleted rows back at their original positions
        for i, idx in enumerate(self.deleted_indices):
            self.df.loc[idx] = self.deleted_rows.iloc[i]
        self.df.sort_index(inplace=True)  # Sort to restore original order
        self.model._data = self.df
        self.model.endResetModel()


class RenameColumnCommand(Command):
    def __init__(self, df, model, index, new_name):
        self.df = df
        self.model = model
        self.index = index
        self.old_name = df.columns[index]
        self.new_name = new_name

    def execute(self):
        self.df.rename(columns={self.old_name: self.new_name}, inplace=True)
        self.model._data = self.df
        self.model.headerDataChanged.emit(Qt.Horizontal, self.index, self.index)

    def undo(self):
        self.df.rename(columns={self.new_name: self.old_name}, inplace=True)
        self.model._data = self.df
        self.model.headerDataChanged.emit(Qt.Horizontal, self.index, self.index)


class ChangeColumnTypeCommand(Command):
    def execute(self):
        try:
            if self.new_dtype == "datetime":
                self._convert_to_datetime()
            elif self.new_dtype == "float":
                self.df[self.column_name] = pd.to_numeric(self.df[self.column_name], errors='coerce')
            elif self.new_dtype == "int":
                self.df[self.column_name] = pd.to_numeric(self.df[self.column_name], errors='coerce').astype('Int64')
            elif self.new_dtype == "str":
                self.df[self.column_name] = self.df[self.column_name].astype(str)

            self._check_for_errors()
            self.model.layoutChanged.emit()
        except Exception as e:
            self.model._handle_error(f"Error converting type: {e}")

    def _convert_to_datetime(self):
        if self.date_format == "Auto":
            self.df[self.column_name] = pd.to_datetime(self.df[self.column_name], infer_datetime_format=True,
                                                       errors='coerce')
        else:
            format_dict = {
                "YYYY-MM-DD": "%Y-%m-%d",
                "DD-MM-YYYY": "%d-%m-%Y",
                "MM-DD-YYYY": "%m-%d-%Y",
                "YYYY": "%Y",
                "YYYY-MM": "%Y-%m",
                "Month YYYY": "%B %Y"
            }
            pandas_format = format_dict.get(self.date_format, "%Y-%m-%d")

            self.df[self.column_name] = pd.to_datetime(self.df[self.column_name], format=pandas_format, errors='coerce')

        # Update the column to display only the specified format
        if self.date_format != "Auto":
            self.df[self.column_name] = self.df[self.column_name].dt.strftime(pandas_format)

    def _check_for_errors(self):
        if self.df[self.column_name].isnull().any():
            error_indices = self.df[self.df[self.column_name].isnull()].index
            self._highlight_errors(error_indices)
            self.model._handle_error(
                f"Some values could not be converted to {self.new_dtype}. These have been replaced with NaN/NaT.")

    def _highlight_errors(self, row_indices):
        for row in row_indices:
            index = self.model.index(row, self.index)
            self.model.setData(index, QBrush(QColor(255, 111, 97)), Qt.BackgroundRole)

    def undo(self):
        try:
            if self.old_dtype.startswith("datetime"):
                self.df[self.column_name] = pd.to_datetime(self.df[self.column_name], errors='raise')
            elif self.old_dtype.startswith("float"):
                self.df[self.column_name] = pd.to_numeric(self.df[self.column_name], errors='raise').astype(float)
            elif self.old_dtype.startswith("int"):
                self.df[self.column_name] = pd.to_numeric(self.df[self.column_name], errors='raise').astype('Int64')
            elif self.old_dtype.startswith("object"):
                self.df[self.column_name] = self.df[self.column_name].astype(str)
            self.model.layoutChanged.emit()
            self._clear_error_highlighting()
        except Exception as e:
            self.model._handle_error(f"Error converting type: {e}")

    def _clear_error_highlighting(self):
        for row in range(self.model.rowCount()):
            index = self.model.index(row, self.index)
            self.model.setData(index, QBrush(QColor(0, 0, 0, 0)), Qt.BackgroundRole)

class CommandManager:
    def __init__(self):
        self.undo_stack = []
        self.redo_stack = []

    def execute(self, command):
        command.execute()
        self.undo_stack.append(command)
        self.redo_stack.clear()

    def undo(self):
        if self.undo_stack:
            command = self.undo_stack.pop()
            command.undo()
            self.redo_stack.append(command)

    def redo(self):
        if self.redo_stack:
            command = self.redo_stack.pop()
            command.execute()
            self.undo_stack.append(command)


class OptimalEmbeddingTab(QWidget):
    def run_simplex_projection(self):
        if self.main_window.df is None or self.column_combo.currentText() == "":
            return

        column = self.column_combo.currentText()
        lib_size = self.lib_size_spinbox.value()
        pred_step = self.pred_step_spinbox.value()

        # Prepare data for pyEDM
        data = self.main_window.df[[column]].copy()
        data.columns = ['x']  # pyEDM expects column name to be 'x'

        # Ensure the data index is reset (if necessary)
        data = data.reset_index(drop=True)

        # Define total_size for the pred parameter
        total_size = data.shape[0]

        # Use EmbedDimension to find optimal E
        embedding_results = pyEDM.EmbedDimension(
            dataFrame=data,
            columns="x",
            target="x",
            lib="1 {}".format(lib_size),
            pred="{} {}".format(lib_size + 1, total_size),
            maxE=self.embedding_dim_spinbox.value(),  # Use maxE from spinbox
            Tp=pred_step
        )

        # Extract E and rho values for plotting
        E_values = embedding_results['E']
        rho_values = embedding_results['rho']

        # Clear previous results and plot
        for i in reversed(range(self.results_layout.count())):
            self.results_layout.itemAt(i).widget().setParent(None)

        fig, ax = plt.subplots(figsize=(8, 6))
        ax.plot(E_values, rho_values, 'bo-', markersize=5)  # Plot all E values with line
        ax.set_xlabel('Embedding Dimension (E)')
        ax.set_ylabel('Forecast Skill (ρ)')
        ax.set_title('Simplex Projection Results')

        # Highlight the optimal E (optional)
        optimal_E_index = np.argmax(rho_values)
        optimal_E = E_values[optimal_E_index]
        optimal_rho = rho_values[optimal_E_index]
        ax.plot(optimal_E, optimal_rho, 'ro', markersize=10, label=f"Optimal E: {optimal_E}")
        ax.legend()

        canvas = FigureCanvas(fig)
        self.results_layout.addWidget(canvas)

        # Display E, rho, RMSE, and MAE (optional)
        result_label = QLabel(f"Optimal E: {optimal_E}, ρ: {optimal_rho:.4f}")
        self.results_layout.addWidget(result_label)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("EDM")
        self.setGeometry(100, 100, 800, 600)

        self.df = None
        self.model = None
        self.command_manager = CommandManager()
        self.variable_viewer = None  # Initialize here, but create later
        self.toolbar = None  # Initialize here, but create later

        self._setup_ui()

    def _setup_ui(self):
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        tabs_container = QWidget()
        tabs_container_layout = QVBoxLayout(tabs_container)
        tabs_container_layout.setAlignment(Qt.AlignLeft)
        tabs_container_layout.setContentsMargins(0, 0, 0, 0)

        tabs = QTabWidget()
        tabs.setTabPosition(QTabWidget.North)
        tabs.setTabShape(QTabWidget.Triangular)
        tabs.tabBar().setExpanding(True)

        data_editor_tab = self._create_data_editor_tab()
        optimal_embedding_tab = self._create_optimal_embedding_tab()

        tabs.addTab(data_editor_tab, "Data Editor")
        tabs.addTab(optimal_embedding_tab, "Optimal Embedding")

        tabs_container_layout.addWidget(tabs)
        main_layout.addWidget(tabs_container)

        tabs.currentChanged.connect(self._on_tab_changed)

    def _create_data_editor_tab(self):
        data_editor_widget = QWidget()
        layout = QVBoxLayout(data_editor_widget)

        toolbar_layout = QVBoxLayout()

        self.data_table_view = QTableView()
        self.data_table_view.horizontalHeader().setStretchLastSection(True)
        self.data_table_view.verticalHeader().setVisible(False)
        self.data_table_view.setEditTriggers(QTableView.DoubleClicked | QTableView.EditKeyPressed)

        self.variable_viewer = VariableViewer()
        self.variable_viewer.variable_changed.connect(self._on_variable_changed)
        self.variable_viewer.variable_added.connect(self._on_variable_added)
        self.variable_viewer.variable_deleted.connect(self._on_variable_deleted)
        self.variable_viewer.row_added.connect(self._on_add_row)
        self.variable_viewer.row_deleted.connect(self._on_delete_row)

        splitter = QSplitter(Qt.Horizontal)
        splitter.addWidget(self.data_table_view)
        splitter.addWidget(self.variable_viewer)

        self.toolbar = self._create_toolbar()
        toolbar_layout.addWidget(self.toolbar)
        toolbar_layout.addWidget(splitter)
        layout.addLayout(toolbar_layout)

        return data_editor_widget

    def _create_toolbar(self):
        toolbar = QToolBar("Main Toolbar", self)
        toolbar.setFloatable(False)
        toolbar.setMovable(False)
        toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        toolbar.setIconSize(QSize(16, 16))

        self._add_file_menu_to_toolbar(toolbar)
        self._add_edit_menu_to_toolbar(toolbar)
        self._update_button_states()

        return toolbar

    def _add_file_menu_to_toolbar(self, toolbar):
        file_button = QToolButton(self)
        file_button.setIcon(QIcon("/Users/sergiymuzyka/Desktop/Python/Python/P/folder--plus.png"))
        file_button.setText("File")
        file_button.setPopupMode(QToolButton.MenuButtonPopup)
        file_menu = QMenu(self)
        file_menu.addAction("Save", self.placeholder_action)
        file_menu.addAction("Load", self.load_csv)
        file_menu.addAction("Create New Data Set", self.create_new_data_set)
        file_button.setMenu(file_menu)
        toolbar.addWidget(file_button)

    def _add_edit_menu_to_toolbar(self, toolbar):
        edit_button = QToolButton(self)
        edit_button.setIcon(QIcon("/Users/sergiymuzyka/Desktop/Python/Python/P/hammer.png"))
        edit_button.setText("Edit")
        edit_button.setPopupMode(QToolButton.MenuButtonPopup)
        edit_menu = QMenu(self)
        edit_menu.addAction("Undo", self.undo)
        edit_menu.addAction("Redo", self.redo)
        edit_button.setMenu(edit_menu)
        toolbar.addWidget(edit_button)

    def _create_optimal_embedding_tab(self):
        return OptimalEmbeddingTab(self)

    def _on_tab_changed(self, index):
        self.toolbar.setVisible(index == 0)
        if index == 1:  # Optimal Embedding tab
            self.centralWidget().findChild(OptimalEmbeddingTab).update_column_list()

    def placeholder_action(self):
        print("Placeholder action triggered.")

    def load_csv(self):
        file_path, _ = QFileDialog.getOpenFileName(self, "Load CSV File", "", "CSV Files (*.csv)")
        if file_path:
            try:
                new_df = pd.read_csv(file_path)
                if self.df is not None:
                    reply = QMessageBox.question(
                        self, "Overwrite Data?",
                        "A DataFrame already exists. Do you want to overwrite it?",
                        QMessageBox.Yes | QMessageBox.No
                    )
                    if reply == QMessageBox.No:
                        return
                self.df = new_df
                if self.model is not None:
                    try:
                        self.model.column_dtype_changed.disconnect(self.variable_viewer.update_dtype_in_viewer)
                        self.model.error_occurred.disconnect(self._handle_error)  # Disconnect error signal
                    except TypeError:
                        pass  # No existing connection
                self.model = PandasModel(self.df)
                self.model.column_dtype_changed.connect(self.variable_viewer.update_dtype_in_viewer)
                self.model.error_occurred.connect(self._handle_error)
                self.data_table_view.setModel(self.model)
                self.variable_viewer.populate_variables(self.df.columns, self.df.dtypes)
                self._update_button_states()
            except Exception as e:
                self._handle_error(f"Error loading CSV: {e}")

    def _on_variable_changed(self, index, property_name, new_value, date_format=None):
        if self.df is not None:
            if property_name == "name":
                if new_value in self.df.columns:
                    QMessageBox.warning(self, "Duplicate Column", f"The column name '{new_value}' already exists.")
                    self.variable_viewer.populate_variables(self.df.columns, self.df.dtypes)
                else:
                    command = RenameColumnCommand(self.df, self.model, index, new_value)
                    self.command_manager.execute(command)
            elif property_name == "type":
                command = ChangeColumnTypeCommand(self.df, self.model, index, new_value, date_format)
                self.command_manager.execute(command)
            elif property_name == "date_format":
                if self.df.dtypes[self.df.columns[index]] == 'datetime64[ns]':
                    command = ChangeColumnTypeCommand(self.df, self.model, index, "datetime", new_value)
                    self.command_manager.execute(command)
            self._update_model_and_view()
            self._update_button_states()

    def create_new_data_set(self):
        if self.df is not None:
            reply = QMessageBox.question(
                self, "Overwrite Data?",
                "A DataFrame already exists. Do you want to overwrite it?",
                QMessageBox.Yes | QMessageBox.No
            )
            if reply == QMessageBox.No:
                return

        num_columns, ok1 = QInputDialog.getInt(self, "New Data Set", "Enter the number of columns:", 1, 1)
        num_rows, ok2 = QInputDialog.getInt(self, "New Data Set", "Enter the number of rows:", 1, 1)

        if not ok1 or not ok2:
            return

        try:
            columns = [f"Column{i + 1}" for i in range(num_columns)]
            data = [[np.nan] * num_columns for _ in range(num_rows)]  # Use np.nan instead of None
            self.df = pd.DataFrame(data, columns=columns)

            # Reset the model
            if self.model is not None:
                try:
                    self.model.column_dtype_changed.disconnect(self.variable_viewer.update_dtype_in_viewer)
                    self.model.error_occurred.disconnect(self._handle_error)
                except TypeError:
                    pass  # No existing connection
            self.model = PandasModel(self.df)
            self.model.column_dtype_changed.connect(self.variable_viewer.update_dtype_in_viewer)
            self.model.error_occurred.connect(self._handle_error)
            self.data_table_view.setModel(self.model)
            self.variable_viewer.populate_variables(self.df.columns, self.df.dtypes)
            self._update_button_states()

            QMessageBox.information(self, "Success", "New data set created successfully.")

        except Exception as e:
            self._handle_error(f"Error creating new data set: {e}")

    def _on_variable_added(self):
        if self.df is not None:
            base_name = "NewColumn"
            counter = 1
            new_col_name = f"{base_name}{counter}"
            while new_col_name in self.df.columns:
                counter += 1
                new_col_name = f"{base_name}{counter}"
            command = AddColumnCommand(self.df, self.model, new_col_name)
            self.command_manager.execute(command)
            self._update_model_and_view()
            self._update_button_states()

    def _on_variable_deleted(self, index):
        if self.df is not None and not self.df.empty and 0 <= index < len(self.df.columns):
            command = DeleteColumnCommand(self.df, self.model, index)
            self.command_manager.execute(command)
            self._update_model_and_view()
            self._update_button_states()
        else:
            QMessageBox.warning(self, "Invalid Operation", "Cannot delete the selected variable.")

    def _on_add_row(self):
        if self.df is not None:
            command = AddRowCommand(self.df, self.model)
            self.command_manager.execute(command)
            self._update_model_and_view()  # Ensure the model and view are updated
            self.data_table_view.scrollToBottom()  # Scroll to the newly added row
        else:
            QMessageBox.warning(self, "No Data", "Please load or create a DataFrame before trying to add a row.")

    def _on_delete_row(self):
        if self.df is not None and not self.df.empty:
            selected_rows = self.data_table_view.selectionModel().selectedRows()
            if selected_rows:
                rows_to_delete = sorted([row.row() for row in selected_rows], reverse=True)
                # Validate row indices
                valid_rows = [row for row in rows_to_delete if 0 <= row < len(self.df)]
                if not valid_rows:
                    QMessageBox.warning(self, "Invalid Selection", "Selected rows are invalid.")
                    return
                command = DeleteMultipleRowsCommand(self.df, self.model, valid_rows)
                self.command_manager.execute(command)
                self._update_model_and_view()  # Ensure the model and view are updated
            else:
                QMessageBox.warning(self, "No Selection", "Please select one or more rows to delete.")
        else:
            QMessageBox.warning(self, "No Data", "There are no rows to delete.")

    def _update_model_and_view(self):
        if self.df is not None:
            if self.model is None:
                self.model = PandasModel(self.df)
                self.model.column_dtype_changed.connect(self.variable_viewer.update_dtype_in_viewer)
                self.model.error_occurred.connect(self._handle_error)
                self.data_table_view.setModel(self.model)
            else:
                # Use beginResetModel for bulk changes or when the structure changes significantly
                self.model.beginResetModel()
                self.model._data = self.df
                self.model.endResetModel()
            self.variable_viewer.populate_variables(self.df.columns, self.df.dtypes)

    def _update_button_states(self):
        has_data = self.df is not None
        if self.variable_viewer:
            self.variable_viewer.add_variable_button.setEnabled(has_data)
            self.variable_viewer.delete_variable_button.setEnabled(has_data)
            self.variable_viewer.add_row_button.setEnabled(has_data)
            self.variable_viewer.delete_row_button.setEnabled(has_data)
        if self.toolbar:
            undo_action = self.toolbar.actions()[1] if len(self.toolbar.actions()) > 1 else None
            redo_action = self.toolbar.actions()[2] if len(self.toolbar.actions()) > 2 else None
            if undo_action:
                undo_action.setEnabled(bool(self.command_manager.undo_stack))
            if redo_action:
                redo_action.setEnabled(bool(self.command_manager.redo_stack))

    def _handle_error(self, message):
        print(message)
        QMessageBox.critical(self, "Error", message)

    def undo(self):
        self.command_manager.undo()
        self._update_model_and_view()
        self._update_button_states()  # Update button states after undo/redo

    def redo(self):
        self.command_manager.redo()
        self._update_model_and_view()
        self._update_button_states()  # Update button states after undo/redo

if __name__ == "__main__":
    app = QApplication(sys.argv)

    # Apply a custom style sheet for better visual appeal
    app.setStyleSheet("""
    /* Main colors and fonts */
    QWidget {
        background-color: #1e1e1e;
        color: #ffffff;
        font-family: 'Arial', 'Segoe UI', sans-serif;
        font-size: 14px;
    }

    QMainWindow {
        background-color: #121212;
    }

    /* Tab Widget */
    QTabWidget::pane {
        border: 1px solid #2a2a2a;
        background-color: #1e1e1e;
        border-radius: 0;
    }

    QTabBar::tab {
        background: #2a2a2a;
        color: #b0b0b0;
        border: none;
        padding: 8px 16px;
        margin-right: 2px;
        font-weight: bold;
        text-transform: uppercase;
    }

    QTabBar::tab:selected {
        background: #008080;  /* Teal */
        color: #ffffff;
    }

    QTabBar::tab:hover:!selected {
        background: #3a3a3a;
    }

    /* Toolbar */
    QToolBar {
        background-color: #2a2a2a;
        border: none;
        spacing: 6px;
        padding: 3px;
    }

    QToolButton {
        background-color: #2a2a2a;
        border: none;
        border-radius: 4px;
        padding: 5px;
        color: #ffffff;
    }

    QToolButton:hover {
        background-color: #3a3a3a;
    }

    QToolButton:pressed {
        background-color: #008080;  /* Teal */
    }

    /* Table View */
    QTableView {
        background-color: #1e1e1e;
        border: 1px solid #008080;  /* Teal border */
        gridline-color: #2a2a2a;
        selection-background-color: #008080;  /* Teal selection */
        selection-color: #ffffff;
    }

    QTableView::item {
        padding: 5px;
        border: 1px solid #2a2a2a;
    }

    QTableView::item:selected {
        background-color: rgba(0, 128, 128, 0.7);  /* Semi-transparent Teal */
        color: #ffffff;
    }

    QTableView::item:hover {
        background-color: rgba(255, 111, 97, 0.3);  /* Semi-transparent Coral */
    }

    QHeaderView::section {
        background-color: #2a2a2a;
        color: #ffffff;
        padding: 5px;
        border: 1px solid #008080;  /* Teal border */
        font-weight: bold;
    }

    QHeaderView::section:hover {
        background-color: #008080;  /* Teal */
    }

    /* Buttons */
    QPushButton {
        background-color: #ff6f61;  /* Coral */
        color: #ffffff;
        border: none;
        padding: 8px 16px;
        border-radius: 4px;
        font-weight: bold;
    }

    QPushButton:hover {
        background-color: #ff8f81;
    }

    QPushButton:pressed {
        background-color: #ff4f41;
    }

    /* Combo Box */
    QComboBox {
        background-color: #2a2a2a;
        border: 1px solid #3a3a3a;
        border-radius: 4px;
        padding: 5px;
        min-width: 6em;
        color: #ffffff;
    }

    QComboBox:hover {
        border-color: #008080;  /* Teal */
    }

    QComboBox::drop-down {
        subcontrol-origin: padding;
        subcontrol-position: top right;
        width: 20px;
        border-left: 1px solid #3a3a3a;
        border-top-right-radius: 3px;
        border-bottom-right-radius: 3px;
    }

    QComboBox::down-arrow {
        image: url(down_arrow_white.png);
    }

    /* Line Edit */
    QLineEdit {
        background-color: #2a2a2a;
        border: 1px solid #3a3a3a;
        border-radius: 4px;
        padding: 5px;
        color: #ffffff;
    }

    QLineEdit:focus {
        border-color: #008080;  /* Teal */
    }

    /* List Widget (Variable View) */
    QListWidget {
        background-color: #1e1e1e;
        border: 1px solid #008080;  /* Teal border */
        border-radius: 4px;
    }

    QListWidget::item {
        padding: 5px;
        border-bottom: 1px solid #2a2a2a;
    }

    QListWidget::item:selected {
        background-color: rgba(0, 128, 128, 0.7);  /* Semi-transparent Teal */
        color: #ffffff;
    }

    QListWidget::item:hover {
        background-color: rgba(255, 111, 97, 0.3);  /* Semi-transparent Coral */
    }

    /* Variable Viewer specific styles */
    #variable_list QWidget {
        background-color: #1e1e1e;
    }

    #variable_list QLineEdit {
        background-color: #2a2a2a;
        border: 1px solid #3a3a3a;
        border-radius: 4px;
        padding: 5px;
        color: #ffffff;
    }

    #variable_list QLineEdit:focus {
        border-color: #008080;  /* Teal */
    }

    #variable_list QComboBox {
        background-color: #2a2a2a;
        border: 1px solid #3a3a3a;
        border-radius: 4px;
        padding: 5px;
        color: #ffffff;
    }

    #variable_list QComboBox:hover {
        border-color: #ff6f61;  /* Coral */
    }

    /* Message Box */
    QMessageBox {
        background-color: #1e1e1e;
    }

    QMessageBox QLabel {
        color: #ffffff;
    }

    QMessageBox QPushButton {
        background-color: #008080;  /* Teal */
        color: #ffffff;
        border: none;
        padding: 8px 16px;
        border-radius: 4px;
        font-weight: bold;
    }

    QMessageBox QPushButton:hover {
        background-color: #009999;
    }

    /* Scrollbar */
    QScrollBar:vertical {
        border: none;
        background-color: #2a2a2a;
        width: 10px;
        margin: 0px;
    }

    QScrollBar::handle:vertical {
        background-color: #3a3a3a;
        min-height: 20px;
        border-radius: 5px;
    }

    QScrollBar::handle:vertical:hover {
        background-color: #008080;  /* Teal */
    }

    QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
        height: 0px;
    }

    QScrollBar:horizontal {
        border: none;
        background-color: #2a2a2a;
        height: 10px;
        margin: 0px;
    }

    QScrollBar::handle:horizontal {
        background-color: #3a3a3a;
        min-width: 20px;
        border-radius: 5px;
    }

    QScrollBar::handle:horizontal:hover {
        background-color: #008080;  /* Teal */
    }

    QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal {
        width: 0px;
    }

    /* Splitter */
    QSplitter::handle {
        background-color: #2a2a2a;
    }

    QSplitter::handle:hover {
        background-color: #008080;  /* Teal */
    }

    /* Status Bar */
    QStatusBar {
        background-color: #1e1e1e;
        color: #b0b0b0;
    }
    """)

    window = MainWindow()
    window.show()
    sys.exit(app.exec())"