import "../../runtime/eltest.el"
// tests/runtime/string_test.el — Test suite for runtime/string.el
//
// Exercises every public function exported by runtime/string.el using the
// runtime/test.el framework. Each test function covers one string primitive
// or a tight family of related functions.
//
// ── Build and run ────────────────────────────────────────────────────────────
//
//   # Combine runtime modules + test framework + this file, then compile:
//   cat runtime/string.el runtime/math.el runtime/state.el runtime/env.el \
//       runtime/fs.el runtime/exec.el runtime/time.el runtime/json.el \
//       runtime/http.el runtime/engram.el runtime/thread.el \
//       runtime/test.el \
//       tests/runtime/string_test.el > /tmp/string_test_combined.el
//
//   ./dist/platform/elc /tmp/string_test_combined.el > /tmp/string_test.c
//   cc -std=c11 -I runtime -lcurl -lpthread \
//      -o /tmp/string_test /tmp/string_test.c runtime/el_seed.c
//   /tmp/string_test; echo "exit: $?"
//
// Exit code equals the number of failing assertions (0 = all pass).
//
// ── Coverage ─────────────────────────────────────────────────────────────────
//
//   str_eq            str_neq (via assert_neq)
//   str_len           str_concat
//   str_starts_with   str_ends_with
//   str_contains      str_index_of
//   str_slice         str_replace
//   str_to_upper      str_to_lower
//   str_trim          str_lstrip / str_rstrip
//   str_split         str_join
//   int_to_str        str_to_int
//   str_repeat        str_reverse
//   str_count

// ── str_eq ────────────────────────────────────────────────────────────────────

fn test_str_eq(_: String) -> String {
    assert_true(str_eq("hello", "hello"), "identical strings are equal")
    assert_false(str_eq("hello", "world"), "different strings are not equal")
    assert_true(str_eq("", ""), "empty strings are equal")
    assert_false(str_eq("a", ""), "non-empty vs empty is not equal")
    assert_false(str_eq("", "a"), "empty vs non-empty is not equal")
    assert_false(str_eq("Hello", "hello"), "case-sensitive comparison")
    return ""
}

// ── str_len ───────────────────────────────────────────────────────────────────

fn test_str_len(_: String) -> String {
    assert_int_eq(str_len(""), 0, "empty string has length 0")
    assert_int_eq(str_len("a"), 1, "single char has length 1")
    assert_int_eq(str_len("hello"), 5, "hello has length 5")
    assert_int_eq(str_len("hello world"), 11, "space included in length")
    return ""
}

// ── str_concat ────────────────────────────────────────────────────────────────

fn test_str_concat(_: String) -> String {
    assert_eq(str_concat("hello", " world"), "hello world", "basic concat")
    assert_eq(str_concat("", "world"), "world", "empty prefix")
    assert_eq(str_concat("hello", ""), "hello", "empty suffix")
    assert_eq(str_concat("", ""), "", "both empty")
    return ""
}

// ── str_starts_with ───────────────────────────────────────────────────────────

fn test_str_starts_with(_: String) -> String {
    assert_true(str_starts_with("hello world", "hello"), "prefix present")
    assert_false(str_starts_with("hello world", "world"), "not a prefix")
    assert_true(str_starts_with("hello", "hello"), "string is its own prefix")
    assert_true(str_starts_with("hello", ""), "empty prefix always true")
    assert_false(str_starts_with("", "a"), "empty string has no prefix")
    assert_false(str_starts_with("hi", "hello"), "prefix longer than string")
    return ""
}

// ── str_ends_with ─────────────────────────────────────────────────────────────

fn test_str_ends_with(_: String) -> String {
    assert_true(str_ends_with("hello world", "world"), "suffix present")
    assert_false(str_ends_with("hello world", "hello"), "not a suffix")
    assert_true(str_ends_with("hello", "hello"), "string is its own suffix")
    assert_true(str_ends_with("hello", ""), "empty suffix always true")
    assert_false(str_ends_with("", "a"), "empty string has no suffix")
    assert_false(str_ends_with("hi", "world"), "suffix longer than string")
    return ""
}

// ── str_contains ─────────────────────────────────────────────────────────────

fn test_str_contains(_: String) -> String {
    assert_true(str_contains("hello world", "world"), "contains at end")
    assert_true(str_contains("hello world", "hello"), "contains at start")
    assert_true(str_contains("hello world", "lo wo"), "contains in middle")
    assert_false(str_contains("hello world", "xyz"), "not contained")
    assert_true(str_contains("hello", ""), "empty sub always contained")
    assert_false(str_contains("", "a"), "empty string contains nothing")
    assert_true(str_contains("hello", "hello"), "string contains itself")
    return ""
}

// ── str_index_of ─────────────────────────────────────────────────────────────

fn test_str_index_of(_: String) -> String {
    assert_int_eq(str_index_of("hello world", "world"), 6, "index of suffix")
    assert_int_eq(str_index_of("hello world", "hello"), 0, "index of prefix")
    assert_int_eq(str_index_of("hello world", "o"), 4, "index of first occurrence")
    assert_int_eq(str_index_of("hello world", "xyz"), -1, "not found returns -1")
    assert_int_eq(str_index_of("hello", ""), 0, "empty sub returns 0")
    assert_int_eq(str_index_of("", "a"), -1, "search in empty string")
    assert_int_eq(str_index_of("aababc", "ab"), 1, "finds first occurrence")
    return ""
}

// ── str_replace ───────────────────────────────────────────────────────────────

fn test_str_replace(_: String) -> String {
    assert_eq(str_replace("hello world", "world", "there"), "hello there", "basic replace")
    assert_eq(str_replace("aaa", "a", "b"), "bbb", "replace all occurrences")
    assert_eq(str_replace("hello", "xyz", "abc"), "hello", "no match is identity")
    assert_eq(str_replace("", "a", "b"), "", "empty string unchanged")
    assert_eq(str_replace("hello", "", "x"), "hello", "empty from is identity")
    assert_eq(str_replace("hello hello", "hello", "bye"), "bye bye", "replace multiple")
    assert_eq(str_replace("aXbXc", "X", "-"), "a-b-c", "single-char delimiter")
    return ""
}

// ── str_slice ─────────────────────────────────────────────────────────────────

fn test_str_slice(_: String) -> String {
    assert_eq(str_slice("hello world", 0, 5), "hello", "slice from start")
    assert_eq(str_slice("hello world", 6, 11), "world", "slice from middle")
    assert_eq(str_slice("hello world", 0, 0), "", "zero-length slice")
    assert_eq(str_slice("hello", 0, 100), "hello", "end beyond length clamped")
    assert_eq(str_slice("hello", 3, 3), "", "start == end is empty")
    assert_eq(str_slice("hello world", 2, 7), "llo w", "interior slice")
    return ""
}

// ── str_to_upper / str_to_lower ──────────────────────────────────────────────

fn test_str_to_upper(_: String) -> String {
    assert_eq(str_to_upper("hello"), "HELLO", "lowercase to uppercase")
    assert_eq(str_to_upper("HELLO"), "HELLO", "already uppercase unchanged")
    assert_eq(str_to_upper("Hello World"), "HELLO WORLD", "mixed case")
    assert_eq(str_to_upper(""), "", "empty string unchanged")
    assert_eq(str_to_upper("hello123"), "HELLO123", "digits unchanged")
    return ""
}

fn test_str_to_lower(_: String) -> String {
    assert_eq(str_to_lower("HELLO"), "hello", "uppercase to lowercase")
    assert_eq(str_to_lower("hello"), "hello", "already lowercase unchanged")
    assert_eq(str_to_lower("Hello World"), "hello world", "mixed case")
    assert_eq(str_to_lower(""), "", "empty string unchanged")
    assert_eq(str_to_lower("HELLO123"), "hello123", "digits unchanged")
    return ""
}

// ── str_trim ─────────────────────────────────────────────────────────────────

fn test_str_trim(_: String) -> String {
    assert_eq(str_trim("  hello  "), "hello", "trims spaces both sides")
    assert_eq(str_trim("hello"), "hello", "no whitespace unchanged")
    assert_eq(str_trim("   "), "", "all-space string becomes empty")
    assert_eq(str_trim(""), "", "empty string unchanged")
    assert_eq(str_trim("\t hello \n"), "hello", "trims tabs and newlines")
    assert_eq(str_trim("  hello world  "), "hello world", "internal spaces preserved")
    return ""
}

// ── str_split ─────────────────────────────────────────────────────────────────

fn test_str_split(_: String) -> String {
    let parts: [String] = str_split("a,b,c", ",")
    assert_int_eq(el_list_len(parts), 3, "split yields 3 parts")
    assert_eq(el_list_get(parts, 0), "a", "first part is a")
    assert_eq(el_list_get(parts, 1), "b", "second part is b")
    assert_eq(el_list_get(parts, 2), "c", "third part is c")

    let single: [String] = str_split("hello", ",")
    assert_int_eq(el_list_len(single), 1, "no sep found yields 1 part")
    assert_eq(el_list_get(single, 0), "hello", "single part is the full string")

    let trailing: [String] = str_split("a,b,", ",")
    assert_int_eq(el_list_len(trailing), 3, "trailing sep yields empty last element")
    assert_eq(el_list_get(trailing, 2), "", "last element is empty")

    let empty_str: [String] = str_split("", ",")
    assert_int_eq(el_list_len(empty_str), 1, "splitting empty string yields one empty element")

    let multi: [String] = str_split("one::two::three", "::")
    assert_int_eq(el_list_len(multi), 3, "multi-char separator works")
    assert_eq(el_list_get(multi, 0), "one", "multi-sep first part")
    assert_eq(el_list_get(multi, 2), "three", "multi-sep last part")
    return ""
}

// ── str_join ─────────────────────────────────────────────────────────────────

fn test_str_join(_: String) -> String {
    let parts: [String] = el_list_empty()
    let parts = el_list_append(parts, "a")
    let parts = el_list_append(parts, "b")
    let parts = el_list_append(parts, "c")
    assert_eq(str_join(parts, ","), "a,b,c", "basic join with comma")
    assert_eq(str_join(parts, ""), "abc", "join with empty separator")
    assert_eq(str_join(parts, " | "), "a | b | c", "join with multi-char sep")

    let empty_list: [String] = el_list_empty()
    assert_eq(str_join(empty_list, ","), "", "joining empty list yields empty string")

    let one: [String] = el_list_empty()
    let one = el_list_append(one, "solo")
    assert_eq(str_join(one, ","), "solo", "joining single element yields that element")
    return ""
}

// ── int_to_str / str_to_int ──────────────────────────────────────────────────

fn test_int_to_str(_: String) -> String {
    assert_eq(int_to_str(0), "0", "zero")
    assert_eq(int_to_str(42), "42", "positive integer")
    assert_eq(int_to_str(-1), "-1", "negative integer")
    assert_eq(int_to_str(1000000), "1000000", "large integer")
    return ""
}

fn test_str_to_int(_: String) -> String {
    assert_int_eq(str_to_int("0"), 0, "zero")
    assert_int_eq(str_to_int("42"), 42, "positive integer")
    assert_int_eq(str_to_int("-1"), -1, "negative integer")
    assert_int_eq(str_to_int("1000000"), 1000000, "large integer")
    return ""
}

// ── str_repeat ────────────────────────────────────────────────────────────────

fn test_str_repeat(_: String) -> String {
    assert_eq(str_repeat("ab", 3), "ababab", "repeat 3 times")
    assert_eq(str_repeat("x", 1), "x", "repeat once")
    assert_eq(str_repeat("x", 0), "", "repeat zero times yields empty")
    assert_eq(str_repeat("", 5), "", "repeating empty string yields empty")
    assert_eq(str_repeat("-", 4), "----", "single char repeat")
    return ""
}

// ── str_reverse ───────────────────────────────────────────────────────────────

fn test_str_reverse(_: String) -> String {
    assert_eq(str_reverse("hello"), "olleh", "basic reverse")
    assert_eq(str_reverse("a"), "a", "single char is its own reverse")
    assert_eq(str_reverse(""), "", "empty string reverses to empty")
    assert_eq(str_reverse("abcd"), "dcba", "even-length reverse")
    assert_eq(str_reverse("racecar"), "racecar", "palindrome is unchanged")
    return ""
}

// ── str_count ─────────────────────────────────────────────────────────────────

fn test_str_count(_: String) -> String {
    assert_int_eq(str_count("hello world hello", "hello"), 2, "two occurrences")
    assert_int_eq(str_count("aaa", "a"), 3, "adjacent single chars")
    assert_int_eq(str_count("aaa", "aa"), 1, "non-overlapping: one match")
    assert_int_eq(str_count("hello", "xyz"), 0, "no match")
    assert_int_eq(str_count("", "a"), 0, "empty string has no occurrences")
    assert_int_eq(str_count("hello", ""), 0, "empty sub returns 0")
    return ""
}

// ── str_strip_prefix / str_strip_suffix ───────────────────────────────────────

fn test_str_strip_prefix(_: String) -> String {
    assert_eq(str_strip_prefix("foobar", "foo"), "bar", "strips matching prefix")
    assert_eq(str_strip_prefix("foobar", "baz"), "foobar", "no-match is identity")
    assert_eq(str_strip_prefix("hello", ""), "hello", "empty prefix is identity")
    assert_eq(str_strip_prefix("hello", "hello"), "", "full match yields empty")
    return ""
}

fn test_str_strip_suffix(_: String) -> String {
    assert_eq(str_strip_suffix("hello.md", ".md"), "hello", "strips matching suffix")
    assert_eq(str_strip_suffix("hello.md", ".txt"), "hello.md", "no-match is identity")
    assert_eq(str_strip_suffix("hello", ""), "hello", "empty suffix is identity")
    assert_eq(str_strip_suffix("hello", "hello"), "", "full match yields empty")
    return ""
}

// ── str_find_chars ────────────────────────────────────────────────────────────

fn test_str_find_chars(_: String) -> String {
    assert_int_eq(str_find_chars("hello world", " "), 5, "finds space at index 5")
    assert_int_eq(str_find_chars("hello", "xyz"), -1, "not found returns -1")
    assert_int_eq(str_find_chars("hello", ""), -1, "empty charset returns -1")
    assert_int_eq(str_find_chars("hello", "aeiou"), 1, "finds first vowel at index 1")
    assert_int_eq(str_find_chars("", "a"), -1, "search in empty string returns -1")
    return ""
}

// ── str_last_index_of ─────────────────────────────────────────────────────────

fn test_str_last_index_of(_: String) -> String {
    assert_int_eq(str_last_index_of("hello hello", "hello"), 6, "last occurrence")
    assert_int_eq(str_last_index_of("hello", "hello"), 0, "single occurrence")
    assert_int_eq(str_last_index_of("hello", "xyz"), -1, "not found returns -1")
    assert_int_eq(str_last_index_of("aababc", "ab"), 3, "last ab in aababc")
    return ""
}

// ── Entry point ───────────────────────────────────────────────────────────────

fn main() -> Int {
    test_case("str_eq", "test_str_eq")
    test_case("str_len", "test_str_len")
    test_case("str_concat", "test_str_concat")
    test_case("str_starts_with", "test_str_starts_with")
    test_case("str_ends_with", "test_str_ends_with")
    test_case("str_contains", "test_str_contains")
    test_case("str_index_of", "test_str_index_of")
    test_case("str_replace", "test_str_replace")
    test_case("str_slice", "test_str_slice")
    test_case("str_to_upper", "test_str_to_upper")
    test_case("str_to_lower", "test_str_to_lower")
    test_case("str_trim", "test_str_trim")
    test_case("str_split", "test_str_split")
    test_case("str_join", "test_str_join")
    test_case("int_to_str", "test_int_to_str")
    test_case("str_to_int", "test_str_to_int")
    test_case("str_repeat", "test_str_repeat")
    test_case("str_reverse", "test_str_reverse")
    test_case("str_count", "test_str_count")
    test_case("str_strip_prefix", "test_str_strip_prefix")
    test_case("str_strip_suffix", "test_str_strip_suffix")
    test_case("str_find_chars", "test_str_find_chars")
    test_case("str_last_index_of", "test_str_last_index_of")
    return test_run_all()
}
