Here is my python code that shall implement simplex projection from pyEDM in order to estimate optimal embeding dimensions. Identify code logic errors and other errors in it. " 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
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):
    """
    Model to display Pandas DataFrame in a QTableView.
    """
    column_dtype_changed = Signal(int, str)
    error_occurred = Signal(str)

    def __init__(self, data):
        super().__init__()
        self._data = data

    def rowCount(self, parent=None):
        return self._data.shape[0]

    def columnCount(self, parent=None):
        return self._data.shape[1]

    def data(self, index, role=Qt.DisplayRole):
        if index.isValid() and role == Qt.DisplayRole:
            return str(self._data.iloc[index.row(), index.column()])
        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
        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)
    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.editingFinished.connect(
            lambda idx=index, widget=name_edit: self.variable_changed.emit(idx, "name", widget.text())
        )

        type_combo = QComboBox()
        type_combo.addItems(["datetime", "float", "int", "str"])
        type_combo.setCurrentText(self._convert_dtype_to_str(dtype))
        type_combo.currentTextChanged.connect(
            lambda text, idx=index: self.variable_changed.emit(idx, "type", text)
        )

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

    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)
            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 __init__(self, df, model, index, new_dtype):
        self.df = df
        self.model = model
        self.index = index
        self.column_name = df.columns[index]
        self.old_dtype = str(df.dtypes[index])
        self.new_dtype = new_dtype

    def execute(self):
        try:
            if self.new_dtype == "datetime":
                # Attempt conversion, but check for errors
                converted_column = pd.to_datetime(self.df[self.column_name], errors='raise')
                if converted_column.isnull().any():
                    raise ValueError("Invalid datetime values found. Conversion failed.")
                self.df[self.column_name] = converted_column
            elif self.new_dtype == "float":
                converted_column = pd.to_numeric(self.df[self.column_name], errors='raise')
                if converted_column.isnull().any():
                    raise ValueError("Invalid float values found. Conversion failed.")
                self.df[self.column_name] = converted_column.astype(float)
            elif self.new_dtype == "int":
                converted_column = pd.to_numeric(self.df[self.column_name], errors='raise')
                if converted_column.isnull().any():
                    raise ValueError("Invalid integer values found. Conversion failed.")
                self.df[self.column_name] = converted_column.astype('Int64')
            elif self.new_dtype == "str":
                self.df[self.column_name] = self.df[self.column_name].astype(str)
            self.model.layoutChanged.emit()
        except Exception as e:
            self.model._handle_error(f"Error converting type: {e}")
            # Optionally, you can revert the change here if an error occurs

    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()
        except Exception as e:
            self.model._handle_error(f"Error converting type: {e}")


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 __init__(self, parent=None):
        super().__init__(parent)
        self.main_window = parent
        self.layout = QVBoxLayout(self)

        # Input selection
        input_layout = QHBoxLayout()
        self.column_combo = QComboBox()
        self.column_combo.setToolTip("Select the column to analyze")
        input_layout.addWidget(QLabel("Select column:"))
        input_layout.addWidget(self.column_combo)

        # Parameters
        param_layout = QHBoxLayout()
        self.lib_size_spinbox = QSpinBox()
        self.lib_size_spinbox.setRange(10, 1000)
        self.lib_size_spinbox.setValue(100)
        self.lib_size_spinbox.setToolTip("Set the library size for simplex projection")
        param_layout.addWidget(QLabel("Library Size:"))
        param_layout.addWidget(self.lib_size_spinbox)

        self.pred_step_spinbox = QSpinBox()
        self.pred_step_spinbox.setRange(1, 10)
        self.pred_step_spinbox.setValue(1)
        self.pred_step_spinbox.setToolTip("Set the prediction step")
        param_layout.addWidget(QLabel("Prediction Step:"))
        param_layout.addWidget(self.pred_step_spinbox)

        # Run button
        self.run_button = QPushButton("Run Simplex Projection")
        self.run_button.clicked.connect(self.run_simplex_projection)

        # Results area
        self.results_area = QScrollArea()
        self.results_widget = QWidget()
        self.results_layout = QVBoxLayout(self.results_widget)
        self.results_area.setWidget(self.results_widget)
        self.results_area.setWidgetResizable(True)

        # Add all elements to the main layout
        self.layout.addLayout(input_layout)
        self.layout.addLayout(param_layout)
        self.layout.addWidget(self.run_button)
        self.layout.addWidget(self.results_area)

    def update_column_list(self):
        self.column_combo.clear()
        if self.main_window.df is not None:
            self.column_combo.addItems(self.main_window.df.columns)

    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'

        # Run simplex projections for embedding dimensions 1 to 10
        results = []
        for E in range(1, 11):
            simplex_output = pyEDM.Simplex(
                dataFrame=data,
                lib="1 {}".format(lib_size),
                pred="{}".format(lib_size + 1),
                embedding=E,
                columns="x",
                target="x",
                Tp=pred_step
            )
            results.append((E, simplex_output['rho'][0]))

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

        # Plot results
        fig, ax = plt.subplots(figsize=(8, 6))
        Es, rhos = zip(*results)
        ax.plot(Es, rhos, 'bo-')
        ax.set_xlabel('Embedding Dimension (E)')
        ax.set_ylabel('Forecast Skill (ρ)')
        ax.set_title('Simplex Projection Results')

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

        # Display optimal E
        optimal_E = max(results, key=lambda x: x[1])[0]
        result_label = QLabel(f"Optimal Embedding Dimension: {optimal_E}")
        self.results_layout.addWidget(result_label)

        plt.close(fig)  # Close the figure to free up memory


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):
        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)
                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())"