"""Coarse LiDAR deskew using a constant body rate over the sweep."""

from __future__ import annotations

import numpy as np

from .contracts import ImuSeries, LidarFrame
from .geometry import so3_exp
from .time_offset import lidar_time_to_imu_time


def deskew_lidar_frames(
    frames: list[LidarFrame],
    imu: ImuSeries,
    *,
    delta_t_s: float,
    R_IMU_lidar: np.ndarray | None = None,
    gyro_bias_rad_s: np.ndarray | None = None,
) -> list[LidarFrame]:
    """Return deskewed copies when extrinsic is known; otherwise return originals."""

    if R_IMU_lidar is None:
        return frames

    bias = np.zeros(3) if gyro_bias_rad_s is None else np.asarray(gyro_bias_rad_s, dtype=float)
    r_li = np.asarray(R_IMU_lidar, dtype=float).reshape(3, 3).T
    output: list[LidarFrame] = []

    for frame in frames:
        n = frame.points_xyz.shape[0]
        if n < 10:
            output.append(frame)
            continue
        t_mid_imu = lidar_time_to_imu_time(frame.t_mid_s, delta_t_s)
        index = int(np.clip(np.searchsorted(imu.t_s, t_mid_imu), 1, imu.t_s.size - 1))
        omega_lidar = r_li @ (imu.gyro_rad_s[index] - bias)
        duration = max(frame.t_end_s - frame.t_start_s, 1e-3)
        rel = np.linspace(-0.5, 0.5, n) * duration
        deskewed = np.empty_like(frame.points_xyz)
        # Piecewise-constant rotation over a few time bins.
        bins = 12
        edges = np.linspace(-0.5 * duration, 0.5 * duration, bins + 1)
        for b in range(bins):
            mask = (rel >= edges[b]) & (rel <= edges[b + 1] if b == bins - 1 else rel < edges[b + 1])
            if not np.any(mask):
                continue
            tau = 0.5 * (edges[b] + edges[b + 1])
            rot = so3_exp(omega_lidar * float(tau))
            deskewed[mask] = frame.points_xyz[mask] @ rot.T
        output.append(
            LidarFrame(
                frame_id=frame.frame_id,
                t_start_s=frame.t_start_s,
                t_end_s=frame.t_end_s,
                points_xyz=deskewed,
                path=frame.path,
            )
        )
    return output
