using Gee;

public class PhpToValaComposerLibrary : Object {
    private class ComposerMethod : Object {
        public string name = "";
        public string doc_comment = "";
        public string return_type = "";
        public string visibility = "public";
        public bool is_static = false;
        public bool is_abstract = false;
        public string body = "";
        public ArrayList<LocalParameter> parameters = new ArrayList<LocalParameter> ();
    }

    private class ComposerConstant : Object {
        public string name = "";
        public string value = "";
    }

    private class ComposerProperty : Object {
        public string name = "";
        public string type_name = "";
        public string visibility = "public";
        public bool nullable = false;
    }

    private class ComposerSymbol : Object {
        public string kind = "";
        public string namespace_name = "";
        public string name = "";
        public string doc_comment = "";
        public string extends_name = "";
        public string implements_name = "";
        public bool is_abstract = false;
        public ArrayList<string> traits = new ArrayList<string> ();
        public ArrayList<ComposerMethod> methods = new ArrayList<ComposerMethod> ();
        public ArrayList<ComposerConstant> constants = new ArrayList<ComposerConstant> ();
        public ArrayList<ComposerProperty> properties = new ArrayList<ComposerProperty> ();
    }

    public string vala_source = "";
    public string stub_source = "";

    private ArrayList<ComposerSymbol> symbols = new ArrayList<ComposerSymbol> ();
    private HashMap<string, ComposerSymbol> symbol_by_name = new HashMap<string, ComposerSymbol> ();

    public static PhpToValaComposerLibrary generate (string composer_json_path) throws Error {
        var generator = new PhpToValaComposerLibrary ();
        generator.load (composer_json_path);
        generator.vala_source = generator.build_vala_source (composer_json_path);
        generator.stub_source = generator.build_stub_source (composer_json_path);
        return generator;
    }

    private void load (string composer_json_path) throws Error {
        string composer_json;
        FileUtils.get_contents (composer_json_path, out composer_json);
        string source_dir = Path.get_dirname (composer_json_path);
        string psr_namespace;
        string psr_path;
        parse_first_psr4_mapping (composer_json, out psr_namespace, out psr_path);

        string php_dir = Path.build_filename (source_dir, psr_path);
        var files = new ArrayList<string> ();
        collect_php_files (php_dir, files);
        files.sort ((a, b) => strcmp (a, b));
        foreach (string php_file in files) {
            parse_php_file (php_file);
        }

        foreach (ComposerSymbol symbol in symbols) {
            symbol_by_name[symbol.name] = symbol;
            if (symbol.namespace_name != "") {
                symbol_by_name[symbol.namespace_name + "\\" + symbol.name] = symbol;
            }
        }
    }

    private static void parse_first_psr4_mapping (string composer_json, out string namespace_name, out string path) throws Error {
        namespace_name = "";
        path = "";
        bool in_psr4 = false;
        foreach (string raw_line in composer_json.split ("\n")) {
            string line = raw_line.strip ();
            if (line.index_of ("\"psr-4\"") >= 0) {
                in_psr4 = true;
                continue;
            }
            if (!in_psr4) {
                continue;
            }
            if (line.index_of ("}") >= 0) {
                break;
            }
            var quoted = extract_quoted_strings (line);
            if (quoted.size >= 2) {
                namespace_name = unescape_json_string (quoted[0]);
                path = unescape_json_string (quoted[1]);
                return;
            }
        }
        throw new PhpToValaError.UNSUPPORTED ("composer.json has no readable autoload.psr-4 mapping");
    }

    private static ArrayList<string> extract_quoted_strings (string line) {
        var values = new ArrayList<string> ();
        bool in_string = false;
        bool escaped = false;
        var current = new StringBuilder ();
        for (int i = 0; i < line.length; i++) {
            char c = line[i];
            if (escaped) {
                current.append_c (c);
                escaped = false;
                continue;
            }
            if (in_string && c == '\\') {
                current.append_c (c);
                escaped = true;
                continue;
            }
            if (c == '"') {
                if (in_string) {
                    values.add (current.str);
                    current.truncate (0);
                    in_string = false;
                } else {
                    in_string = true;
                }
                continue;
            }
            if (in_string) {
                current.append_c (c);
            }
        }
        return values;
    }

    private static string unescape_json_string (string value) {
        return value.replace ("\\\\", "\\");
    }

    private static void collect_php_files (string dir_path, ArrayList<string> files) throws Error {
        Dir dir = Dir.open (dir_path);
        string? name;
        while ((name = dir.read_name ()) != null) {
            if (name == "." || name == "..") {
                continue;
            }
            string path = Path.build_filename (dir_path, name);
            if (FileUtils.test (path, FileTest.IS_DIR)) {
                collect_php_files (path, files);
            } else if (path.has_suffix (".php")) {
                files.add (path);
            }
        }
    }

    private void parse_php_file (string php_file) throws Error {
        string source;
        FileUtils.get_contents (php_file, out source);
        string namespace_name = "";
        ComposerSymbol? current_symbol = null;
        ComposerMethod? current_method = null;
        var pending_doc = new StringBuilder ();
        bool in_doc = false;
        bool in_method_body = false;
        int method_depth = 0;
        var method_body = new StringBuilder ();

        foreach (string raw_line in source.split ("\n")) {
            string line = raw_line.strip ();
            if (line == "<?php" || line == "") {
                continue;
            }

            if (in_doc || line.has_prefix ("/**")) {
                if (pending_doc.len > 0) {
                    pending_doc.append ("\n");
                }
                pending_doc.append (raw_line);
                in_doc = line.index_of ("*/") < 0;
                continue;
            }

            if (in_method_body && current_method != null) {
                int next_depth = method_depth + count_braces (line);
                if (!(line == "}" && next_depth == 0)) {
                    method_body.append (raw_line);
                    method_body.append ("\n");
                }
                method_depth = next_depth;
                if (method_depth <= 0) {
                    current_method.body = method_body.str;
                    current_method = null;
                    in_method_body = false;
                    method_body.truncate (0);
                }
                continue;
            }

            if (line.has_prefix ("namespace ") && line.has_suffix (";")) {
                namespace_name = line.substring ("namespace ".length, line.length - "namespace ".length - 1).strip ();
                continue;
            }

            if (current_symbol == null) {
                ComposerSymbol? symbol = parse_symbol_declaration (line, namespace_name, consume_doc (pending_doc));
                if (symbol != null) {
                    symbols.add (symbol);
                    current_symbol = symbol;
                }
                continue;
            }

            if (line == "}") {
                current_symbol = null;
                continue;
            }

            if (line.has_prefix ("use ") && line.has_suffix (";")) {
                current_symbol.traits.add (line.substring ("use ".length, line.length - "use ".length - 1).strip ());
                continue;
            }

            ComposerConstant? constant = parse_constant (line);
            if (constant != null) {
                current_symbol.constants.add (constant);
                continue;
            }

            ComposerMethod? method = parse_method_header (line, consume_doc (pending_doc));
            if (method != null) {
                current_symbol.methods.add (method);
                if (!line.has_suffix (";")) {
                    current_method = method;
                    in_method_body = true;
                    method_depth = count_braces (line);
                    method_body.truncate (0);
                }
                continue;
            }

            ComposerProperty? property = parse_property (line);
            if (property != null) {
                current_symbol.properties.add (property);
                continue;
            }

            if (line != "") {
                consume_doc (pending_doc);
            }
        }
    }

    private static ComposerSymbol? parse_symbol_declaration (string line, string namespace_name, string doc_comment) {
        string clean = line;
        var symbol = new ComposerSymbol ();
        symbol.namespace_name = namespace_name;
        symbol.doc_comment = doc_comment;
        if (clean.has_prefix ("abstract class ")) {
            symbol.is_abstract = true;
            symbol.kind = "class";
            clean = clean.substring ("abstract class ".length).strip ();
        } else if (clean.has_prefix ("class ")) {
            symbol.kind = "class";
            clean = clean.substring ("class ".length).strip ();
        } else if (clean.has_prefix ("interface ")) {
            symbol.kind = "interface";
            clean = clean.substring ("interface ".length).strip ();
        } else if (clean.has_prefix ("trait ")) {
            symbol.kind = "trait";
            clean = clean.substring ("trait ".length).strip ();
        } else {
            return null;
        }

        clean = clean.replace ("{", "").strip ();
        var tokens = clean.split (" ");
        if (tokens.length < 1 || tokens[0] == "") {
            return null;
        }
        symbol.name = tokens[0].strip ();
        int extends_index = clean.index_of (" extends ");
        if (extends_index >= 0) {
            string rest = clean.substring (extends_index + " extends ".length).strip ();
            int end = rest.index_of (" ");
            symbol.extends_name = strip_global_prefix (end >= 0 ? rest.substring (0, end).strip () : rest);
        }
        int implements_index = clean.index_of (" implements ");
        if (implements_index >= 0) {
            symbol.implements_name = clean.substring (implements_index + " implements ".length).strip ();
        }
        return symbol;
    }

    private static ComposerConstant? parse_constant (string line) {
        if (!line.has_prefix ("const ") || !line.has_suffix (";")) {
            return null;
        }
        string rest = line.substring ("const ".length, line.length - "const ".length - 1).strip ();
        int equals_index = rest.index_of ("=");
        if (equals_index < 0) {
            return null;
        }
        var constant = new ComposerConstant ();
        constant.name = rest.substring (0, equals_index).strip ();
        constant.value = rest.substring (equals_index + 1).strip ();
        return constant;
    }

    private static ComposerProperty? parse_property (string line) {
        if (!line.has_suffix (";") || line.index_of ("$") < 0) {
            return null;
        }
        string visibility = "";
        foreach (string candidate in new string[] { "public ", "protected ", "private " }) {
            if (line.has_prefix (candidate)) {
                visibility = candidate.strip ();
                break;
            }
        }
        if (visibility == "") {
            return null;
        }
        int dollar_index = line.index_of ("$");
        int end = dollar_index + 1;
        while (end < line.length && (line[end].isalnum () || line[end] == '_')) {
            end++;
        }
        var property = new ComposerProperty ();
        property.visibility = visibility;
        property.name = line.substring (dollar_index + 1, end - dollar_index - 1);
        string before = line.substring (visibility.length, dollar_index - visibility.length).strip ();
        if (before.has_prefix ("?")) {
            property.nullable = true;
            before = before.substring (1).strip ();
        }
        property.type_name = before;
        return property;
    }

    private static ComposerMethod? parse_method_header (string line, string doc_comment) {
        if (line.index_of ("function ") < 0) {
            return null;
        }
        string header = line.replace ("{", "").replace (";", "").strip ();
        var method = new ComposerMethod ();
        method.doc_comment = doc_comment;
        while (true) {
            if (header.has_prefix ("abstract ")) {
                method.is_abstract = true;
                header = header.substring ("abstract ".length).strip ();
            } else if (header.has_prefix ("public ")) {
                method.visibility = "public";
                header = header.substring ("public ".length).strip ();
            } else if (header.has_prefix ("protected ")) {
                method.visibility = "protected";
                header = header.substring ("protected ".length).strip ();
            } else if (header.has_prefix ("private ")) {
                method.visibility = "private";
                header = header.substring ("private ".length).strip ();
            } else if (header.has_prefix ("static ")) {
                method.is_static = true;
                header = header.substring ("static ".length).strip ();
            } else {
                break;
            }
        }
        if (!header.has_prefix ("function ")) {
            return null;
        }
        header = header.substring ("function ".length).strip ();
        int open_index = header.index_of ("(");
        int close_index = header.last_index_of (")");
        if (open_index <= 0 || close_index < open_index) {
            return null;
        }
        method.name = header.substring (0, open_index).strip ();
        parse_parameters (header.substring (open_index + 1, close_index - open_index - 1), method.parameters);
        string return_part = header.substring (close_index + 1).strip ();
        if (return_part.has_prefix (":")) {
            method.return_type = return_part.substring (1).strip ();
        }
        return method;
    }

    private static void parse_parameters (string raw_parameters, ArrayList<LocalParameter> parameters) {
        if (raw_parameters.strip () == "") {
            return;
        }
        foreach (string raw_parameter in split_top_level (raw_parameters, ',')) {
            string parameter = raw_parameter.strip ();
            string? default_value = null;
            int equals_index = parameter.index_of ("=");
            if (equals_index >= 0) {
                default_value = parameter.substring (equals_index + 1).strip ();
                parameter = parameter.substring (0, equals_index).strip ();
            }
            int dollar_index = parameter.index_of ("$");
            if (dollar_index < 0) {
                continue;
            }
            string type_name = dollar_index > 0 ? parameter.substring (0, dollar_index).strip () : "";
            string name = parameter.substring (dollar_index + 1).strip ();
            parameters.add (new LocalParameter (name, type_name == "" ? null : type_name, default_value));
        }
    }

    private string build_vala_source (string composer_json_path) {
        var output = new StringBuilder ();
        output.append ("using Gee;\n\n");
        string namespace_name = symbols.size > 0 ? symbols[0].namespace_name : "";
        if (namespace_name != "") {
            output.append_printf ("namespace %s {\n", php_namespace_to_vala (namespace_name));
        }

        foreach (ComposerSymbol symbol in symbols) {
            if (symbol.kind == "interface") {
                append_vala_interface (output, symbol, 1);
            }
        }
        foreach (ComposerSymbol symbol in symbols) {
            if (symbol.kind == "trait") {
                append_vala_trait_surrogate (output, symbol, 1);
            }
        }
        foreach (ComposerSymbol symbol in symbols) {
            if (symbol.kind == "class") {
                append_vala_class (output, symbol, 1);
            }
        }

        if (namespace_name != "") {
            output.append ("}\n");
        }
        return output.str;
    }

    private void append_vala_interface (StringBuilder output, ComposerSymbol symbol, int indent) {
        append_indent (output, indent);
        output.append_printf ("public interface %s : Object {\n", symbol.name);
        foreach (ComposerMethod method in symbol.methods) {
            append_indent (output, indent + 1);
            output.append_printf ("public abstract %s %s (%s) throws Error;\n", vala_return_type (method.return_type), method.name, vala_parameter_list (method));
        }
        append_indent (output, indent);
        output.append ("}\n\n");
    }

    private void append_vala_trait_surrogate (StringBuilder output, ComposerSymbol symbol, int indent) {
        append_indent (output, indent);
        output.append_printf ("public abstract class %s : Object {\n", symbol.name);
        foreach (ComposerProperty property in symbol.properties) {
            append_indent (output, indent + 1);
            output.append_printf ("%s %s%s %s = null;\n", property.visibility, vala_type (property.type_name), property.nullable ? "?" : "", property.name);
        }
        foreach (ComposerMethod method in symbol.methods) {
            append_vala_method (output, symbol, method, indent + 1, false, true);
        }
        append_indent (output, indent);
        output.append ("}\n\n");
    }

    private void append_vala_class (StringBuilder output, ComposerSymbol symbol, int indent) {
        append_indent (output, indent);
        output.append ("public ");
        if (symbol.is_abstract) {
            output.append ("abstract ");
        }
        output.append_printf ("class %s", symbol.name);
        string parent = vala_parent_for (symbol);
        output.append_printf (" : %s", parent);
        foreach (string iface in split_implements (symbol.implements_name)) {
            output.append_printf (", %s", short_php_name (iface));
        }
        output.append (" {\n");

        foreach (ComposerConstant constant in symbol.constants) {
            append_indent (output, indent + 1);
            output.append_printf ("public const string %s = %s;\n", constant.name, php_string_to_vala_string (constant.value));
        }

        foreach (ComposerProperty property in symbol.properties) {
            append_indent (output, indent + 1);
            output.append_printf ("%s %s%s %s = null;\n", property.visibility, vala_type (property.type_name), property.nullable ? "?" : "", property.name);
        }

        foreach (string trait_name in symbol.traits) {
            ComposerSymbol? trait = symbol_by_name[trait_name] ?? symbol_by_name[symbol.namespace_name + "\\" + trait_name];
            if (trait == null) {
                continue;
            }
            foreach (ComposerProperty property in trait.properties) {
                append_indent (output, indent + 1);
                output.append_printf ("%s %s%s %s = null;\n", property.visibility, vala_type (property.type_name), property.nullable ? "?" : "", property.name);
            }
            foreach (ComposerMethod method in trait.methods) {
                append_vala_method (output, symbol, method, indent + 1, true, true);
            }
        }

        foreach (ComposerMethod method in symbol.methods) {
            append_vala_method (output, symbol, method, indent + 1, symbol.extends_name != "", false);
        }

        append_indent (output, indent);
        output.append ("}\n\n");
    }

    private void append_vala_method (StringBuilder output, ComposerSymbol owner, ComposerMethod method, int indent, bool from_trait, bool allow_abstract) {
        append_indent (output, indent);
        string return_type = vala_return_type (method.return_type);
        if (allow_abstract && method.is_abstract) {
            output.append_printf ("public abstract %s %s (%s) throws Error;\n", return_type, method.name, vala_parameter_list (method));
            return;
        }
        output.append ("public ");
        if (method.is_static) {
            output.append ("static ");
        }
        if (!owner.is_abstract && overrides_parent_method (owner, method.name)) {
            output.append ("override ");
        }
        output.append_printf ("%s %s (%s) throws Error {", return_type, method.name, vala_parameter_list (method));
        string? delegate_body = vala_delegate_body (method);
        if (delegate_body != null) {
            output.append (" ");
            output.append (delegate_body);
            output.append (" ");
        }
        output.append ("}\n");
    }

    private string? vala_delegate_body (ComposerMethod method) {
        foreach (string raw_line in method.body.split ("\n")) {
            string line = raw_line.strip ();
            if (line.has_prefix ("$this->log(") && line.has_suffix (");")) {
                string args = line.substring ("$this->log(".length, line.length - "$this->log(".length - 2);
                var parts = split_top_level (args, ',');
                if (parts.size == 3 && parts[0].strip ().has_prefix ("LogLevel::")) {
                    string level = parts[0].strip ().substring ("LogLevel::".length);
                    return "this.log (Php.zval.string_value (LogLevel.%s), %s, %s);".printf (level, php_variable_to_vala (parts[1].strip ()), php_variable_to_vala (parts[2].strip ()));
                }
            }
            if (line.index_of ("$this->logger =") >= 0) {
                return "this.logger = logger;";
            }
        }
        return null;
    }

    private string build_stub_source (string composer_json_path) {
        var output = new StringBuilder ();
        output.append ("<?php\n");
        output.append ("/*\n");
        output.append (" * php2vala generated Composer PHP stub. Declarative only; do not execute as application code.\n");
        output.append_printf (" * composer: %s\n", composer_json_path);
        output.append (" */\n\n");
        string current_namespace = "";
        foreach (ComposerSymbol symbol in symbols) {
            if (symbol.namespace_name != current_namespace) {
                current_namespace = symbol.namespace_name;
                output.append_printf ("namespace %s;\n\n", current_namespace);
            }
            append_doc (output, symbol.doc_comment, "");
            output.append (symbol.kind);
            if (symbol.kind == "class" && symbol.is_abstract) {
                output.truncate (output.len - "class".length);
                output.append ("abstract class");
            }
            output.append_printf (" %s", symbol.name);
            if (symbol.extends_name != "") {
                string extends_name = symbol.extends_name == symbol.name ? "\\" + symbol.extends_name : symbol.extends_name;
                output.append_printf (" extends %s", extends_name);
            }
            if (symbol.implements_name != "") {
                output.append_printf (" implements %s", symbol.implements_name);
            }
            output.append (" {\n");
            foreach (ComposerConstant constant in symbol.constants) {
                output.append_printf ("    const %s = %s;\n", constant.name, constant.value);
            }
            foreach (ComposerProperty property in symbol.properties) {
                output.append_printf ("    %s %s$%s = null;\n", property.visibility, property.nullable ? "?" + property.type_name + " " : (property.type_name == "" ? "" : property.type_name + " "), property.name);
            }
            foreach (string trait_name in symbol.traits) {
                output.append_printf ("    use %s;\n", trait_name);
                ComposerSymbol? trait = symbol_by_name[trait_name] ?? symbol_by_name[symbol.namespace_name + "\\" + trait_name];
                if (trait != null) {
                    foreach (ComposerMethod method in trait.methods) {
                        append_doc (output, method.doc_comment, "    ");
                        string prefix = method.is_abstract ? "abstract " : "";
                        string suffix = method.is_abstract ? ";" : " {}";
                        output.append_printf ("    %s%s%sfunction %s(%s): %s%s\n", prefix, method.visibility, method.is_static ? " static " : " ", method.name, php_parameter_list (method), method.return_type == "" ? "mixed" : method.return_type, suffix);
                    }
                }
            }
            foreach (ComposerMethod method in symbol.methods) {
                append_doc (output, method.doc_comment, "    ");
                string prefix = method.is_abstract ? "abstract " : "";
                string suffix = (symbol.kind == "interface" || method.is_abstract) ? ";" : " {}";
                output.append_printf ("    %s%s%sfunction %s(%s): %s%s\n", prefix, method.visibility, method.is_static ? " static " : " ", method.name, php_parameter_list (method), method.return_type == "" ? "mixed" : method.return_type, suffix);
            }
            output.append ("}\n\n");
        }
        return output.str;
    }

    private string vala_parent_for (ComposerSymbol symbol) {
        if (symbol.extends_name == "") {
            return "Object";
        }
        string parent = short_php_name (symbol.extends_name);
        if (parent == "InvalidArgumentException") {
            return "Object";
        }
        return parent;
    }

    private bool overrides_parent_method (ComposerSymbol owner, string method_name) {
        if (owner.extends_name == "") {
            return false;
        }
        ComposerSymbol? parent = symbol_by_name[owner.extends_name] ?? symbol_by_name[owner.namespace_name + "\\" + owner.extends_name];
        while (parent != null) {
            foreach (ComposerMethod method in parent.methods) {
                if (method.name == method_name) {
                    return true;
                }
            }
            foreach (string trait_name in parent.traits) {
                ComposerSymbol? trait = symbol_by_name[trait_name] ?? symbol_by_name[parent.namespace_name + "\\" + trait_name];
                if (trait == null) {
                    continue;
                }
                foreach (ComposerMethod method in trait.methods) {
                    if (method.name == method_name) {
                        return true;
                    }
                }
            }
            if (parent.extends_name == "") {
                break;
            }
            parent = symbol_by_name[parent.extends_name] ?? symbol_by_name[parent.namespace_name + "\\" + parent.extends_name];
        }
        return false;
    }

    private static string vala_parameter_list (ComposerMethod method) {
        var output = new StringBuilder ();
        for (int i = 0; i < method.parameters.size; i++) {
            LocalParameter parameter = method.parameters[i];
            if (i > 0) {
                output.append (", ");
            }
            output.append_printf ("%s %s", vala_type (parameter.type_name ?? ""), parameter.name);
        }
        return output.str;
    }

    private static string php_parameter_list (ComposerMethod method) {
        var output = new StringBuilder ();
        for (int i = 0; i < method.parameters.size; i++) {
            LocalParameter parameter = method.parameters[i];
            if (i > 0) {
                output.append (", ");
            }
            if (parameter.type_name != null && parameter.type_name != "") {
                output.append (parameter.type_name);
                output.append (" ");
            }
            output.append ("$");
            output.append (parameter.name);
            if (parameter.default_value != null) {
                output.append (" = ");
                output.append (parameter.default_value);
            }
        }
        return output.str;
    }

    private static string vala_type (string php_type) {
        string clean = strip_global_prefix (php_type.strip ());
        bool nullable = false;
        if (clean.has_prefix ("?")) {
            nullable = true;
            clean = clean.substring (1).strip ();
        }
        if (clean == "void") {
            return "void";
        }
        if (clean == "LoggerInterface") {
            return nullable ? "LoggerInterface?" : "LoggerInterface";
        }
        if (clean == "" || clean == "mixed" || clean == "array" || clean.index_of ("|") >= 0 || clean == "string") {
            return "Php.zval";
        }
        return nullable ? clean + "?" : clean;
    }

    private static string vala_return_type (string php_type) {
        string clean = php_type.strip ();
        if (clean == "void") {
            return "void";
        }
        return vala_type (clean);
    }

    private static string php_namespace_to_vala (string namespace_name) {
        return namespace_name.replace ("\\", ".");
    }

    private static string short_php_name (string name) {
        string clean = strip_global_prefix (name.strip ());
        int slash = clean.last_index_of ("\\");
        return slash >= 0 ? clean.substring (slash + 1) : clean;
    }

    private static string strip_global_prefix (string name) {
        return name.has_prefix ("\\") ? name.substring (1) : name;
    }

    private static string php_variable_to_vala (string value) {
        return value.has_prefix ("$") ? value.substring (1) : value;
    }

    private static string php_string_to_vala_string (string value) {
        string clean = value.strip ();
        if (clean.has_prefix ("'") && clean.has_suffix ("'")) {
            clean = clean.substring (1, clean.length - 2);
            return "\"%s\"".printf (clean.replace ("\\", "\\\\").replace ("\"", "\\\""));
        }
        return clean;
    }

    private static string consume_doc (StringBuilder pending_doc) {
        string doc = pending_doc.str;
        pending_doc.truncate (0);
        return doc;
    }

    private static void append_doc (StringBuilder output, string doc, string indent) {
        if (doc.strip () == "") {
            return;
        }
        foreach (string raw_line in doc.split ("\n")) {
            output.append (indent);
            output.append (raw_line.strip ());
            output.append ("\n");
        }
    }

    private static void append_indent (StringBuilder output, int indent) {
        for (int i = 0; i < indent; i++) {
            output.append ("    ");
        }
    }

    private static ArrayList<string> split_implements (string implements_name) {
        var result = new ArrayList<string> ();
        if (implements_name.strip () == "") {
            return result;
        }
        foreach (string item in implements_name.split (",")) {
            result.add (item.strip ());
        }
        return result;
    }

    private static ArrayList<string> split_top_level (string value, char delimiter) {
        var parts = new ArrayList<string> ();
        var current = new StringBuilder ();
        int depth = 0;
        bool in_single_quote = false;
        bool in_double_quote = false;
        for (int i = 0; i < value.length; i++) {
            char c = value[i];
            if (c == '\'' && !in_double_quote) {
                in_single_quote = !in_single_quote;
            } else if (c == '"' && !in_single_quote) {
                in_double_quote = !in_double_quote;
            } else if (!in_single_quote && !in_double_quote) {
                if (c == '(' || c == '[' || c == '{') {
                    depth++;
                } else if (c == ')' || c == ']' || c == '}') {
                    depth--;
                } else if (c == delimiter && depth == 0) {
                    parts.add (current.str);
                    current.truncate (0);
                    continue;
                }
            }
            current.append_c (c);
        }
        parts.add (current.str);
        return parts;
    }

    private static int count_braces (string line) {
        int count = 0;
        foreach (char c in line.to_utf8 ()) {
            if (c == '{') count++;
            if (c == '}') count--;
        }
        return count;
    }
}
