"""UI construction mixin for the main radar control window.

Layout is intentionally split into two independent plot surfaces:
- single `PlotWidget` for B-scan heatmap rendering;
- single `PlotWidget` for GPR accumulator/annotation rendering;
- stacked magnitude/phase `PlotWidget`s for pass-through traces.

`_set_plot_mode()` switches between these surfaces via `QStackedWidget`.
"""

from __future__ import annotations

from PyQt6.QtWidgets import (
    QComboBox,
    QFrame,
    QGroupBox,
    QHBoxLayout,
    QLabel,
    QPlainTextEdit,
    QPushButton,
    QScrollArea,
    QStackedWidget,
    QVBoxLayout,
    QWidget,
)
import pyqtgraph as pg

from python_app.gui.controllers.sections import (
    build_data_actions_group,
    build_gpr_config_group,
    build_hardware_actions_group,
    build_pipeline_group,
    build_preprocess_summary_group,
    build_processing_group,
    build_radar_group,
    build_switch_group,
)


class AppWindowUiMixin:
    """Build and wire all static widgets used by :class:`AppWindow`.

    This mixin only creates UI objects and connects lightweight UI signals.
    Runtime logic (pipeline start/stop, polling, drawing, snapshot saving)
    is implemented in other mixins.
    """

    def _build_ui(self) -> None:
        """Build complete main-window layout.

        High-level structure:
        1. Left: plot stack (`B-scan` page + `trace magnitude/phase` page).
        2. Middle: narrow toggle button to collapse/expand settings.
        3. Right: scrollable settings panel + status/history + runtime log.
        """
        self.setWindowTitle("Radar System Control")
        root = QWidget(self)
        self.setCentralWidget(root)

        layout = QHBoxLayout(root)
        layout.setContentsMargins(12, 12, 12, 12)
        layout.setSpacing(14)

        self._build_plot_area(layout, root)
        self._build_settings_toggle(layout)
        self._build_settings_panel(layout, root)

        self._toggle_settings_panel(visible=True)
        self.resize(1650, 940)

    def _build_plot_area(self, root_layout: QHBoxLayout, root: QWidget) -> None:
        """Build left plot area with stacked B-scan and trace pages."""
        # Plot stack holds mutually exclusive visualization surfaces.
        # We create both upfront and only switch active page at runtime.
        self._plot_stack = QStackedWidget(root)
        self._build_bscan_plot_page()
        self._build_gpr_plot_page()
        self._build_trace_plot_page()

        # Default view on startup is pass-through traces.
        self._plot_stack.setCurrentWidget(self._trace_plots_container)
        root_layout.addWidget(self._plot_stack, stretch=11)

    def _build_bscan_plot_page(self) -> None:
        """Create B-scan page in plot stack."""
        # B-scan surface: one PlotWidget used as canvas for ImageItem heatmap.
        self._bscan_plot = pg.PlotWidget(background="#0f141c")
        self._bscan_plot.showGrid(x=True, y=True, alpha=0.2)
        self._plot_stack.addWidget(self._bscan_plot)

    def _build_trace_plot_page(self) -> None:
        """Create pass-through page with magnitude and phase plots."""
        # Pass-through surface: container with two synchronized line plots.
        # Upper plot shows magnitude, lower plot shows phase.
        self._trace_plots_container = QWidget()
        trace_layout = QVBoxLayout(self._trace_plots_container)
        trace_layout.setContentsMargins(0, 0, 0, 0)
        trace_layout.setSpacing(6)

        self._trace_magnitude_plot = pg.PlotWidget(background="#0f141c")
        self._trace_magnitude_plot.showGrid(x=True, y=True, alpha=0.2)
        self._trace_magnitude_plot.setLabel("left", "Magnitude", units="dB")
        self._trace_magnitude_plot.getPlotItem().showAxis("bottom", show=False)
        self._trace_magnitude_plot.getPlotItem().setDownsampling(mode="peak")
        self._trace_magnitude_plot.getPlotItem().setClipToView(True)
        trace_layout.addWidget(self._trace_magnitude_plot, stretch=1)

        self._trace_phase_plot = pg.PlotWidget(background="#0f141c")
        self._trace_phase_plot.showGrid(x=True, y=True, alpha=0.2)
        self._trace_phase_plot.setLabel("left", "Phase", units="deg")
        self._trace_phase_plot.setLabel("bottom", "Frequency", units="Hz")
        self._trace_phase_plot.getPlotItem().setDownsampling(mode="peak")
        self._trace_phase_plot.getPlotItem().setClipToView(True)
        trace_layout.addWidget(self._trace_phase_plot, stretch=1)

        # Legends are created lazily only when trace mode draws line series.
        self._trace_magnitude_legend = None
        self._trace_phase_legend = None
        self._trace_magnitude_legend_combo_keys = set()
        self._trace_phase_legend_combo_keys = set()
        self._trace_magnitude_curves = {}
        self._trace_phase_curves = {}
        self._trace_phase_render_max_points = 400

        self._plot_stack.addWidget(self._trace_plots_container)

    def _build_gpr_plot_page(self) -> None:
        """Create GPR page in plot stack."""
        self._gpr_plot = pg.PlotWidget(background="#0f141c")
        self._gpr_plot.showGrid(x=True, y=True, alpha=0.2)
        self._plot_stack.addWidget(self._gpr_plot)

    def _build_settings_toggle(self, root_layout: QHBoxLayout) -> None:
        """Create narrow button used to collapse or show settings panel."""
        self._settings_toggle_button = QPushButton("<")
        self._settings_toggle_button.setObjectName("settingsToggleButton")
        self._settings_toggle_button.setFixedWidth(26)
        self._settings_toggle_button.clicked.connect(lambda: self._toggle_settings_panel())
        root_layout.addWidget(self._settings_toggle_button, stretch=0)

    def _build_settings_panel(self, root_layout: QHBoxLayout, root: QWidget) -> None:
        """Build right settings panel with controls, status labels, and log."""
        self._settings_panel = QWidget(root)
        self._settings_panel.setMinimumWidth(659)
        right_layout = QVBoxLayout(self._settings_panel)
        right_layout.setContentsMargins(0, 0, 0, 0)
        right_layout.setSpacing(10)

        # Build log early so `_show_error()` can append text even during
        # subsequent group construction if something fails.
        self._log_box = QPlainTextEdit(self._settings_panel)
        self._log_box.setReadOnly(True)
        self._log_box.setMinimumHeight(170)

        right_layout.addWidget(self._build_settings_scroll(), stretch=1)

        self._status_label = QLabel("Status: idle", self._settings_panel)
        self._status_label.setObjectName("statusLabel")
        right_layout.addWidget(self._status_label)

        self._history_label = QLabel("History: raw=0, preprocessed=0, results=0", self._settings_panel)
        self._history_label.setObjectName("hintLabel")
        right_layout.addWidget(self._history_label)

        right_layout.addWidget(self._log_box, stretch=0)
        root_layout.addWidget(self._settings_panel, stretch=8)

    def _build_settings_scroll(self) -> QScrollArea:
        """Build scroll area with all control groups in display order."""
        # All control groups are placed into a scroll area so right panel
        # remains usable on smaller screens and with future extra controls.
        controls = QWidget(self._settings_panel)
        controls_layout = QVBoxLayout(controls)
        controls_layout.setContentsMargins(0, 0, 0, 0)
        controls_layout.setSpacing(10)
        for group in self._build_control_groups():
            controls_layout.addWidget(group)
        controls_layout.addStretch(1)

        scroll = QScrollArea(self._settings_panel)
        scroll.setWidgetResizable(True)
        scroll.setFrameShape(QFrame.Shape.NoFrame)
        scroll.setWidget(controls)
        return scroll

    def _build_control_groups(self) -> list[QGroupBox]:
        """Create all settings groups in top-to-bottom order."""
        return [
            build_pipeline_group(self),
            build_hardware_actions_group(self),
            build_data_actions_group(self),
            build_preprocess_summary_group(self),
            build_processing_group(self),
            build_gpr_config_group(self),
            build_radar_group(self),
            build_switch_group(self),
        ]

    def _toggle_settings_panel(self, *, visible: bool | None = None) -> None:
        """Toggle settings panel visibility or force a specific state.

        When `visible` is `None`, state is toggled.
        When `visible` is set, panel visibility is forced to that value.
        """
        if visible is None:
            visible = not self._settings_panel.isVisible()
        self._settings_panel.setVisible(visible)
        if visible:
            self._settings_toggle_button.setText(">")
            self._settings_toggle_button.setToolTip("Hide settings panel")
        else:
            self._settings_toggle_button.setText("<")
            self._settings_toggle_button.setToolTip("Show settings panel")

    def _set_plot_mode(self, mode: str) -> None:
        """Switch visible plot page according to processing mode.

        `bscan`   -> show `self._bscan_plot`
        `gpr`     -> show `self._gpr_plot`
        otherwise -> show `self._trace_plots_container`
        """
        if mode == "bscan":
            self._plot_stack.setCurrentWidget(self._bscan_plot)
            return
        if mode == "gpr":
            self._plot_stack.setCurrentWidget(self._gpr_plot)
            return
        self._plot_stack.setCurrentWidget(self._trace_plots_container)

    @staticmethod
    def _set_combo_current_text(combo: QComboBox, value: str) -> None:
        """Select combo item by text, appending it when missing."""
        index = combo.findText(value)
        if index >= 0:
            combo.setCurrentIndex(index)
            return
        combo.addItem(value)
        combo.setCurrentIndex(combo.count() - 1)
