//! Cypher value types for query results.

use std::fmt;
use std::cmp::Ordering;

use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

use crate::graph::{EdgeLike, NodeLike, Path, PropertyValue};
use crate::result::temporal::{
    DateValue, DateTimeValue, DurationValue, ExtendedDateValue, ExtendedLocalDateTimeValue,
    LocalDateTimeValue, LocalTimeValue, TimeValue,
};

/// A value that can be returned from a Cypher query.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum CypherValue {
    /// Null value.
    Null,
    /// Boolean value.
    Boolean(bool),
    /// Integer value.
    Integer(i64),
    /// Floating point value.
    Float(f64),
    /// String value.
    String(String),
    /// List of values.
    List(Vec<CypherValue>),
    /// Map of values.
    Map(IndexMap<String, CypherValue>),
    /// A node.
    Node(NodeValue),
    /// A relationship.
    Relationship(RelationshipValue),
    /// A path.
    Path(PathValue),
    /// A date (no time, no timezone).
    Date(DateValue),
    /// A time with timezone.
    Time(TimeValue),
    /// A time without timezone.
    LocalTime(LocalTimeValue),
    /// A date-time with timezone.
    DateTime(DateTimeValue),
    /// A date-time without timezone.
    LocalDateTime(LocalDateTimeValue),
    /// A duration (time period).
    Duration(DurationValue),
    /// An extended date for astronomical dates outside chrono's range (-999999999 to +999999999).
    ExtendedDate(ExtendedDateValue),
    /// An extended local datetime for astronomical dates outside chrono's range.
    ExtendedLocalDateTime(ExtendedLocalDateTimeValue),
}

impl CypherValue {
    /// Check if this value is null.
    pub fn is_null(&self) -> bool {
        matches!(self, CypherValue::Null)
    }

    /// Check if this value is truthy.
    pub fn is_truthy(&self) -> bool {
        match self {
            CypherValue::Null => false,
            CypherValue::Boolean(b) => *b,
            CypherValue::Integer(i) => *i != 0,
            CypherValue::Float(f) => *f != 0.0,
            CypherValue::String(s) => !s.is_empty(),
            CypherValue::List(l) => !l.is_empty(),
            CypherValue::Map(m) => !m.is_empty(),
            CypherValue::Node(_) => true,
            CypherValue::Relationship(_) => true,
            CypherValue::Path(_) => true,
            CypherValue::Date(_) => true,
            CypherValue::Time(_) => true,
            CypherValue::LocalTime(_) => true,
            CypherValue::DateTime(_) => true,
            CypherValue::LocalDateTime(_) => true,
            CypherValue::Duration(_) => true,
            CypherValue::ExtendedDate(_) => true,
            CypherValue::ExtendedLocalDateTime(_) => true,
        }
    }

    /// Try to get as bool.
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            CypherValue::Boolean(b) => Some(*b),
            _ => None,
        }
    }

    /// Try to get as integer.
    pub fn as_integer(&self) -> Option<i64> {
        match self {
            CypherValue::Integer(i) => Some(*i),
            _ => None,
        }
    }

    /// Try to get as float.
    pub fn as_float(&self) -> Option<f64> {
        match self {
            CypherValue::Integer(i) => Some(*i as f64),
            CypherValue::Float(f) => Some(*f),
            _ => None,
        }
    }

    /// Try to get as string.
    pub fn as_string(&self) -> Option<&str> {
        match self {
            CypherValue::String(s) => Some(s),
            _ => None,
        }
    }

    /// Try to get as list.
    pub fn as_list(&self) -> Option<&Vec<CypherValue>> {
        match self {
            CypherValue::List(l) => Some(l),
            _ => None,
        }
    }

    /// Try to get as map.
    pub fn as_map(&self) -> Option<&IndexMap<String, CypherValue>> {
        match self {
            CypherValue::Map(m) => Some(m),
            _ => None,
        }
    }

    /// Try to get as node.
    pub fn as_node(&self) -> Option<&NodeValue> {
        match self {
            CypherValue::Node(n) => Some(n),
            _ => None,
        }
    }

    /// Try to get as relationship.
    pub fn as_relationship(&self) -> Option<&RelationshipValue> {
        match self {
            CypherValue::Relationship(r) => Some(r),
            _ => None,
        }
    }

    /// Get the type name.
    pub fn type_name(&self) -> &'static str {
        match self {
            CypherValue::Null => "NULL",
            CypherValue::Boolean(_) => "BOOLEAN",
            CypherValue::Integer(_) => "INTEGER",
            CypherValue::Float(_) => "FLOAT",
            CypherValue::String(_) => "STRING",
            CypherValue::List(_) => "LIST",
            CypherValue::Map(_) => "MAP",
            CypherValue::Node(_) => "NODE",
            CypherValue::Relationship(_) => "RELATIONSHIP",
            CypherValue::Path(_) => "PATH",
            CypherValue::Date(_) => "DATE",
            CypherValue::Time(_) => "TIME",
            CypherValue::LocalTime(_) => "LOCAL TIME",
            CypherValue::DateTime(_) => "DATE TIME",
            CypherValue::LocalDateTime(_) => "LOCAL DATE TIME",
            CypherValue::Duration(_) => "DURATION",
            CypherValue::ExtendedDate(_) => "DATE",
            CypherValue::ExtendedLocalDateTime(_) => "LOCAL DATE TIME",
        }
    }

    /// Compare two values for ordering (handles null comparison).
    pub fn compare(&self, other: &CypherValue) -> Option<Ordering> {
        match (self, other) {
            (CypherValue::Null, CypherValue::Null) => Some(Ordering::Equal),
            (CypherValue::Null, _) => None,
            (_, CypherValue::Null) => None,
            (CypherValue::Boolean(a), CypherValue::Boolean(b)) => a.partial_cmp(b),
            (CypherValue::Integer(a), CypherValue::Integer(b)) => a.partial_cmp(b),
            (CypherValue::Float(a), CypherValue::Float(b)) => a.partial_cmp(b),
            (CypherValue::Integer(a), CypherValue::Float(b)) => (*a as f64).partial_cmp(b),
            (CypherValue::Float(a), CypherValue::Integer(b)) => a.partial_cmp(&(*b as f64)),
            (CypherValue::String(a), CypherValue::String(b)) => a.partial_cmp(b),
            (CypherValue::List(a), CypherValue::List(b)) => {
                for (av, bv) in a.iter().zip(b.iter()) {
                    match av.compare(bv) {
                        Some(Ordering::Equal) => continue,
                        other => return other,
                    }
                }
                a.len().partial_cmp(&b.len())
            }
            (CypherValue::Date(a), CypherValue::Date(b)) => a.partial_cmp(b),
            (CypherValue::Time(a), CypherValue::Time(b)) => a.partial_cmp(b),
            (CypherValue::LocalTime(a), CypherValue::LocalTime(b)) => a.partial_cmp(b),
            (CypherValue::DateTime(a), CypherValue::DateTime(b)) => a.partial_cmp(b),
            (CypherValue::LocalDateTime(a), CypherValue::LocalDateTime(b)) => a.partial_cmp(b),
            (CypherValue::Duration(a), CypherValue::Duration(b)) => a.partial_cmp(b),
            (CypherValue::ExtendedDate(a), CypherValue::ExtendedDate(b)) => a.partial_cmp(b),
            (CypherValue::ExtendedLocalDateTime(a), CypherValue::ExtendedLocalDateTime(b)) => a.partial_cmp(b),
            // Different types cannot be compared
            _ => None,
        }
    }

    /// Compare two values for ORDER BY with total ordering.
    /// This provides a deterministic ordering for all types as per openCypher spec:
    /// Map < Node < Relationship < List < Path < String < Boolean < Number < NaN < null
    pub fn order_compare(&self, other: &CypherValue) -> Ordering {
        // Get type order (lower = comes first in ascending order)
        fn type_order(v: &CypherValue) -> u8 {
            match v {
                CypherValue::Map(_) => 0,
                CypherValue::Node(_) => 1,
                CypherValue::Relationship(_) => 2,
                CypherValue::List(_) => 3,
                CypherValue::Path(_) => 4,
                CypherValue::String(_) => 5,
                CypherValue::Boolean(_) => 6,
                CypherValue::Integer(_) => 7,
                CypherValue::Float(f) if f.is_nan() => 8,  // NaN after regular numbers
                CypherValue::Float(_) => 7,
                CypherValue::Date(_) | CypherValue::ExtendedDate(_) => 9,
                CypherValue::Time(_) | CypherValue::LocalTime(_) => 10,
                CypherValue::DateTime(_) | CypherValue::LocalDateTime(_) | CypherValue::ExtendedLocalDateTime(_) => 11,
                CypherValue::Duration(_) => 12,
                CypherValue::Null => 13,  // Null comes last
            }
        }

        let type_a = type_order(self);
        let type_b = type_order(other);

        if type_a != type_b {
            return type_a.cmp(&type_b);
        }

        // Same type ordering - use the existing compare or custom ordering
        match (self, other) {
            (CypherValue::Null, CypherValue::Null) => Ordering::Equal,
            (CypherValue::Boolean(a), CypherValue::Boolean(b)) => a.cmp(b),
            (CypherValue::Integer(a), CypherValue::Integer(b)) => a.cmp(b),
            (CypherValue::Float(a), CypherValue::Float(b)) => {
                // Handle NaN specially - NaN == NaN for ordering
                if a.is_nan() && b.is_nan() {
                    Ordering::Equal
                } else if a.is_nan() {
                    Ordering::Greater
                } else if b.is_nan() {
                    Ordering::Less
                } else {
                    a.partial_cmp(b).unwrap_or(Ordering::Equal)
                }
            }
            (CypherValue::Integer(a), CypherValue::Float(b)) => {
                if b.is_nan() {
                    Ordering::Less
                } else {
                    (*a as f64).partial_cmp(b).unwrap_or(Ordering::Equal)
                }
            }
            (CypherValue::Float(a), CypherValue::Integer(b)) => {
                if a.is_nan() {
                    Ordering::Greater
                } else {
                    a.partial_cmp(&(*b as f64)).unwrap_or(Ordering::Equal)
                }
            }
            (CypherValue::String(a), CypherValue::String(b)) => a.cmp(b),
            (CypherValue::List(a), CypherValue::List(b)) => {
                for (av, bv) in a.iter().zip(b.iter()) {
                    let cmp = av.order_compare(bv);
                    if cmp != Ordering::Equal {
                        return cmp;
                    }
                }
                a.len().cmp(&b.len())
            }
            (CypherValue::Map(a), CypherValue::Map(b)) => {
                // Order by keys first, then values
                let mut keys_a: Vec<_> = a.keys().collect();
                let mut keys_b: Vec<_> = b.keys().collect();
                keys_a.sort();
                keys_b.sort();
                for (ka, kb) in keys_a.iter().zip(keys_b.iter()) {
                    let key_cmp = ka.cmp(kb);
                    if key_cmp != Ordering::Equal {
                        return key_cmp;
                    }
                    if let (Some(va), Some(vb)) = (a.get(*ka), b.get(*kb)) {
                        let val_cmp = va.order_compare(vb);
                        if val_cmp != Ordering::Equal {
                            return val_cmp;
                        }
                    }
                }
                a.len().cmp(&b.len())
            }
            (CypherValue::Node(a), CypherValue::Node(b)) => a.id.cmp(&b.id),
            (CypherValue::Relationship(a), CypherValue::Relationship(b)) => a.id.cmp(&b.id),
            (CypherValue::Path(a), CypherValue::Path(b)) => {
                // Compare by length first, then by nodes/relationships
                let len_cmp = a.nodes.len().cmp(&b.nodes.len());
                if len_cmp != Ordering::Equal {
                    return len_cmp;
                }
                for (na, nb) in a.nodes.iter().zip(b.nodes.iter()) {
                    let cmp = na.id.cmp(&nb.id);
                    if cmp != Ordering::Equal {
                        return cmp;
                    }
                }
                Ordering::Equal
            }
            (CypherValue::Date(a), CypherValue::Date(b)) => a.date.cmp(&b.date),
            (CypherValue::Time(a), CypherValue::Time(b)) => a.cmp(b),
            (CypherValue::LocalTime(a), CypherValue::LocalTime(b)) => a.cmp(b),
            (CypherValue::DateTime(a), CypherValue::DateTime(b)) => a.cmp(b),
            (CypherValue::LocalDateTime(a), CypherValue::LocalDateTime(b)) => a.cmp(b),
            (CypherValue::Duration(a), CypherValue::Duration(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
            (CypherValue::ExtendedDate(a), CypherValue::ExtendedDate(b)) => a.cmp(b),
            (CypherValue::ExtendedLocalDateTime(a), CypherValue::ExtendedLocalDateTime(b)) => a.cmp(b),
            _ => Ordering::Equal,
        }
    }
}

impl PartialEq for CypherValue {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (CypherValue::Null, CypherValue::Null) => true,
            (CypherValue::Boolean(a), CypherValue::Boolean(b)) => a == b,
            (CypherValue::Integer(a), CypherValue::Integer(b)) => a == b,
            (CypherValue::Float(a), CypherValue::Float(b)) => a == b,
            (CypherValue::Integer(a), CypherValue::Float(b)) => (*a as f64) == *b,
            (CypherValue::Float(a), CypherValue::Integer(b)) => *a == (*b as f64),
            (CypherValue::String(a), CypherValue::String(b)) => a == b,
            (CypherValue::List(a), CypherValue::List(b)) => a == b,
            (CypherValue::Map(a), CypherValue::Map(b)) => a == b,
            (CypherValue::Node(a), CypherValue::Node(b)) => a.id == b.id,
            (CypherValue::Relationship(a), CypherValue::Relationship(b)) => a.id == b.id,
            (CypherValue::Path(a), CypherValue::Path(b)) => {
                a.nodes == b.nodes && a.relationships == b.relationships
            }
            (CypherValue::Date(a), CypherValue::Date(b)) => a == b,
            (CypherValue::Time(a), CypherValue::Time(b)) => a == b,
            (CypherValue::LocalTime(a), CypherValue::LocalTime(b)) => a == b,
            (CypherValue::DateTime(a), CypherValue::DateTime(b)) => a == b,
            (CypherValue::LocalDateTime(a), CypherValue::LocalDateTime(b)) => a == b,
            (CypherValue::Duration(a), CypherValue::Duration(b)) => a == b,
            (CypherValue::ExtendedDate(a), CypherValue::ExtendedDate(b)) => a == b,
            (CypherValue::ExtendedLocalDateTime(a), CypherValue::ExtendedLocalDateTime(b)) => a == b,
            _ => false,
        }
    }
}

impl fmt::Display for CypherValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CypherValue::Null => write!(f, "null"),
            CypherValue::Boolean(b) => write!(f, "{}", b),
            CypherValue::Integer(i) => write!(f, "{}", i),
            CypherValue::Float(fl) => write!(f, "{}", fl),
            CypherValue::String(s) => {
                // Escape special characters for proper display
                write!(f, "'")?;
                for c in s.chars() {
                    match c {
                        '\\' => write!(f, "\\\\")?,
                        '\'' => write!(f, "\\'")?,
                        '"' => write!(f, "\\\"")?,
                        '\n' => write!(f, "\\n")?,
                        '\r' => write!(f, "\\r")?,
                        '\t' => write!(f, "\\t")?,
                        '\x08' => write!(f, "\\b")?,
                        '\x0C' => write!(f, "\\f")?,
                        _ => write!(f, "{}", c)?,
                    }
                }
                write!(f, "'")
            }
            CypherValue::List(l) => {
                write!(f, "[")?;
                for (i, v) in l.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}", v)?;
                }
                write!(f, "]")
            }
            CypherValue::Map(m) => {
                write!(f, "{{")?;
                for (i, (k, v)) in m.iter().enumerate() {
                    if i > 0 {
                        write!(f, ", ")?;
                    }
                    write!(f, "{}: {}", k, v)?;
                }
                write!(f, "}}")
            }
            CypherValue::Node(n) => write!(f, "{}", n),
            CypherValue::Relationship(r) => write!(f, "{}", r),
            CypherValue::Path(p) => write!(f, "{}", p),
            CypherValue::Date(d) => write!(f, "{}", d),
            CypherValue::Time(t) => write!(f, "{}", t),
            CypherValue::LocalTime(lt) => write!(f, "{}", lt),
            CypherValue::DateTime(dt) => write!(f, "{}", dt),
            CypherValue::LocalDateTime(ldt) => write!(f, "{}", ldt),
            CypherValue::Duration(dur) => write!(f, "{}", dur),
            CypherValue::ExtendedDate(d) => write!(f, "{}", d),
            CypherValue::ExtendedLocalDateTime(ldt) => write!(f, "{}", ldt),
        }
    }
}

impl From<PropertyValue> for CypherValue {
    fn from(pv: PropertyValue) -> Self {
        match pv {
            PropertyValue::Null => CypherValue::Null,
            PropertyValue::Bool(b) => CypherValue::Boolean(b),
            PropertyValue::Integer(i) => CypherValue::Integer(i),
            PropertyValue::Float(f) => CypherValue::Float(f),
            PropertyValue::String(s) => CypherValue::String(s),
            PropertyValue::List(l) => CypherValue::List(l.into_iter().map(Into::into).collect()),
            PropertyValue::Map(m) => {
                CypherValue::Map(m.into_iter().map(|(k, v)| (k, v.into())).collect())
            }
            // Convert temporal types
            PropertyValue::Date(d) => CypherValue::Date(d),
            PropertyValue::Time(t) => CypherValue::Time(t),
            PropertyValue::LocalTime(lt) => CypherValue::LocalTime(lt),
            PropertyValue::DateTime(dt) => CypherValue::DateTime(dt),
            PropertyValue::LocalDateTime(ldt) => CypherValue::LocalDateTime(ldt),
            PropertyValue::Duration(dur) => CypherValue::Duration(dur),
            // Convert extended temporal types (NxDate -> old ExtendedDateValue for CypherValue)
            PropertyValue::ExtendedDate(nxdate) => {
                use crate::result::NxDate;
                use crate::result::temporal::ExtendedDateValue;
                match nxdate {
                    NxDate::Standard(d) => CypherValue::Date(DateValue::new(d)),
                    NxDate::Extended(ext) => CypherValue::ExtendedDate(
                        ExtendedDateValue::new(ext.year(), ext.month() as u8, ext.day() as u8)
                    ),
                }
            }
            PropertyValue::ExtendedLocalDateTime(nxldt) => {
                use crate::result::NxLocalDateTime;
                use crate::result::temporal::ExtendedLocalDateTimeValue;
                match nxldt {
                    NxLocalDateTime::Standard(dt) => CypherValue::LocalDateTime(LocalDateTimeValue::new(dt)),
                    NxLocalDateTime::Extended(ext) => CypherValue::ExtendedLocalDateTime(
                        ExtendedLocalDateTimeValue::new(
                            ExtendedDateValue::new(ext.year(), ext.month() as u8, ext.day() as u8),
                            ext.hour() as u8,
                            ext.minute() as u8,
                            ext.second() as u8,
                            ext.nanosecond(),
                        )
                    ),
                }
            }
        }
    }
}

impl From<CypherValue> for PropertyValue {
    fn from(cv: CypherValue) -> Self {
        match cv {
            CypherValue::Null => PropertyValue::Null,
            CypherValue::Boolean(b) => PropertyValue::Bool(b),
            CypherValue::Integer(i) => PropertyValue::Integer(i),
            CypherValue::Float(f) => PropertyValue::Float(f),
            CypherValue::String(s) => PropertyValue::String(s),
            CypherValue::List(l) => PropertyValue::List(l.into_iter().map(Into::into).collect()),
            CypherValue::Map(m) => {
                PropertyValue::Map(m.into_iter().map(|(k, v)| (k, v.into())).collect())
            }
            // Convert graph types to maps
            CypherValue::Node(n) => {
                let mut map = IndexMap::new();
                map.insert("_id".to_string(), PropertyValue::Integer(n.id as i64));
                map.insert(
                    "_labels".to_string(),
                    PropertyValue::List(
                        n.labels
                            .into_iter()
                            .map(PropertyValue::String)
                            .collect(),
                    ),
                );
                for (k, v) in n.properties {
                    map.insert(k, v.into());
                }
                PropertyValue::Map(map)
            }
            CypherValue::Relationship(r) => {
                let mut map = IndexMap::new();
                map.insert("_id".to_string(), PropertyValue::Integer(r.id as i64));
                map.insert("_type".to_string(), PropertyValue::String(r.rel_type));
                map.insert("_start".to_string(), PropertyValue::Integer(r.start_node_id as i64));
                map.insert("_end".to_string(), PropertyValue::Integer(r.end_node_id as i64));
                for (k, v) in r.properties {
                    map.insert(k, v.into());
                }
                PropertyValue::Map(map)
            }
            CypherValue::Path(p) => {
                let nodes: Vec<PropertyValue> = p.nodes.into_iter().map(|n| CypherValue::Node(n).into()).collect();
                let rels: Vec<PropertyValue> = p.relationships.into_iter().map(|r| CypherValue::Relationship(r).into()).collect();
                let mut map = IndexMap::new();
                map.insert("_nodes".to_string(), PropertyValue::List(nodes));
                map.insert("_relationships".to_string(), PropertyValue::List(rels));
                PropertyValue::Map(map)
            }
            // Convert temporal types directly
            CypherValue::Date(d) => PropertyValue::Date(d),
            CypherValue::Time(t) => PropertyValue::Time(t),
            CypherValue::LocalTime(lt) => PropertyValue::LocalTime(lt),
            CypherValue::DateTime(dt) => PropertyValue::DateTime(dt),
            CypherValue::LocalDateTime(ldt) => PropertyValue::LocalDateTime(ldt),
            CypherValue::Duration(dur) => PropertyValue::Duration(dur),
            // Extended dates are stored as strings (for serialization)
            CypherValue::ExtendedDate(d) => PropertyValue::String(d.to_string()),
            CypherValue::ExtendedLocalDateTime(ldt) => PropertyValue::String(ldt.to_string()),
        }
    }
}

impl From<bool> for CypherValue {
    fn from(b: bool) -> Self {
        CypherValue::Boolean(b)
    }
}

impl From<i64> for CypherValue {
    fn from(i: i64) -> Self {
        CypherValue::Integer(i)
    }
}

impl From<i32> for CypherValue {
    fn from(i: i32) -> Self {
        CypherValue::Integer(i as i64)
    }
}

impl From<f64> for CypherValue {
    fn from(f: f64) -> Self {
        CypherValue::Float(f)
    }
}

impl From<String> for CypherValue {
    fn from(s: String) -> Self {
        CypherValue::String(s)
    }
}

impl From<&str> for CypherValue {
    fn from(s: &str) -> Self {
        CypherValue::String(s.to_string())
    }
}

impl<T: Into<CypherValue>> From<Vec<T>> for CypherValue {
    fn from(v: Vec<T>) -> Self {
        CypherValue::List(v.into_iter().map(Into::into).collect())
    }
}

impl<T: Into<CypherValue>> From<Option<T>> for CypherValue {
    fn from(opt: Option<T>) -> Self {
        match opt {
            Some(v) => v.into(),
            None => CypherValue::Null,
        }
    }
}

/// A node value in query results.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeValue {
    /// The node ID.
    pub id: u64,
    /// The labels.
    pub labels: Vec<String>,
    /// The properties.
    pub properties: IndexMap<String, CypherValue>,
}

impl NodeValue {
    /// Create from a graph node.
    pub fn from_graph_node(node: impl NodeLike) -> Self {
        Self {
            id: node.id(),
            labels: node.labels().iter().cloned().collect(),
            properties: node
                .properties()
                .iter()
                .map(|(k, v)| (k.clone(), v.clone().into()))
                .collect(),
        }
    }
}

impl PartialEq for NodeValue {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl fmt::Display for NodeValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "(")?;
        for label in &self.labels {
            write!(f, ":{}", label)?;
        }
        if !self.properties.is_empty() {
            write!(f, " {{")?;
            for (i, (k, v)) in self.properties.iter().enumerate() {
                if i > 0 {
                    write!(f, ", ")?;
                }
                write!(f, "{}: {}", k, v)?;
            }
            write!(f, "}}")?;
        }
        write!(f, ")")
    }
}

/// A relationship value in query results.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RelationshipValue {
    /// The relationship ID.
    pub id: u64,
    /// The relationship type.
    pub rel_type: String,
    /// The start node ID.
    pub start_node_id: u64,
    /// The end node ID.
    pub end_node_id: u64,
    /// The properties.
    pub properties: IndexMap<String, CypherValue>,
}

impl RelationshipValue {
    /// Create from a graph edge and endpoints.
    pub fn from_graph_edge(edge: impl EdgeLike, start_id: u64, end_id: u64) -> Self {
        Self {
            id: edge.id(),
            rel_type: edge.rel_type().to_string(),
            start_node_id: start_id,
            end_node_id: end_id,
            properties: edge
                .properties()
                .iter()
                .map(|(k, v)| (k.clone(), v.clone().into()))
                .collect(),
        }
    }
}

impl PartialEq for RelationshipValue {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

impl fmt::Display for RelationshipValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "-[:{}]-", self.rel_type)?;
        if !self.properties.is_empty() {
            write!(f, " {{")?;
            for (i, (k, v)) in self.properties.iter().enumerate() {
                if i > 0 {
                    write!(f, ", ")?;
                }
                write!(f, "{}: {}", k, v)?;
            }
            write!(f, "}}")?;
        }
        Ok(())
    }
}

/// A path value in query results.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathValue {
    /// The nodes in the path.
    pub nodes: Vec<NodeValue>,
    /// The relationships in the path.
    pub relationships: Vec<RelationshipValue>,
}

impl PathValue {
    /// Create from a graph path.
    pub fn from_path(path: &Path, get_endpoints: impl Fn(u64) -> (u64, u64)) -> Self {
        let nodes = path
            .nodes
            .iter()
            .map(NodeValue::from_graph_node)
            .collect();
        let relationships = path
            .relationships
            .iter()
            .map(|e| {
                let (start, end) = get_endpoints(e.id);
                RelationshipValue::from_graph_edge(e, start, end)
            })
            .collect();
        Self {
            nodes,
            relationships,
        }
    }

    /// Get the length of the path (number of relationships).
    pub fn length(&self) -> usize {
        self.relationships.len()
    }
}

impl PartialEq for PathValue {
    fn eq(&self, other: &Self) -> bool {
        self.nodes == other.nodes && self.relationships == other.relationships
    }
}

impl fmt::Display for PathValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (i, node) in self.nodes.iter().enumerate() {
            if i > 0 {
                write!(f, "-[{}]->", self.relationships[i - 1])?;
            }
            write!(f, "{}", node)?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_cypher_value_equality() {
        assert_eq!(CypherValue::Integer(5), CypherValue::Integer(5));
        assert_eq!(CypherValue::Integer(5), CypherValue::Float(5.0));
        assert_eq!(CypherValue::String("hello".to_string()), CypherValue::String("hello".to_string()));
        assert_ne!(CypherValue::Integer(5), CypherValue::Integer(6));
    }

    #[test]
    fn test_cypher_value_truthy() {
        assert!(!CypherValue::Null.is_truthy());
        assert!(!CypherValue::Boolean(false).is_truthy());
        assert!(CypherValue::Boolean(true).is_truthy());
        assert!(!CypherValue::Integer(0).is_truthy());
        assert!(CypherValue::Integer(1).is_truthy());
        assert!(!CypherValue::String("".to_string()).is_truthy());
        assert!(CypherValue::String("hello".to_string()).is_truthy());
    }

    #[test]
    fn test_cypher_value_comparison() {
        assert_eq!(
            CypherValue::Integer(5).compare(&CypherValue::Integer(3)),
            Some(Ordering::Greater)
        );
        assert_eq!(
            CypherValue::String("a".to_string()).compare(&CypherValue::String("b".to_string())),
            Some(Ordering::Less)
        );
        assert_eq!(
            CypherValue::Integer(5).compare(&CypherValue::Null),
            None
        );
    }
}
