"""Profile load/save workflows for the main window config facade."""

from __future__ import annotations

from collections import deque
from contextlib import ExitStack
import json
from pathlib import Path

from PyQt6.QtCore import QSignalBlocker
from PyQt6.QtWidgets import QFileDialog

from python_app.models.gui_profile_model import GuiProfileModel
from python_app.models.run_config_model import RunConfigModel
from python_app.orchestration.preprocess_assets import (
    VISIBLE_PREPROCESS_ASSET_KEYS,
    preprocess_asset_model,
)


class AppWindowConfigProfileIOMixin:
    """Persist and restore full GUI profiles without restarting the app."""

    def _write_gui_profile_to_path(self, output_path: Path, *, allow_overwrite: bool = True) -> GuiProfileModel:
        """Serialize current full GUI profile to `output_path` and return the persisted model."""
        if not allow_overwrite and output_path.exists():
            raise FileExistsError(f"Config profile output already exists: {output_path}")

        profile = self._build_gui_profile()
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(json.dumps(profile.to_dict(), indent=2), encoding="utf-8")
        return profile

    def _set_combo_selection_mode(self, mode: str) -> None:
        """Highlight current combo mode and enable only the relevant editors."""
        if self._is_matrix_radar_model_selected():
            self._run_combos_select_button.setChecked(True)
            self._single_combo_select_button.setChecked(False)
            self._combos_text.setText(self._fixed_matrix_radar_combo_text())
            self._combos_text.setEnabled(False)
            self._single_combo_output.setEnabled(True)
            self._single_combo_input.setEnabled(True)
            self._run_combos_select_button.setEnabled(False)
            self._single_combo_select_button.setEnabled(False)
            return

        text_selected = mode != "single"
        self._run_combos_select_button.setChecked(text_selected)
        self._single_combo_select_button.setChecked(not text_selected)
        self._combos_text.setEnabled(text_selected)
        self._single_combo_output.setEnabled(not text_selected)
        self._single_combo_input.setEnabled(not text_selected)
        self._run_combos_select_button.setEnabled(True)
        self._single_combo_select_button.setEnabled(True)

    def _is_matrix_radar_model_selected(self) -> bool:
        """Return whether the loaded config targets a matrix-mode radar acquisition."""
        return bool(self._defaults_config.is_matrix_radar)

    def _fixed_matrix_radar_combo_text(self) -> str:
        """Return the canonical virtual combo matrix shown for matrix-mode radars."""
        return ",".join(
            f"{int(combo.input)}:{int(combo.output)}"
            for combo in RunConfigModel.build_matrix_radar_virtual_combos()
        )

    def _sync_pass_through_y_controls(self) -> None:
        """Enable Y-range editors only when fixed Y mode is active."""
        enabled = bool(self._pass_through_fixed_y_enabled.isChecked())
        self._pass_through_y_min_db.setEnabled(enabled)
        self._pass_through_y_max_db.setEnabled(enabled)

    def _set_radar_settings_mode(self, adc_mode: bool) -> None:
        """Show the radar settings panel appropriate for the loaded radar model."""
        self._vna_radar_settings_panel.setVisible(not adc_mode)
        self._adc_radar_settings_panel.setVisible(adc_mode)

    def _sync_adc_settings_controls(self) -> None:
        """Enable optical-board controls according to enabled flag and mode."""
        if not hasattr(self, "_optical_enabled_checkbox"):
            return
        enabled = bool(self._optical_enabled_checkbox.isChecked())
        mode = self._optical_mode_combo.currentText()
        common_widgets = [
            self._optical_port_input,
            self._optical_mode_combo,
            self._optical_pi_coeff1_p_input,
            self._optical_pi_coeff1_i_input,
            self._optical_pi_coeff2_p_input,
            self._optical_pi_coeff2_i_input,
        ]
        manual_widgets = [
            self._optical_manual_temp1_input,
            self._optical_manual_temp2_input,
            self._optical_manual_current1_input,
            self._optical_manual_current2_input,
        ]
        variation_widgets = [
            self._optical_variation_type_combo,
            self._optical_static_temp1_input,
            self._optical_static_temp2_input,
            self._optical_static_current1_input,
            self._optical_static_current2_input,
            self._optical_min_value_input,
            self._optical_max_value_input,
            self._optical_step_input,
            self._optical_time_step_input,
            self._optical_delay_time_input,
        ]
        for widget in common_widgets:
            widget.setEnabled(enabled)
        for widget in manual_widgets:
            widget.setEnabled(enabled and mode == "manual")
        for widget in variation_widgets:
            widget.setEnabled(enabled and mode == "variation")
        self._optical_manual_panel.setVisible(mode == "manual")
        self._optical_variation_panel.setVisible(mode == "variation")

    def _apply_history_limit_from_config(self, config) -> None:
        """Resize in-memory history buffers to match the loaded config."""
        history_limit = self._history_limit_for_config(config)
        self._raw_history = deque(self._raw_history, maxlen=history_limit)
        self._pre_history = deque(self._pre_history, maxlen=history_limit)
        self._result_history = deque(self._result_history, maxlen=history_limit)
        self._bscan_history_limit = history_limit
        self._clear_bscan_plot_history()

    def _save_current_config(self) -> None:
        """Persist current full GUI profile to a user-selected JSON file."""
        try:
            suggested_path = str(self._active_profile_path)
            selected_path, _selected_filter = QFileDialog.getSaveFileName(
                self,
                "Save Config Profile",
                suggested_path,
                "JSON Files (*.json);;All Files (*)",
            )
            if not selected_path:
                return

            output_path = self._normalize_profile_path(Path(selected_path))
            if not output_path.suffix:
                output_path = output_path.with_suffix(".json")

            profile = self._write_gui_profile_to_path(output_path)

            self._defaults_config = profile.run_config.clone()
            self._gui_defaults = profile.gui
            self._remember_active_profile_path(output_path)
            points_text = (
                "points=from Kamil ADC stream"
                if profile.run_config.is_kamil_adc
                else f"points={profile.run_config.radar.sweep.points}"
            )
            self._log(
                f"Config profile saved: path={output_path}, "
                f"combos={len(profile.run_config.combos)}, "
                f"sweep={profile.run_config.radar.sweep.start_hz:g}.."
                f"{profile.run_config.radar.sweep.stop_hz:g} Hz, "
                f"{points_text}, "
                f"ifbw={profile.run_config.radar.sweep.if_bandwidth_hz:g} Hz, "
                f"power={profile.run_config.radar.sweep.power_dbm:g} dBm, "
                f"processing_mode={profile.gui.processing.selected_mode}"
            )
        except Exception as exc:  # noqa: BLE001
            self._show_exception("Failed to save config profile", exc)

    def _load_config_from_dialog(self) -> None:
        """Load full GUI profile from a user-selected JSON file."""
        if self._capture_session is not None:
            self._show_error(
                "Cannot load config during active capture sequence",
                details=self._capture_state_details(),
            )
            return
        if self._supervisor.is_running():
            self._show_error(
                "Stop all pipeline processes before loading a config profile",
                details=self._process_state_details(),
            )
            return

        selected_path, _selected_filter = QFileDialog.getOpenFileName(
            self,
            "Load Config Profile",
            str(self._active_profile_path),
            "JSON Files (*.json);;All Files (*)",
        )
        if not selected_path:
            return

        try:
            normalized_path = self._normalize_profile_path(Path(selected_path))
            profile = GuiProfileModel.load_from_path(normalized_path)
            processor_running = self._supervisor.is_processor_running()
            self._apply_loaded_profile(profile, normalized_path)
            profile_kind = "legacy run config" if profile.gui is None else "full GUI profile"
            message = (
                f"Config profile loaded: path={normalized_path}, "
                f"kind={profile_kind}, "
                f"combos={len(self._defaults_config.combos)}, "
                f"processing_mode={self._processing_mode.currentText()}"
            )
            if processor_running:
                message += (
                    "; data_processor is still running, so live processing settings were applied immediately "
                    "and stable settings are now staged in the UI for the next Start"
                )
            self._log(message)
        except Exception as exc:  # noqa: BLE001
            self._show_exception("Failed to load config profile", exc)

    def _apply_loaded_profile(self, profile: GuiProfileModel, profile_path: Path) -> None:
        """Apply already parsed profile to GUI state without restarting the pipeline."""
        config = profile.run_config.clone()
        gui_state = profile.gui if profile.gui is not None else self._default_gui_state_for_config(config)
        self._defaults_config = config
        selected_preprocess_sets = {
            key: str(preprocess_asset_model(config, key).set_name)
            for key in VISIBLE_PREPROCESS_ASSET_KEYS
        }

        radio_widgets = (
            self._start_hz_input,
            self._stop_hz_input,
            self._points_input,
            self._ifbw_input,
            self._power_input,
            self._settling_ms,
            self._combos_text,
            self._single_combo_output,
            self._single_combo_input,
            self._run_combos_select_button,
            self._single_combo_select_button,
            self._processing_mode,
            self._show_magnitude_checkbox,
            self._show_phase_checkbox,
            self._pass_through_combo_filter_input,
            self._pass_through_fixed_y_enabled,
            self._pass_through_y_min_db,
            self._pass_through_y_max_db,
            self._bscan_axis,
            self._bscan_cut_m,
            self._bscan_max_depth_m,
            self._bscan_gain,
            self._bscan_start_freq_mhz,
            self._bscan_stop_freq_mhz,
            self._bscan_subtract_mean_ascan,
            self._gpr_relative_permittivity,
            self._gpr_tx_geometry_input,
            self._gpr_rx_geometry_input,
            self._gpr_input_positions_input,
            self._gpr_output_positions_input,
            self._gpr_min_depth_m,
            self._gpr_max_depth_m,
            self._gpr_range_comp_power,
            self._gpr_angle_comp_power,
            self._gpr_score_mode,
            self._gpr_max_detected_objects_to_draw,
            self._gpr_draw_top_m_objects,
            self._gpr_start_freq_mhz,
            self._gpr_stop_freq_mhz,
            self._gpr_background_subtract_enabled,
            self._gpr_background_mean_count,
            self._gpr_remove_sidelobe_objects_enabled,
            self._gpr_imaging_plane_y_m,
            self._gpr_render_mode,
            self._gpr_min_visible_score,
            self._gpr_visible_x_min_m,
            self._gpr_visible_x_max_m,
            self._gpr_visible_z_min_m,
            self._gpr_visible_z_max_m,
            self._legacy_gpr_config_mode,
            self._legacy_gpr_input_positions_input,
            self._legacy_gpr_output_positions_input,
            self._legacy_gpr_min_depth_m,
            self._legacy_gpr_max_depth_m,
            self._legacy_gpr_comp_power,
            self._legacy_gpr_snr_thresh,
            self._legacy_gpr_snr_comp_max,
            self._legacy_gpr_start_freq_mhz,
            self._legacy_gpr_stop_freq_mhz,
            self._legacy_gpr_speed_m_s,
            self._legacy_gpr_ignore_socket_speed_enabled,
            self._legacy_gpr_look_angle_deg,
            self._legacy_gpr_background_subtract_enabled,
            self._legacy_gpr_background_mean_count,
            self._legacy_gpr_render_mode,
            self._legacy_gpr_min_visible_pair_count,
            self._legacy_gpr_visible_x_min_m,
            self._legacy_gpr_visible_x_max_m,
            self._legacy_gpr_visible_z_min_m,
            self._legacy_gpr_visible_z_max_m,
            self._save_count,
            self._save_path_input,
            self._save_name_input,
            self._adc_project_dir_input,
            self._adc_executable_path_input,
            self._adc_tty_path_input,
            self._adc_args_input,
            self._adc_env_input,
            self._adc_startup_timeout_s_input,
            self._adc_sweep_timeout_s_input,
            self._adc_stop_timeout_s_input,
            self._optical_enabled_checkbox,
            self._optical_port_input,
            self._optical_mode_combo,
            self._optical_pi_coeff1_p_input,
            self._optical_pi_coeff1_i_input,
            self._optical_pi_coeff2_p_input,
            self._optical_pi_coeff2_i_input,
            self._optical_manual_temp1_input,
            self._optical_manual_temp2_input,
            self._optical_manual_current1_input,
            self._optical_manual_current2_input,
            self._optical_variation_type_combo,
            self._optical_static_temp1_input,
            self._optical_static_temp2_input,
            self._optical_static_current1_input,
            self._optical_static_current2_input,
            self._optical_min_value_input,
            self._optical_max_value_input,
            self._optical_step_input,
            self._optical_time_step_input,
            self._optical_delay_time_input,
        )

        with ExitStack() as blockers:
            for widget in radio_widgets:
                blockers.enter_context(QSignalBlocker(widget))

            self._start_hz_input.setText(f"{config.radar.sweep.start_hz:g}")
            self._stop_hz_input.setText(f"{config.radar.sweep.stop_hz:g}")
            self._points_input.setText(str(int(config.radar.sweep.points)))
            self._ifbw_input.setText(f"{config.radar.sweep.if_bandwidth_hz:g}")
            self._power_input.setText(f"{config.radar.sweep.power_dbm:g}")
            self._settling_ms.setText(str(int(config.runtime.settling_ms)))
            self._set_radar_settings_mode(config.is_kamil_adc)

            adc = config.radar.kamil_adc
            optical = config.radar.laser_control
            manual = optical.manual
            variation = optical.variation
            self._adc_project_dir_input.setText(str(adc.project_dir))
            self._adc_executable_path_input.setText(str(adc.executable_path))
            self._adc_tty_path_input.setText(str(adc.tty_path))
            self._adc_args_input.setPlainText("\n".join(adc.args))
            self._adc_env_input.setPlainText(json.dumps(adc.env, indent=2, sort_keys=True) if adc.env else "{}")
            self._adc_startup_timeout_s_input.setText(f"{float(adc.startup_timeout_s):g}")
            self._adc_sweep_timeout_s_input.setText(f"{float(adc.sweep_timeout_s):g}")
            self._adc_stop_timeout_s_input.setText(f"{float(adc.stop_timeout_s):g}")
            self._optical_enabled_checkbox.setChecked(bool(optical.enabled))
            self._optical_port_input.setText(str(optical.port))
            self._set_combo_current_text(self._optical_mode_combo, str(optical.mode))
            self._optical_pi_coeff1_p_input.setText(str(int(optical.pi_coeff1_p)))
            self._optical_pi_coeff1_i_input.setText(str(int(optical.pi_coeff1_i)))
            self._optical_pi_coeff2_p_input.setText(str(int(optical.pi_coeff2_p)))
            self._optical_pi_coeff2_i_input.setText(str(int(optical.pi_coeff2_i)))
            self._optical_manual_temp1_input.setText(f"{float(manual.temp1):g}")
            self._optical_manual_temp2_input.setText(f"{float(manual.temp2):g}")
            self._optical_manual_current1_input.setText(f"{float(manual.current1):g}")
            self._optical_manual_current2_input.setText(f"{float(manual.current2):g}")
            self._set_combo_current_text(self._optical_variation_type_combo, str(variation.variation_type))
            self._optical_static_temp1_input.setText(f"{float(variation.static_temp1):g}")
            self._optical_static_temp2_input.setText(f"{float(variation.static_temp2):g}")
            self._optical_static_current1_input.setText(f"{float(variation.static_current1):g}")
            self._optical_static_current2_input.setText(f"{float(variation.static_current2):g}")
            self._optical_min_value_input.setText(f"{float(variation.min_value):g}")
            self._optical_max_value_input.setText(f"{float(variation.max_value):g}")
            self._optical_step_input.setText(f"{float(variation.step):g}")
            self._optical_time_step_input.setText(str(int(variation.time_step)))
            self._optical_delay_time_input.setText(str(int(variation.delay_time)))
            self._sync_adc_settings_controls()

            self._combos_text.setText(str(gui_state.switches.combos_text))
            self._single_combo_output.setText(str(gui_state.switches.single_output))
            self._single_combo_input.setText(str(gui_state.switches.single_input))
            self._set_combo_selection_mode(gui_state.switches.combo_mode)

            self._set_combo_current_text(self._processing_mode, gui_state.processing.selected_mode)
            self._show_magnitude_checkbox.setChecked(bool(gui_state.processing.pass_through.show_magnitude))
            self._show_phase_checkbox.setChecked(bool(gui_state.processing.pass_through.show_phase))
            self._pass_through_combo_filter_input.setText(str(gui_state.processing.pass_through.combo_filter))
            self._pass_through_fixed_y_enabled.setChecked(bool(gui_state.processing.pass_through.fixed_y_enabled))
            self._pass_through_y_min_db.setValue(float(gui_state.processing.pass_through.y_min_db))
            self._pass_through_y_max_db.setValue(float(gui_state.processing.pass_through.y_max_db))

            self._set_combo_current_text(self._bscan_axis, gui_state.processing.bscan.axis)
            self._bscan_cut_m.setValue(float(gui_state.processing.bscan.cut_m))
            self._bscan_max_depth_m.setValue(float(gui_state.processing.bscan.max_depth_m))
            self._bscan_gain.setValue(float(gui_state.processing.bscan.gain))
            self._bscan_start_freq_mhz.setValue(float(gui_state.processing.bscan.start_freq_mhz))
            self._bscan_stop_freq_mhz.setValue(float(gui_state.processing.bscan.stop_freq_mhz))
            self._bscan_subtract_mean_ascan.setChecked(bool(gui_state.processing.bscan.subtract_mean_ascan))

            self._gpr_relative_permittivity.setValue(float(config.gpr.relative_permittivity))
            self._gpr_tx_geometry_input.setPlainText(
                "\n".join(
                    f"{int(entry.output_pos)} {float(entry.x_m):g}"
                    for entry in config.gpr.tx_geometry
                )
            )
            self._gpr_rx_geometry_input.setPlainText(
                "\n".join(
                    f"{int(entry.input_pos)} {float(entry.x_m):g}"
                    for entry in config.gpr.rx_geometry
                )
            )
            self._gpr_input_positions_input.setText(str(gui_state.processing.gpr.input_positions))
            self._gpr_output_positions_input.setText(str(gui_state.processing.gpr.output_positions))
            self._gpr_min_depth_m.setValue(float(gui_state.processing.gpr.min_depth_m))
            self._gpr_max_depth_m.setValue(float(gui_state.processing.gpr.max_depth_m))
            self._gpr_range_comp_power.setValue(float(gui_state.processing.gpr.range_comp_power))
            self._gpr_angle_comp_power.setValue(float(gui_state.processing.gpr.angle_comp_power))
            self._set_combo_current_text(self._gpr_score_mode, gui_state.processing.gpr.score_mode)
            self._gpr_max_detected_objects_to_draw.setValue(
                int(gui_state.processing.gpr.max_detected_objects_to_draw)
            )
            self._gpr_draw_top_m_objects.setValue(int(gui_state.processing.gpr.draw_top_m_objects))
            self._gpr_start_freq_mhz.setValue(float(gui_state.processing.gpr.start_freq_mhz))
            self._gpr_stop_freq_mhz.setValue(float(gui_state.processing.gpr.stop_freq_mhz))
            self._gpr_background_subtract_enabled.setChecked(
                bool(gui_state.processing.gpr.background_subtract_enabled)
            )
            self._gpr_background_mean_count.setValue(int(gui_state.processing.gpr.background_mean_count))
            self._gpr_remove_sidelobe_objects_enabled.setChecked(
                bool(gui_state.processing.gpr.remove_sidelobe_objects_enabled)
            )
            self._gpr_imaging_plane_y_m.setValue(float(gui_state.processing.gpr.imaging_plane_y_m))
            self._set_combo_current_text(self._gpr_render_mode, gui_state.processing.gpr.render_mode)
            self._gpr_min_visible_score.setValue(float(gui_state.processing.gpr.min_visible_score))
            self._gpr_visible_x_min_m.setValue(float(gui_state.processing.gpr.visible_x_min_m))
            self._gpr_visible_x_max_m.setValue(float(gui_state.processing.gpr.visible_x_max_m))
            self._gpr_visible_z_min_m.setValue(float(gui_state.processing.gpr.visible_z_min_m))
            self._gpr_visible_z_max_m.setValue(float(gui_state.processing.gpr.visible_z_max_m))

            self._set_combo_current_text(self._legacy_gpr_config_mode, gui_state.processing.legacy_gpr.mode)
            self._legacy_gpr_input_positions_input.setText(str(gui_state.processing.legacy_gpr.input_positions))
            self._legacy_gpr_output_positions_input.setText(str(gui_state.processing.legacy_gpr.output_positions))
            self._legacy_gpr_min_depth_m.setValue(float(gui_state.processing.legacy_gpr.min_depth_m))
            self._legacy_gpr_max_depth_m.setValue(float(gui_state.processing.legacy_gpr.max_depth_m))
            self._legacy_gpr_comp_power.setValue(float(gui_state.processing.legacy_gpr.comp_power))
            self._legacy_gpr_snr_thresh.setValue(float(gui_state.processing.legacy_gpr.snr_thresh))
            self._legacy_gpr_snr_comp_max.setValue(float(gui_state.processing.legacy_gpr.snr_comp_max))
            self._legacy_gpr_start_freq_mhz.setValue(float(gui_state.processing.legacy_gpr.start_freq_mhz))
            self._legacy_gpr_stop_freq_mhz.setValue(float(gui_state.processing.legacy_gpr.stop_freq_mhz))
            self._legacy_gpr_speed_m_s.setValue(float(gui_state.processing.legacy_gpr.speed_m_s))
            self._legacy_gpr_ignore_socket_speed_enabled.setChecked(
                bool(gui_state.processing.legacy_gpr.ignore_socket_speed_enabled)
            )
            self._legacy_gpr_look_angle_deg.setValue(float(gui_state.processing.legacy_gpr.look_angle_deg))
            self._legacy_gpr_background_subtract_enabled.setChecked(
                bool(gui_state.processing.legacy_gpr.background_subtract_enabled)
            )
            self._legacy_gpr_background_mean_count.setValue(int(gui_state.processing.legacy_gpr.background_mean_count))
            self._set_combo_current_text(self._legacy_gpr_render_mode, gui_state.processing.legacy_gpr.render_mode)
            self._legacy_gpr_min_visible_pair_count.setValue(int(gui_state.processing.legacy_gpr.min_visible_pair_count))
            self._legacy_gpr_visible_x_min_m.setValue(float(gui_state.processing.legacy_gpr.visible_x_min_m))
            self._legacy_gpr_visible_x_max_m.setValue(float(gui_state.processing.legacy_gpr.visible_x_max_m))
            self._legacy_gpr_visible_z_min_m.setValue(float(gui_state.processing.legacy_gpr.visible_z_min_m))
            self._legacy_gpr_visible_z_max_m.setValue(float(gui_state.processing.legacy_gpr.visible_z_max_m))

            self._save_count.setValue(int(gui_state.data_actions.save_count))
            self._save_path_input.setText(str(gui_state.data_actions.save_path))
            self._save_name_input.setText(str(gui_state.data_actions.save_name))

        self._gui_defaults = gui_state
        self._selected_preprocess_sets = selected_preprocess_sets
        self._selected_preprocess_radar_key = self._radar_key(config)
        self._preprocess_set_name = str(gui_state.preprocess_dialog.set_name)
        self._preprocess_radar_config_dir = str(gui_state.preprocess_dialog.radar_config_dir)
        self._preprocess_use_all_radar_configs = bool(gui_state.preprocess_dialog.use_all_radar_configs)
        self._preprocess_median_sweep_count = max(1, int(gui_state.preprocess_dialog.median_sweep_count))
        self._apply_history_limit_from_config(config)
        self._gpr_geometry_signature = None
        self._gpr_selected_geometry = None
        self._sync_pass_through_y_controls()
        self._set_processing_mode_page(gui_state.processing.selected_mode)
        self._refresh_preprocess_summary_labels()

        if self._preprocess_dialog is not None:
            with ExitStack() as dialog_blockers:
                dialog_blockers.enter_context(QSignalBlocker(self._preprocess_dialog._set_name_input))
                dialog_blockers.enter_context(QSignalBlocker(self._preprocess_dialog._radar_config_dir_input))
                dialog_blockers.enter_context(QSignalBlocker(self._preprocess_dialog._use_all_radar_configs_checkbox))
                for combo in self._preprocess_dialog._set_combos.values():
                    dialog_blockers.enter_context(QSignalBlocker(combo))
                self._preprocess_dialog.set_set_name(self._preprocess_set_name)
                self._preprocess_dialog.set_radar_config_dir(self._preprocess_radar_config_dir)
                self._preprocess_dialog.set_use_all_radar_configs(self._preprocess_use_all_radar_configs)
                self._preprocess_dialog.set_median_sweep_count(self._preprocess_median_sweep_count)
                self._preprocess_dialog.set_selected_sets(self._selected_preprocess_sets, emit_signal=False)

        self._apply_initial_radar_limits()
        if self._preprocess_dialog is not None:
            self._refresh_sets()
        self._reload_locator_service_from_config()
        self._on_processing_mode_changed(gui_state.processing.selected_mode)
        self._update_history_indicator()
        self._remember_active_profile_path(profile_path)
