use nom::bytes::complete::tag;
use nom::character::complete::char;
use nom::error::context;
use nom::sequence::terminated;
use nom::{
    branch::alt,
    bytes::complete::take_while1,
    character::complete::{alpha1, digit1, multispace0},
    combinator::{map, recognize},
    multi::{fold_many0, separated_list1},
    sequence::{delimited, pair, preceded},
};
use serde_json_path_core::spec::segment::{QuerySegment, QuerySegmentKind, Segment};
use serde_json_path_core::spec::selector::Selector;

use super::selector::{parse_selector, parse_wildcard_selector};
use super::utils::cut_with;
use super::PResult;

// The specification requires that a non-ASCII character is in the range
// %x80-10FFFF. In Rust, the `char` type can not hold characters higher
// than %x10FFFF, so we only need to check the lower bound in this function.
//
// See: https://doc.rust-lang.org/std/primitive.char.html#validity
fn is_non_ascii_unicode(chr: char) -> bool {
    chr >= '\u{0080}'
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_non_ascii_unicode(input: &str) -> PResult<&str> {
    take_while1(is_non_ascii_unicode)(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_name_first(input: &str) -> PResult<&str> {
    alt((alpha1, recognize(char('_')), parse_non_ascii_unicode))(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_name_char(input: &str) -> PResult<&str> {
    alt((digit1, parse_name_first))(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
pub(crate) fn parse_dot_member_name(input: &str) -> PResult<String> {
    map(
        recognize(pair(
            cut_with(parse_name_first, |_| {
                SegmentError::InvalidFirstNameCharacter
            }),
            fold_many0(parse_name_char, String::new, |mut s, item| {
                s.push_str(item);
                s
            }),
        )),
        |s| s.to_owned(),
    )(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_dot_member_name_shorthand(input: &str) -> PResult<Segment> {
    map(
        preceded(char('.'), context("dot member name", parse_dot_member_name)),
        Segment::DotName,
    )(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_multi_selector(input: &str) -> PResult<Vec<Selector>> {
    separated_list1(
        delimited(multispace0, char(','), multispace0),
        parse_selector,
    )(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_child_long_hand(input: &str) -> PResult<Segment> {
    context(
        "long-hand segment",
        preceded(
            pair(char('['), multispace0),
            terminated(
                map(parse_multi_selector, Segment::LongHand),
                pair(
                    multispace0,
                    cut_with(char(']'), |_| SegmentError::ExpectedClosingBrace),
                ),
            ),
        ),
    )(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_dot_wildcard_shorthand(input: &str) -> PResult<Segment> {
    map(preceded(char('.'), parse_wildcard_selector), |_| {
        Segment::Wildcard
    })(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_child_segment(input: &str) -> PResult<Segment> {
    preceded(
        multispace0,
        alt((
            parse_dot_wildcard_shorthand,
            parse_dot_member_name_shorthand,
            parse_child_long_hand,
        )),
    )(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
fn parse_descendant_segment(input: &str) -> PResult<Segment> {
    preceded(
        tag(".."),
        alt((
            map(parse_wildcard_selector, |_| Segment::Wildcard),
            parse_child_long_hand,
            map(parse_dot_member_name, Segment::DotName),
        )),
    )(input)
}

#[cfg_attr(feature = "trace", tracing::instrument(level = "trace", parent = None, ret, err))]
pub(crate) fn parse_segment(input: &str) -> PResult<QuerySegment> {
    alt((
        map(parse_descendant_segment, |inner| QuerySegment {
            kind: QuerySegmentKind::Descendant,
            segment: inner,
        }),
        map(parse_child_segment, |inner| QuerySegment {
            kind: QuerySegmentKind::Child,
            segment: inner,
        }),
    ))(input)
}

#[doc(hidden)]
#[derive(Debug, thiserror::Error)]
pub(crate) enum SegmentError {
    #[error("must start with lowercase alpha or '_'")]
    InvalidFirstNameCharacter,
    #[error("expected closing ']'")]
    ExpectedClosingBrace,
}

#[cfg(test)]
mod tests {
    #[cfg(feature = "trace")]
    use test_log::test;

    use nom::combinator::all_consuming;
    use serde_json_path_core::spec::{
        integer::Integer,
        selector::{index::Index, name::Name, slice::Slice, Selector},
    };

    use super::{
        parse_child_long_hand, parse_child_segment, parse_descendant_segment,
        parse_dot_member_name_shorthand, Segment,
    };

    #[test]
    fn dot_member_names() {
        assert!(matches!(
            parse_dot_member_name_shorthand(".name"),
            Ok(("", Segment::DotName(s))) if s == "name",
        ));
        assert!(matches!(
            parse_dot_member_name_shorthand(".foo_bar"),
            Ok(("", Segment::DotName(s))) if s == "foo_bar",
        ));
        assert!(parse_dot_member_name_shorthand(". space").is_err());
        assert!(all_consuming(parse_dot_member_name_shorthand)(".no-dash").is_err());
        assert!(parse_dot_member_name_shorthand(".1no_num_1st").is_err());
    }

    #[test]
    fn child_long_hand() {
        {
            let (_, sk) = parse_child_long_hand(r#"["name"]"#).unwrap();
            let s = sk.as_long_hand().unwrap();
            assert_eq!(s[0], Selector::Name(Name::from("name")));
        }
        {
            let (_, sk) = parse_child_long_hand(r#"['name']"#).unwrap();
            let s = sk.as_long_hand().unwrap();
            assert_eq!(s[0], Selector::Name(Name::from("name")));
        }
        {
            let (_, sk) = parse_child_long_hand(r#"["name","test"]"#).unwrap();
            let s = sk.as_long_hand().unwrap();
            assert_eq!(s[0], Selector::Name(Name::from("name")));
            assert_eq!(s[1], Selector::Name(Name::from("test")));
        }
        {
            let (_, sk) = parse_child_long_hand(r#"['name',10,0:3]"#).unwrap();
            let s = sk.as_long_hand().unwrap();
            assert_eq!(s[0], Selector::Name(Name::from("name")));
            assert_eq!(
                s[1],
                Selector::Index(Index(Integer::from_i64_unchecked(10)))
            );
            assert_eq!(
                s[2],
                Selector::ArraySlice(Slice::new().with_start(0).with_end(3))
            );
        }
        {
            let (_, sk) = parse_child_long_hand(r#"[::,*]"#).unwrap();
            let s = sk.as_long_hand().unwrap();
            assert_eq!(s[0], Selector::ArraySlice(Slice::new()));
            assert_eq!(s[1], Selector::Wildcard);
        }
        {
            let err = parse_child_long_hand("[010]").unwrap_err();
            match err {
                nom::Err::Error(e) | nom::Err::Failure(e) => println!("{e:#?}"),
                nom::Err::Incomplete(_) => panic!("wrong error kind: {err:?}"),
            }
        }
    }

    #[test]
    fn child_segment() {
        {
            let (_, sk) = parse_child_segment(".name").unwrap();
            assert_eq!(sk.as_dot_name(), Some("name"));
        }
        {
            let (_, sk) = parse_child_segment(".*").unwrap();
            assert!(matches!(sk, Segment::Wildcard));
        }
        {
            let (_, sk) = parse_child_segment("[*]").unwrap();
            let s = sk.as_long_hand().unwrap();
            assert_eq!(s[0], Selector::Wildcard);
        }
    }

    #[test]
    fn descendant_segment() {
        {
            let (_, sk) = parse_descendant_segment("..['name']").unwrap();
            let s = sk.as_long_hand().unwrap();
            assert_eq!(s[0], Selector::Name(Name::from("name")));
        }
        {
            let (_, sk) = parse_descendant_segment("..name").unwrap();
            assert_eq!(sk.as_dot_name().unwrap(), "name");
        }
        {
            let (_, sk) = parse_descendant_segment("..*").unwrap();
            assert!(matches!(sk, Segment::Wildcard));
        }
    }
}
