Add a delete button to the variables field. Python 3.12 code: import math
import re
from typing import Any, Union, Callable

from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QLineEdit, QTextEdit, QScrollBar, QPushButton, QLabel, QFrame,
    QGridLayout
)
from PySide6.QtGui import QFont, QIcon, Qt
from PySide6.QtCore import Qt, QSize, QRect


def convert_angle(func: Callable[[float], float], x: float, use_degrees: bool) -> float:
    """Apply trig function with optional degree conversion."""
    return func(math.radians(x)) if use_degrees else func(x)


def kilograms_to_pounds(kg: float) -> float:
    """Convert kilograms to pounds."""
    return kg / 0.45359237


def pounds_to_kilograms(lb: float) -> float:
    """Convert pounds to kilograms."""
    return lb * 0.45359237


def celsius_to_kelvin(c: float) -> float:
    """Convert Celsius to Kelvin."""
    return c + 273.15


def kelvin_to_celsius(k: float) -> float:
    """Convert Kelvin to Celsius."""
    return k - 273.15


def celsius_to_fahrenheit(c: float) -> float:
    """Convert Celsius to Fahrenheit."""
    return c * 9 / 5 + 32


def fahrenheit_to_celsius(f: float) -> float:
    """Convert Fahrenheit to Celsius."""
    return (f - 32) * 5 / 9


def capitalize_sentence(sentence: str) -> str:
    """Capitalize first letter of each word."""
    return ' '.join(word.capitalize() for word in sentence.split())


def remove_duplicates(text: str) -> str:
    """Remove duplicate words and empty lines."""
    words = ' '.join(line.strip() for line in text.splitlines() if line.strip()).split()
    return ' '.join(dict.fromkeys(words))


def get_safe_globals(use_degrees: bool, variables: dict) -> dict:
    """Provide safe globals for expression evaluation."""
    trig_funcs = {'sin', 'cos', 'tan', 'asin', 'acos', 'atan'}
    safe_globals = {
        "sqrt": math.sqrt,
        "log": math.log10,
        "ln": math.log,
        "pi": math.pi,
        "e": math.e,
        "lb": kilograms_to_pounds,
        "kg": pounds_to_kilograms,
        "K": celsius_to_kelvin,
        "C": kelvin_to_celsius,
        "F": celsius_to_fahrenheit,
        "CF": fahrenheit_to_celsius,
        "capitalize": capitalize_sentence,
        "duplicates": remove_duplicates,
    }
    for func in trig_funcs:
        safe_globals[func] = lambda x, f=func: convert_angle(getattr(math, f), x, use_degrees)
    safe_globals.update(variables)
    return safe_globals


def preprocess_text_functions(expression: str) -> str:
    """Preprocess text functions with argument quotes."""
    def add_quotes(match):
        func, args = match.groups()
        args = args.strip()
        if not (args.startswith('"') and args.endswith('"')) and not (args.startswith("'") and args.endswith("'")):
            args = f'"{args}"'
        return f'{func}({args})'
    return re.sub(r'\b(capitalize|duplicates)\s*\(\s*([^)]+)\s*\)', add_quotes, expression)


def validate_inverse_trig(expression: str, safe_globals: dict) -> Union[str, None]:
    """Check inverse trig function domain validity."""
    for func, arg in re.findall(r"(\b\w+\b)\s*\(\s*([^)]+)\s*\)", expression):
        if func in ["asin", "acos"]:
            try:
                value = eval(arg, {"__builtins__": None}, safe_globals)
                if abs(value) > 1:
                    return f"Error: {func}({arg}) is undefined for |{arg}| > 1"
            except Exception as e:
                return f"Error: {e}"
    return None


def safe_evaluate(expression: str, safe_globals: dict) -> Union[str, Any]:
    """Safely evaluate expression and format result with three decimal places."""
    try:
        result = eval(expression, {"__builtins__": None}, safe_globals)
        if isinstance(result, (int, float)):
            if isinstance(result, float):
                abs_result = abs(result)
                if (abs_result >= 1e9) or (abs_result != 0 and abs_result < 1e-6):
                    result = f"{result:.3e}"
                else:
                    result = f"{result:.3f}"
            else:
                if len(str(abs(result))) > 9:
                    result = f"{result:.3e}"
                else:
                    result = str(result)
            return result
        else:
            return result
    except Exception as e:
        return f"Error: {e}"


def evaluate_expression(expression: str, variables: dict, use_degrees: bool = False) -> Union[float, str]:
    """Evaluate math expression considering modes."""
    assignment_match = re.match(r'^\s*([a-zA-Z_]\w*)\s*=\s*(.+)$', expression)
    expression = preprocess_text_functions(expression)
    safe_globals = get_safe_globals(use_degrees, variables)

    if assignment_match:
        var_name, var_expr = assignment_match.groups()
        var_value = safe_evaluate(var_expr, safe_globals)
        try:
            if isinstance(var_value, str) and not var_value.startswith("Error"):
                if '.' in var_value:
                    var_value_converted = float(var_value)
                else:
                    var_value_converted = int(var_value)
                variables[var_name] = var_value_converted
                return f"{var_name} = {var_value_converted}"
            else:
                return var_value
        except ValueError:
            return f"Error: Cannot assign non-numeric value to variable '{var_name}'"
    else:
        error = validate_inverse_trig(expression, safe_globals)
        if error:
            return error
        return safe_evaluate(expression, safe_globals)


class CalculatorGUI(QMainWindow):
    """PySide6-based calculator application."""

    def __init__(self):
        super().__init__()
        self.use_degrees = False
        self.variables = {}
        self.history_entries = []
        self.text_functions = ['capitalize', 'duplicates']
        self.setup_window()
        self.create_widgets()
        self.layout_widgets()
        self.connect_signals()

    def setup_window(self) -> None:
        """Set main window properties."""
        self.setWindowTitle("Calculator")
        self.setGeometry(100, 100, 1280, 720)

    def create_widgets(self) -> None:
        """Initialize GUI components."""
        font = QFont()
        font.setPointSize(12)
        font.setFamily('Literata')

        self.entry = QLineEdit()
        self.entry.setFont(font)
        self.entry.setPlaceholderText('Enter expression')
        self.entry.setClearButtonEnabled(True)
        self.entry.returnPressed.connect(self.calculate)

        self.history_text = QTextEdit()
        self.history_text.setFont(font)
        self.history_text.setReadOnly(True)
        self.history_text.setStyleSheet("position: relative;")

        self.scrollbar = QScrollBar(Qt.Vertical)
        self.history_text.setVerticalScrollBar(self.scrollbar)

        # Search Button
        self.search_button = QPushButton("🔍", self.history_text)
        self.search_button.setFixedSize(24, 24)
        self.search_button.setToolTip("Search History")
        self.search_button.setStyleSheet("""
            QPushButton {
                border: none;
                background-color: rgba(255, 255, 255, 200);
                font-size: 14px;
                border-radius: 12px;
            }
            QPushButton:hover {
                background-color: rgba(0, 120, 215, 200);
                color: white;
            }
        """)
        self.search_button.clicked.connect(self.toggle_search)
        self.search_button.raise_()

        # Delete Button
        self.delete_button = QPushButton("🗑️", self.history_text)
        self.delete_button.setFixedSize(24, 24)
        self.delete_button.setToolTip("Clear History")
        self.delete_button.setStyleSheet("""
            QPushButton {
                border: none;
                background-color: rgba(255, 255, 255, 200);
                font-size: 14px;
                border-radius: 12px;
            }
            QPushButton:hover {
                background-color: rgba(255, 0, 0, 200);
                color: white;
            }
        """)
        self.delete_button.clicked.connect(self.clear_history)
        self.delete_button.raise_()

        # Variables Button (New)
        self.vars_button = QPushButton("🧮", self.history_text)  # Using an abacus emoji as an example
        self.vars_button.setFixedSize(24, 24)
        self.vars_button.setToolTip("Show Variables")
        self.vars_button.setStyleSheet("""
            QPushButton {
                border: none;
                background-color: rgba(255, 255, 255, 200);
                font-size: 14px;
                border-radius: 12px;
            }
            QPushButton:hover {
                background-color: rgba(0, 150, 136, 200);
                color: white;
            }
        """)
        self.vars_button.clicked.connect(self.toggle_vars_overlay)
        self.vars_button.raise_()

        # Search Bar
        self.search_bar = QLineEdit(self.history_text)
        self.search_bar.setPlaceholderText("Search...")
        self.search_bar.setFixedHeight(24)
        self.search_bar.setVisible(False)
        self.search_bar.setStyleSheet("""
            QLineEdit {
                border: 1px solid gray;
                border-radius: 4px;
                padding: 2px 4px;
                background-color: white;
            }
        """)
        self.search_bar.textChanged.connect(self.filter_history)
        self.search_bar.setClearButtonEnabled(True)

        # Function Buttons
        self.function_buttons = {
            "Functions": ["sqrt", "sin", "cos", "tan", "asin", "acos", "atan", "log", "ln"],
            "Conversions": ["lb", "kg", "K", "C", "F", "CF"],
            "Text": ["capitalize", "duplicates"],
            "Constants": ["pi", "e"],
            "Settings": []
        }
        self.function_frame = QFrame()
        self.create_function_buttons(font)

        # Variables Overlay (New) as a QLineEdit
        self.vars_overlay = QLineEdit(self.history_text)
        self.vars_overlay.setReadOnly(True)
        self.vars_overlay.setPlaceholderText("a = 1, b = 85, ...")
        self.vars_overlay.setFixedHeight(24)
        self.vars_overlay.setStyleSheet("""
            QLineEdit {
                border: 1px solid gray;
                border-radius: 4px;
                padding: 2px 4px;
                background-color: white;
            }
        """)
        self.vars_overlay.setVisible(False)

    def create_function_buttons(self, font: QFont) -> None:
        """Create buttons for calculator functions."""
        layout = QGridLayout()
        categories = [("Functions", 3), ("Conversions", 2), ("Text", 2), ("Constants", 2), ("Settings", 1)]
        starting_col = 0

        for category, col_span in categories:
            label = QLabel(category)
            label.setFont(font)
            layout.addWidget(label, 0, starting_col, 1, col_span, Qt.AlignCenter)

            buttons = self.function_buttons.get(category, [])
            if category == "Functions":
                for i, func in enumerate(buttons):
                    button = QPushButton(func)
                    button.setFont(font)
                    button.clicked.connect(lambda _, f=func: self.insert_function(f, is_text_function=(f in self.text_functions)))
                    layout.addWidget(button, (i // 3) + 1, starting_col + (i % 3))

            elif category == "Settings":
                self.mode_button = QPushButton("Radians")
                self.mode_button.setFont(font)
                self.mode_button.clicked.connect(self.toggle_mode)
                layout.addWidget(self.mode_button, 1, starting_col, 1, 1)

            elif category == "Conversions":
                for idx, func in enumerate(buttons):
                    display_text = {
                        "lb": "lb(kg)", "kg": "kg(lb)", "K": "K(°C)",
                        "C": "°C(K)", "F": "F(°C)", "CF": "°C(F)"
                    }.get(func, func)
                    button = QPushButton(display_text)
                    button.setFont(font)
                    button.clicked.connect(lambda _, f=func: self.insert_function(f, is_text_function=False))
                    row, col = 1 + idx // 2, starting_col + idx % 2
                    layout.addWidget(button, row, col)

            elif category == "Text":
                for idx, func in enumerate(buttons):
                    button = QPushButton(func)
                    button.setFont(font)
                    button.clicked.connect(lambda _, f=func: self.insert_function(f, is_text_function=True))
                    row, col = 1 + idx // col_span, starting_col + idx % col_span
                    layout.addWidget(button, row, col)

            elif category == "Constants":
                for idx, func in enumerate(buttons):
                    button = QPushButton(func)
                    button.setFont(font)
                    button.clicked.connect(lambda _, f=func: self.insert_function(f, is_constant=True))
                    row, col = 1 + idx // col_span, starting_col + idx % col_span
                    layout.addWidget(button, row, col)

            # Fill empty spaces if needed
            total_rows = math.ceil(len(buttons) / col_span)
            for extra in range(col_span * total_rows - len(buttons)):
                layout.addWidget(QLabel(""), 1 + extra // col_span, starting_col + extra % col_span)

            starting_col += col_span

        self.function_frame.setLayout(layout)

    def layout_widgets(self) -> None:
        """Arrange widgets in the main window."""
        central_widget = QWidget()
        main_layout = QVBoxLayout()

        main_layout.addWidget(self.entry)

        history_layout = QHBoxLayout()
        history_layout.addWidget(self.history_text)
        history_layout.addWidget(self.scrollbar)
        main_layout.addLayout(history_layout)

        main_layout.addWidget(self.function_frame)

        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)

    def connect_signals(self) -> None:
        """Connect widget signals."""
        QApplication.instance().focusChanged.connect(self.on_focus_changed)

    def toggle_mode(self) -> None:
        """Toggle between degrees and radians."""
        self.use_degrees = not self.use_degrees
        self.mode_button.setText("Degrees" if self.use_degrees else "Radians")

    def insert_function(self, function: str, is_constant: bool = False, is_text_function: bool = False) -> None:
        """Insert function or constant in entry."""
        current_text = self.entry.text()
        cursor_position = self.entry.cursorPosition()
        if is_constant:
            new_text = f"{current_text[:cursor_position]}{function} "
            new_cursor_position = cursor_position + len(function) + 1
        elif is_text_function and function in self.text_functions:
            new_text = f'{current_text[:cursor_position]}{function}() {current_text[cursor_position:]}'
            new_cursor_position = cursor_position + len(function) + 1
        else:
            new_text = f"{current_text[:cursor_position]}{function}() {current_text[cursor_position:]}"
            new_cursor_position = cursor_position + len(function) + 1
        self.entry.setText(new_text)
        self.entry.setCursorPosition(new_cursor_position)
        self.entry.setFocus()

    def calculate(self) -> None:
        """Evaluate entered expression."""
        expression = self.entry.text().strip()
        if expression:
            result = evaluate_expression(
                expression, self.variables, use_degrees=self.use_degrees
            )
            self.display_result(expression, result)
            self.entry.clear()

    def display_result(self, expression: str, result: Union[float, str]) -> None:
        """Show result in history."""
        mode = "D" if self.use_degrees else "R"
        if re.match(r'^\s*[a-zA-Z_]\w*\s*=\s*.+$', expression):
            history_entry = f"[{mode}] {result}"
        else:
            history_entry = f"[{mode}] {expression} = {result}"
        self.history_entries.append(history_entry)
        self.refresh_history()
        self.history_text.verticalScrollBar().setValue(
            self.history_text.verticalScrollBar().maximum()
        )

    def toggle_search(self) -> None:
        """Toggle search bar visibility."""
        is_visible = self.search_bar.isVisible()
        self.search_bar.setVisible(not is_visible)
        if not is_visible:
            self.search_bar.setFocus()
            self.search_bar.setText("")
        else:
            self.search_bar.clear()
        self.update_overlay_buttons_position()
        self.refresh_history()

    def filter_history(self, text: str) -> None:
        """Filter history based on search input."""
        self.refresh_history(text)

    def refresh_history(self, filter_text: str = "") -> None:
        """Refresh history view with optional filtering."""
        self.history_text.clear()
        for entry in self.history_entries:
            if filter_text.lower() in entry.lower():
                self.history_text.append(entry)

    def resizeEvent(self, event) -> None:
        """Handle window resize events."""
        super().resizeEvent(event)
        self.update_overlay_buttons_position()

    def update_overlay_buttons_position(self) -> None:
        """Reposition search, delete, and vars buttons and related overlays over the history_text widget."""
        history_rect = self.history_text.viewport().rect()
        size = self.history_text.viewport().size()

        margin_right = 5
        margin_top = 5
        margin_bottom = 5
        spacing = 5

        # Search Button Position
        self.search_button.move(
            size.width() - self.search_button.width() - margin_right,
            margin_top
        )

        # Delete Button Position
        self.delete_button.move(
            size.width() - self.delete_button.width() - margin_right,
            size.height() - self.delete_button.height() - margin_bottom
        )

        # Vars Button Position
        self.vars_button.move(
            margin_right,  # Positioned from the left
            size.height() - self.vars_button.height() - margin_bottom
        )

        # Vars Overlay Position (to the right of vars_button)
        if self.vars_overlay.isVisible():
            overlay_width = int(size.width() * 0.3)  # Adjust width as needed
            self.vars_overlay.setFixedWidth(overlay_width)
            self.vars_overlay.move(
                self.vars_button.x() + self.vars_button.width() + spacing,
                size.height() - self.vars_overlay.height() - margin_bottom
            )

        # Search Bar Position
        if self.search_bar.isVisible():
            search_bar_width = int(size.width() * 0.25)
            # Ensure the search bar does not overlap with vars_overlay
            available_width = self.search_button.x() - spacing - search_bar_width
            if available_width < 0:
                search_bar_width = self.search_button.x() - spacing
            self.search_bar.setFixedWidth(search_bar_width)
            self.search_bar.move(
                self.search_button.x() - search_bar_width - spacing,
                margin_top
            )

    def on_focus_changed(self, old: QWidget, new: QWidget) -> None:
        """Auto-hide search if focus is lost."""
        if self.search_bar.isVisible() and not self.search_bar.text().strip() and new != self.search_bar:
            self.toggle_search()

    def showEvent(self, event) -> None:
        """Position overlay buttons on showing the window."""
        super().showEvent(event)
        self.update_overlay_buttons_position()

    def clear_history(self) -> None:
        """Clear the history entries and update the display."""
        self.history_entries.clear()
        self.refresh_history()

    def toggle_vars_overlay(self) -> None:
        """Toggle the visibility of the variables overlay."""
        if self.vars_overlay.isVisible():
            self.vars_overlay.setVisible(False)
        else:
            self.update_vars_overlay()
            self.vars_overlay.setVisible(True)
        self.update_overlay_buttons_position()

    def update_vars_overlay(self) -> None:
        """Update the variables overlay with current variables."""
        if not self.variables:
            self.vars_overlay.setText("No variables assigned.")
        else:
            vars_text = ", ".join(f"{var} = {value}" for var, value in self.variables.items())
            self.vars_overlay.setText(vars_text)


def main() -> None:
    """Start the calculator application."""
    app = QApplication([])
    calculator = CalculatorGUI()
    calculator.show()
    app.exec()


if __name__ == "__main__":
    main()