// src/graph.rs

//! Provides the main [`Graph`] struct for in-memory graph operations.
//!
//! This module defines the central [`Graph`] object, which is the primary
//! entry point for querying a loaded ODGI graph. It also defines the
//! associated [`Error`] type for handling failures.

use cxx::UniquePtr;
use std::error::Error as StdError;
use std::fmt;
use super::ffi;

// Re-export the FFI data structures so they are part of the public API
// and can be used as return types from the Graph methods.
pub use super::ffi::{Edge, PathPosition};

/// A custom error type for operations within the `odgi-ffi` crate.
///
/// This error is returned by functions that might fail, such as [`Graph::load`].
#[derive(Debug)]
pub struct Error(pub String);

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl StdError for Error {}

/// A safe, idiomatic Rust wrapper around a C++ `odgi::graph_t` object.
///
/// A `Graph` instance represents a pangenome graph loaded into memory.
/// Once loaded, you can use its methods to perform various queries, such as
/// retrieving node sequences, finding paths, and traversing the graph structure.
///
/// The only way to create a `Graph` is by calling [`Graph::load`].
pub struct Graph {
    // This field will only exist in real builds.
    #[cfg(not(feature = "docs-only"))]
    inner: UniquePtr<ffi::OpaqueGraph>,

    // For docs builds, add a dummy field to make the struct valid.
    #[cfg(feature = "docs-only")]
    _inner: (),
}

// --- REAL IMPLEMENTATION (for normal builds) ---
#[cfg(not(feature = "docs-only"))]
impl Graph {
    /// Loads an ODGI graph from a file into memory.
    ///
    /// # Arguments
    ///
    /// * `path` - A string slice that holds the path to the ODGI file.
    ///
    /// # Errors
    ///
    /// Returns an [`Error`] if the file does not exist or if the file format is invalid.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// use odgi_ffi::Graph;
    ///
    /// match Graph::load("my_graph.odgi") {
    ///     Ok(graph) => println!("Graph loaded successfully!"),
    ///     Err(e) => eprintln!("Failed to load graph: {}", e),
    /// }
    /// ```
    pub fn load(path: &str) -> Result<Self, Error> {
        let graph_ptr = ffi::load_graph(path);
        if graph_ptr.is_null() {
            Err(Error(format!("Failed to load ODGI graph from '{}'", path)))
        } else {
            Ok(Graph { inner: graph_ptr })
        }
    }

    /// Returns the total number of nodes in the graph.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// # use odgi_ffi::Graph;
    /// # let graph = Graph::load("my_graph.odgi").unwrap();
    /// let count = graph.node_count();
    /// println!("The graph has {} nodes.", count);
    /// ```
    pub fn node_count(&self) -> u64 {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        ffi::get_node_count(graph_t_ref)
    }

    /// Returns a list of all path names in the graph.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// # use odgi_ffi::Graph;
    /// # let graph = Graph::load("my_graph.odgi").unwrap();
    /// let paths = graph.get_path_names();
    /// for path_name in paths {
    ///     println!("Found path: {}", path_name);
    /// }
    /// ```
    pub fn get_path_names(&self) -> Vec<String> {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        ffi::graph_get_path_names(graph_t_ref)
    }

    /// Projects a 0-based linear coordinate on a path to graph coordinates.
    ///
    /// This is useful for finding which node and offset corresponds to a
    /// specific position along a named path.
    ///
    /// # Arguments
    ///
    /// * `path_name` - The name of the path to project onto.
    /// * `pos` - The 0-based nucleotide position along the path.
    ///
    /// # Returns
    ///
    /// Returns `Some(PathPosition)` if the path exists and the position is
    /// within its bounds. Returns `None` otherwise.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// # use odgi_ffi::Graph;
    /// # let graph = Graph::load("my_graph.odgi").unwrap();
    /// if let Some(position) = graph.project("human_chr1", 1_000_000) {
    ///     println!("Position 1M on chr1 is at node {} offset {}",
    ///              position.node_id, position.offset);
    /// } else {
    ///     println!("Position not found on path.");
    /// }
    /// ```
    pub fn project(&self, path_name: &str, pos: u64) -> Option<PathPosition> {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        let result_ptr = ffi::graph_project(graph_t_ref, path_name, pos);

        if result_ptr.is_null() {
            None
        } else {
            Some(result_ptr.as_ref().unwrap().clone())
        }
    }

    /// Gets the DNA sequence for a given node ID.
    ///
    /// # Arguments
    ///
    /// * `node_id` - The ID of the node to query.
    ///
    /// # Returns
    ///
    /// Returns the sequence as a `String`. If the `node_id` is invalid,
    /// an empty string is returned.
    pub fn get_node_sequence(&self, node_id: u64) -> String {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        ffi::graph_get_node_sequence(graph_t_ref, node_id)
    }

    /// Gets the length of the sequence for a given node ID.
    ///
    /// # Arguments
    ///
    /// * `node_id` - The ID of the node to query.
    ///
    /// # Returns
    ///
    /// Returns the sequence length. If the `node_id` is invalid, `0` is returned.
    pub fn get_node_len(&self, node_id: u64) -> u64 {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        ffi::graph_get_node_len(graph_t_ref, node_id)
    }

    /// Gets all successor edges for a given node ID.
    ///
    /// Successors are the nodes immediately following this one in the graph topology.
    pub fn get_successors(&self, node_id: u64) -> Vec<Edge> {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        ffi::graph_get_successors(graph_t_ref, node_id)
    }

    /// Gets all predecessor edges for a given node ID.
    ///
    /// Predecessors are the nodes immediately preceding this one in the graph topology.
    pub fn get_predecessors(&self, node_id: u64) -> Vec<Edge> {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        ffi::graph_get_predecessors(graph_t_ref, node_id)
    }

    /// Gets the names of all paths that step on a given node ID.
    pub fn get_paths_on_node(&self, node_id: u64) -> Vec<String> {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        ffi::graph_get_paths_on_node(graph_t_ref, node_id)
    }

    /// Gets the total length of a path in base pairs.
    ///
    /// # Arguments
    ///
    /// * `path_name` - The name of the path to measure.
    ///
    /// # Returns
    ///
    /// Returns `Some(u64)` with the path length if the path exists.
    /// Returns `None` if no path with that name is found in the graph.
    pub fn get_path_length(&self, path_name: &str) -> Option<u64> {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        // We can use the existing get_path_names to check for existence first,
        // making our Rust API safer and more idiomatic than the C++ one.
        if !self.get_path_names().iter().any(|p| p == path_name) {
            return None;
        }
        let length = ffi::graph_get_path_length(graph_t_ref, path_name);
        Some(length)
    }
    // ADD THIS NEW PUBLIC METHOD
    /// Gets the next node ID on a given path from a specified node.
    ///
    /// # Returns
    ///
    /// Returns `Some(u64)` with the next node ID if the current node is on the
    /// path and is not the last node. Returns `None` otherwise.
    pub fn get_next_node_on_path(&self, node_id: u64, path_name: &str) -> Option<u64> {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        let next_node_id = ffi::graph_get_next_node_on_path(graph_t_ref, path_name, node_id);
        if next_node_id >= 0 {
            Some(next_node_id as u64)
        } else {
            None
        }
    }

    /// Gets the names of all paths that traverse a specific directed edge.
    ///
    /// An edge is defined by a source node and a destination node, including the
    /// orientation of each node. This function will return a unique list of
    /// path names that step from `from_node` to `to_node` with the specified
    /// orientations.
    ///
    /// # Arguments
    ///
    /// * `from_node` - The ID of the node where the edge begins.
    /// * `from_orientation` - The orientation of the `from_node`. `true` for forward, `false` for reverse.
    /// * `to_node` - The ID of the node where the edge ends.
    /// * `to_orientation` - The orientation of the `to_node`. `true` for forward, `false` for reverse.
    ///
    /// # Returns
    ///
    /// Returns a `Vec<String>` containing the names of all paths on the given edge.
    /// If the edge does not exist on any path, an empty vector is returned.
    ///
    /// # Examples
    ///
    /// ```rust,no_run
    /// # use odgi_ffi::Graph;
    /// # let graph = Graph::load("my_graph.odgi").unwrap();
    /// // Find paths going from the forward orientation of node 1 to the forward orientation of node 2.
    /// let paths = graph.get_paths_on_edge(1, true, 2, true);
    /// for path_name in paths {
    ///     println!("Found path on edge 1+ -> 2+: {}", path_name);
    /// }
    /// ```
    pub fn get_paths_on_edge(
        &self,
        from_node: u64,
        from_orientation: bool,
        to_node: u64,
        to_orientation: bool,
    ) -> Vec<String> {
        let graph_t_ref = ffi::get_graph_t(&self.inner);
        ffi::graph_get_paths_on_edge(
            graph_t_ref,
            from_node,
            from_orientation,
            to_node,
            to_orientation
        )
    }
}

// --- MOCK IMPLEMENTATION (for docs.rs) ---
#[cfg(feature = "docs-only")]
impl Graph {
    /// Loads an ODGI graph from a file into memory.
    pub fn load(_path: &str) -> Result<Self, Error> { Ok(Graph { _inner: () }) }

    /// Returns the total number of nodes in the graph.
    pub fn node_count(&self) -> u64 { 0 }

    /// Returns a list of all path names in the graph.
    pub fn get_path_names(&self) -> Vec<String> { vec![] }

    /// Projects a 0-based linear coordinate on a path to graph coordinates.
    pub fn project(&self, _path_name: &str, _pos: u64) -> Option<PathPosition> { None }

    /// Gets the DNA sequence for a given node ID.
    pub fn get_node_sequence(&self, _node_id: u64) -> String { String::new() }

    /// Gets the length of the sequence for a given node ID.
    pub fn get_node_len(&self, _node_id: u64) -> u64 { 0 }

    /// Gets all successor edges for a given node ID.
    pub fn get_successors(&self, _node_id: u64) -> Vec<Edge> { vec![] }

    /// Gets all predecessor edges for a given node ID.
    pub fn get_predecessors(&self, _node_id: u64) -> Vec<Edge> { vec![] }

    /// Gets the names of all paths that step on a given node ID.
    pub fn get_paths_on_node(&self, _node_id: u64) -> Vec<String> { vec![] }

    /// Gets the total length of a path in base pairs.
    pub fn get_path_length(&self, _path_name: &str) -> Option<u64> { None }

    /// Gets the names of all paths that traverse a specific directed edge.
    pub fn get_paths_on_edge(
        &self,
        _from_node: u64,
        _from_orientation: bool,
        _to_node: u64,
        _to_orientation: bool,
    ) -> Vec<String> {
        vec![]
    }
}


/// Marks the `Graph` struct as safe to send between threads.
// The `unsafe` keyword is our guarantee to the compiler that we've ensured
// the underlying C++ object is safe to be sent and accessed across threads,
// which is true in our read-only use case.
unsafe impl Send for Graph {}

/// Marks the `Graph` struct as safe to share between threads.
// The `unsafe` keyword is our guarantee to the compiler that we've ensured
// the underlying C++ object is safe to be sent and accessed across threads,
// which is true in our read-only use case.
unsafe impl Sync for Graph {}// src/lib.rs
//! A safe Rust interface to the `odgi` C++ library.
//!
//! The `odgi-ffi` crate provides high-level, idiomatic Rust bindings for querying
//! [ODGI](https://github.com/pangenome/odgi) graphs. It handles the complexity of the
//! C++ FFI boundary, providing a safe and easy-to-use API for Rust developers.
//!
//! The primary entry point is the [`Graph`] struct, which represents a loaded ODGI graph
//! in memory. This crate also provides utility functions for converting between GFA and
//! ODGI file formats.
//!
//! # Modules
//!
//! - [`graph`]: Contains the main [`Graph`] struct for querying graph data.
//! - [`conversion`]: Provides functions like [`gfa_to_odgi`] for format conversion.
//!
//! # Features
//!
//! - Load ODGI graphs from disk into a safe Rust wrapper.
//! - Query graph properties, such as node count, path names, and node sequences.
//! - Perform topological queries, such as finding node successors and predecessors.
//! - Project path coordinates to their corresponding nodes and offsets.
//! - Convert between GFA and ODGI formats using the bundled `odgi` executable.
//!
//! # Example
//!
//! Here's a complete example of loading a graph and performing some basic queries.
//!
//! ```rust,no_run
//! use odgi_ffi::{Graph, gfa_to_odgi};
//! use tempfile::NamedTempFile;
//! use std::io::Write;
//!
//! // Create a temporary GFA file for the example.
//! let mut gfa_file = NamedTempFile::new().unwrap();
//! writeln!(gfa_file, "H\tVN:Z:1.0").unwrap();
//! writeln!(gfa_file, "S\t1\tGATTACA").unwrap();
//! writeln!(gfa_file, "S\t2\tT").unwrap();
//! writeln!(gfa_file, "L\t1\t+\t2\t+\t0M").unwrap();
//! writeln!(gfa_file, "P\tx\t1+,2+\t*").unwrap();
//! let gfa_path = gfa_file.path();
//!
//! // Create a path for the ODGI output file.
//! let odgi_file = NamedTempFile::new().unwrap();
//! let odgi_path = odgi_file.path();
//!
//! // 1. Convert the GFA file to an ODGI file.
//! // This function is only available when not using the `docs-only` feature.
//! # #[cfg(not(feature = "docs-only"))]
//! gfa_to_odgi(gfa_path.to_str().unwrap(), odgi_path.to_str().unwrap())
//!      .expect("Failed to convert GFA to ODGI");
//!
//! // 2. Load the ODGI graph into memory.
//! let graph = Graph::load(odgi_path.to_str().unwrap())
//!      .expect("Failed to load ODGI graph");
//!
//! // 3. Query the graph.
//! assert_eq!(graph.node_count(), 2);
//!
//! let path_names = graph.get_path_names();
//! assert_eq!(path_names, vec!["x"]);
//!
//! let seq = graph.get_node_sequence(1);
//! assert_eq!(seq, "GATTACA");
//!
//! // Get path length using the new method.
//! let length = graph.get_path_length("x").unwrap();
//! assert_eq!(length, 8);
//!
//! // Projecting position 7 on path "x" should land at the start of node 2.
//! let position = graph.project("x", 7).unwrap();
//! assert_eq!(position.node_id, 2);
//! assert_eq!(position.offset, 0);
//! ```

mod graph;

// Conditionally compile the conversion module.
// It will not exist for docs.rs builds.
#[cfg(not(feature = "docs-only"))]
mod conversion;

// Publicly re-export the core types for easy access.
pub use graph::{Graph, Error, Edge, PathPosition};

// Conditionally re-export the conversion functions.
#[cfg(not(feature = "docs-only"))]
pub use conversion::{gfa_to_odgi, odgi_to_gfa};


// --- REAL FFI BRIDGE (for normal builds) ---
#[cfg(not(feature = "docs-only"))]
#[cxx::bridge(namespace = "odgi")]
mod ffi {
    /// Represents a directed edge between two nodes in the graph.
    #[derive(Debug, Clone)]
    struct Edge {
        /// The ID of the node this edge points to.
        to_node: u64,
        /// The orientation of the "from" node's handle in this edge.
        from_orientation: bool,
        /// The orientation of the "to" node's handle in this edge.
        to_orientation: bool,
    }

    /// Represents a specific position on a path.
    #[derive(Debug, Clone)]
    struct PathPosition {
        /// The ID of the node at this position.
        node_id: u64,
        /// The 0-based offset within the node's sequence.
        offset: u64,
        /// The orientation of the node on the path at this position.
        is_forward: bool,
    }

    unsafe extern "C++" {
        include!("odgi-ffi/src/odgi_wrapper.hpp");
        include!("odgi-ffi/src/lib.rs.h");

        type graph_t;
        #[namespace = ""]
        type OpaqueGraph;

        #[namespace = ""]
        fn load_graph(path: &str) -> UniquePtr<OpaqueGraph>;
        #[namespace = ""]
        fn get_graph_t<'a>(graph: &'a OpaqueGraph) -> &'a graph_t;
        #[namespace = ""]
        fn get_node_count(graph: &graph_t) -> u64;
        #[namespace = ""]
        fn graph_get_path_names(graph: &graph_t) -> Vec<String>;
        #[namespace = ""]
        fn graph_project(graph: &graph_t, path_name: &str, pos: u64) -> UniquePtr<PathPosition>;
        #[namespace = ""]
        fn graph_get_node_sequence(graph: &graph_t, node_id: u64) -> String;
        #[namespace = ""]
        fn graph_get_node_len(graph: &graph_t, node_id: u64) -> u64;
        #[namespace = ""]
        fn graph_get_successors(graph: &graph_t, node_id: u64) -> Vec<Edge>;
        #[namespace = ""]
        fn graph_get_predecessors(graph: &graph_t, node_id: u64) -> Vec<Edge>;
        #[namespace = ""]
        fn graph_get_paths_on_node(graph: &graph_t, node_id: u64) -> Vec<String>;
        #[namespace = ""]
        fn graph_get_path_length(graph: &graph_t, path_name: &str) -> u64;
        #[namespace = ""]
        fn graph_get_next_node_on_path(graph: &graph_t, path_name: &str, node_id: u64) -> i64;
        #[namespace = ""]
        fn graph_get_paths_on_edge(
            graph: &graph_t,
            from_node: u64,
            from_orient: bool,
            to_node: u64,
            to_orient: bool
        ) -> Vec<String>;
    }
}

// --- MOCK FFI BRIDGE (for docs.rs) ---
#[cfg(feature = "docs-only")]
mod ffi {
    // This self-contained mock module provides all the types that `graph.rs` needs
    // to compile its public API for documentation purposes.

    // Mock the opaque C++ types.
    pub enum OpaqueGraph {}

    // Provide mock definitions for the shared structs.
    #[derive(Debug, Clone)]
    pub struct Edge {
        pub to_node: u64,
        pub from_orientation: bool,
        pub to_orientation: bool,
    }

    #[derive(Debug, Clone)]
    pub struct PathPosition {
        pub node_id: u64,
        pub offset: u64,
        pub is_forward: bool,
    }
}#include "odgi_wrapper.hpp"
#include <fstream>
#include <string>
#include <vector>
#include <algorithm> // Required for std::sort and std::unique
#include "odgi-ffi/src/lib.rs.h"
// src/odgi.cpp
// --- Core API ---
std::unique_ptr<OpaqueGraph> load_graph(rust::Str path) {
    auto odgi_graph = std::make_unique<odgi::graph_t>();
    std::ifstream in{std::string(path)};
    if (!in) { return nullptr; }
    odgi_graph->deserialize(in);
    auto wrapper = std::make_unique<OpaqueGraph>();
    wrapper->graph = std::move(odgi_graph);
    return wrapper;
}

const odgi::graph_t& get_graph_t(const OpaqueGraph& wrapper) {
    return *wrapper.graph.get();
}

uint64_t get_node_count(const odgi::graph_t& graph) {
    return graph.get_node_count();
}

// --- Query Functions ---
rust::Vec<rust::String> graph_get_path_names(const odgi::graph_t& graph) {
    rust::Vec<rust::String> names;
    graph.for_each_path_handle([&](const odgi::path_handle_t& path) {
        names.push_back(graph.get_path_name(path));
    });
    return names;
}

std::unique_ptr<odgi::PathPosition> graph_project(const odgi::graph_t& graph, rust::Str path_name, uint64_t pos) {
    if (!graph.has_path(std::string(path_name))) {
        return nullptr;
    }
    odgi::path_handle_t path = graph.get_path_handle(std::string(path_name));
    
    uint64_t path_len = 0;
    graph.for_each_step_in_path(path, [&](const odgi::step_handle_t& step) {
        path_len += graph.get_length(graph.get_handle_of_step(step));
        return true;
    });

    if (pos >= path_len) {
        return nullptr;
    }

    uint64_t current_pos = 0;
    std::unique_ptr<odgi::PathPosition> found_pos = nullptr;

    graph.for_each_step_in_path(path, [&](const odgi::step_handle_t& step) {
        if (found_pos) { 
            return true;
        }

        odgi::handle_t handle = graph.get_handle_of_step(step);
        uint64_t node_len = graph.get_length(handle);

        if (pos < current_pos + node_len) {
            uint64_t offset_in_step = pos - current_pos;
            
            found_pos = std::make_unique<odgi::PathPosition>(odgi::PathPosition{
                (uint64_t)graph.get_id(handle),
                graph.get_is_reverse(handle) ? (node_len - 1 - offset_in_step) : offset_in_step,
                !graph.get_is_reverse(handle)
            });
        }
        current_pos += node_len;
        return true;
    });

    return found_pos;
}

rust::String graph_get_node_sequence(const odgi::graph_t& graph, uint64_t node_id) {
    if (!graph.has_node(node_id)) return "";
    return graph.get_sequence(graph.get_handle(node_id, false));
}

uint64_t graph_get_node_len(const odgi::graph_t& graph, uint64_t node_id) {
    if (!graph.has_node(node_id)) return 0;
    return graph.get_length(graph.get_handle(node_id, false));
}

rust::Vec<odgi::Edge> graph_get_successors(const odgi::graph_t& graph, uint64_t node_id) {
    rust::Vec<odgi::Edge> edges;
    if (!graph.has_node(node_id)) return edges;

    auto handle_fwd = graph.get_handle(node_id, false);
    graph.follow_edges(handle_fwd, false, [&](const odgi::handle_t& next) {
        edges.push_back({(uint64_t)graph.get_id(next), true, !graph.get_is_reverse(next)});
        return true;
    });
    
    auto handle_rev = graph.get_handle(node_id, true);
    graph.follow_edges(handle_rev, false, [&](const odgi::handle_t& next) {
        edges.push_back({(uint64_t)graph.get_id(next), false, !graph.get_is_reverse(next)});
        return true;
    });
    return edges;
}

rust::Vec<odgi::Edge> graph_get_predecessors(const odgi::graph_t& graph, uint64_t node_id) {
    rust::Vec<odgi::Edge> edges;
    if (!graph.has_node(node_id)) return edges;

    auto handle_fwd = graph.get_handle(node_id, false);
    graph.follow_edges(handle_fwd, true, [&](const odgi::handle_t& prev) {
        edges.push_back({(uint64_t)graph.get_id(prev), !graph.get_is_reverse(prev), true});
        return true;
    });

    auto handle_rev = graph.get_handle(node_id, true);
    graph.follow_edges(handle_rev, true, [&](const odgi::handle_t& prev) {
        edges.push_back({(uint64_t)graph.get_id(prev), !graph.get_is_reverse(prev), false});
        return true;
    });
    return edges;
}

rust::Vec<rust::String> graph_get_paths_on_node(const odgi::graph_t& graph, uint64_t node_id) {
    rust::Vec<rust::String> paths;
    if (!graph.has_node(node_id)) return paths;

    auto handle = graph.get_handle(node_id, false);
    graph.for_each_step_on_handle(handle, [&](const odgi::step_handle_t& step) {
        paths.push_back(graph.get_path_name(graph.get_path_handle_of_step(step)));
        return true;
    });
    return paths;
}

int64_t graph_get_next_node_on_path(const odgi::graph_t& graph, rust::Str path_name_str, uint64_t node_id) {
    std::string path_name(path_name_str);
    if (!graph.has_path(path_name) || !graph.has_node(node_id)) {
        return -1;
    }

    odgi::path_handle_t path_handle = graph.get_path_handle(path_name);
    odgi::handle_t target_handle = graph.get_handle(node_id, false); // Check both orientations
    odgi::handle_t target_handle_rev = graph.get_handle(node_id, true);

    int64_t next_node = -1;
    bool found_step = false;

    graph.for_each_step_in_path(path_handle, [&](const odgi::step_handle_t& step) {
        if (found_step) {
            // This is the step immediately after our target step
            odgi::handle_t next_handle = graph.get_handle_of_step(step);
            next_node = graph.get_id(next_handle);
            return false; // Stop iterating
        }
        odgi::handle_t current_handle = graph.get_handle_of_step(step);
        if (current_handle == target_handle || current_handle == target_handle_rev) {
            // We found our node. The next iteration will get the successor.
            found_step = true;
        }
        return true; // Continue iterating
    });

    return next_node;
}

uint64_t graph_get_path_length(const odgi::graph_t& graph, rust::Str path_name) {
    if (!graph.has_path(std::string(path_name))) {
        return 0; 
    }
    odgi::path_handle_t path = graph.get_path_handle(std::string(path_name));
    uint64_t path_len = 0;
    graph.for_each_step_in_path(path, [&](const odgi::step_handle_t& step) {
        path_len += graph.get_length(graph.get_handle_of_step(step));
        return true;
    });
    return path_len;
}

rust::Vec<rust::String> graph_get_paths_on_edge(
    const odgi::graph_t& graph,
    uint64_t from_node, bool from_is_forward,
    uint64_t to_node, bool to_is_forward
) {
    rust::Vec<rust::String> initial_paths;
    if (!graph.has_node(from_node) || !graph.has_node(to_node)) {
        return initial_paths;
    }

    bool from_is_reverse = !from_is_forward;
    bool to_is_reverse = !to_is_forward;

    odgi::handle_t from_handle = graph.get_handle(from_node, from_is_reverse);
    odgi::handle_t to_handle = graph.get_handle(to_node, to_is_reverse);

    graph.for_each_step_on_handle(from_handle, [&](const odgi::step_handle_t& step) {
        if (graph.has_next_step(step)) {
            odgi::step_handle_t next_step = graph.get_next_step(step);
            if (graph.get_handle_of_step(next_step) == to_handle) {
                initial_paths.push_back(graph.get_path_name(graph.get_path_handle_of_step(step)));
            }
        }
        return true;
    });

    // --- FIX APPLIED HERE ---
    // Convert the rust::Vec to a std::vector to use standard algorithms.
    std::vector<std::string> std_paths;
    for (const auto& path : initial_paths) {
        std_paths.push_back(std::string(path));
    }

    // Sort and remove duplicates from the std::vector.
    std::sort(std_paths.begin(), std_paths.end());
    std_paths.erase(std::unique(std_paths.begin(), std_paths.end()), std_paths.end());

    // Convert the result back into a rust::Vec to return to Rust.
    rust::Vec<rust::String> final_paths;
    for (const auto& path : std_paths) {
        final_paths.push_back(rust::String(path));
    }

    return final_paths;
}// File: src/conversion.rs

//! Provides utilities to convert between GFA and ODGI file formats.
//!
//! The functions in this module shell out to the `odgi` command-line executable
//! that is compiled as part of this crate's build process. This provides a stable
//! and robust way to perform complex file conversions without linking the entire
//! `odgi build` and `odgi view` logic into the library binary.
use super::graph::Error;
use std::io::Write; // Needed for the updated examples
use std::process::Command;
use tempfile::NamedTempFile; // Needed for the updated examples

/// Converts a GFA file to an ODGI file by calling `odgi build`.
///
/// This function is useful for preparing an ODGI graph from the more common
/// GFA format, making it ready to be loaded by [`super::Graph::load`].
///
/// # Arguments
///
/// * `gfa_path` - Path to the input GFA file.
/// * `odgi_path` - Path for the output ODGI file.
///
/// # Errors
///
/// Returns an [`Error`] if the `odgi build` command fails. This can happen if the
/// input file does not exist, the GFA is malformed, or the output path is
/// not writable.
///
/// # Examples
///
/// ```rust,no_run
/// use odgi_ffi::gfa_to_odgi;
/// use std::io::Write;
/// use tempfile::NamedTempFile;
///
/// // 1. Create a temporary GFA file.
/// let mut gfa_file = NamedTempFile::new().unwrap();
/// writeln!(gfa_file, "S\t1\tGATTACA").unwrap();
///
/// // 2. Prepare the path for the ODGI output file.
/// let odgi_file = NamedTempFile::new().unwrap();
/// let odgi_path = odgi_file.path();
///
/// // 3. Convert the GFA to ODGI format.
/// gfa_to_odgi(gfa_file.path().to_str().unwrap(), odgi_path.to_str().unwrap())
///     .expect("Conversion failed");
///
/// assert!(odgi_path.exists());
/// ```
pub fn gfa_to_odgi(gfa_path: &str, odgi_path: &str) -> Result<(), Error> {
    let odgi_exe = env!("ODGI_EXE");
    let output = Command::new(odgi_exe)
        .arg("build")
        .arg("-g")
        .arg(gfa_path)
        .arg("-o")
        .arg(odgi_path)
        .output()
        .map_err(|e| Error(format!("Failed to execute odgi command: {}", e)))?;

    if output.status.success() {
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        Err(Error(format!(
            "odgi build command failed for '{}': {}",
            gfa_path, stderr
        )))
    }
}

/// Converts an ODGI file to a GFA file by calling `odgi view`.
///
/// This is the reverse operation of [`gfa_to_odgi`].
///
/// # Arguments
///
/// * `odgi_path` - Path to the input ODGI file.
/// * `gfa_path` - Path for the output GFA file.
///
/// # Errors
///
/// Returns an [`Error`] if the `odgi view` command fails or if the resulting
/// GFA content cannot be written to the output file.
///
/// # Examples
///
/// ```rust,no_run
/// use odgi_ffi::{gfa_to_odgi, odgi_to_gfa};
/// use std::io::Write;
/// use tempfile::NamedTempFile;
///
/// // 1. First, create a dummy ODGI file to use as input.
/// let mut gfa_file = NamedTempFile::new().unwrap();
/// writeln!(gfa_file, "S\t1\tGATTACA").unwrap();
/// let odgi_file = NamedTempFile::new().unwrap();
/// gfa_to_odgi(gfa_file.path().to_str().unwrap(), odgi_file.path().to_str().unwrap()).unwrap();
///
/// // 2. Prepare the path for the GFA output file.
/// let gfa_out_file = NamedTempFile::new().unwrap();
/// let gfa_out_path = gfa_out_file.path();
///
/// // 3. Convert it back to GFA format.
/// odgi_to_gfa(odgi_file.path().to_str().unwrap(), gfa_out_path.to_str().unwrap())
///     .expect("Conversion failed");
///
/// assert!(gfa_out_path.exists());
/// ```
pub fn odgi_to_gfa(odgi_path: &str, gfa_path: &str) -> Result<(), Error> {
    let odgi_exe = env!("ODGI_EXE");
    let output = Command::new(odgi_exe)
        .arg("view")
        .arg("-i")
        .arg(odgi_path)
        .arg("-g") // Output in GFA format
        .output()
        .map_err(|e| Error(format!("Failed to execute odgi command: {}", e)))?;

    if output.status.success() {
        std::fs::write(gfa_path, output.stdout)
            .map_err(|e| Error(format!("Failed to write GFA output to file: {}", e)))?;
        Ok(())
    } else {
        let stderr = String::from_utf8_lossy(&output.stderr);
        Err(Error(format!(
            "odgi view command failed for '{}': {}",
            odgi_path, stderr
        )))
    }
}// File: src/odgi_wrapper.hpp
#pragma once

#include "odgi.hpp"
#include "rust/cxx.h"
#include <memory>

// OpaqueGraph is defined in the global namespace.
struct OpaqueGraph {
    std::unique_ptr<odgi::graph_t> graph;
};

// The function declarations.
// Note: odgi::Edge and odgi::PathPosition are now known types because
// our .cpp file will include the header generated by cxx.
namespace odgi {
struct Edge;
struct PathPosition;
}


std::unique_ptr<OpaqueGraph> load_graph(rust::Str path);
const odgi::graph_t& get_graph_t(const OpaqueGraph& graph);
uint64_t get_node_count(const odgi::graph_t& graph);

rust::Vec<rust::String> graph_get_path_names(const odgi::graph_t& graph);
// CORRECTED: Update signature to match the bridge
std::unique_ptr<odgi::PathPosition> graph_project(const odgi::graph_t& graph, rust::Str path_name, uint64_t pos);
rust::String graph_get_node_sequence(const odgi::graph_t& graph, uint64_t node_id);
uint64_t graph_get_node_len(const odgi::graph_t& graph, uint64_t node_id);
rust::Vec<odgi::Edge> graph_get_successors(const odgi::graph_t& graph, uint64_t node_id);
rust::Vec<odgi::Edge> graph_get_predecessors(const odgi::graph_t& graph, uint64_t node_id);
rust::Vec<rust::String> graph_get_paths_on_node(const odgi::graph_t& graph, uint64_t node_id);
uint64_t graph_get_path_length(const odgi::graph_t& graph, rust::Str path_name);
int64_t graph_get_next_node_on_path(const odgi::graph_t& graph, rust::Str path_name, uint64_t node_id);

rust::Vec<rust::String> graph_get_paths_on_edge(
    const odgi::graph_t& graph,
    uint64_t from_node, bool from_orient,
    uint64_t to_node, bool to_orient
);