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

use std::assert_matches;

use super::{parse, Error, ErrorKind, Location};
use crate::json::{Document, ElemId, Span, Value};

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

#[test]
fn null_value() {
    let doc = parsed("null");
    assert_eq!(doc.root.value(), &Value::Null);
    assert_eq!(doc.root.span(), Span::new(0, 4));
    assert_eq!(doc.root.id(), ElemId(0));
}

#[test]
fn true_value() {
    let doc = parsed("true");
    assert_eq!(doc.root.value(), &Value::True);
    assert_eq!(doc.root.span(), Span::new(0, 4));
}

#[test]
fn false_value() {
    let doc = parsed("false");
    assert_eq!(doc.root.value(), &Value::False);
    assert_eq!(doc.root.span(), Span::new(0, 5));
}

#[test]
fn string_value() {
    let doc = parsed(r#""hello""#);
    let Value::String(raw) = doc.root.value() else {
        panic!("expected String")
    };
    assert_eq!(raw.as_unescaped_str(), "hello");
    assert_eq!(doc.root.span(), Span::new(0, 7));
}

#[test]
fn string_with_escape() {
    let doc = parsed(r#""a\"b""#);
    let Value::String(raw) = doc.root.value() else {
        panic!("expected String")
    };
    assert_eq!(raw.as_unescaped_str(), r#"a\"b"#);
}

#[test]
fn string_with_unicode_escape() {
    // `"` encodes `"` - must not be treated as the closing quote
    let doc = parsed("\"\\u0022\"");
    let Value::String(raw) = doc.root.value() else {
        panic!("expected String")
    };
    assert_eq!(raw.as_unescaped_str(), "\\u0022");
}

#[test]
fn number_integer() {
    let doc = parsed("42");
    assert_eq!(doc.root.value(), &Value::Number("42"));
    assert_eq!(doc.root.span(), Span::new(0, 2));
}

#[test]
fn number_negative() {
    let doc = parsed("-7");
    assert_eq!(doc.root.value(), &Value::Number("-7"));
}

#[test]
fn number_decimal() {
    let doc = parsed("3.14");
    assert_eq!(doc.root.value(), &Value::Number("3.14"));
}

#[test]
fn number_exponent() {
    let doc = parsed("1e10");
    assert_eq!(doc.root.value(), &Value::Number("1e10"));
}

#[test]
fn number_full() {
    let doc = parsed("-1.5E+3");
    assert_eq!(doc.root.value(), &Value::Number("-1.5E+3"));
}

#[test]
fn array_empty() {
    let doc = parsed("[]");
    let Value::Array(items) = doc.root.value() else {
        panic!("expected Array")
    };
    assert!(items.is_empty());
    assert_eq!(doc.root.span(), Span::new(0, 2));
}

#[test]
fn array_values() {
    let doc = parsed("[1, true, null]");
    let Value::Array(items) = doc.root.value() else {
        panic!("expected Array")
    };
    assert_eq!(items.len(), 3);
    assert_eq!(items[0].value(), &Value::Number("1"));
    assert_eq!(items[1].value(), &Value::True);
    assert_eq!(items[2].value(), &Value::Null);
}

#[test]
fn object_empty() {
    let doc = parsed("{}");
    let Value::Object(fields) = doc.root.value() else {
        panic!("expected Object")
    };
    assert!(fields.is_empty());
}

#[test]
fn object_fields() {
    let doc = parsed(r#"{"a": 1, "b": false}"#);
    let Value::Object(fields) = doc.root.value() else {
        panic!("expected Object")
    };
    assert_eq!(fields.len(), 2);
    assert_eq!(fields[0].element().value(), &Value::Number("1"));
    assert_eq!(fields[1].element().value(), &Value::False);
}

#[test]
fn elem_ids_are_sequential_depth_first() {
    let doc = parsed(r#"{"a": [10, 20]}"#);
    assert_eq!(doc.root.id(), ElemId(0));
    let Value::Object(fields) = doc.root.value() else {
        panic!()
    };
    let arr = fields[0].element();
    assert_eq!(arr.id(), ElemId(1));
    let Value::Array(items) = arr.value() else {
        panic!()
    };
    assert_eq!(items[0].id(), ElemId(2));
    assert_eq!(items[1].id(), ElemId(3));
}

#[test]
fn whitespace_is_ignored() {
    let doc = parsed("  \n  null  \t");
    assert_eq!(doc.root.value(), &Value::Null);
}

#[test]
fn error_unexpected_char() {
    assert_matches!(
        parse("x".into()).unwrap_err(),
        Error {
            byte_offset: 0,
            position: Location { line: 0, col: 0 },
            kind: ErrorKind::ExpectedStart
        }
    );
}

#[test]
fn error_unexpected_end() {
    assert_matches!(
        parse("".into()).unwrap_err(),
        Error {
            byte_offset: 0,
            position: Location { line: 0, col: 0 },
            kind: ErrorKind::UnexpectedEOF
        }
    );
    assert_matches!(
        parse("[".into()).unwrap_err(),
        Error {
            byte_offset: 1,
            position: Location { line: 0, col: 1 },
            kind: ErrorKind::UnexpectedEOF
        }
    );
    assert_matches!(
        parse("{".into()).unwrap_err(),
        Error {
            byte_offset: 1,
            position: Location { line: 0, col: 1 },
            kind: ErrorKind::UnexpectedEOF
        }
    );
}

#[test]
fn error_trailing_content() {
    assert_matches!(
        parse("null x".into()).unwrap_err(),
        Error {
            byte_offset: 5,
            position: Location { line: 0, col: 5 },
            kind: ErrorKind::TrailingContent
        }
    );
}

#[test]
fn full_span_equals_span_when_no_trailing_comma() {
    let doc = parsed("42");
    assert_eq!(doc.root.full_span(), doc.root.span());
    assert_eq!(doc.root.full_span(), Span::new(0, 2));
}

#[test]
fn full_span_array_non_last_includes_comma_and_whitespace() {
    // [1, 2]
    //  01 34 byte positions
    // item 0: value = `1..2`, full_span extends past ", " to byte 4
    // item 1: value = `4..5`, no trailing comma so full_span == span
    let doc = parsed("[1, 2]");
    let Value::Array(items) = doc.root.value() else {
        panic!("expected Array")
    };
    assert_eq!(items[0].span(), Span::new(1, 2));
    assert_eq!(items[0].full_span(), Span::new(1, 4));
    assert_eq!(items[1].span(), Span::new(4, 5));
    assert_eq!(items[1].full_span(), Span::new(4, 5));
}

#[test]
fn full_span_object_non_last_field_includes_comma_and_whitespace() {
    // `{"a": 1, "b": 2}`
    // ` 1    6  9    14` - key/value byte positions
    // field 0 value: `6..7`, `full_span.end` extends past ", " to 9
    // field 1 value: `14..15`, no trailing comma
    let doc = parsed(r#"{"a": 1, "b": 2}"#);
    let Value::Object(fields) = doc.root.value() else {
        panic!("expected Object")
    };
    assert_eq!(fields[0].element().span(), Span::new(6, 7));
    assert_eq!(fields[0].element().full_span(), Span::new(6, 9));
    assert_eq!(fields[1].element().span(), Span::new(14, 15));
    assert_eq!(fields[1].element().full_span(), Span::new(14, 15));
}

#[test]
fn field_full_span_extends_from_key_start() {
    // {"a": 1, "b": 2}
    // field 0: key "a" starts at 1; value full_span ends at 9 => full_span={1,9}
    // field 1: key "b" starts at 9; value full_span ends at 15 => full_span={9,15}
    let doc = parsed(r#"{"a": 1, "b": 2}"#);
    let Value::Object(fields) = doc.root.value() else {
        panic!("expected Object")
    };
    assert_eq!(fields[0].full_span(), Span::new(1, 9));
    assert_eq!(fields[1].full_span(), Span::new(9, 15));
}

#[test]
fn full_span_single_array_item_equals_span() {
    // [42] - only element; no trailing comma, so full_span == span
    let doc = parsed("[42]");
    let Value::Array(items) = doc.root.value() else {
        panic!("expected Array")
    };
    assert_eq!(items[0].span(), Span::new(1, 3));
    assert_eq!(items[0].full_span(), items[0].span());
}

#[test]
fn full_span_single_object_field_equals_span() {
    // {"a": 1} - only field; no trailing comma
    // key_span: `1..4`, value span: `6..7`, value full_span == span
    // field full_span covers `key..value`: `1..7`
    let doc = parsed(r#"{"a": 1}"#);
    let Value::Object(fields) = doc.root.value() else {
        panic!("expected Object")
    };
    assert_eq!(fields[0].element().span(), Span::new(6, 7));
    assert_eq!(fields[0].element().full_span(), fields[0].element().span());
    assert_eq!(fields[0].full_span(), Span::new(1, 7));
}
