"""Geometric helper functions for constraint equations.

Provides Expr-level vector operations and marker axis extraction.
All functions work with Expr triples (tuples of 3 Expr nodes)
representing 3D vectors in world coordinates.

Marker convention (from Types.h): the marker's Z-axis defines the
constraint direction (hinge axis, face normal, line direction, etc.).
"""

from __future__ import annotations

from .entities import RigidBody
from .expr import Const, Expr
from .quat import quat_rotate

# Type alias for an Expr triple (3D vector)
Vec3 = tuple[Expr, Expr, Expr]


# -- Marker axis extraction ---------------------------------------------------


def _composed_quat(
    body: RigidBody,
    marker_quat: tuple[float, float, float, float],
) -> tuple[Expr, Expr, Expr, Expr]:
    """Compute q_total = q_body * q_marker as Expr quaternion.

    q_body comes from the body's Var params; q_marker is constant.
    """
    bw, bx, by, bz = body.qw, body.qx, body.qy, body.qz
    mw, mx, my, mz = (Const(v) for v in marker_quat)
    # Hamilton product: body * marker
    rw = bw * mw - bx * mx - by * my - bz * mz
    rx = bw * mx + bx * mw + by * mz - bz * my
    ry = bw * my - bx * mz + by * mw + bz * mx
    rz = bw * mz + bx * my - by * mx + bz * mw
    return rw, rx, ry, rz


def marker_z_axis(
    body: RigidBody,
    marker_quat: tuple[float, float, float, float],
) -> Vec3:
    """World-frame Z-axis of a marker on a body.

    Computes rotate(q_body * q_marker, [0, 0, 1]).
    """
    qw, qx, qy, qz = _composed_quat(body, marker_quat)
    return quat_rotate(qw, qx, qy, qz, Const(0.0), Const(0.0), Const(1.0))


def marker_x_axis(
    body: RigidBody,
    marker_quat: tuple[float, float, float, float],
) -> Vec3:
    """World-frame X-axis of a marker on a body.

    Computes rotate(q_body * q_marker, [1, 0, 0]).
    """
    qw, qx, qy, qz = _composed_quat(body, marker_quat)
    return quat_rotate(qw, qx, qy, qz, Const(1.0), Const(0.0), Const(0.0))


def marker_y_axis(
    body: RigidBody,
    marker_quat: tuple[float, float, float, float],
) -> Vec3:
    """World-frame Y-axis of a marker on a body.

    Computes rotate(q_body * q_marker, [0, 1, 0]).
    """
    qw, qx, qy, qz = _composed_quat(body, marker_quat)
    return quat_rotate(qw, qx, qy, qz, Const(0.0), Const(1.0), Const(0.0))


# -- Vector operations on Expr triples ----------------------------------------


def dot3(a: Vec3, b: Vec3) -> Expr:
    """Dot product of two Expr triples."""
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]


def cross3(a: Vec3, b: Vec3) -> Vec3:
    """Cross product of two Expr triples."""
    return (
        a[1] * b[2] - a[2] * b[1],
        a[2] * b[0] - a[0] * b[2],
        a[0] * b[1] - a[1] * b[0],
    )


def sub3(a: Vec3, b: Vec3) -> Vec3:
    """Vector subtraction a - b."""
    return (a[0] - b[0], a[1] - b[1], a[2] - b[2])


# -- Geometric primitives -----------------------------------------------------


def point_plane_distance(
    point: Vec3,
    plane_origin: Vec3,
    normal: Vec3,
) -> Expr:
    """Signed distance from point to plane defined by origin + normal.

    Returns (point - plane_origin) . normal
    """
    d = sub3(point, plane_origin)
    return dot3(d, normal)


def point_line_perp_components(
    point: Vec3,
    line_origin: Vec3,
    line_dir: Vec3,
) -> tuple[Expr, Expr, Expr]:
    """Three perpendicular-distance components from point to line.

    The line passes through line_origin along line_dir.
    Returns all 3 components of (point - line_origin) x line_dir,
    which are zero when the point lies on the line.  Only 2 of 3 are
    independent, but using all 3 avoids a singularity when the line
    direction aligns with a coordinate axis.
    """
    d = sub3(point, line_origin)
    cx, cy, cz = cross3(d, line_dir)
    return cx, cy, cz
