use snapbox::assert_data_eq;
use snapbox::prelude::*;
use snapbox::str;
use toml_edit::{DocumentMut, Key, Value};

macro_rules! parse {
    ($s:expr, $ty:ty) => {{
        let v = $s.parse::<$ty>();
        assert!(v.is_ok(), "Failed with {}", v.unwrap_err());
        v.unwrap()
    }};
}

macro_rules! parse_value {
    ($s:expr) => {
        parse!($s, Value)
    };
}

macro_rules! test_key {
    ($s:expr, $expected:expr) => {{
        let key = parse!($s, Key);
        assert_eq!($expected, key.get(), "");
    }};
}

#[test]
fn test_key_from_str() {
    test_key!("a", "a");
    test_key!(r#"'hello key'"#, "hello key");
    test_key!(
        r#""Jos\u00E9\U000A0000\n\t\r\f\b\"""#,
        "Jos\u{00E9}\u{A0000}\n\t\r\u{c}\u{8}\""
    );
    test_key!("\"\"", "");
    test_key!("\"'hello key'bla\"", "'hello key'bla");
    test_key!(
        "'C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\cargo-edit-test.YizxPxxElXn9'",
        "C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\cargo-edit-test.YizxPxxElXn9"
    );
}

#[test]
fn test_value_from_str() {
    assert!(parse_value!("1979-05-27T00:32:00.999999-07:00").is_datetime());
    assert!(parse_value!("1979-05-27T00:32:00.999999Z").is_datetime());
    assert!(parse_value!("1979-05-27T00:32:00.999999").is_datetime());
    assert!(parse_value!("1979-05-27T00:32:00").is_datetime());
    assert!(parse_value!("1979-05-27").is_datetime());
    assert!(parse_value!("00:32:00").is_datetime());
    assert!(parse_value!("-239").is_integer());
    assert!(parse_value!("1e200").is_float());
    assert!(parse_value!("9_224_617.445_991_228_313").is_float());
    assert!(parse_value!(r#""basic string\nJos\u00E9\n""#).is_str());
    assert!(parse_value!(
        r#""""
multiline basic string
""""#
    )
    .is_str());
    assert!(parse_value!(r"'literal string\ \'").is_str());
    assert!(parse_value!(
        r"'''multiline
literal \ \
string'''"
    )
    .is_str());
    assert!(parse_value!(r#"{ hello = "world", a = 1}"#).is_inline_table());
    assert!(
        parse_value!(r#"[ { x = 1, a = "2" }, {a = "a",b = "b",     c =    "c"} ]"#).is_array()
    );
    let wp = "C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\cargo-edit-test.YizxPxxElXn9";
    let lwp = "'C:\\Users\\appveyor\\AppData\\Local\\Temp\\1\\cargo-edit-test.YizxPxxElXn9'";
    assert_eq!(Value::from(wp).as_str(), parse_value!(lwp).as_str());
    assert!(parse_value!(r#""\\\"\b\f\n\r\t\u00E9\U000A0000""#).is_str());
}

#[test]
fn test_key_unification() {
    let toml = r#"
[a]
[a.'b'.c]
[a."b".c.e]
[a.b.c.d]
"#;
    let expected = str![[r#"

[a]
[a.'b'.c]
[a.'b'.c.e]
[a.'b'.c.d]

"#]];
    let doc = toml.parse::<DocumentMut>();
    assert!(doc.is_ok());
    let doc = doc.unwrap();

    assert_data_eq!(doc.to_string(), expected.raw());
}

macro_rules! bad {
    ($toml:expr, $msg:expr) => {
        match $toml.parse::<DocumentMut>() {
            Ok(s) => panic!("parsed to: {:#?}", s),
            Err(e) => assert_data_eq!(e.to_string(), $msg.raw()),
        }
    };
}

#[test]
fn crlf() {
    "\
     [project]\r\n\
     \r\n\
     name = \"splay\"\r\n\
     version = \"0.1.0\"\r\n\
     authors = [\"alex@crichton.co\"]\r\n\
     \r\n\
     [[lib]]\r\n\
     \r\n\
     path = \"lib.rs\"\r\n\
     name = \"splay\"\r\n\
     description = \"\"\"\
     A Rust implementation of a TAR file reader and writer. This library does not\r\n\
     currently handle compression, but it is abstract over all I/O readers and\r\n\
     writers. Additionally, great lengths are taken to ensure that the entire\r\n\
     contents are never required to be entirely resident in memory all at once.\r\n\
     \"\"\"\
     "
    .parse::<DocumentMut>()
    .unwrap();
}

#[test]
fn fun_with_strings() {
    let table = r#"
bar = "\U00000000"
key1 = "One\nTwo"
key2 = """One\nTwo"""
key3 = """
One
Two"""

key4 = "The quick brown fox jumps over the lazy dog."
key5 = """
The quick brown \


fox jumps over \
the lazy dog."""
key6 = """\
   The quick brown \
   fox jumps over \
   the lazy dog.\
   """
# What you see is what you get.
winpath  = 'C:\Users\nodejs\templates'
winpath2 = '\\ServerX\admin$\system32\'
quoted   = 'Tom "Dubs" Preston-Werner'
regex    = '<\i\c*\s*>'

regex2 = '''I [dw]on't need \d{2} apples'''
lines  = '''
The first newline is
trimmed in raw strings.
All other whitespace
is preserved.
'''
"#
    .parse::<DocumentMut>()
    .unwrap();
    assert_eq!(table["bar"].as_str(), Some("\0"));
    assert_eq!(table["key1"].as_str(), Some("One\nTwo"));
    assert_eq!(table["key2"].as_str(), Some("One\nTwo"));
    assert_eq!(table["key3"].as_str(), Some("One\nTwo"));

    let msg = "The quick brown fox jumps over the lazy dog.";
    assert_eq!(table["key4"].as_str(), Some(msg));
    assert_eq!(table["key5"].as_str(), Some(msg));
    assert_eq!(table["key6"].as_str(), Some(msg));

    assert_eq!(
        table["winpath"].as_str(),
        Some(r"C:\Users\nodejs\templates")
    );
    assert_eq!(
        table["winpath2"].as_str(),
        Some(r"\\ServerX\admin$\system32\")
    );
    assert_eq!(
        table["quoted"].as_str(),
        Some(r#"Tom "Dubs" Preston-Werner"#)
    );
    assert_eq!(table["regex"].as_str(), Some(r"<\i\c*\s*>"));
    assert_eq!(
        table["regex2"].as_str(),
        Some(r"I [dw]on't need \d{2} apples")
    );
    assert_eq!(
        table["lines"].as_str(),
        Some(
            "The first newline is\n\
             trimmed in raw strings.\n\
             All other whitespace\n\
             is preserved.\n"
        )
    );
}

#[test]
fn tables_in_arrays() {
    let table = r#"
[[foo]]
#…
[foo.bar]
#…

[[foo]] # ...
#…
[foo.bar]
#...
"#
    .parse::<DocumentMut>()
    .unwrap();
    table["foo"][0]["bar"].as_table().unwrap();
    table["foo"][1]["bar"].as_table().unwrap();
}

#[test]
fn empty_table() {
    let table = r#"
[foo]"#
        .parse::<DocumentMut>()
        .unwrap();
    table["foo"].as_table().unwrap();
}

#[test]
fn mixed_table_issue_527() {
    let input = r#"
[package]
metadata.msrv = "1.65.0"

[package.metadata.release.pre-release-replacements]
"#;
    let document = input.parse::<DocumentMut>().unwrap();
    let actual = document.to_string();
    assert_data_eq!(actual, input.raw());
}

#[test]
fn fruit() {
    let table = r#"
[[fruit]]
name = "apple"

[fruit.physical]
color = "red"
shape = "round"

[[fruit.variety]]
name = "red delicious"

[[fruit.variety]]
name = "granny smith"

[[fruit]]
name = "banana"

[[fruit.variety]]
name = "plantain"
"#
    .parse::<DocumentMut>()
    .unwrap();
    assert_eq!(table["fruit"][0]["name"].as_str(), Some("apple"));
    assert_eq!(table["fruit"][0]["physical"]["color"].as_str(), Some("red"));
    assert_eq!(
        table["fruit"][0]["physical"]["shape"].as_str(),
        Some("round")
    );
    assert_eq!(
        table["fruit"][0]["variety"][0]["name"].as_str(),
        Some("red delicious")
    );
    assert_eq!(
        table["fruit"][0]["variety"][1]["name"].as_str(),
        Some("granny smith")
    );
    assert_eq!(table["fruit"][1]["name"].as_str(), Some("banana"));
    assert_eq!(
        table["fruit"][1]["variety"][0]["name"].as_str(),
        Some("plantain")
    );
}

#[test]
fn stray_cr() {
    bad!(
        "\r",
        str![[r#"
TOML parse error at line 1, column 1
  |
1 | 
  | ^


"#]]
    );
    bad!(
        "a = [ \r ]",
        str![[r#"
TOML parse error at line 1, column 8
  |
1 | a = [ 
 ]
  |        ^


"#]]
    );
    bad!(
        "a = \"\"\"\r\"\"\"",
        str![[r#"
TOML parse error at line 1, column 8
  |
1 | a = """
"""
  |        ^
invalid multiline basic string

"#]]
    );
    bad!(
        "a = \"\"\"\\  \r  \"\"\"",
        str![[r#"
TOML parse error at line 1, column 10
  |
1 | a = """\  
  """
  |          ^
invalid escape sequence
expected `b`, `f`, `n`, `r`, `t`, `u`, `U`, `\`, `"`

"#]]
    );
    bad!(
        "a = '''\r'''",
        str![[r#"
TOML parse error at line 1, column 8
  |
1 | a = '''
'''
  |        ^
invalid multiline literal string

"#]]
    );
    bad!(
        "a = '\r'",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = '
'
  |      ^
invalid literal string

"#]]
    );
    bad!(
        "a = \"\r\"",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = "
"
  |      ^
invalid basic string

"#]]
    );
}

#[test]
fn blank_literal_string() {
    let table = "foo = ''".parse::<DocumentMut>().unwrap();
    assert_eq!(table["foo"].as_str(), Some(""));
}

#[test]
fn many_blank() {
    let table = "foo = \"\"\"\n\n\n\"\"\"".parse::<DocumentMut>().unwrap();
    assert_eq!(table["foo"].as_str(), Some("\n\n"));
}

#[test]
fn literal_eats_crlf() {
    let table = "
        foo = \"\"\"\\\r\n\"\"\"
        bar = \"\"\"\\\r\n   \r\n   \r\n   a\"\"\"
    "
    .parse::<DocumentMut>()
    .unwrap();
    assert_eq!(table["foo"].as_str(), Some(""));
    assert_eq!(table["bar"].as_str(), Some("a"));
}

#[test]
fn string_no_newline() {
    bad!(
        "a = \"\n\"",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = "
  |      ^
invalid basic string

"#]]
    );
    bad!(
        "a = '\n'",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = '
  |      ^
invalid literal string

"#]]
    );
}

#[test]
fn bad_leading_zeros() {
    bad!(
        "a = 00",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = 00
  |      ^
expected newline, `#`

"#]]
    );
    bad!(
        "a = -00",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | a = -00
  |       ^
expected newline, `#`

"#]]
    );
    bad!(
        "a = +00",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | a = +00
  |       ^
expected newline, `#`

"#]]
    );
    bad!(
        "a = 00.0",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = 00.0
  |      ^
expected newline, `#`

"#]]
    );
    bad!(
        "a = -00.0",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | a = -00.0
  |       ^
expected newline, `#`

"#]]
    );
    bad!(
        "a = +00.0",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | a = +00.0
  |       ^
expected newline, `#`

"#]]
    );
    bad!(
        "a = 9223372036854775808",
        str![[r#"
TOML parse error at line 1, column 5
  |
1 | a = 9223372036854775808
  |     ^
number too large to fit in target type

"#]]
    );
    bad!(
        "a = -9223372036854775809",
        str![[r#"
TOML parse error at line 1, column 5
  |
1 | a = -9223372036854775809
  |     ^
number too small to fit in target type

"#]]
    );
}

#[test]
fn bad_floats() {
    bad!(
        "a = 0.",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | a = 0.
  |       ^
invalid floating-point number
expected digit

"#]]
    );
    bad!(
        "a = 0.e",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | a = 0.e
  |       ^
invalid floating-point number
expected digit

"#]]
    );
    bad!(
        "a = 0.E",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | a = 0.E
  |       ^
invalid floating-point number
expected digit

"#]]
    );
    bad!(
        "a = 0.0E",
        str![[r#"
TOML parse error at line 1, column 9
  |
1 | a = 0.0E
  |         ^
invalid floating-point number

"#]]
    );
    bad!(
        "a = 0.0e",
        str![[r#"
TOML parse error at line 1, column 9
  |
1 | a = 0.0e
  |         ^
invalid floating-point number

"#]]
    );
    bad!(
        "a = 0.0e-",
        str![[r#"
TOML parse error at line 1, column 10
  |
1 | a = 0.0e-
  |          ^
invalid floating-point number

"#]]
    );
    bad!(
        "a = 0.0e+",
        str![[r#"
TOML parse error at line 1, column 10
  |
1 | a = 0.0e+
  |          ^
invalid floating-point number

"#]]
    );
}

#[test]
fn floats() {
    macro_rules! t {
        ($actual:expr, $expected:expr) => {{
            let f = format!("foo = {}", $actual);
            println!("{}", f);
            let a = f.parse::<DocumentMut>().unwrap();
            assert_eq!(a["foo"].as_float().unwrap(), $expected);
        }};
    }

    t!("1.0", 1.0);
    t!("1.0e0", 1.0);
    t!("1.0e+0", 1.0);
    t!("1.0e-0", 1.0);
    t!("1E-0", 1.0);
    t!("1.001e-0", 1.001);
    t!("2e10", 2e10);
    t!("2e+10", 2e10);
    t!("2e-10", 2e-10);
    t!("2_0.0", 20.0);
    t!("2_0.0_0e1_0", 20.0e10);
    t!("2_0.1_0e1_0", 20.1e10);
}

#[test]
fn bare_key_names() {
    let a = "
        foo = 3
        foo_3 = 3
        foo_-2--3--r23f--4-f2-4 = 3
        _ = 3
        - = 3
        8 = 8
        \"a\" = 3
        \"!\" = 3
        \"a^b\" = 3
        \"\\\"\" = 3
        \"character encoding\" = \"value\"
        'ʎǝʞ' = \"value\"
    "
    .parse::<DocumentMut>()
    .unwrap();
    let _ = &a["foo"];
    let _ = &a["-"];
    let _ = &a["_"];
    let _ = &a["8"];
    let _ = &a["foo_3"];
    let _ = &a["foo_-2--3--r23f--4-f2-4"];
    let _ = &a["a"];
    let _ = &a["!"];
    let _ = &a["\""];
    let _ = &a["character encoding"];
    let _ = &a["ʎǝʞ"];
}

#[test]
fn bad_keys() {
    bad!(
        "key\n=3",
        str![[r#"
TOML parse error at line 1, column 4
  |
1 | key
  |    ^
expected `.`, `=`

"#]]
    );
    bad!(
        "key=\n3",
        str![[r#"
TOML parse error at line 1, column 5
  |
1 | key=
  |     ^
invalid string
expected `"`, `'`

"#]]
    );
    bad!(
        "key|=3",
        str![[r#"
TOML parse error at line 1, column 4
  |
1 | key|=3
  |    ^
expected `.`, `=`

"#]]
    );
    bad!(
        "=3",
        str![[r#"
TOML parse error at line 1, column 1
  |
1 | =3
  | ^
invalid key

"#]]
    );
    bad!(
        "\"\"|=3",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | ""|=3
  |   ^
expected `.`, `=`

"#]]
    );
    bad!(
        "\"\n\"|=3",
        str![[r#"
TOML parse error at line 1, column 2
  |
1 | "
  |  ^
invalid basic string

"#]]
    );
    bad!(
        "\"\r\"|=3",
        str![[r#"
TOML parse error at line 1, column 2
  |
1 | "
"|=3
  |  ^
invalid basic string

"#]]
    );
    bad!(
        "''''''=3",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | ''''''=3
  |   ^
expected `.`, `=`

"#]]
    );
    bad!(
        "\"\"\"\"\"\"=3",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | """"""=3
  |   ^
expected `.`, `=`

"#]]
    );
    bad!(
        "'''key'''=3",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | '''key'''=3
  |   ^
expected `.`, `=`

"#]]
    );
    bad!(
        "\"\"\"key\"\"\"=3",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | """key"""=3
  |   ^
expected `.`, `=`

"#]]
    );
}

#[test]
fn bad_table_names() {
    bad!(
        "[]",
        str![[r#"
TOML parse error at line 1, column 2
  |
1 | []
  |  ^
invalid key

"#]]
    );
    bad!(
        "[.]",
        str![[r#"
TOML parse error at line 1, column 2
  |
1 | [.]
  |  ^
invalid key

"#]]
    );
    bad!(
        "[a.]",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | [a.]
  |   ^
invalid table header
expected `.`, `]`

"#]]
    );
    bad!(
        "[!]",
        str![[r#"
TOML parse error at line 1, column 2
  |
1 | [!]
  |  ^
invalid key

"#]]
    );
    bad!(
        "[\"\n\"]",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | ["
  |   ^
invalid basic string

"#]]
    );
    bad!(
        "[a.b]\n[a.\"b\"]",
        str![[r#"
TOML parse error at line 2, column 1
  |
2 | [a."b"]
  | ^
invalid table header
duplicate key `b` in table `a`

"#]]
    );
    bad!(
        "[']",
        str![[r#"
TOML parse error at line 1, column 4
  |
1 | [']
  |    ^
invalid literal string

"#]]
    );
    bad!(
        "[''']",
        str![[r#"
TOML parse error at line 1, column 4
  |
1 | [''']
  |    ^
invalid table header
expected `.`, `]`

"#]]
    );
    bad!(
        "['''''']",
        str![[r#"
TOML parse error at line 1, column 4
  |
1 | ['''''']
  |    ^
invalid table header
expected `.`, `]`

"#]]
    );
    bad!(
        "['''foo''']",
        str![[r#"
TOML parse error at line 1, column 4
  |
1 | ['''foo''']
  |    ^
invalid table header
expected `.`, `]`

"#]]
    );
    bad!(
        "[\"\"\"bar\"\"\"]",
        str![[r#"
TOML parse error at line 1, column 4
  |
1 | ["""bar"""]
  |    ^
invalid table header
expected `.`, `]`

"#]]
    );
    bad!(
        "['\n']",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | ['
  |   ^
invalid literal string

"#]]
    );
    bad!(
        "['\r\n']",
        str![[r#"
TOML parse error at line 1, column 3
  |
1 | ['
  |   ^
invalid literal string

"#]]
    );
}

#[test]
fn table_names() {
    let a = "
        [a.\"b\"]
        [\"f f\"]
        [\"f.f\"]
        [\"\\\"\"]
        ['a.a']
        ['\"\"']
    "
    .parse::<DocumentMut>()
    .unwrap();
    println!("{a:?}");
    let _ = &a["a"]["b"];
    let _ = &a["f f"];
    let _ = &a["f.f"];
    let _ = &a["\""];
    let _ = &a["\"\""];
}

#[test]
fn invalid_bare_numeral() {
    bad!(
        "4",
        str![[r#"
TOML parse error at line 1, column 2
  |
1 | 4
  |  ^
expected `.`, `=`

"#]]
    );
}

#[test]
fn inline_tables() {
    "a = {}".parse::<DocumentMut>().unwrap();
    "a = {b=1}".parse::<DocumentMut>().unwrap();
    "a = {   b   =   1    }".parse::<DocumentMut>().unwrap();
    "a = {a=1,b=2}".parse::<DocumentMut>().unwrap();
    "a = {a=1,b=2,c={}}".parse::<DocumentMut>().unwrap();

    bad!(
        "a = {a=1,}",
        str![[r#"
TOML parse error at line 1, column 9
  |
1 | a = {a=1,}
  |         ^
invalid inline table
expected `}`

"#]]
    );
    bad!(
        "a = {,}",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = {,}
  |      ^
invalid inline table
expected `}`

"#]]
    );
    bad!(
        "a = {a=1,a=1}",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = {a=1,a=1}
  |      ^
duplicate key `a`

"#]]
    );
    bad!(
        "a = {\n}",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = {
  |      ^
invalid inline table
expected `}`

"#]]
    );
    bad!(
        "a = {",
        str![[r#"
TOML parse error at line 1, column 6
  |
1 | a = {
  |      ^
invalid inline table
expected `}`

"#]]
    );

    "a = {a=[\n]}".parse::<DocumentMut>().unwrap();
    "a = {\"a\"=[\n]}".parse::<DocumentMut>().unwrap();
    "a = [\n{},\n{},\n]".parse::<DocumentMut>().unwrap();
}

#[test]
fn number_underscores() {
    macro_rules! t {
        ($actual:expr, $expected:expr) => {{
            let f = format!("foo = {}", $actual);
            let table = f.parse::<DocumentMut>().unwrap();
            assert_eq!(table["foo"].as_integer().unwrap(), $expected);
        }};
    }

    t!("1_0", 10);
    t!("1_0_0", 100);
    t!("1_000", 1000);
    t!("+1_000", 1000);
    t!("-1_000", -1000);
}

#[test]
fn bad_underscores() {
    bad!(
        "foo = 0_",
        str![[r#"
TOML parse error at line 1, column 8
  |
1 | foo = 0_
  |        ^
expected newline, `#`

"#]]
    );
    bad!(
        "foo = 0__0",
        str![[r#"
TOML parse error at line 1, column 8
  |
1 | foo = 0__0
  |        ^
expected newline, `#`

"#]]
    );
    bad!(
        "foo = __0",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | foo = __0
  |       ^
invalid integer
expected leading digit

"#]]
    );
    bad!(
        "foo = 1_0_",
        str![[r#"
TOML parse error at line 1, column 11
  |
1 | foo = 1_0_
  |           ^
invalid integer
expected digit

"#]]
    );
}

#[test]
fn bad_unicode_codepoint() {
    bad!(
        "foo = \"\\uD800\"",
        str![[r#"
TOML parse error at line 1, column 10
  |
1 | foo = "\uD800"
  |          ^
invalid unicode 4-digit hex code
value is out of range

"#]]
    );
}

#[test]
fn bad_strings() {
    bad!(
        "foo = \"\\uxx\"",
        str![[r#"
TOML parse error at line 1, column 10
  |
1 | foo = "\uxx"
  |          ^
invalid unicode 4-digit hex code

"#]]
    );
    bad!(
        "foo = \"\\u\"",
        str![[r#"
TOML parse error at line 1, column 10
  |
1 | foo = "\u"
  |          ^
invalid unicode 4-digit hex code

"#]]
    );
    bad!(
        "foo = \"\\",
        str![[r#"
TOML parse error at line 1, column 8
  |
1 | foo = "\
  |        ^
invalid basic string

"#]]
    );
    bad!(
        "foo = '",
        str![[r#"
TOML parse error at line 1, column 8
  |
1 | foo = '
  |        ^
invalid literal string

"#]]
    );
}

#[test]
fn empty_string() {
    assert_eq!(
        "foo = \"\"".parse::<DocumentMut>().unwrap()["foo"]
            .as_str()
            .unwrap(),
        ""
    );
}

#[test]
fn booleans() {
    let table = "foo = true".parse::<DocumentMut>().unwrap();
    assert_eq!(table["foo"].as_bool(), Some(true));

    let table = "foo = false".parse::<DocumentMut>().unwrap();
    assert_eq!(table["foo"].as_bool(), Some(false));

    bad!(
        "foo = true2",
        str![[r#"
TOML parse error at line 1, column 11
  |
1 | foo = true2
  |           ^
expected newline, `#`

"#]]
    );
    bad!(
        "foo = false2",
        str![[r#"
TOML parse error at line 1, column 12
  |
1 | foo = false2
  |            ^
expected newline, `#`

"#]]
    );
    bad!(
        "foo = t1",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | foo = t1
  |       ^
invalid string
expected `"`, `'`

"#]]
    );
    bad!(
        "foo = f2",
        str![[r#"
TOML parse error at line 1, column 7
  |
1 | foo = f2
  |       ^
invalid string
expected `"`, `'`

"#]]
    );
}

#[test]
fn bad_nesting() {
    bad!(
        "
        a = [2]
        [[a]]
        b = 5
        ",
        str![[r#"
TOML parse error at line 3, column 9
  |
3 |         [[a]]
  |         ^
invalid table header
duplicate key `a` in document root

"#]]
    );
    bad!(
        "
        a = 1
        [a.b]
        ",
        str![[r#"
TOML parse error at line 3, column 9
  |
3 |         [a.b]
  |         ^
invalid table header
dotted key `a` attempted to extend non-table type (integer)

"#]]
    );
    bad!(
        "
        a = []
        [a.b]
        ",
        str![[r#"
TOML parse error at line 3, column 9
  |
3 |         [a.b]
  |         ^
invalid table header
dotted key `a` attempted to extend non-table type (array)

"#]]
    );
    bad!(
        "
        a = []
        [[a.b]]
        ",
        str![[r#"
TOML parse error at line 3, column 9
  |
3 |         [[a.b]]
  |         ^
invalid table header
dotted key `a` attempted to extend non-table type (array)

"#]]
    );
    bad!(
        "
        [a]
        b = { c = 2, d = {} }
        [a.b]
        c = 2
        ",
        str![[r#"
TOML parse error at line 4, column 9
  |
4 |         [a.b]
  |         ^
invalid table header
duplicate key `b` in table `a`

"#]]
    );
}

#[test]
fn bad_table_redefine() {
    bad!(
        "
        [a]
        foo=\"bar\"
        [a.b]
        foo=\"bar\"
        [a]
        ",
        str![[r#"
TOML parse error at line 6, column 9
  |
6 |         [a]
  |         ^
invalid table header
duplicate key `a` in document root

"#]]
    );
    bad!(
        "
        [a]
        foo=\"bar\"
        b = { foo = \"bar\" }
        [a]
        ",
        str![[r#"
TOML parse error at line 5, column 9
  |
5 |         [a]
  |         ^
invalid table header
duplicate key `a` in document root

"#]]
    );
    bad!(
        "
        [a]
        b = {}
        [a.b]
        ",
        str![[r#"
TOML parse error at line 4, column 9
  |
4 |         [a.b]
  |         ^
invalid table header
duplicate key `b` in table `a`

"#]]
    );

    bad!(
        "
        [a]
        b = {}
        [a]
        ",
        str![[r#"
TOML parse error at line 4, column 9
  |
4 |         [a]
  |         ^
invalid table header
duplicate key `a` in document root

"#]]
    );
}

#[test]
fn datetimes() {
    macro_rules! t {
        ($actual:expr) => {{
            let f = format!("foo = {}", $actual);
            let toml = f.parse::<DocumentMut>().expect(&format!("failed: {}", f));
            assert_eq!(toml["foo"].as_datetime().unwrap().to_string(), $actual);
        }};
    }

    t!("2016-09-09T09:09:09Z");
    t!("2016-09-09T09:09:09.1Z");
    t!("2016-09-09T09:09:09.2+10:00");
    t!("2016-09-09T09:09:09.123456789-02:00");
    bad!(
        "foo = 2016-09-09T09:09:09.Z",
        str![[r#"
TOML parse error at line 1, column 26
  |
1 | foo = 2016-09-09T09:09:09.Z
  |                          ^
expected newline, `#`

"#]]
    );
    bad!(
        "foo = 2016-9-09T09:09:09Z",
        str![[r#"
TOML parse error at line 1, column 12
  |
1 | foo = 2016-9-09T09:09:09Z
  |            ^
invalid date-time

"#]]
    );
    bad!(
        "foo = 2016-09-09T09:09:09+2:00",
        str![[r#"
TOML parse error at line 1, column 27
  |
1 | foo = 2016-09-09T09:09:09+2:00
  |                           ^
invalid time offset

"#]]
    );
    bad!(
        "foo = 2016-09-09T09:09:09-2:00",
        str![[r#"
TOML parse error at line 1, column 27
  |
1 | foo = 2016-09-09T09:09:09-2:00
  |                           ^
invalid time offset

"#]]
    );
    bad!(
        "foo = 2016-09-09T09:09:09Z-2:00",
        str![[r#"
TOML parse error at line 1, column 27
  |
1 | foo = 2016-09-09T09:09:09Z-2:00
  |                           ^
expected newline, `#`

"#]]
    );
}

#[test]
fn require_newline_after_value() {
    bad!(
        "0=0r=false",
        str![[r#"
TOML parse error at line 1, column 4
  |
1 | 0=0r=false
  |    ^
expected newline, `#`

"#]]
    );
    bad!(
        r#"
0=""o=""m=""r=""00="0"q="""0"""e="""0"""
"#,
        str![[r#"
TOML parse error at line 2, column 5
  |
2 | 0=""o=""m=""r=""00="0"q="""0"""e="""0"""
  |     ^
expected newline, `#`

"#]]
    );
    bad!(
        r#"
[[0000l0]]
0="0"[[0000l0]]
0="0"[[0000l0]]
0="0"l="0"
"#,
        str![[r#"
TOML parse error at line 3, column 6
  |
3 | 0="0"[[0000l0]]
  |      ^
expected newline, `#`

"#]]
    );
    bad!(
        r#"
0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
"#,
        str![[r#"
TOML parse error at line 2, column 6
  |
2 | 0=[0]00=[0,0,0]t=["0","0","0"]s=[1000-00-00T00:00:00Z,2000-00-00T00:00:00Z]
  |      ^
expected newline, `#`

"#]]
    );
    bad!(
        r#"
0=0r0=0r=false
"#,
        str![[r#"
TOML parse error at line 2, column 4
  |
2 | 0=0r0=0r=false
  |    ^
expected newline, `#`

"#]]
    );
    bad!(
        r#"
0=0r0=0r=falsefal=false
"#,
        str![[r#"
TOML parse error at line 2, column 4
  |
2 | 0=0r0=0r=falsefal=false
  |    ^
expected newline, `#`

"#]]
    );
}

#[test]
fn dont_use_dotted_key_prefix_on_table_fuzz_57049() {
    // This could generate
    // ```toml
    // [
    // p.o]
    // ```
    let input = r#"
p.a=4
[p.o]
"#;
    let document = input.parse::<DocumentMut>().unwrap();
    let actual = document.to_string();
    assert_data_eq!(actual, input.raw());
}

#[test]
fn despan_keys() {
    let mut doc = r#"aaaaaa = 1"#.parse::<DocumentMut>().unwrap();
    let key = "bbb".parse::<Key>().unwrap();
    let table = doc.as_table_mut();
    table.insert_formatted(
        &key,
        toml_edit::Item::Value(Value::Integer(toml_edit::Formatted::new(2))),
    );

    assert_eq!(doc.to_string(), "aaaaaa = 1\nbbb = 2\n");
}

#[test]
fn dotted_key_comment_roundtrip() {
    let input = r###"
rust.unsafe_op_in_unsafe_fn = "deny"

rust.explicit_outlives_requirements = "warn"
# rust.unused_crate_dependencies = "warn"

clippy.cast_lossless = "warn"
clippy.doc_markdown = "warn"
clippy.exhaustive_enums = "warn"
"###;
    let expected = input;

    let manifest: DocumentMut = input.parse().unwrap();
    let actual = manifest.to_string();

    assert_data_eq!(actual, expected.raw());
}

#[test]
fn string_repr_roundtrip() {
    assert_string_repr_roundtrip(r#""""#, str![[r#""""#]]);
    assert_string_repr_roundtrip(r#""a""#, str![[r#""a""#]]);

    assert_string_repr_roundtrip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]);
    assert_string_repr_roundtrip(r#""lf \n lf""#, str![[r#""lf /n lf""#]]);
    assert_string_repr_roundtrip(r#""crlf \r\n crlf""#, str![[r#""crlf /r/n crlf""#]]);
    assert_string_repr_roundtrip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]);
    assert_string_repr_roundtrip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]);
    assert_string_repr_roundtrip(
        r#""backslash \\ backslash""#,
        str![[r#""backslash // backslash""#]],
    );

    assert_string_repr_roundtrip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]);
    assert_string_repr_roundtrip(
        r#""triple squote ''' triple squote""#,
        str![[r#""triple squote ''' triple squote""#]],
    );
    assert_string_repr_roundtrip(r#""end squote '""#, str![[r#""end squote '""#]]);

    assert_string_repr_roundtrip(r#""quote \" quote""#, str![[r#""quote /" quote""#]]);
    assert_string_repr_roundtrip(
        r#""triple quote \"\"\" triple quote""#,
        str![[r#""triple quote /"/"/" triple quote""#]],
    );
    assert_string_repr_roundtrip(r#""end quote \"""#, str![[r#""end quote /"""#]]);
    assert_string_repr_roundtrip(
        r#""quoted \"content\" quoted""#,
        str![[r#""quoted /"content/" quoted""#]],
    );
    assert_string_repr_roundtrip(
        r#""squoted 'content' squoted""#,
        str![[r#""squoted 'content' squoted""#]],
    );
    assert_string_repr_roundtrip(
        r#""mixed quoted \"start\" 'end'' mixed quote""#,
        str![[r#""mixed quoted /"start/" 'end'' mixed quote""#]],
    );
}

#[track_caller]
fn assert_string_repr_roundtrip(input: &str, expected: impl IntoData) {
    let value: Value = input.parse().unwrap();
    let actual = value.to_string();
    let _: Value = actual.parse().unwrap_or_else(|_err| {
        panic!(
            "invalid `Value`:
```
{actual}
```
"
        )
    });
    let expected = expected.into_data();
    assert_data_eq!(actual, expected);
}

#[test]
fn string_value_roundtrip() {
    assert_string_value_roundtrip(r#""""#, str![[r#""""#]]);
    assert_string_value_roundtrip(r#""a""#, str![[r#""a""#]]);

    assert_string_value_roundtrip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]);
    assert_string_value_roundtrip(
        r#""lf \n lf""#,
        str![[r#"
"""
lf 
 lf"""
"#]],
    );
    assert_string_value_roundtrip(
        r#""crlf \r\n crlf""#,
        str![[r#"
"""
crlf /r
 crlf"""
"#]],
    );
    assert_string_value_roundtrip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]);
    assert_string_value_roundtrip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]);
    assert_string_value_roundtrip(
        r#""backslash \\ backslash""#,
        str!["'backslash / backslash'"],
    );

    assert_string_value_roundtrip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]);
    assert_string_value_roundtrip(
        r#""triple squote ''' triple squote""#,
        str![[r#""triple squote ''' triple squote""#]],
    );
    assert_string_value_roundtrip(r#""end squote '""#, str![[r#""end squote '""#]]);

    assert_string_value_roundtrip(r#""quote \" quote""#, str![[r#"'quote " quote'"#]]);
    assert_string_value_roundtrip(
        r#""triple quote \"\"\" triple quote""#,
        str![[r#"'triple quote """ triple quote'"#]],
    );
    assert_string_value_roundtrip(r#""end quote \"""#, str![[r#"'end quote "'"#]]);
    assert_string_value_roundtrip(
        r#""quoted \"content\" quoted""#,
        str![[r#"'quoted "content" quoted'"#]],
    );
    assert_string_value_roundtrip(
        r#""squoted 'content' squoted""#,
        str![[r#""squoted 'content' squoted""#]],
    );
    assert_string_value_roundtrip(
        r#""mixed quoted \"start\" 'end'' mixed quote""#,
        str![[r#"'''mixed quoted "start" 'end'' mixed quote'''"#]],
    );
}

#[track_caller]
fn assert_string_value_roundtrip(input: &str, expected: impl IntoData) {
    let value: Value = input.parse().unwrap();
    let value = Value::from(value.as_str().unwrap()); // Remove repr
    let actual = value.to_string();
    let _: Value = actual.parse().unwrap_or_else(|_err| {
        panic!(
            "invalid `Value`:
```
{actual}
```
"
        )
    });
    let expected = expected.into_data();
    assert_data_eq!(actual, expected);
}

#[test]
fn key_repr_roundtrip() {
    assert_key_repr_roundtrip(r#""""#, str![[r#""""#]]);
    assert_key_repr_roundtrip(r#""a""#, str![[r#""a""#]]);

    assert_key_repr_roundtrip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]);
    assert_key_repr_roundtrip(r#""lf \n lf""#, str![[r#""lf /n lf""#]]);
    assert_key_repr_roundtrip(r#""crlf \r\n crlf""#, str![[r#""crlf /r/n crlf""#]]);
    assert_key_repr_roundtrip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]);
    assert_key_repr_roundtrip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]);
    assert_key_repr_roundtrip(
        r#""backslash \\ backslash""#,
        str![[r#""backslash // backslash""#]],
    );

    assert_key_repr_roundtrip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]);
    assert_key_repr_roundtrip(
        r#""triple squote ''' triple squote""#,
        str![[r#""triple squote ''' triple squote""#]],
    );
    assert_key_repr_roundtrip(r#""end squote '""#, str![[r#""end squote '""#]]);

    assert_key_repr_roundtrip(r#""quote \" quote""#, str![[r#""quote /" quote""#]]);
    assert_key_repr_roundtrip(
        r#""triple quote \"\"\" triple quote""#,
        str![[r#""triple quote /"/"/" triple quote""#]],
    );
    assert_key_repr_roundtrip(r#""end quote \"""#, str![[r#""end quote /"""#]]);
    assert_key_repr_roundtrip(
        r#""quoted \"content\" quoted""#,
        str![[r#""quoted /"content/" quoted""#]],
    );
    assert_key_repr_roundtrip(
        r#""squoted 'content' squoted""#,
        str![[r#""squoted 'content' squoted""#]],
    );
    assert_key_repr_roundtrip(
        r#""mixed quoted \"start\" 'end'' mixed quote""#,
        str![[r#""mixed quoted /"start/" 'end'' mixed quote""#]],
    );
}

#[track_caller]
fn assert_key_repr_roundtrip(input: &str, expected: impl IntoData) {
    let value: Key = input.parse().unwrap();
    let actual = value.to_string();
    let _: Key = actual.parse().unwrap_or_else(|_err| {
        panic!(
            "invalid `Key`:
```
{actual}
```
"
        )
    });
    let expected = expected.into_data();
    assert_data_eq!(actual, expected);
}

#[test]
fn key_value_roundtrip() {
    assert_key_value_roundtrip(r#""""#, str![[r#""""#]]);
    assert_key_value_roundtrip(r#""a""#, str!["a"]);

    assert_key_value_roundtrip(r#""tab \t tab""#, str![[r#""tab /t tab""#]]);
    assert_key_value_roundtrip(r#""lf \n lf""#, str![[r#""lf /n lf""#]]);
    assert_key_value_roundtrip(r#""crlf \r\n crlf""#, str![[r#""crlf /r/n crlf""#]]);
    assert_key_value_roundtrip(r#""bell \b bell""#, str![[r#""bell /b bell""#]]);
    assert_key_value_roundtrip(r#""feed \f feed""#, str![[r#""feed /f feed""#]]);
    assert_key_value_roundtrip(
        r#""backslash \\ backslash""#,
        str!["'backslash / backslash'"],
    );

    assert_key_value_roundtrip(r#""squote ' squote""#, str![[r#""squote ' squote""#]]);
    assert_key_value_roundtrip(
        r#""triple squote ''' triple squote""#,
        str![[r#""triple squote ''' triple squote""#]],
    );
    assert_key_value_roundtrip(r#""end squote '""#, str![[r#""end squote '""#]]);

    assert_key_value_roundtrip(r#""quote \" quote""#, str![[r#"'quote " quote'"#]]);
    assert_key_value_roundtrip(
        r#""triple quote \"\"\" triple quote""#,
        str![[r#"'triple quote """ triple quote'"#]],
    );
    assert_key_value_roundtrip(r#""end quote \"""#, str![[r#"'end quote "'"#]]);
    assert_key_value_roundtrip(
        r#""quoted \"content\" quoted""#,
        str![[r#"'quoted "content" quoted'"#]],
    );
    assert_key_value_roundtrip(
        r#""squoted 'content' squoted""#,
        str![[r#""squoted 'content' squoted""#]],
    );
    assert_key_value_roundtrip(
        r#""mixed quoted \"start\" 'end'' mixed quote""#,
        str![[r#""mixed quoted /"start/" 'end'' mixed quote""#]],
    );
}

#[track_caller]
fn assert_key_value_roundtrip(input: &str, expected: impl IntoData) {
    let value: Key = input.parse().unwrap();
    let value = Key::new(value.get()); // Remove repr
    let actual = value.to_string();
    let _: Key = actual.parse().unwrap_or_else(|_err| {
        panic!(
            "invalid `Key`:
```
{actual}
```
"
        )
    });
    let expected = expected.into_data();
    assert_data_eq!(actual, expected);
}
