use std::collections::{BTreeMap, HashMap};
use std::net::SocketAddr;
use std::path::PathBuf;

use toml_edit_derive::TomlEdit;

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Primitives {
    name: String,
    count: i64,
    ratio: f64,
    flag: bool,
}

#[test]
fn primitives_read() {
    let edit =
        PrimitivesTomlEdit::parse("name = \"hello\"\ncount = 42\nratio = 1.5\nflag = true\n")
            .unwrap();
    assert_eq!(edit.name(), Some("hello".to_owned()));
    assert_eq!(edit.count(), Some(42i64));
    assert_eq!(edit.ratio(), Some(1.5_f64));
    assert_eq!(edit.flag(), Some(true));
}

#[test]
fn primitives_write() {
    let mut edit =
        PrimitivesTomlEdit::parse("name = \"x\"\ncount = 0\nratio = 0.0\nflag = false\n").unwrap();
    edit.name_mut().set("world".to_owned());
    edit.count_mut().set(99i64);
    edit.ratio_mut().set(2.71f64);
    edit.flag_mut().set(true);
    let out = edit.to_toml_string();
    assert!(out.contains("\"world\""));
    assert!(out.contains("99"));
    assert!(out.contains("2.71"));
    assert!(out.contains("true"));
}

#[test]
fn primitives_missing_key_returns_none() {
    let edit = PrimitivesTomlEdit::parse("").unwrap();
    assert_eq!(edit.name(), None);
    assert_eq!(edit.count(), None);
}

#[test]
fn parse_error_on_invalid_toml() {
    assert!(PrimitivesTomlEdit::parse("not valid toml :::").is_err());
}

#[test]
fn comments_preserved_after_mutation() {
    let doc = "# top comment\nname = \"old\"\ncount = 1\nratio = 0.0\nflag = false\n";
    let mut edit = PrimitivesTomlEdit::parse(doc).unwrap();
    edit.name_mut().set("new".to_owned());
    assert!(edit.to_toml_string().contains("# top comment"));
}

#[test]
fn debug_and_clone() {
    let edit =
        PrimitivesTomlEdit::parse("name = \"x\"\ncount = 1\nratio = 0.0\nflag = true\n").unwrap();
    let cloned = edit.clone();
    assert_eq!(edit.name(), cloned.name());
    assert!(!format!("{edit:?}").is_empty());
}

#[test]
fn into_table_produces_valid_table() {
    let p = Primitives {
        name: "hi".to_owned(),
        count: 1,
        ratio: 1.5,
        flag: true,
    };
    let item = <Primitives as toml_edit_derive::TomlEditable>::into_item(p);
    let table = item.into_table().unwrap();
    assert_eq!(
        table["name"].as_value().and_then(|v| v.as_str()),
        Some("hi")
    );
    assert_eq!(
        table["count"].as_value().and_then(|v| v.as_integer()),
        Some(1)
    );
}

#[test]
fn roundtrip_preserves_content() {
    let doc = "name = \"round\"\ncount = 7\nratio = 0.5\nflag = false\n";
    assert_eq!(
        PrimitivesTomlEdit::parse(doc).unwrap().to_toml_string(),
        doc
    );
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Options {
    required: String,
    optional: Option<i64>,
}

#[test]
fn option_present() {
    let edit = OptionsTomlEdit::parse("required = \"r\"\noptional = 7\n").unwrap();
    assert_eq!(edit.optional(), Some(7i64));
}

#[test]
fn option_absent() {
    let edit = OptionsTomlEdit::parse("required = \"r\"\n").unwrap();
    assert_eq!(edit.optional(), None);
}

#[test]
fn option_set_some() {
    let mut edit = OptionsTomlEdit::parse("required = \"r\"\n").unwrap();
    edit.optional_mut().set(42i64);
    assert_eq!(edit.optional(), Some(42i64));
}

#[test]
fn option_set_none_removes_key() {
    let mut edit = OptionsTomlEdit::parse("required = \"r\"\noptional = 5\n").unwrap();
    edit.optional_mut().remove();
    assert_eq!(edit.optional(), None);
    assert!(!edit.to_toml_string().contains("optional"));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct PrimArray {
    tags: Vec<String>,
    scores: Vec<i64>,
}

#[test]
fn array_read() {
    let edit = PrimArrayTomlEdit::parse("tags = [\"a\", \"b\"]\nscores = [1, 2, 3]\n").unwrap();
    assert_eq!(edit.tags(), Some(vec!["a".to_owned(), "b".to_owned()]));
    assert_eq!(edit.scores(), Some(vec![1i64, 2, 3]));
}

#[test]
fn array_write() {
    let mut edit = PrimArrayTomlEdit::parse("tags = []\nscores = []\n").unwrap();
    edit.tags_mut().set(vec!["x".to_owned(), "y".to_owned()]);
    assert_eq!(edit.tags(), Some(vec!["x".to_owned(), "y".to_owned()]));
}

#[test]
fn array_empty() {
    let edit = PrimArrayTomlEdit::parse("tags = []\nscores = []\n").unwrap();
    assert_eq!(edit.tags(), Some(vec![]));
}

#[allow(dead_code)]
#[derive(TomlEdit, Default)]
struct Inner {
    value: i64,
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Outer {
    label: String,
    inner: Inner,
}

#[test]
fn nested_read() {
    let edit = OuterTomlEdit::parse("label = \"top\"\n[inner]\nvalue = 5\n").unwrap();
    assert_eq!(edit.label(), Some("top".to_owned()));
    assert_eq!(edit.inner().unwrap().value(), Some(5i64));
}

#[test]
fn nested_write() {
    let mut edit = OuterTomlEdit::parse("label = \"top\"\n[inner]\nvalue = 5\n").unwrap();
    edit.inner_mut().unwrap().value_mut().set(99i64);
    assert!(edit.to_toml_string().contains("99"));
}

#[test]
fn nested_inline_table_read() {
    // inner stored as inline table, not [inner] section
    let edit = OuterTomlEdit::parse("label = \"top\"\ninner = { value = 5 }\n").unwrap();
    assert_eq!(edit.label(), Some("top".to_owned()));
    assert_eq!(edit.inner().unwrap().value(), Some(5i64));
}

#[test]
fn nested_inline_table_mutate() {
    let mut edit = OuterTomlEdit::parse("label = \"top\"\ninner = { value = 5 }\n").unwrap();
    edit.inner_mut().unwrap().value_mut().set(99i64);
    assert_eq!(edit.inner().unwrap().value(), Some(99i64));
    // stays inline (no [inner] header)
    assert!(!edit.to_toml_string().contains("[inner]"));
}

#[test]
fn nested_missing_returns_none() {
    let edit = OuterTomlEdit::parse("label = \"x\"\n").unwrap();
    assert!(edit.inner().is_none());
}

#[test]
fn nested_comment_preserved() {
    let doc = "label = \"x\"\n[inner]\n# inner comment\nvalue = 1\n";
    let mut edit = OuterTomlEdit::parse(doc).unwrap();
    edit.inner_mut().unwrap().value_mut().set(2i64);
    assert!(edit.to_toml_string().contains("# inner comment"));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Entry {
    id: i64,
    name: String,
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Aot {
    title: String,
    entries: Vec<Entry>,
}

#[test]
fn aot_read() {
    let doc =
        "title = \"t\"\n[[entries]]\nid = 1\nname = \"a\"\n[[entries]]\nid = 2\nname = \"b\"\n";
    let edit = AotTomlEdit::parse(doc).unwrap();
    let e = edit.entries().unwrap();
    assert_eq!(e.len(), 2);
    assert_eq!(e.first().unwrap().id(), Some(1i64));
    assert_eq!(e.get(1).unwrap().name(), Some("b".to_owned()));
}

#[test]
fn aot_iter() {
    let doc =
        "title = \"t\"\n[[entries]]\nid = 1\nname = \"a\"\n[[entries]]\nid = 2\nname = \"b\"\n";
    let edit = AotTomlEdit::parse(doc).unwrap();
    let ids: Vec<_> = edit
        .entries()
        .unwrap()
        .iter()
        .filter_map(|e| e.id())
        .collect();
    assert_eq!(ids, vec![1i64, 2]);
}

#[test]
fn aot_mutate_existing() {
    let doc = "title = \"t\"\n[[entries]]\nid = 1\nname = \"a\"\n";
    let mut edit = AotTomlEdit::parse(doc).unwrap();
    edit.entries_mut()
        .unwrap()
        .get_mut(0)
        .unwrap()
        .id_mut()
        .set(99i64);
    assert!(edit.to_toml_string().contains("99"));
}

#[test]
fn aot_push_empty() {
    let doc = "title = \"t\"\n[[entries]]\nid = 1\nname = \"a\"\n";
    let mut edit = AotTomlEdit::parse(doc).unwrap();
    {
        let mut arr = edit.entries_mut().unwrap();
        let mut e = arr.push_empty();
        e.id_mut().set(2i64);
        e.name_mut().set("b".to_owned());
    }
    assert_eq!(edit.entries().unwrap().len(), 2);
    assert!(edit.to_toml_string().contains("[[entries]]"));
}

#[test]
fn aot_set_empty_vec_preserves_array_of_tables() {
    let doc = "title = \"t\"\n[[entries]]\nid = 1\nname = \"a\"\n";
    let mut edit = AotTomlEdit::parse(doc).unwrap();
    edit.entries_mut().set(vec![]);

    let mut arr = edit.entries_mut().expect("expected array of tables");
    let mut e = arr.push_empty();
    e.id_mut().set(2i64);
    e.name_mut().set("b".to_owned());

    assert_eq!(edit.entries().unwrap().len(), 1);
    assert_eq!(edit.entries().unwrap().first().unwrap().id(), Some(2i64));
}

#[test]
fn empty_struct_vec_into_item_stays_array_of_tables() {
    let item = <Aot as toml_edit_derive::TomlEditable>::into_item(Aot {
        title: "t".to_owned(),
        entries: Vec::new(),
    });
    let table = item.into_table().unwrap();

    assert!(
        table
            .get("entries")
            .and_then(|item| item.as_array_of_tables())
            .is_some()
    );
}

#[test]
fn aot_missing_returns_none() {
    let edit = AotTomlEdit::parse("title = \"t\"\n").unwrap();
    assert!(edit.entries().is_none());
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct InnerList {
    label: String,
    inners: Vec<Inner>,
}

#[test]
fn inner_array_read() {
    let doc = "label = \"x\"\n[[inners]]\nvalue = 1\n[[inners]]\nvalue = 2\n";
    let edit = InnerListTomlEdit::parse(doc).unwrap();
    let arr = edit.inners().unwrap();
    assert_eq!(arr.len(), 2);
    assert_eq!(arr.first().unwrap().value(), Some(1i64));
    assert_eq!(arr.get(1).unwrap().value(), Some(2i64));
}

#[test]
fn inner_array_iter() {
    let doc = "label = \"x\"\n[[inners]]\nvalue = 10\n[[inners]]\nvalue = 20\n";
    let edit = InnerListTomlEdit::parse(doc).unwrap();
    let vals: Vec<_> = edit
        .inners()
        .unwrap()
        .iter()
        .filter_map(|i| i.value())
        .collect();
    assert_eq!(vals, vec![10i64, 20]);
}

#[test]
fn inner_array_mutate() {
    let doc = "label = \"x\"\n[[inners]]\nvalue = 1\n";
    let mut edit = InnerListTomlEdit::parse(doc).unwrap();
    edit.inners_mut()
        .unwrap()
        .get_mut(0)
        .unwrap()
        .value_mut()
        .set(99i64);
    assert_eq!(edit.inners().unwrap().first().unwrap().value(), Some(99i64));
}

#[test]
fn inner_array_push() {
    let doc = "label = \"x\"\n[[inners]]\nvalue = 1\n";
    let mut edit = InnerListTomlEdit::parse(doc).unwrap();
    {
        let mut arr = edit.inners_mut().unwrap();
        let mut e = arr.push_empty();
        e.value_mut().set(2i64);
    }
    let arr = edit.inners().unwrap();
    assert_eq!(arr.len(), 2);
    assert_eq!(arr.get(1).unwrap().value(), Some(2i64));
}

#[test]
fn inner_array_missing_returns_none() {
    let edit = InnerListTomlEdit::parse("label = \"x\"\n").unwrap();
    assert!(edit.inners().is_none());
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct OptInner {
    label: String,
    inner: Option<Inner>,
}

#[test]
fn opt_table_present() {
    let edit = OptInnerTomlEdit::parse("label = \"x\"\n[inner]\nvalue = 3\n").unwrap();
    assert_eq!(edit.inner().unwrap().value(), Some(3i64));
}

#[test]
fn opt_table_set_some() {
    let mut edit = OptInnerTomlEdit::parse("label = \"x\"\n").unwrap();
    edit.inner_mut().set(Inner { value: 42 });
    assert_eq!(edit.inner().unwrap().value(), Some(42i64));
}

#[test]
fn opt_table_set_none() {
    let mut edit = OptInnerTomlEdit::parse("label = \"x\"\n[inner]\nvalue = 3\n").unwrap();
    edit.inner_mut().remove();
    assert!(edit.inner().is_none());
    assert!(!edit.to_toml_string().contains("inner"));
}

#[test]
fn opt_table_replace() {
    let mut edit = OptInnerTomlEdit::parse("label = \"x\"\n[inner]\nvalue = 3\n").unwrap();
    edit.inner_mut().set(Inner { value: 99 });
    assert_eq!(edit.inner().unwrap().value(), Some(99i64));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
#[serde(rename_all = "kebab-case")]
struct KebabKeys {
    max_retries: i64,
    server_host: String,
}

#[test]
fn rename_all_kebab_read() {
    let edit = KebabKeysTomlEdit::parse("max-retries = 5\nserver-host = \"host\"\n").unwrap();
    assert_eq!(edit.max_retries(), Some(5i64));
    assert_eq!(edit.server_host(), Some("host".to_owned()));
}

#[test]
fn rename_all_kebab_write() {
    let mut edit = KebabKeysTomlEdit::parse("max-retries = 0\nserver-host = \"x\"\n").unwrap();
    edit.max_retries_mut().set(9i64);
    let out = edit.to_toml_string();
    assert!(out.contains("max-retries"));
    assert!(out.contains("9"));
    assert!(!out.contains("max_retries"));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
#[serde(rename_all = "camelCase")]
struct CamelKeys {
    max_retries: i64,
}

#[test]
fn rename_all_camel_read() {
    let edit = CamelKeysTomlEdit::parse("maxRetries = 3\n").unwrap();
    assert_eq!(edit.max_retries(), Some(3i64));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct FieldRename {
    #[serde(rename = "my-key")]
    my_key: String,
}

#[test]
fn field_rename_read() {
    let edit = FieldRenameTomlEdit::parse("\"my-key\" = \"val\"\n").unwrap();
    assert_eq!(edit.my_key(), Some("val".to_owned()));
}

#[test]
fn field_rename_write() {
    let mut edit = FieldRenameTomlEdit::parse("\"my-key\" = \"old\"\n").unwrap();
    edit.my_key_mut().set("new".to_owned());
    let out = edit.to_toml_string();
    assert!(out.contains("my-key"));
    assert!(out.contains("new"));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Skip {
    visible: String,
    #[serde(skip)]
    hidden: String,
}

#[test]
fn skip_visible_still_accessible() {
    // hidden field has no accessor — verified at compile time by not calling it
    let edit = SkipTomlEdit::parse("visible = \"yes\"\n").unwrap();
    assert_eq!(edit.visible(), Some("yes".to_owned()));
}

#[derive(TomlEdit, PartialEq, Debug)]
#[serde(rename_all = "snake_case")]
enum LogLevel {
    Debug,
    Info,
    Warn,
    Error,
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct WithLevel {
    name: String,
    level: LogLevel,
}

#[test]
fn unit_enum_read() {
    let edit = WithLevelTomlEdit::parse("name = \"app\"\nlevel = \"warn\"\n").unwrap();
    assert_eq!(edit.level(), Some(LogLevel::Warn));
}

#[test]
fn unit_enum_write() {
    let mut edit = WithLevelTomlEdit::parse("name = \"app\"\nlevel = \"debug\"\n").unwrap();
    edit.level_mut().set(LogLevel::Error);
    assert!(edit.to_toml_string().contains("\"error\""));
    assert!(!edit.to_toml_string().contains("\"debug\""));
}

#[test]
fn unit_enum_field_mut_set() {
    let mut edit = WithLevelTomlEdit::parse("name = \"app\"\nlevel = \"debug\"\n").unwrap();
    edit.level_mut().unwrap().set(LogLevel::Error);
    assert_eq!(edit.level(), Some(LogLevel::Error));
    assert!(edit.to_toml_string().contains("\"error\""));
}

#[test]
fn unit_enum_field_mut_get() {
    let mut edit = WithLevelTomlEdit::parse("name = \"app\"\nlevel = \"warn\"\n").unwrap();
    assert_eq!(edit.level_mut().get(), Some(LogLevel::Warn));
}

#[test]
fn unit_enum_field_mut_missing_returns_none() {
    let mut edit = WithLevelTomlEdit::parse("name = \"app\"\n").unwrap();
    // slot absent -> no mutable handle
    assert!(edit.level_mut().is_none());
}

#[test]
fn unit_enum_missing_returns_none() {
    let edit = WithLevelTomlEdit::parse("name = \"app\"\n").unwrap();
    assert_eq!(edit.level(), None);
}

#[test]
fn unit_enum_unknown_value_returns_none() {
    let edit = WithLevelTomlEdit::parse("name = \"app\"\nlevel = \"trace\"\n").unwrap();
    assert_eq!(edit.level(), None);
}

#[derive(TomlEdit)]
#[serde(tag = "type", rename_all = "snake_case")]
enum Event {
    Click {
        x: i64,
        y: i64,
    },
    #[allow(dead_code)]
    Key {
        code: i64,
    },
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Log {
    entries: Vec<Event>,
}

#[test]
fn struct_enum_read_click() {
    let edit = EventTomlEdit::parse("type = \"click\"\nx = 10\ny = 20\n").unwrap();
    let r = edit.as_click().unwrap();
    assert_eq!(r.x(), Some(10i64));
    assert_eq!(r.y(), Some(20i64));
}

#[test]
fn struct_enum_read_key() {
    let edit = EventTomlEdit::parse("type = \"key\"\ncode = 65\n").unwrap();
    let r = edit.as_key().unwrap();
    assert_eq!(r.code(), Some(65i64));
}

#[test]
fn struct_enum_wrong_variant_returns_none() {
    let edit = EventTomlEdit::parse("type = \"click\"\nx = 1\ny = 2\n").unwrap();
    assert!(edit.as_key().is_none());
}

#[test]
fn struct_enum_unknown_tag_returns_unknown() {
    let edit = EventTomlEdit::parse("type = \"scroll\"\n").unwrap();
    assert!(matches!(edit.view(), EventTomlEditView::Unknown));
}

#[test]
fn struct_enum_mutate() {
    let mut edit = EventTomlEdit::parse("type = \"click\"\nx = 1\ny = 2\n").unwrap();
    edit.as_click_mut().unwrap().x_mut().set(99i64);
    assert!(edit.to_toml_string().contains("99"));
}

#[test]
fn struct_enum_into_table() {
    let item = <Event as toml_edit_derive::TomlEditable>::into_item(Event::Click { x: 3, y: 7 });
    let table = item.into_table().unwrap();
    assert_eq!(
        table["type"].as_value().and_then(|v| v.as_str()),
        Some("click")
    );
    assert_eq!(table["x"].as_value().and_then(|v| v.as_integer()), Some(3));
}

#[test]
fn struct_enum_nested_in_aot() {
    let doc =
        "[[entries]]\ntype = \"click\"\nx = 1\ny = 2\n[[entries]]\ntype = \"key\"\ncode = 65\n";
    let edit = LogTomlEdit::parse(doc).unwrap();
    let arr = edit.entries().unwrap();
    assert_eq!(arr.len(), 2);
    let first = arr.first().unwrap();
    assert!(matches!(first, EventTomlEditView::Click(_)));
    if let EventTomlEditView::Click(r) = first {
        assert_eq!(r.x(), Some(1i64));
    }
}

#[test]
fn struct_enum_aot_push_empty() {
    let doc = "[[entries]]\ntype = \"click\"\nx = 1\ny = 2\n";
    let mut edit = LogTomlEdit::parse(doc).unwrap();
    edit.entries_mut().set(vec![]);

    {
        let mut arr = edit.entries_mut().unwrap();
        let mut entry = arr.push_empty().click();
        entry.x_mut().set(3i64);
        entry.y_mut().set(7i64);
    }

    let arr = edit.entries().unwrap();
    assert_eq!(arr.len(), 1);
    let first = arr.first().unwrap();
    assert!(matches!(first, EventTomlEditView::Click(_)));
    assert!(edit.to_toml_string().contains("type = \"click\""));
}

#[test]
fn struct_enum_aot_as_key_mut() {
    let doc = "[[entries]]\ntype = \"click\"\nx = 1\ny = 2\n";
    let mut edit = LogTomlEdit::parse(doc).unwrap();
    edit.entries_mut().set(vec![]);

    {
        let mut arr = edit.entries_mut().unwrap();
        let mut entry = arr.push_empty().key();
        entry.code_mut().set(65i64);
    }

    let arr = edit.entries().unwrap();
    assert_eq!(arr.len(), 1);
    assert!(matches!(arr.first().unwrap(), EventTomlEditView::Key(_)));
    assert!(edit.to_toml_string().contains("type = \"key\""));
}

#[test]
fn struct_enum_aot_push_empty_wrong_variant_returns_none() {
    let doc = "[[entries]]\ntype = \"click\"\nx = 1\ny = 2\n";
    let mut edit = LogTomlEdit::parse(doc).unwrap();
    {
        let mut arr = edit.entries_mut().unwrap();
        let builder = arr.push_empty();
        // builder.click() sets the tag; as_key_mut on a click entry returns None
        let mut click = builder.click();
        click.x_mut().set(5i64);
    }
    {
        let mut arr = edit.entries_mut().unwrap();
        // entry at index 1 is a click; as_key_mut should return None
        assert!(arr.get_mut(1).unwrap().as_key_mut().is_none());
    }
}

#[test]
fn struct_enum_aot_iter_mut() {
    let doc =
        "[[entries]]\ntype = \"click\"\nx = 1\ny = 2\n[[entries]]\ntype = \"key\"\ncode = 65\n";
    let mut edit = LogTomlEdit::parse(doc).unwrap();
    {
        let mut arr = edit.entries_mut().unwrap();
        for builder in arr.iter_mut() {
            if let Some(mut click) = builder.as_click_mut() {
                click.x_mut().set(99i64);
            }
        }
    }
    assert!(edit.to_toml_string().contains("99"));
    // key entry unchanged
    assert!(edit.to_toml_string().contains("code = 65"));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct PrimMap {
    settings: HashMap<String, i64>,
}

#[test]
fn prim_map_read() {
    let edit = PrimMapTomlEdit::parse("[settings]\na = 1\nb = 2\n").unwrap();
    let m = edit.settings().unwrap();
    assert_eq!(m.len(), 2);
    assert_eq!(m.get("a"), Some(&1));
    assert_eq!(m.get("b"), Some(&2));
}

#[test]
fn prim_map_write() {
    let mut edit = PrimMapTomlEdit::parse("").unwrap();
    let mut m = HashMap::new();
    m.insert("x".to_owned(), 5i64);
    m.insert("y".to_owned(), 9i64);
    edit.settings_mut().set(m);
    let got = edit.settings().unwrap();
    assert_eq!(got.get("x"), Some(&5));
    assert_eq!(got.get("y"), Some(&9));
}

#[test]
fn prim_map_missing_returns_none() {
    let edit = PrimMapTomlEdit::parse("").unwrap();
    assert!(edit.settings().is_none());
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct SortedMap {
    labels: BTreeMap<String, String>,
}

#[test]
fn btree_map_read_is_sorted() {
    let edit =
        SortedMapTomlEdit::parse("[labels]\nb = \"two\"\na = \"one\"\nc = \"three\"\n").unwrap();
    let m = edit.labels().unwrap();
    let keys: Vec<_> = m.keys().cloned().collect();
    assert_eq!(keys, vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]);
    assert_eq!(m.get("a"), Some(&"one".to_owned()));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct TableMap {
    inners: HashMap<String, Inner>,
}

#[test]
fn table_map_read() {
    let edit = TableMapTomlEdit::parse("[inners.x]\nvalue = 1\n[inners.y]\nvalue = 2\n").unwrap();
    let m = edit.inners().unwrap();
    assert_eq!(m.len(), 2);
    assert_eq!(m.get("x").unwrap().value(), Some(1i64));
    assert_eq!(m.get("y").unwrap().value(), Some(2i64));
}

#[test]
fn table_map_set() {
    let mut edit = TableMapTomlEdit::parse("").unwrap();
    let mut src = HashMap::new();
    src.insert("a".to_owned(), Inner { value: 7 });
    edit.inners_mut().set(src);
    assert_eq!(edit.inners().unwrap().get("a").unwrap().value(), Some(7i64));
}

#[test]
fn table_map_mut() {
    let mut edit = TableMapTomlEdit::parse("[inners.x]\nvalue = 1\n").unwrap();
    {
        let mut m = edit.inners_mut().unwrap();
        m.get_mut("x").unwrap().value_mut().set(99i64);
        let mut e = m.insert_empty("y");
        e.value_mut().set(2i64);
    }
    let m = edit.inners().unwrap();
    assert_eq!(m.get("x").unwrap().value(), Some(99i64));
    assert_eq!(m.get("y").unwrap().value(), Some(2i64));
}

#[test]
fn table_map_iter() {
    let edit = TableMapTomlEdit::parse("[inners.x]\nvalue = 10\n[inners.y]\nvalue = 20\n").unwrap();
    let m = edit.inners().unwrap();
    let mut sum = 0;
    for (_k, v) in m.iter() {
        sum += v.value().unwrap();
    }
    assert_eq!(sum, 30);
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct OptMap {
    settings: Option<HashMap<String, i64>>,
}

#[test]
fn opt_map_present() {
    let edit = OptMapTomlEdit::parse("[settings]\na = 1\n").unwrap();
    assert_eq!(edit.settings().unwrap().get("a"), Some(&1));
}

#[test]
fn opt_map_absent() {
    let edit = OptMapTomlEdit::parse("").unwrap();
    assert!(edit.settings().is_none());
}

#[test]
fn opt_map_set_none_removes_key() {
    let mut edit = OptMapTomlEdit::parse("[settings]\na = 1\n").unwrap();
    edit.settings_mut().remove();
    assert!(edit.settings().is_none());
    assert!(!edit.to_toml_string().contains("settings"));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Paths {
    log: PathBuf,
    listen: SocketAddr,
}

#[test]
fn pathbuf_and_socketaddr_read() {
    let edit =
        PathsTomlEdit::parse("log = \"/var/log/app.log\"\nlisten = \"127.0.0.1:8080\"\n").unwrap();
    assert_eq!(edit.log(), Some(PathBuf::from("/var/log/app.log")));
    assert_eq!(
        edit.listen(),
        Some("127.0.0.1:8080".parse::<SocketAddr>().unwrap())
    );
}

#[test]
fn pathbuf_write_and_mut() {
    let mut edit = PathsTomlEdit::parse("log = \"/a\"\nlisten = \"127.0.0.1:1\"\n").unwrap();
    edit.log_mut().set(PathBuf::from("/tmp/x.log"));
    assert_eq!(edit.log(), Some(PathBuf::from("/tmp/x.log")));
    edit.log_mut().unwrap().set(PathBuf::from("/b"));
    assert_eq!(edit.log(), Some(PathBuf::from("/b")));
    assert!(edit.to_toml_string().contains("\"/b\""));
}

#[test]
fn socketaddr_invalid_returns_none() {
    let edit = PathsTomlEdit::parse("log = \"/x\"\nlisten = \"not-an-addr\"\n").unwrap();
    assert_eq!(edit.listen(), None);
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct PathColl {
    dirs: Vec<PathBuf>,
    cache: Option<PathBuf>,
}

#[test]
fn pathbuf_vec_and_option() {
    let edit = PathCollTomlEdit::parse("dirs = [\"/a\", \"/b\"]\ncache = \"/c\"\n").unwrap();
    assert_eq!(
        edit.dirs(),
        Some(vec![PathBuf::from("/a"), PathBuf::from("/b")])
    );
    assert_eq!(edit.cache(), Some(PathBuf::from("/c")));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct DbConfig {
    host: String,
    port: i64,
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct AppConfig {
    name: String,
    #[serde(flatten)]
    db: DbConfig,
}

#[test]
fn flatten_read() {
    let edit =
        AppConfigTomlEdit::parse("name = \"app\"\nhost = \"localhost\"\nport = 5432\n").unwrap();
    assert_eq!(edit.name(), Some("app".to_owned()));
    let db = edit.db().unwrap();
    assert_eq!(db.host(), Some("localhost".to_owned()));
    assert_eq!(db.port(), Some(5432i64));
}

#[test]
fn flatten_mut_edits_parent_table() {
    let mut edit =
        AppConfigTomlEdit::parse("name = \"app\"\nhost = \"localhost\"\nport = 5432\n").unwrap();
    edit.db_mut().unwrap().port_mut().set(6000i64);
    assert_eq!(edit.db().unwrap().port(), Some(6000i64));
    // flattened: no [db] header in output
    assert!(!edit.to_toml_string().contains("[db]"));
}

#[test]
fn flatten_into_item_merges_to_top_level() {
    let item = <AppConfig as toml_edit_derive::TomlEditable>::into_item(AppConfig {
        name: "app".to_owned(),
        db: DbConfig {
            host: "h".to_owned(),
            port: 1,
        },
    });
    let table = item.into_table().unwrap();
    assert_eq!(table["host"].as_value().and_then(|v| v.as_str()), Some("h"));
    assert_eq!(
        table["port"].as_value().and_then(|v| v.as_integer()),
        Some(1)
    );
    // not nested under a `db` key
    assert!(table.get("db").is_none());
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct Seeded {
    count: i64,
    label: String,
    level: LogLevel,
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct IntBounds {
    u8_val: u8,
    u16_val: u16,
    i8_val: i8,
}

#[test]
fn if_absent_sets_when_key_missing() {
    let mut edit = SeededTomlEdit::parse("").unwrap();
    edit.count_mut().get_or_insert(42i64);
    assert_eq!(edit.count(), Some(42i64));
}

#[test]
fn if_absent_preserves_existing_value() {
    let mut edit = SeededTomlEdit::parse("count = 99\n").unwrap();
    edit.count_mut().get_or_insert(42i64);
    assert_eq!(edit.count(), Some(99i64));
}

#[test]
fn if_absent_works_for_string() {
    let mut edit = SeededTomlEdit::parse("").unwrap();
    edit.label_mut().get_or_insert("default".to_owned());
    assert_eq!(edit.label(), Some("default".to_owned()));
    // does not overwrite
    edit.label_mut().get_or_insert("other".to_owned());
    assert_eq!(edit.label(), Some("default".to_owned()));
}

#[test]
fn if_absent_works_for_value_enum() {
    let mut edit = SeededTomlEdit::parse("").unwrap();
    edit.level_mut().get_or_insert(LogLevel::Warn);
    assert_eq!(edit.level(), Some(LogLevel::Warn));
    // does not overwrite
    edit.level_mut().get_or_insert(LogLevel::Error);
    assert_eq!(edit.level(), Some(LogLevel::Warn));
}

#[test]
fn if_absent_on_mut_proxy() {
    let mut edit = OuterTomlEdit::parse("[inner]\nvalue = 5\n").unwrap();
    edit.inner_mut().unwrap().value_mut().get_or_insert(99i64);
    // already present — not overwritten
    assert_eq!(edit.inner().unwrap().value(), Some(5i64));
}

#[test]
fn if_absent_on_mut_proxy_missing_key() {
    let mut edit = OuterTomlEdit::parse("[inner]\n").unwrap();
    edit.inner_mut().unwrap().value_mut().get_or_insert(42i64);
    assert_eq!(edit.inner().unwrap().value(), Some(42i64));
}

#[test]
fn get_or_insert_empty_creates_empty_table() {
    let mut edit = OuterTomlEdit::parse("label = \"x\"\n").unwrap();
    edit.inner_mut().get_or_insert_empty().value_mut().set(7i64);
    assert_eq!(edit.inner().unwrap().value(), Some(7i64));
}

#[test]
fn get_or_insert_empty_preserves_existing() {
    let mut edit = OuterTomlEdit::parse("label = \"x\"\n[inner]\nvalue = 5\n").unwrap();
    edit.inner_mut()
        .get_or_insert_empty()
        .value_mut()
        .set(99i64);
    // key already present — existing table is used, value overwritten by the set
    assert_eq!(edit.inner().unwrap().value(), Some(99i64));
}

#[test]
fn get_or_insert_default_inserts_type_default_when_absent() {
    let mut edit = OuterTomlEdit::parse("label = \"x\"\n").unwrap();
    // Inner::default() has value = 0; key is absent so default is inserted
    edit.inner_mut().get_or_insert_default();
    assert_eq!(edit.inner().unwrap().value(), Some(0i64));
}

#[test]
fn get_or_insert_default_preserves_existing_table() {
    let mut edit = OuterTomlEdit::parse("label = \"x\"\n[inner]\nvalue = 5\n").unwrap();
    edit.inner_mut().get_or_insert_default();
    assert_eq!(edit.inner().unwrap().value(), Some(5i64));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct WithOptInner {
    label: String,
    inner: Option<Inner>,
}

#[test]
fn opt_table_mut_returns_some_when_present() {
    let mut edit = WithOptInnerTomlEdit::parse("label = \"x\"\n[inner]\nvalue = 3\n").unwrap();
    edit.inner_mut().unwrap().value_mut().set(99i64);
    assert_eq!(edit.inner().unwrap().value(), Some(99i64));
}

#[test]
fn opt_table_mut_returns_none_when_absent() {
    let mut edit = WithOptInnerTomlEdit::parse("label = \"x\"\n").unwrap();
    assert!(edit.inner_mut().is_none());
}

#[test]
fn opt_table_set_then_mut() {
    let mut edit = WithOptInnerTomlEdit::parse("label = \"x\"\n").unwrap();
    edit.inner_mut().set(Inner { value: 1 });
    edit.inner_mut().unwrap().value_mut().set(99i64);
    assert_eq!(edit.inner().unwrap().value(), Some(99i64));
}

#[test]
fn get_or_insert_empty_replaces_wrong_type_value() {
    let mut edit = WithOptInnerTomlEdit::parse("label = \"x\"\ninner = \"not a table\"\n").unwrap();
    // inner exists as a string — get_or_insert_empty must replace it with an empty table
    edit.inner_mut()
        .get_or_insert_empty()
        .value_mut()
        .set(42i64);
    assert_eq!(edit.inner().unwrap().value(), Some(42i64));
}

#[test]
fn get_or_insert_replaces_wrong_type_value() {
    let mut edit = WithOptInnerTomlEdit::parse("label = \"x\"\ninner = \"not a table\"\n").unwrap();
    edit.inner_mut()
        .get_or_insert(Inner { value: 7 })
        .value_mut()
        .set(99i64);
    assert_eq!(edit.inner().unwrap().value(), Some(99i64));
}

#[allow(dead_code)]
#[derive(TomlEdit)]
struct VecOfMaps {
    sections: Vec<HashMap<String, String>>,
}

#[test]
fn vec_of_maps_roundtrip() {
    let doc = "[[sections]]\na = \"1\"\nb = \"2\"\n[[sections]]\nc = \"3\"\n";
    let edit = VecOfMapsTomlEdit::parse(doc).unwrap();
    let sections = edit.sections().unwrap();
    assert_eq!(sections.len(), 2);
    assert_eq!(sections.first().unwrap().get("a"), Some(&"1".to_owned()));
    assert_eq!(sections.get(1).unwrap().get("c"), Some(&"3".to_owned()));
}

#[test]
fn value_array_get_mut_returns_some() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"a\", \"b\"]\nscores = [1, 2]\n").unwrap();
    let mut entry = edit.tags_mut();
    let arr = entry.get_mut().expect("expected Some for value array");
    assert_eq!(arr.len(), 2);
    assert_eq!(arr.get(0), Some("a".to_owned()));
    assert_eq!(arr.get(1), Some("b".to_owned()));
}

#[test]
fn value_array_iter_via_get_mut() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"x\", \"y\", \"z\"]\nscores = []\n").unwrap();
    let mut entry = edit.tags_mut();
    let arr = entry.get_mut().unwrap();
    let tags: Vec<_> = arr.iter().collect();
    assert_eq!(tags, vec!["x".to_owned(), "y".to_owned(), "z".to_owned()]);
}

#[test]
fn value_array_remove_via_get_mut() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"a\", \"b\", \"c\"]\nscores = []\n").unwrap();
    {
        let mut entry = edit.tags_mut();
        entry.get_mut().unwrap().remove(1);
    }
    assert_eq!(edit.tags(), Some(vec!["a".to_owned(), "c".to_owned()]));
}

#[test]
fn value_array_get_mut_element_and_set() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"a\", \"b\"]\nscores = []\n").unwrap();
    {
        let mut entry = edit.tags_mut();
        let mut arr = entry.get_mut().unwrap();
        arr.get_mut(0).unwrap().set("z".to_owned());
    }
    assert_eq!(edit.tags(), Some(vec!["z".to_owned(), "b".to_owned()]));
}

#[test]
fn value_array_iter_mut_set_all() {
    let mut edit = PrimArrayTomlEdit::parse("scores = [1, 2, 3]\ntags = []\n").unwrap();
    {
        let mut entry = edit.scores_mut();
        let mut arr = entry.get_mut().unwrap();
        for mut m in arr.iter_mut() {
            let v = m.get().unwrap_or(0);
            m.set(v * 10);
        }
    }
    assert_eq!(edit.scores(), Some(vec![10i64, 20, 30]));
}

#[test]
fn u8_rejects_out_of_range() {
    let edit = IntBoundsTomlEdit::parse("u8_val = 256\nu16_val = 0\ni8_val = 0\n").unwrap();
    // 256 > u8::MAX (255), should return None
    assert_eq!(edit.u8_val(), None);
}

#[test]
fn u8_accepts_max() {
    let edit = IntBoundsTomlEdit::parse("u8_val = 255\nu16_val = 0\ni8_val = 0\n").unwrap();
    assert_eq!(edit.u8_val(), Some(255u8));
}

#[test]
fn i8_rejects_out_of_range_positive() {
    let edit = IntBoundsTomlEdit::parse("u8_val = 0\nu16_val = 0\ni8_val = 128\n").unwrap();
    // 128 > i8::MAX (127), should return None
    assert_eq!(edit.i8_val(), None);
}

#[test]
fn i8_rejects_out_of_range_negative() {
    let edit = IntBoundsTomlEdit::parse("u8_val = 0\nu16_val = 0\ni8_val = -129\n").unwrap();
    // -129 < i8::MIN (-128), should return None
    assert_eq!(edit.i8_val(), None);
}

#[test]
fn i8_accepts_bounds() {
    let edit = IntBoundsTomlEdit::parse("u8_val = 0\nu16_val = 0\ni8_val = 127\n").unwrap();
    assert_eq!(edit.i8_val(), Some(127i8));
    let edit = IntBoundsTomlEdit::parse("u8_val = 0\nu16_val = 0\ni8_val = -128\n").unwrap();
    assert_eq!(edit.i8_val(), Some(-128i8));
}

#[test]
fn u16_rejects_out_of_range() {
    let edit = IntBoundsTomlEdit::parse("u8_val = 0\nu16_val = 65536\ni8_val = 0\n").unwrap();
    // 65536 > u16::MAX (65535), should return None
    assert_eq!(edit.u16_val(), None);
}

#[test]
fn array_proxy_at_convenience() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"a\", \"b\", \"c\"]\nscores = []\n").unwrap();
    {
        let mut entry = edit.tags_mut();
        let mut arr = entry.get_mut().unwrap();
        arr.at(1).unwrap().set("x".to_owned());
    }
    assert_eq!(
        edit.tags(),
        Some(vec!["a".to_owned(), "x".to_owned(), "c".to_owned()])
    );
}

#[test]
fn array_proxy_at_missing_index() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"a\"]\nscores = []\n").unwrap();
    let mut entry = edit.tags_mut();
    let mut arr = entry.get_mut().unwrap();
    assert!(arr.at(5).is_none());
}

#[test]
fn map_proxy_get_mut_via_binding() {
    let mut edit = PrimMapTomlEdit::parse("[settings]\na = 1\nb = 2\n").unwrap();
    {
        let mut entry = edit.settings_mut();
        let mut map = entry.get_mut().unwrap();
        map.get_mut("a").unwrap().set(99i64);
    }
    let m = edit.settings().unwrap();
    assert_eq!(m.get("a"), Some(&99));
    assert_eq!(m.get("b"), Some(&2));
}

#[test]
fn map_proxy_missing_key_via_binding() {
    let mut edit = PrimMapTomlEdit::parse("[settings]\na = 1\n").unwrap();
    let mut entry = edit.settings_mut();
    let mut map = entry.get_mut().unwrap();
    assert!(map.get_mut("missing").is_none());
}

// ── FieldEntry builder methods ───────────────────────────────────────────

#[test]
fn field_entry_replace() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"old\"]\nscores = []\n").unwrap();
    edit.tags_mut()
        .replace(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()]);
    assert_eq!(
        edit.tags(),
        Some(vec!["a".to_owned(), "b".to_owned(), "c".to_owned()])
    );
}

#[test]
fn field_entry_and_mutate_present() {
    let mut edit = OuterTomlEdit::parse("label = \"x\"\n[inner]\nvalue = 5\n").unwrap();
    let did_mutate = edit.inner_mut().and_mutate(|inner| {
        inner.value_mut().set(99i64);
    });
    assert!(did_mutate);
    assert_eq!(edit.inner().unwrap().value(), Some(99i64));
}

#[test]
fn field_entry_and_mutate_absent() {
    let mut edit = OuterTomlEdit::parse("label = \"x\"\n").unwrap();
    let did_mutate = edit.inner_mut().and_mutate(|inner| {
        inner.value_mut().set(99i64);
    });
    assert!(!did_mutate);
    assert!(edit.inner().is_none());
}

#[test]
fn field_entry_expect_present() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"a\"]\nscores = []\n").unwrap();
    let arr = edit.tags_mut().expect("tags must exist");
    assert_eq!(arr.len(), 1);
}

#[test]
#[should_panic(expected = "tags must exist")]
fn field_entry_expect_absent() {
    let mut edit = PrimArrayTomlEdit::parse("tags = []\nscores = []\n").unwrap();
    edit.tags_mut().remove();
    let _arr = edit.tags_mut().expect("tags must exist");
}

#[test]
fn array_clear() {
    let mut edit = PrimArrayTomlEdit::parse("tags = [\"a\", \"b\", \"c\"]\nscores = []\n").unwrap();
    {
        let mut entry = edit.tags_mut();
        let mut arr = entry.get_mut().unwrap();
        arr.clear();
    }
    assert_eq!(edit.tags(), Some(vec![]));
}

#[test]
#[should_panic(expected = "out of TOML integer range")]
fn into_value_panics_for_u64_overflow() {
    use toml_edit_derive::TomlEditValue;
    let _ = u64::MAX.into_value();
}

#[test]
#[should_panic(expected = "out of TOML integer range")]
fn into_value_panics_for_i128_overflow() {
    use toml_edit_derive::TomlEditValue;
    let _ = i128::MAX.into_value();
}

#[test]
fn get_or_insert_empty_works_for_vec_of_structs() {
    let mut edit = AotTomlEdit::parse("title = \"t\"\n").unwrap();
    {
        let mut arr = edit.entries_mut().get_or_insert_empty();
        assert_eq!(arr.len(), 0);
        arr.push_empty().id_mut().set(1i64);
    }
    assert_eq!(edit.entries().unwrap().len(), 1);
    assert_eq!(edit.entries().unwrap().first().unwrap().id(), Some(1i64));
}

#[test]
fn get_or_insert_empty_works_for_vec_of_scalars() {
    let mut edit = PrimArrayTomlEdit::parse("scores = []\n").unwrap();
    {
        let arr = edit.tags_mut().get_or_insert_empty();
        assert_eq!(arr.len(), 0);
    }
    assert_eq!(edit.tags(), Some(vec![]));
}

#[test]
fn get_or_insert_replaces_wrong_type_scalar_field() {
    use toml_edit_derive::{Item, Table};
    let mut edit = PrimitivesTomlEdit::parse("count = 0\nratio = 0.0\nflag = false\n").unwrap();
    edit.doc.insert("name", Item::Table(Table::new()));
    let _handle = edit.name_mut().get_or_insert("fallback".to_owned());
    assert_eq!(edit.name(), Some("fallback".to_owned()));
}

#[test]
fn replace_preserves_key_position_and_comment() {
    // replace() now uses in-place slot assignment (like set()), so key decor is preserved.
    let doc = "# header\ncount = 1\nname = \"old\"\nflag = false\nratio = 0.0\n";
    let mut edit = PrimitivesTomlEdit::parse(doc).unwrap();
    edit.name_mut().replace("new".to_owned());
    let out = edit.to_toml_string();
    // Comment and key order preserved
    assert!(out.contains("# header"));
    let name_pos = out.find("name").unwrap();
    let count_pos = out.find("count").unwrap();
    assert!(
        count_pos < name_pos,
        "key order must be preserved after replace"
    );
    assert_eq!(edit.name(), Some("new".to_owned()));
}

#[test]
fn set_preserves_inline_value_comment() {
    // Verify the library's core promise: inline comments on values survive a set().
    let doc = "count = 1 # keep me\nname = \"x\"\nratio = 0.0\nflag = false\n";
    let mut edit = PrimitivesTomlEdit::parse(doc).unwrap();
    edit.count_mut().set(99i64);
    let out = edit.to_toml_string();
    assert!(
        out.contains("# keep me"),
        "inline comment must survive set()"
    );
    assert!(out.contains("99"));
}

#[test]
fn aot_clear_works() {
    let doc =
        "title = \"t\"\n[[entries]]\nid = 1\nname = \"a\"\n[[entries]]\nid = 2\nname = \"b\"\n";
    let mut edit = AotTomlEdit::parse(doc).unwrap();
    {
        let mut entry = edit.entries_mut();
        let mut arr = entry.get_mut().unwrap();
        arr.clear();
    }
    assert_eq!(edit.entries().unwrap().len(), 0);
}
