"""Geometry protocol and utilities."""

from __future__ import annotations

from typing import Protocol, runtime_checkable

import numpy as np
from numpy.typing import NDArray


@runtime_checkable
class Geometry(Protocol):
    """Protocol for boundary geometry discretizations.

    Any object providing these attributes is compatible with panel-bie solvers.
    The `superellipse.PanelDiscretization` class implements this protocol.
    """

    @property
    def points(self) -> NDArray[np.floating]:
        """Boundary nodes, shape (N, 2)."""
        ...

    @property
    def normals(self) -> NDArray[np.floating]:
        """Outward unit normals, shape (N, 2)."""
        ...

    @property
    def weights(self) -> NDArray[np.floating]:
        """Quadrature weights (ds measure), shape (N,)."""
        ...

    @property
    def curvature(self) -> NDArray[np.floating]:
        """Signed curvature at each node, shape (N,)."""
        ...


def validate_geometry(geom: Geometry) -> None:
    """Validate geometry object has correct shapes and properties."""
    n = geom.points.shape[0]

    if geom.points.shape != (n, 2):
        raise ValueError(f"points must be (N, 2), got {geom.points.shape}")

    if geom.normals.shape != (n, 2):
        raise ValueError(f"normals must be (N, 2), got {geom.normals.shape}")

    if geom.weights.shape != (n,):
        raise ValueError(f"weights must be (N,), got {geom.weights.shape}")

    if geom.curvature.shape != (n,):
        raise ValueError(f"curvature must be (N,), got {geom.curvature.shape}")

    # Check normals are unit vectors
    norms = np.linalg.norm(geom.normals, axis=1)
    if not np.allclose(norms, 1.0, atol=1e-10):
        raise ValueError("normals must be unit vectors")

    # Check weights are positive
    if not np.all(geom.weights > 0):
        raise ValueError("weights must be positive")
