// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (c) 2025 lacklustr@protonmail.com https://github.com/eadf

use crate::common::VertexIndex;
use crate::common::macros::integrity_println;
use crate::corner_table::build_non_manifold::NonManifoldEdge;
use crate::corner_table::{CornerIndex, CornerTable};
use rustc_hash::FxHashSet;
use smallvec::SmallVec;

/// Represents a potential pairing of opposite corners across a non-manifold edge
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct EdgePairing {
    corner_a: CornerIndex,
    corner_b: CornerIndex,
}

impl EdgePairing {
    fn new(corner_a: CornerIndex, corner_b: CornerIndex) -> Self {
        Self { corner_a, corner_b }
    }
}

/// Represents a complete configuration of pairings for a non-manifold edge
#[derive(Debug, Clone, Copy)]
pub(super) struct PairingConfiguration {
    pairing_a: EdgePairing,
    pairing_b: EdgePairing,
}

impl PairingConfiguration {
    fn all_corners_of_vertex<const ENABLE_UNSAFE: bool>(
        &self,
        corner_table: &CornerTable<ENABLE_UNSAFE>,
        vertex: VertexIndex,
    ) -> SmallVec<[CornerIndex; 8]> {
        [
            self.pairing_a.corner_a,
            self.pairing_a.corner_b,
            self.pairing_b.corner_a,
            self.pairing_b.corner_b,
        ]
        .into_iter()
        .flat_map(|c| {
            let next_prev = c.next_prev();
            [next_prev.0, next_prev.1]
        })
        .filter(|&c| corner_table.vertex(c) == vertex)
        .collect()
    }

    pub(super) fn apply_configuration<const ENABLE_UNSAFE: bool>(
        &self,
        corner_table: &mut CornerTable<ENABLE_UNSAFE>,
    ) {
        corner_table.set_dual_opposite(self.pairing_a.corner_a, self.pairing_a.corner_b);
        corner_table.set_dual_opposite(self.pairing_b.corner_a, self.pairing_b.corner_b);
    }

    fn rollback_configuration<const ENABLE_UNSAFE: bool>(
        &self,
        corner_table: &mut CornerTable<ENABLE_UNSAFE>,
    ) {
        corner_table.clear_opposite(self.pairing_a.corner_a);
        corner_table.clear_opposite(self.pairing_a.corner_b);
        corner_table.clear_opposite(self.pairing_b.corner_a);
        corner_table.clear_opposite(self.pairing_b.corner_b);
    }
}

/// Represents the two alternative configurations for a non-manifold edge
#[derive(Debug, Clone)]
pub(super) struct EdgeConfigurationSet {
    pub(super) edge_vertices: (VertexIndex, VertexIndex),
    pub(super) config_a: PairingConfiguration, // e.g., C18<->C37, C35<->C40
    pub(super) config_b: PairingConfiguration, // e.g., C18<->C40, C35<->C37
}

impl EdgeConfigurationSet {
    /// Creates a new configuration set and finds the two valid alternatives
    pub(super) fn from_corners<const ENABLE_UNSAFE: bool>(
        edge: NonManifoldEdge,
        corner_table: &mut CornerTable<ENABLE_UNSAFE>,
    ) -> Option<EdgeConfigurationSet> {
        if edge.corners.len() != 4 {
            return None;
        }

        let corners_array = [
            edge.corners[0],
            edge.corners[1],
            edge.corners[2],
            edge.corners[3],
        ];
        let configs =
            EdgeConfigurationSet::find_valid_configurations(&corners_array, corner_table)?;

        if configs.len() != 2 {
            return None; // Should always be exactly 2 for this case
        }

        Some(EdgeConfigurationSet {
            edge_vertices: (edge.v_i, edge.v_j),
            config_a: configs[0],
            config_b: configs[1],
        })
    }

    /// Finds the two valid configuration alternatives
    fn find_valid_configurations<const ENABLE_UNSAFE: bool>(
        corners: &[CornerIndex; 4],
        corner_table: &CornerTable<ENABLE_UNSAFE>,
    ) -> Option<Vec<PairingConfiguration>> {
        let mut configs = Vec::new();

        // Try all possible ways to pair 4 corners into 2 pairs
        // There are only 3 ways: (0-1,2-3), (0-2,1-3), (0-3,1-2)
        let pairing_schemes = [((0, 1), (2, 3)), ((0, 2), (1, 3)), ((0, 3), (1, 2))];

        for &((i1, j1), (i2, j2)) in &pairing_schemes {
            let pairing_a = EdgePairing::new(corners[i1], corners[j1]);
            let pairing_b = EdgePairing::new(corners[i2], corners[j2]);

            // Both pairings must be valid for this configuration to work
            if Self::is_valid_pairing(pairing_a, corner_table)
                && Self::is_valid_pairing(pairing_b, corner_table)
            {
                configs.push(PairingConfiguration {
                    pairing_a,
                    pairing_b,
                });
            }
        }

        if configs.len() == 2 {
            Some(configs)
        } else {
            None
        }
    }

    /// Checks if a pairing satisfies the orientation constraint
    fn is_valid_pairing<const ENABLE_UNSAFE: bool>(
        pairing: EdgePairing,
        corner_table: &CornerTable<ENABLE_UNSAFE>,
    ) -> bool {
        let c1 = pairing.corner_a;
        let c2 = pairing.corner_b;

        // V(c1.next) == V(c2.prev) && V(c1.prev) == V(c2.next)
        corner_table.vertex(corner_table.next(c1)) == corner_table.vertex(corner_table.prev(c2))
            && corner_table.vertex(corner_table.prev(c1))
                == corner_table.vertex(corner_table.next(c2))
    }
}

/// Evaluation result for a configuration
#[derive(Debug, Copy, Clone)]
pub(super) struct ConfigurationTest {
    pub(super) config: PairingConfiguration,
    pub(super) vertex_0_result: VertexStats,
    pub(super) vertex_1_result: VertexStats,
}

impl ConfigurationTest {
    pub(super) fn is_simple_edge(&self, other_configuration: Self) -> bool {
        // this will split every edge connecting two components/blobs (if connected by a simple shared edge)
        (!self.vertex_0_result.is_manifold && !self.vertex_1_result.is_manifold)
            && (other_configuration.vertex_0_result.is_manifold
                && other_configuration.vertex_1_result.is_manifold)
            && (self.vertex_0_result.valence == other_configuration.vertex_0_result.valence)
            && (self.vertex_1_result.valence == other_configuration.vertex_1_result.valence)
    }

    pub(super) fn evaluate<const ENABLE_UNSAFE: bool>(
        config: &PairingConfiguration,
        edge_vertices: (VertexIndex, VertexIndex),
        corner_table: &mut CornerTable<ENABLE_UNSAFE>,
    ) -> Self {
        // Apply the configuration
        config.apply_configuration(corner_table);

        // Evaluate connectivity
        let valence_0 = corner_table.bnm_estimate_valence(
            edge_vertices.0,
            &config.all_corners_of_vertex(corner_table, edge_vertices.0),
        );
        let valence_1 = corner_table.bnm_estimate_valence(
            edge_vertices.1,
            &config.all_corners_of_vertex(corner_table, edge_vertices.1),
        );

        integrity_println!(
            "when connecting opposites {:?}<->{:?} and {:?}<->{:?}:",
            corner_table.dbg_corner(config.pairing_a.corner_a),
            corner_table.dbg_corner(config.pairing_a.corner_b),
            corner_table.dbg_corner(config.pairing_b.corner_a),
            corner_table.dbg_corner(config.pairing_b.corner_b)
        );
        integrity_println!(" result vertex 0: {:?}", valence_0,);
        integrity_println!(" result vertex 1: {:?}", valence_1);

        // Roll back to INVALID
        config.rollback_configuration(corner_table);

        Self {
            config: *config,
            vertex_0_result: valence_0,
            vertex_1_result: valence_1,
        }
    }
}

#[derive(Debug, Copy, Clone)]
pub(super) struct VertexStats {
    pub(super) vertex: VertexIndex,
    // a 'good enough'™ estimate of valence if not manifold
    pub(super) valence: usize,
    pub(super) is_manifold: bool,
}

#[derive(Debug)]
struct LoopResult {
    completed_loop: bool,
    loop_size: usize,
}

#[derive(Debug)]
struct SwingResult {
    hit_boundary: bool,
}

impl<const ENABLE_UNSAFE: bool> CornerTable<ENABLE_UNSAFE> {
    /// Count reachable valence from a list of known corners around a vertex
    /// Returns early if a complete loop is found, otherwise accumulates all reachable corners
    fn bnm_estimate_valence(
        &self,
        vertex: VertexIndex,
        candidate_corners: &[CornerIndex],
    ) -> VertexStats {
        if candidate_corners.is_empty() {
            return VertexStats {
                vertex,
                valence: 0,
                is_manifold: false,
            };
        }

        let mut visited = FxHashSet::default();
        let mut longest_continuous_loop = 0;
        let mut found_complete_loop = false;

        // Try each candidate corner to explore all reachable corners
        for &start_corner in candidate_corners {
            if visited.contains(&start_corner) {
                // Already explored from this corner
                continue;
            }

            let LoopResult {
                completed_loop,
                loop_size,
            } = self.bnm_try_swing_loop(start_corner, vertex, &mut visited);

            if completed_loop {
                found_complete_loop = true;
                longest_continuous_loop = longest_continuous_loop.max(loop_size);
            }
        }

        // Manifold if we found a complete loop that accounts for all visited corners
        let is_manifold = found_complete_loop && longest_continuous_loop == visited.len();

        VertexStats {
            vertex,
            valence: visited.len(),
            is_manifold,
        }
    }

    /// Try to swing from a corner and detect if we complete a loop or hit boundaries
    /// Returns (completed_loop, number_of_corners_in_this_loop)
    fn bnm_try_swing_loop(
        &self,
        start: CornerIndex,
        vertex: VertexIndex,
        visited: &mut FxHashSet<CornerIndex>,
    ) -> LoopResult {
        let visited_before = visited.len();

        // Swing CCW - if no boundary hit, we have a complete manifold loop
        let ccw_result = self.bnm_swing_direction::<true>(start, vertex, visited);

        if ccw_result.hit_boundary {
            // Hit a boundary, so not manifold. Swing CW to collect remaining corners.
            let _ = self.bnm_swing_direction::<false>(start, vertex, visited);
            let loop_size = visited.len() - visited_before;
            return LoopResult {
                completed_loop: false,
                loop_size,
            };
        }

        let loop_size = visited.len() - visited_before;
        LoopResult {
            completed_loop: true,
            loop_size,
        }
    }

    /// Swing in one direction (CCW or CW), adding corners to visited set
    /// Returns whether we hit a boundary and whether we visited any corners
    fn bnm_swing_direction<const CCW: bool>(
        &self,
        start: CornerIndex,
        vertex: VertexIndex,
        visited: &mut FxHashSet<CornerIndex>,
    ) -> SwingResult {
        let mut current = start;
        let mut visited_any = false;
        //println!(" -start swing ccw:{ccw} corner:{}", self.dbg_corner(current));

        loop {
            // Verify this corner belongs to the vertex
            if self.vertex(current) != vertex {
                /*println!(
                    "swing_direction() found an unrelated vertex (should not happen) {}",
                    self.dbg_corner(current)
                );*/
                return SwingResult { hit_boundary: true };
            }

            // Always insert, but only check if already visited after first iteration
            if !visited.insert(current) && visited_any {
                // Already visited - we completed a loop back to a known corner
                return SwingResult {
                    hit_boundary: false,
                };
            }

            visited_any = true;

            // Try to swing to next triangle
            if let Some(next_corner) = if CCW {
                self.try_swing_ccw(current)
            } else {
                self.try_swing_cw(current)
            } {
                if next_corner == start {
                    //println!(" -found start corner:{}", self.dbg_corner(next_corner));
                    return SwingResult {
                        hit_boundary: false,
                    };
                }
                //println!(" -found swing corner:{}", self.dbg_corner(next_corner));
                current = next_corner;
            } else {
                //println!(" -found boundary corner after:{}", self.dbg_corner(current));
                // Hit a boundary edge
                return SwingResult { hit_boundary: true };
            }
        }
    }
}
