///|
/// Create or get a nested table structure from a path
fn create_nested_table(
  root_table : Map[String, TomlValue],
  path : Array[String],
) -> Map[String, TomlValue] raise TableConflicts {
  let mut current_table = root_table
  for key in path {
    match current_table.get(key) {
      Some(TomlTable(existing_table)) => current_table = existing_table
      Some(value) => raise ExpectedTable(key, value)
      // parse_error(
      //   error_tokens,
      //   "Table path conflicts with existing value: \{key}",
      // )
      None => {
        let new_table = Map::new()
        current_table[key] = TomlTable(new_table)
        current_table = new_table
      }
    }
  }
  current_table
}

///|
priv suberror TableConflicts {
  ExpectedTable(String, TomlValue)
  ExpectedArray(String, TomlValue)
} derive(Show, ToJson)

///|
/// Create or append to an array of tables structure in TOML.
/// 
/// This function handles the `[[table]]` syntax in TOML, which creates an array
/// of tables. If the array doesn't exist, it creates it with the first table.
/// If the array exists, it appends a new table to it.
/// 
/// ## Parameters
/// - `root_table`: The root table where the array of tables will be created/modified
/// - `path`: The dotted key path to the array (e.g., ["servers", "alpha"] for [[servers.alpha]])
/// - `error_tokens`: Token view for error reporting
/// 
/// ## Returns
/// The newly created empty table that was added to the array
/// 
/// ## Errors
/// - Raises if the path conflicts with existing non-table values
/// - Raises if the final key exists but is not an array
/// 
/// ## Example
/// ```
/// // For TOML: [[servers.alpha]]
/// // Creates: { servers: { alpha: [{}] } }
/// // Second call appends: { servers: { alpha: [{}, {}] } }
/// ```
fn create_array_of_tables(
  root_table : Map[String, TomlValue],
  path : Array[String],
) -> Map[String, TomlValue] raise TableConflicts {
  let mut current_table = root_table

  // Navigate to the parent of the final key
  for key in path[:-1] {
    match current_table.get(key) {
      Some(TomlTable(existing_table)) => current_table = existing_table
      Some(value) => raise ExpectedTable(key, value)
      None => {
        let new_table = Map::new()
        current_table[key] = TomlTable(new_table)
        current_table = new_table
      }
    }
  }

  // Handle the final key as an array of tables
  let final_key = path[path.length() - 1]
  let new_table = {}
  match current_table.get(final_key) {
    Some(TomlArray(existing_array)) => {
      // Append a new table to the existing array
      existing_array.push(TomlTable(new_table))
      new_table
    }
    Some(value) => raise ExpectedArray(final_key, value)
    None => {
      // Create new array with the first table
      let array = Array::new()
      array.push(TomlTable(new_table))
      current_table[final_key] = TomlArray(array)
      new_table
    }
  }
}

///|
test "create_array_of_tables - first table creation" {
  // Test creating the first table in an array of tables
  let root_table : Map[String, TomlValue] = {}

  // Create first table in array [[servers]]
  let new_table = create_array_of_tables(root_table, ["servers"])

  // Add some data to the new table
  new_table["name"] = TomlString("alpha")
  new_table["ip"] = TomlString("10.0.0.1")
  inspect(
    TomlTable(root_table),
    content=(
      #|[[servers]]
      #|name = "alpha"
      #|ip = "10.0.0.1"
      #|
    ),
  )
}

///|
test "create_array_of_tables - append to existing array" {
  // Test appending to an existing array of tables
  let root_table : Map[String, TomlValue] = {}

  // Create first table
  let table1 = create_array_of_tables(root_table, ["products"])
  table1["name"] = TomlString("Hammer")
  table1["sku"] = TomlInteger(738594937)

  // Append second table to same array
  let table2 = create_array_of_tables(root_table, ["products"])
  table2["name"] = TomlString("Nail")
  table2["sku"] = TomlInteger(284758393)

  // Append third table
  let table3 = create_array_of_tables(root_table, ["products"])
  table3["name"] = TomlString("Screwdriver")
  table3["sku"] = TomlInteger(987654321)
  inspect(
    TomlTable(root_table),
    content=(
      #|[[products]]
      #|name = "Hammer"
      #|sku = 738594937
      #|
      #|[[products]]
      #|name = "Nail"
      #|sku = 284758393
      #|
      #|[[products]]
      #|name = "Screwdriver"
      #|sku = 987654321
      #|
    ),
  )
}

///|
test "create_array_of_tables - nested path creation" {
  // Test creating array of tables with nested path
  let root_table : Map[String, TomlValue] = {}

  // Create [[fruit.physical.color]]
  let color_table1 = create_array_of_tables(root_table, [
    "fruit", "physical", "color",
  ])
  color_table1["name"] = TomlString("red")
  color_table1["code"] = TomlString("#FF0000")

  // Add another color
  let color_table2 = create_array_of_tables(root_table, [
    "fruit", "physical", "color",
  ])
  color_table2["name"] = TomlString("green")
  color_table2["code"] = TomlString("#00FF00")
  inspect(
    TomlTable(root_table),
    content=(
      #|[fruit]
      #|[fruit.physical]
      #|[[fruit.physical.color]]
      #|name = "red"
      #|code = "#FF0000"
      #|
      #|[[fruit.physical.color]]
      #|name = "green"
      #|code = "#00FF00"
      #|
    ),
  )
}

///|
test "create_array_of_tables - mixed with regular tables" {
  // Test array of tables mixed with regular tables
  let root_table : Map[String, TomlValue] = Map::new()

  // First create a regular table structure
  set_dotted_key_value(root_table, ["owner", "name"], TomlString("Tom"))
  set_dotted_key_value(root_table, ["owner", "dob"], TomlString("1979-05-27"))

  // Now create array of tables at a different path
  let db1 = create_array_of_tables(root_table, ["database", "servers"])
  db1["host"] = TomlString("192.168.1.1")
  db1["port"] = TomlInteger(5432)
  let db2 = create_array_of_tables(root_table, ["database", "servers"])
  db2["host"] = TomlString("192.168.1.2")
  db2["port"] = TomlInteger(5433)
  @json.inspect(root_table, content={
    "owner": [
      "TomlTable",
      { "name": ["TomlString", "Tom"], "dob": ["TomlString", "1979-05-27"] },
    ],
    "database": [
      "TomlTable",
      {
        "servers": [
          "TomlArray",
          [
            [
              "TomlTable",
              {
                "host": ["TomlString", "192.168.1.1"],
                "port": ["TomlInteger", "5432"],
              },
            ],
            [
              "TomlTable",
              {
                "host": ["TomlString", "192.168.1.2"],
                "port": ["TomlInteger", "5433"],
              },
            ],
          ],
        ],
      },
    ],
  })
}

///|
#skip("error message is not correct")
test "create_array_of_tables - path conflicts with non-table" {
  // Test error when intermediate path conflicts with non-table value
  let root_table : Map[String, TomlValue] = Map::new()

  // Set a string value at "products"
  root_table["products"] = TomlString("not a table")

  // Try to create array of tables at ["products", "items"]
  // This should fail because "products" is not a table
  let result = try? create_array_of_tables(root_table, ["products", "items"])
  inspect(
    result,
    content=(
      #|Err(Failure("/Users/hongbozhang/git/toml-parser/parser.mbt:1038:11-1038:45 FAILED: Array of tables path conflicts with existing value: products at the end of input"))
    ),
  )
}

///|
#skip("error message is not correct")
test "create_array_of_tables - final key conflicts with non-array" {
  // Test error when final key exists but is not an array
  let root_table : Map[String, TomlValue] = Map::new()

  // Create a regular table at servers.alpha
  set_dotted_key_value(
    root_table,
    ["servers", "alpha"],
    TomlTable(Map::from_array([("name", TomlString("main"))])),
  )

  // Try to treat servers.alpha as array of tables
  let result = try? create_array_of_tables(root_table, ["servers", "alpha"])
  inspect(
    result,
    content=(
      #|Err(Failure("/Users/hongbozhang/git/toml-parser/parser.mbt:1049:11-1049:45 FAILED: Array of tables conflicts with existing non-array value: alpha at the end of input"))
    ),
  )
}

///|
test "create_array_of_tables - multiple nested arrays" {
  // Test creating multiple different arrays of tables
  let root_table : Map[String, TomlValue] = Map::new()

  // Create first [[fruits]]
  let fruit1 = create_array_of_tables(root_table, ["fruits"])
  fruit1["name"] = TomlString("apple")

  // Create second [[fruits]]
  let fruit2 = create_array_of_tables(root_table, ["fruits"])
  fruit2["name"] = TomlString("banana")

  // Create [[vegetables]]
  let veg1 = create_array_of_tables(root_table, ["vegetables"])
  veg1["name"] = TomlString("carrot")
  veg1["color"] = TomlString("orange")
  let veg2 = create_array_of_tables(root_table, ["vegetables"])
  veg2["name"] = TomlString("lettuce")
  veg2["color"] = TomlString("green")
  @json.inspect(root_table, content={
    "fruits": [
      "TomlArray",
      [
        ["TomlTable", { "name": ["TomlString", "apple"] }],
        ["TomlTable", { "name": ["TomlString", "banana"] }],
      ],
    ],
    "vegetables": [
      "TomlArray",
      [
        [
          "TomlTable",
          {
            "name": ["TomlString", "carrot"],
            "color": ["TomlString", "orange"],
          },
        ],
        [
          "TomlTable",
          {
            "name": ["TomlString", "lettuce"],
            "color": ["TomlString", "green"],
          },
        ],
      ],
    ],
  })
}

///|
test "create_array_of_tables - empty table in array" {
  // Test that empty tables are correctly added to arrays
  let root_table : Map[String, TomlValue] = Map::new()

  // Create empty table in array
  let _empty_table1 = create_array_of_tables(root_table, ["empty", "tables"])
  // Don't add any fields

  let _empty_table2 = create_array_of_tables(root_table, ["empty", "tables"])
  // Also empty

  let table3 = create_array_of_tables(root_table, ["empty", "tables"])
  table3["has_content"] = TomlBoolean(true)
  @json.inspect(root_table, content={
    "empty": [
      "TomlTable",
      {
        "tables": [
          "TomlArray",
          [
            ["TomlTable", {}],
            ["TomlTable", {}],
            ["TomlTable", { "has_content": ["TomlBoolean", true] }],
          ],
        ],
      },
    ],
  })
}

///|
/// Set a value using a dotted key path in a table
fn set_dotted_key_value(
  table : Map[String, TomlValue],
  key_path : Array[String],
  value : TomlValue,
) -> Unit raise TableConflicts {
  guard key_path.length() != 0
  if key_path.length() == 1 {
    // Simple key assignment
    table[key_path[0]] = value
    return
  }

  // Navigate through the dotted path, creating tables as needed
  let mut current_table = table
  for i = 0; i < key_path.length() - 1; i = i + 1 {
    let key = key_path[i]
    match current_table.get(key) {
      Some(TomlTable(existing_table)) => current_table = existing_table
      Some(value) => raise ExpectedTable(key, value)
      // parse_error(
      //   error_tokens,
      //   "Dotted key path conflicts with existing non-table value: \{key}",
      // )
      None => {
        // Create new nested table
        let new_table = Map::new()
        current_table[key] = TomlTable(new_table)
        current_table = new_table
      }
    }
  }

  // Set the final value
  let final_key = key_path[key_path.length() - 1]
  current_table[final_key] = value
}

///|
test "set dotted key value - simple key" {
  // Test setting a simple key (path length 1)
  let table : Map[String, TomlValue] = Map::new()
  set_dotted_key_value(table, ["name"], TomlString("Alice"))
  @json.inspect(table, content={ "name": ["TomlString", "Alice"] })
}

///|
test "set dotted key value - nested keys creating tables" {
  // Test creating nested tables automatically
  let table : Map[String, TomlValue] = Map::new()
  set_dotted_key_value(table, ["server", "host"], TomlString("localhost"))
  set_dotted_key_value(table, ["server", "port"], TomlInteger(8080))
  @json.inspect(table, content={
    "server": [
      "TomlTable",
      { "host": ["TomlString", "localhost"], "port": ["TomlInteger", "8080"] },
    ],
  })
}

///|
test "set dotted key value - deep nesting" {
  // Test deep nested table creation
  let table : Map[String, TomlValue] = Map::new()
  set_dotted_key_value(
    table,
    ["a", "b", "c", "d", "e"],
    TomlString("deep value"),
  )
  @json.inspect(table, content={
    "a": [
      "TomlTable",
      {
        "b": [
          "TomlTable",
          {
            "c": [
              "TomlTable",
              { "d": ["TomlTable", { "e": ["TomlString", "deep value"] }] },
            ],
          },
        ],
      },
    ],
  })
}

///|
test "set dotted key value - multiple values same nested table" {
  // Test adding multiple values to the same nested table
  let table : Map[String, TomlValue] = Map::new()

  // Add multiple values at different levels
  set_dotted_key_value(table, ["database", "server"], TomlString("postgres"))
  set_dotted_key_value(table, ["database", "port"], TomlInteger(5432))
  set_dotted_key_value(
    table,
    ["database", "credentials", "user"],
    TomlString("admin"),
  )
  set_dotted_key_value(
    table,
    ["database", "credentials", "password"],
    TomlString("secret"),
  )
  set_dotted_key_value(table, ["database", "options", "ssl"], TomlBoolean(true))
  @json.inspect(table, content={
    "database": [
      "TomlTable",
      {
        "server": ["TomlString", "postgres"],
        "port": ["TomlInteger", "5432"],
        "credentials": [
          "TomlTable",
          {
            "user": ["TomlString", "admin"],
            "password": ["TomlString", "secret"],
          },
        ],
        "options": ["TomlTable", { "ssl": ["TomlBoolean", true] }],
      },
    ],
  })
}

///|
test "set dotted key value - overwrite existing value" {
  // Test overwriting an existing value
  let table : Map[String, TomlValue] = Map::new()

  // Set initial value
  set_dotted_key_value(table, ["config", "timeout"], TomlInteger(30))

  // Overwrite with new value
  set_dotted_key_value(table, ["config", "timeout"], TomlInteger(60))
  @json.inspect(table, content={
    "config": ["TomlTable", { "timeout": ["TomlInteger", "60"] }],
  })
}

///|
test "set dotted key value - add to existing table" {
  // Test adding values to an already existing table
  let table : Map[String, TomlValue] = Map::new()

  // Create initial structure
  set_dotted_key_value(table, ["app", "name"], TomlString("MyApp"))
  set_dotted_key_value(table, ["app", "version"], TomlString("1.0.0"))

  // Add more values to the same table later
  set_dotted_key_value(table, ["app", "author"], TomlString("Developer"))
  set_dotted_key_value(table, ["app", "license"], TomlString("MIT"))
  @json.inspect(table, content={
    "app": [
      "TomlTable",
      {
        "name": ["TomlString", "MyApp"],
        "version": ["TomlString", "1.0.0"],
        "author": ["TomlString", "Developer"],
        "license": ["TomlString", "MIT"],
      },
    ],
  })
}

///|
test "set dotted key value - conflict with non-table value" {
  let table : Map[String, TomlValue] = {}

  // Set a string value at "config"
  set_dotted_key_value(table, ["config"], TomlString("simple value"))

  // Try to treat "config" as a table - this should raise an error
  try
    set_dotted_key_value(table, ["config", "nested"], TomlString("value"))
  catch {
    err =>
      @json.inspect(
        err.to_string(),
        content="ExpectedTable(\"config\", \"simple value\")",
      )
  } noraise {
    _ => fail("Expected error but got success")
  }
}

///|
test "set dotted key value - mixed types in path" {
  // Test with different value types at different levels
  let table : Map[String, TomlValue] = Map::new()
  set_dotted_key_value(
    table,
    ["root", "array"],
    TomlArray([TomlInteger(1), TomlInteger(2), TomlInteger(3)]),
  )
  set_dotted_key_value(table, ["root", "float"], TomlFloat(3.14))
  set_dotted_key_value(table, ["root", "nested", "bool"], TomlBoolean(false))
  set_dotted_key_value(
    table,
    ["root", "nested", "datetime"],
    TomlDateTime(@tokenize.TomlDateTime::OffsetDateTime("2024-01-01T12:00:00Z")),
  )
  @json.inspect(table, content={
    "root": [
      "TomlTable",
      {
        "array": [
          "TomlArray",
          [["TomlInteger", "1"], ["TomlInteger", "2"], ["TomlInteger", "3"]],
        ],
        "float": ["TomlFloat", 3.14],
        "nested": [
          "TomlTable",
          {
            "bool": ["TomlBoolean", false],
            "datetime": [
              "TomlDateTime",
              ["OffsetDateTime", "2024-01-01T12:00:00Z"],
            ],
          },
        ],
      },
    ],
  })
}

///|
#skip("empty path edge case to fix later")
test "set dotted key value - empty path edge case" {
  // Test with empty path (edge case)
  let table : Map[String, TomlValue] = Map::new()

  // Set a value first
  set_dotted_key_value(table, ["key"], TomlString("value"))

  // Try with empty path - currently does nothing
  set_dotted_key_value(table, [], TomlString("should not appear"))

  // Table should only have the first key
  @json.inspect(table, content={ "key": ["TomlString", "value"] })
}