//! OBB-snapping regularization pass (spec §3.3).
//!
//! When `params.regularity > 0`, parcel side-edge vertices are
//! linearly interpolated toward their oriented-bounding-box-snapped
//! positions with weight `ρ`. At ρ=1 parcels become perfect
//! road-aligned rectangles; at ρ=0 the raw subdivision is preserved.
//!
//! ## How the snap is computed
//!
//! Let the parcel's frontage edge be `(p_a, p_b)`. Define the
//! oriented coordinate frame
//! - axis 1 = `(p_b - p_a) / |p_b - p_a|` (unit tangent along the
//!   frontage),
//! - axis 2 = the inward perpendicular (rotated 90° CCW into the
//!   parcel interior).
//!
//! Project every parcel vertex onto these axes; the maximum
//! projection onto axis 2 is the parcel's "depth" in this frame
//! (call it `d_*`). The OBB is the rectangle with corners
//! `{p_a, p_b, p_b + d_* · axis_2, p_a + d_* · axis_2}`.
//!
//! For each non-frontage vertex `v`, let
//! `(t_along, t_perp) = ((v - p_a) · axis_1, (v - p_a) · axis_2)`.
//! The OBB-snapped target is
//! `p_a + clamp(t_along, 0, |p_b - p_a|) · axis_1 + d_* · axis_2`,
//! which is the closest point on the OBB's *back* edge along axis 2.
//! Side vertices snap to the OBB corners (clamp pulls `t_along` to
//! `0` or `|p_b - p_a|` for vertices that fell outside the
//! frontage's extent).
//!
//! Each non-frontage vertex is moved to `lerp(v, snapped, ρ)`. At
//! ρ = 1 the parcel becomes the OBB exactly (drop redundant
//! collinear vertices). At ρ = 0 nothing moves. Intermediate ρ
//! interpolates.
//!
//! Validation: the resulting polygon must satisfy I1 (relaxed for
//! collinear-triple tolerance because OBB collapse can create
//! them). On any failure the parcel is left untouched.

use glam::DVec2;

use crate::config::SubdivisionParams;
use crate::geometry::{Polygon, EPS_GEOM};
use crate::parcel::classify::classify_edges;
use crate::parcel::Parcel;

const EPS_RHO: f64 = 1.0e-9;

pub(crate) fn regularize_parcel(parcel: &mut Parcel, params: &SubdivisionParams) {
    let rho = params.regularity;
    if !(EPS_RHO..=1.0 + EPS_RHO).contains(&rho) {
        return;
    }
    if rho < EPS_RHO {
        return;
    }
    let v = parcel.polygon.vertices().to_vec();
    let n = v.len();
    if n < 4 {
        // Triangles are already simple; nothing meaningful to regularize.
        return;
    }
    let fi = parcel.frontage_edge_index;
    let p_a = v[fi];
    let p_b = v[(fi + 1) % n];
    let frontage_vec = p_b - p_a;
    let frontage_len = frontage_vec.length();
    if frontage_len < EPS_GEOM {
        return;
    }
    let axis1 = frontage_vec / frontage_len;
    let axis2 = DVec2::new(-axis1.y, axis1.x);

    // Compute parcel "depth" in the oriented frame: maximum projection
    // onto axis2.
    let mut max_perp = 0.0_f64;
    for vert in &v {
        let d = (*vert - p_a).dot(axis2);
        if d > max_perp {
            max_perp = d;
        }
    }
    if max_perp < EPS_GEOM {
        return;
    }

    // ρ very close to 1: snap fully to the OBB rectangle.
    if (rho - 1.0).abs() < EPS_RHO {
        let new_verts = vec![p_a, p_b, p_b + axis2 * max_perp, p_a + axis2 * max_perp];
        if let Ok(new_poly) = Polygon::new(new_verts) {
            parcel.polygon = new_poly;
            parcel.frontage_edge_index = 0;
            parcel.edge_kinds = classify_edges(4, 0);
        }
        return;
    }

    // 0 < ρ < 1: lerp each non-frontage vertex toward its OBB-snap target.
    let mut new_verts = v.clone();
    for (i, vert) in v.iter().enumerate() {
        if i == fi || i == (fi + 1) % n {
            continue;
        }
        let t_along = (*vert - p_a).dot(axis1).clamp(0.0, frontage_len);
        let snapped = p_a + axis1 * t_along + axis2 * max_perp;
        new_verts[i] = vert.lerp(snapped, rho);
    }
    if let Ok(new_poly) = Polygon::new(new_verts) {
        parcel.polygon = new_poly;
    }
}
