"""Head Injury Criterion (HIC) calculation.

HIC = max over (t1, t2) { (t2-t1) * [ 1/(t2-t1) * integral(t1,t2) a(t) dt ]^2.5 }

where a(t) is the resultant head acceleration in g's.

HIC15: max window = 15 ms
HIC36: max window = 36 ms
"""

from __future__ import annotations

from typing import Any

import numpy as np
from numpy.typing import NDArray

from impakt.channel.model import Channel, ChannelGroup, DummyInfo
from impakt.criteria.base import CriterionResult


def _compute_hic(
    accel: NDArray[np.floating[Any]],
    time: NDArray[np.floating[Any]],
    max_window_s: float,
) -> tuple[float, float, float]:
    """Core HIC computation using cumulative integration.

    Returns:
        (hic_value, t1, t2) — the HIC value and the optimal window.
    """
    n = len(accel)
    if n < 2:
        return 0.0, 0.0, 0.0

    dt = np.diff(time)
    # Cumulative integral using trapezoidal rule
    cum_integral = np.zeros(n)
    cum_integral[1:] = np.cumsum(0.5 * (accel[:-1] + accel[1:]) * dt)

    best_hic = 0.0
    best_t1 = 0.0
    best_t2 = 0.0

    # Sliding window search
    j_start = 0
    for i in range(n - 1):
        t_i = time[i]

        # Advance j_start so we search forward from i
        if j_start <= i:
            j_start = i + 1

        for j in range(j_start, n):
            dt_window = time[j] - t_i
            if dt_window <= 0:
                continue
            if dt_window > max_window_s:
                break

            avg_accel = (cum_integral[j] - cum_integral[i]) / dt_window
            hic_val = dt_window * (abs(avg_accel) ** 2.5)

            if hic_val > best_hic:
                best_hic = hic_val
                best_t1 = t_i
                best_t2 = time[j]

    return best_hic, float(best_t1), float(best_t2)


def hic(
    channel_or_group: Channel | ChannelGroup,
    window_ms: int = 15,
    channels: dict[str, Channel] | None = None,
    dummy: DummyInfo | None = None,
) -> CriterionResult:
    """Compute HIC (Head Injury Criterion).

    Args:
        channel_or_group: Either a resultant acceleration Channel (in g's),
            or a ChannelGroup with X/Y/Z head acceleration components.
        window_ms: Maximum window in milliseconds (15 or 36).
        channels: Alternative: dict of named channels.
        dummy: Dummy info (not used for HIC, but part of the protocol).

    Returns:
        CriterionResult with the HIC value.
    """
    if window_ms not in (15, 36):
        raise ValueError(f"HIC window must be 15 or 36 ms, got {window_ms}")

    # Get the resultant acceleration
    if isinstance(channel_or_group, ChannelGroup):
        resultant = channel_or_group.resultant()
    elif isinstance(channel_or_group, Channel):
        resultant = channel_or_group
    elif channels is not None:
        # Try to compute resultant from dict
        comps = list(channels.values())
        if len(comps) == 1:
            resultant = comps[0]
        else:
            from impakt.transform.resultant import resultant_from_channels

            resultant = resultant_from_channels(*comps)
    else:
        raise TypeError("Provide either a Channel (resultant), ChannelGroup, or channels dict")

    # Ensure data is in g's (basic check — if unit is m/s², convert)
    accel = resultant.data.copy()
    if resultant.unit in ("m/s²", "m/s^2", "m/s2"):
        accel = accel / 9.80665  # Convert to g

    max_window_s = window_ms / 1000.0
    hic_value, t1, t2 = _compute_hic(accel, resultant.time, max_window_s)

    return CriterionResult(
        criterion=f"HIC{window_ms}",
        value=hic_value,
        unit="",
        time_of_peak=(t1 + t2) / 2.0,
        window=(t1, t2),
        body_region="Head",
        details={
            "window_ms": window_ms,
            "t1": t1,
            "t2": t2,
            "window_duration_ms": (t2 - t1) * 1000,
            "input_unit": resultant.unit,
            "input_channel": resultant.name,
        },
    )


def hic15(
    channel_or_group: Channel | ChannelGroup,
    **kwargs: Any,
) -> CriterionResult:
    """Convenience: compute HIC15."""
    return hic(channel_or_group, window_ms=15, **kwargs)


def hic36(
    channel_or_group: Channel | ChannelGroup,
    **kwargs: Any,
) -> CriterionResult:
    """Convenience: compute HIC36."""
    return hic(channel_or_group, window_ms=36, **kwargs)
