# ============================
# geometry.py — Geometric/Math Analysis Helpers
# ============================
from __future__ import annotations
import math
import numpy as np
from typing import Tuple, Optional

def normalize(v: np.ndarray) -> np.ndarray:
    """Returns the normalized vector v."""
    n = float(np.linalg.norm(v))
    return v / (n if n > 1e-12 else 1.0)

def compute_angle(p1: np.ndarray, p2: np.ndarray, p3: np.ndarray) -> float:
    """Calculates the angle (in degrees) defined by points p1-p2-p3."""
    v1 = p1 - p2
    v2 = p3 - p2
    norm1 = np.linalg.norm(v1)
    norm2 = np.linalg.norm(v2)
    if norm1 < 1e-6 or norm2 < 1e-6:
        return 0.0
    cosine = np.dot(v1, v2) / (norm1 * norm2)
    return float(np.degrees(np.arccos(np.clip(cosine, -1.0, 1.0))))

def compute_dihedral(p1: np.ndarray, p2: np.ndarray, p3: np.ndarray, p4: np.ndarray) -> float:
    """Calculates the dihedral angle (in degrees) defined by points p1-p2-p3-p4."""
    b1 = p2 - p1
    b2 = p3 - p2
    b3 = p4 - p3
    
    # Normalize b2
    norm_b2 = np.linalg.norm(b2)
    if norm_b2 > 1e-12:
        b2 /= norm_b2
    
    # Vector rejections
    # v = b1 - <b1, b2>*b2
    v = b1 - np.dot(b1, b2) * b2
    w = b3 - np.dot(b3, b2) * b2
    
    x = np.dot(v, w)
    y = np.dot(np.cross(b2, v), w)
    
    return float(np.degrees(np.arctan2(y, x)))

def compute_bounds(points: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """Returns the AABB (min, max) of the point cloud."""
    return points.min(axis=0), points.max(axis=0)

def cell_corners(lattice: np.ndarray) -> np.ndarray:
    """Generates the 8 corners of a triclinic unit cell matrix."""
    a, b, c = lattice[0], lattice[1], lattice[2]
    O = np.zeros(3, dtype=np.float32)
    # Corners: 0:O, 1:a, 2:b, 3:c, 4:a+b, 5:a+c, 6:b+c, 7:a+b+c
    return np.array([O, a, b, c, a+b, a+c, b+c, a+b+c], dtype=np.float32)

CELL_EDGES = np.array([
    [0, 1], [0, 2], [0, 3], # Origin to Axes
    [1, 4], [1, 5],         # From a
    [2, 4], [2, 6],         # From b
    [3, 5], [3, 6],         # From c
    [4, 7], [5, 7], [6, 7]  # To Far Corner
], dtype=np.int32)

def compute_rmsd(pos: np.ndarray, pos_ref: np.ndarray) -> float:
    """Calculates Root Mean Square Deviation (RMSD) between two structures."""
    if pos.shape != pos_ref.shape: return 0.0
    diff = pos - pos_ref
    return float(np.sqrt(np.mean(np.sum(diff**2, axis=1))))
