import json
import math
import sys

from PySide6.QtCore import Qt, QSize, QStringListModel
from PySide6.QtGui import (
    QColor,
    QFont,
    QPainter,
    QSyntaxHighlighter,
    QTextCharFormat,
    QTextCursor,
)
from PySide6.QtWidgets import (
    QApplication,
    QDockWidget,
    QFrame,
    QCompleter,
    QFileDialog,
    QHBoxLayout,
    QLabel,
    QLineEdit,
    QListWidget,
    QMainWindow,
    QPlainTextEdit,
    QTextEdit,
    QPushButton,
    QStatusBar,
    QTabWidget,
    QTableWidget,
    QTableWidgetItem,
    QToolBar,
    QVBoxLayout,
    QWidget,
)

from abacus_core import AbacusCore


STYLE = """
QMainWindow, QWidget {
    background: #0d1117;
    color: #e6edf3;
    font-family: "Segoe UI";
    font-size: 13px;
}

QToolBar {
    background: #111820;
    border: 0;
    border-bottom: 1px solid #202a36;
    spacing: 5px;
    padding: 6px 8px;
}

QToolButton {
    color: #c9d1d9;
    background: transparent;
    border-radius: 7px;
    padding: 7px 10px;
}

QToolButton:hover {
    background: #1b2531;
    color: #ffffff;
}

QTabWidget::pane {
    border: 1px solid #202a36;
    background: #0d1117;
}

QTabBar::tab {
    background: #111820;
    color: #7d8b99;
    border: 1px solid #202a36;
    border-bottom: none;
    padding: 9px 16px;
    margin-right: 2px;
}

QTabBar::tab:selected {
    background: #17212c;
    color: #f0f6fc;
}

QDockWidget {
    color: #aeb9c6;
    titlebar-close-icon: none;
    titlebar-normal-icon: none;
}

QDockWidget::title {
    background: #111820;
    border-bottom: 1px solid #202a36;
    padding: 9px 11px;
    font-weight: 700;
}

QFrame#editorCard, QFrame#bottomCard {
    background: #111820;
    border: 1px solid #202a36;
    border-radius: 10px;
}

QPlainTextEdit {
    background: #0b0f14;
    color: #d6deea;
    border: 1px solid #202a36;
    border-radius: 9px;
    selection-background-color: #244a7d;
    padding: 8px;
    font-family: "JetBrains Mono", "Consolas", monospace;
    font-size: 14px;
}

QLineEdit#singleLine {
    background: #0b0f14;
    color: #f0f6fc;
    border: 1px solid #303b48;
    border-radius: 9px;
    padding: 9px 11px;
    font-family: "JetBrains Mono", "Consolas", monospace;
}

QLineEdit#singleLine:focus {
    border: 1px solid #3b82f6;
}

QPushButton {
    background: #2563eb;
    color: white;
    border: none;
    border-radius: 8px;
    padding: 8px 13px;
    font-weight: 700;
}

QPushButton:hover {
    background: #3475ef;
}

QPushButton#operator {
    background: #1b2531;
    color: #cbd5e1;
    border: 1px solid #293543;
    min-width: 34px;
}

QPushButton#operator:hover {
    background: #253344;
    color: #ffffff;
}

QPushButton#secondary {
    background: #1b2531;
    color: #b9c4d0;
    border: 1px solid #293543;
}

QListWidget, QTableWidget {
    background: #0d131a;
    color: #cbd5e1;
    border: 1px solid #202a36;
    border-radius: 8px;
    outline: none;
}

QListWidget::item {
    padding: 7px 8px;
    border-radius: 6px;
}

QListWidget::item:hover {
    background: #17212c;
}

QListWidget::item:selected {
    background: #1c3153;
    color: #ffffff;
}

QHeaderView::section {
    background: #161f29;
    color: #9aa8b7;
    border: none;
    border-right: 1px solid #202a36;
    border-bottom: 1px solid #202a36;
    padding: 7px;
}

QTableWidget::item {
    padding: 6px;
    border-bottom: 1px solid #18212b;
}

QStatusBar {
    background: #0b0f14;
    color: #7d8b99;
    border-top: 1px solid #202a36;
}

QScrollBar:vertical {
    background: #0d131a;
    width: 9px;
}

QScrollBar::handle:vertical {
    background: #303c4a;
    border-radius: 4px;
    min-height: 25px;
}

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


class AbacusHighlighter(QSyntaxHighlighter):
    def __init__(self, document):
        super().__init__(document)

        self.number = QTextCharFormat()
        self.number.setForeground(QColor("#79c0ff"))

        self.operator = QTextCharFormat()
        self.operator.setForeground(QColor("#ff7b72"))

        self.comment = QTextCharFormat()
        self.comment.setForeground(QColor("#6e7681"))
        self.comment.setFontItalic(True)

        self.assignment = QTextCharFormat()
        self.assignment.setForeground(QColor("#d2a8ff"))

        self.keyword = QTextCharFormat()
        self.keyword.setForeground(QColor("#ffa657"))

    def highlightBlock(self, text):
        # Comments
        comment_pos = text.find("#")
        if comment_pos >= 0:
            self.setFormat(comment_pos, len(text) - comment_pos, self.comment)
            code = text[:comment_pos]
        else:
            code = text

        # Numbers
        i = 0
        while i < len(code):
            if code[i].isdigit() or (
                code[i] == "." and i + 1 < len(code) and code[i + 1].isdigit()
            ):
                start = i
                i += 1
                while i < len(code) and (code[i].isdigit() or code[i] == "."):
                    i += 1
                self.setFormat(start, i - start, self.number)
            else:
                i += 1

        for char in "+-*/=():[];":
            start = 0
            while True:
                pos = code.find(char, start)
                if pos < 0:
                    break
                self.setFormat(pos, 1, self.operator)
                start = pos + 1

        # Function definitions: name before ':'
        colon = code.find(":")
        if colon > 0:
            name = code[:colon].strip()
            if name:
                pos = code.find(name)
                self.setFormat(pos, len(name), self.keyword)


class LineNumberArea(QWidget):
    def __init__(self, editor):
        super().__init__(editor)
        self.editor = editor

    def sizeHint(self):
        return QSize(self.editor.line_number_width(), 0)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.fillRect(event.rect(), QColor("#0f151c"))

        block = self.editor.firstVisibleBlock()
        block_number = block.blockNumber()
        top = int(
            self.editor.blockBoundingGeometry(block).translated(
                self.editor.contentOffset()
            ).top()
        )
        bottom = top + int(self.editor.blockBoundingRect(block).height())

        while block.isValid() and top <= event.rect().bottom():
            if block.isVisible() and bottom >= event.rect().top():
                painter.setPen(QColor("#596878"))
                painter.drawText(
                    0,
                    top,
                    self.width() - 7,
                    self.editor.fontMetrics().height(),
                    Qt.AlignRight,
                    str(block_number + 1),
                )

            block = block.next()
            top = bottom
            bottom = top + int(self.editor.blockBoundingRect(block).height())
            block_number += 1


class CodeEditor(QPlainTextEdit):
    def __init__(self):
        super().__init__()

        self.line_numbers = LineNumberArea(self)
        self.error_lines = {}
        self.completer = None

        self.blockCountChanged.connect(self.update_line_number_width)
        self.updateRequest.connect(self.update_line_number_area)
        self.cursorPositionChanged.connect(self.highlight_current_line)

        self.update_line_number_width(0)
        self.highlight_current_line()

        font = QFont("JetBrains Mono", 11)
        font.setStyleHint(QFont.Monospace)
        self.setFont(font)
        self.setTabStopDistance(
            self.fontMetrics().horizontalAdvance(" ") * 4
        )

    def line_number_width(self):
        digits = len(str(max(1, self.blockCount())))
        return 12 + self.fontMetrics().horizontalAdvance("9") * digits

    def update_line_number_width(self, _):
        self.setViewportMargins(self.line_number_width(), 0, 0, 0)

    def update_line_number_area(self, rect, dy):
        if dy:
            self.line_numbers.scroll(0, dy)
        else:
            self.line_numbers.update(
                0,
                rect.y(),
                self.line_numbers.width(),
                rect.height(),
            )

        if rect.contains(self.viewport().rect()):
            self.update_line_number_width(0)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        rect = self.contentsRect()
        rect.setWidth(self.line_number_width())
        self.line_numbers.setGeometry(rect)

    def highlight_current_line(self):
        extra = []

        if not self.isReadOnly():
            selection = QTextCharFormat()
            selection.setBackground(QColor("#111a24"))
            selection.setProperty(
                QTextCharFormat.FullWidthSelection,
                True,
            )

            extra_selection = QTextEdit.ExtraSelection()
            extra_selection.cursor = self.textCursor()
            extra_selection.format = selection
            extra.append(extra_selection)

        # Error lines always get a stronger red background.
        for line_number, message in self.error_lines.items():
            block = self.document().findBlockByNumber(line_number - 1)
            if not block.isValid():
                continue

            cursor = self.textCursor()
            cursor.setPosition(block.position())
            cursor.setPosition(
                block.position() + max(1, block.length() - 1),
                QTextCursor.KeepAnchor,
            )

            selection = QTextCharFormat()
            selection.setBackground(QColor("#4a1f25"))
            selection.setForeground(QColor("#ffb4b4"))
            selection.setProperty(
                QTextCharFormat.FullWidthSelection,
                True,
            )

            extra_selection = QTextEdit.ExtraSelection()
            extra_selection.cursor = cursor
            extra_selection.format = selection
            extra.append(extra_selection)

        self.setExtraSelections(extra)

        self.line_numbers.update()

    def set_error_lines(self, errors):
        self.error_lines = dict(errors)
        self.highlight_current_line()

    def clear_error_lines(self):
        self.error_lines.clear()
        self.highlight_current_line()

    def set_completer(self, completer):
        self.completer = completer
        completer.setWidget(self)
        completer.setCompletionMode(QCompleter.PopupCompletion)
        completer.setCaseSensitivity(Qt.CaseInsensitive)
        completer.activated.connect(self.insert_completion)

    def insert_completion(self, completion):
        prefix = self.completion_prefix()
        cursor = self.textCursor()

        if prefix:
            cursor.movePosition(
                QTextCursor.Left,
                QTextCursor.KeepAnchor,
                len(prefix),
            )
            cursor.insertText(completion)
        else:
            cursor.insertText(completion)

        self.setTextCursor(cursor)

    def completion_prefix(self):
        cursor = self.textCursor()
        text = cursor.block().text()
        pos = cursor.positionInBlock()

        start = pos
        while start > 0:
            char = text[start - 1]
            if not (char.isalnum() or char == "_"):
                break
            start -= 1

        return text[start:pos]

    def show_completion(self, show_all=False):
        if not self.completer:
            return

        prefix = self.completion_prefix()
        if not prefix and not show_all:
            self.completer.popup().hide()
            return

        self.completer.setCompletionPrefix(prefix)

        if show_all:
            self.completer.setCompletionPrefix("")

        if self.completer.completionCount() == 0:
            self.completer.popup().hide()
            return

        rect = self.cursorRect()
        rect.setWidth(
            self.completer.popup().sizeHintForColumn(0)
            + self.completer.popup().verticalScrollBar().sizeHint().width()
        )
        self.completer.complete(rect)

    def keyPressEvent(self, event):
        if self.completer and self.completer.popup().isVisible():
            if event.key() in (
                Qt.Key_Enter,
                Qt.Key_Return,
                Qt.Key_Tab,
                Qt.Key_Backtab,
            ):
                event.ignore()
                return

            if event.key() == Qt.Key_Escape:
                self.completer.popup().hide()
                return

        if (
            event.key() == Qt.Key_Space
            and event.modifiers() & Qt.ControlModifier
        ):
            self.show_completion(show_all=True)
            return

        super().keyPressEvent(event)

        if self.completer:
            self.show_completion()

class ResultGraph(QWidget):
    """Small dependency-free graph of numeric results."""

    def __init__(self):
        super().__init__()
        self.values = []

    def set_values(self, values):
        self.values = values[-50:]
        self.update()

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(QPainter.Antialiasing)
        painter.fillRect(self.rect(), QColor("#0b0f14"))

        r = self.rect().adjusted(45, 20, -20, -35)
        painter.setPen(QColor("#273442"))
        painter.drawRect(r)

        if not self.values:
            painter.setPen(QColor("#647383"))
            painter.drawText(r, Qt.AlignCenter, "No numeric results yet")
            return

        numeric = [v for v in self.values if math.isfinite(v)]
        if not numeric:
            return

        lo = min(numeric)
        hi = max(numeric)
        if abs(hi - lo) < 1e-12:
            lo -= 1
            hi += 1

        painter.setPen(QColor("#344454"))
        for frac in (0.0, 0.5, 1.0):
            y = int(r.bottom() - frac * r.height())
            painter.drawLine(r.left(), y, r.right(), y)

        painter.setPen(QColor("#5f6f80"))
        painter.drawText(5, r.top() + 5, 35, 20, Qt.AlignRight, f"{hi:g}")
        painter.drawText(5, r.bottom() - 15, 35, 20, Qt.AlignRight, f"{lo:g}")

        if len(numeric) == 1:
            x = r.center().x()
            y = r.bottom() - (numeric[0] - lo) / (hi - lo) * r.height()
            painter.setBrush(QColor("#58a6ff"))
            painter.drawEllipse(int(x - 4), int(y - 4), 8, 8)
            return

        points = []
        count = len(numeric)
        for i, value in enumerate(numeric):
            x = r.left() + i * r.width() / max(1, count - 1)
            y = r.bottom() - (value - lo) / (hi - lo) * r.height()
            points.append((x, y))

        painter.setPen(QColor("#58a6ff"))
        for a, b in zip(points, points[1:]):
            painter.drawLine(int(a[0]), int(a[1]), int(b[0]), int(b[1]))

        painter.setBrush(QColor("#58a6ff"))
        for x, y in points:
            painter.drawEllipse(int(x - 3), int(y - 3), 6, 6)


class AbacusIDE(QMainWindow):
    def __init__(self):
        super().__init__()

        self.core = AbacusCore()
        self.step = 0
        self.results = []

        self.setWindowTitle("Abacus IDE")
        self.resize(1400, 850)
        self.setMinimumSize(1050, 650)

        self._build_main()
        self._build_menu()
        self._build_toolbar()
        self._build_docks()
        self._build_statusbar()

        self._refresh_sidebars()
        self.editor.setFocus()

    def _build_menu(self):
        menu = self.menuBar()

        file_menu = menu.addMenu("File")

        export_action = file_menu.addAction("Export history to JSON...")
        export_action.triggered.connect(self.export_history)

        import_action = file_menu.addAction("Import history from JSON...")
        import_action.triggered.connect(self.import_history)

        file_menu.addSeparator()

        reset_action = file_menu.addAction("Reset Abacus")
        reset_action.triggered.connect(self.reset_abacus)

        file_menu.addSeparator()

        exit_action = file_menu.addAction("Exit")
        exit_action.triggered.connect(self.close)

        edit_menu = menu.addMenu("Edit")
        clear_errors = edit_menu.addAction("Clear error highlighting")
        clear_errors.triggered.connect(self.editor.clear_error_lines)

    def _build_toolbar(self):
        toolbar = QToolBar()
        toolbar.setMovable(False)
        toolbar.setToolButtonStyle(Qt.ToolButtonTextOnly)
        self.addToolBar(toolbar)

        run = toolbar.addAction("▶  Run")
        run.triggered.connect(self.run_document)

        run_line = toolbar.addAction("Run line")
        run_line.triggered.connect(self.run_current_line)

        toolbar.addSeparator()

        for symbol in ("+", "-", "*", "/", "(", ")", "[", "]", "="):
            button = QPushButton(symbol)
            button.setObjectName("operator")
            button.clicked.connect(
                lambda checked=False, s=symbol: self.insert_operator(s)
            )
            toolbar.addWidget(button)

        toolbar.addSeparator()

        clear = toolbar.addAction("Clear")
        clear.triggered.connect(self.editor.clear)

        toolbar.addSeparator()

        reset = toolbar.addAction("Reset")
        reset.triggered.connect(self.reset_abacus)

    def _build_main(self):
        self.tabs = QTabWidget()
        self.setCentralWidget(self.tabs)

        # Workspace tab
        workspace = QWidget()
        workspace_layout = QVBoxLayout(workspace)
        workspace_layout.setContentsMargins(10, 10, 10, 10)
        workspace_layout.setSpacing(8)

        editor_header = QHBoxLayout()
        label = QLabel("Expression editor")
        label.setStyleSheet(
            "font-size: 13px; font-weight: 700; color: #c9d1d9;"
        )

        hint = QLabel(
            "One command per line  •  # comments  •  Enter = run line"
        )
        hint.setStyleSheet("color: #657585;")

        editor_header.addWidget(label)
        editor_header.addStretch()
        editor_header.addWidget(hint)
        workspace_layout.addLayout(editor_header)

        self.editor = CodeEditor()
        self.highlighter = AbacusHighlighter(self.editor.document())

        self.completion_model = QStringListModel()
        self.completer = QCompleter(self.completion_model, self)
        self.editor.set_completer(self.completer)

        self.editor.setPlainText(
            "# Example\n"
            "alpha = 10\n"
            "beta = 2\n"
            "alpha + beta * 3\n"
        )
        workspace_layout.addWidget(self.editor, 1)

        bottom = QFrame()
        bottom.setObjectName("bottomCard")
        bottom_layout = QHBoxLayout(bottom)
        bottom_layout.setContentsMargins(10, 8, 10, 8)

        bottom_layout.addWidget(QLabel("Quick input:"))

        self.single_line = QLineEdit()
        self.single_line.setObjectName("singleLine")
        self.single_line.setPlaceholderText("Enter expression...")
        self.single_line.returnPressed.connect(self.run_single_line)
        bottom_layout.addWidget(self.single_line, 1)

        execute = QPushButton("Execute")
        execute.clicked.connect(self.run_single_line)
        bottom_layout.addWidget(execute)

        workspace_layout.addWidget(bottom)

        self.tabs.addTab(workspace, "Workspace")

        # Results tab
        results_page = QWidget()
        results_layout = QVBoxLayout(results_page)
        results_layout.setContentsMargins(10, 10, 10, 10)

        self.results_table = QTableWidget(0, 4)
        self.results_table.setHorizontalHeaderLabels(
            ["Step", "Expression", "Result", "Comment"]
        )
        self.results_table.horizontalHeader().setStretchLastSection(True)
        self.results_table.setAlternatingRowColors(False)
        results_layout.addWidget(self.results_table)

        self.tabs.addTab(results_page, "Results")

        # Graph tab
        graph_page = QWidget()
        graph_layout = QVBoxLayout(graph_page)
        graph_layout.setContentsMargins(10, 10, 10, 10)

        graph_title = QLabel("Numeric result history")
        graph_title.setStyleSheet(
            "font-size: 13px; font-weight: 700; color: #c9d1d9;"
        )
        graph_layout.addWidget(graph_title)

        self.graph = ResultGraph()
        graph_layout.addWidget(self.graph, 1)

        graph_info = QLabel(
            "The graph uses numeric results from executed expressions."
        )
        graph_info.setStyleSheet("color: #657585;")
        graph_layout.addWidget(graph_info)

        self.tabs.addTab(graph_page, "Graph")

    def _build_docks(self):
        self.variables_dock = self._make_dock(
            "Variables", self._make_list_widget()
        )
        self.functions_dock = self._make_dock(
            "Functions", self._make_list_widget()
        )
        self.history_dock = self._make_dock(
            "History", self._make_list_widget()
        )

        self.variables_list = self.variables_dock.widget()
        self.functions_list = self.functions_dock.widget()
        self.history_list = self.history_dock.widget()

        self.addDockWidget(Qt.RightDockWidgetArea, self.variables_dock)
        self.addDockWidget(Qt.RightDockWidgetArea, self.functions_dock)
        self.addDockWidget(Qt.RightDockWidgetArea, self.history_dock)

        self.tabifyDockWidget(self.variables_dock, self.functions_dock)
        self.tabifyDockWidget(self.functions_dock, self.history_dock)
        self.variables_dock.raise_()

    def _make_list_widget(self):
        return QListWidget()

    def _make_dock(self, title, widget):
        dock = QDockWidget(title, self)
        dock.setAllowedAreas(
            Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea
        )
        dock.setFeatures(
            QDockWidget.DockWidgetMovable
            | QDockWidget.DockWidgetFloatable
        )
        dock.setWidget(widget)
        return dock

    def _build_statusbar(self):
        self.status = QStatusBar()
        self.setStatusBar(self.status)
        self.status.showMessage("Ready")

    def insert_operator(self, symbol):
        self.editor.insertPlainText(symbol)
        self.editor.setFocus()

    def run_single_line(self):
        text = self.single_line.text().strip()
        if not text:
            return

        self._execute(text)
        self.single_line.clear()

    def run_current_line(self):
        cursor = self.editor.textCursor()
        cursor.select(cursor.LineUnderCursor)
        line = cursor.selectedText().strip()

        if line:
            self.editor.clear_error_lines()
            line_number = cursor.blockNumber() + 1
            self._execute(line, line_number)

    def run_document(self):
        self.editor.clear_error_lines()
        lines = self.editor.toPlainText().splitlines()

        executed = 0
        failed = 0

        for line_index, line in enumerate(lines, start=1):
            stripped = line.strip()
            if not stripped or stripped.startswith("#"):
                continue

            if self._execute(stripped, line_index):
                executed += 1
            else:
                failed += 1

        if failed:
            self.status.showMessage(
                f"Executed {executed} command(s), {failed} error(s)"
            )
        else:
            self.status.showMessage(f"Executed {executed} command(s)")

    def _execute(self, text, line_number=None):
        self.step += 1

        try:
            result = self.core.parse_input(text)
        except Exception as exc:
            self.step -= 1
            self.status.showMessage(f"Error: {exc}")

            if line_number is not None:
                self.editor.set_error_lines({
                    line_number: str(exc),
                })

            self.history_list.addItem(
                f"ERROR  line {line_number or '?'}  {text}  →  {exc}"
            )
            return False

        # Critical: assignments/functions return None, but their
        # dictionaries are already changed. Refresh immediately.
        self._refresh_sidebars()

        if not result:
            self.status.showMessage(f"Updated: {text}")
            return True

        original, _, value, comment = result
        value_text = str(value)
        comment_text = comment or ""

        self.results.append(
            {
                "step": self.step,
                "expression": original,
                "result": value_text,
                "comment": comment_text,
            }
        )

        self.history_list.addItem(
            f"{self.step}.  {original}  =  {value_text}"
        )
        self.history_list.scrollToBottom()

        self._add_result_row(
            self.step, original, value_text, comment_text
        )

        try:
            numeric = float(value)
        except (TypeError, ValueError):
            numeric = None

        if numeric is not None and math.isfinite(numeric):
            self.graph.values.append(numeric)
            self.graph.set_values(self.graph.values)

        self.status.showMessage(f"Result: {value_text}")
        return True

    def _add_result_row(self, step, expression, result, comment):
        row = self.results_table.rowCount()
        self.results_table.insertRow(row)

        values = [str(step), expression, result, comment]

        for col, value in enumerate(values):
            self.results_table.setItem(
                row, col, QTableWidgetItem(value)
            )

        self.results_table.resizeColumnsToContents()
        self.results_table.scrollToBottom()

    def _refresh_sidebars(self):
        self.variables_list.clear()

        for key, value in self.core.get_vars().items():
            self.variables_list.addItem(f"{key}    =    {value}")

        self.functions_list.clear()

        functions = self.core.get_funcs()

        for key, value in functions.items():
            self.functions_list.addItem(f"{key}    =    {value}")

        names = list(self.core.get_vars().keys()) + list(functions.keys())
        self.completion_model.setStringList(sorted(set(names)))

    def export_history(self):
        path, _ = QFileDialog.getSaveFileName(
            self,
            "Export history",
            "abacus_history.json",
            "JSON files (*.json)",
        )
        if not path:
            return

        data = {
            "format": "abacus-history",
            "version": 1,
            "step": self.step,
            "document": self.editor.toPlainText(),
            "variables": self.core.get_vars(),
            "functions": self.core.get_funcs(),
            "history": [
                self.history_list.item(i).text()
                for i in range(self.history_list.count())
            ],
            "results": self.results,
            "graph_values": self.graph.values,
        }

        try:
            with open(path, "w", encoding="utf-8") as file:
                json.dump(
                    data,
                    file,
                    ensure_ascii=False,
                    indent=2,
                )
        except OSError as exc:
            self.status.showMessage(f"Export error: {exc}")
            return

        self.status.showMessage(f"History exported: {path}")

    def import_history(self):
        path, _ = QFileDialog.getOpenFileName(
            self,
            "Import history",
            "",
            "JSON files (*.json)",
        )
        if not path:
            return

        try:
            with open(path, "r", encoding="utf-8") as file:
                data = json.load(file)

            if data.get("format") != "abacus-history":
                raise ValueError("Not an Abacus history file")

            if data.get("version") != 1:
                raise ValueError(
                    f"Unsupported history version: {data.get('version')}"
                )

            history = data.get("history", [])
            results = data.get("results", [])
            graph_values = data.get("graph_values", [])
            variables = data.get("variables", {})
            functions = data.get("functions", {})
            document = data.get("document", "")
            step = int(data.get("step", len(results)))

            if not isinstance(history, list):
                raise ValueError("Invalid history data")
            if not isinstance(results, list):
                raise ValueError("Invalid results data")
            if not all(isinstance(item, dict) for item in results):
                raise ValueError("Invalid result item")
            if not isinstance(graph_values, list):
                raise ValueError("Invalid graph data")
            if not isinstance(variables, dict):
                raise ValueError("Invalid variables data")
            if not isinstance(functions, dict):
                raise ValueError("Invalid functions data")
            if not isinstance(document, str):
                raise ValueError("Invalid document data")

            imported_graph_values = []
            for value in graph_values:
                number = float(value)
                if not math.isfinite(number):
                    raise ValueError("Graph contains a non-finite value")
                imported_graph_values.append(number)

        except (OSError, ValueError, TypeError, json.JSONDecodeError) as exc:
            self.status.showMessage(f"Import error: {exc}")
            return

        self.step = step
        self.results = results

        self.core = AbacusCore()
        self.core.get_vars().clear()
        self.core.get_vars().update(variables)
        self.core.get_funcs().clear()
        self.core.get_funcs().update(functions)

        self.history_list.clear()
        self.history_list.addItems(
            str(item) for item in history
        )

        self.results_table.setRowCount(0)
        for item in self.results:
            self._add_result_row(
                item.get("step", ""),
                item.get("expression", ""),
                item.get("result", ""),
                item.get("comment", ""),
            )

        self.graph.set_values(imported_graph_values)

        self.editor.setPlainText(document)
        self.editor.clear_error_lines()
        self._refresh_sidebars()

        self.status.showMessage(f"History imported: {path}")

    def reset_abacus(self):
        self.core = AbacusCore()
        self.step = 0
        self.results.clear()

        self.variables_list.clear()
        self.functions_list.clear()
        self.history_list.clear()
        self.results_table.setRowCount(0)

        self.graph.set_values([])
        self.editor.clear_error_lines()
        self._refresh_sidebars()
        self.status.showMessage("Abacus state reset")


def main():
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    app.setStyleSheet(STYLE)

    window = AbacusIDE()
    window.show()

    sys.exit(app.exec())


if __name__ == "__main__":
    main()
