"""Focused tests for persistent GUI settings."""

from __future__ import annotations

import json
from pathlib import Path

import pytest
from platformdirs import user_data_dir

from openpkpd_gui.app.runtime import load_qt_modules, qt_widgets_available
from openpkpd_gui.app.settings import (
    BUTTON_GROUP_SELECTIONS_KEY,
    COLLAPSIBLE_SECTION_STATES_KEY,
    COMBO_BOX_SELECTIONS_KEY,
    DEFAULT_FONT_SIZE_PROPERTY,
    DEFAULT_WORKSPACE_ROOT_KEY,
    FONT_SIZE_KEY,
    LIST_WIDGET_SELECTIONS_KEY,
    MAX_FONT_SIZE,
    NAV_ACTIVE_PAGE_KEY,
    NAV_EXPANDED_ITEM_KEYS_KEY,
    NAV_SELECTED_ITEM_KEY,
    TAB_SELECTIONS_KEY,
    TABLE_COLUMN_WIDTHS_KEY,
    WINDOW_HEIGHT_KEY,
    WINDOW_MAXIMIZED_KEY,
    WINDOW_SPLITTER_SIZES_KEY,
    WINDOW_WIDTH_KEY,
    WINDOW_X_KEY,
    WINDOW_Y_KEY,
    GuiPreferences,
    default_workspace_root_path,
    load_gui_preferences,
)
from openpkpd_gui.domain.artifact import ArtifactRecord
from openpkpd_gui.domain.run_record import RunRecord, RunStatus
from openpkpd_gui.domain.workspace import Workspace
from openpkpd_gui.shell.main_window import (
    _compute_default_splitter_sizes,
    _compute_default_window_bounds,
    create_main_window,
)


class FakeSettingsStore:
    def __init__(self, initial: dict[str, object] | None = None) -> None:
        self._values = dict(initial or {})

    def value(self, key: str, default: object = None) -> object:
        return self._values.get(key, default)

    def setValue(self, key: str, value: object) -> None:
        self._values[key] = value

    def remove(self, key: str) -> None:
        self._values.pop(key, None)

    def sync(self) -> None:
        return None


def _workspace_with_results_outputs(tmp_path: Path) -> Workspace:
    workspace = Workspace(name="Settings results", root_path=str(tmp_path))
    first_run = RunRecord(workflow="fit", run_id="run-1", status=RunStatus.SUCCEEDED)
    second_run = RunRecord(workflow="bootstrap", run_id="run-2", status=RunStatus.SUCCEEDED)
    workspace.add_run(first_run)
    workspace.add_run(second_run)
    workspace.add_artifact(
        ArtifactRecord(
            kind="report",
            label="Fit report",
            artifact_id="artifact-1",
            source_run_id=first_run.run_id,
        )
    )
    workspace.add_artifact(
        ArtifactRecord(
            kind="table",
            label="Bootstrap summary",
            artifact_id="artifact-2",
            source_run_id=second_run.run_id,
        )
    )
    return workspace


def _nav_key(**payload: str) -> str:
    return json.dumps(payload, sort_keys=True, separators=(",", ":"))


def _find_child_item(parent, label: str):
    for index in range(parent.childCount()):
        child = parent.child(index)
        if child.text(0) == label:
            return child
    return None


def test_default_workspace_root_path_uses_platform_user_data_dir() -> None:
    assert default_workspace_root_path() == Path(user_data_dir("OpenPKPD")).resolve()


@pytest.mark.unit
def test_saved_font_size_is_applied_when_creating_main_window() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, qt_gui, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    baseline_size = max(app.font().pointSize(), 10)
    app.setProperty(DEFAULT_FONT_SIZE_PROPERTY, baseline_size)
    font = app.font()
    font.setPointSize(baseline_size)
    app.setFont(font)

    saved_size = min(baseline_size + 3, MAX_FONT_SIZE)
    workspace = Workspace(name="Settings load")
    window = create_main_window(
        workspace,
        settings_store=FakeSettingsStore({FONT_SIZE_KEY: saved_size}),
    )

    assert app.font().pointSize() == saved_size
    assert workspace.root_path is None
    assert window.findChild(qt_widgets.QDialog, "preferences-dialog") is None

    font = app.font()
    font.setPointSize(baseline_size)
    app.setFont(font)


@pytest.mark.unit
def test_saved_default_workspace_root_is_applied_to_rootless_workspace(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, qt_gui, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    default_root = tmp_path / "workspace-files"
    default_root.mkdir()
    workspace = Workspace(name="Settings root")

    window = create_main_window(
        workspace,
        settings_store=FakeSettingsStore({DEFAULT_WORKSPACE_ROOT_KEY: str(default_root)}),
    )
    sidebar_path = window.findChild(qt_widgets.QLabel, "sidebar-project-path")

    assert workspace.root_path == str(default_root)
    assert sidebar_path is not None
    assert f"Workspace: {default_root}" in sidebar_path.text()
    assert app is not None


@pytest.mark.unit
def test_saved_default_workspace_root_does_not_override_existing_workspace_root(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    default_root = tmp_path / "workspace-files"
    explicit_root = tmp_path / "explicit-root"
    default_root.mkdir()
    explicit_root.mkdir()
    workspace = Workspace(name="Settings explicit", root_path=str(explicit_root))

    create_main_window(
        workspace,
        settings_store=FakeSettingsStore({DEFAULT_WORKSPACE_ROOT_KEY: str(default_root)}),
    )

    assert workspace.root_path == str(explicit_root)


def test_default_window_bounds_scale_to_available_screen() -> None:
    x, y, width, height = _compute_default_window_bounds(0, 0, 2560, 1440)

    assert (x, y) == (320, 120)
    assert (width, height) == (1920, 1200)


def test_default_window_bounds_shrink_to_small_screens() -> None:
    x, y, width, height = _compute_default_window_bounds(10, 20, 900, 700)

    assert (x, y) == (22, 32)
    assert (width, height) == (876, 676)


def test_default_splitter_sizes_scale_to_window_width() -> None:
    sidebar, content = _compute_default_splitter_sizes(1600)

    assert sidebar == 384
    assert content == 1216


def test_default_splitter_sizes_protect_small_windows() -> None:
    sidebar, content = _compute_default_splitter_sizes(820)

    assert sidebar == 280
    assert content == 620


@pytest.mark.unit
def test_saved_window_geometry_is_restored_on_startup() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, qt_gui, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore(
        {
            WINDOW_X_KEY: 140,
            WINDOW_Y_KEY: 90,
            WINDOW_WIDTH_KEY: 1330,
            WINDOW_HEIGHT_KEY: 810,
        }
    )
    window = create_main_window(Workspace(name="Restore geometry"), settings_store=store)
    window.show()
    app.processEvents()

    geometry = window.geometry()
    screen = window.screen() or qt_gui.QGuiApplication.primaryScreen()

    assert screen is not None
    available = screen.availableGeometry()

    assert geometry.width() == min(1330, available.width())
    assert geometry.height() == min(810, available.height())
    if available.width() >= 1330 and available.height() >= 810:
        assert geometry.x() == 140
        assert geometry.y() == 90
    else:
        assert geometry.x() >= available.x()
        assert geometry.y() >= available.y()
    window.close()


@pytest.mark.unit
def test_saved_shell_splitter_sizes_are_restored_on_startup() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore(
        {
            WINDOW_WIDTH_KEY: 1400,
            WINDOW_HEIGHT_KEY: 900,
            WINDOW_SPLITTER_SIZES_KEY: "330,1070",
        }
    )
    window = create_main_window(Workspace(name="Restore splitter"), settings_store=store)
    window.show()
    app.processEvents()

    splitter = window.findChild(qt_widgets.QSplitter, "shell-main-splitter")

    assert splitter is not None
    sizes = splitter.sizes()
    assert len(sizes) == 2
    assert sum(sizes) > 0
    assert sizes[0] == pytest.approx(330, abs=8)
    assert sizes[1] > sizes[0]
    window.close()


@pytest.mark.unit
def test_saved_table_column_widths_are_restored_on_startup() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore(
        {
            TABLE_COLUMN_WIDTHS_KEY: json.dumps(
                {"model-theta-table": [240, 120, 120, 120, 90]},
                sort_keys=True,
            )
        }
    )
    window = create_main_window(Workspace(name="Restore table widths"), settings_store=store)
    window.show()
    app.processEvents()

    theta_table = window.findChild(qt_widgets.QTableWidget, "model-theta-table")

    assert theta_table is not None
    assert theta_table.columnWidth(0) == pytest.approx(240, abs=8)
    assert theta_table.columnWidth(1) == pytest.approx(120, abs=8)
    window.close()


@pytest.mark.unit
def test_saved_tab_selection_is_restored_on_startup() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore(
        {TAB_SELECTIONS_KEY: json.dumps({"advanced-tab-widget": 2}, sort_keys=True)}
    )
    window = create_main_window(Workspace(name="Restore tabs"), settings_store=store)
    window.show()
    app.processEvents()

    tab_widget = window.findChild(qt_widgets.QTabWidget, "advanced-tab-widget")

    assert tab_widget is not None
    assert tab_widget.currentIndex() == 2
    window.close()


@pytest.mark.unit
def test_saved_collapsible_section_states_are_restored_on_startup() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore(
        {
            COLLAPSIBLE_SECTION_STATES_KEY: json.dumps(
                {
                    "advanced-vpc-log-section": True,
                    "advanced-artifact-preview-section": False,
                },
                sort_keys=True,
            )
        }
    )
    window = create_main_window(Workspace(name="Restore sections"), settings_store=store)
    window.show()
    app.processEvents()

    vpc_log_section = window.findChild(qt_widgets.QWidget, "advanced-vpc-log-section")
    artifact_preview_section = window.findChild(
        qt_widgets.QWidget, "advanced-artifact-preview-section"
    )

    assert vpc_log_section is not None
    assert artifact_preview_section is not None
    assert bool(vpc_log_section.property("expanded")) is True
    assert bool(artifact_preview_section.property("expanded")) is False
    window.close()


@pytest.mark.unit
def test_saved_combo_box_selection_is_restored_on_startup() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore(
        {
            COMBO_BOX_SELECTIONS_KEY: json.dumps(
                {"advanced-artifact-scope-combo": "Bootstrap only"},
                sort_keys=True,
            )
        }
    )
    window = create_main_window(Workspace(name="Restore combo selection"), settings_store=store)
    window.show()
    app.processEvents()

    combo_box = window.findChild(qt_widgets.QComboBox, "advanced-artifact-scope-combo")

    assert combo_box is not None
    assert combo_box.currentText() == "Bootstrap only"
    window.close()


@pytest.mark.unit
def test_saved_results_kind_filter_is_restored_on_startup(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore(
        {
            BUTTON_GROUP_SELECTIONS_KEY: json.dumps(
                {"results-kind-filter": "results-kind-filter-table"},
                sort_keys=True,
            )
        }
    )
    window = create_main_window(_workspace_with_results_outputs(tmp_path), settings_store=store)
    window.show()
    app.processEvents()

    table_button = window.findChild(qt_widgets.QPushButton, "results-kind-filter-table")
    all_button = window.findChild(qt_widgets.QPushButton, "results-kind-filter-all")

    assert table_button is not None
    assert all_button is not None
    assert table_button.isChecked() is True
    assert all_button.isChecked() is False
    window.close()


@pytest.mark.unit
def test_saved_results_list_selections_are_restored_on_startup(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    qt_core, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore(
        {
            LIST_WIDGET_SELECTIONS_KEY: json.dumps(
                {
                    "results-runs-list": "run-2",
                    "results-artifacts-list": "artifact-2",
                },
                sort_keys=True,
            )
        }
    )
    window = create_main_window(_workspace_with_results_outputs(tmp_path), settings_store=store)
    window.show()
    app.processEvents()

    runs_list = window.findChild(qt_widgets.QListWidget, "results-runs-list")
    artifacts_list = window.findChild(qt_widgets.QListWidget, "results-artifacts-list")

    assert runs_list is not None
    assert artifacts_list is not None
    assert runs_list.currentItem() is not None
    assert artifacts_list.currentItem() is not None
    assert runs_list.currentItem().data(qt_core.Qt.ItemDataRole.UserRole) == "run-2"
    assert artifacts_list.currentItem().data(qt_core.Qt.ItemDataRole.UserRole) == "artifact-2"
    window.close()


@pytest.mark.unit
def test_saved_navigation_state_is_restored_on_startup() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    qt_core, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    workspace = Workspace(name="Restore navigation")
    active_project = workspace.active_project
    active_scenario = workspace.active_scenario
    store = FakeSettingsStore(
        {
            NAV_SELECTED_ITEM_KEY: _nav_key(
                kind="workflow",
                workflow_id="model",
                project_id=active_project.project_id,
                scenario_id=active_scenario.scenario_id,
            ),
            NAV_ACTIVE_PAGE_KEY: "model-workflow",
            NAV_EXPANDED_ITEM_KEYS_KEY: json.dumps(
                [
                    _nav_key(kind="workspace"),
                    _nav_key(kind="project", project_id=active_project.project_id),
                ]
            ),
        }
    )
    window = create_main_window(workspace, settings_store=store)
    window.show()
    app.processEvents()

    nav = window.findChild(qt_widgets.QTreeWidget, "workflow-nav")

    assert nav is not None
    current_item = nav.currentItem()
    assert current_item is not None
    assert current_item.data(0, qt_core.Qt.ItemDataRole.UserRole + 1) == "model"

    workspace_item = nav.topLevelItem(0)
    assert workspace_item is not None and workspace_item.isExpanded()
    project_item = _find_child_item(workspace_item, active_project.name)
    assert project_item is not None and project_item.isExpanded()
    scenario_item = _find_child_item(project_item, active_scenario.name)
    assert scenario_item is not None and scenario_item.isExpanded()
    window.close()


@pytest.mark.unit
def test_saved_scenario_details_page_is_restored_on_startup() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    workspace = Workspace(name="Restore scenario details")
    active_project = workspace.active_project
    active_scenario = workspace.active_scenario
    store = FakeSettingsStore(
        {
            NAV_SELECTED_ITEM_KEY: _nav_key(
                kind="workflow",
                workflow_id="model",
                project_id=active_project.project_id,
                scenario_id=active_scenario.scenario_id,
            ),
            NAV_ACTIVE_PAGE_KEY: "scenario-details-page",
        }
    )

    window = create_main_window(workspace, settings_store=store)
    window.show()
    app.processEvents()

    stack = window.findChild(qt_widgets.QStackedWidget, "workflow-stack")

    assert stack is not None
    assert stack.currentWidget() is not None
    assert stack.currentWidget().objectName() == "scenario-details-page"
    window.close()


@pytest.mark.unit
def test_closing_main_window_persists_current_geometry() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    window = create_main_window(Workspace(name="Persist geometry"), settings_store=store)
    window.show()
    app.processEvents()
    window.setGeometry(160, 110, 1210, 790)
    app.processEvents()

    window.close()
    app.processEvents()

    assert store.value(WINDOW_X_KEY) == 160
    assert store.value(WINDOW_Y_KEY) == 110
    assert store.value(WINDOW_WIDTH_KEY) == 1210
    assert store.value(WINDOW_HEIGHT_KEY) == 790
    assert store.value(WINDOW_MAXIMIZED_KEY) is False


@pytest.mark.unit
def test_closing_main_window_persists_splitter_sizes() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    window = create_main_window(Workspace(name="Persist splitter"), settings_store=store)
    window.show()
    app.processEvents()

    splitter = window.findChild(qt_widgets.QSplitter, "shell-main-splitter")
    assert splitter is not None
    splitter.setSizes([340, 960])
    app.processEvents()
    expected_sizes = tuple(splitter.sizes())

    window.close()
    app.processEvents()

    stored_sizes = tuple(
        int(part) for part in str(store.value(WINDOW_SPLITTER_SIZES_KEY, "")).split(",") if part
    )
    assert stored_sizes == expected_sizes


@pytest.mark.unit
def test_closing_main_window_persists_table_column_widths() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    window = create_main_window(Workspace(name="Persist table widths"), settings_store=store)
    window.show()
    app.processEvents()

    theta_table = window.findChild(qt_widgets.QTableWidget, "model-theta-table")
    assert theta_table is not None
    theta_table.setColumnWidth(0, 236)
    theta_table.setColumnWidth(1, 144)
    app.processEvents()

    window.close()
    app.processEvents()

    loaded = load_gui_preferences(settings_store=store)
    assert loaded.table_column_widths["model-theta-table"][0] == pytest.approx(236, abs=8)
    assert loaded.table_column_widths["model-theta-table"][1] == pytest.approx(144, abs=8)


@pytest.mark.unit
def test_closing_main_window_persists_tab_selection() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    window = create_main_window(Workspace(name="Persist tabs"), settings_store=store)
    window.show()
    app.processEvents()

    tab_widget = window.findChild(qt_widgets.QTabWidget, "advanced-tab-widget")
    assert tab_widget is not None
    tab_widget.setCurrentIndex(3)
    app.processEvents()

    window.close()
    app.processEvents()

    loaded = load_gui_preferences(settings_store=store)
    assert loaded.tab_selections == {"advanced-tab-widget": 3}


@pytest.mark.unit
def test_closing_main_window_persists_collapsible_section_states() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    window = create_main_window(Workspace(name="Persist sections"), settings_store=store)
    window.show()
    app.processEvents()

    vpc_log_section = window.findChild(qt_widgets.QWidget, "advanced-vpc-log-section")
    artifact_preview_section = window.findChild(
        qt_widgets.QWidget, "advanced-artifact-preview-section"
    )
    assert vpc_log_section is not None
    assert artifact_preview_section is not None

    vpc_log_section._set_expanded(True)  # type: ignore[attr-defined]
    artifact_preview_section._set_expanded(False)  # type: ignore[attr-defined]
    app.processEvents()

    window.close()
    app.processEvents()

    loaded = load_gui_preferences(settings_store=store)
    assert loaded.collapsible_section_states["advanced-vpc-log-section"] is True
    assert loaded.collapsible_section_states["advanced-artifact-preview-section"] is False


@pytest.mark.unit
def test_closing_main_window_persists_combo_box_selection() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    window = create_main_window(Workspace(name="Persist combo selection"), settings_store=store)
    window.show()
    app.processEvents()

    combo_box = window.findChild(qt_widgets.QComboBox, "advanced-artifact-scope-combo")
    assert combo_box is not None
    combo_box.setCurrentText("Design only")
    app.processEvents()

    window.close()
    app.processEvents()

    loaded = load_gui_preferences(settings_store=store)
    assert loaded.combo_box_selections["advanced-artifact-scope-combo"] == "Design only"


@pytest.mark.unit
def test_closing_main_window_persists_results_kind_filter(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    window = create_main_window(_workspace_with_results_outputs(tmp_path), settings_store=store)
    window.show()
    app.processEvents()

    plot_button = window.findChild(qt_widgets.QPushButton, "results-kind-filter-plot")
    assert plot_button is not None
    plot_button.setChecked(True)
    app.processEvents()

    window.close()
    app.processEvents()

    loaded = load_gui_preferences(settings_store=store)
    assert loaded.button_group_selections == {"results-kind-filter": "results-kind-filter-plot"}


@pytest.mark.unit
def test_closing_main_window_persists_results_list_selections(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    qt_core, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    window = create_main_window(_workspace_with_results_outputs(tmp_path), settings_store=store)
    window.show()
    app.processEvents()

    runs_list = window.findChild(qt_widgets.QListWidget, "results-runs-list")
    artifacts_list = window.findChild(qt_widgets.QListWidget, "results-artifacts-list")
    assert runs_list is not None
    assert artifacts_list is not None

    runs_list.setCurrentRow(0)
    app.processEvents()
    assert runs_list.currentItem() is not None
    assert runs_list.currentItem().data(qt_core.Qt.ItemDataRole.UserRole) == "run-2"
    assert artifacts_list.currentItem() is not None
    assert artifacts_list.currentItem().data(qt_core.Qt.ItemDataRole.UserRole) == "artifact-2"

    window.close()
    app.processEvents()

    loaded = load_gui_preferences(settings_store=store)
    assert loaded.list_widget_selections["results-runs-list"] == "run-2"
    assert loaded.list_widget_selections["results-artifacts-list"] == "artifact-2"


@pytest.mark.unit
def test_closing_main_window_persists_navigation_state() -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, _, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    store = FakeSettingsStore()
    workspace = Workspace(name="Persist navigation")
    active_project = workspace.active_project
    active_scenario = workspace.active_scenario
    window = create_main_window(workspace, settings_store=store)
    window.show()
    app.processEvents()

    nav = window.findChild(qt_widgets.QTreeWidget, "workflow-nav")
    assert nav is not None
    workspace_item = nav.topLevelItem(0)
    assert workspace_item is not None
    project_item = _find_child_item(workspace_item, active_project.name)
    assert project_item is not None
    scenario_item = _find_child_item(project_item, active_scenario.name)
    assert scenario_item is not None
    scenario_item.setExpanded(False)
    app.processEvents()

    window._select_navigation_item(
        "model", project_id=active_project.project_id, scenario_id=active_scenario.scenario_id
    )  # type: ignore[attr-defined]
    window._edit_scenario_details()  # type: ignore[attr-defined]
    app.processEvents()

    window.close()
    app.processEvents()

    loaded = load_gui_preferences(settings_store=store)
    assert loaded.nav_selected_item_key == _nav_key(
        kind="workflow",
        workflow_id="model",
        project_id=active_project.project_id,
        scenario_id=active_scenario.scenario_id,
    )
    assert loaded.nav_active_page == "scenario-details-page"
    assert _nav_key(kind="workspace") in loaded.nav_expanded_item_keys
    assert (
        _nav_key(kind="project", project_id=active_project.project_id)
        in loaded.nav_expanded_item_keys
    )
    assert (
        _nav_key(
            kind="scenario",
            project_id=active_project.project_id,
            scenario_id=active_scenario.scenario_id,
        )
        in loaded.nav_expanded_item_keys
    )


@pytest.mark.unit
def test_save_as_updates_and_reuses_last_dialog_directory(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, qt_gui, qt_widgets = load_qt_modules()
    qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    save_dir = tmp_path / "save-target"
    save_dir.mkdir()
    destination = save_dir / "saved-project.opkpd"
    store = FakeSettingsStore()
    workspace = Workspace(name="Remember dialog dir")
    window = create_main_window(workspace, settings_store=store)
    save_as_action = window.findChild(qt_gui.QAction, "file-save-project-as-action")
    open_action = window.findChild(qt_gui.QAction, "file-open-project-action")

    assert save_as_action is not None
    assert open_action is not None

    original_get_save_file_name = qt_widgets.QFileDialog.getSaveFileName
    original_get_open_file_name = qt_widgets.QFileDialog.getOpenFileName
    captured_open_default_dir: dict[str, str] = {}

    def _save_file_name(_parent, _title, _default_path, _filters):
        return str(destination), ""

    def _open_file_name(_parent, _title, default_dir, _filters):
        captured_open_default_dir["path"] = str(default_dir)
        return "", ""

    qt_widgets.QFileDialog.getSaveFileName = staticmethod(_save_file_name)
    qt_widgets.QFileDialog.getOpenFileName = staticmethod(_open_file_name)
    try:
        save_as_action.trigger()
        open_action.trigger()
    finally:
        qt_widgets.QFileDialog.getSaveFileName = original_get_save_file_name
        qt_widgets.QFileDialog.getOpenFileName = original_get_open_file_name

    loaded = load_gui_preferences(settings_store=store)
    assert loaded.last_file_dialog_dir == str(save_dir.resolve())
    assert captured_open_default_dir["path"] == str(save_dir.resolve())
    window.close()


@pytest.mark.unit
def test_preferences_menu_action_updates_font_size_and_default_workspace_root(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, qt_gui, qt_widgets = load_qt_modules()
    app = qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    baseline_size = max(app.font().pointSize(), 10)
    app.setProperty(DEFAULT_FONT_SIZE_PROPERTY, baseline_size)
    font = app.font()
    font.setPointSize(baseline_size)
    app.setFont(font)

    store = FakeSettingsStore()
    workspace_root = tmp_path / "settings-workspace-root"
    workspace_root.mkdir()
    workspace = Workspace(name="Settings save")
    window = create_main_window(workspace, settings_store=store)
    action = window.findChild(qt_gui.QAction, "settings-preferences-action")
    new_size = min(baseline_size + 2, MAX_FONT_SIZE)

    assert action is not None

    window._edit_preferences_dialog = lambda _current: GuiPreferences(  # type: ignore[attr-defined]
        font_size=new_size,
        default_workspace_root=str(workspace_root),
    )
    action.trigger()
    app.processEvents()

    assert app.font().pointSize() == new_size
    assert store.value(FONT_SIZE_KEY) == new_size
    assert store.value(DEFAULT_WORKSPACE_ROOT_KEY) == str(workspace_root)
    assert workspace.root_path == str(workspace_root)

    font = app.font()
    font.setPointSize(baseline_size)
    app.setFont(font)


@pytest.mark.unit
def test_save_as_uses_updated_default_workspace_root_after_preferences_change(tmp_path) -> None:
    if not qt_widgets_available():
        pytest.skip("Qt GUI modules are unavailable in this environment")

    _, qt_gui, qt_widgets = load_qt_modules()
    qt_widgets.QApplication.instance() or qt_widgets.QApplication(
        ["test", "-platform", "offscreen"]
    )
    initial_root = tmp_path / "initial-root"
    updated_root = tmp_path / "updated-root"
    initial_root.mkdir()
    updated_root.mkdir()
    store = FakeSettingsStore({DEFAULT_WORKSPACE_ROOT_KEY: str(initial_root)})
    workspace = Workspace(name="Save default path")
    window = create_main_window(workspace, settings_store=store)
    preferences_action = window.findChild(qt_gui.QAction, "settings-preferences-action")
    save_as_action = window.findChild(qt_gui.QAction, "file-save-project-as-action")

    assert preferences_action is not None
    assert save_as_action is not None
    assert workspace.root_path == str(initial_root)

    window._edit_preferences_dialog = lambda _current: GuiPreferences(  # type: ignore[attr-defined]
        default_workspace_root=str(updated_root),
    )
    preferences_action.trigger()

    captured_default_path: dict[str, str] = {}
    original_get_save_file_name = qt_widgets.QFileDialog.getSaveFileName

    def _capture_get_save_file_name(_parent, _title, default_path, _filters):
        captured_default_path["path"] = str(default_path)
        return "", ""

    qt_widgets.QFileDialog.getSaveFileName = staticmethod(_capture_get_save_file_name)
    try:
        save_as_action.trigger()
    finally:
        qt_widgets.QFileDialog.getSaveFileName = original_get_save_file_name

    assert workspace.root_path == str(updated_root)
    assert Path(captured_default_path["path"]).parent == updated_root.resolve()
