use super::Expression;
use crate::Span;
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt::Display};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PathMember {
    String {
        val: String,
        span: Span,
        optional: bool,
    },
    Int {
        val: usize,
        span: Span,
        optional: bool,
    },
}
impl PathMember {
    pub fn int(val: usize, optional: bool, span: Span) -> Self {
        PathMember::Int {
            val,
            span,
            optional,
        }
    }
    pub fn string(val: String, optional: bool, span: Span) -> Self {
        PathMember::String {
            val,
            span,
            optional,
        }
    }
    pub fn test_int(val: usize, optional: bool) -> Self {
        PathMember::Int {
            val,
            optional,
            span: Span::test_data(),
        }
    }
    pub fn test_string(val: String, optional: bool) -> Self {
        PathMember::String {
            val,
            optional,
            span: Span::test_data(),
        }
    }
    pub fn make_optional(&mut self) {
        match self {
            PathMember::String {
                ref mut optional, ..
            } => *optional = true,
            PathMember::Int {
                ref mut optional, ..
            } => *optional = true,
        }
    }
}
impl PartialEq for PathMember {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (
                Self::String {
                    val: l_val,
                    optional: l_opt,
                    ..
                },
                Self::String {
                    val: r_val,
                    optional: r_opt,
                    ..
                },
            ) => l_val == r_val && l_opt == r_opt,
            (
                Self::Int {
                    val: l_val,
                    optional: l_opt,
                    ..
                },
                Self::Int {
                    val: r_val,
                    optional: r_opt,
                    ..
                },
            ) => l_val == r_val && l_opt == r_opt,
            _ => false,
        }
    }
}
impl PartialOrd for PathMember {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        match (self, other) {
            (
                PathMember::String {
                    val: l_val,
                    optional: l_opt,
                    ..
                },
                PathMember::String {
                    val: r_val,
                    optional: r_opt,
                    ..
                },
            ) => {
                let val_ord = Some(l_val.cmp(r_val));
                if let Some(Ordering::Equal) = val_ord {
                    Some(l_opt.cmp(r_opt))
                } else {
                    val_ord
                }
            }
            (
                PathMember::Int {
                    val: l_val,
                    optional: l_opt,
                    ..
                },
                PathMember::Int {
                    val: r_val,
                    optional: r_opt,
                    ..
                },
            ) => {
                let val_ord = Some(l_val.cmp(r_val));
                if let Some(Ordering::Equal) = val_ord {
                    Some(l_opt.cmp(r_opt))
                } else {
                    val_ord
                }
            }
            (PathMember::Int { .. }, PathMember::String { .. }) => Some(Ordering::Greater),
            (PathMember::String { .. }, PathMember::Int { .. }) => Some(Ordering::Less),
        }
    }
}
#[derive(Debug, Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
pub struct CellPath {
    pub members: Vec<PathMember>,
}
impl CellPath {
    pub fn make_optional(&mut self) {
        for member in &mut self.members {
            member.make_optional();
        }
    }
}
impl Display for CellPath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for (idx, elem) in self.members.iter().enumerate() {
            if idx > 0 {
                write!(f, ".")?;
            }
            match elem {
                PathMember::Int { val, .. } => write!(f, "{val}")?,
                PathMember::String { val, .. } => write!(f, "{val}")?,
            }
        }
        Ok(())
    }
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FullCellPath {
    pub head: Expression,
    pub tail: Vec<PathMember>,
}
#[cfg(test)]
mod test {
    use super::*;
    use std::cmp::Ordering::Greater;
    #[test]
    fn path_member_partial_ord() {
        assert_eq!(
            Some(Greater),
            PathMember::test_int(5, true).partial_cmp(&PathMember::test_string("e".into(), true))
        );
        assert_eq!(
            Some(Greater),
            PathMember::test_int(5, true).partial_cmp(&PathMember::test_int(5, false))
        );
        assert_eq!(
            Some(Greater),
            PathMember::test_int(6, true).partial_cmp(&PathMember::test_int(5, true))
        );
        assert_eq!(
            Some(Greater),
            PathMember::test_string("e".into(), true)
                .partial_cmp(&PathMember::test_string("e".into(), false))
        );
        assert_eq!(
            Some(Greater),
            PathMember::test_string("f".into(), true)
                .partial_cmp(&PathMember::test_string("e".into(), true))
        );
    }
}