use assert_matches::assert_matches;

use crate::{json::Value, test};

use super::{parse, test::spanned_json, Element, PathNode};

#[test]
fn should_parse_nested_object() {
    test::setup();

    let json = r#"{ "field_a": "one", "field_b": { "field_ba": "two", "field_bb": "three" } }"#;
    let elem = parse(json.into()).unwrap();
    let Element {
        path_node: path,
        value,
        span,
        id: _,
    } = elem;

    assert_eq!(*path, PathNode::Root);
    assert_eq!(spanned_json(span, json), json);

    let fields = assert_matches!(value, Value::Object(elems) => elems);
    let [field_a, field_b] = fields.try_into().unwrap();

    {
        let (_id, path, span, value) = field_a.into_parts();

        assert_eq!(*path, "$.field_a");
        assert_eq!(spanned_json(span, json), r#""one""#);
        let s = assert_matches!(value, Value::String(s) => s);
        assert_eq!(s.as_raw(), "one");
    }

    {
        let (_id, path, span, value) = field_b.into_parts();
        assert_eq!(*path, "$.field_b");
        assert_eq!(
            spanned_json(span, json),
            r#"{ "field_ba": "two", "field_bb": "three" }"#
        );

        let fields = assert_matches!(value, Value::Object(fields) => fields);
        let [field_b_a, field_b_b] = fields.try_into().unwrap();

        {
            let (_id, path, span, value) = field_b_a.into_parts();

            assert_eq!(spanned_json(span, json), r#""two""#);
            assert_eq!(*path, "$.field_b.field_ba");
            let s = assert_matches!(value, Value::String(s) => s);
            assert_eq!(s.as_raw(), "two");
        }

        {
            let (_id, path, span, value) = field_b_b.into_parts();

            assert_eq!(spanned_json(span, json), r#""three""#);
            assert_eq!(*path, "$.field_b.field_bb");
            let s = assert_matches!(value, Value::String(s) => s);
            assert_eq!(s.as_raw(), "three");
        }
    }
}

#[test]
fn should_parse_object_with_nested_array() {
    test::setup();

    let json = r#"{ "field_a": "one", "field_b": [ "two", "three" ] }"#;
    let elem = parse(json.into()).unwrap();
    let Element {
        path_node: path,
        value,
        span,
        id: _,
    } = elem;

    assert_eq!(*path, PathNode::Root);
    assert_eq!(spanned_json(span, json), json);

    let fields = assert_matches!(value, Value::Object(fields) => fields);
    let [field_a, field_b] = fields.try_into().unwrap();

    {
        let (_id, path, span, value) = field_a.into_parts();

        assert_eq!(spanned_json(span, json), r#""one""#);
        assert_eq!(*path, "$.field_a");
        let s = assert_matches!(value, Value::String(s) => s);
        assert_eq!(s.as_raw(), "one");
    }

    {
        let (_id, path, span, value) = field_b.into_parts();
        assert_eq!(*path, "$.field_b");
        assert_eq!(spanned_json(span, json), r#"[ "two", "three" ]"#);

        let elems = assert_matches!(value, Value::Array(elems) => elems);
        let [elem_b_a, elem_b_b] = elems.try_into().unwrap();

        {
            let (_id, path, span, value) = elem_b_a.into_parts();

            assert_eq!(spanned_json(span, json), r#""two""#);
            assert_eq!(*path, "$.field_b.0");
            let s = assert_matches!(value, Value::String(s) => s);
            assert_eq!(s.as_raw(), "two");
        }

        {
            let (_id, path, span, value) = elem_b_b.into_parts();

            assert_eq!(spanned_json(span, json), r#""three""#);
            assert_eq!(*path, "$.field_b.1");
            let s = assert_matches!(value, Value::String(s) => s);
            assert_eq!(s.as_raw(), "three");
        }
    }
}

#[test]
fn should_parse_nested_array() {
    test::setup();

    let json = r#"[ "one", ["two", "three"] ]"#;
    let elem = parse(json.into()).unwrap();
    let Element {
        path_node: path,
        value,
        span,
        id: _,
    } = elem;

    assert_eq!(*path, PathNode::Root);
    assert_eq!(spanned_json(span, json), json);

    let elems = assert_matches!(value, Value::Array(elems) => elems);
    let [elem_a, elem_b] = elems.try_into().unwrap();

    {
        let Element {
            path_node: path,
            value,
            span,
            id: _,
        } = elem_a;

        assert_eq!(spanned_json(span, json), r#""one""#);
        assert_eq!(*path, "$.0");
        let s = assert_matches!(value, Value::String(s) => s);
        assert_eq!(s.as_raw(), "one");
    }

    {
        let Element {
            path_node: path,
            value,
            span,
            id: _,
        } = elem_b;
        assert_eq!(*path, "$.1");
        assert_eq!(spanned_json(span, json), r#"["two", "three"]"#);

        let elems = assert_matches!(value, Value::Array(elems) => elems);
        let [elem_b_a, elem_b_b] = elems.try_into().unwrap();

        {
            let Element {
                path_node: path,
                value,
                span,
                id: _,
            } = elem_b_a;

            assert_eq!(spanned_json(span, json), r#""two""#);
            assert_eq!(*path, "$.1.0");
            let s = assert_matches!(value, Value::String(s) => s);
            assert_eq!(s.as_raw(), "two");
        }

        {
            let Element {
                path_node: path,
                value,
                span,
                id: _,
            } = elem_b_b;

            assert_eq!(spanned_json(span, json), r#""three""#);
            assert_eq!(*path, "$.1.1");
            let s = assert_matches!(value, Value::String(s) => s);
            assert_eq!(s.as_raw(), "three");
        }
    }
}

#[test]
fn should_parse_array_with_nested_object() {
    test::setup();

    let json = r#"[ "one", {"field_a": "two", "field_b": "three"} ]"#;
    let elem = parse(json.into()).unwrap();
    let Element {
        path_node: path,
        value,
        span,
        id: _,
    } = elem;

    assert_eq!(*path, PathNode::Root);
    assert_eq!(spanned_json(span, json), json);

    let elems = assert_matches!(value, Value::Array(elems) => elems);
    let [elem_a, elem_b] = elems.try_into().unwrap();

    {
        let Element {
            path_node: path,
            value,
            span,
            id: _,
        } = elem_a;

        assert_eq!(spanned_json(span, json), r#""one""#);
        assert_eq!(*path, "$.0");
        let s = assert_matches!(value, Value::String(s) => s);
        assert_eq!(s.as_raw(), "one");
    }

    {
        let Element {
            path_node: path,
            value,
            span,
            id: _,
        } = elem_b;
        assert_eq!(*path, "$.1");
        assert_eq!(
            spanned_json(span, json),
            r#"{"field_a": "two", "field_b": "three"}"#
        );

        let fields = assert_matches!(value, Value::Object(fields) => fields);
        let [field_b_a, field_b_b] = fields.try_into().unwrap();

        {
            let (_id, path, span, value) = field_b_a.into_parts();

            assert_eq!(spanned_json(span, json), r#""two""#);
            assert_eq!(*path, "$.1.field_a");
            let s = assert_matches!(value, Value::String(s) => s);
            assert_eq!(s.as_raw(), "two");
        }

        {
            let (_id, path, span, value) = field_b_b.into_parts();

            assert_eq!(spanned_json(span, json), r#""three""#);
            assert_eq!(*path, "$.1.field_b");
            let s = assert_matches!(value, Value::String(s) => s);
            assert_eq!(s.as_raw(), "three");
        }
    }
}
