import "csv"
| import "json"
| import "toml"
| import "yaml"
| import "xml"
| import "fuzzy"
| import "section"
| import "table"
| import "toon"
| include "test"

# -- CSV and PSV Tests --

| let csv_input = "a,b,c\n\"1,2\",\"2,3\",\"3,4\"\n4,5,6\n\"multi\nline\",7,8\n9,10,\"quoted,comma\"\n\"\",11,12\n13,14,15\n" |

def test_csv_parse_for_dict():
  let result = csv::csv_parse(csv_input, true)
  | assert_eq(len(result), 6)
end

def test_csv_to_json_for_dict():
  let result = csv::csv_to_json(csv::csv_parse(csv_input, true))
  | assert_eq(result, "[{\"a\":\"1,2\",\"b\":\"2,3\",\"c\":\"3,4\"},{\"a\":\"4\",\"b\":\"5\",\"c\":\"6\"},{\"a\":\"multi\\nline\",\"b\":\"7\",\"c\":\"8\"},{\"a\":\"9\",\"b\":\"10\",\"c\":\"quoted,comma\"},{\"a\":\"\",\"b\":\"11\",\"c\":\"12\"},{\"a\":\"13\",\"b\":\"14\",\"c\":\"15\"}]")
end

def test_csv_parse_for_array():
  let result = csv::csv_parse(csv_input, false)
  | assert_eq(len(result), 7)
end

def test_csv_to_json_for_array():
  let result = csv::csv_to_json(csv::csv_parse(csv_input, false))
  | assert_eq(result, "[[\"a\",\"b\",\"c\"],[\"1,2\",\"2,3\",\"3,4\"],[\"4\",\"5\",\"6\"],[\"multi\\nline\",\"7\",\"8\"],[\"9\",\"10\",\"quoted,comma\"],[\"\",\"11\",\"12\"],[\"13\",\"14\",\"15\"]]")
end

| let psv_input = "a|b|c\n\"1,2\"|\"2,3\"|\"3,4\"\n4|5|6\n\"multi\nline\"|7|8\n9|10|\"quoted,comma\"\n\"\"|11|12\n13|14|15\n" |

def test_psv_parse_for_dict():
  let result = csv::psv_parse(psv_input, true)
  | assert_eq(len(result), 6)
end

def test_psv_to_json_for_dict():
  let result = csv::csv_to_json(csv::psv_parse(psv_input, true))
  | assert_eq(result, "[{\"a\":\"1,2\",\"b\":\"2,3\",\"c\":\"3,4\"},{\"a\":\"4\",\"b\":\"5\",\"c\":\"6\"},{\"a\":\"multi\\nline\",\"b\":\"7\",\"c\":\"8\"},{\"a\":\"9\",\"b\":\"10\",\"c\":\"quoted,comma\"},{\"a\":\"\",\"b\":\"11\",\"c\":\"12\"},{\"a\":\"13\",\"b\":\"14\",\"c\":\"15\"}]")
end

def test_psv_parse_for_array():
  let result = csv::psv_parse(psv_input, false)
  | assert_eq(len(result), 7)
end

def test_psv_to_json_for_array():
  let result = csv::csv_to_json(csv::psv_parse(psv_input, false))
  | assert_eq(result, "[[\"a\",\"b\",\"c\"],[\"1,2\",\"2,3\",\"3,4\"],[\"4\",\"5\",\"6\"],[\"multi\\nline\",\"7\",\"8\"],[\"9\",\"10\",\"quoted,comma\"],[\"\",\"11\",\"12\"],[\"13\",\"14\",\"15\"]]")
end

# -- JSON Tests --

| let json_input = "{\"users\":[{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\",\"roles\":[\"admin\",\"user\"]},{\"id\":2,\"name\":\"Bob\",\"email\":\"bob@example.com\",\"roles\":[\"user\"]},{\"id\":3,\"name\":\"Charlie\",\"email\":\"charlie@example.com\",\"roles\":[\"editor\",\"user\"]}],\"meta\":{\"count\":3,\"generated_at\":\"2024-06-01T12:00:00Z\"}}" |

def test_json_parse():
  let result = json::json_parse(json_input)
  | assert_eq(len(result["users"]), 3)
end

def test_json_stringify():
  let result = json::json_stringify(json::json_parse(json_input))
  | assert_eq(result, "{\"users\": [{\"name\": \"Alice\", \"id\": 1, \"email\": \"alice@example.com\", \"roles\": [\"admin\", \"user\"]}, {\"name\": \"Bob\", \"id\": 2, \"email\": \"bob@example.com\", \"roles\": [\"user\"]}, {\"name\": \"Charlie\", \"id\": 3, \"email\": \"charlie@example.com\", \"roles\": [\"editor\", \"user\"]}], \"meta\": {\"count\": 3, \"generated_at\": \"2024-06-01T12:00:00Z\"}}")
end

def test_json_to_markdown_table():
  let result = json::json_parse(json_input)
  | let result = json::json_to_markdown_table(result["users"])
  | assert_eq(result, "| name | id | email | roles |\n| --- | --- | --- | --- |\n| Alice | 1 | alice@example.com | [\"admin\", \"user\"] |\n| Bob | 2 | bob@example.com | [\"user\"] |\n| Charlie | 3 | charlie@example.com | [\"editor\", \"user\"] |")
end

# -- TOML Tests --

| let toml_input = "[package]\nauthors = [\"Test Author <test@example.com>\"]\ndescription = \"A test TOML configuration file\"\nedition = \"2021\"\nlicense = \"MIT\"\nname = \"test-package\"\nversion = \"1.0.0\"\n\n[dependencies]\n" |

def test_toml_parse():
  let result = toml::toml_parse(toml_input)
  | assert_eq(len(result), 2)
end

def test_toml_to_json():
  let result = toml::toml_to_json(toml::toml_parse(toml_input))
  | assert_eq(result, "{\"dependencies\":{},\"package\":{\"name\":\"test-package\",\"authors\":[\"Test Author <test@example.com>\"],\"description\":\"A test TOML configuration file\",\"edition\":\"2021\",\"license\":\"MIT\",\"version\":\"1.0.0\"}}")
end

# -- YAML Tests --

| let yaml_input = "number: 42\nfloat: 3.14\nbool_true: true\nbool_false: false\nnull_value: null\narray:\n  - item1\n  - item2\n  - item3\nobject:\n  key1: value1\n  key2: value2\nnested:\n  arr:\n    - a\n    - b\n  obj:\n    subkey: subval\n" |

def test_yaml_parse():
  let result = yaml::yaml_parse(yaml_input)
  | assert_eq(len(keys(result)), 8)
end

def test_yaml_to_json():
  let result = yaml::yaml_to_json(yaml::yaml_parse(yaml_input))
  | assert_eq(result, "{\"array\": [\"item1\", \"item2\", \"item3\"], \"number\": 42, \"float\": 3.14, \"bool_true\": true, \"bool_false\": false, \"null_value\": null, \"object\": {\"key1\": \"value1\", \"key2\": \"value2\"}, \"nested\": {\"arr\": [\"a\", \"b\"], \"obj\": {\"subkey\": \"subval\"}}}")
end

def test_yaml_to_markdown_table():
  let result = yaml::yaml_to_markdown_table(yaml::yaml_parse(yaml_input))
  | assert_eq(result, "| Key | Value |\n| --- | --- |\n| array | [\"item1\", \"item2\", \"item3\"] |\n| number | 42 |\n| float | 3.14 |\n| bool_true | true |\n| bool_false | false |\n| null_value |  |\n| object | {\"key1\": \"value1\", \"key2\": \"value2\"} |\n| nested | {\"arr\": [\"a\", \"b\"], \"obj\": {\"subkey\": \"subval\"}} |")
end

def test_to_front_matter():
  let input = {title: "title", date: "2024-06-01", tags: ["test", "yaml"], draft: false}
  | let result = yaml::to_front_matter(input)
  | assert_eq(result, "---\ntitle: title\ndate: 2024-06-01\ntags: \n  - test\n  - yaml\ndraft: false\n---")
end

# -- XML Tests --

| let xml_input = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- test --><users><user id=\"1\"><name>Alice</name><email>alice@example.com</email><roles><role>admin</role><role>user</role></roles></user><user id=\"2\"><name>Bob</name><email>bob@example.com</email><roles><role>user</role></roles></user><user id=\"3\"><name>Charlie</name><email>charlie@example.com</email><roles><role>editor</role><role>user</role></roles></user></users>" |

def test_xml_parse():
  let result = xml::xml_parse(xml_input)
  | assert_eq(len(result["children"]), 3)
end

def test_xml_stringify():
  let result = xml::xml_stringify(xml::xml_parse(xml_input))
  | assert_eq(result, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<users><user id=\"1\"><name>Alice</name><email>alice@example.com</email><roles><role>admin</role><role>user</role></roles></user><user id=\"2\"><name>Bob</name><email>bob@example.com</email><roles><role>user</role></roles></user><user id=\"3\"><name>Charlie</name><email>charlie@example.com</email><roles><role>editor</role><role>user</role></roles></user></users>\n")
end

def test_xml_to_markdown_table():
  let result = xml::xml_to_markdown_table(xml::xml_parse(xml_input))
  | assert_eq(result, "| Index | Tag | Attributes | Text |\n| --- | --- | --- | --- |\n| 0 | user | id=1 |  |\n| 1 | user | id=2 |  |\n| 2 | user | id=3 |  |\n\n## Children of user:\n\n| Index | Tag | Attributes | Text |\n| --- | --- | --- | --- |\n| 0 | name |  | Alice |\n| 1 | email |  | alice@example.com |\n| 2 | roles |  |  |\n\n### Children of name:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| name |  | Alice | 0 |\n\n### Children of email:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| email |  | alice@example.com | 0 |\n\n### Children of roles:\n\n| Index | Tag | Attributes | Text |\n| --- | --- | --- | --- |\n| 0 | role |  | admin |\n| 1 | role |  | user |\n\n#### Children of role:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| role |  | admin | 0 |\n\n#### Children of role:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| role |  | user | 0 |\n\n## Children of user:\n\n| Index | Tag | Attributes | Text |\n| --- | --- | --- | --- |\n| 0 | name |  | Bob |\n| 1 | email |  | bob@example.com |\n| 2 | roles |  |  |\n\n### Children of name:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| name |  | Bob | 0 |\n\n### Children of email:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| email |  | bob@example.com | 0 |\n\n### Children of roles:\n\n| Index | Tag | Attributes | Text |\n| --- | --- | --- | --- |\n| 0 | role |  | user |\n\n#### Children of role:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| role |  | user | 0 |\n\n## Children of user:\n\n| Index | Tag | Attributes | Text |\n| --- | --- | --- | --- |\n| 0 | name |  | Charlie |\n| 1 | email |  | charlie@example.com |\n| 2 | roles |  |  |\n\n### Children of name:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| name |  | Charlie | 0 |\n\n### Children of email:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| email |  | charlie@example.com | 0 |\n\n### Children of roles:\n\n| Index | Tag | Attributes | Text |\n| --- | --- | --- | --- |\n| 0 | role |  | editor |\n| 1 | role |  | user |\n\n#### Children of role:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| role |  | editor | 0 |\n\n#### Children of role:\n\n| Tag | Attributes | Text | Children |\n| --- | --- | --- | --- |\n| role |  | user | 0 |")
end

# -- TOON Tests --

| let toon_input = "context:\n  task: Our favorite hikes together\n  location: Boulder\n  season: spring_2025\nfriends[3]: ana,luis,sam\nhikes[3]{id,name,distanceKm,elevationGain,companion,wasSunny}:\n  1,Blue Lake Trail,7.5,320,ana,true\n  2,Ridge Overlook,9.2,540,luis,false\n  3,Wildflower Loop,5.1,180,sam,true\nitems[3]:\n  - 1\n  - a: 1\n  - text\nitems2[2]:\n  - name: Laptop\n    price: 999\n  - name: Mouse\n    price: 29\n" |

def test_toon_parse():
  let result = toon::toon_parse(toon_input)
  | assert_eq(len(result), 5)
end

def test_toon_stringify():
  let result = toon::toon_stringify(toon::toon_parse(toon_input))
  | assert_eq(result, "items[3]:\n    - 1\n    - a: 1\n    - text\ncontext:\n  task: Our favorite hikes together\n  location: Boulder\n  season: spring_2025\nfriends[3]: ana,luis,sam\nhikes[3]{name,id,distanceKm,elevationGain,companion,wasSunny}:\n    Blue Lake Trail,1,7.5,320,ana,true\n    Ridge Overlook,2,9.2,540,luis,false\n    Wildflower Loop,3,5.1,180,sam,true\nitems2[2]{name,price}:\n    Laptop,999\n    Mouse,29")
end

def test_levenshtein():
  let result1 = fuzzy::levenshtein("flaw", "lawn")
  | assert_eq(result1, 2)

  | let result2 = fuzzy::levenshtein("", "")
  | assert_eq(result2, 0)

  | let result3 = fuzzy::levenshtein("a", "")
  | assert_eq(result3, 1)

  | let result4 = fuzzy::levenshtein("", "a")
  | assert_eq(result4, 1)

  | let result5 = fuzzy::levenshtein("abc", "abc")
  | assert_eq(result5, 0)
end

def test_jaro_winkler():
  let result1 = fuzzy::jaro_winkler("flaw", "lawn")
  | assert(result1 > 0 && result1 < 1)

  | let result2 = fuzzy::jaro_winkler("", "")
  | assert_eq(result2, 1)

  | let result3 = fuzzy::jaro_winkler("a", "")
  | assert_eq(result3, 0)

  | let result4 = fuzzy::jaro_winkler("", "a")
  | assert_eq(result4, 0)

  | let result5 = fuzzy::jaro_winkler("abc", "abc")
  | assert_eq(result5, 1)
end

def test_fuzzy_best_match():
  let result1 = fuzzy::fuzzy_best_match("lawn", "lawn")
  | assert_eq(result1, {"score": 1, "text": "lawn"})

  | let result2 = fuzzy::fuzzy_best_match(["flaw", "lawn", "pawn"], "lawn")
  | assert_eq(result2, {"score": 1, "text": "lawn"})

  | let result3 = fuzzy::fuzzy_best_match(["lawn", "flaw", "claw"], "flaw")
  | assert_eq(result3, {"score": 1, "text": "flaw"})

  | let result4 = fuzzy::fuzzy_best_match(["lawn", "claw"], "flaw")
  | assert(result4["score"] < 1 && (result4["text"] == "lawn" || result4["text"] == "claw"))

  | let result5 = fuzzy::fuzzy_best_match([], "flaw")
  | assert_eq(result5, None)
end

def test_fuzzy_match_levenshtein():
  let result1 = fuzzy::fuzzy_match_levenshtein("lawn", "lawn")
  | assert_eq(result1, [{"score": 0, "text": "lawn"}])

  | let result2 = fuzzy::fuzzy_match_levenshtein("back", "book")
  | assert_eq(result2, [{"score": 2, "text": "back"}])
end

def test_fuzzy_filter():
  let result1 = fuzzy::fuzzy_filter(["flaw", "lawn", "pawn"], "lawn", 0.9)
  | assert_eq(len(result1), 1)

  | let result2 = fuzzy::fuzzy_filter(["lawn", "claw"], "lawn", 0.7)
  | assert_eq(len(result2), 1)

  | let result3 = fuzzy::fuzzy_filter(["lawn", "claw"], "flaw", 0.6)
  | assert_eq(len(result3), 2)

  | let result4 = fuzzy::fuzzy_filter([], "flaw", 0.8)
  | assert_eq(len(result4), 0)
end

| let section_md = "# Introduction\nThis is the introduction section.\n\n## Advanced Topics\nThis section covers advanced topics.\n\n# Conclusion\nThis is the conclusion section.\n"
| let test_sections = to_markdown(section_md) |

def test_section_sections():
  let sections = do test_sections | section::sections();
  | assert_eq(len(sections), 3)

  | let sections = do "# test" | to_markdown() | section::sections();
  | assert_eq(len(sections), 1)
end

def test_section_filter_sections():
  let sections = do test_sections | section::filter_sections(fn(x): section::level(x) == 1;);
  | assert_eq(len(sections), 2)
end

def test_section_map_sections():
  let level_list = do test_sections | section::map_sections(fn(head, children): head.level;);
  | assert_eq(level_list, [1, 2, 1])
end

def test_section_split():
  let sections = do test_sections | section::split(1);
  | assert_eq(len(sections), 2)
end

def test_section_title_contains():
  let sections = do test_sections | section::split(1);
  | let filtered = section::title_contains(sections, "Introduction")
  | assert_eq(len(filtered), 1)
end

def test_section_title_match():
  let sections = do test_sections | section::split(1);
  | let filtered = section::title_match(sections, "^I.*")
  | assert_eq(len(filtered), 1)
end

def test_section_title():
  let sections = do test_sections | section::split(1);
  | let first_section = first(sections)
  | let title_text = section::title(first_section)
  | assert(contains(title_text, "Introduction"))
end

def test_section_content():
  let sections = do test_sections | section::split(1);
  | let first_section = first(sections)
  | let content_nodes = section::content(first_section)
  | assert(len(content_nodes) > 0)
end

def test_section_level():
  let sections = do test_sections | section::split(1);
  | let first_section = first(sections)
  | let lvl = section::level(first_section)
  | assert_eq(lvl, 1)
end

def test_section_nth():
  let sections = do test_sections | section::split(1);
  | let second = section::nth(sections, 1)
  | assert(not(is_none(second)))
end

def test_section_titles():
  let sections = do test_sections | section::split(1);
  | let all_titles = section::titles(sections)
  | assert_eq(len(all_titles), 2)
end

def test_section_flatten():
  let sections = do test_sections | section::split(1);
  | let flattened = section::collect(sections)
  | assert(is_array(flattened))
end

def test_section_section_pattern():
  let secs = do test_sections | section::section("Introduction");
  | assert_eq(len(secs), 1)
  | assert(contains(section::title(first(secs)), "Introduction"))
end

def test_section_section_no_match():
  let secs = do test_sections | section::section("NonExistent");
  | assert_eq(secs, [])
end

def test_section_section_depth_h1():
  let secs = do test_sections | section::section(., "Introduction", true);
  | assert_eq(len(secs), 1)
  | let s = first(secs)
  | assert_eq(section::level(s), 1)
  | assert(len(section::body(s)) > 1)
end

def test_section_section_depth_h2():
  let secs = do test_sections | section::section(., "Advanced Topics", true);
  | assert_eq(len(secs), 1)
  | let s = first(secs)
  | assert_eq(section::level(s), 2)
end

def test_section_section_depth_false_unchanged():
  let secs_default = do test_sections | section::section("Introduction");
  | let secs_depth = do test_sections | section::section(., "Introduction", false);
  | assert_eq(len(secs_default), len(secs_depth))
end

def test_section_bodies_pipe():
  let body_list = do test_sections | section::section("Introduction") | section::bodies();
  | assert_eq(len(body_list), 1)
  | assert(is_array(first(body_list)))
end

def test_section_by_level_scalar():
  let secs = do test_sections | section::sections() | section::by_level(1);
  | assert_eq(len(secs), 2)
  | assert_eq(section::level(first(secs)), 1)
end

def test_section_by_level_h2():
  let secs = do test_sections | section::sections() | section::by_level(2);
  | assert_eq(len(secs), 1)
  | assert_eq(section::level(first(secs)), 2)
end

def test_section_by_level_range():
  let secs = do test_sections | section::sections() | section::by_level(1..2);
  | assert_eq(len(secs), 3)
end

def test_section_by_level_empty():
  let secs = section::by_level([], 1)
  | assert_eq(secs, [])
end

| let table_md = "| Name | Age | City |\n| --- | --- | --- |\n| Alice | 30 | Tokyo |\n| Bob | 25 | Osaka |\n"
| let table_nodes = to_markdown(table_md) |

def test_table_tables():
  let result = table::tables(table_nodes)
  | assert_eq(len(result), 1)
  | let t = result[0]
  | assert_eq(t[:type], :table)
  | assert_eq(len(t[:header]), 3)
  | assert_eq(len(t[:rows]), 2)
end

def test_table_tables_empty():
  let result = table::tables([])
  | assert_eq(result, [])
end

def test_table_set_align():
  let t1 = do table::tables(table_nodes) | first();
  | let t1 = table::set_align(t1, [:left, :center, :right])
  | assert_eq(to_string(t1[:align]), "|:---|:---:|---:|")
end

def test_table_add_row():
  let t = first(table::tables(table_nodes))
  | let t = table::add_row(t, ["Charlie", "35", "Nagoya"])
  | assert_eq(len(t[:rows]), 3)
end

def test_table_add_column():
  let t = first(table::tables(table_nodes))
  | let t = table::add_column(t, ["Country", "Japan", "Japan"])
  | assert_eq(len(t[:header]), 3)
  | assert_eq(len(t[:rows][0]), 4)
end

def test_table_remove_row():
  let t = first(table::tables(table_nodes))
  | let t = table::remove_row(t, 0)
  | assert_eq(len(t[:rows]), 1)
end

def test_table_remove_column():
  let t = first(table::tables(table_nodes))
  | let t = table::remove_column(t, 0)
  | assert_eq(len(t[:rows][0]), 2)
end

def test_table_to_markdown():
  let t = first(table::tables(table_nodes))
  | let md_result = table::to_markdown(t)
  | assert(is_array(md_result))
  | assert(len(md_result) > 0)
end

def test_table_map_rows():
  let t = first(table::tables(table_nodes))
  | let md_result = table::map_rows(t, fn(row): map(row, upcase);)
  | assert_eq(to_string(md_result[:rows][0][0]), "ALICE")
  | assert_eq(to_string(md_result[:rows][1][0]), "BOB")
end

def test_table_filter_rows():
  let t = first(table::tables(table_nodes))
  | let md_result = table::filter_rows(t, fn(row): to_string(row[0]) == "Alice";)
  | assert_eq(len(md_result[:rows]), 1)
end

def test_table_filter_tables():
  let tables = table::tables(table_nodes)
  | let tables = table::filter_tables(tables, fn(header, rows): to_string(header[0]) != "Name";)
  | assert_eq(len(tables), 0)
end

def test_table_sort_rows():
  let t = first(table::tables(table_nodes))
  | let md_result = table::sort_rows(t, 1)
  | assert_eq(to_string(md_result[:rows][0][0]), "Bob")

  | let md_result = table::sort_rows(t)
  | assert_eq(to_string(md_result[:rows][0][0]), "Alice")
end

