// File: 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 is private. It holds the pointer to our C++ OpaqueGraph wrapper.
    inner: UniquePtr<ffi::OpaqueGraph>,
}

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)
    }
}

/// 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 {}// File: 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.
//! 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");
//!
//! // 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;
mod conversion;

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


/// Internal FFI bridge to the C++ odgi library.
#[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++" {
        // We include our own header first.
        include!("odgi-ffi/src/odgi_wrapper.hpp");
        
        // This is the C++ header that cxx generates from the Rust code above.
        // We must include it so our C++ functions know about the Rust-defined structs.
        include!("odgi-ffi/src/lib.rs.h");

        // The opaque C++ types remain the same.
        type graph_t;
        #[namespace = ""]
        type OpaqueGraph;

        // All functions are in the global namespace.
        #[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>;
    }
}// File: src/odgi.cpp
#include "odgi_wrapper.hpp"
#include <fstream>
#include <string>
#include <vector>
#include "odgi-ffi/src/lib.rs.h"

// --- 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));
    
    // CORRECTED: The method `get_path_length` does not exist in this odgi version.
    // We calculate the path length manually by summing the lengths of its nodes.
    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) { // If we've already found it, just let the iterator finish
            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;
}// 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);