//! Block extraction from the half-edge graph (spec §2.4).
//!
//! A *block* is a bounded face of the planar road graph. Each block
//! carries its boundary polygon (CCW) plus, for every edge in the
//! ring, the [`RoadId`] of the road that edge runs along.

use glam::DVec2;

use crate::geometry::Polygon;
use crate::network::graph::{FaceId, HalfEdgeId, RoadGraph, RoadId};

/// A bounded face — the geometric block to be subdivided.
#[derive(Debug, Clone)]
pub struct Block {
    /// Stable face identifier within the graph.
    pub(crate) face: FaceId,
    /// Boundary polygon, CCW, with vertex \(i\) at the origin of
    /// `boundary_edges[i]`.
    pub polygon: Polygon,
    /// Half-edge cycle bounding this block, parallel to
    /// `polygon.vertices()`. Reserved for milestone 0.2's
    /// edit-preserves path.
    #[allow(dead_code)]
    pub(crate) boundary_edges: Vec<HalfEdgeId>,
    /// Road associated with each boundary edge, parallel to
    /// `polygon.vertices()`.
    pub roads: Vec<RoadId>,
}

impl Block {
    /// Vertex positions of the block boundary, CCW.
    #[must_use]
    pub fn boundary(&self) -> &[DVec2] {
        self.polygon.vertices()
    }

    /// Face id this block was extracted from.
    #[must_use]
    pub(crate) fn face_id(&self) -> FaceId {
        self.face
    }

    /// Half-edge cycle for this block. Reserved for milestone 0.2's
    /// edit-preserves path; currently exposed crate-internally so
    /// the edit pipeline can wire up to it.
    #[must_use]
    #[allow(dead_code)]
    pub(crate) fn boundary_edges(&self) -> &[HalfEdgeId] {
        &self.boundary_edges
    }
}

/// Extract every bounded face of `graph` as a [`Block`]. Caller must
/// ensure `rebuild_topology` has been run.
pub(crate) fn extract_blocks(graph: &RoadGraph) -> Vec<Block> {
    let mut out = Vec::new();
    for (face_id, face) in &graph.faces {
        if face.is_exterior {
            continue;
        }
        let mut edges = Vec::new();
        let mut verts = Vec::new();
        let mut roads = Vec::new();
        let start = face.boundary;
        let mut h = start;
        loop {
            edges.push(h);
            let he = graph.half_edges[h];
            verts.push(graph.nodes[he.origin].pos);
            roads.push(he.road);
            h = he.next;
            if h == start {
                break;
            }
        }
        // Block boundaries may legitimately contain collinear-corner
        // vertices (where two end-to-end roads share a node), so use
        // the relaxed validator here. Parcel polygons remain strict.
        let Ok(polygon) = Polygon::new_relaxed(verts) else {
            continue;
        };
        out.push(Block {
            face: face_id,
            polygon,
            boundary_edges: edges,
            roads,
        });
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn square_yields_one_block() {
        let mut g = RoadGraph::new();
        let a = g.add_node(DVec2::new(0.0, 0.0));
        let b = g.add_node(DVec2::new(100.0, 0.0));
        let c = g.add_node(DVec2::new(100.0, 50.0));
        let d = g.add_node(DVec2::new(0.0, 50.0));
        g.add_road(a, b).unwrap();
        g.add_road(b, c).unwrap();
        g.add_road(c, d).unwrap();
        g.add_road(d, a).unwrap();
        g.rebuild_topology().unwrap();
        let blocks = extract_blocks(&g);
        assert_eq!(blocks.len(), 1);
        assert_eq!(blocks[0].polygon.len(), 4);
        assert!((blocks[0].polygon.area() - 100.0 * 50.0).abs() < 1e-6);
    }
}
