use super::in_memory_error::OxigraphInMemoryError;
#[cfg(feature = "sparql")]
use crate::rdf_core::query::{QueryRDF, QueryResultFormat, QuerySolution, QuerySolutions, VarName};
use crate::rdf_core::{AsyncRDF, BuildRDF, FocusRDF, Matcher, NeighsRDF, RDFFormat, Rdf};

use crate::rdf_core::vocabs::RdfVocab;
use colored::*;
use oxjsonld::JsonLdParser;
use oxrdf::{
    BlankNode as OxBlankNode, Graph, GraphName, Literal as OxLiteral, NamedNode as OxNamedNode, NamedNodeRef,
    NamedOrBlankNode as OxSubject, NamedOrBlankNodeRef as OxSubjectRef, Quad, Term as OxTerm, TermRef,
    Triple as OxTriple, TripleRef,
};
use oxrdfio::{JsonLdProfileSet, RdfFormat, RdfSerializer};
use oxrdfxml::RdfXmlParser;
use oxttl::{NQuadsParser, NTriplesParser, TurtleParser};
use prefixmap::{PrefixMapError, prefix_map::*};
use rudof_iri::IriS;
use serde::{Serialize, ser::SerializeStruct};
use std::{
    collections::{HashMap, HashSet},
    fmt::{Debug, Display, Formatter},
    io::{self, Cursor, Write},
    str::FromStr,
    sync::Arc,
};
#[cfg(not(target_family = "wasm"))]
use std::{fs::File, io::BufReader, path::Path};
#[cfg(feature = "sparql")]
use {
    oxigraph::{
        sparql::{QueryResults, SparqlEvaluator},
        store::Store,
    },
    sparesults::QuerySolution as SparQuerySolution,
};

/// An RDF graph stored entirely in memory.
///
/// The graph is backed by [`oxrdf::Graph`] and enriched with prefix handling,
/// base IRI support, blank node generation, and optional SPARQL querying via
/// an Oxigraph [`Store`].
#[derive(Default, Clone)]
pub struct OxigraphInMemory {
    /// Optional focus term used by [`FocusRDF`] operations.
    focus: Option<OxTerm>,

    /// Underlying RDF graph.
    graph: Arc<Graph>,

    /// Prefix map used for CURIE resolution and qualification.
    pm: PrefixMap,

    /// Optional base IRI for resolving relative IRIs.
    base: Option<IriS>,

    /// Counter used to generate unique blank node identifiers.
    bnode_counter: usize,

    /// Optional Oxigraph store used for SPARQL evaluation.
    #[cfg(feature = "sparql")]
    store: Option<Store>,
}

impl Debug for OxigraphInMemory {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("OxigraphInMemory")
            .field("triples_count", &self.graph.len())
            .field("prefixmap", &self.pm)
            .field("base", &self.base)
            .finish()
    }
}

impl Serialize for OxigraphInMemory {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        let mut state = serializer.serialize_struct("SRDFGraph", 3)?;
        state.serialize_field("triples_count", &self.graph.len())?;
        state.serialize_field("prefixmap", &self.pm)?;
        state.serialize_field("base", &self.base)?;
        state.end()
    }
}

impl OxigraphInMemory {
    /// Creates an empty graph.
    pub fn new() -> Self {
        Self::default()
    }

    /// Returns the number of triples in the graph.
    pub fn len(&self) -> usize {
        self.graph.len()
    }

    /// Returns an iterator over all triples as quads in the default graph.
    pub fn quads(&self) -> impl Iterator<Item = Quad> + '_ {
        let graph_name = GraphName::DefaultGraph;
        self.graph.iter().map(move |t| triple_to_quad(t, graph_name.clone()))
    }

    /// Returns `true` if the graph contains no triples.
    pub fn is_empty(&self) -> bool {
        self.graph.is_empty()
    }

    pub fn set_default_base_prefixes(&mut self, default_base: &Option<IriS>) {
        self.pm.set_default_base(default_base);
    }

    /// Merges RDF data from a reader into the current graph.
    ///
    /// The parsing behavior depends on [`RDFFormat`] and [`ReaderMode`].
    /// Prefixes and base IRI are merged when available.
    ///
    /// # Parameters
    ///
    /// * `reader` - Input stream containing RDF data
    /// * `source_name` - Name used for error reporting
    /// * `format` - RDF serialization format
    /// * `base` - Optional base IRI for resolving relative IRIs
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails in strict mode or if I/O errors occur.
    pub fn merge_from_reader<R: io::Read>(
        &mut self,
        reader: &mut R,
        source_name: &str,
        format: &RDFFormat,
        base: Option<&str>,
        reader_mode: &ReaderMode,
    ) -> Result<(), OxigraphInMemoryError> {
        match format {
            RDFFormat::Turtle => {
                self.parse_turtle(reader, source_name, base, reader_mode)?;
            },
            RDFFormat::NTriples => {
                self.parse_ntriples(reader, reader_mode)?;
            },
            RDFFormat::Rdfxml => {
                self.parse_rdfxml(reader, reader_mode)?;
            },
            RDFFormat::TriG => {
                todo!();
            },
            RDFFormat::N3 => {
                todo!();
            },
            RDFFormat::NQuads => {
                self.parse_nquads(reader, reader_mode)?;
            },
            RDFFormat::JsonLd => {
                self.parse_jsonld(reader, reader_mode)?;
            },
        }
        if let Some(base) = base {
            let default_base = Some(IriS::new_unchecked(base));
            self.set_default_base_prefixes(&default_base);
        }
        Ok(())
    }

    /// Parses Turtle data and merges it into the graph.
    ///
    /// # Parameters
    ///
    /// * `reader` - Input stream containing Turtle data
    /// * `source_name` - Name used for error reporting
    /// * `base` - Optional base IRI for resolving relative IRIs
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails in strict mode.
    fn parse_turtle<R: io::Read>(
        &mut self,
        reader: &mut R,
        source_name: &str,
        base: Option<&str>,
        reader_mode: &ReaderMode,
    ) -> Result<(), OxigraphInMemoryError> {
        let turtle_parser = match base {
            None => TurtleParser::new().lenient(),
            Some(iri) => TurtleParser::new().lenient().with_base_iri(iri)?,
        };

        let mut turtle_reader = turtle_parser.for_reader(reader);
        let graph = Arc::make_mut(&mut self.graph);

        for triple_result in turtle_reader.by_ref() {
            let triple = match handle_parse_error(triple_result, reader_mode, |e| {
                OxigraphInMemoryError::TurtleParseError {
                    source_name: source_name.to_string(),
                    error: e,
                }
            })? {
                Some(t) => t,
                None => continue,
            };
            let triple_ref = triple.as_ref();
            if let Err(e) = validate_triple_iris(triple_ref) {
                match handle_parse_error(Err::<(), _>(e), reader_mode, |e| {
                    OxigraphInMemoryError::TurtleParseError {
                        source_name: source_name.to_string(),
                        error: format!("Invalid IRI in triple: {e}"),
                    }
                })? {
                    Some(_) => unreachable!(),
                    None => continue,
                }
            }
            graph.insert(triple_ref);
        }

        let prefixes: HashMap<&str, &str> = turtle_reader.prefixes().collect();
        self.base = match (&self.base, base) {
            (None, None) => None,
            (Some(b), None) => Some(b.clone()),
            (_, Some(b)) => Some(IriS::new_unchecked(b)),
        };
        self.merge_prefixes(prefixes.try_into()?);

        Ok(())
    }

    /// Parses N-Triples data and merges it into the graph.
    ///
    /// # Parameters
    ///
    /// * `reader` - Input stream containing N-Triples data
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails in strict mode.
    fn parse_ntriples<R: io::Read>(
        &mut self,
        reader: &mut R,
        reader_mode: &ReaderMode,
    ) -> Result<(), OxigraphInMemoryError> {
        let parser = NTriplesParser::new();
        let mut nt_reader = parser.for_reader(reader);
        let graph = Arc::make_mut(&mut self.graph);

        for triple_result in nt_reader.by_ref() {
            let triple =
                match handle_parse_error(triple_result, reader_mode, |e| OxigraphInMemoryError::NTriplesError {
                    data: "Reading N-Triples".to_string(),
                    error: e,
                })? {
                    Some(t) => t,
                    None => continue,
                };
            graph.insert(triple.as_ref());
        }

        Ok(())
    }

    /// Parses RDF/XML data and merges it into the graph.
    ///
    /// # Parameters
    ///
    /// * `reader` - Input stream containing RDF/XML data
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails in strict mode.
    fn parse_rdfxml<R: io::Read>(
        &mut self,
        reader: &mut R,
        reader_mode: &ReaderMode,
    ) -> Result<(), OxigraphInMemoryError> {
        let parser = RdfXmlParser::new();
        let mut xml_reader = parser.for_reader(reader);
        let graph = Arc::make_mut(&mut self.graph);

        for triple_result in xml_reader.by_ref() {
            let triple = match handle_parse_error(triple_result, reader_mode, |e| OxigraphInMemoryError::RDFXMLError {
                data: "Reading RDF/XML".to_string(),
                error: e,
            })? {
                Some(t) => t,
                None => continue,
            };
            let triple_ref = cnv_triple(&triple);
            graph.insert(triple_ref);
        }

        Ok(())
    }

    /// Parses N-Quads data and merges it into the graph.
    ///
    /// # Parameters
    ///
    /// * `reader` - Input stream containing N-Quads data
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails in strict mode.
    fn parse_nquads<R: io::Read>(
        &mut self,
        reader: &mut R,
        reader_mode: &ReaderMode,
    ) -> Result<(), OxigraphInMemoryError> {
        let parser = NQuadsParser::new();
        let mut nq_reader = parser.for_reader(reader);
        let graph = Arc::make_mut(&mut self.graph);

        for triple_result in nq_reader.by_ref() {
            let triple = match handle_parse_error(triple_result, reader_mode, |e| OxigraphInMemoryError::NQuadsError {
                data: "Reading NQuads".to_string(),
                error: e,
            })? {
                Some(t) => t,
                None => continue,
            };
            graph.insert(triple.as_ref());
        }

        Ok(())
    }

    /// Parses JSON-LD data and merges it into the graph.
    ///
    /// # Parameters
    ///
    /// * `reader` - Input stream containing JSON-LD data
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails in strict mode.
    fn parse_jsonld<R: io::Read>(
        &mut self,
        reader: &mut R,
        reader_mode: &ReaderMode,
    ) -> Result<(), OxigraphInMemoryError> {
        let parser = JsonLdParser::new();
        let mut jsonld_reader = parser.for_reader(reader);
        let graph = Arc::make_mut(&mut self.graph);

        for triple_result in jsonld_reader.by_ref() {
            let triple = match handle_parse_error(triple_result, reader_mode, |e| OxigraphInMemoryError::JsonLDError {
                data: "Reading JSON-LD".to_string(),
                error: e,
            })? {
                Some(t) => t,
                None => continue,
            };
            graph.insert(triple.as_ref());
        }

        Ok(())
    }

    /// Builds a new graph from a reader.
    ///
    /// This is a convenience constructor that creates an empty graph and merges
    /// data from the reader.
    ///
    /// # Parameters
    ///
    /// * `read` - Input stream containing RDF data
    /// * `source_name` - Name used for error reporting
    /// * `format` - RDF serialization format
    /// * `base` - Optional base IRI for resolving relative IRIs
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails.
    pub fn from_reader<R: io::Read>(
        read: &mut R,
        source_name: &str,
        format: &RDFFormat,
        base: Option<&str>,
        reader_mode: &ReaderMode,
    ) -> Result<OxigraphInMemory, OxigraphInMemoryError> {
        let mut srdf_graph = OxigraphInMemory::new();
        srdf_graph.merge_from_reader(read, source_name, format, base, reader_mode)?;
        Ok(srdf_graph)
    }

    /// Resolves a CURIE or prefixed name into a full IRI.
    ///
    /// Uses the graph's prefix map to expand the prefixed form.
    ///
    /// # Parameters
    ///
    /// * `str` - The CURIE or prefixed name to resolve (e.g., "ex:Alice")
    ///
    /// # Errors
    ///
    /// Returns an error if the prefix is not defined or if the string format is invalid.
    pub fn resolve(&self, str: &str) -> Result<OxNamedNode, OxigraphInMemoryError> {
        let r = self.pm.resolve(str)?;
        Ok(Self::cnv_iri(r))
    }

    /// Formats a blank node for display with color.
    ///
    /// Returns a green-colored string representation.
    ///
    /// # Parameters
    ///
    /// * `bn` - The blank node to format
    pub fn show_blanknode(&self, bn: &OxBlankNode) -> String {
        format!("{}", bn.to_string().green())
    }

    /// Formats a literal for display with color.
    ///
    /// Returns a red-colored string representation.
    ///
    /// # Parameters
    ///
    /// * `lit` - The literal to format
    pub fn show_literal(&self, lit: &OxLiteral) -> String {
        format!("{}", lit.to_string().red())
    }

    /// Builds a graph from a string.
    ///
    /// # Parameters
    ///
    /// * `data` - RDF data as a string
    /// * `format` - RDF serialization format
    /// * `base` - Optional base IRI for resolving relative IRIs
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if parsing fails.
    pub fn from_str(
        data: &str,
        format: &RDFFormat,
        base: Option<&str>,
        reader_mode: &ReaderMode,
    ) -> Result<OxigraphInMemory, OxigraphInMemoryError> {
        Self::from_reader(&mut Cursor::new(data), "String", format, base, reader_mode)
    }

    /// Converts an [`IriS`] into an Oxigraph named node.
    ///
    /// # Parameters
    ///
    /// * `iri` - The IRI to convert
    ///
    /// # Returns
    ///
    /// An Oxigraph named node representation of the IRI.
    fn cnv_iri(iri: IriS) -> OxNamedNode {
        OxNamedNode::new_unchecked(iri.as_str())
    }

    /// Adds a triple using borrowed references.
    ///
    /// This method avoids cloning the triple components by working with references.
    ///
    /// # Parameters
    ///
    /// * `subj` - Triple subject (named node or blank node)
    /// * `pred` - Triple predicate (named node)
    /// * `obj` - Triple object (term)
    ///
    /// # Errors
    ///
    /// This method currently always returns `Ok(())`.
    pub fn add_triple_ref<'a, S, P, O>(&mut self, subj: S, pred: P, obj: O) -> Result<(), OxigraphInMemoryError>
    where
        S: Into<OxSubjectRef<'a>>,
        P: Into<NamedNodeRef<'a>>,
        O: Into<TermRef<'a>>,
    {
        let triple = TripleRef::new(subj.into(), pred.into(), obj.into());
        Arc::make_mut(&mut self.graph).insert(triple);
        Ok(())
    }
}

#[cfg(not(target_family = "wasm"))]
impl OxigraphInMemory {
    /// Merges RDF data from a filesystem path.
    ///
    /// Opens the file and delegates to [`merge_from_reader`](Self::merge_from_reader).
    ///
    /// # Parameters
    ///
    /// * `path` - Path to the RDF file
    /// * `format` - RDF serialization format
    /// * `base` - Optional base IRI for resolving relative IRIs
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if the file cannot be opened or if parsing fails.
    pub fn merge_from_path<P: AsRef<Path>>(
        &mut self,
        path: P,
        format: &RDFFormat,
        base: Option<&str>,
        reader_mode: &ReaderMode,
    ) -> Result<(), OxigraphInMemoryError> {
        let path_ref = path.as_ref();
        let file = File::open(path_ref).map_err(|e| OxigraphInMemoryError::ReadingPathError {
            path_name: path_ref.display().to_string(),
            error: e,
        })?;
        let mut reader = BufReader::new(file);
        self.merge_from_reader(&mut reader, &path_ref.display().to_string(), format, base, reader_mode)
    }

    /// Builds a graph from a filesystem path.
    ///
    /// Creates a new empty graph and merges data from the file.
    ///
    /// # Parameters
    ///
    /// * `path` - Path to the RDF file
    /// * `format` - RDF serialization format
    /// * `base` - Optional base IRI for resolving relative IRIs
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if the file cannot be opened or if parsing fails.
    pub fn from_path<P: AsRef<Path>>(
        path: P,
        format: &RDFFormat,
        base: Option<&str>,
        reader_mode: &ReaderMode,
    ) -> Result<OxigraphInMemory, OxigraphInMemoryError> {
        let path_ref = path.as_ref();
        let file = File::open(path_ref).map_err(|e| OxigraphInMemoryError::ReadingPathError {
            path_name: path_ref.display().to_string(),
            error: e,
        })?;
        let mut reader = BufReader::new(file);
        Self::from_reader(&mut reader, &path_ref.display().to_string(), format, base, reader_mode)
    }

    /// Parses data from a relative path within a folder.
    ///
    /// Convenience method that joins the data file name with the folder path.
    ///
    /// # Parameters
    ///
    /// * `data` - Relative file name within the folder
    /// * `format` - RDF serialization format
    /// * `folder` - Base directory path
    /// * `base` - Optional base IRI for resolving relative IRIs
    /// * `reader_mode` - Controls error handling (strict or lax)
    ///
    /// # Errors
    ///
    /// Returns an error if the file cannot be opened or if parsing fails.
    pub fn parse_data(
        data: &str,
        format: &RDFFormat,
        folder: &Path,
        base: Option<&str>,
        reader_mode: &ReaderMode,
    ) -> Result<OxigraphInMemory, OxigraphInMemoryError> {
        let data_path = folder.join(data);
        Self::from_path(&data_path, format, base, reader_mode)
    }
}

impl OxigraphInMemory {
    /// Returns a reference to the prefix map.
    pub fn prefixmap(&self) -> &PrefixMap {
        &self.pm
    }

    /// Lazily build the embedded SPARQL store from the in-memory graph.
    ///
    /// SPARQL evaluation on the in-memory backend goes through an
    /// `oxigraph::store::Store`. It is not built eagerly because indexing the
    /// quads costs memory and most callers never run SPARQL. Run this once
    /// before issuing `query_select`, `query_ask`, or `query_construct`.
    #[cfg(feature = "sparql")]
    pub fn ensure_store(&mut self) -> Result<(), OxigraphInMemoryError> {
        if self.store.is_some() {
            return Ok(());
        }
        let store = Store::new()?;
        let mut loader = store.bulk_loader();
        loader.load_quads(self.quads())?;
        loader.commit()?;
        self.store = Some(store);
        Ok(())
    }
}

/// Implementation of the core `Rdf` trait.
///
/// This implementation provides the fundamental RDF operations including type definitions,
/// prefix resolution, and term qualification (converting full IRIs to prefixed names).
impl Rdf for OxigraphInMemory {
    type IRI = OxNamedNode;
    type BNode = OxBlankNode;
    type Literal = OxLiteral;
    type Subject = OxSubject;
    type Term = OxTerm;
    type Triple = OxTriple;
    type Err = OxigraphInMemoryError;

    /// Resolves a prefix and local name to a full IRI.
    ///
    /// # Parameters
    ///
    /// * `prefix` - The namespace prefix (e.g., "foaf")
    /// * `local` - The local name (e.g., "Person")
    ///
    /// # Returns
    ///
    /// The full IRI (e.g., "http://xmlns.com/foaf/0.1/Person")
    ///
    /// # Errors
    ///
    /// Returns an error if the prefix is not defined in the prefix map.
    fn resolve_prefix_local(&self, prefix: &str, local: &str) -> Result<IriS, PrefixMapError> {
        let iri = self.pm.resolve_prefix_local(prefix, local)?;
        Ok(iri)
    }

    /// Converts a full IRI to a qualified (prefixed) name if possible.
    ///
    /// If the IRI matches a known namespace prefix, it returns a shortened form
    /// (e.g., "foaf:Person"). Otherwise, it returns the full IRI.
    ///
    /// # Parameters
    ///
    /// * `node` - The named node to qualify
    ///
    /// # Returns
    ///
    /// A string representation, either prefixed or full IRI.
    fn qualify_iri(&self, node: &Self::IRI) -> String {
        let iri = IriS::from_str(node.as_str()).expect("OxNamedNode should always contain valid IRI");
        self.pm.qualify(&iri)
    }

    /// Converts a subject (named node or blank node) to a qualified string.
    ///
    /// Named nodes are qualified using the prefix map. Blank nodes are formatted
    /// with color for display.
    ///
    /// # Parameters
    ///
    /// * `subj` - The subject to qualify
    ///
    /// # Returns
    ///
    /// A string representation of the subject.
    fn qualify_subject(&self, subj: &OxSubject) -> String {
        match subj {
            OxSubject::BlankNode(bn) => self.show_blanknode(bn),
            OxSubject::NamedNode(n) => self.qualify_iri(n),
        }
    }

    /// Converts an RDF term (named node, blank node, or literal) to a qualified string.
    ///
    /// Different term types are formatted differently:
    /// - Named nodes: qualified using prefix map
    /// - Blank nodes: formatted with green color
    /// - Literals: formatted with red color
    /// - RDF-star triples: not yet supported
    ///
    /// # Parameters
    ///
    /// * `term` - The term to qualify
    ///
    /// # Returns
    ///
    /// A string representation of the term.
    fn qualify_term(&self, term: &OxTerm) -> String {
        match term {
            OxTerm::BlankNode(bn) => self.show_blanknode(bn),
            OxTerm::Literal(lit) => self.show_literal(lit),
            OxTerm::NamedNode(n) => self.qualify_iri(n),
            OxTerm::Triple(_) => unimplemented!("RDF-star triples not yet supported"),
        }
    }

    /// Returns a reference to the graph's prefix map.
    ///
    /// # Returns
    ///
    /// `Some(&PrefixMap)` containing all defined namespace prefixes.
    fn prefixmap(&self) -> Option<PrefixMap> {
        Some(self.pm.clone())
    }
}

/// Implementation of the `NeighsRDF` trait for navigating graph neighbors.
///
/// This implementation provides methods to iterate over triples in the graph,
/// optionally filtered by subject, predicate, or object patterns.
impl NeighsRDF for OxigraphInMemory {
    /// Returns an iterator over all triples in the graph.
    ///
    /// # Returns
    ///
    /// An iterator that yields owned triples.
    fn triples(&self) -> Result<impl Iterator<Item = Self::Triple>, Self::Err> {
        Ok(self.graph.iter().map(TripleRef::into_owned))
    }

    /// Returns an iterator over triples matching a pattern.
    ///
    /// Uses the appropriate oxrdf index based on which positions are concrete
    /// vs wildcard, giving O(k) complexity instead of a full O(n) table scan.
    ///
    /// # Parameters
    ///
    /// * `subject` - Pattern matcher for the subject
    /// * `predicate` - Pattern matcher for the predicate
    /// * `object` - Pattern matcher for the object
    ///
    /// # Returns
    ///
    /// An iterator that yields triples matching all three patterns.
    fn triples_matching<S, P, O>(
        &self,
        subject: &S,
        predicate: &P,
        object: &O,
    ) -> Result<impl Iterator<Item = Self::Triple> + '_, Self::Err>
    where
        S: Matcher<Self::Subject>,
        P: Matcher<Self::IRI>,
        O: Matcher<Self::Term>,
    {
        // Dispatch to the tightest available index to avoid full scans.
        let result: Box<dyn Iterator<Item = OxTriple> + '_> = match (subject.value(), predicate.value(), object.value())
        {
            (Some(s), Some(p), Some(o)) => {
                let found = self.graph.contains(TripleRef::new(
                    OxSubjectRef::from(s),
                    NamedNodeRef::from(p),
                    TermRef::from(o),
                ));
                let s = s.clone();
                let p = p.clone();
                let o = o.clone();
                Box::new(found.then(|| OxTriple::new(s, p, o)).into_iter())
            },
            (Some(s), Some(p), None) => {
                let s_owned = s.clone();
                let p_owned = p.clone();
                Box::new(
                    self.graph
                        .objects_for_subject_predicate(OxSubjectRef::from(s), NamedNodeRef::from(p))
                        .map(move |o| OxTriple::new(s_owned.clone(), p_owned.clone(), o.into_owned())),
                )
            },
            (Some(s), None, Some(o)) => {
                let o_owned = o.clone();
                Box::new(
                    self.graph
                        .triples_for_subject(OxSubjectRef::from(s))
                        .map(TripleRef::into_owned)
                        .filter(move |t| t.object == o_owned),
                )
            },
            (Some(s), None, None) => Box::new(
                self.graph
                    .triples_for_subject(OxSubjectRef::from(s))
                    .map(TripleRef::into_owned),
            ),
            (None, Some(p), Some(o)) => {
                let p_owned = p.clone();
                let o_owned = o.clone();
                Box::new(
                    self.graph
                        .subjects_for_predicate_object(NamedNodeRef::from(p), TermRef::from(o))
                        .map(move |s| OxTriple::new(s.into_owned(), p_owned.clone(), o_owned.clone())),
                )
            },
            (None, Some(p), None) => Box::new(
                self.graph
                    .triples_for_predicate(NamedNodeRef::from(p))
                    .map(TripleRef::into_owned),
            ),
            (None, None, Some(o)) => Box::new(
                self.graph
                    .triples_for_object(TermRef::from(o))
                    .map(TripleRef::into_owned),
            ),
            (None, None, None) => Box::new(self.graph.iter().map(TripleRef::into_owned)),
        };

        Ok(result)
    }
}

impl AsyncRDF for OxigraphInMemory {
    type IRI = OxNamedNode;
    type BNode = OxBlankNode;
    type Literal = OxLiteral;
    type Subject = OxSubject;
    type Term = OxTerm;
    type Err = OxigraphInMemoryError;

    /// Returns all predicates associated with a given subject.
    ///
    /// # Parameters
    ///
    /// * `subject` - The subject node to query
    ///
    /// # Errors
    ///
    /// This method currently always returns `Ok`.
    async fn get_predicates_subject(&self, subject: &OxSubject) -> Result<HashSet<OxNamedNode>, OxigraphInMemoryError> {
        let mut results = HashSet::new();
        for triple in self.graph.triples_for_subject(subject) {
            let predicate: OxNamedNode = triple.predicate.to_owned().into();
            results.insert(predicate);
        }
        Ok(results)
    }

    /// Returns all objects for a given subject-predicate pair.
    ///
    /// # Parameters
    ///
    /// * `subject` - The subject node
    /// * `pred` - The predicate node
    ///
    /// # Errors
    ///
    /// This method currently always returns `Ok`.
    async fn get_objects_for_subject_predicate(
        &self,
        subject: &OxSubject,
        pred: &OxNamedNode,
    ) -> Result<HashSet<OxTerm>, OxigraphInMemoryError> {
        let mut results = HashSet::new();
        for triple in self.graph.triples_for_subject(subject) {
            let predicate: OxNamedNode = triple.predicate.to_owned().into();
            if predicate.eq(pred) {
                let object: OxTerm = triple.object.to_owned().into();
                results.insert(object);
            }
        }
        Ok(results)
    }

    /// Returns all subjects for a given object-predicate pair.
    ///
    /// # Parameters
    ///
    /// * `object` - The object term
    /// * `pred` - The predicate node
    ///
    /// # Errors
    ///
    /// This method currently always returns `Ok`.
    async fn get_subjects_for_object_predicate(
        &self,
        object: &OxTerm,
        pred: &OxNamedNode,
    ) -> Result<HashSet<OxSubject>, OxigraphInMemoryError> {
        let mut results = HashSet::new();
        for triple in self.graph.triples_for_object(object) {
            let predicate: OxNamedNode = triple.predicate.to_owned().into();
            if predicate.eq(pred) {
                let subject: OxSubject = triple.subject.to_owned().into();
                results.insert(subject);
            }
        }
        Ok(results)
    }
}

/// Implementation of the `FocusRDF` trait for managing a focus term.
///
/// The focus term is used to track a specific RDF term of interest during graph operations.
/// This can be useful for navigation, querying, or maintaining context during traversals.
impl FocusRDF for OxigraphInMemory {
    /// Sets the focus term for this graph.
    ///
    /// # Parameters
    ///
    /// * `focus` - The term to set as the current focus
    fn set_focus(&mut self, focus: &Self::Term) {
        self.focus = Some(focus.clone());
    }

    /// Returns the current focus term, if one is set.
    ///
    /// # Returns
    ///
    /// * `Some(&Term)` - If a focus term has been set
    /// * `None` - If no focus term is currently set
    fn get_focus(&self) -> Option<&Self::Term> {
        self.focus.as_ref()
    }
}

/// Implementation of the `BuildRDF` trait for constructing and modifying RDF graphs.
///
/// This implementation provides methods to build RDF graphs programmatically by adding
/// prefixes, base IRIs, blank nodes, triples, and types. It also supports serialization
/// to various RDF formats.
impl BuildRDF for OxigraphInMemory {
    /// Sets the base IRI for the graph.
    ///
    /// The base IRI is used to resolve relative IRIs during parsing and serialization.
    ///
    /// # Parameters
    ///
    /// * `base` - Optional base IRI to set
    fn add_base(&mut self, base: &Option<IriS>) {
        self.base = base.clone();
    }

    /// Adds a prefix mapping to the graph's prefix map.
    ///
    /// Prefix mappings are used to abbreviate IRIs in qualified form (CURIEs).
    ///
    /// # Parameters
    ///
    /// * `alias` - The prefix alias (e.g., "ex", "foaf")
    /// * `iri` - The full IRI that the alias represents
    ///
    /// # Errors
    ///
    // Returns an error if the prefix cannot be added to the prefix map.
    fn add_prefix(&mut self, alias: &str, iri: &IriS) {
        self.pm.add_prefix(alias, iri.clone());
    }

    /// Replaces the entire prefix map with a new one.
    ///
    /// # Parameters
    ///
    /// * `prefix_map` - The new prefix map to use
    fn set_prefix_map(&mut self, prefix_map: PrefixMap) {
        self.pm = prefix_map;
    }

    /// Merges a prefix map into the graph's prefix map.
    ///
    /// # Parameters
    ///
    /// * `prefixmap` - The prefix map to merge
    ///
    /// # Errors
    ///
    // Returns an error if merging fails due to conflicting prefixes.
    fn merge_prefixes(&mut self, prefix_map: PrefixMap) {
        self.pm.merge(prefix_map);
    }

    /// Generates a new unique blank node.
    ///
    /// Each call to this method increments an internal counter to ensure uniqueness.
    ///
    /// # Errors
    ///
    /// Returns an error if the blank node counter overflows (extremely unlikely).
    fn add_bnode(&mut self) -> Result<Self::BNode, Self::Err> {
        self.bnode_counter += 1;
        let bn = u128::try_from(self.bnode_counter).map_err(|_| OxigraphInMemoryError::BlankNodeId {
            msg: format!("Blank node counter overflow: {}", self.bnode_counter),
        })?;
        Ok(OxBlankNode::new_from_unique_id(bn))
    }

    /// Adds a triple to the graph.
    ///
    /// # Parameters
    ///
    /// * `subj` - The subject of the triple
    /// * `pred` - The predicate of the triple
    /// * `obj` - The object of the triple
    fn add_triple<S, P, O>(&mut self, subj: S, pred: P, obj: O) -> Result<(), Self::Err>
    where
        S: Into<Self::Subject>,
        P: Into<Self::IRI>,
        O: Into<Self::Term>,
    {
        let triple = OxTriple::new(subj.into(), pred.into(), obj.into());
        Arc::make_mut(&mut self.graph).insert(&triple);
        Ok(())
    }

    /// Removes a triple from the graph.
    ///
    /// # Parameters
    ///
    /// * `subj` - The subject of the triple to remove
    /// * `pred` - The predicate of the triple to remove
    /// * `obj` - The object of the triple to remove
    fn remove_triple<S, P, O>(&mut self, subj: S, pred: P, obj: O) -> Result<(), Self::Err>
    where
        S: Into<Self::Subject>,
        P: Into<Self::IRI>,
        O: Into<Self::Term>,
    {
        let triple = OxTriple::new(subj.into(), pred.into(), obj.into());
        Arc::make_mut(&mut self.graph).remove(&triple);
        Ok(())
    }

    /// Adds an `rdf:type` assertion to the graph.
    ///
    /// This is a convenience method that adds a triple with `rdf:type` as the predicate.
    ///
    /// # Parameters
    ///
    /// * `node` - The subject that has the type
    /// * `type_` - The type (class) of the subject
    fn add_type<S, T>(&mut self, node: S, type_: T) -> Result<(), Self::Err>
    where
        S: Into<Self::Subject>,
        T: Into<Self::Term>,
    {
        let triple = OxTriple::new(node.into(), rdf_type(), type_.into());
        Arc::make_mut(&mut self.graph).insert(&triple);
        Ok(())
    }

    /// Creates a new empty graph.
    ///
    /// # Returns
    ///
    /// A new `OxigraphInMemory` with no triples, prefixes, or base IRI.
    fn empty() -> Self {
        OxigraphInMemory {
            focus: None,
            graph: Graph::new().into(),
            pm: PrefixMap::new(),
            base: None,
            bnode_counter: 0,
            #[cfg(feature = "sparql")]
            store: None,
        }
    }

    /// Serializes the graph to a writer in the specified RDF format.
    ///
    /// All prefixes defined in the graph's prefix map are included in the serialization.
    ///
    /// # Parameters
    ///
    /// * `format` - The RDF serialization format to use
    /// * `write` - The writer to serialize to
    ///
    /// # Errors
    ///
    /// Returns an error if serialization fails or if the writer encounters an I/O error.
    fn serialize<W: Write>(&self, format: &RDFFormat, write: &mut W) -> Result<(), Self::Err> {
        let mut serializer = RdfSerializer::from_format(cnv_rdf_format(format));

        for (prefix, iri) in &self.pm.map {
            serializer = serializer.with_prefix(prefix, iri.as_str())?;
        }

        let mut writer = serializer.for_writer(write);
        for triple in self.graph.iter() {
            writer.serialize_triple(triple)?;
        }
        writer.finish()?;
        Ok(())
    }
}

/// Converts an RDF format enum to the Oxigraph RdfFormat type.
///
/// # Parameters
///
/// * `rdf_format` - The RDF format to convert
///
/// # Returns
///
/// The corresponding Oxigraph RdfFormat.
fn cnv_rdf_format(rdf_format: &RDFFormat) -> RdfFormat {
    match rdf_format {
        RDFFormat::NTriples => RdfFormat::NTriples,
        RDFFormat::Turtle => RdfFormat::Turtle,
        RDFFormat::Rdfxml => RdfFormat::RdfXml,
        RDFFormat::TriG => RdfFormat::TriG,
        RDFFormat::N3 => RdfFormat::N3,
        RDFFormat::NQuads => RdfFormat::NQuads,
        RDFFormat::JsonLd => RdfFormat::JsonLd {
            profile: JsonLdProfileSet::empty(),
        },
    }
}

/// Returns the RDF type predicate IRI.
///
/// # Returns
///
/// An Oxigraph named node representing `rdf:type`.
fn rdf_type() -> OxNamedNode {
    OxNamedNode::new_unchecked(RdfVocab::RDF_TYPE)
}

/// Converts a triple reference to a quad with the specified graph name.
///
/// # Parameters
///
/// * `t` - The triple reference to convert
/// * `graph_name` - The graph name to use for the quad
///
/// # Returns
///
/// A quad representing the triple in the specified graph.
fn triple_to_quad(t: TripleRef, graph_name: GraphName) -> Quad {
    let subj: oxrdf::NamedOrBlankNode = t.subject.into();
    let pred: oxrdf::NamedNode = t.predicate.into();
    let obj: oxrdf::Term = t.object.into();
    Quad::new(subj, pred, obj, graph_name)
}

/// Helper function to handle parse errors consistently.
///
/// This function implements a consistent error handling strategy across all parsers.
/// In strict mode, errors are propagated. In lax mode, errors are logged and parsing continues.
///
/// # Parameters
///
/// * `result` - The parse result to handle
/// * `reader_mode` - Controls error handling behavior
/// * `error_constructor` - Function to construct an appropriate error type
///
/// # Returns
///
/// * `Ok(Some(value))` - Parsing succeeded
/// * `Ok(None)` - Parsing failed in lax mode (skip this item)
/// * `Err(error)` - Parsing failed in strict mode
fn validate_triple_iris(triple: TripleRef) -> Result<(), String> {
    if let oxrdf::NamedOrBlankNodeRef::NamedNode(n) = triple.subject {
        OxNamedNode::new(n.as_str()).map_err(|e| e.to_string())?;
    }
    OxNamedNode::new(triple.predicate.as_str()).map_err(|e| e.to_string())?;
    if let TermRef::NamedNode(n) = triple.object {
        OxNamedNode::new(n.as_str()).map_err(|e| e.to_string())?;
    }
    Ok(())
}

fn handle_parse_error<T, E: std::fmt::Display>(
    result: Result<T, E>,
    reader_mode: &ReaderMode,
    error_constructor: impl FnOnce(String) -> OxigraphInMemoryError,
) -> Result<Option<T>, OxigraphInMemoryError> {
    match result {
        Ok(val) => Ok(Some(val)),
        Err(e) => {
            if reader_mode.is_strict() {
                Err(error_constructor(e.to_string()))
            } else {
                Ok(None)
            }
        },
    }
}

/// Reader mode when parsing RDF data files.
///
/// Controls how parsing errors are handled during RDF data ingestion.
///
/// # Variants
///
/// * `Strict` - Stops when there is an error
/// * `Lax` - Emits a warning and continues processing
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug)]
pub enum ReaderMode {
    /// Stops when there is an error.
    #[default]
    Strict,

    /// Emits a warning and continues processing.
    Lax,
}

impl ReaderMode {
    /// Returns `true` if this is strict mode.
    pub fn is_strict(&self) -> bool {
        matches!(self, ReaderMode::Strict)
    }
}

impl FromStr for ReaderMode {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "strict" => Ok(ReaderMode::Strict),
            "lax" => Ok(ReaderMode::Lax),
            _ => Err(format!("Unknown reader mode format: {s}. Expected 'strict' or 'lax'")),
        }
    }
}

impl Display for ReaderMode {
    fn fmt(&self, dest: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
        match &self {
            ReaderMode::Strict => write!(dest, "strict"),
            ReaderMode::Lax => write!(dest, "lax"),
        }
    }
}

/// Converts an owned triple to a triple reference.
///
/// # Parameters
///
/// * `t` - The owned triple to convert
///
/// # Returns
///
/// A triple reference with the same subject, predicate, and object.
fn cnv_triple(t: &OxTriple) -> TripleRef<'_> {
    TripleRef::new(
        OxSubjectRef::from(&t.subject),
        NamedNodeRef::from(&t.predicate),
        TermRef::from(&t.object),
    )
}

#[cfg(feature = "sparql")]
impl QueryRDF for OxigraphInMemory {
    fn query_construct(&self, query_str: &str, _format: &QueryResultFormat) -> Result<String, OxigraphInMemoryError>
    where
        Self: Sized,
    {
        // CONSTRUCT against the embedded store. Returns empty when no store
        // has been built yet; callers should `ensure_store` first if they
        // need real results.
        let Some(store) = &self.store else {
            return Ok(String::new());
        };
        let results = SparqlEvaluator::new()
            .parse_query(query_str)
            .map_err(|e| OxigraphInMemoryError::ParsingQueryError {
                msg: format!("Error parsing query: {e}"),
            })?
            .on_store(store)
            .execute()
            .map_err(|e| OxigraphInMemoryError::RunningQueryError {
                query: query_str.to_string(),
                msg: format!("Error executing query: {e}"),
            })?;

        // Serialize CONSTRUCT output as Turtle. The QueryResultFormat parameter
        // is forwarded by the trait but Oxigraph's CONSTRUCT path emits triples,
        // not solutions, so we pin the wire format here.
        match results {
            QueryResults::Graph(triples) => {
                use oxrdfio::{RdfFormat, RdfSerializer};
                let mut buffer = Vec::new();
                let mut writer = RdfSerializer::from_format(RdfFormat::Turtle).for_writer(&mut buffer);
                for triple in triples {
                    let triple = triple.map_err(|e| OxigraphInMemoryError::RunningQueryError {
                        query: query_str.to_string(),
                        msg: format!("Error iterating CONSTRUCT results: {e}"),
                    })?;
                    writer
                        .serialize_triple(triple.as_ref())
                        .map_err(|e| OxigraphInMemoryError::RunningQueryError {
                            query: query_str.to_string(),
                            msg: format!("Error serializing CONSTRUCT triple: {e}"),
                        })?;
                }
                writer.finish().map_err(|e| OxigraphInMemoryError::RunningQueryError {
                    query: query_str.to_string(),
                    msg: format!("Error finalizing CONSTRUCT output: {e}"),
                })?;
                Ok(String::from_utf8(buffer).unwrap_or_default())
            },
            _ => Ok(String::new()),
        }
    }

    /// Executes a SPARQL SELECT query against the embedded store.
    ///
    /// Returns empty solutions if no store has been built. Run
    /// [`OxigraphInMemory::ensure_store`] first to populate it.
    fn query_select(&self, query_str: &str) -> Result<QuerySolutions<OxigraphInMemory>, OxigraphInMemoryError>
    where
        Self: Sized,
    {
        let mut sols = QuerySolutions::empty();

        if let Some(store) = &self.store {
            let parsed_query = SparqlEvaluator::new().parse_query(query_str).map_err(|e| {
                OxigraphInMemoryError::ParsingQueryError {
                    msg: format!("Error parsing query: {}", e),
                }
            })?;

            let query_results =
                parsed_query
                    .on_store(store)
                    .execute()
                    .map_err(|e| OxigraphInMemoryError::RunningQueryError {
                        query: query_str.to_string(),
                        msg: format!("Error executing query: {}", e),
                    })?;

            let solutions = cnv_query_results(query_results)?;

            sols.extend(solutions, self.prefixmap().clone());
        }

        Ok(sols)
    }

    fn query_ask(&self, query_str: &str) -> Result<bool, Self::Err> {
        // Same lazy-store behaviour as SELECT: empty store ⇒ no match.
        let Some(store) = &self.store else {
            return Ok(false);
        };
        let result = SparqlEvaluator::new()
            .parse_query(query_str)
            .map_err(|e| OxigraphInMemoryError::ParsingQueryError {
                msg: format!("Error parsing query: {e}"),
            })?
            .on_store(store)
            .execute()
            .map_err(|e| OxigraphInMemoryError::RunningQueryError {
                query: query_str.to_string(),
                msg: format!("Error executing query: {e}"),
            })?;
        if let QueryResults::Boolean(b) = result {
            Ok(b)
        } else {
            Ok(false)
        }
    }
}

/// Converts Oxigraph query results to internal query solutions.
///
/// # Parameters
///
/// * `query_results` - The query results from Oxigraph
///
/// # Errors
///
/// Returns an error if any solution cannot be processed.
///
/// # Returns
///
/// A vector of query solutions.
#[cfg(feature = "sparql")]
fn cnv_query_results(
    query_results: QueryResults,
) -> Result<Vec<QuerySolution<OxigraphInMemory>>, OxigraphInMemoryError> {
    let QueryResults::Solutions(solutions) = query_results else {
        return Ok(Vec::new());
    };

    #[allow(clippy::unused_enumerate_index)]
    solutions
        .enumerate()
        .map(|(_, solution_result)| {
            solution_result
                .map(cnv_query_solution)
                .map_err(|e| OxigraphInMemoryError::QueryResultError {
                    msg: format!("Error getting query solution: {}", e),
                })
        })
        .collect()
}

/// Converts a single Oxigraph query solution to internal representation.
///
/// # Parameters
///
/// * `qs` - The Oxigraph query solution
///
/// # Returns
///
/// An internal query solution with variables and values.
#[cfg(feature = "sparql")]
fn cnv_query_solution(qs: SparQuerySolution) -> QuerySolution<OxigraphInMemory> {
    let mut variables = Vec::new();
    let mut values = Vec::new();
    for v in qs.variables() {
        let varname = VarName::new(v.as_str());
        variables.push(varname);
    }
    for t in qs.values() {
        let term = t.clone();
        values.push(term)
    }
    QuerySolution::new(variables, values)
}
