//! Tests for the [`Path`] type: how an element's path is rendered and compared.

#![allow(
    clippy::indexing_slicing,
    clippy::panic,
    clippy::unwrap_used,
    reason = "test code with known-structure parsed data"
)]

use super::{parse, Component, Document, Path, Value};

fn parsed(s: &str) -> Document<'_> {
    parse(s.into()).unwrap()
}

#[test]
fn root_path_is_dollar() {
    assert_eq!(parsed("{}").root().path(), "$");
}

#[test]
fn object_member_uses_dot_notation() {
    let doc = parsed(r#"{"currency": "EUR"}"#);
    let Value::Object(fields) = doc.root().value() else {
        panic!("expected object");
    };
    assert_eq!(fields[0].element().path(), "$.currency");
}

#[test]
fn array_index_uses_bracket_notation() {
    let doc = parsed("[10, 20]");
    let Value::Array(items) = doc.root().value() else {
        panic!("expected array");
    };
    assert_eq!(items[0].path(), "$[0]");
    assert_eq!(items[1].path(), "$[1]");
}

#[test]
fn array_under_object_indexes_each_element() {
    let doc = parsed(r#"{"items": [1, 2]}"#);
    let Value::Object(fields) = doc.root().value() else {
        panic!("expected object");
    };
    let Value::Array(items) = fields[0].element().value() else {
        panic!("expected array");
    };
    assert_eq!(items[0].path(), "$.items[0]");
    assert_eq!(items[1].path(), "$.items[1]");
}

#[test]
fn nested_members_and_indices_combine() {
    let doc = parsed(r#"{"elements": [{"id": "x"}]}"#);
    assert_eq!(nested_id_path(&doc), "$.elements[0].id");
}

#[test]
fn path_compares_with_str_and_displays_the_same() {
    let doc = parsed(r#"{"elements": [{"id": "x"}]}"#);
    let path = nested_id_path(&doc);

    // Equality (`PartialEq<&str>`), `Display` and `as_str` all agree on the
    // bracketed canonical form.
    assert_eq!(path, "$.elements[0].id");
    assert_eq!(path.to_string(), "$.elements[0].id");
    assert_eq!(path.as_str(), "$.elements[0].id");

    // The old dotted-array form is no longer produced.
    assert_ne!(path, "$.elements.0.id");
}

#[test]
fn root_has_no_components() {
    assert_eq!(parsed("{}").root().path().components().count(), 0);
}

#[test]
fn components_distinguish_members_and_indices() {
    let doc = parsed(r#"{"elements": [{"id": "x"}]}"#);
    let path = nested_id_path(&doc);
    let components: Vec<Component<'_>> = path.components().collect();
    assert_eq!(
        components,
        vec![
            Component::Member("elements"),
            Component::Index("0"),
            Component::Member("id"),
        ]
    );
}

/// Resolve the path of `$.elements[0].id` in the test document.
fn nested_id_path(doc: &Document<'_>) -> Path {
    let Value::Object(root) = doc.root().value() else {
        panic!("expected object");
    };
    let Value::Array(elements) = root[0].element().value() else {
        panic!("expected array");
    };
    let Value::Object(first) = elements[0].value() else {
        panic!("expected object");
    };
    first[0].element().path()
}
