module Std::String {

    extern func bux_strlen(s: String) -> uint;
    extern func bux_strcmp(a: String, b: String) -> int;
    extern func bux_strncmp(a: String, b: String, n: uint) -> int;
    extern func bux_strcpy(dest: *char8, src: String) -> *char8;
    extern func bux_strcat(dest: *char8, src: String) -> *char8;
    extern func bux_strncpy(dest: *char8, src: String, n: uint) -> *char8;
    extern func bux_strstr(haystack: String, needle: String) -> String;
    extern func bux_str_contains(haystack: String, needle: String) -> int;
    extern func bux_str_offset(pos: String, base: String) -> uint;
    extern func bux_str_is_null(s: String) -> int;
    extern func bux_str_slice(s: String, start: uint, len: uint) -> String;
    extern func bux_str_trim_left(s: String) -> String;
    extern func bux_str_trim_right(s: String) -> String;
    extern func bux_str_trim(s: String) -> String;
    extern func bux_int_to_str(n: int64) -> String;
    extern func bux_str_to_int(s: String) -> int64;
    extern func bux_sb_new(initial_cap: uint) -> *void;
    extern func bux_sb_append(sb: *void, s: String);
    extern func bux_sb_append_int(sb: *void, n: int64);
    extern func bux_sb_append_float(sb: *void, f: float64);
    extern func bux_sb_append_char(sb: *void, c: char8);
    extern func bux_sb_build(sb: *void) -> String;
    extern func bux_sb_free(sb: *void);
    extern func bux_str_split_count(s: String, delim: String) -> uint;
    extern func bux_str_split_part(s: String, delim: String, index: uint) -> String;
    extern func bux_str_join2(a: String, b: String, sep: String) -> String;
    extern func bux_float_to_string(f: float64) -> String;
    extern func bux_str_format(pattern: String, a0: String, a1: String, a2: String, a3: String, a4: String, a5: String, a6: String, a7: String) -> String;


    /// Byte length of a C string (`strlen`).
    func String_Len(s: String) -> uint {
        return bux_strlen(s);
    }

    /// True if the string has zero length.
    func String_IsEmpty(s: String) -> bool {
        return bux_strlen(s) == 0;
    }

    /// True if the pointer is null.
    func String_IsNull(s: String) -> bool {
        return bux_str_is_null(s) != 0;
    }

    /// Lexicographic equality.
    func String_Eq(a: String, b: String) -> bool {
        return bux_strcmp(a, b) == 0;
    }

    /// Allocate and return `a` concatenated with `b`.
    func String_Concat(a: String, b: String) -> String {
        let len_a: uint = bux_strlen(a);
        let len_b: uint = bux_strlen(b);
        let total: uint = len_a + len_b + 1;
        let buf: *char8 = bux_alloc(total) as *char8;
        bux_strcpy(buf, a);
        bux_strcat(buf, b);
        return buf;
    }

    /// Heap-copy of `s`.
    func String_Copy(s: String) -> String {
        let len: uint = bux_strlen(s);
        let buf: *char8 = bux_alloc(len + 1) as *char8;
        bux_strcpy(buf, s);
        return buf;
    }

    /// True if `s` begins with `prefix`.
    func String_StartsWith(s: String, prefix: String) -> bool {
        let s_len: uint = bux_strlen(s);
        let p_len: uint = bux_strlen(prefix);
        if p_len > s_len {
            return false;
        }
        let r: int = bux_strncmp(s, prefix, p_len);
        return r == 0;
    }

    /// True if `s` ends with `suffix`.
    func String_EndsWith(s: String, suffix: String) -> bool {
        let s_len: uint = bux_strlen(s);
        let suf_len: uint = bux_strlen(suffix);
        if suf_len > s_len {
            return false;
        }
        let start: uint = s_len - suf_len;
        let tail: String = bux_str_slice(s, start, suf_len);
        let eq: bool = bux_strcmp(tail, suffix) == 0;
        return eq;
    }

    /// True if `substr` occurs anywhere in `s`.
    func String_Contains(s: String, substr: String) -> bool {
        let r: int = bux_str_contains(s, substr);
        return r != 0;
    }

    func String_Slice(s: String, start: uint, len: uint) -> String {
        return bux_str_slice(s, start, len);
    }

    func String_Trim(s: String) -> String {
        return bux_str_trim(s);
    }

    func String_TrimLeft(s: String) -> String {
        return bux_str_trim_left(s);
    }

    func String_TrimRight(s: String) -> String {
        return bux_str_trim_right(s);
    }

    func String_FromInt(n: int64) -> String {
        return bux_int_to_str(n);
    }

    func String_ToInt(s: String) -> int64 {
        return bux_str_to_int(s);
    }

    // String Builder — efficient string construction
    struct StringBuilder {
        handle: *void,
    }

    func StringBuilder_New() -> StringBuilder {
        return StringBuilder { handle: bux_sb_new(64) };
    }

    func StringBuilder_NewCap(cap: uint) -> StringBuilder {
        return StringBuilder { handle: bux_sb_new(cap) };
    }

    func StringBuilder_Append(sb: *StringBuilder, s: String) {
        bux_sb_append(sb.handle, s);
    }

    func StringBuilder_AppendInt(sb: *StringBuilder, n: int64) {
        bux_sb_append_int(sb.handle, n);
    }

    func StringBuilder_AppendFloat(sb: *StringBuilder, f: float64) {
        bux_sb_append_float(sb.handle, f);
    }

    func StringBuilder_AppendChar(sb: *StringBuilder, c: char8) {
        bux_sb_append_char(sb.handle, c);
    }

    func StringBuilder_Build(sb: *StringBuilder) -> String {
        return bux_sb_build(sb.handle);
    }

    func StringBuilder_Free(sb: *StringBuilder) {
        bux_sb_free(sb.handle);
    }

    /// True if empty or only whitespace (space, tab, CR, LF).
    func String_IsBlank(s: String) -> bool {
        let n: uint = bux_strlen(s);
        var i: uint = 0;
        while i < n {
            let ch: String = bux_str_slice(s, i, 1);
            if !(String_Eq(ch, " ") || String_Eq(ch, "\t") || String_Eq(ch, "\n") || String_Eq(ch, "\r")) {
                return false;
            }
            i = i + 1;
        }
        return true;
    }

    /// Repeat `s`, `count` times (`count == 0` → empty string).
    func String_Repeat(s: String, count: uint) -> String {
        if count == 0 {
            return "";
        }
        if count == 1 {
            return s;
        }
        var sb: StringBuilder = StringBuilder_New();
        var i: uint = 0;
        while i < count {
            StringBuilder_Append(&sb, s);
            i = i + 1;
        }
        let result: String = StringBuilder_Build(&sb);
        StringBuilder_Free(&sb);
        return result;
    }

    // ---------------------------------------------------------------------------
    // String split/join
    // ---------------------------------------------------------------------------

    func String_SplitCount(s: String, delim: String) -> uint {
        return bux_str_split_count(s, delim);
    }

    func String_SplitPart(s: String, delim: String, index: uint) -> String {
        return bux_str_split_part(s, delim, index);
    }

    func String_Join2(a: String, b: String, sep: String) -> String {
        return bux_str_join2(a, b, sep);
    }

    // ---------------------------------------------------------------------------
    // String find/replace/format
    // ---------------------------------------------------------------------------

    // String_Chars — return single-character string at index (for iteration)
    func String_Chars(s: String, index: uint) -> String {
        return bux_str_slice(s, index, 1);
    }

    func String_Find(haystack: String, needle: String) -> String {
        return bux_strstr(haystack, needle);
    }

    func String_Offset(pos: String, base: String) -> uint {
        return bux_str_offset(pos, base);
    }

    func String_Replace(s: String, old: String, new: String) -> String {
        let pos: String = bux_strstr(s, old);
        if String_IsNull(pos) {
            return s;
        }
        let oldLen: uint = bux_strlen(old);
        let prefixLen: uint = String_Offset(pos, s);
        let prefix: String = bux_str_slice(s, 0, prefixLen);
        let suffix: String = bux_str_slice(s, prefixLen + oldLen, bux_strlen(s) - prefixLen - oldLen);
        let temp: String = String_Concat(prefix, new);
        let result: String = String_Concat(temp, suffix);
        return result;
    }

    /// Replace every non-overlapping occurrence of `old` with `new`.
    /// Empty `old` is a no-op (returns `s` unchanged). Safe if `new` contains `old`.
    func String_ReplaceAll(s: String, old: String, new: String) -> String {
        let oldLen: uint = bux_strlen(old);
        if oldLen == 0 {
            return s;
        }
        var sb: StringBuilder = StringBuilder_New();
        var remaining: String = s;
        while true {
            let pos: String = bux_strstr(remaining, old);
            if String_IsNull(pos) {
                StringBuilder_Append(&sb, remaining);
                break;
            }
            let prefixLen: uint = String_Offset(pos, remaining);
            let prefix: String = bux_str_slice(remaining, 0, prefixLen);
            StringBuilder_Append(&sb, prefix);
            StringBuilder_Append(&sb, new);
            let remLen: uint = bux_strlen(remaining);
            remaining = bux_str_slice(remaining, prefixLen + oldLen, remLen - prefixLen - oldLen);
        }
        let result: String = StringBuilder_Build(&sb);
        StringBuilder_Free(&sb);
        return result;
    }

    extern func bux_str_to_float(s: String) -> float64;

    func String_ToFloat(s: String) -> float64 {
        return bux_str_to_float(s);
    }

    func String_FromBool(b: bool) -> String {
        if b { return "true"; }
        return "false";
    }

    func String_FromFloat(f: float64) -> String {
        return bux_float_to_string(f);
    }

    func String_Format1(pattern: String, a0: String) -> String {
        return bux_str_format(pattern, a0, "", "", "", "", "", "", "");
    }

    func String_Format2(pattern: String, a0: String, a1: String) -> String {
        return bux_str_format(pattern, a0, a1, "", "", "", "", "", "");
    }

    func String_Format3(pattern: String, a0: String, a1: String, a2: String) -> String {
        return bux_str_format(pattern, a0, a1, a2, "", "", "", "", "");
    }

}
