"""
Negative peak detection summary metric for CUDA-accelerated batch integration.

This module implements a summary metric that detects and records the timing
of local minima (negative peaks) in variable values during integration.
"""

from numba import cuda, int32
from cubie.cuda_simsafe import compile_kwargs

from cubie.outputhandling.summarymetrics import summary_metrics
from cubie.outputhandling.summarymetrics.metrics import (
    SummaryMetric,
    register_metric,
    MetricFuncCache,
)


@register_metric(summary_metrics)
class NegativePeaks(SummaryMetric):
    """Summary metric that records the indices of detected negative peaks.

    Notes
    -----
    The buffer stores the two previous values, a peak counter, and slots for
    the recorded negative peak indices. The algorithm assumes ``0.0`` does not
    occur in valid data so it can serve as an initial sentinel.
    """

    def __init__(self, precision) -> None:
        """Initialise the NegativePeaks summary metric."""
        super().__init__(
            name="negative_peaks",
            precision=precision,
            buffer_size=lambda n: 3 + n,
            output_size=lambda n: n,
            unit_modification="s",
        )

    def build(self) -> MetricFuncCache:
        """Generate CUDA device functions for negative peak detection.

        Returns
        -------
        MetricFuncCache
            Cache containing the device update and save callbacks.

        Notes
        -----
        The update callback compares the current value against stored history
        to identify negative peaks (local minima), while the save callback
        copies stored indices and resets the buffer for the next period.
        """

        precision = self.compile_settings.precision

        # no cover: start
        @cuda.jit(
            # [
            #     "float32, float32[::1], int32, int32",
            #     "float64, float64[::1], int32, int32",
            # ],
            device=True,
            inline=True,
            **compile_kwargs,
        )
        def update(
            value,
            buffer,
            current_index,
            customisable_variable,
        ):
            """Update negative peak detection with a new value.

            Parameters
            ----------
            value
                float. New value to analyse for negative peak detection.
            buffer
                device array. Layout ``[prev, prev_prev, counter, times...]``.
            current_index
                int. Current integration step index, used to record peaks.
            customisable_variable
                int. Maximum number of negative peaks to detect.

            Notes
            -----
            Detects negative peaks (local minima) when the prior value is
            less than both the current and second-prior values. Peak indices
            are stored from ``buffer[3]`` onward.
            """
            npeaks = customisable_variable
            prev = buffer[0]
            prev_prev = buffer[1]
            peak_counter = int32(buffer[2])

            if (
                (current_index >= int32(2))
                and (peak_counter < npeaks)
                and (prev_prev != precision(0.0))
            ):
                if prev < value and prev_prev > prev:
                    buffer[3 + peak_counter] = (current_index - 1)
                    buffer[2] = precision(int32(buffer[2]) + 1)
            buffer[0] = value
            buffer[1] = prev

        @cuda.jit(
            # [
            #     "float32[::1], float32[::1], int32, int32",
            #     "float64[::1], float64[::1], int32, int32",
            # ],
            device=True,
            inline=True,
            **compile_kwargs,
        )
        def save(
            buffer,
            output_array,
            summarise_every,
            customisable_variable,
        ):
            """Save detected negative peak time indices and reset the buffer.

            Parameters
            ----------
            buffer
                device array. Buffer with detected negative peak time indices.
            output_array
                device array. Output array for saving peak time indices.
            summarise_every
                int. Number of steps between saves (unused).
            customisable_variable
                int. Maximum number of negative peaks to detect.

            Notes
            -----
            Copies peak indices from ``buffer[3:]`` to the output array then
            clears the storage for the next summary interval.
            """
            n_peaks = int32(customisable_variable)
            for p in range(n_peaks):
                output_array[p] = buffer[3 + p]
                buffer[3 + p] = precision(0.0)
            buffer[2] = precision(0.0)

        # no cover: end
        return MetricFuncCache(update=update, save=save)
