"""Configuration classes for gtrack package."""

from dataclasses import dataclass
from typing import Optional
import numpy as np


@dataclass
class TracerConfig:
    """
    Configuration parameters for seafloor age tracking.

    Parameters match GPlately's SeafloorGrid for compatibility.

    Attributes
    ----------
    time_step : float
        Time step size in Myr (default: 1.0)
    earth_radius : float
        Earth's radius in meters (default: 6.3781e6)

    Collision Detection (C++ backend)
    ----------------------------------
    velocity_delta_threshold : float
        Minimum velocity difference to trigger collision check, in km/Myr.
        Converted to cm/yr (divide by 10) for pygplates C++ API.
        Default: 7.0 km/Myr (= 0.7 cm/yr)
    distance_threshold_per_myr : float
        Base distance threshold for collision detection, in km/Myr.
        Default: 10.0 km/Myr

    Initialization
    --------------
    default_mesh_points : int
        Number of points for the initial sphere mesh.
        Default: 10000
    initial_ocean_mean_spreading_rate : float
        Mean spreading rate in mm/yr for initial age calculation.
        Used to compute age = distance / (rate / 2).
        Default: 75.0 mm/yr (GPlately default)

    MOR Seed Generation
    -------------------
    ridge_sampling_degrees : float
        Ridge tessellation resolution in degrees (~50 km at equator).
        Default: 0.5 degrees
    spreading_offset_degrees : float
        Angle to rotate new points off ridge, in degrees.
        Default: 0.01 degrees (~1 km)

    Continental Handling
    --------------------
    continental_reconstruction_interval : int
        Interval in Myr between continental polygon reconstructions.
        Continental polygons are reconstructed once per interval and reused
        for all timesteps within that interval. E.g. interval=5 means
        polygons are reconstructed at 200, 195, 190, ... Ma.
        Default: 5

    Reinitialization
    ----------------
    reinit_k_neighbors : int
        Number of nearest neighbors for interpolation during reinitialization.
        Default: 6
    reinit_max_distance : float
        Maximum distance (meters) for valid interpolation neighbors.
        Default: None (pi * earth_radius, i.e. half the circumference)

    Examples
    --------
    >>> # Use default configuration (GPlately-compatible)
    >>> config = TracerConfig()
    >>>
    >>> # Custom configuration with higher resolution
    >>> config = TracerConfig(
    ...     default_mesh_points=40000,    # Higher resolution mesh
    ...     ridge_sampling_degrees=0.25,  # ~25 km resolution
    ...     time_step=0.5                 # 0.5 Myr timesteps
    ... )
    """

    # Time stepping
    time_step: float = 1.0  # Myr
    earth_radius: float = 6.3781e6  # meters

    # Collision detection (C++ backend - GPlately compatible)
    # These are passed to pygplates.ReconstructedGeometryTimeSpan.DefaultDeactivatePoints
    velocity_delta_threshold: float = 7.0  # km/Myr (converted to 0.7 cm/yr for API)
    distance_threshold_per_myr: float = 10.0  # km/Myr

    # Initialization - sphere mesh
    default_mesh_points: int = 10000
    initial_ocean_mean_spreading_rate: float = 75.0  # mm/yr (GPlately default)

    # MOR seed generation
    ridge_sampling_degrees: float = 0.5  # ~50 km at equator
    spreading_offset_degrees: float = 0.01  # ~1 km offset from ridge

    # Continental polygon reconstruction
    continental_reconstruction_interval: int = 5  # Myr between reconstructions

    # Reinitialization parameters
    reinit_k_neighbors: int = 6
    reinit_max_distance: Optional[float] = None  # defaults to pi * earth_radius in __post_init__

    # Garbage collection
    gc_collect_frequency: Optional[int] = 10  # call gc.collect() every N internal steps; None disables

    def __post_init__(self):
        """Validate configuration parameters."""
        if self.time_step <= 0:
            raise ValueError(f"time_step must be positive, got {self.time_step}")
        if self.earth_radius <= 0:
            raise ValueError(f"earth_radius must be positive, got {self.earth_radius}")
        if self.velocity_delta_threshold < 0:
            raise ValueError(
                f"velocity_delta_threshold must be non-negative, "
                f"got {self.velocity_delta_threshold}"
            )
        if self.distance_threshold_per_myr < 0:
            raise ValueError(
                f"distance_threshold_per_myr must be non-negative, "
                f"got {self.distance_threshold_per_myr}"
            )
        if self.default_mesh_points < 1:
            raise ValueError(
                f"default_mesh_points must be at least 1, "
                f"got {self.default_mesh_points}"
            )
        if self.initial_ocean_mean_spreading_rate <= 0:
            raise ValueError(
                f"initial_ocean_mean_spreading_rate must be positive, "
                f"got {self.initial_ocean_mean_spreading_rate}"
            )
        if self.ridge_sampling_degrees <= 0:
            raise ValueError(
                f"ridge_sampling_degrees must be positive, "
                f"got {self.ridge_sampling_degrees}"
            )
        if self.spreading_offset_degrees <= 0:
            raise ValueError(
                f"spreading_offset_degrees must be positive, "
                f"got {self.spreading_offset_degrees}"
            )
        if self.continental_reconstruction_interval < 1:
            raise ValueError(
                f"continental_reconstruction_interval must be at least 1, "
                f"got {self.continental_reconstruction_interval}"
            )
        if self.reinit_k_neighbors < 1:
            raise ValueError(
                f"reinit_k_neighbors must be at least 1, "
                f"got {self.reinit_k_neighbors}"
            )
        if self.reinit_max_distance is None:
            self.reinit_max_distance = np.pi * self.earth_radius
        if self.reinit_max_distance <= 0:
            raise ValueError(
                f"reinit_max_distance must be positive, "
                f"got {self.reinit_max_distance}"
            )
        if self.gc_collect_frequency is not None and self.gc_collect_frequency < 1:
            raise ValueError(
                f"gc_collect_frequency must be >= 1 or None, "
                f"got {self.gc_collect_frequency}"
            )

    @property
    def velocity_delta_threshold_cm_yr(self) -> float:
        """
        Velocity threshold in cm/yr for pygplates C++ API.

        The C++ API expects cm/yr, while we store km/Myr for readability.
        Conversion: 1 km/Myr = 0.1 cm/yr
        """
        return self.velocity_delta_threshold / 10.0

    def to_dict(self) -> dict:
        """
        Convert configuration to dictionary.

        Returns
        -------
        dict
            Configuration as dictionary
        """
        return {
            'time_step': self.time_step,
            'earth_radius': self.earth_radius,
            'velocity_delta_threshold': self.velocity_delta_threshold,
            'distance_threshold_per_myr': self.distance_threshold_per_myr,
            'default_mesh_points': self.default_mesh_points,
            'initial_ocean_mean_spreading_rate': self.initial_ocean_mean_spreading_rate,
            'ridge_sampling_degrees': self.ridge_sampling_degrees,
            'spreading_offset_degrees': self.spreading_offset_degrees,
            'continental_reconstruction_interval': self.continental_reconstruction_interval,
            'reinit_k_neighbors': self.reinit_k_neighbors,
            'reinit_max_distance': self.reinit_max_distance,
            'gc_collect_frequency': self.gc_collect_frequency,
        }

    @classmethod
    def from_dict(cls, config_dict: dict):
        """
        Create configuration from dictionary.

        Parameters
        ----------
        config_dict : dict
            Dictionary with configuration parameters

        Returns
        -------
        TracerConfig
            Configuration object
        """
        return cls(**config_dict)
