"""Signal data <-> proto converters."""

from __future__ import annotations

import numpy as np
import numpy.typing as npt

from radiens_core._generated import common_pb2
from radiens_core.models.signals import SignalArrays, SigSelect


def raw_signals_to_numpy(raw: common_pb2.RawSignals) -> npt.NDArray[np.float64] | None:
    """Convert proto RawSignals to a numpy 2D array (channels x samples).

    Returns None if data is empty.
    """
    if len(raw.data) == 0:
        return None
    arr = np.frombuffer(raw.data, dtype=np.float32).astype(np.float64)
    return arr.reshape(int(raw.shape[0]), int(raw.shape[1]))


def signal_arrays_from_hd_snapshot(response: common_pb2.HDSnapshot) -> SignalArrays:
    """Build domain SignalArrays from proto HDSnapshot (old Pcache1/GetHDSnapshot API)."""
    return SignalArrays(
        amp=raw_signals_to_numpy(response.priSignals) if response.HasField("priSignals") else None,
        gpio_ain=raw_signals_to_numpy(response.auxSignals) if response.HasField("auxSignals") else None,
        gpio_din=raw_signals_to_numpy(response.dinSignals) if response.HasField("dinSignals") else None,
        gpio_dout=raw_signals_to_numpy(response.doutSignals) if response.HasField("doutSignals") else None,
    )


def radix_matrix_to_numpy(
    mb: common_pb2.RadixMatrixBytes,
) -> npt.NDArray[np.float64] | None:
    """Convert proto RadixMatrixBytes to a numpy 2D array (channels x samples).

    Returns None if data is empty.
    """
    if len(mb.data) == 0:
        return None
    arr = np.frombuffer(mb.data, dtype=np.float32).astype(np.float64)
    return arr.reshape(mb.shape[0], mb.shape[1])


def signal_arrays_from_signals2(
    sigs: common_pb2.Signals2,
) -> SignalArrays:
    """Build domain SignalArrays from proto Signals2."""
    return SignalArrays(
        amp=radix_matrix_to_numpy(sigs.amp),
        gpio_ain=radix_matrix_to_numpy(sigs.ain),
        gpio_din=radix_matrix_to_numpy(sigs.din),
        gpio_dout=radix_matrix_to_numpy(sigs.dout),
    )


def sig_select_to_proto(sig_sel: SigSelect) -> common_pb2.SigSelector:
    """Convert domain SigSelect to proto SigSelector."""
    return common_pb2.SigSelector(
        ampNtvIdxs=sig_sel.amp.tolist(),
        ainNtvIdxs=sig_sel.gpio_ain.tolist(),
        dinNtvIdxs=sig_sel.gpio_din.tolist(),
        doutNtvIdxs=sig_sel.gpio_dout.tolist(),
    )
