SYSTEM: You are a Python 3.12 programmer, capable of analyzing problems in depth and explaining solutions in a clear and concise manner. Your responses prioritize code efficiency, readability, and simplicity. You leverage the latest Python features. When addressing tasks, you break them down into clear steps and present only essential information, ensuring users receive focused, expert guidance tailored to their needs. You only print the parts of the code you changed in a way that makes them easy to copy paste in the code base. USER:Modify the code so that it automatically reloads columns  in the headers filter after deleting a column. tksheet handbook: Header and Index
Set the header

set_header_data(value: object, c: int | None | Iterator = None, redraw: bool = True) -> Sheet

    value (iterable, int, Any) if c is left as None then it attempts to set the whole header as the value (converting a generator to a list). If value is int it sets the header to display the row with that position.
    c (iterable, int, None) if both value and c are iterables it assumes c is an iterable of positions and value is an iterable of values and attempts to set each value to each position. If c is int it attempts to set the value at that position.

headers(
    newheaders: object = None,
    index: None | int = None,
    reset_col_positions: bool = False,
    show_headers_if_not_sheet: bool = True,
    redraw: bool = True,
) -> object

    Using an integer int for argument newheaders makes the sheet use that row as a header e.g. headers(0) means the first row will be used as a header (the first row will not be hidden in the sheet though), this is sort of equivalent to freezing the row.
    Leaving newheaders as None and using the index argument returns the existing header value in that index.
    Leaving all arguments as default e.g. headers() returns existing headers.

Set the index

set_index_data(value: object, r: int | None | Iterator = None, redraw: bool = True) -> Sheet

    value (iterable, int, Any) if r is left as None then it attempts to set the whole index as the value (converting a generator to a list). If value is int it sets the index to display the row with that position.
    r (iterable, int, None) if both value and r are iterables it assumes r is an iterable of positions and value is an iterable of values and attempts to set each value to each position. If r is int it attempts to set the value at that position.

row_index(
    newindex: object = None,
    index: None | int = None,
    reset_row_positions: bool = False,
    show_index_if_not_sheet: bool = True,
    redraw: bool = True,
) -> object

    Using an integer int for argument newindex makes the sheet use that column as an index e.g. row_index(0) means the first column will be used as an index (the first column will not be hidden in the sheet though), this is sort of equivalent to freezing the column.
    Leaving newindex as None and using the index argument returns the existing row index value in that index.
    Leaving all arguments as default e.g. row_index() returns the existing row index.

Bindings and Functionality
Enable table functionality and bindings

enable_bindings(*bindings)

    bindings (str) options are (rc stands for right click):
        "all"
        "single_select"
        "toggle_select"
        "drag_select"
            "select_all"
        "column_drag_and_drop" / "move_columns"
        "row_drag_and_drop" / "move_rows"
        "column_select"
        "row_select"
        "column_width_resize"
        "double_click_column_resize"
        "row_width_resize"
        "column_height_resize"
        "arrowkeys" # all arrowkeys including page up and down
        "up"
        "down"
        "left"
        "right"
        "prior" # page up
        "next" # page down
        "row_height_resize"
        "double_click_row_resize"
        "right_click_popup_menu"
        "rc_select"
        "rc_insert_column"
        "rc_delete_column"
        "rc_insert_row"
        "rc_delete_row"
        "ctrl_click_select" / "ctrl_select"
        "copy"
        "cut"
        "paste"
        "delete"
        "undo"
        "edit_cell"
        "edit_header"
        "edit_index"

Notes:

    You can change the Sheets key bindings for functionality such as copy, paste, up, down etc. Instructions can be found here.
    Control selection is NOT enabled with "all" and has to be specifically enabled.
    Header cell editing is NOT enabled with "all" and has to be specifically enabled.
    Index cell editing is NOT enabled with "all" and has to be specifically enabled.
    To allow table expansion when pasting data which doesn't fit in the table use either:
        paste_can_expand_x=True, paste_can_expand_y=True in sheet initialization arguments or the same keyword arguments with the function set_options().

Example:

    sheet.enable_bindings() to enable absolutely everything.

Disable table functionality and bindings

disable_bindings(*bindings)

Notes:

    Uses the same arguments as enable_bindings().

Bind specific table functionality

This function allows you to bind very specific table functionality to your own functions.

    If you want less specificity in event names you can also bind all sheet modifying events to a single function, see here.
    If you want to validate/modify user cell edits see here.

extra_bindings(
    bindings: str | list | tuple,
    func: Callable | None = None,
) -> Sheet

Notes:

    There are several ways to use this function:
        bindings as a str and func as either None or a function. Using None as an argument for func will effectively unbind the function.
            extra_bindings("edit_cell", func=my_function)
        bindings as an iterable of strs and func as either None or a function. Using None as an argument for func will effectively unbind the function.
            extra_bindings(["all_select_events", "copy", "cut"], func=my_function)
        bindings as an iterable of lists or tuples with length of two, e.g.
            extra_bindings([(binding, function), (binding, function), ...]) In this example you could also use None in the place of function to unbind the binding.
            In this case the arg func is totally ignored.
    For "end_..." events the bound function is run before the value is set.
    To unbind a function either set func argument to None or leave it as default e.g. extra_bindings("begin_copy") to unbind "begin_copy".

Parameters:

    bindings (str) options are:
        "begin_copy", "begin_ctrl_c"
        "ctrl_c", "end_copy", "end_ctrl_c", "copy"
        "begin_cut", "begin_ctrl_x"
        "ctrl_x", "end_cut", "end_ctrl_x", "cut"
        "begin_paste", "begin_ctrl_v"
        "ctrl_v", "end_paste", "end_ctrl_v", "paste"
        "begin_undo", "begin_ctrl_z"
        "ctrl_z", "end_undo", "end_ctrl_z", "undo"
        "begin_delete_key", "begin_delete"
        "delete_key", "end_delete", "end_delete_key", "delete"
        "begin_edit_cell", "begin_edit_table"
        "end_edit_cell", "edit_cell", "edit_table"
        "begin_edit_header"
        "end_edit_header", "edit_header"
        "begin_edit_index"
        "end_edit_index", "edit_index"
        "begin_row_index_drag_drop", "begin_move_rows"
        "row_index_drag_drop", "move_rows", "end_move_rows", "end_row_index_drag_drop"
        "begin_column_header_drag_drop", "begin_move_columns"
        "column_header_drag_drop", "move_columns", "end_move_columns", "end_column_header_drag_drop"
        "begin_rc_delete_row", "begin_delete_rows"
        "rc_delete_row", "end_rc_delete_row", "end_delete_rows", "delete_rows"
        "begin_rc_delete_column", "begin_delete_columns"
        "rc_delete_column", "end_rc_delete_column","end_delete_columns", "delete_columns"
        "begin_rc_insert_column", "begin_insert_column", "begin_insert_columns", "begin_add_column","begin_rc_add_column", "begin_add_columns"
        "rc_insert_column", "end_rc_insert_column", "end_insert_column", "end_insert_columns", "rc_add_column", "end_rc_add_column", "end_add_column", "end_add_columns"
        "begin_rc_insert_row", "begin_insert_row", "begin_insert_rows", "begin_rc_add_row", "begin_add_row", "begin_add_rows"
        "rc_insert_row", "end_rc_insert_row", "end_insert_row", "end_insert_rows", "rc_add_row", "end_rc_add_row", "end_add_row", "end_add_rows"
        "row_height_resize"
        "column_width_resize"
        "cell_select"
        "select_all"
        "row_select"
        "column_select"
        "drag_select_cells"
        "drag_select_rows"
        "drag_select_columns"
        "shift_cell_select"
        "shift_row_select"
        "shift_column_select"
        "ctrl_cell_select"
        "ctrl_row_select"
        "ctrl_column_select"
        "deselect"
        "all_select_events", "select", "selectevents", "select_events"
        "all_modified_events", "sheetmodified", "sheet_modified" "modified_events", "modified"
        "bind_all"
        "unbind_all"
    func argument is the function you want to send the binding event to.
    Using one of the following "all_modified_events", "sheetmodified", "sheet_modified", "modified_events", "modified" will make any insert, delete or cell edit including pastes and undos send an event to your function.
    For events "begin_move_columns"/"begin_move_rows" the point where columns/rows will be moved to will be accessible by the key named "value".
    For "begin_edit..." events the bound function must return a value to open the cell editor with, example here.

Event Data

Using extra_bindings() the function you bind needs to have at least one argument which will receive a dict. The values of which can be accessed by dot notation e.g. event.eventname or event.cells.table:

for (row, column), old_value in event.cells.table.items():
    print (f"R{row}", f"C{column}", "Old Value:", old_value)

It has the following layout and keys:

{
    "eventname": "",
    "sheetname": "",
    "cells": {
        "table": {},
        "header": {},
        "index": {},
    },
    "moved": {
        "rows": {},
        "columns": {},
    },
    "added": {
        "rows": {},
        "columns": {},
    },
    "deleted": {
        "rows": {},
        "columns": {},
        "header": {},
        "index": {},
        "column_widths": {},
        "row_heights": {},
        "options": {},
        "displayed_columns": None,
        "displayed_rows": None,
    },
    "named_spans": {},
    "selection_boxes": {},
    "selected": tuple(),
    "being_selected": tuple(),
    "data": [],
    "key": "",
    "value": None,
    "loc": tuple(),
    "row": None,
    "column": None,
    "resized": {
        "rows": {},
        "columns": {},
    },
    "widget": None,
}

Keys:

    Key ["eventname"] will be one of the following:
        "begin_ctrl_c"
        "end_ctrl_c"
        "begin_ctrl_x"
        "end_ctrl_x"
        "begin_ctrl_v"
        "end_ctrl_v"
        "begin_delete"
        "end_delete"
        "begin_undo"
        "end_undo"
        "begin_add_columns"
        "end_add_columns"
        "begin_add_rows"
        "end_add_rows"
        "begin_delete_columns"
        "end_delete_columns"
        "begin_delete_rows"
        "end_delete_rows"
        "begin_edit_table"
        "end_edit_table"
        "begin_edit_index"
        "end_edit_index"
        "begin_edit_header"
        "end_edit_header"
        "begin_move_rows"
        "end_move_rows"
        "begin_move_columns"
        "end_move_columns"
        "select"
        "resize"
    For events "begin_move_columns"/"begin_move_rows" the point where columns/rows will be moved to will be under the event_data key "value".
    Key ["sheetname"] is the name given to the sheet widget on initialization, useful if you have multiple sheets to determine which one emitted the event.
    Key ["cells"]["table"] if any table cells have been modified by cut, paste, delete, cell editors, dropdown boxes, check boxes, undo or redo this will be a dict with tuple keys of (data row index: int, data column index: int) and the values will be the cell values at that location prior to the change. The dict will be empty if no such changes have taken place.
    Key ["cells"]["header"] if any header cells have been modified by cell editors, dropdown boxes, check boxes, undo or redo this will be a dict with keys of int: data column index and the values will be the cell values at that location prior to the change. The dict will be empty if no such changes have taken place.
    Key ["cells"]["index"] if any index cells have been modified by cell editors, dropdown boxes, check boxes, undo or redo this will be a dict with keys of int: data row index and the values will be the cell values at that location prior to the change. The dict will be empty if no such changes have taken place.
    Key ["moved"]["rows"] if any rows have been moved by dragging and dropping or undoing/redoing of dragging and dropping rows this will be a dict with the following keys:
        {"data": {old data index: new data index, ...}, "displayed": {old displayed index: new displayed index, ...}}
            "data" will be a dict where the keys are the old data indexes of the rows and the values are the data indexes they have moved to.
            "displayed" will be a dict where the keys are the old displayed indexes of the rows and the values are the displayed indexes they have moved to.
            If no rows have been moved the dict under ["moved"]["rows"] will be empty.
            Note that if there are hidden rows the values for "data" will include all currently displayed row indexes and their new locations. If required and available, the values under "displayed" include only the directly moved rows, convert to data indexes using Sheet.data_r().
        For events "begin_move_rows" the point where rows will be moved to will be under the event_data key "value".
    Key ["moved"]["columns"] if any columns have been moved by dragging and dropping or undoing/redoing of dragging and dropping columns this will be a dict with the following keys:
        {"data": {old data index: new data index, ...}, "displayed": {old displayed index: new displayed index, ...}}
            "data" will be a dict where the keys are the old data indexes of the columns and the values are the data indexes they have moved to.
            "displayed" will be a dict where the keys are the old displayed indexes of the columns and the values are the displayed indexes they have moved to.
            If no columns have been moved the dict under ["moved"]["columns"] will be empty.
            Note that if there are hidden columns the values for "data" will include all currently displayed column indexes and their new locations. If required and available, the values under "displayed" include only the directly moved columns, convert to data indexes using Sheet.data_c().
        For events "begin_move_columns" the point where columns will be moved to will be under the event_data key "value".
    Key ["added"]["rows"] if any rows have been added by the inbuilt popup menu insert rows or by a paste which expands the sheet then this will be a dict with the following keys:
        {"data_index": int, "displayed_index": int, "num": int, "displayed": []}
            "data_index" is an int representing the row where the rows were added in the data.
            "displayed_index" is an int representing the displayed table index where the rows were added (which will be different from the data index if there are hidden rows).
            "displayed" is simply a copied list of the Sheet()s displayed rows immediately prior to the change.
            If no rows have been added the dict will be empty.
    Key ["added"]["columns"] if any columns have been added by the inbuilt popup menu insert columns or by a paste which expands the sheet then this will be a dict with the following keys:
        {"data_index": int, "displayed_index": int, "num": int, "displayed": []}
            "data_index" is an int representing the column where the columns were added in the data.
            "displayed_index" is an int representing the displayed table index where the columns were added (which will be different from the data index if there are hidden columns).
            "displayed" is simply a copied list of the Sheet()s displayed columns immediately prior to the change.
            If no columns have been added the dict will be empty.
    Key ["deleted"]["columns"] if any columns have been deleted by the inbuilt popup menu delete columns or by undoing a paste which added columns then this will be a dict. This dict will look like the following:
        {[column data index]: {[row data index]: cell value, [row data index]: cell value}, [column data index]: {...} ...}
        If no columns have been deleted then the dict value for ["deleted"]["columns"] will be empty.
    Key ["deleted"]["rows"] if any rows have been deleted by the inbuilt popup menu delete rows or by undoing a paste which added rows then this will be a dict. This dict will look like the following:
        {[row data index]: {[column data index]: cell value, [column data index]: cell value}, [row data index]: {...} ...}
        If no rows have been deleted then the dict value for ["deleted"]["rows"] will be empty.
    Key ["deleted"]["header"] if any header values have been deleted by the inbuilt popup menu delete columns or by undoing a paste which added columns and header values then this will be a dict. This dict will look like the following:
        {[column data index]: header cell value, [column data index]: header cell value, ...}
        If no columns have been deleted by the mentioned methods then the dict value for ["deleted"]["header"] will be empty.
    Key ["deleted"]["index"] if any index values have been deleted by the inbuilt popup menu delete rows or by undoing a paste which added rows and index values then this will be a dict. This dict will look like the following:
        {[row data index]: index cell value, [row data index]: index cell value, ...}
        If no index values have been deleted by the mentioned methods then the dict value for ["deleted"]["index"] will be empty.
    Key ["deleted"]["column_widths"] if any columns have been deleted by the inbuilt popup menu delete columns or by undoing a paste which added columns then this will be a dict. This dict will look like the following:
        {[column data index]: column width, [column data index]: column width, ...}
        If no columns have been deleted then the dict value for ["deleted"]["column_widths"] will be empty.
    Key ["deleted"]["row_heights"] if any rows have been deleted by the inbuilt popup menu delete rows or by undoing a paste which added rows then this will be a dict. This dict will look like the following:
        {[row data index]: row height, [row data index]: row height, ...}
        If no rows have been deleted then the dict value for ["deleted"]["row_heights"] will be empty.
    Key ["deleted"]["displayed_columns"] if any columns have been deleted by the inbuilt popup menu delete columns or by undoing a paste which added columns then this will be a list. This list stores the displayed columns (the columns that are showing when others are hidden) immediately prior to the change.
    Key ["deleted"]["displayed_rows"] if any rows have been deleted by the inbuilt popup menu delete rows or by undoing a paste which added rows then this will be a list. This list stores the displayed rows (the rows that are showing when others are hidden) immediately prior to the change.
    Key ["named_spans"] This dict serves as storage for the Sheet()s named spans. Each value in the dict is a pickled span object.
    Key ["options"] This serves as storage for the Sheet()s options such as highlights, formatting, alignments, dropdown boxes, check boxes etc. It is a dict where the values are the sheets internal cell/row/column options dicts.
    Key ["selection_boxes"] the value of this is all selection boxes on the sheet in the form of a dict as shown below:
        For every event except "select" events the selection boxes are those immediately prior to the modification, for "select" events they are the current selection boxes.
        The layout is always: "selection_boxes": {(start row, start column, up to but not including row, up to but not including column): selection box type}.
            The row/column indexes are ints and the selection box type is a str either "cells", "rows" or "columns".
        The dict will be empty if there is nothing selected.
    Key ["selected"] the value of this when there is something selected on the sheet is a namedtuple. The values of which can be found here.
        When nothing is selected or the event is not relevant to the currently selected box, such as a resize event it will be an empty tuple.
    Key ["being_selected"] if any selection box is in the process of being drawn by holding down mouse button 1 and dragging then this will be a tuple with the following layout:
        (start row, start column, up to but not including row, up to but not including column, selection box type).
            The selection box type is a str either "cells", "rows" or "columns".
        If no box is in the process of being created then this will be a an empty tuple.
        See here for an example.
    Key ["data"] is primarily used for paste and it will contain the pasted data if any.
    Key ["key"] - str - is primarily used for cell edit events where a key press has occurred. For "begin_edit..." events the value is the actual key which was pressed (or "??" for using the mouse to open a cell). It also might be one of the following for end edit events:
        "Return" - enter key.
        "FocusOut" - the editor or box lost focus, perhaps by mouse clicking elsewhere.
        "Tab" - tab key.
    Key ["value"] is used primarily by cell editing events. For "begin_edit..." events it's the value displayed in he text editor when it opens. For "end_edit..." events it's the value in the text editor when it was closed, for example by hitting Return. It also used by "begin_move_columns"/"begin_move_rows" - the point where columns/rows will be moved to will be under the event_data key "value".
    Key ["loc"] is for cell editing events to show the displayed (not data) coordinates of the event. It will be either:
        A tuple of (int displayed row index, int displayed column index) in the case of editing table cells.
        A single int in the case of editing index/header cells.
    Key ["row"] is for cell editing events to show the displayed (not data) row number int of the event. If the event was not a cell editing event or a header cell was edited the value will be None.
    Key ["column"] is for cell editing events to show the displayed (not data) column number int of the event. If the event was not a cell editing event or an index cell was edited the value will be None.
    Key ["resized"]["rows"] is for row height resizing events, it will be a dict with the following layout:
        {int displayed row index: {"old_size": old_height, "new_size": new_height}}.
        If no rows have been resized then the value for ["resized"]["rows"] will be an empty dict.
    Key ["resized"]["columns"] is for column width resizing events, it will be a dict with the following layout:
        {int displayed column index: {"old_size": old_width, "new_size": new_width}}.
        If no columns have been resized then the value for ["resized"]["columns"] will be an empty dict.
    Key ["widget"] will contain the widget which emitted the event, either the MainTable(), ColumnHeaders() or RowIndex() which are all tk.Canvas widgets.

Validate user cell edits

With this function you can validate or modify most user sheet edits, includes cut, paste, delete (including column/row clear), dropdown boxes and cell edits.

edit_validation(func: Callable | None = None) -> Sheet

Parameters:

    func (Callable, None) must either be a function which will receive a tksheet event dict which looks like this or None which unbinds the function.

Notes:

    If your bound function returns None then that specific cell edit will not be performed.
    For examples of this function see here and here.

Add commands to the in-built right click popup menu

popup_menu_add_command(
    label: str,
    func: Callable,
    table_menu: bool = True,
    index_menu: bool = True,
    header_menu: bool = True,
    empty_space_menu: bool = True,
) -> Sheet

Remove commands added using popup_menu_add_command from the in-built right click popup menu

popup_menu_del_command(label: str | None = None) -> Sheet

    If label is None then it removes all.

Enable or disable mousewheel, left click etc

basic_bindings(enable: bool = False) -> Sheet

These functions are links to the Sheets own functionality. Functions such as cut() rely on whatever is currently selected on the Sheet.

cut(event: object = None) -> Sheet
copy(event: object = None) -> Sheet
paste(event: object = None) -> Sheet
delete(event: object = None) -> Sheet
undo(event: object = None) -> Sheet
redo(event: object = None) -> Sheet
zoom_in() -> Sheet
zoom_out() -> Sheet

Get the last event data dict

@property
def event() -> EventDataDict

    e.g. last_event_data = sheet.event
    Will be empty EventDataDict if there is no last event.

Set focus to the Sheet

focus_set(
    canvas: Literal[
        "table",
        "header",
        "row_index",
        "index",
        "topleft",
        "top_left",
    ] = "table",
) -> Sheet  Python 3.12 Code: import ctypes
import csv
import json
import tkinter as tk
from pathlib import Path
from tkinter import filedialog, messagebox, simpledialog, ttk
from typing import Any, Optional

from tksheet import Sheet

def set_dpi_awareness():
    """Enable DPI awareness for high DPI settings."""
    try:
        ctypes.windll.shcore.SetProcessDpiAwareness(1)
    except Exception as e:
        print(f"Error setting DPI awareness: {e}")

def custom_filter_design(master, style: ttk.Style, headers=None, filter_types=None, filter_font=None, filter_combobox_font=None, colors=None):
    """Creates a custom filter frame with all components."""
    filter_frame = ttk.Frame(master, style="Custom.TFrame")

    # Configure styles
    style.configure("Custom.TFrame", background=colors["background"], relief="flat", borderwidth=0)
    style.configure("TCombobox", relief="flat", borderwidth=0, background=colors["background"], fieldbackground=colors["background"])
    style.map("TCombobox", fieldbackground=[('readonly', colors["background"])], background=[('readonly', colors["background"])])
    style.configure("TEntry", relief="flat", borderwidth=0, background=colors["background"])
    style.configure("Line.TFrame", background=colors["filter_line"])

    # Create widgets
    header_combobox = ttk.Combobox(filter_frame, values=headers, width=15, font=filter_combobox_font, state="readonly")
    filtertype_combobox = ttk.Combobox(filter_frame, values=filter_types, width=15, font=filter_combobox_font, state="readonly")
    filter_entry = ttk.Entry(filter_frame, font=filter_font)
    underline = ttk.Frame(filter_frame, height=1, style="Line.TFrame")

    # Layout widgets
    header_combobox.grid(row=0, column=0, padx=(0, 30), pady=2, sticky='ew')
    filtertype_combobox.grid(row=0, column=1, padx=2, pady=2, sticky='ew')
    filter_entry.grid(row=1, column=0, columnspan=2, padx=2, pady=(2, 0), sticky='ew')
    underline.grid(row=2, column=0, columnspan=2, sticky='ew', pady=(0, 2))

    # Prevent text selection in comboboxes
    for combobox in (header_combobox, filtertype_combobox):
        combobox.bind("<FocusIn>", lambda event: event.widget.selection_clear())

    # Configure column weights
    filter_frame.columnconfigure((0, 1), weight=1)

    # Ensure the underline doesn't resize vertically
    underline.pack_propagate(False)

    return filter_frame, header_combobox, filtertype_combobox, filter_entry

def custom_scrollbar_design(style: ttk.Style, colors=None):
    """Configure custom scrollbar style."""
    style.configure("Custom.Vertical.TScrollbar", troughcolor=colors["scrollbar_trough"], background=colors["scrollbar_background"], borderwidth=0, arrowcolor=colors["scrollbar_arrow"])
    style.map("Custom.Vertical.TScrollbar", background=[('active', colors["scrollbar_active"])])
    style.layout("Custom.Vertical.TScrollbar", [('Vertical.Scrollbar.trough', {'children': [('Vertical.Scrollbar.thumb', {'expand': '1', 'sticky': 'ns'})], 'sticky': 'ns'})])

class TableApp:
    """Table Editor with sidebar and table management."""

    def __init__(self, root: tk.Tk):
        """Initialize the TableApp."""
        self.root = root
        self.config_dimensions()
        self.config_fonts()
        self.config_colors()
        self.initialize_data()
        self.style = ttk.Style()
        self.style.theme_use("default")
        self.setup_ui()
        self.load_tables()

    def config_dimensions(self):
        """Configure app dimensions."""
        self.root.title("Database")
        self.root.geometry("1920x1080")
        self.root.minsize(1435, 910)
        self.sidebar_width = 400
        self.spacer_frame_width = 100
        self.header_height = 190
        self.separator_height = 1

    def config_fonts(self):
        """Configure app fonts."""
        self.fonts = {
            "text": ("Literata", 12),
            "tksheet": ("Literata", 12, "normal"),
            "header_info": ("Literata", 10),
            "filter": ("Literata", 12),
            "filter_combobox": ("Literata", 8),
        }

    def config_colors(self):
        """Configure app colors."""
        self.colors = {
            "text": "#333333",
            "background": "#FFFFFF",
            "sidebar": "#F7F8FC",
            "button": "#F7F8FC",
            "button_selected": "#EAEDEF",
            "separator": "#333333",
            "header_info": "#999999",
            "scrollbar_trough": "#F7F8FC",
            "scrollbar_background": "#c0c1c4",
            "scrollbar_arrow": "#333333",
            "scrollbar_active": "#878789",
            "filter_line": "#00bcf0",
        }

    def setup_ui(self):
        """Set up the user interface with a 3x2 grid layout."""
        self.root.grid_rowconfigure(0, weight=0)
        self.root.grid_rowconfigure(1, weight=1)

        self.root.grid_columnconfigure(0, weight=0)
        self.root.grid_columnconfigure(1, weight=0, minsize=self.spacer_frame_width)
        self.root.grid_columnconfigure(2, weight=3)

        self.create_sidebar()
        self.create_header_frame()
        self.create_spacer_frame()
        self.create_table_frame()

    def create_sidebar(self):
        """Create the sidebar that spans both rows."""
        sidebar = self._create_frame(
            parent=self.root,
            width=self.sidebar_width,
            bg=self.colors["sidebar"]
        )
        sidebar.grid(row=0, column=0, rowspan=2, sticky='ns')
        sidebar.grid_propagate(False)
        sidebar.columnconfigure(0, weight=1)
        sidebar.rowconfigure(0, weight=1)

        functional_sidebar = self._create_frame(
            parent=sidebar,
            width=self.sidebar_width,
            bg=self.colors["sidebar"]
        )
        functional_sidebar.grid(row=0, column=0, sticky='nsew')
        functional_sidebar.columnconfigure(0, weight=1)
        functional_sidebar.rowconfigure(0, weight=0)
        functional_sidebar.rowconfigure(1, weight=1)
        functional_sidebar.rowconfigure(2, weight=0)

        self._add_normal_buttons(functional_sidebar)
        self._add_scrollable_canvas(functional_sidebar)
        self._add_delete_table_section(functional_sidebar)

    def create_header_frame(self) -> None:
        """Create the header divided into upper and lower sub-frames."""
        header_frame = self._create_and_configure_header_frame()
        
        upper_header_frame = self._create_upper_header(header_frame)
        self._create_upper_header_content(upper_header_frame)
        
        lower_header_frame = self._create_lower_header(header_frame)
        self._create_filter_controls(lower_header_frame)
    
    def _create_and_configure_header_frame(self) -> tk.Frame:
        """Create and configure the main header frame."""
        header_frame = self._create_frame(parent=self.root, width=None, bg=self.colors["background"])
        header_frame.grid(row=0, column=1, columnspan=2, sticky='ew')
        header_frame.config(height=self.header_height)
        header_frame.grid_propagate(False)
        
        for i in range(2):
            header_frame.grid_rowconfigure(i, weight=0)
        
        for i in range(5):
            weight = 1 if i == 4 else 0
            header_frame.grid_columnconfigure(i, weight=weight)
        
        return header_frame
    
    def _create_upper_header(self, parent: tk.Frame) -> tk.Frame:
        """Create the upper header frame."""
        upper_header = self._create_frame(parent=parent, width=None, bg=self.colors["background"])
        upper_header.grid(row=0, column=0, columnspan=5, sticky='ew', pady=(0, 10))
        upper_header.grid_columnconfigure(5, weight=1)
        return upper_header
    
    def _create_upper_header_content(self, upper_header: tk.Frame) -> None:
        """Create and add content to the upper header frame."""
        self._create_data_label(upper_header)
        self._create_table_name_label(upper_header)
        self._create_settings_label(upper_header)
    
    def _create_data_label(self, parent: tk.Frame) -> None:
        """Create and grid the 'Data >' label."""
        data_label = tk.Label(
            parent,
            text="Data > ",
            bg=self.colors["background"],
            fg=self.colors["header_info"],
            font=self.fonts["header_info"]
        )
        data_label.grid(row=0, column=0, padx=22, pady=(22, 0), sticky='nw')
    
    def _create_table_name_label(self, parent: tk.Frame) -> None:
        """Create and grid the table name label."""
        self.table_name_label = tk.Label(
            parent,
            text=self.current_table_name if self.current_table_name else "No Table Selected",
            bg=self.colors["background"],
            fg=self.colors["text"],
            font=self.fonts["header_info"]
        )
        self.table_name_label.grid(row=0, column=0, padx=(88, 10), pady=(22, 0), sticky='nw')
    
    def _create_settings_label(self, parent: tk.Frame) -> None:
        """Create and grid the settings label with its associated menu."""
        settings_label = tk.Label(
            parent,
            text="...",
            bg=self.colors["background"],
            fg=self.colors["text"],
            cursor="hand2",
            anchor='e',
            font=self.fonts["text"]
        )
        settings_label.grid(row=0, column=5, padx=40, pady=20, sticky='e')
        
        settings_menu = self._create_settings_menu()
        self._bind_settings_label_events(settings_label, settings_menu)
    
    def _create_settings_menu(self) -> tk.Menu:
        """Create the settings dropdown menu."""
        menu = tk.Menu(self.root, tearoff=0)
        menu.add_command(label="Export Table", command=self.export_table)
        menu.add_command(label="Import Table", command=self.import_file)
        menu.add_command(label="Rename Headers", command=self.rename_headers)
        return menu
    
    def _bind_settings_label_events(self, label: tk.Label, menu: tk.Menu) -> None:
        """Bind events to the settings label."""
        label.bind("<Button-1>", lambda e: menu.post(e.x_root, e.y_root))
        label.bind("<Enter>", lambda _: label.config(bg=self.colors["button_selected"]))
        label.bind("<Leave>", lambda _: label.config(bg=self.colors["background"]))
    
    def _create_lower_header(self, parent: tk.Frame) -> tk.Frame:
        """Create the lower header frame."""
        lower_header = self._create_frame(parent=parent, width=None, bg=self.colors["background"])
        lower_header.grid(row=1, column=0, columnspan=5, sticky='ew')
        return lower_header

    def _create_filter_controls(self, parent: tk.Frame) -> None:
        """Create and add filter controls to the lower header frame."""
        self._create_filter_label(parent)
        filter_frame, self.filter_header_dropdown, self.filter_option_dropdown, self.filter_text_entry = custom_filter_design(parent, self.style, headers=["All"] + self.headers.get(self.current_table_name, []), filter_types=[
            "is", "is not", "contains", "does not contain",
            "starts with", "ends with", "is empty", "is not empty"
        ], filter_font=self.fonts["filter"], filter_combobox_font=self.fonts["filter_combobox"], colors=self.colors)
        filter_frame.grid(row=0, column=1, columnspan=4, padx=0, pady=0, sticky='ew')

        self.filter_header_var.set("All")
        self.filter_option_var.set("contains")
        self.filter_text_var.set("")

        self.filter_header_dropdown['textvariable'] = self.filter_header_var
        self.filter_option_dropdown['textvariable'] = self.filter_option_var
        self.filter_text_entry['textvariable'] = self.filter_text_var

        self.filter_header_dropdown.bind("<<ComboboxSelected>>", self.apply_filter)
        self.filter_option_dropdown.bind("<<ComboboxSelected>>", lambda event: [self.update_filter_text_state(event), self.apply_filter()])
        self.filter_text_entry.bind("<KeyRelease>", self.apply_filter)
        self.filter_text_entry.bind("<Return>", self.apply_filter)
        
    def _create_filter_label(self, parent: tk.Frame) -> None:
        filter_label = tk.Label(parent, text="Filter: ", bg=self.colors["background"], fg=self.colors["text"])
        filter_label.grid(row=0, column=0, padx=(100, 0), pady=20, sticky='e')
    
    def update_filter_text_state(self, event=None):
        """Enable or disable the filter text entry based on filter option."""
        option = self.filter_option_var.get()
        if option in ["is empty", "is not empty"]:
            self.filter_text_var.set("")
            self.filter_text_entry.config(state='disabled')
            self.apply_filter()
        else:
            self.filter_text_entry.config(state='normal')

    def create_spacer_frame(self):
        """Create a spacer frame in column 1, row 1."""
        spacer = self._create_frame(
            parent=self.root,
            width=self.spacer_frame_width,
            bg=self.colors["background"]
        )
        spacer.grid(row=1, column=1, sticky='nsew')
        spacer.grid_propagate(False)

    def create_table_frame(self):
        """Create the table frame in column 2, row 1."""
        self.table_frame = self._create_frame(
            parent=self.root,
            width=None,
            bg=self.colors["background"]
        )
        self.table_frame.grid(row=1, column=2, sticky='nsew')
        self.table_frame.grid_rowconfigure(0, weight=1)
        self.table_frame.grid_columnconfigure(0, weight=1)

    def _create_frame(
        self,
        parent: tk.Widget,
        width: Optional[int],
        bg: str
    ) -> tk.Frame:
        """Create a frame with specified width and background."""
        frame = tk.Frame(parent, width=width, bg=bg, bd=0, relief='flat')
        if width:
            frame.config(width=width)
        return frame

    def _add_normal_buttons(self, sidebar: tk.Frame):
        """Add buttons to the sidebar."""
        normal_buttons_frame = self._create_frame(
            parent=sidebar,
            width=self.sidebar_width,
            bg=self.colors["sidebar"]
        )
        normal_buttons_frame.grid(row=0, column=0, sticky='ew', padx=10, pady=(10, 0))
        normal_buttons_frame.columnconfigure(0, weight=1)

        create_table_label = self._create_clickable_label(
            parent=normal_buttons_frame,
            text="Create New Table",
            command=self.create_new_table,
            anchor='center'
        )
        create_table_label.grid(row=0, column=0, pady=10, sticky='ew')
        self.add_custom_hover(create_table_label)

        separator = tk.Frame(normal_buttons_frame, height=self.separator_height, bg=self.colors["separator"])
        separator.grid(row=1, column=0, sticky='ew', pady=5)

    def _add_scrollable_canvas(self, sidebar: tk.Frame):
        """Add scrollable canvas for table labels within a single frame."""
        scrollable_container = self._create_frame(
            parent=sidebar,
            width=self.sidebar_width,
            bg=self.colors["sidebar"]
        )
        scrollable_container.grid(row=1, column=0, sticky='nsew', padx=10, pady=10)
        scrollable_container.columnconfigure(0, weight=1)
        scrollable_container.rowconfigure(0, weight=1)
    
        self.canvas = tk.Canvas(
            scrollable_container,
            bg=self.colors["sidebar"],
            highlightthickness=0
        )
        self.canvas.grid(row=0, column=0, sticky='nsew')
    
        custom_scrollbar_design(self.style, self.colors) 

        self.scrollbar = ttk.Scrollbar(
            scrollable_container,
            orient="vertical",
            command=self.canvas.yview,
            style="Custom.Vertical.TScrollbar"
        )
        self.scrollbar.grid(row=0, column=1, sticky='ns')

        self.canvas.configure(yscrollcommand=self.scrollbar.set)

        self.table_labels_frame = tk.Frame(self.canvas, bg=self.colors["sidebar"])
        self.canvas.create_window((0, 0), window=self.table_labels_frame, anchor="nw")

        self.canvas.bind("<MouseWheel>", self._on_mouse_wheel)
        self.table_labels_frame.bind("<MouseWheel>", self._on_mouse_wheel)

        self.table_labels_frame.bind(
            "<Configure>",
            lambda event: (
                self.canvas.configure(scrollregion=self.canvas.bbox("all")),
                self._update_scrollbar(scrollable_container, self.canvas, self.scrollbar)
            )
        )
        scrollable_container.bind(
            "<Configure>",
            lambda event: self._update_scrollbar(scrollable_container, self.canvas, self.scrollbar)
        )

    def _on_mouse_wheel(self, event):
        """Handle mouse wheel scrolling only if scrollbar is visible."""
        if self.scrollbar.winfo_viewable():
            self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

    def _update_scrollbar(self, scrollbar_frame, canvas, scrollbar):
        """Update scrollbar visibility based on content size."""
        canvas.update_idletasks()
        canvas_height = scrollbar_frame.winfo_height()
        content_height = self.table_labels_frame.winfo_reqheight()

        if content_height > canvas_height:
            scrollbar.grid()
            canvas.configure(yscrollcommand=scrollbar.set)
            canvas.bind("<MouseWheel>", lambda event: canvas.yview_scroll(int(-1 * (event.delta / 120)), "units"))
        else:
            scrollbar.grid_remove()
            canvas.configure(yscrollcommand=lambda *args: None)
            canvas.unbind("<MouseWheel>")

    def _add_delete_table_section(self, sidebar: tk.Frame):
        """Add delete table option to the sidebar."""
        delete_table_frame = self._create_frame(
            parent=sidebar,
            width=self.sidebar_width,
            bg=self.colors["sidebar"]
        )
        delete_table_frame.grid(row=2, column=0, sticky='ew', padx=10, pady=(0, 10))
        delete_table_frame.columnconfigure(0, weight=1)
        delete_table_frame.rowconfigure(1, weight=1)

        separator = tk.Frame(delete_table_frame, height=self.separator_height, bg=self.colors["separator"])
        separator.grid(row=0, column=0, sticky='ew', pady=5)

        delete_table_label = self._create_clickable_label(
            parent=delete_table_frame,
            text="🗑 Delete Selected Table",
            command=self.delete_table,
            anchor='center'
        )
        delete_table_label.grid(row=1, column=0, pady=10, sticky='ew')
        self.add_custom_hover(delete_table_label)

    def _create_clickable_label(
        self,
        parent: tk.Widget,
        text: str,
        command,
        allow_rename: bool = False,
        anchor: str = 'w'
    ) -> tk.Label:
        """Create a clickable label with optional rename functionality."""
        cursor = "arrow" if allow_rename else "hand2"
        label = tk.Label(
            parent,
            text=text,
            bg=self.colors["button"],
            fg=self.colors["text"],
            cursor=cursor,
            anchor=anchor,
            font=self.fonts["text"]
        )
        label.bind("<Button-1>", lambda _: command())
    
        if allow_rename:
            label.bind("<Double-Button-1>", lambda _: self.begin_rename(text))
    
        label.bind("<MouseWheel>", self._on_mouse_wheel)
    
        return label

    def add_custom_hover(self, widget: tk.Widget):
        """Add custom hover effects to a widget."""
        widget.bind("<Enter>", lambda e: widget.config(bg=self.colors["button_selected"]))
        widget.bind("<Leave>", lambda e: widget.config(bg=self.colors["button"]))

    def initialize_data(self):
        """Initialize data structures."""
        self.save_file_path = Path(__file__).parent / "Data.json"
        self.tables_data: dict[str, dict] = {}
        self.current_sheet: Sheet | None = None
        self.headers: dict[str, list[str]] = {}
        self.current_table_name: str | None = None
        self.rename_entry: tk.Entry | None = None
        self.selected_table_label: tk.Label | None = None
        self.table_labels: dict[str, tk.Label] = {}
            
        self.table_name_label = None
    
        self.filter_header_var = tk.StringVar(value="All")
        self.filter_option_var = tk.StringVar(value="contains")
        self.filter_text_var = tk.StringVar()
    
        # Added: Initialize displayed_row_indices
        self.displayed_row_indices: list[int] = []

    def create_new_table(self):
        """Create a new table."""
        existing_numbers = [int(name.split(" ")[1]) for name in self.tables_data if name.startswith("Table ") and name.split(" ")[1].isdigit()]
        next_number = max(existing_numbers, default=0) + 1
        table_name = f"Table {next_number}"
        self.add_table(table_name)
        self.load_selected_table(table_name)

    def import_file(self):
        """Import table from file."""
        file_path = filedialog.askopenfilename(title="Import File", filetypes=[("All Supported Files", "*.json *.csv"), ("JSON files", "*.json"), ("CSV files", "*.csv")])
        if not file_path:
            return
        try:
            if file_path.endswith('.csv'):
                with open(file_path, newline='', encoding='utf-8') as csvfile:
                    data = list(csv.reader(csvfile))
            elif file_path.endswith('.json'):
                with open(file_path, 'r', encoding='utf-8') as jsonfile:
                    data = json.load(jsonfile)
            else:
                raise ValueError("Unsupported file format.")
            if not data or not isinstance(data, list) or not all(isinstance(row, list) for row in data):
                raise ValueError("Invalid data format in the file.")
            headers, table_data = data[0], data[1:]
            self.add_table_from_data(file_path, headers, table_data)
        except Exception as e:
            messagebox.showerror("File Import Error", str(e))

    def add_table_from_data(self, file_path: str, headers: list[str], table_data: list[list[str]]):
        """Add a table from file data."""
        table_name = Path(file_path).stem
        table_name = self.ensure_unique_table_name(table_name)
        self.add_table(table_name, data=table_data, headers=headers)
        self.load_selected_table(table_name)
        self.save_data()
    
    def ensure_unique_table_name(self, table_name: str) -> str:
        """Ensure table name is unique."""
        original_name = table_name
        counter = 1
        while table_name in self.tables_data:
            table_name = f"{original_name} (New {counter})"
            counter += 1
        return table_name

    def save_table_data(self, file_path: str, data: list[list[str]], format: str):
        """Save table data in specified format."""
        match format:
            case 'csv':
                with open(file_path, 'w', newline='', encoding='utf-8') as file:
                    csv.writer(file).writerows(data)
            case 'json':
                with open(file_path, 'w', encoding='utf-8') as file:
                    json.dump(data, file, indent=4)
            case _:
                raise ValueError("Unsupported file format.")

    def export_table(self):
        """Export current table to file."""
        if not self.current_table_name or self.current_table_name not in self.tables_data:
            messagebox.showwarning("No Table Selected", "Please select a table to export.")
            return
        file_path = filedialog.asksaveasfilename(title="Export File", defaultextension=".json", filetypes=[("JSON files", "*.json"), ("CSV files", "*.csv")], initialfile=self.current_table_name)
        if not file_path:
            return
        try:
            data = [self.headers[self.current_table_name]] + self.tables_data[self.current_table_name]["data"]
            if file_path.endswith('.csv'):
                self.save_table_data(file_path, data, 'csv')
            elif file_path.endswith('.json'):
                with open(file_path, 'w', encoding='utf-8') as file:
                    json.dump(data, file, indent=4)
            else:
                raise ValueError("Unsupported file format.")
            self.save_data()
            messagebox.showinfo("Export Successful", f"Table '{self.current_table_name}' has been exported successfully.")
        except Exception as e:
            messagebox.showerror("Export Failed", f"An error occurred during export: {e}")

    def add_table(self, table_name: str, data: list[list[str]] | None = None, headers: Optional[list[str]] = None):
        """Add a new table."""
        if headers:
            self.headers[table_name] = headers
        else:
            default_headers = ["Column 1", "Column 2", "Column 3", "Column 4", "Column 5"]
            self.headers[table_name] = default_headers
        self.tables_data[table_name] = {"headers": self.headers[table_name], "data": data or [["" for _ in range(len(self.headers[table_name]))] for _ in range(10)]}
        self.create_table_label(table_name)
        self.save_data()

    def create_table_label(self, table_name: str):
        """Create label for table."""
        label = self._create_clickable_label(self.table_labels_frame, table_name, lambda: self.load_selected_table(table_name), True)
        label.pack(fill='x', padx=20, pady=5, expand=True)
        self.table_labels[table_name] = label
        self.add_custom_hover(label)

    def delete_table(self):
        """Delete selected table."""
        if not self.current_table_name or self.current_table_name not in self.tables_data:
            messagebox.showwarning("No Table Selected", "Please select a valid table to delete.")
            return
        if messagebox.askyesno("Delete Table", f"Are you sure you want to delete the table '{self.current_table_name}'?"):
            self.remove_table(self.current_table_name)

    def remove_table(self, table_name: str):
        """Remove a table."""
        if self.current_table_name == table_name and self.current_sheet:
            self.current_sheet.destroy()
            self.current_sheet = None
        label = self.table_labels.get(table_name)
        if label:
            label.destroy()
        if table_name in self.tables_data:
            del self.tables_data[table_name]
        if table_name in self.headers:
            del self.headers[table_name]
        if table_name in self.table_labels:
            del self.table_labels[table_name]
        self.current_table_name = None
        self.selected_table_label = None
        self.save_data()

    def load_tables(self):
        """Load tables from file."""
        if not self.save_file_path.exists():
            return
        try:
            with self.save_file_path.open('r', encoding='utf-8') as file:
                all_data = json.load(file)
        except Exception as e:
            messagebox.showerror("Load Error", f"Failed to load tables: {e}")
            return
        for table_name, data in all_data.items():
            if not isinstance(data, list) or not data:
                continue
            headers, table_data = data[0], data[1:]
            self.headers[table_name] = headers
            self.tables_data[table_name] = {"headers": headers, "data": table_data}
            self.create_table_label(table_name)

    def load_selected_table(self, table_name: str):
        """Display selected table."""
        if self.current_sheet:
            self.current_sheet.pack_forget()
            self.current_sheet.destroy()
            self.current_sheet = None
        if self.selected_table_label:
            try:
                self.selected_table_label.config(bg=self.colors["button"])
            except tk.TclError:
                self.selected_table_label = None
    
        if table_name not in self.tables_data:
            messagebox.showerror("Error", f"Table '{table_name}' does not exist.")
            self.current_table_name = None
            self.selected_table_label = None
            return
    
        table_info = self.tables_data[table_name]
        headers = table_info["headers"]
        data = table_info["data"]
        try:
            sheet = Sheet(
                self.table_frame,
                data=data,
                show_table=True,
                headers=headers,
                font=self.fonts["tksheet"],
                header_font=self.fonts["tksheet"],
                auto_resize_columns=True
            )
            sheet.set_options(table_fg=self.colors["text"], header_fg=self.colors["text"])
            sheet.enable_bindings()
            sheet.extra_bindings([("end_edit_cell", self.on_cell_edit)])
            sheet.pack(expand=True, fill='both')
            self.current_sheet = sheet
            self.current_table_name = table_name
    
            if self.table_name_label:
                self.table_name_label.config(text=self.current_table_name)
            else:
                self.table_name_label = tk.Label(
                    self.root,
                    text=self.current_table_name,
                    bg=self.colors["background"],
                    fg=self.colors["text"],
                    font=self.fonts["header_info"]
                )
                self.table_name_label.grid(row=0, column=0, padx=(60, 10), pady=(5, 0), sticky='nw')
    
            label = self.table_labels.get(table_name)
            if label:
                self.selected_table_label = label
                try:
                    self.selected_table_label.config(bg=self.colors["button_selected"])
                except tk.TclError:
                    self.selected_table_label = None
                    messagebox.showerror("Error", f"Failed to update the label for '{table_name}'.")
            else:
                self.selected_table_label = None
                messagebox.showerror("Error", f"Label for '{table_name}' not found.")
            
            self.filter_header_var.set("All")
            self.filter_header_dropdown['values'] = ["All"] + headers
            
            self.filter_option_var.set("contains")
            self.update_filter_text_state()
            
            self.apply_filter()
        except Exception as e:
            messagebox.showerror("Sheet Error", f"Failed to load table '{table_name}': {e}")
            self.current_table_name = None
            self.selected_table_label = None

    def on_cell_edit(self, event=None):
        """Handle cell edits."""
        if self.current_table_name and self.current_sheet:
            sheet_data = self.current_sheet.get_sheet_data()
            for idx, row in enumerate(sheet_data):
                if idx < len(self.displayed_row_indices):
                    data_idx = self.displayed_row_indices[idx]
                    self.tables_data[self.current_table_name]["data"][data_idx] = row
                else:
                    # Added: Handle new rows by appending to the underlying data
                    self.tables_data[self.current_table_name]["data"].append(row)
            self.save_data()

    def save_data(self):
        """Save all tables data."""
        all_data = {}
        for table_name, table_info in self.tables_data.items():
            all_data[table_name] = [table_info["headers"]] + table_info["data"]
        try:
            with self.save_file_path.open('w', encoding='utf-8') as file:
                json.dump(all_data, file, indent=4)
        except Exception as e:
            print(f"Error saving data: {e}")

    def apply_filter(self, event=None):
        """Apply filter to table data based on selected criteria."""
        if not self.current_table_name or not self.current_sheet:
            return
        header = self.filter_header_var.get()
        option = self.filter_option_var.get()
        text = self.filter_text_var.get().lower()
        original_data = self.tables_data[self.current_table_name]["data"]
    
        def row_matches(row):
            if header == "All":
                cells = row
                target_indices = range(len(cells))
            else:
                try:
                    target_indices = [self.headers[self.current_table_name].index(header)]
                except ValueError:
                    return False
            
            conditions = {
                "is": lambda cell, text: cell == text,
                "is not": lambda cell, text: cell != text,
                "contains": lambda cell, text: text in cell,
                "does not contain": lambda cell, text: text not in cell,
                "starts with": lambda cell, text: cell.startswith(text),
                "ends with": lambda cell, text: cell.endswith(text),
                "is empty": lambda cell, _: cell == "",
                "is not empty": lambda cell, _: cell != ""
            }
            
            for idx in target_indices:
                cell = str(row[idx]).lower()
                if conditions.get(option, lambda _, __: False)(cell, text):
                    return True
            return False
    
        # Collect both filtered data and their original indices
        filtered_data = []
        self.displayed_row_indices = []
    
        for i, row in enumerate(original_data):
            if row_matches(row):
                filtered_data.append(row)
                self.displayed_row_indices.append(i)
    
        try:
            self.current_sheet.set_sheet_data(filtered_data)
        except tk.TclError as e:
            print(f"Error applying filter: {e}")

    def rename_headers(self):
        """Rename table headers."""
        if not self.current_table_name:
            messagebox.showwarning("No Table Selected", "Please select a table to rename headers.")
            return
        current_headers = self.headers[self.current_table_name]
        new_headers = simpledialog.askstring(
            "Rename Headers", 
            "Enter new headers (comma-separated):", 
            initialvalue=", ".join(current_headers)
        )
        if new_headers:
            header_list = [header.strip() for header in new_headers.split(',')]
            if not header_list:
                messagebox.showerror("Invalid Headers", "Header list cannot be empty.")
                return
            self.headers[self.current_table_name] = header_list
            self.tables_data[self.current_table_name]["headers"] = header_list
            if self.current_sheet:
                try:
                    self.current_sheet.headers(header_list)
                except tk.TclError as e:
                    messagebox.showerror("Error", f"Failed to rename headers: {e}")
            
            self.filter_header_var.set("All")
            self.filter_header_dropdown['values'] = ["All"] + header_list
            self.apply_filter()
            
            self.save_data()

    def begin_rename(self, table_name: str):
        """Start renaming a table."""
        if table_name not in self.table_labels:
            messagebox.showerror("Error", f"Table '{table_name}' not found.")
            return
        label = self.table_labels[table_name]
        self.cancel_rename()
        self.rename_entry = tk.Entry(self.table_labels_frame, bg=label.cget("bg"), fg=self.colors["text"], relief='flat')
        self.rename_entry.insert(0, table_name)
        self.rename_entry.place(x=label.winfo_x(), y=label.winfo_y(), width=label.winfo_width(), height=label.winfo_height())
        self.rename_entry.focus()
        self.rename_entry.select_range(0, tk.END)
        self.rename_entry.bind('<Return>', lambda event: self.end_rename(table_name))
        self.rename_entry.bind('<FocusOut>', lambda event: self.cancel_rename())

    def end_rename(self, old_name: str):
        """Finalize table renaming."""
        new_name = self.rename_entry.get().strip()
        if not new_name:
            messagebox.showerror("Invalid Name", "Table name cannot be empty.")
            self.rename_entry.focus_set()
            return
        if new_name in self.tables_data and new_name != old_name:
            messagebox.showerror("Duplicate Name", f"A table named '{new_name}' already exists.")
            self.rename_entry.focus_set()
            return
        if new_name != old_name:
            self.update_table_name(old_name, new_name)
            self.load_selected_table(new_name)
        self.cancel_rename()

    def update_table_name(self, old_name: str, new_name: str):
        """Update table name and resources."""
        self.tables_data[new_name] = self.tables_data.pop(old_name)
        self.headers[new_name] = self.headers.pop(old_name)
        label = self.table_labels.pop(old_name)
        label.config(text=new_name)
        label.bind("<Button-1>", lambda event: self.load_selected_table(new_name))
        label.bind("<Double-1>", lambda event: self.begin_rename(new_name))
        self.table_labels[new_name] = label
        if self.selected_table_label == label:
            try:
                label.config(bg=self.colors["button_selected"])
            except tk.TclError:
                pass
        self.save_data()

    def cancel_rename(self):
        """Cancel rename operation."""
        if self.rename_entry:
            self.rename_entry.destroy()
            self.rename_entry = None

    def add_custom_hover(self, label: tk.Label):
        """Add hover effects to label."""
        def on_enter(event):
            if label != self.selected_table_label:
                try:
                    label.config(bg=self.colors["button_selected"])
                except tk.TclError:
                    pass

        def on_leave(event):
            if label != self.selected_table_label:
                try:
                    label.config(bg=self.colors["button"])
                except tk.TclError:
                    pass

        label.bind("<Enter>", on_enter)
        label.bind("<Leave>", on_leave)

if __name__ == "__main__":
    set_dpi_awareness()
    root = tk.Tk()
    app = TableApp(root)
    root.mainloop()