// fmt.bux — Bux source code formatter (indentation-based, preserves line structure)
module Fmt {

    extern func bux_read_file(path: String) -> String;
    extern func bux_write_file(path: String, content: String) -> bool;
    extern func bux_strlen(s: String) -> uint;

    // Count leading spaces on a line
    func Fmt_CountLeadingSpaces(line: String) -> int {
        var count: int = 0;
        while count < 256 {
            let c: int = line[count] as int;
            if c == 0 { break; }
            if c != 32 { break; } // space
            count = count + 1;
        }
        return count;
    }

    // Skip whitespace from start of line, return rest
    func Fmt_TrimLeft(line: String) -> String {
        var i: int = 0;
        while i < 256 {
            let c: int = line[i] as int;
            if c == 0 { break; }
            if c != 32 && c != 9 { break; }
            i = i + 1;
        }
        if i == 0 { return line; }
        // Extract substring from i
        var len: int = 0;
        while len < 256 {
            let c: int = line[len] as int;
            if c == 0 { break; }
            len = len + 1;
        }
        if i >= len { return ""; }
        return bux_str_slice(line, i as uint, (len - i) as uint);
    }

    // Check if char at position is inside a string or comment (simplified)
    func Fmt_IsInStringOrComment(line: String, pos: int) -> bool {
        var inString: bool = false;
        var inChar: bool = false;
        var inComment: bool = false;
        var i: int = 0;
        while i < pos {
            let c: int = line[i] as int;
            let n: int = 0;
            if i + 1 < 256 { n = line[i + 1] as int; }
            if inComment { i = i + 1; continue; }
            if c == 47 && n == 47 { inComment = true; i = i + 1; continue; } // //
            if c == 34 && !inChar { inString = !inString; }
            if c == 39 && !inString { inChar = !inChar; }
            i = i + 1;
        }
        return inString || inChar || inComment;
    }

    // Count brace depth change on a line, skipping strings/comments
    func Fmt_CountBraceDelta(line: String) -> int {
        var delta: int = 0;
        var i: int = 0;
        while i < 256 {
            let c: int = line[i] as int;
            if c == 0 { break; }
            if !Fmt_IsInStringOrComment(line, i) {
                if c == 123 { delta = delta + 1; } // {
                if c == 125 { delta = delta - 1; } // }
            }
            i = i + 1;
        }
        return delta;
    }

    func Fmt_FormatSource(source: String) -> String {
        let sb: StringBuilder = StringBuilder_NewCap(8192);
        var indent: int = 0;
        var i: uint = 0;
        var lineCount: uint = bux_str_split_count(source, "\n");

        // Trailing "\n" yields a final empty part (split artifact). Drop it so
        // re-formatting is idempotent and does not accumulate blank lines.
        if lineCount > 0 {
            let last: String = bux_str_split_part(source, "\n", lineCount - 1);
            if String_Eq(last, "") {
                lineCount = lineCount - 1;
            }
        }

        while i < lineCount {
            let line: String = bux_str_split_part(source, "\n", i);
            let trimmed: String = Fmt_TrimLeft(line);

            // Empty line (intentional blank) — keep a single newline
            if String_Eq(trimmed, "") {
                StringBuilder_Append(&sb, "\n");
                i = i + 1;
                continue;
            }

            // Adjust indent for closing braces on this line
            let delta: int = Fmt_CountBraceDelta(trimmed);
            // If line starts with }, decrease indent before emitting
            let firstChar: int = trimmed[0] as int;
            if firstChar == 125 { // }
                indent = indent - 1;
                if indent < 0 { indent = 0; }
            }

            // Emit indentation
            var si: int = 0;
            while si < indent {
                StringBuilder_Append(&sb, "    ");
                si = si + 1;
            }

            // Emit the trimmed line
            StringBuilder_Append(&sb, trimmed);
            StringBuilder_Append(&sb, "\n");

            // Adjust indent for opening braces
            if firstChar != 125 {
                indent = indent + delta;
            } else {
                // For lines starting with }, apply the net delta after the initial decrease
                indent = indent + delta + 1;
                if indent < 0 { indent = 0; }
            }

            i = i + 1;
        }

        let result: String = StringBuilder_Build(&sb);
        StringBuilder_Free(&sb);
        return result;
    }

    func Fmt_FormatFile(path: String) -> bool {
        let source: String = bux_read_file(path);
        if source == null as String || String_Eq(source, "") { return false; }
        let formatted: String = Fmt_FormatSource(source);
        if String_Eq(formatted, "") { return false; }
        return bux_write_file(path, formatted);
    }

    // Returns true if formatting would change the file (CI --check).
    func Fmt_WouldChange(path: String) -> bool {
        let source: String = bux_read_file(path);
        if source == null as String { return false; }
        let formatted: String = Fmt_FormatSource(source);
        return !String_Eq(formatted, source);
    }

    // Check a file without writing. Returns 0 if clean, 1 if would reformat / error.
    func Fmt_CheckFile(path: String) -> int {
        let source: String = bux_read_file(path);
        if source == null as String {
            return 1;
        }
        let formatted: String = Fmt_FormatSource(source);
        if String_Eq(formatted, source) {
            return 0;
        }
        return 1;
    }

}
