"""Tibia Index (TI) calculation.

TI = |M| / Mc + |F| / Fc

where:
    M = resultant bending moment = sqrt(Mx^2 + My^2)
    F = axial compressive force
    Mc = critical bending moment (225 N·m for H3 50th)
    Fc = critical compressive force (35.9 kN for H3 50th)
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

import numpy as np

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


@dataclass(frozen=True)
class TibiaIntercepts:
    """Critical intercept values for Tibia Index."""

    mc: float  # Critical bending moment (N·m)
    fc: float  # Critical compressive force (N)


TIBIA_INTERCEPTS: dict[str, TibiaIntercepts] = {
    "H3-50M": TibiaIntercepts(mc=225.0, fc=35900.0),
    "H3-05F": TibiaIntercepts(mc=140.0, fc=22400.0),
    "H3-95M": TibiaIntercepts(mc=307.0, fc=48900.0),
}

DEFAULT_TIBIA_INTERCEPTS = TIBIA_INTERCEPTS["H3-50M"]


def tibia_index(
    fz_channel: Channel | None = None,
    mx_channel: Channel | None = None,
    my_channel: Channel | None = None,
    channels: dict[str, Channel] | None = None,
    dummy: DummyInfo | None = None,
    intercepts: TibiaIntercepts | None = None,
    location: str = "",
) -> CriterionResult:
    """Compute Tibia Index.

    Args:
        fz_channel: Tibia axial force (N).
        mx_channel: Tibia bending moment about X (N·m).
        my_channel: Tibia bending moment about Y (N·m).
        channels: Dict with 'fz', 'mx', 'my' keys.
        dummy: Dummy info for intercept lookup.
        intercepts: Override intercept values.
        location: Location label (e.g., 'Left Upper', 'Right Lower').

    Returns:
        CriterionResult with TI value.
    """
    if channels is not None:
        fz_channel = fz_channel or channels.get("fz") or channels.get("Fz")
        mx_channel = mx_channel or channels.get("mx") or channels.get("Mx")
        my_channel = my_channel or channels.get("my") or channels.get("My")

    if fz_channel is None:
        raise ValueError("Tibia axial force (Fz) channel is required")

    # Get intercepts
    ints = intercepts
    if ints is None and dummy is not None:
        for key, ti_ints in TIBIA_INTERCEPTS.items():
            if key.lower() in dummy.dummy_type.lower():
                ints = ti_ints
                break
    if ints is None:
        ints = DEFAULT_TIBIA_INTERCEPTS

    fz = fz_channel.data.copy()
    if fz_channel.unit in ("kN",):
        fz = fz * 1000.0

    n = len(fz)

    # Compute resultant bending moment
    if mx_channel is not None and my_channel is not None:
        mx = mx_channel.data[:n].copy()
        my = my_channel.data[:n].copy()
        if mx_channel.unit in ("kN·m", "kNm"):
            mx = mx * 1000.0
        if my_channel.unit in ("kN·m", "kNm"):
            my = my * 1000.0
        m_resultant = np.sqrt(mx**2 + my**2)
    elif mx_channel is not None:
        mx = mx_channel.data[:n].copy()
        if mx_channel.unit in ("kN·m", "kNm"):
            mx = mx * 1000.0
        m_resultant = np.abs(mx)
    elif my_channel is not None:
        my = my_channel.data[:n].copy()
        if my_channel.unit in ("kN·m", "kNm"):
            my = my * 1000.0
        m_resultant = np.abs(my)
    else:
        # Only axial force component
        m_resultant = np.zeros(n)

    # TI = |M|/Mc + |F|/Fc
    ti = m_resultant / ints.mc + np.abs(fz) / ints.fc

    peak_ti = float(np.max(ti))
    peak_idx = int(np.argmax(ti))
    peak_time = float(fz_channel.time[peak_idx])

    loc_label = f" ({location})" if location else ""

    return CriterionResult(
        criterion=f"Tibia Index{loc_label}",
        value=peak_ti,
        unit="",
        time_of_peak=peak_time,
        body_region=f"Tibia{loc_label}",
        details={
            "location": location,
            "mc": ints.mc,
            "fc": ints.fc,
            "peak_moment_Nm": float(np.max(m_resultant)),
            "peak_force_N": float(np.max(np.abs(fz))),
            "peak_force_kN": float(np.max(np.abs(fz))) / 1000.0,
        },
    )
