//! Tunable parameters for subdivision. (Spec §3.1.)
//!
//! `SubdivisionParams` is the single configuration object passed to
//! every public subdivision and edit operation. All distances are in
//! meters, areas in m², angles in radians.

use crate::error::SubdivisionError;

/// Parameters controlling frontage-first subdivision.
///
/// See spec §3 (algorithm) and §4 (regeneration thresholds).
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SubdivisionParams {
    /// Target frontage width `w_f`, meters.
    pub frontage_width: f64,
    /// Frontage width variance `sigma_f`, meters.
    pub frontage_variance: f64,
    /// Target parcel depth `d_p`, meters.
    pub depth: f64,
    /// Depth variance `sigma_d`, meters.
    pub depth_variance: f64,
    /// Setback from road edge into the block, `d_s` meters.
    pub setback: f64,
    /// Regularity slider `rho` in `[0, 1]`. 0 = raw, 1 = OBB-snapped.
    pub regularity: f64,
    /// Minimum frontage `w_min`, meters.
    pub min_frontage: f64,
    /// Minimum parcel area `A_min`, m².
    pub min_area: f64,
    /// Maximum side-edge rotation before a deformed parcel is
    /// regenerated, `alpha_max` radians. Default ~30°.
    pub max_side_rotation: f64,
    /// Deterministic RNG seed.
    pub seed: u64,
}

impl Default for SubdivisionParams {
    fn default() -> Self {
        Self {
            frontage_width: 20.0,
            frontage_variance: 3.0,
            depth: 30.0,
            depth_variance: 4.0,
            setback: 1.0,
            regularity: 0.0,
            min_frontage: 6.0,
            min_area: 60.0,
            max_side_rotation: 30.0_f64.to_radians(),
            seed: 0xC17_1E5_u64,
        }
    }
}

impl SubdivisionParams {
    /// Validate that every parameter is in a sane range. Called at the
    /// top of every public entry point.
    pub fn validate(&self) -> Result<(), SubdivisionError> {
        let bad = |s: &str| Err(SubdivisionError::InvalidParams(s.to_string()));
        if !self.frontage_width.is_finite() || self.frontage_width <= 0.0 {
            return bad("frontage_width must be finite and positive");
        }
        if !self.frontage_variance.is_finite() || self.frontage_variance < 0.0 {
            return bad("frontage_variance must be finite and >= 0");
        }
        if !self.depth.is_finite() || self.depth <= 0.0 {
            return bad("depth must be finite and positive");
        }
        if !self.depth_variance.is_finite() || self.depth_variance < 0.0 {
            return bad("depth_variance must be finite and >= 0");
        }
        if !self.setback.is_finite() || self.setback < 0.0 {
            return bad("setback must be finite and >= 0");
        }
        if !(0.0..=1.0).contains(&self.regularity) {
            return bad("regularity must be in [0, 1]");
        }
        if self.min_frontage <= 0.0 || self.min_frontage > self.frontage_width {
            return bad("min_frontage must be in (0, frontage_width]");
        }
        if self.min_area <= 0.0 {
            return bad("min_area must be positive");
        }
        if self.max_side_rotation <= 0.0 || self.max_side_rotation >= std::f64::consts::PI {
            return bad("max_side_rotation must be in (0, π)");
        }
        Ok(())
    }
}
