use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::f64::consts::PI;

use crate::error::LayoutError;
use crate::layering::compute_hierarchy_levels;
use crate::model::{
    EdgeRoute, EdgeRouteKind, Graph, LayoutArtifacts, LayoutConfig, Point, RingDistribution,
    RoutedNode,
};

const MIN_DIST: i32 = 1;
const MAX_SIFTING_ROUNDS: usize = 10;
const INTRA_LEVEL_ALPHA: f64 = 10.0;
const INTRA_LEVEL_SEGMENT_ANGLE: f64 = PI / 18.0;
const ARC_SAMPLE_STEP: f64 = PI / 36.0;
const QUADRATIC_SAMPLES: usize = 12;
const EPSILON: f64 = 1e-9;
const ANGLE_EPSILON: f64 = 1e-6;

pub fn layout_radial_hierarchy(graph: &mut Graph, config: LayoutConfig) -> Result<(), LayoutError> {
    layout_radial_hierarchy_with_artifacts(graph, config).map(|_| ())
}

pub fn layout_radial_hierarchy_with_artifacts(
    graph: &mut Graph,
    config: LayoutConfig,
) -> Result<LayoutArtifacts, LayoutError> {
    if graph.nodes.is_empty() {
        return Ok(LayoutArtifacts {
            node_levels: Vec::new(),
            edge_offsets: Vec::new(),
            edge_routes: Vec::new(),
            routed_nodes: Vec::new(),
            center: Point::new(config.left_border, config.upper_border),
        });
    }

    let levels = compute_hierarchy_levels(graph)?;
    let (working_graph, dummies, order) = build_working_graph(graph, &levels)?;
    let (order, offset) = CrossingReduction::new(&working_graph, order).minimize_crossings();
    let coordinates = CoordinateAssignment::new(&working_graph, &order, &dummies).get_coordinates();
    let projection = project_radial(&working_graph, &order, &coordinates, config);

    for (work_node_index, work_node) in working_graph.nodes.iter().enumerate() {
        if let WorkNodeKind::Original(original_index) = work_node.kind {
            let point = projection.points[work_node_index];
            graph.nodes[original_index].x = point.x;
            graph.nodes[original_index].y = point.y;
        }
    }

    Ok(build_layout_artifacts(
        &working_graph,
        &order,
        &offset,
        &projection,
        &levels,
        config,
    ))
}

#[derive(Debug, Clone)]
struct WorkGraph {
    nodes: Vec<WorkNode>,
    edges: Vec<WorkEdge>,
    incident_edges: Vec<Vec<usize>>,
    edge_lookup: HashMap<(usize, usize), usize>,
}

impl WorkGraph {
    fn new(original_count: usize) -> Self {
        let nodes = (0..original_count)
            .map(|node_index| WorkNode {
                kind: WorkNodeKind::Original(node_index),
            })
            .collect();

        Self {
            nodes,
            edges: Vec::new(),
            incident_edges: vec![Vec::new(); original_count],
            edge_lookup: HashMap::new(),
        }
    }

    fn add_dummy(&mut self) -> usize {
        let node_index = self.nodes.len();
        self.nodes.push(WorkNode {
            kind: WorkNodeKind::Dummy,
        });
        self.incident_edges.push(Vec::new());
        node_index
    }

    fn add_edge(
        &mut self,
        source: usize,
        target: usize,
        original_edge: usize,
    ) -> Result<usize, LayoutError> {
        let key = canonical_pair(source, target);
        if self.edge_lookup.contains_key(&key) {
            return Err(LayoutError::DuplicateEdge {
                edge_index: original_edge,
                source,
                target,
            });
        }

        let edge_index = self.edges.len();
        self.edges.push(WorkEdge {
            source,
            target,
            original_edge,
        });
        self.edge_lookup.insert(key, edge_index);
        self.incident_edges[source].push(edge_index);
        self.incident_edges[target].push(edge_index);
        Ok(edge_index)
    }

    fn node_count(&self) -> usize {
        self.nodes.len()
    }

    fn edge_between(&self, left: usize, right: usize) -> Option<usize> {
        self.edge_lookup.get(&canonical_pair(left, right)).copied()
    }

    fn edges_to_node_from_level(&self, level_nodes: &[usize], node: usize) -> Vec<usize> {
        let mut edges = Vec::new();
        for &other in level_nodes {
            if let Some(edge_index) = self.edge_between(other, node) {
                edges.push(edge_index);
            }
        }
        edges
    }

    fn vertical_edges(&self, level1: &[usize], level2: &[usize]) -> Vec<usize> {
        let mut edges = Vec::new();
        for &lower in level2 {
            for &upper in level1 {
                if let Some(edge_index) = self.edge_between(upper, lower) {
                    edges.push(edge_index);
                }
            }
        }
        edges
    }

    fn horizontal_edges(&self, level: &[usize]) -> Vec<usize> {
        let mut edges = Vec::new();
        for &left in level {
            for &right in level {
                if let Some(edge_index) = self.edge_between(left, right) {
                    if !edges.contains(&edge_index) {
                        edges.push(edge_index);
                    }
                }
            }
        }
        edges
    }

    fn adjacent_node(&self, edge_index: usize, node: usize) -> Option<usize> {
        let edge = self.edges[edge_index];
        if edge.source == node {
            Some(edge.target)
        } else if edge.target == node {
            Some(edge.source)
        } else {
            None
        }
    }

    fn connects_nodes(&self, edge_index: usize, left: usize, right: usize) -> bool {
        let edge = self.edges[edge_index];
        (edge.source == left && edge.target == right)
            || (edge.source == right && edge.target == left)
    }
}

#[derive(Debug, Clone)]
struct WorkNode {
    kind: WorkNodeKind,
}

#[derive(Debug, Clone, Copy)]
enum WorkNodeKind {
    Original(usize),
    Dummy,
}

#[derive(Debug, Clone, Copy)]
struct WorkEdge {
    source: usize,
    target: usize,
    original_edge: usize,
}

#[derive(Debug, Clone)]
struct ProjectionResult {
    points: Vec<Point>,
    center: Point,
    node_levels: Vec<usize>,
}

fn build_working_graph(
    graph: &Graph,
    levels: &[usize],
) -> Result<(WorkGraph, HashSet<usize>, Vec<Vec<usize>>), LayoutError> {
    let level_count = levels
        .iter()
        .copied()
        .max()
        .map_or(0, |max_level| max_level + 1);
    let mut order = vec![Vec::new(); level_count];
    let mut working_graph = WorkGraph::new(graph.nodes.len());
    let mut dummies = HashSet::new();

    for (node_index, &level) in levels.iter().enumerate() {
        order[level].push(node_index);
    }

    for (edge_index, edge) in graph.edges.iter().enumerate() {
        let source_level = levels[edge.source];
        let target_level = levels[edge.target];
        if target_level < source_level {
            return Err(LayoutError::InvalidHierarchyEdge {
                edge_index,
                source: edge.source,
                target: edge.target,
                source_level,
                target_level,
            });
        }

        if target_level == source_level {
            working_graph.add_edge(edge.source, edge.target, edge_index)?;
            continue;
        }

        let mut current = edge.source;
        for level in (source_level + 1)..target_level {
            let dummy = working_graph.add_dummy();
            order[level].push(dummy);
            dummies.insert(dummy);
            working_graph.add_edge(current, dummy, edge_index)?;
            current = dummy;
        }

        working_graph.add_edge(current, edge.target, edge_index)?;
    }

    Ok((working_graph, dummies, order))
}

fn project_radial(
    working_graph: &WorkGraph,
    order: &[Vec<usize>],
    coordinates: &[i32],
    config: LayoutConfig,
) -> ProjectionResult {
    if order.is_empty() {
        return ProjectionResult {
            points: Vec::new(),
            center: Point::new(config.left_border, config.upper_border),
            node_levels: Vec::new(),
        };
    }

    let mut node_levels = vec![0usize; working_graph.node_count()];
    for (level_index, level) in order.iter().enumerate() {
        for &node in level {
            node_levels[node] = level_index;
        }
    }

    let mut z = 0i32;
    for level in order {
        if level.is_empty() {
            continue;
        }
        let min = level
            .iter()
            .map(|&node| coordinates[node])
            .min()
            .unwrap_or(0);
        let max = level
            .iter()
            .map(|&node| coordinates[node])
            .max()
            .unwrap_or(0);
        z = z.max(max - min + 1);
    }
    if z <= 0 {
        z = 1;
    }

    let mut raw_x = vec![0.0; working_graph.node_count()];
    let mut raw_y = vec![0.0; working_graph.node_count()];

    let start = if order.first().map_or(false, |level| level.len() == 1) {
        1
    } else {
        0
    };

    let mut radius = config.min_radius;
    let mut adaptive_min_gap = None;
    let mut node_angles = vec![None; working_graph.node_count()];
    let mut previous_level = if start > 0 {
        Some(order[start - 1].as_slice())
    } else {
        None
    };

    if start == 1 {
        node_angles[order[0][0]] = Some(0.0);
    }

    for level in order.iter().skip(start) {
        let (angles, next_min_gap) = project_level_angles(
            working_graph,
            level,
            previous_level,
            &node_angles,
            coordinates,
            z,
            config.ring_distribution,
            adaptive_min_gap,
        );
        adaptive_min_gap = next_min_gap;

        for (&node, angle) in level.iter().zip(angles.into_iter()) {
            node_angles[node] = Some(angle);
            raw_x[node] = radius * angle.cos();
            raw_y[node] = radius * angle.sin();
        }
        previous_level = Some(level.as_slice());
        radius += config.level_distance;
    }

    if order[0].len() == 1 {
        let center_node = order[0][0];
        raw_x[center_node] = 0.0;
        raw_y[center_node] = 0.0;
    }

    let (shift_x, shift_y, center) = if config.align_positive_coords {
        let min_x = raw_x.iter().copied().fold(f64::INFINITY, f64::min);
        let min_y = raw_y.iter().copied().fold(f64::INFINITY, f64::min);
        (
            -min_x,
            -min_y,
            Point::new(
                config.left_border - min_x * config.node_distance,
                config.upper_border - min_y * config.node_distance,
            ),
        )
    } else {
        (
            0.0,
            0.0,
            Point::new(config.left_border, config.upper_border),
        )
    };

    let mut points = vec![Point::new(0.0, 0.0); working_graph.node_count()];
    for node in 0..working_graph.node_count() {
        points[node] = Point::new(
            config.left_border + (raw_x[node] + shift_x) * config.node_distance,
            config.upper_border + (raw_y[node] + shift_y) * config.node_distance,
        );
    }

    ProjectionResult {
        points,
        center,
        node_levels,
    }
}

fn project_level_angles(
    working_graph: &WorkGraph,
    level: &[usize],
    previous_level: Option<&[usize]>,
    node_angles: &[Option<f64>],
    coordinates: &[i32],
    z: i32,
    distribution: RingDistribution,
    previous_min_gap: Option<f64>,
) -> (Vec<f64>, Option<f64>) {
    match distribution {
        RingDistribution::Packed => (
            level
                .iter()
                .map(|&node| packed_angle(z, coordinates[node]))
                .collect(),
            previous_min_gap,
        ),
        RingDistribution::Distributed => (
            (0..level.len())
                .map(|index| distributed_angle(level.len(), index))
                .collect(),
            previous_min_gap,
        ),
        RingDistribution::Adaptive => adaptive_level_angles(
            working_graph,
            level,
            previous_level,
            node_angles,
            coordinates,
            z,
            previous_min_gap,
        ),
    }
}

fn adaptive_level_angles(
    working_graph: &WorkGraph,
    level: &[usize],
    previous_level: Option<&[usize]>,
    node_angles: &[Option<f64>],
    coordinates: &[i32],
    z: i32,
    previous_min_gap: Option<f64>,
) -> (Vec<f64>, Option<f64>) {
    if level.is_empty() {
        return (Vec::new(), previous_min_gap);
    }

    if previous_min_gap.is_none() {
        let angles = (0..level.len())
            .map(|index| distributed_angle(level.len(), index))
            .collect::<Vec<_>>();
        return (
            angles.clone(),
            Some(positive_gap(min_circular_gap(&angles))),
        );
    }

    let preferred_angles = preferred_level_angles(
        working_graph,
        level,
        previous_level,
        node_angles,
        coordinates,
        z,
    );

    if level.len() == 1 {
        let angle = normalize_angle(preferred_angles[0]);
        let next_min_gap = Some(positive_gap(previous_min_gap.unwrap_or(2.0 * PI)));
        return (vec![angle], next_min_gap);
    }

    let minimum_gap = positive_gap(
        previous_min_gap
            .unwrap_or(2.0 * PI)
            .min(2.0 * PI / level.len() as f64),
    );
    let normalized_angles = solve_cyclic_adaptive_angles(&preferred_angles, minimum_gap);
    let next_min_gap = Some(positive_gap(
        previous_min_gap
            .unwrap_or(2.0 * PI)
            .min(min_circular_gap(&normalized_angles)),
    ));

    (normalized_angles, next_min_gap)
}

fn solve_cyclic_adaptive_angles(preferred_angles: &[f64], minimum_gap: f64) -> Vec<f64> {
    debug_assert!(preferred_angles.len() >= 2);

    let mut best_solution = Vec::new();
    let mut best_error = f64::INFINITY;
    let mut best_cut = usize::MAX;

    for cut in 0..preferred_angles.len() {
        let rotated_preferred = rotate_values(preferred_angles, cut);
        let unwrapped_preferred = unwrap_angles(&rotated_preferred);
        let placed_unwrapped = solve_unwrapped_level_placement(&unwrapped_preferred, minimum_gap);
        let error = squared_error(&placed_unwrapped, &unwrapped_preferred);

        if error < best_error - EPSILON || (approx_eq(error, best_error) && cut < best_cut) {
            best_error = error;
            best_cut = cut;
            best_solution = inverse_rotate_values(
                &placed_unwrapped
                    .into_iter()
                    .map(normalize_angle)
                    .collect::<Vec<_>>(),
                cut,
            );
        }
    }

    best_solution
}

fn solve_unwrapped_level_placement(unwrapped_preferred: &[f64], minimum_gap: f64) -> Vec<f64> {
    let count = unwrapped_preferred.len();
    let slack = (2.0 * PI - count as f64 * minimum_gap).max(0.0);
    let targets = unwrapped_preferred
        .iter()
        .enumerate()
        .map(|(index, angle)| angle - index as f64 * minimum_gap)
        .collect::<Vec<_>>();
    let free_positions = bounded_isotonic_regression(&targets, 0.0, slack);
    let relative_positions = free_positions
        .iter()
        .enumerate()
        .map(|(index, position)| position + index as f64 * minimum_gap)
        .collect::<Vec<_>>();
    let shift = unwrapped_preferred
        .iter()
        .zip(&relative_positions)
        .map(|(preferred, placed)| preferred - placed)
        .sum::<f64>()
        / count as f64;

    relative_positions
        .into_iter()
        .map(|position| position + shift)
        .collect()
}

fn positive_gap(gap: f64) -> f64 {
    gap.max(ANGLE_EPSILON)
}

fn rotate_values(values: &[f64], start: usize) -> Vec<f64> {
    values[start..]
        .iter()
        .chain(values[..start].iter())
        .copied()
        .collect()
}

fn inverse_rotate_values(values: &[f64], start: usize) -> Vec<f64> {
    let mut restored = vec![0.0; values.len()];
    for (index, value) in values.iter().copied().enumerate() {
        restored[(start + index) % values.len()] = value;
    }
    restored
}

fn squared_error(left: &[f64], right: &[f64]) -> f64 {
    left.iter()
        .zip(right)
        .map(|(left, right)| (left - right).powi(2))
        .sum()
}

fn bounded_isotonic_regression(values: &[f64], lower: f64, upper: f64) -> Vec<f64> {
    if values.is_empty() {
        return Vec::new();
    }

    let mut result = vec![0.0; values.len()];
    let mut start = 0usize;
    let mut end = values.len();

    while start < end {
        let regression = isotonic_regression(&values[start..end]);
        if let Some(last_below) = regression
            .iter()
            .rposition(|value| *value < lower - EPSILON)
        {
            result[start..=start + last_below].fill(lower);
            start += last_below + 1;
            continue;
        }

        if let Some(first_above) = regression.iter().position(|value| *value > upper + EPSILON) {
            result[start + first_above..end].fill(upper);
            end = start + first_above;
            continue;
        }

        result[start..end].copy_from_slice(&regression);
        return result;
    }

    result
}

fn isotonic_regression(values: &[f64]) -> Vec<f64> {
    #[derive(Clone, Copy)]
    struct Block {
        start: usize,
        end: usize,
        mean: f64,
        count: usize,
    }

    let mut blocks = Vec::<Block>::new();
    for (index, value) in values.iter().copied().enumerate() {
        blocks.push(Block {
            start: index,
            end: index + 1,
            mean: value,
            count: 1,
        });

        while blocks.len() >= 2 {
            let right = blocks[blocks.len() - 1];
            let left = blocks[blocks.len() - 2];
            if left.mean <= right.mean + EPSILON {
                break;
            }

            let merged_count = left.count + right.count;
            let merged_mean = (left.mean * left.count as f64 + right.mean * right.count as f64)
                / merged_count as f64;
            blocks.pop();
            blocks.pop();
            blocks.push(Block {
                start: left.start,
                end: right.end,
                mean: merged_mean,
                count: merged_count,
            });
        }
    }

    let mut result = vec![0.0; values.len()];
    for block in blocks {
        result[block.start..block.end].fill(block.mean);
    }
    result
}

fn preferred_level_angles(
    working_graph: &WorkGraph,
    level: &[usize],
    previous_level: Option<&[usize]>,
    node_angles: &[Option<f64>],
    coordinates: &[i32],
    z: i32,
) -> Vec<f64> {
    level
        .iter()
        .map(|&node| {
            preferred_angle_for_node(
                working_graph,
                previous_level,
                node_angles,
                node,
                coordinates[node],
                z,
            )
        })
        .collect()
}

fn preferred_angle_for_node(
    working_graph: &WorkGraph,
    previous_level: Option<&[usize]>,
    node_angles: &[Option<f64>],
    node: usize,
    coordinate: i32,
    z: i32,
) -> f64 {
    let Some(previous_level) = previous_level else {
        return packed_angle(z, coordinate);
    };

    let parent_angles = working_graph
        .edges_to_node_from_level(previous_level, node)
        .into_iter()
        .filter_map(|edge_index| {
            working_graph
                .adjacent_node(edge_index, node)
                .and_then(|adjacent| node_angles[adjacent])
        })
        .collect::<Vec<_>>();

    if parent_angles.is_empty() {
        packed_angle(z, coordinate)
    } else {
        circular_mean_angle(&parent_angles)
    }
}

fn build_layout_artifacts(
    working_graph: &WorkGraph,
    order: &[Vec<usize>],
    offsets: &[i32],
    projection: &ProjectionResult,
    original_levels: &[usize],
    config: LayoutConfig,
) -> LayoutArtifacts {
    let routed_nodes = working_graph
        .nodes
        .iter()
        .enumerate()
        .map(|(node_index, node)| RoutedNode {
            original_index: match node.kind {
                WorkNodeKind::Original(original_index) => Some(original_index),
                WorkNodeKind::Dummy => None,
            },
            level: projection.node_levels[node_index],
            point: projection.points[node_index],
        })
        .collect::<Vec<_>>();

    let edge_routes = working_graph
        .edges
        .iter()
        .enumerate()
        .map(|(edge_index, edge)| {
            let kind = classify_route(edge, &projection.node_levels, order);
            let points = build_route_points(
                edge,
                kind,
                offsets[edge_index],
                &projection.points,
                &projection.node_levels,
                projection.center,
                config,
                order,
            );

            EdgeRoute {
                original_edge_index: edge.original_edge,
                source: edge.source,
                target: edge.target,
                kind,
                points,
            }
        })
        .collect();

    LayoutArtifacts {
        node_levels: original_levels.to_vec(),
        edge_offsets: offsets.to_vec(),
        edge_routes,
        routed_nodes,
        center: projection.center,
    }
}

fn classify_route(edge: &WorkEdge, node_levels: &[usize], order: &[Vec<usize>]) -> EdgeRouteKind {
    let source_level = node_levels[edge.source];
    let target_level = node_levels[edge.target];

    if order.first().map_or(false, |level| level.len() == 1)
        && (source_level == 0 || target_level == 0)
    {
        EdgeRouteKind::Straight
    } else if source_level != target_level {
        EdgeRouteKind::Spiral
    } else if source_level == 0 {
        EdgeRouteKind::Straight
    } else {
        EdgeRouteKind::IntraLevel
    }
}

fn build_route_points(
    edge: &WorkEdge,
    kind: EdgeRouteKind,
    offset: i32,
    points: &[Point],
    node_levels: &[usize],
    center: Point,
    config: LayoutConfig,
    order: &[Vec<usize>],
) -> Vec<Point> {
    match kind {
        EdgeRouteKind::Straight => vec![points[edge.source], points[edge.target]],
        EdgeRouteKind::Spiral => build_spiral_route(
            edge,
            points,
            node_levels,
            center,
            offset,
            config.spiral_quality.max(1),
        ),
        EdgeRouteKind::IntraLevel => build_intra_level_route(
            points[edge.source],
            points[edge.target],
            center,
            config.level_distance,
            config.node_distance,
            order.first().map_or(0, Vec::len),
        ),
    }
}

fn build_spiral_route(
    edge: &WorkEdge,
    points: &[Point],
    node_levels: &[usize],
    center: Point,
    offset: i32,
    quality: usize,
) -> Vec<Point> {
    let source_level = node_levels[edge.source];
    let target_level = node_levels[edge.target];

    let (start_node, end_node, reverse_output) = if source_level <= target_level {
        (edge.source, edge.target, false)
    } else {
        (edge.target, edge.source, true)
    };

    let start = points[start_node];
    let end = points[end_node];
    let start_radius = distance(start, center);
    let end_radius = distance(end, center);
    let start_angle = polar_angle(start, center);
    let end_angle = effective_spiral_end_angle(start_angle, polar_angle(end, center), offset);

    if approx_eq(start_angle, end_angle) {
        let mut straight = vec![start, end];
        if reverse_output {
            straight.reverse();
        }
        return straight;
    }

    let outer_radius = start_radius.max(end_radius);
    let flatness = outer_radius / quality as f64;
    let angle_direction = if start_angle < end_angle { 1.0 } else { -1.0 };
    let mut angle = start_angle;
    let mut radius = start_radius;
    let mut route = vec![start];

    loop {
        route.push(Point::new(
            center.x + radius * angle.cos(),
            center.y - radius * angle.sin(),
        ));

        let x = if radius.abs() < EPSILON {
            f64::INFINITY
        } else {
            flatness / radius
        };
        if x.is_nan() || x > 0.1 {
            angle += PI / 4.0 * angle_direction;
        } else {
            let y = (2.0 * x * x - 4.0 * x + 1.0).clamp(-1.0, 1.0);
            angle += y.acos() * angle_direction;
        }

        if (angle - end_angle) * angle_direction > 0.0 {
            break;
        }

        let denominator = end_angle - start_angle;
        let fraction_complete = if denominator.abs() < EPSILON {
            1.0
        } else {
            (angle - start_angle) / denominator
        };
        radius = start_radius + (end_radius - start_radius) * fraction_complete;
    }

    route.push(end);
    dedup_points(&mut route);

    if reverse_output {
        route.reverse();
    }

    route
}

fn effective_spiral_end_angle(start_angle: f64, raw_end_angle: f64, offset: i32) -> f64 {
    let original_end_angle = raw_end_angle + (-(offset as f64)) * 2.0 * PI;
    let base_turns = ((start_angle - original_end_angle) / (2.0 * PI)).round() as i32;

    let mut best_angle = original_end_angle;
    let mut best_span = (best_angle - start_angle).abs();
    let mut best_distance_from_original = (best_angle - original_end_angle).abs();

    for turns in (base_turns - 2)..=(base_turns + 2) {
        let candidate = original_end_angle + turns as f64 * 2.0 * PI;
        let span = (candidate - start_angle).abs();
        let distance_from_original = (candidate - original_end_angle).abs();

        if span < best_span - EPSILON
            || (approx_eq(span, best_span)
                && distance_from_original < best_distance_from_original - EPSILON)
        {
            best_angle = candidate;
            best_span = span;
            best_distance_from_original = distance_from_original;
        }
    }

    best_angle
}

fn build_intra_level_route(
    start: Point,
    end: Point,
    center: Point,
    level_distance: f64,
    node_distance: f64,
    _inner_level_size: usize,
) -> Vec<Point> {
    let start_angle = polar_angle(start, center);
    let end_angle = polar_angle(end, center);

    let mid_angle = if (start_angle - end_angle).abs() <= PI {
        (start_angle + end_angle) / 2.0
    } else {
        (start_angle + end_angle) / 2.0 - PI
    };

    let dist_center_edge = distance(center, start)
        - (level_distance * node_distance / INTRA_LEVEL_ALPHA
            * ((start_angle - end_angle) / PI).abs());
    let edge_point = Point::new(
        center.x + dist_center_edge * mid_angle.cos(),
        center.y - dist_center_edge * mid_angle.sin(),
    );

    if let Some(circle_center) = compute_circle_center(start, edge_point, end) {
        let radius = distance(circle_center, edge_point);
        let start_circle_angle = polar_angle(start, circle_center);
        let end_circle_angle = polar_angle(end, circle_center);

        let (first, first_angle, last, last_angle, reverse_output) =
            normalize_circle_arc(start, start_circle_angle, end, end_circle_angle);

        let mut route =
            if ((start_circle_angle - end_circle_angle).abs() / PI) < INTRA_LEVEL_SEGMENT_ANGLE {
                sample_quadratic(first, edge_point, last, QUADRATIC_SAMPLES)
            } else {
                sample_circle_arc(circle_center, radius, first, first_angle, last, last_angle)
            };

        if reverse_output {
            route.reverse();
        }
        dedup_points(&mut route);
        route
    } else {
        sample_quadratic(start, edge_point, end, QUADRATIC_SAMPLES)
    }
}

fn sample_circle_arc(
    center: Point,
    radius: f64,
    first: Point,
    first_angle: f64,
    last: Point,
    last_angle: f64,
) -> Vec<Point> {
    let mut route = vec![first];
    let mut angle = first_angle + ARC_SAMPLE_STEP;
    while angle < last_angle - EPSILON {
        route.push(Point::new(
            center.x + radius * angle.cos(),
            center.y - radius * angle.sin(),
        ));
        angle += ARC_SAMPLE_STEP;
    }
    route.push(last);
    route
}

fn sample_quadratic(start: Point, control: Point, end: Point, samples: usize) -> Vec<Point> {
    let mut route = Vec::with_capacity(samples + 1);
    for step in 0..=samples {
        let t = step as f64 / samples as f64;
        let one_minus_t = 1.0 - t;
        let x =
            one_minus_t * one_minus_t * start.x + 2.0 * one_minus_t * t * control.x + t * t * end.x;
        let y =
            one_minus_t * one_minus_t * start.y + 2.0 * one_minus_t * t * control.y + t * t * end.y;
        route.push(Point::new(x, y));
    }
    dedup_points(&mut route);
    route
}

fn normalize_circle_arc(
    start: Point,
    start_angle: f64,
    end: Point,
    end_angle: f64,
) -> (Point, f64, Point, f64, bool) {
    if start_angle < end_angle {
        if (start_angle - end_angle).abs() < PI {
            (start, start_angle, end, end_angle, false)
        } else {
            (end, end_angle, start, start_angle + 2.0 * PI, true)
        }
    } else if (start_angle - end_angle).abs() < PI {
        (end, end_angle, start, start_angle, true)
    } else {
        (start, start_angle, end, end_angle + 2.0 * PI, false)
    }
}

fn compute_circle_center(p1: Point, p2: Point, p3: Point) -> Option<Point> {
    let p = [[p1.x, p1.y], [p2.x, p2.y], [p3.x, p3.y]];

    let m11 = determinant([
        [p[0][0], p[0][1], 1.0],
        [p[1][0], p[1][1], 1.0],
        [p[2][0], p[2][1], 1.0],
    ]);
    if approx_eq(m11, 0.0) {
        return None;
    }

    let m12 = determinant([
        [p[0][0] * p[0][0] + p[0][1] * p[0][1], p[0][1], 1.0],
        [p[1][0] * p[1][0] + p[1][1] * p[1][1], p[1][1], 1.0],
        [p[2][0] * p[2][0] + p[2][1] * p[2][1], p[2][1], 1.0],
    ]);
    let m13 = determinant([
        [p[0][0] * p[0][0] + p[0][1] * p[0][1], p[0][0], 1.0],
        [p[1][0] * p[1][0] + p[1][1] * p[1][1], p[1][0], 1.0],
        [p[2][0] * p[2][0] + p[2][1] * p[2][1], p[2][0], 1.0],
    ]);

    Some(Point::new(0.5 * m12 / m11, -0.5 * m13 / m11))
}

fn determinant(a: [[f64; 3]; 3]) -> f64 {
    a[0][0] * (a[1][1] * a[2][2] - a[2][1] * a[1][2])
        - a[0][1] * (a[1][0] * a[2][2] - a[2][0] * a[1][2])
        + a[0][2] * (a[1][0] * a[2][1] - a[2][0] * a[1][1])
}

struct CrossingReduction<'a> {
    graph: &'a WorkGraph,
    order: Vec<Vec<usize>>,
    offset: Vec<i32>,
    hl_edges: Vec<Vec<usize>>,
    hr_edges: Vec<Vec<usize>>,
    v_edges: Vec<Vec<usize>>,
}

impl<'a> CrossingReduction<'a> {
    fn new(graph: &'a WorkGraph, order: Vec<Vec<usize>>) -> Self {
        let node_count = graph.node_count();
        Self {
            graph,
            order,
            offset: vec![0; graph.edges.len()],
            hl_edges: vec![Vec::new(); node_count],
            hr_edges: vec![Vec::new(); node_count],
            v_edges: vec![Vec::new(); node_count],
        }
    }

    fn minimize_crossings(mut self) -> (Vec<Vec<usize>>, Vec<i32>) {
        let mut counter = 0usize;

        loop {
            counter += 1;
            let order_snapshot = self.order.clone();
            let temp_crossings = self.count_all_crossings(&order_snapshot);
            let mut best_crossings = temp_crossings;
            let mut best_order = self.order.clone();
            let mut best_offset = self.offset.clone();

            for level in 0..self.order.len().saturating_sub(1) {
                let fixed = self.order[level].clone();
                let movable = self.order[level + 1].clone();
                self.order[level + 1] = self.sifting(&fixed, movable);
            }

            let order_snapshot = self.order.clone();
            let current_crossings = self.count_all_crossings(&order_snapshot);
            if current_crossings < best_crossings {
                best_crossings = current_crossings;
                best_order = self.order.clone();
                best_offset = self.offset.clone();
            }

            for level in (1..self.order.len()).rev() {
                if level >= 2 {
                    let upper = self.order[level - 2].clone();
                    let lower = self.order[level - 1].clone();
                    self.store_adjacent_vertical_edges(&upper, &lower);
                } else {
                    for &node in &self.order[level - 1] {
                        self.v_edges[node].clear();
                    }
                }

                let fixed = self.order[level].clone();
                let movable = self.order[level - 1].clone();
                self.order[level - 1] = self.sifting(&fixed, movable);
            }

            let order_snapshot = self.order.clone();
            let current_crossings = self.count_all_crossings(&order_snapshot);
            if current_crossings < best_crossings {
                best_crossings = current_crossings;
                best_order = self.order.clone();
                best_offset = self.offset.clone();
            }

            self.order = best_order;
            self.offset = best_offset;

            if temp_crossings <= best_crossings || counter == MAX_SIFTING_ROUNDS {
                break;
            }
        }

        self.minimize_edge_lengths();
        (self.order, self.offset)
    }

    fn sifting(&mut self, level1: &[usize], mut level2: Vec<usize>) -> Vec<usize> {
        let vertical_edges = self.graph.vertical_edges(level1, &level2);
        let horizontal_edges = self.graph.horizontal_edges(&level2);
        let level2_snapshot = level2.clone();

        for v in level2_snapshot {
            let edges_to_v = self.graph.edges_to_node_from_level(level1, v);
            self.store_adjacent_horizontal_edges(&level2, &horizontal_edges);
            move_to_front(&mut level2, v);

            for &edge_index in &edges_to_v {
                self.offset[edge_index] = 1;
            }

            let mut best_position = 0usize;
            let mut current_offset = 0i32;
            let mut best_offset = 0i32;
            let mut parting = 0usize;
            let mut best_parting = 0usize;
            let mut crossing_delta = 0i32;
            let mut best_crossing_delta = 0i32;

            for i in 0..level2.len().saturating_sub(1) {
                let v2 = level2[i + 1];
                let edges_to_v2 = self.graph.edges_to_node_from_level(level1, v2);

                crossing_delta -=
                    self.count_vertical_crossings_lists(&edges_to_v, &edges_to_v2, level1, &level2);
                swap_nodes(&mut level2, v, v2);
                crossing_delta +=
                    self.count_vertical_crossings_lists(&edges_to_v, &edges_to_v2, level1, &level2);

                let mut before = 0i32;
                let mut after = 0i32;

                loop {
                    if edges_to_v.is_empty()
                        || edges_to_v2.is_empty()
                        || (edges_to_v.len() == 1
                            && vertical_edges.len() == 1
                            && edges_to_v == vertical_edges)
                    {
                        break;
                    }

                    let edge_index = edges_to_v[parting];
                    before = self.count_vertical_crossings_edge(
                        edge_index,
                        &vertical_edges,
                        level1,
                        &level2,
                    );
                    self.offset[edge_index] = current_offset;
                    after = self.count_vertical_crossings_edge(
                        edge_index,
                        &vertical_edges,
                        level1,
                        &level2,
                    );

                    if after <= before {
                        crossing_delta = crossing_delta - before + after;
                        parting += 1;
                        if parting == edges_to_v.len() {
                            current_offset -= 1;
                            parting = 0;
                        }
                    } else {
                        self.offset[edge_index] = current_offset + 1;
                        break;
                    }
                }

                let x = self.update_h_adj(&level2, v, v2);
                crossing_delta += self.sifting_swap_h(v, v2, &level2);
                crossing_delta += self.sifting_swap_hv(v, v2, x);

                if crossing_delta < best_crossing_delta {
                    best_position = i + 1;
                    best_offset = current_offset;
                    best_parting = parting;
                    best_crossing_delta = crossing_delta;
                }

                if after > before && !edges_to_v.is_empty() {
                    continue;
                }
            }

            for (index, &edge_index) in edges_to_v.iter().enumerate() {
                self.offset[edge_index] = if index < best_parting {
                    best_offset
                } else {
                    best_offset + 1
                };
            }

            move_to_position(&mut level2, v, best_position);
        }

        level2
    }

    fn sifting_swap_h(&self, v: usize, v2: usize, nodes: &[usize]) -> i32 {
        let mut hv = self.hr_edges[v].clone();
        hv.extend(self.hl_edges[v].iter().copied());
        let mut hv2 = self.hr_edges[v2].clone();
        hv2.extend(self.hl_edges[v2].iter().copied());

        let mut v_adj = Vec::new();
        let mut v2_adj = Vec::new();

        for edge_index in hv {
            if let Some(adjacent) = self.graph.adjacent_node(edge_index, v) {
                if adjacent != v2 {
                    v_adj.push(adjacent);
                }
            }
        }

        for edge_index in hv2 {
            if let Some(adjacent) = self.graph.adjacent_node(edge_index, v2) {
                if adjacent != v {
                    v2_adj.push(adjacent);
                }
            }
        }

        let r = v_adj.len();
        let s = v2_adj.len();
        let n = nodes.len();
        let mut i = 0usize;
        let mut j = 0usize;
        let mut c = 0i32;

        while i < r && j < s {
            let left = (position(nodes, v_adj[i]) + n - position(nodes, v2)) % n;
            let right = (position(nodes, v2_adj[j]) + n - position(nodes, v2)) % n;

            match left.cmp(&right) {
                Ordering::Less => {
                    c -= (s - j) as i32;
                    i += 1;
                }
                Ordering::Greater => {
                    c += (r - i) as i32;
                    j += 1;
                }
                Ordering::Equal => {
                    c += (r - i) as i32 - (s - j) as i32;
                    i += 1;
                    j += 1;
                }
            }
        }

        c
    }

    fn sifting_swap_hv(&self, v: usize, v2: usize, x: i32) -> i32 {
        let hr_adj_v = self.hr_edges[v].len() as i32;
        let hl_adj_v = self.hl_edges[v].len() as i32;
        let hr_adj_v2 = self.hr_edges[v2].len() as i32;
        let hl_adj_v2 = self.hl_edges[v2].len() as i32;
        let v_adj_v = self.v_edges[v].len() as i32;
        let v_adj_v2 = self.v_edges[v2].len() as i32;

        ((hr_adj_v2 - x) - hl_adj_v2) * v_adj_v + ((hl_adj_v - x) - hr_adj_v) * v_adj_v2
    }

    fn store_adjacent_horizontal_edges(&mut self, level2: &[usize], horizontal_edges: &[usize]) {
        for &v in level2 {
            let mut hl = Vec::new();
            let mut hr = Vec::new();
            let n = level2.len();
            let s = position(level2, v);

            for &edge_index in horizontal_edges {
                let Some(u) = self.graph.adjacent_node(edge_index, v) else {
                    continue;
                };

                let list = if ((position(level2, u) + n - s) % n) <= n / 2 {
                    &mut hr
                } else {
                    &mut hl
                };

                let mut inserted = false;
                for cursor in 0..list.len() {
                    let w = self.graph.adjacent_node(list[cursor], v).unwrap();
                    let u_pos = (position(level2, u) + n - s) % n;
                    let w_pos = (position(level2, w) + n - s) % n;
                    if u_pos < w_pos {
                        list.insert(cursor, edge_index);
                        inserted = true;
                        break;
                    }
                }
                if !inserted {
                    list.push(edge_index);
                }
            }

            self.hl_edges[v] = hl;
            self.hr_edges[v] = hr;
        }
    }

    fn store_adjacent_vertical_edges(&mut self, level1: &[usize], level2: &[usize]) {
        for &v in level2 {
            self.v_edges[v] = self.graph.edges_to_node_from_level(level1, v);
        }
    }

    fn update_h_adj(&mut self, level2: &[usize], v: usize, v2: usize) -> i32 {
        let mut hr_v = self.hr_edges[v].clone();
        let mut hl_v = self.hl_edges[v].clone();

        if let Some(&edge_index) = hl_v.first() {
            if let Some(u) = self.graph.adjacent_node(edge_index, v) {
                let n = level2.len();
                if ((position(level2, u) + n - position(level2, v)) % n) < n / 2 {
                    hl_v.remove(0);
                    hr_v.push(edge_index);
                }
            }
        }

        if let Some(&edge_index) = hr_v.first() {
            if self.graph.connects_nodes(edge_index, v, v2) {
                let moved = hr_v.remove(0);
                hl_v.push(moved);
                self.hr_edges[v] = hr_v;
                self.hl_edges[v] = hl_v;
                return 1;
            }
        }

        self.hr_edges[v] = hr_v;
        self.hl_edges[v] = hl_v;
        0
    }

    fn count_all_crossings(&mut self, order: &[Vec<usize>]) -> i32 {
        self.count_horizontal_crossings(order)
            + self.count_all_vertical_crossings(order)
            + self.count_hv_crossings(order)
    }

    fn count_horizontal_crossings(&self, order: &[Vec<usize>]) -> i32 {
        let mut c = 0;

        for level in order {
            let n = level.len();
            let edges = self.graph.horizontal_edges(level);

            for first in 0..edges.len().saturating_sub(1) {
                for second in (first + 1)..edges.len() {
                    let e1 = self.graph.edges[edges[first]];
                    let e2 = self.graph.edges[edges[second]];

                    let (u1, v1) = ordered_pair(level, e1.source, e1.target);
                    let (u2, v2) = ordered_pair(level, e2.source, e2.target);

                    if ((u2 + n - u1) % n) < ((v1 + n - u1) % n)
                        && ((v1 + n - u1) % n) < ((v2 + n - u1) % n)
                        && ((u2 + n - u1) % n) > 0
                    {
                        c += 1;
                    }
                }
            }
        }

        c
    }

    fn count_hv_crossings(&mut self, order: &[Vec<usize>]) -> i32 {
        let mut c = 0;

        for level in 0..order.len().saturating_sub(1) {
            self.store_adjacent_vertical_edges(&order[level], &order[level + 1]);
            let horizontal_edges = self.graph.horizontal_edges(&order[level + 1]);

            for edge_index in horizontal_edges {
                let edge = self.graph.edges[edge_index];
                let mut i1 = position(&order[level + 1], edge.target);
                let mut i2 = position(&order[level + 1], edge.source);
                if i2 < i1 {
                    i1 = position(&order[level + 1], edge.source);
                    i2 = position(&order[level + 1], edge.target);
                }

                let n = order[level + 1].len();
                let (start, end) = if ((i1 + n - i2) % n) < ((i2 + n - i1) % n) {
                    if i1 > i2 {
                        (i1, i2)
                    } else {
                        (i2, i1)
                    }
                } else if ((i1 + n - i2) % n) > ((i2 + n - i1) % n) {
                    if i2 > i1 {
                        (i1, i2)
                    } else {
                        (i2, i1)
                    }
                } else {
                    (i1, i2)
                };

                if start < end {
                    for index in (start + 1)..end {
                        let node = order[level + 1][index];
                        c += self.v_edges[node].len() as i32;
                    }
                } else {
                    for index in (start + 1)..n {
                        let node = order[level + 1][index];
                        c += self.v_edges[node].len() as i32;
                    }
                    for index in 0..end {
                        let node = order[level + 1][index];
                        c += self.v_edges[node].len() as i32;
                    }
                }
            }
        }

        c
    }

    fn count_all_vertical_crossings(&self, order: &[Vec<usize>]) -> i32 {
        let mut c = 0;

        for level in 0..order.len().saturating_sub(1) {
            let edges = self.graph.vertical_edges(&order[level], &order[level + 1]);
            for first in 0..edges.len().saturating_sub(1) {
                for second in (first + 1)..edges.len() {
                    c += self.crossings_between_two_edges(
                        edges[first],
                        edges[second],
                        &order[level],
                        &order[level + 1],
                    );
                }
            }
        }

        c
    }

    fn count_vertical_crossings_edge(
        &self,
        edge: usize,
        edges: &[usize],
        level1: &[usize],
        level2: &[usize],
    ) -> i32 {
        let mut crossings = 0;
        for &other in edges {
            crossings += self.crossings_between_two_edges(other, edge, level1, level2);
        }
        crossings
    }

    fn count_vertical_crossings_lists(
        &self,
        first_edges: &[usize],
        second_edges: &[usize],
        level1: &[usize],
        level2: &[usize],
    ) -> i32 {
        let mut crossings = 0;
        for &first in first_edges {
            for &second in second_edges {
                crossings += self.crossings_between_two_edges(first, second, level1, level2);
            }
        }
        crossings
    }

    fn crossings_between_two_edges(
        &self,
        first_edge: usize,
        second_edge: usize,
        level1: &[usize],
        level2: &[usize],
    ) -> i32 {
        let (u1, v1) = self.endpoints_for_levels(first_edge, level1);
        let (u2, v2) = self.endpoints_for_levels(second_edge, level1);

        let a = sign(position(level1, u2) as i32 - position(level1, u1) as i32);
        let b = sign(position(level2, v2) as i32 - position(level2, v1) as i32);

        let value = (self.offset[second_edge] - self.offset[first_edge] + (b - a) / 2).abs()
            + (a.abs() + b.abs()) / 2
            - 1;
        value.max(0)
    }

    fn endpoints_for_levels(&self, edge_index: usize, level1: &[usize]) -> (usize, usize) {
        let edge = self.graph.edges[edge_index];
        if level1.contains(&edge.source) {
            (edge.source, edge.target)
        } else {
            (edge.target, edge.source)
        }
    }

    fn minimize_edge_lengths(&mut self) {
        for level in 0..self.order.len().saturating_sub(1) {
            if self.order[level].is_empty() || self.order[level + 1].is_empty() {
                continue;
            }

            let inner_increment = 2.0 * PI / self.order[level].len() as f64;
            let outer_increment = 2.0 * PI / self.order[level + 1].len() as f64;
            let mut average_span = 0.0;
            let mut outer_angle = 0.0;
            let mut edge_count = 0usize;

            for &outer_node in &self.order[level + 1] {
                for &inner_node in &self.order[level] {
                    if let Some(edge_index) = self.graph.edge_between(outer_node, inner_node) {
                        edge_count += 1;
                        let inner_angle =
                            position(&self.order[level], inner_node) as f64 * inner_increment;
                        average_span += (inner_angle - outer_angle)
                            + (-(self.offset[edge_index] as f64) * 2.0 * PI);
                    }
                }
                outer_angle += outer_increment;
            }

            if edge_count == 0 {
                continue;
            }

            average_span /= edge_count as f64;
            let rotation = (average_span / outer_increment).round() as isize;
            self.rotate(level + 1, rotation);

            if self.order[level + 1].len() == 1 {
                let node = self.order[level + 1][0];
                for &edge_index in &self.graph.incident_edges[node] {
                    self.offset[edge_index] = 0;
                }
            }
        }
    }

    fn rotate(&mut self, level: usize, rotation: isize) {
        if rotation > 0 {
            for _ in 0..rotation {
                let node = self.order[level].pop().unwrap();
                self.order[level].insert(0, node);

                if level + 1 < self.order.len() {
                    for &neighbour in &self.order[level + 1] {
                        if let Some(edge_index) = self.graph.edge_between(node, neighbour) {
                            self.offset[edge_index] -= 1;
                        }
                    }
                }

                for &neighbour in &self.order[level - 1] {
                    if let Some(edge_index) = self.graph.edge_between(node, neighbour) {
                        self.offset[edge_index] += 1;
                    }
                }
            }
        } else if rotation < 0 {
            for _ in rotation..0 {
                let node = self.order[level].remove(0);
                self.order[level].push(node);

                if level + 1 < self.order.len() {
                    for &neighbour in &self.order[level + 1] {
                        if let Some(edge_index) = self.graph.edge_between(node, neighbour) {
                            self.offset[edge_index] += 1;
                        }
                    }
                }

                for &neighbour in &self.order[level - 1] {
                    if let Some(edge_index) = self.graph.edge_between(node, neighbour) {
                        self.offset[edge_index] -= 1;
                    }
                }
            }
        }
    }
}

struct CoordinateAssignment<'a> {
    graph: &'a WorkGraph,
    order: &'a [Vec<usize>],
    dummies: &'a HashSet<usize>,
}

impl<'a> CoordinateAssignment<'a> {
    fn new(graph: &'a WorkGraph, order: &'a [Vec<usize>], dummies: &'a HashSet<usize>) -> Self {
        Self {
            graph,
            order,
            dummies,
        }
    }

    fn get_coordinates(&self) -> Vec<i32> {
        if self.graph.node_count() == 0 {
            return Vec::new();
        }

        let mut assignments = Vec::with_capacity(4);
        for direction in 0..4 {
            let new_order = self.update_order(direction);
            let mut bk = BrandesKoepf::new(self.graph);
            let relative = bk.compute_relative_coordinates(&new_order, self.dummies);
            let mut absolute = bk.get_absolute_coordinates(&relative);

            if direction == 2 || direction == 3 {
                let max_coordinate = absolute.iter().copied().max().unwrap_or(0);
                for value in &mut absolute {
                    *value = max_coordinate - *value;
                }
            }

            assignments.push(absolute);
        }

        self.balance(&mut assignments)
    }

    fn update_order(&self, direction: usize) -> Vec<Vec<usize>> {
        match direction {
            0 => self.order.to_vec(),
            1 => {
                let mut new_order = vec![Vec::new(); self.order.len()];
                for (index, level) in self.order.iter().enumerate() {
                    new_order[self.order.len() - 1 - index] = level.clone();
                }
                new_order
            }
            2 => self
                .order
                .iter()
                .map(|level| level.iter().rev().copied().collect())
                .collect(),
            3 => {
                let mut new_order = vec![Vec::new(); self.order.len()];
                for (index, level) in self.order.iter().enumerate() {
                    new_order[self.order.len() - 1 - index] = level.iter().rev().copied().collect();
                }
                new_order
            }
            _ => self.order.to_vec(),
        }
    }

    fn balance(&self, assignments: &mut [Vec<i32>]) -> Vec<i32> {
        let mut min_width = i32::MAX;
        let mut smallest_layout = 0usize;
        let mut min_values = [i32::MAX; 4];
        let mut max_values = [0i32; 4];

        for (index, coordinates) in assignments.iter().enumerate() {
            for &x in coordinates {
                min_values[index] = min_values[index].min(x);
                max_values[index] = max_values[index].max(x);
            }
            let width = max_values[index] - min_values[index];
            if width < min_width {
                min_width = width;
                smallest_layout = index;
            }
        }

        for (index, coordinates) in assignments.iter_mut().enumerate() {
            if index == smallest_layout {
                continue;
            }

            let diff = if index == 0 || index == 1 {
                min_values[index] - min_values[smallest_layout]
            } else {
                max_values[index] - max_values[smallest_layout]
            };
            for value in coordinates {
                *value -= diff;
            }
        }

        let min_value = assignments
            .iter()
            .flat_map(|coordinates| coordinates.iter())
            .copied()
            .min()
            .unwrap_or(0);
        if min_value != 0 {
            for coordinates in &mut *assignments {
                for value in coordinates {
                    *value -= min_value;
                }
            }
        }

        let mut coordinates = vec![0; self.graph.node_count()];
        for node in 0..self.graph.node_count() {
            let mut values = [
                assignments[0][node],
                assignments[1][node],
                assignments[2][node],
                assignments[3][node],
            ];
            values.sort();
            coordinates[node] = (values[1] + values[2]) / 2;
        }

        let min_coordinate = coordinates.iter().copied().min().unwrap_or(0);
        if min_coordinate != 0 {
            for value in &mut coordinates {
                *value -= min_coordinate;
            }
        }

        coordinates
    }
}

struct BrandesKoepf<'a> {
    graph: &'a WorkGraph,
    order: Vec<Vec<usize>>,
    dummies: HashSet<usize>,
    marked: HashSet<usize>,
    root: Vec<usize>,
    align: Vec<usize>,
    x: Vec<Option<i32>>,
    sink: Vec<usize>,
    shift: Vec<i32>,
    level: Vec<usize>,
    positions: Vec<usize>,
}

impl<'a> BrandesKoepf<'a> {
    fn new(graph: &'a WorkGraph) -> Self {
        let node_count = graph.node_count();
        Self {
            graph,
            order: Vec::new(),
            dummies: HashSet::new(),
            marked: HashSet::new(),
            root: (0..node_count).collect(),
            align: (0..node_count).collect(),
            x: vec![None; node_count],
            sink: (0..node_count).collect(),
            shift: vec![i32::MAX; node_count],
            level: vec![0; node_count],
            positions: vec![0; node_count],
        }
    }

    fn compute_relative_coordinates(
        &mut self,
        order: &[Vec<usize>],
        dummies: &HashSet<usize>,
    ) -> Vec<i32> {
        self.order = order.to_vec();
        self.dummies = dummies.clone();
        self.marked.clear();

        for (level_index, level) in self.order.iter().enumerate() {
            for (position_index, &node) in level.iter().enumerate() {
                self.level[node] = level_index;
                self.positions[node] = position_index;
            }
        }

        self.mark_conflicts();
        self.init();
        self.vertical_alignment();
        self.horizontal_compaction();

        self.x.iter().map(|value| value.unwrap_or(0)).collect()
    }

    fn mark_conflicts(&mut self) {
        self.marked.clear();
        for level in 1..self.order.len().saturating_sub(2) {
            for lower_index in 0..self.order[level + 1].len() {
                let current = self.order[level + 1][lower_index];
                let neighbours = self.get_neighbours(current, level);
                let incident_dummy = neighbours
                    .iter()
                    .copied()
                    .find(|node| self.dummies.contains(node));

                if self.dummies.contains(&current) {
                    if let Some(incident) = incident_dummy {
                        let incident_position = self.positions[incident];

                        for left_index in 0..lower_index {
                            let left = self.order[level + 1][left_index];
                            let left_neighbours = self.get_neighbours(left, level);
                            for neighbour in left_neighbours {
                                if self.positions[neighbour] > incident_position
                                    && !(self.dummies.contains(&left)
                                        && self.dummies.contains(&neighbour))
                                {
                                    if let Some(edge_index) =
                                        self.graph.edge_between(left, neighbour)
                                    {
                                        self.marked.insert(edge_index);
                                    }
                                }
                            }
                        }

                        for right_index in lower_index..self.order[level + 1].len() {
                            let right = self.order[level + 1][right_index];
                            let right_neighbours = self.get_neighbours(right, level);
                            for neighbour in right_neighbours {
                                if incident_position > self.positions[neighbour]
                                    && !(self.dummies.contains(&right)
                                        && self.dummies.contains(&neighbour))
                                {
                                    if let Some(edge_index) =
                                        self.graph.edge_between(right, neighbour)
                                    {
                                        self.marked.insert(edge_index);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    fn init(&mut self) {
        let node_count = self.graph.node_count();
        self.root = (0..node_count).collect();
        self.align = (0..node_count).collect();
        self.sink = (0..node_count).collect();
        self.shift = vec![i32::MAX; node_count];
        self.x = vec![None; node_count];
    }

    fn vertical_alignment(&mut self) {
        for level in 1..self.order.len() {
            let mut barrier = -1i32;
            for current_index in 0..self.order[level].len() {
                let current = self.order[level][current_index];
                let neighbours = self.get_neighbours(current, level - 1);
                let degree = neighbours.len();
                if degree == 0 {
                    continue;
                }

                for median_index in ((degree - 1) / 2)..=(degree / 2) {
                    if self.align[current] != current {
                        continue;
                    }

                    let median = neighbours[median_index];
                    if let Some(edge_index) = self.graph.edge_between(current, median) {
                        let median_position = self.positions[median] as i32;
                        if !self.marked.contains(&edge_index) && barrier < median_position {
                            self.align[median] = current;
                            self.root[current] = self.root[median];
                            self.align[current] = self.root[current];
                            barrier = median_position;
                        }
                    }
                }
            }
        }
    }

    fn horizontal_compaction(&mut self) {
        for node in 0..self.graph.node_count() {
            if self.root[node] == node {
                self.place_block(node);
            }
        }
    }

    fn place_block(&mut self, node: usize) {
        if self.x[node].is_some() {
            return;
        }

        self.x[node] = Some(0);
        let mut current = node;

        loop {
            let level = self.level[current];
            let position = self.positions[current];

            if position > 0 {
                let neighbour = self.order[level][position - 1];
                let root = self.root[neighbour];
                self.place_block(root);

                if self.sink[node] == node {
                    self.sink[node] = self.sink[root];
                }

                if self.sink[node] != self.sink[root] {
                    let candidate = self.x[node].unwrap() - self.x[root].unwrap() - MIN_DIST;
                    let sink = self.sink[root];
                    self.shift[sink] = self.shift[sink].min(candidate);
                } else {
                    let candidate = self.x[root].unwrap() + MIN_DIST;
                    self.x[node] = Some(self.x[node].unwrap().max(candidate));
                }
            }

            current = self.align[current];
            if current == node {
                break;
            }
        }
    }

    fn get_absolute_coordinates(&self, relative: &[i32]) -> Vec<i32> {
        let mut coordinates = relative.to_vec();
        for node in 0..self.graph.node_count() {
            coordinates[node] = coordinates[self.root[node]];
            let current_shift = self.shift[self.sink[self.root[node]]];
            if current_shift < i32::MAX {
                coordinates[node] += current_shift;
            }
        }
        coordinates
    }

    fn get_neighbours(&self, node: usize, level: usize) -> Vec<usize> {
        let mut neighbours = Vec::new();
        for &other in &self.order[level] {
            if self.graph.edge_between(node, other).is_some() {
                neighbours.push(other);
            }
        }
        neighbours
    }
}

fn canonical_pair(left: usize, right: usize) -> (usize, usize) {
    if left < right {
        (left, right)
    } else {
        (right, left)
    }
}

fn ordered_pair(level: &[usize], left: usize, right: usize) -> (usize, usize) {
    let left_index = position(level, left);
    let right_index = position(level, right);
    if left_index < right_index {
        (left_index, right_index)
    } else {
        (right_index, left_index)
    }
}

fn position(nodes: &[usize], node: usize) -> usize {
    nodes.iter().position(|&current| current == node).unwrap()
}

fn sign(value: i32) -> i32 {
    match value.cmp(&0) {
        Ordering::Less => -1,
        Ordering::Equal => 0,
        Ordering::Greater => 1,
    }
}

fn move_to_front(nodes: &mut Vec<usize>, node: usize) {
    move_to_position(nodes, node, 0);
}

fn move_to_position(nodes: &mut Vec<usize>, node: usize, position_index: usize) {
    let current_index = position(nodes, node);
    let node = nodes.remove(current_index);
    nodes.insert(position_index, node);
}

fn swap_nodes(nodes: &mut [usize], first: usize, second: usize) {
    let first_index = position(nodes, first);
    let second_index = position(nodes, second);
    nodes.swap(first_index, second_index);
}

fn approx_eq(left: f64, right: f64) -> bool {
    (left - right).abs() <= EPSILON
}

fn distance(left: Point, right: Point) -> f64 {
    ((left.x - right.x).powi(2) + (left.y - right.y).powi(2)).sqrt()
}

fn polar_angle(point: Point, center: Point) -> f64 {
    let radius = distance(point, center);
    if radius.abs() < EPSILON {
        return 0.0;
    }

    if point.y - center.y < 0.0 {
        ((point.x - center.x) / radius).clamp(-1.0, 1.0).acos()
    } else {
        (-(point.x - center.x) / radius).clamp(-1.0, 1.0).acos() + PI
    }
}

fn dedup_points(points: &mut Vec<Point>) {
    points.dedup_by(|left, right| approx_eq(left.x, right.x) && approx_eq(left.y, right.y));
}

fn normalize_angle(angle: f64) -> f64 {
    angle.rem_euclid(2.0 * PI)
}

fn circular_mean_angle(angles: &[f64]) -> f64 {
    if angles.is_empty() {
        return 0.0;
    }

    let sin_sum = angles.iter().map(|angle| angle.sin()).sum::<f64>();
    let cos_sum = angles.iter().map(|angle| angle.cos()).sum::<f64>();

    if approx_eq(sin_sum, 0.0) && approx_eq(cos_sum, 0.0) {
        return normalize_angle(angles[0]);
    }

    normalize_angle(sin_sum.atan2(cos_sum))
}

fn unwrap_angles(angles: &[f64]) -> Vec<f64> {
    if angles.is_empty() {
        return Vec::new();
    }

    let mut unwrapped = Vec::with_capacity(angles.len());
    unwrapped.push(normalize_angle(angles[0]));
    for &angle in angles.iter().skip(1) {
        let mut candidate = normalize_angle(angle);
        while candidate < *unwrapped.last().unwrap() {
            candidate += 2.0 * PI;
        }
        unwrapped.push(candidate);
    }
    unwrapped
}

fn min_circular_gap(angles: &[f64]) -> f64 {
    if angles.len() <= 1 {
        return 2.0 * PI;
    }

    let mut sorted = angles
        .iter()
        .copied()
        .map(normalize_angle)
        .collect::<Vec<_>>();
    sorted.sort_by(f64::total_cmp);

    sorted
        .windows(2)
        .map(|pair| pair[1] - pair[0])
        .chain(std::iter::once(
            2.0 * PI - sorted.last().copied().unwrap_or(0.0) + sorted[0],
        ))
        .fold(f64::INFINITY, f64::min)
}

#[cfg(test)]
fn count_rendered_crossings(
    working_graph: &WorkGraph,
    node_levels: &[usize],
    points: &[Point],
) -> usize {
    let inter_level_edges = working_graph
        .edges
        .iter()
        .enumerate()
        .filter(|(_, edge)| node_levels[edge.source] != node_levels[edge.target])
        .collect::<Vec<_>>();

    let mut crossings = 0usize;
    for left_index in 0..inter_level_edges.len() {
        for right_index in (left_index + 1)..inter_level_edges.len() {
            let (_, left_edge) = inter_level_edges[left_index];
            let (_, right_edge) = inter_level_edges[right_index];

            if left_edge.source == right_edge.source
                || left_edge.source == right_edge.target
                || left_edge.target == right_edge.source
                || left_edge.target == right_edge.target
            {
                continue;
            }

            if segments_cross(
                points[left_edge.source],
                points[left_edge.target],
                points[right_edge.source],
                points[right_edge.target],
            ) {
                crossings += 1;
            }
        }
    }

    crossings
}

#[cfg(test)]
fn segments_cross(start_a: Point, end_a: Point, start_b: Point, end_b: Point) -> bool {
    let orientation1 = orientation(start_a, end_a, start_b);
    let orientation2 = orientation(start_a, end_a, end_b);
    let orientation3 = orientation(start_b, end_b, start_a);
    let orientation4 = orientation(start_b, end_b, end_a);

    sign_f64(orientation1) != sign_f64(orientation2)
        && sign_f64(orientation3) != sign_f64(orientation4)
}

#[cfg(test)]
fn orientation(start: Point, end: Point, point: Point) -> f64 {
    (end.x - start.x) * (point.y - start.y) - (end.y - start.y) * (point.x - start.x)
}

#[cfg(test)]
fn sign_f64(value: f64) -> i32 {
    if value > EPSILON {
        1
    } else if value < -EPSILON {
        -1
    } else {
        0
    }
}

fn packed_angle(z: i32, coordinate: i32) -> f64 {
    2.0 * PI / f64::from(z) * f64::from(coordinate)
}

fn distributed_angle(level_size: usize, index: usize) -> f64 {
    if level_size <= 1 {
        0.0
    } else {
        2.0 * PI * index as f64 / level_size as f64
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::filter_graph_to_descendants;
    use crate::graph_from_ttl_path;
    use crate::layering::compute_hierarchy_levels;
    use crate::model::{Edge, Graph, Node};
    use crate::LayoutError;
    use std::path::PathBuf;

    fn blank_nodes(count: usize) -> Vec<Node> {
        (0..count).map(|_| Node::default()).collect()
    }

    fn manual_work_graph(node_count: usize, edges: &[(usize, usize)]) -> WorkGraph {
        let mut graph = WorkGraph::new(node_count);
        for (index, &(source, target)) in edges.iter().enumerate() {
            graph.add_edge(source, target, index).unwrap();
        }
        graph
    }

    fn circular_distance(left: f64, right: f64) -> f64 {
        let difference = (normalize_angle(left) - normalize_angle(right)).abs();
        difference.min(2.0 * PI - difference)
    }

    fn unique_point_count(points: &[Point]) -> usize {
        let mut unique = Vec::<Point>::new();
        for point in points {
            if !unique
                .iter()
                .any(|candidate| approx_eq(candidate.x, point.x) && approx_eq(candidate.y, point.y))
            {
                unique.push(*point);
            }
        }
        unique.len()
    }

    fn legacy_spiral_route(
        start: Point,
        end: Point,
        center: Point,
        offset: i32,
        quality: usize,
    ) -> Vec<Point> {
        let start_radius = distance(start, center);
        let end_radius = distance(end, center);
        let start_angle = polar_angle(start, center);
        let end_angle = polar_angle(end, center) + (-(offset as f64)) * 2.0 * PI;

        if approx_eq(start_angle, end_angle) && offset == 0 {
            return vec![start, end];
        }

        let outer_radius = start_radius.max(end_radius);
        let flatness = outer_radius / quality as f64;
        let angle_direction = if start_angle < end_angle { 1.0 } else { -1.0 };
        let mut angle = start_angle;
        let mut radius = start_radius;
        let mut route = vec![start];

        loop {
            route.push(Point::new(
                center.x + radius * angle.cos(),
                center.y - radius * angle.sin(),
            ));

            let x = if radius.abs() < EPSILON {
                f64::INFINITY
            } else {
                flatness / radius
            };
            if x.is_nan() || x > 0.1 {
                angle += PI / 4.0 * angle_direction;
            } else {
                let y = (2.0 * x * x - 4.0 * x + 1.0).clamp(-1.0, 1.0);
                angle += y.acos() * angle_direction;
            }

            if (angle - end_angle) * angle_direction > 0.0 {
                break;
            }

            let denominator = end_angle - start_angle;
            let fraction_complete = if denominator.abs() < EPSILON {
                1.0
            } else {
                (angle - start_angle) / denominator
            };
            radius = start_radius + (end_radius - start_radius) * fraction_complete;
        }

        route.push(end);
        dedup_points(&mut route);
        route
    }

    fn route_angular_span(route: &[Point], center: Point) -> f64 {
        if route.len() <= 1 {
            return 0.0;
        }

        let mut unwrapped = Vec::with_capacity(route.len());
        unwrapped.push(polar_angle(route[0], center));
        for point in route.iter().skip(1) {
            let angle = polar_angle(*point, center);
            let previous = *unwrapped.last().unwrap();
            let mut best_candidate = angle;
            let mut best_delta = (angle - previous).abs();

            for turns in -1..=1 {
                let candidate = angle + turns as f64 * 2.0 * PI;
                let delta = (candidate - previous).abs();
                if delta < best_delta {
                    best_candidate = candidate;
                    best_delta = delta;
                }
            }

            unwrapped.push(best_candidate);
        }

        (unwrapped.last().unwrap() - unwrapped[0]).abs()
    }

    fn count_polyline_crossings(routes: &[Vec<Point>]) -> usize {
        let mut crossings = 0usize;
        for left_route_index in 0..routes.len() {
            for right_route_index in (left_route_index + 1)..routes.len() {
                let left = &routes[left_route_index];
                let right = &routes[right_route_index];
                for left_segment in left.windows(2) {
                    for right_segment in right.windows(2) {
                        if left_segment.iter().any(|left_point| {
                            right_segment.iter().any(|right_point| {
                                approx_eq(left_point.x, right_point.x)
                                    && approx_eq(left_point.y, right_point.y)
                            })
                        }) {
                            continue;
                        }

                        if segments_cross(
                            left_segment[0],
                            left_segment[1],
                            right_segment[0],
                            right_segment[1],
                        ) {
                            crossings += 1;
                        }
                    }
                }
            }
        }
        crossings
    }

    #[test]
    fn longest_path_levels_match_hierarchy_rings() {
        let graph = Graph::new(
            blank_nodes(4),
            vec![
                Edge::new(0, 1),
                Edge::new(0, 2),
                Edge::new(1, 3),
                Edge::new(2, 3),
            ],
        );

        let levels = compute_hierarchy_levels(&graph).unwrap();
        assert_eq!(levels, vec![0, 1, 1, 2]);
    }

    #[test]
    fn working_graph_inserts_dummy_nodes_for_long_edges() {
        let graph = Graph::new(
            blank_nodes(3),
            vec![Edge::new(0, 1), Edge::new(1, 2), Edge::new(0, 2)],
        );
        let levels = compute_hierarchy_levels(&graph).unwrap();
        let (work_graph, dummies, order) = build_working_graph(&graph, &levels).unwrap();

        assert_eq!(levels, vec![0, 1, 2]);
        assert_eq!(dummies.len(), 1);
        assert_eq!(work_graph.node_count(), 4);
        assert_eq!(order[1].len(), 2);
    }

    #[test]
    fn count_horizontal_crossings_matches_java_behavior() {
        let work_graph = manual_work_graph(4, &[(0, 2), (1, 3)]);
        let order = vec![vec![0, 1, 2, 3]];
        let reducer = CrossingReduction::new(&work_graph, order.clone());

        assert_eq!(reducer.count_horizontal_crossings(&order), 1);
    }

    #[test]
    fn count_mixed_crossings_matches_java_behavior() {
        let work_graph = manual_work_graph(6, &[(0, 3), (2, 4)]);
        let order = vec![vec![0, 1], vec![2, 3, 4, 5]];
        let mut reducer = CrossingReduction::new(&work_graph, order.clone());

        assert_eq!(reducer.count_hv_crossings(&order), 1);
    }

    #[test]
    fn sifting_reorders_crossing_two_level_graph() {
        let work_graph = manual_work_graph(4, &[(0, 3), (1, 2)]);
        let order = vec![vec![0, 1], vec![2, 3]];
        let (final_order, _) = CrossingReduction::new(&work_graph, order).minimize_crossings();

        assert_eq!(final_order[1], vec![3, 2]);
    }

    #[test]
    fn layout_sets_coordinates_in_place() {
        let mut graph = Graph::new(
            blank_nodes(4),
            vec![
                Edge::new(0, 1),
                Edge::new(0, 2),
                Edge::new(1, 3),
                Edge::new(2, 3),
            ],
        );

        layout_radial_hierarchy(&mut graph, LayoutConfig::default()).unwrap();

        for node in &graph.nodes {
            assert!(node.x.is_finite());
            assert!(node.y.is_finite());
        }
    }

    #[test]
    fn layout_wrapper_discards_artifacts_without_changing_edges() {
        let graph = Graph::new(blank_nodes(3), vec![Edge::new(0, 1), Edge::new(1, 2)]);
        let mut first = graph.clone();
        let mut second = graph.clone();

        let artifacts =
            layout_radial_hierarchy_with_artifacts(&mut first, LayoutConfig::default()).unwrap();
        layout_radial_hierarchy(&mut second, LayoutConfig::default()).unwrap();

        assert_eq!(first.edges, second.edges);
        assert_eq!(first.nodes, second.nodes);
        assert!(!artifacts.edge_routes.is_empty());
    }

    #[test]
    fn layout_artifacts_include_offsets_for_routed_edges() {
        let mut graph = Graph::new(
            blank_nodes(3),
            vec![Edge::new(0, 1), Edge::new(1, 2), Edge::new(0, 2)],
        );

        let artifacts =
            layout_radial_hierarchy_with_artifacts(&mut graph, LayoutConfig::default()).unwrap();

        assert_eq!(artifacts.edge_offsets.len(), artifacts.edge_routes.len());
        assert!(artifacts
            .edge_routes
            .iter()
            .all(|route| !route.points.is_empty()));
    }

    #[test]
    fn layout_artifacts_include_straight_route_for_single_root_edge() {
        let mut graph = Graph::new(blank_nodes(2), vec![Edge::new(0, 1)]);

        let artifacts =
            layout_radial_hierarchy_with_artifacts(&mut graph, LayoutConfig::default()).unwrap();

        assert_eq!(artifacts.edge_routes[0].kind, EdgeRouteKind::Straight);
        assert_eq!(artifacts.edge_routes[0].points.len(), 2);
    }

    #[test]
    fn layout_artifacts_include_spiral_route_for_non_root_interlevel_edge() {
        let mut graph = Graph::new(blank_nodes(3), vec![Edge::new(0, 1), Edge::new(1, 2)]);

        let artifacts =
            layout_radial_hierarchy_with_artifacts(&mut graph, LayoutConfig::default()).unwrap();

        assert!(artifacts
            .edge_routes
            .iter()
            .any(|route| route.original_edge_index == 1 && route.kind == EdgeRouteKind::Spiral));
    }

    #[test]
    fn intra_level_route_builder_returns_finite_points() {
        let center = Point::new(200.0, 200.0);
        let start = Point::new(300.0, 200.0);
        let end = Point::new(200.0, 300.0);
        let route = build_intra_level_route(start, end, center, 1.0, 80.0, 3);

        assert!(route.len() >= 2);
        assert!(route
            .iter()
            .all(|point| point.x.is_finite() && point.y.is_finite()));
        assert_eq!(route.first().copied(), Some(start));
        assert_eq!(route.last().copied(), Some(end));
    }

    #[test]
    fn layout_is_deterministic() {
        let graph = Graph::new(
            blank_nodes(5),
            vec![
                Edge::new(0, 2),
                Edge::new(1, 2),
                Edge::new(1, 3),
                Edge::new(2, 4),
                Edge::new(3, 4),
            ],
        );

        let mut first = graph.clone();
        let mut second = graph;
        let first_artifacts =
            layout_radial_hierarchy_with_artifacts(&mut first, LayoutConfig::default()).unwrap();
        let second_artifacts =
            layout_radial_hierarchy_with_artifacts(&mut second, LayoutConfig::default()).unwrap();

        assert_eq!(first.nodes, second.nodes);
        assert_eq!(first_artifacts.edge_offsets, second_artifacts.edge_offsets);
        assert_eq!(first_artifacts.edge_routes, second_artifacts.edge_routes);
    }

    #[test]
    fn project_radial_places_single_root_on_center() {
        let work_graph = manual_work_graph(2, &[(0, 1)]);
        let order = vec![vec![0], vec![1]];
        let projection = project_radial(&work_graph, &order, &[0, 0], LayoutConfig::default());

        assert_eq!(projection.points[0], projection.center);
    }

    #[test]
    fn distributed_ring_projection_spreads_nodes_around_full_circle() {
        let work_graph = manual_work_graph(15, &[]);
        let order = vec![
            vec![0],
            vec![1, 2, 3, 4],
            vec![5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
        ];
        let coordinates = vec![0, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        let config = LayoutConfig {
            ring_distribution: RingDistribution::Distributed,
            ..LayoutConfig::default()
        };

        let projection = project_radial(&work_graph, &order, &coordinates, config);
        let level_points = [
            projection.points[1],
            projection.points[2],
            projection.points[3],
            projection.points[4],
        ];

        let center = projection.center;
        let mut angles = level_points
            .iter()
            .map(|point| polar_angle(*point, center))
            .collect::<Vec<_>>();
        angles.sort_by(f64::total_cmp);

        let gaps = angles
            .windows(2)
            .map(|pair| pair[1] - pair[0])
            .chain(std::iter::once(2.0 * PI - angles[3] + angles[0]))
            .collect::<Vec<_>>();

        assert!(gaps.iter().all(|gap| (*gap - PI / 2.0).abs() < 1e-6));
    }

    #[test]
    fn adaptive_ring_projection_caps_sparse_outer_rings_by_previous_density() {
        let work_graph = manual_work_graph(7, &[(0, 1), (0, 2), (0, 3), (0, 4), (1, 5), (2, 6)]);
        let order = vec![vec![0], vec![1, 2, 3, 4], vec![5, 6]];
        let coordinates = vec![0, 0, 1, 2, 3, 0, 1];
        let config = LayoutConfig {
            ring_distribution: RingDistribution::Adaptive,
            ..LayoutConfig::default()
        };

        let projection = project_radial(&work_graph, &order, &coordinates, config);
        let center = projection.center;
        let parent_angles = [
            polar_angle(projection.points[1], center),
            polar_angle(projection.points[2], center),
        ];
        let child_angles = [
            polar_angle(projection.points[5], center),
            polar_angle(projection.points[6], center),
        ];
        let child_gap = min_circular_gap(&child_angles);

        assert!(circular_distance(child_angles[0], parent_angles[0]) < 1e-6);
        assert!(circular_distance(child_angles[1], parent_angles[1]) < 1e-6);
        assert!(child_gap >= PI / 2.0 - 1e-6);
        assert!(child_gap < PI - 1e-6);
    }

    #[test]
    fn adaptive_ring_projection_avoids_duplicates_across_wrap_boundary() {
        let work_graph = manual_work_graph(9, &[(4, 5), (1, 6), (4, 7), (1, 8)]);
        let order = vec![vec![0], vec![1, 2, 3, 4], vec![5, 6, 7, 8]];
        let coordinates = vec![0, 0, 1, 2, 3, 0, 1, 2, 3];
        let config = LayoutConfig {
            ring_distribution: RingDistribution::Adaptive,
            ..LayoutConfig::default()
        };

        let projection = project_radial(&work_graph, &order, &coordinates, config);
        let center = projection.center;
        let child_points = [
            projection.points[5],
            projection.points[6],
            projection.points[7],
            projection.points[8],
        ];
        let child_angles = child_points
            .iter()
            .map(|point| polar_angle(*point, center))
            .collect::<Vec<_>>();

        assert_eq!(unique_point_count(&child_points), child_points.len());
        assert!(min_circular_gap(&child_angles) >= ANGLE_EPSILON);
    }

    #[test]
    fn adaptive_ring_projection_keeps_propagated_gap_positive() {
        let work_graph = manual_work_graph(9, &[(4, 5), (1, 6), (4, 7), (1, 8)]);
        let order = vec![vec![0], vec![1, 2, 3, 4], vec![5, 6, 7, 8]];
        let coordinates = vec![0, 0, 1, 2, 3, 0, 1, 2, 3];

        let (first_angles, first_gap) = project_level_angles(
            &work_graph,
            &order[1],
            Some(order[0].as_slice()),
            &vec![Some(0.0); work_graph.node_count()],
            &coordinates,
            4,
            RingDistribution::Adaptive,
            None,
        );
        let mut node_angles = vec![None; work_graph.node_count()];
        for (&node, angle) in order[1].iter().zip(first_angles.iter().copied()) {
            node_angles[node] = Some(angle);
        }

        let (_, second_gap) = project_level_angles(
            &work_graph,
            &order[2],
            Some(order[1].as_slice()),
            &node_angles,
            &coordinates,
            4,
            RingDistribution::Adaptive,
            first_gap,
        );

        assert!(first_gap.unwrap() >= ANGLE_EPSILON);
        assert!(second_gap.unwrap() >= ANGLE_EPSILON);
    }

    #[test]
    fn adaptive_ring_projection_uses_full_circle_when_a_later_ring_needs_more_space() {
        let work_graph = manual_work_graph(14, &[]);
        let order = vec![
            vec![0],
            vec![1, 2, 3, 4],
            vec![5, 6],
            vec![7, 8, 9, 10, 11, 12, 13],
        ];
        let coordinates = vec![0, 0, 1, 2, 3, 0, 1, 0, 1, 2, 3, 4, 5, 6];
        let config = LayoutConfig {
            ring_distribution: RingDistribution::Adaptive,
            ..LayoutConfig::default()
        };

        let projection = project_radial(&work_graph, &order, &coordinates, config);
        let center = projection.center;
        let outer_angles = [
            polar_angle(projection.points[7], center),
            polar_angle(projection.points[8], center),
            polar_angle(projection.points[9], center),
            polar_angle(projection.points[10], center),
            polar_angle(projection.points[11], center),
            polar_angle(projection.points[12], center),
            polar_angle(projection.points[13], center),
        ];
        let outer_gap = min_circular_gap(&outer_angles);

        assert!((outer_gap - (2.0 * PI / 7.0)).abs() < 1e-6);
    }

    #[test]
    fn adaptive_projection_does_not_increase_rendered_crossings_on_boundary_fixture() {
        let work_graph = manual_work_graph(9, &[(1, 5), (2, 6), (4, 7), (1, 8), (4, 6)]);
        let order = vec![vec![0], vec![1, 2, 3, 4], vec![5, 6, 7, 8]];
        let coordinates = vec![0, 0, 1, 2, 3, 0, 1, 2, 3];

        let packed = project_radial(
            &work_graph,
            &order,
            &coordinates,
            LayoutConfig {
                ring_distribution: RingDistribution::Packed,
                ..LayoutConfig::default()
            },
        );
        let distributed = project_radial(
            &work_graph,
            &order,
            &coordinates,
            LayoutConfig {
                ring_distribution: RingDistribution::Distributed,
                ..LayoutConfig::default()
            },
        );
        let adaptive = project_radial(
            &work_graph,
            &order,
            &coordinates,
            LayoutConfig {
                ring_distribution: RingDistribution::Adaptive,
                ..LayoutConfig::default()
            },
        );

        let packed_crossings =
            count_rendered_crossings(&work_graph, &packed.node_levels, &packed.points);
        let distributed_crossings =
            count_rendered_crossings(&work_graph, &distributed.node_levels, &distributed.points);
        let adaptive_crossings =
            count_rendered_crossings(&work_graph, &adaptive.node_levels, &adaptive.points);

        assert!(adaptive_crossings <= packed_crossings);
        assert!(adaptive_crossings <= distributed_crossings);
    }

    #[test]
    fn effective_spiral_end_angle_prefers_shortest_equivalent_branch() {
        let start_angle = 11.0 * PI / 6.0;
        let raw_end_angle = PI / 6.0;

        let end_angle = effective_spiral_end_angle(start_angle, raw_end_angle, 0);

        assert!((end_angle - (raw_end_angle + 2.0 * PI)).abs() < 1e-6);
        assert!((end_angle - start_angle).abs() < PI / 2.0);
    }

    #[test]
    fn effective_spiral_end_angle_can_reduce_offset_wraps() {
        let start_angle = 11.0 * PI / 6.0;
        let raw_end_angle = PI / 6.0;

        let end_angle = effective_spiral_end_angle(start_angle, raw_end_angle, 1);

        assert!((end_angle - (raw_end_angle + 2.0 * PI)).abs() < 1e-6);
        assert!((end_angle - start_angle).abs() < PI / 2.0);
    }

    #[test]
    fn seam_spiral_route_uses_short_arc() {
        let center = Point::new(200.0, 200.0);
        let start = Point::new(
            center.x + 100.0 * (11.0 * PI / 6.0).cos(),
            center.y - 100.0 * (11.0 * PI / 6.0).sin(),
        );
        let end = Point::new(
            center.x + 200.0 * (PI / 6.0).cos(),
            center.y - 200.0 * (PI / 6.0).sin(),
        );
        let points = vec![start, end];
        let node_levels = vec![0, 1];
        let edge = WorkEdge {
            source: 0,
            target: 1,
            original_edge: 0,
        };

        let route = build_spiral_route(&edge, &points, &node_levels, center, 0, 128);

        assert_eq!(route.first().copied(), Some(start));
        assert_eq!(route.last().copied(), Some(end));
        assert!(route_angular_span(&route, center) < PI / 2.0);
    }

    #[test]
    fn seam_spiral_route_does_not_increase_crossings_against_legacy_branch() {
        let center = Point::new(200.0, 200.0);
        let left_inner = Point::new(
            center.x + 100.0 * (11.0 * PI / 6.0).cos(),
            center.y - 100.0 * (11.0 * PI / 6.0).sin(),
        );
        let left_outer = Point::new(
            center.x + 200.0 * (PI / 6.0).cos(),
            center.y - 200.0 * (PI / 6.0).sin(),
        );
        let right_inner = Point::new(
            center.x + 100.0 * (7.0 * PI / 6.0).cos(),
            center.y - 100.0 * (7.0 * PI / 6.0).sin(),
        );
        let right_outer = Point::new(
            center.x + 200.0 * (5.0 * PI / 6.0).cos(),
            center.y - 200.0 * (5.0 * PI / 6.0).sin(),
        );

        let left_edge = WorkEdge {
            source: 0,
            target: 1,
            original_edge: 0,
        };
        let right_edge = WorkEdge {
            source: 2,
            target: 3,
            original_edge: 1,
        };
        let points = vec![left_inner, left_outer, right_inner, right_outer];
        let node_levels = vec![0, 1, 0, 1];

        let new_routes = vec![
            build_spiral_route(&left_edge, &points, &node_levels, center, 0, 128),
            build_spiral_route(&right_edge, &points, &node_levels, center, 0, 128),
        ];
        let legacy_routes = vec![
            legacy_spiral_route(left_inner, left_outer, center, 0, 128),
            legacy_spiral_route(right_inner, right_outer, center, 0, 128),
        ];

        assert!(count_polyline_crossings(&new_routes) <= count_polyline_crossings(&legacy_routes));
    }

    #[test]
    fn real_bfo_subtree_has_no_duplicate_nodes_or_long_seam_spirals() {
        let ttl_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
            .join("data")
            .join("vkg_full.ttl");
        if !ttl_path.exists() {
            return;
        }

        let imported = graph_from_ttl_path(&ttl_path).unwrap();
        let mut graph =
            filter_graph_to_descendants(&imported, "http://purl.obolibrary.org/obo/BFO_0000001")
                .unwrap();
        let artifacts = layout_radial_hierarchy_with_artifacts(
            &mut graph,
            LayoutConfig {
                spiral_quality: 80,
                ring_distribution: RingDistribution::Adaptive,
                ..LayoutConfig::default()
            },
        )
        .unwrap();

        assert_eq!(
            unique_point_count(
                &graph
                    .nodes
                    .iter()
                    .map(|node| Point::new(node.x, node.y))
                    .collect::<Vec<_>>()
            ),
            graph.nodes.len()
        );

        let suspicious_routes = artifacts
            .edge_routes
            .iter()
            .filter(|route| route.kind == EdgeRouteKind::Spiral && route.points.len() >= 2)
            .filter(|route| {
                let start_angle = polar_angle(route.points[0], artifacts.center);
                let end_angle = polar_angle(*route.points.last().unwrap(), artifacts.center);
                let endpoint_gap = circular_distance(start_angle, end_angle);
                let span = route_angular_span(&route.points, artifacts.center);
                endpoint_gap < PI / 4.0 && span > 1.5 * PI
            })
            .count();

        assert_eq!(suspicious_routes, 0);
    }

    #[test]
    fn rejects_invalid_graph_shapes() {
        let duplicate = Graph::new(blank_nodes(2), vec![Edge::new(0, 1), Edge::new(0, 1)]);
        let cycle = Graph::new(blank_nodes(2), vec![Edge::new(0, 1), Edge::new(1, 0)]);
        let self_loop = Graph::new(blank_nodes(1), vec![Edge::new(0, 0)]);

        assert!(matches!(
            compute_hierarchy_levels(&duplicate),
            Err(LayoutError::DuplicateEdge { .. })
        ));
        assert!(matches!(
            compute_hierarchy_levels(&cycle),
            Err(LayoutError::CycleDetected)
        ));
        assert!(matches!(
            compute_hierarchy_levels(&self_loop),
            Err(LayoutError::SelfLoop { .. })
        ));
    }
}
