#pragma once
// cut_graph.hpp
//
// Phase 6 — Tree-cotree algorithm for computing a cut graph of a triangulated
// surface.
//
// For a closed, orientable, genus-g surface:
//   #vertices (V), #edges (E), #faces (F)
//   Euler: V − E + F = 2 − 2g
//   Primal spanning tree:  V − 1  edges
//   Dual spanning tree:    F − 1  edges  (avoiding duals of tree edges)
//   Remaining:             E − (V−1) − (F−1) = 2g  cut edges
//
// These 2g cut edges generate H₁(M, ℤ) ≅ ℤ^{2g}.
// Cutting along them turns M into a topological disk.
//
// For open meshes (boundary present) the algorithm still works: the dual BFS
// starts from a boundary-adjacent face, and boundary half-edges are skipped.
// The number of cut edges will be E − (V−1) − (F−1) − B where B counts
// boundary edges treated as dual tree edges.
//
// Usage:
//   CutGraph cg = compute_cut_graph(mesh);
//   // cg.cut_edge_flags[e.idx()] == true  →  treat edge as seam in BFS
//   euclidean_layout(mesh, x, maps, &cg);    // layout with holonomy tracking

#include "conformal_mesh.hpp"
#include "gauss_bonnet.hpp"     // for euler_characteristic / genus
#include <vector>
#include <queue>
#include <cstddef>

namespace conformallab {

// ─────────────────────────────────────────────────────────────────────────────
// CutGraph
// ─────────────────────────────────────────────────────────────────────────────

struct CutGraph {
    /// cut_edge_flags[e.idx()] = true ↔ this edge is a cut edge.
    /// Size = mesh.number_of_edges().
    std::vector<bool> cut_edge_flags;

    /// Indices of the 2g cut edges in order (size = 2g).
    std::vector<std::size_t> cut_edge_indices;

    /// Genus of the surface (0 for topological spheres and open patches).
    int genus = 0;

    bool is_cut(Edge_index e) const
    {
        return static_cast<std::size_t>(e.idx()) < cut_edge_flags.size()
            && cut_edge_flags[static_cast<std::size_t>(e.idx())];
    }
};

// ─────────────────────────────────────────────────────────────────────────────
// compute_cut_graph
// ─────────────────────────────────────────────────────────────────────────────
//
// Implements the standard tree-cotree algorithm (Erickson–Whittlesey 2005):
//
//   Step 1: BFS primal spanning tree T  (V−1 primal tree edges).
//   Step 2: BFS dual spanning tree T*   (F−1 dual/primal edges, avoiding
//           edges whose primal crosses T).
//   Step 3: cut edges = primal edges neither in T nor "used" by T*.

inline CutGraph compute_cut_graph(const ConformalMesh& mesh)
{
    const std::size_t nv = mesh.number_of_vertices();
    const std::size_t ne = mesh.number_of_edges();
    const std::size_t nf = mesh.number_of_faces();

    CutGraph cg;
    cg.cut_edge_flags.assign(ne, false);
    cg.genus = conformallab::genus(mesh);

    if (nv == 0 || nf == 0) return cg;

    // ── Step 1: primal spanning tree via BFS from vertex 0 ───────────────────
    std::vector<bool> tree_edge(ne, false);
    std::vector<bool> v_visited(nv, false);

    {
        std::queue<Vertex_index> q;
        auto v0 = *mesh.vertices().begin();
        v_visited[v0.idx()] = true;
        q.push(v0);
        while (!q.empty()) {
            Vertex_index v = q.front(); q.pop();
            for (Halfedge_index h : CGAL::halfedges_around_target(v, mesh)) {
                Vertex_index u = mesh.source(h);
                if (!v_visited[static_cast<std::size_t>(u.idx())]) {
                    v_visited[static_cast<std::size_t>(u.idx())] = true;
                    tree_edge[static_cast<std::size_t>(mesh.edge(h).idx())] = true;
                    q.push(u);
                }
            }
        }
    }

    // ── Step 2: dual spanning tree via BFS from face 0 ───────────────────────
    // Dual edge between face f and face f_adj crosses primal edge e.
    // Include dual edge only if:
    //   (a) e is not a primal tree edge (tree_edge[e] == false)
    //   (b) h_adj is not a border halfedge
    std::vector<bool> dual_tree_edge(ne, false);
    std::vector<bool> f_visited(nf, false);

    {
        std::queue<Face_index> q;
        auto f0 = *mesh.faces().begin();
        f_visited[static_cast<std::size_t>(f0.idx())] = true;
        q.push(f0);
        while (!q.empty()) {
            Face_index f = q.front(); q.pop();
            for (Halfedge_index h :
                 CGAL::halfedges_around_face(mesh.halfedge(f), mesh))
            {
                Halfedge_index h_opp = mesh.opposite(h);
                if (mesh.is_border(h_opp)) continue;        // boundary edge
                Face_index f_adj = mesh.face(h_opp);
                if (f_visited[static_cast<std::size_t>(f_adj.idx())]) continue;

                std::size_t eidx = static_cast<std::size_t>(mesh.edge(h).idx());
                if (!tree_edge[eidx]) {
                    // Use this dual edge in T*
                    f_visited[static_cast<std::size_t>(f_adj.idx())] = true;
                    dual_tree_edge[eidx] = true;
                    q.push(f_adj);
                }
            }
        }
    }

    // ── Step 3: cut edges = neither in T nor in T* nor on boundary ───────────
    // Boundary edges are adjacent to the "outer face" and need no cutting —
    // they are implicitly handled by the boundary itself.
    for (Edge_index e : mesh.edges()) {
        std::size_t idx = static_cast<std::size_t>(e.idx());
        if (tree_edge[idx] || dual_tree_edge[idx]) continue;
        // Skip boundary edges — they are not interior homological cycles.
        Halfedge_index h = mesh.halfedge(e);
        if (mesh.is_border(h) || mesh.is_border(mesh.opposite(h))) continue;
        cg.cut_edge_flags[idx] = true;
        cg.cut_edge_indices.push_back(idx);
    }

    return cg;
}

} // namespace conformallab
