"""
Left-panel widget: editable hardware delay / raster spinboxes.
"""
from __future__ import annotations

import json

from PySide6.QtCore import Signal
from src.gui.scheme_panel import system_is_dark
from src.gui.tr_widgets import TrGroupBox, TrPushButton
from PySide6.QtWidgets import (
    QWidget, QVBoxLayout, QFormLayout,
    QDoubleSpinBox, QGridLayout, QFileDialog,
    QCheckBox, QHBoxLayout,
)


# (attr_name, label, unit_suffix, scale_to_unit, min_val, max_val, step)
_FIELDS = [
    ("RF_DELAY",             "RF Delay",        "ns",  1e9,  0.0, 1e7,  1.0),
    ("TR_DELAY",             "TR Delay",         "ns",  1e9,  0.0, 1e7,  1.0),
    ("START_DELAY",          "Start Delay",      "ns",  1e9,  0.0, 1e7,  10.0),
    ("MIN_BLOCK_DURATION",   "Min Block Dur",    "ns",  1e9,  0.0, 1e7,  1.0),
    ("rf_raster_time",       "RF Raster",        "uss",  1e6,  0.001, 100.0, 0.01),
    ("grad_raster_time",     "Grad Raster",      "uss",  1e6,  0.001, 1000.0, 0.1),
    ("adc_raster_time",      "ADC Raster",       "ns",  1e9,  0.001, 1e6,  1.0),
    ("block_duration_raster","Block Raster",     "uss",  1e6,  0.001, 100.0, 0.01),
]

# Поля задержек с галочкой "вставлять блок": {attr поля: attr флага в hw}.
# Снятая галочка убирает вставку блока задержки (нет артефактов 1/0 тактов).
_ENABLE_FLAGS = {
    "RF_DELAY":    "RF_DELAY_ENABLED",
    "TR_DELAY":    "TR_DELAY_ENABLED",
    "START_DELAY": "START_DELAY_ENABLED",
}


class DelayControlsPanel(QWidget):
    rerun = Signal()        # user clicked "Apply & Rerun"
    reloadConfig = Signal() # user clicked "Reload Config"
    saveConfig = Signal()   # user clicked "Save HW Config"

    def __init__(self, parent: QWidget | None = None):
        super().__init__(parent)
        outer = QVBoxLayout(self)
        outer.setContentsMargins(4, 4, 4, 4)

        self._grp = TrGroupBox("grp_hw_delays")
        form = QFormLayout(self._grp)
        form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)

        self._spinboxes: dict[str, tuple[QDoubleSpinBox, float]] = {}
        self._defaults: dict[str, float] = {}
        self._checkboxes: dict[str, tuple[QCheckBox, str]] = {}
        self._enable_defaults: dict[str, bool] = {}

        for attr, label, unit, scale, mn, mx, step in _FIELDS:
            sb = QDoubleSpinBox()
            sb.setDecimals(3)
            sb.setSuffix(f"  {unit}")
            sb.setRange(mn, mx)
            sb.setSingleStep(step)
            sb.setStepType(QDoubleSpinBox.AdaptiveDecimalStepType)
            sb.valueChanged.connect(self._mark_modified)
            self._spinboxes[attr] = (sb, scale)

            flag_attr = _ENABLE_FLAGS.get(attr)
            if flag_attr:
                cb = QCheckBox()
                cb.setChecked(True)
                cb.setToolTip(
                    "Вставлять блок этой задержки. Снято — блок не вставляется "
                    "(убирает артефакты 1/0 тактов)."
                )
                cb.toggled.connect(self._on_enable_toggled)
                self._checkboxes[attr] = (cb, flag_attr)
                row = QWidget()
                row_lay = QHBoxLayout(row)
                row_lay.setContentsMargins(0, 0, 0, 0)
                row_lay.setSpacing(4)
                row_lay.addWidget(sb, stretch=1)
                row_lay.addWidget(cb)
                form.addRow(f"{label}:", row)
            else:
                form.addRow(f"{label}:", sb)

        outer.addWidget(self._grp)

        # 2x2 grid: prevents text overflow on narrow left panel (min 220 px)
        self.btn_apply  = TrPushButton("btn_apply_rerun")
        self.btn_reset  = TrPushButton("btn_reset")
        self.btn_reload = TrPushButton("btn_reload_config")
        self.btn_save   = TrPushButton("btn_save_hw")
        grid = QGridLayout()
        grid.setSpacing(4)
        grid.addWidget(self.btn_apply,  0, 0)
        grid.addWidget(self.btn_reset,  0, 1)
        grid.addWidget(self.btn_reload, 1, 0)
        grid.addWidget(self.btn_save,   1, 1)
        outer.addLayout(grid)

        self.btn_apply.clicked.connect(self.rerun)
        self.btn_reload.clicked.connect(self.reloadConfig)
        self.btn_reset.clicked.connect(self._on_reset)
        self.btn_save.clicked.connect(self._on_save)
        outer.addStretch()

    # ------------------------------------------------------------------
    # Public API
    # ------------------------------------------------------------------

    def load_from_hw(self, hw) -> None:
        for attr, (sb, scale) in self._spinboxes.items():
            val = getattr(hw, attr, 0.0)
            self._defaults[attr] = val
            sb.blockSignals(True)
            sb.setValue(val * scale)
            sb.blockSignals(False)
            sb.setStyleSheet("")
        for attr, (cb, flag_attr) in self._checkboxes.items():
            enabled = bool(getattr(hw, flag_attr, True))
            self._enable_defaults[attr] = enabled
            cb.blockSignals(True)
            cb.setChecked(enabled)
            cb.blockSignals(False)
            self._spinboxes[attr][0].setEnabled(enabled)

    def get_overrides(self) -> dict:
        """Return dict of {attr: value} in SI units plus *_ENABLED bool flags."""
        overrides = {attr: sb.value() / scale
                     for attr, (sb, scale) in self._spinboxes.items()}
        for cb, flag_attr in self._checkboxes.values():
            overrides[flag_attr] = cb.isChecked()
        return overrides

    # ------------------------------------------------------------------
    # Private helpers
    # ------------------------------------------------------------------

    def _mark_modified(self) -> None:
        sender = self.sender()
        for attr, (sb, scale) in self._spinboxes.items():
            if sb is sender:
                default = self._defaults.get(attr)
                is_modified = (default is not None and
                               abs(sb.value() / scale - default) > 1e-15)
                if is_modified:
                    bg = "#2a2400" if system_is_dark() else "#fffde7"
                    sb.setStyleSheet(f"background-color: {bg};")
                else:
                    sb.setStyleSheet("")
                break

    def _on_enable_toggled(self) -> None:
        sender = self.sender()
        for attr, (cb, flag_attr) in self._checkboxes.items():
            if cb is sender:
                self._spinboxes[attr][0].setEnabled(cb.isChecked())
                break

    def _on_reset(self) -> None:
        for attr, (sb, scale) in self._spinboxes.items():
            default = self._defaults.get(attr)
            if default is not None:
                sb.blockSignals(True)
                sb.setValue(default * scale)
                sb.blockSignals(False)
                sb.setStyleSheet("")
        for attr, (cb, flag_attr) in self._checkboxes.items():
            default = self._enable_defaults.get(attr, True)
            cb.blockSignals(True)
            cb.setChecked(default)
            cb.blockSignals(False)
            self._spinboxes[attr][0].setEnabled(default)

    def _on_save(self) -> None:
        path, _ = QFileDialog.getSaveFileName(
            self, "Save hardware config", "hw_config.json",
            "JSON files (*.json)"
        )
        if not path:
            return
        overrides = self.get_overrides()
        try:
            with open(path, "w", encoding="utf-8") as fh:
                json.dump(overrides, fh, indent=2)
        except OSError as exc:
            from PySide6.QtWidgets import QMessageBox
            QMessageBox.critical(self, "Save failed", str(exc))
        self.saveConfig.emit()
