using Gee;

public class PhpToValaGeneratorUtils : Object {
    public static string? php2vala_doc_value (string doc_comment, string key) {
        foreach (string raw_line in doc_comment.split ("\n")) {
            string line = raw_line.strip ();
            if (line.has_prefix ("*")) {
                line = line.substring (1).strip ();
            }
            if (!line.has_prefix (key)) {
                continue;
            }
            string value = line.substring (key.length).strip ();
            return value == "" ? null : value;
        }
        return null;
    }

    public static bool is_scan_ignored_php_function_name (string function_name) {
        switch (function_name) {
        case "if":
        case "elseif":
        case "for":
        case "foreach":
        case "while":
        case "switch":
        case "catch":
        case "function":
        case "isset":
        case "empty":
        case "exit":
        case "die":
            return true;
        }
        return false;
    }

    public static bool zval_statement_terminates_function (string vala_line) {
        string clean = vala_line.strip ();
        return clean.has_prefix ("return ") || clean.has_prefix ("Process.exit ");
    }

    public static bool function_body_exits (string body) {
        foreach (string raw_line in body.split ("\n")) {
            string line = raw_line.strip ();
            if (line.has_prefix ("exit") || line.has_prefix ("die")) {
                return true;
            }
        }
        return false;
    }

    public static string strip_vala_line_strings_and_comments (string line) {
        var output = new StringBuilder ();
        bool in_single_quote = false;
        bool in_double_quote = false;
        bool escaped = false;

        for (int i = 0; i < line.length; i++) {
            char c = line[i];
            char next = i + 1 < line.length ? line[i + 1] : '\0';

            if (!in_single_quote && !in_double_quote && c == '/' && next == '/') {
                break;
            }

            if (escaped) {
                escaped = false;
                continue;
            }
            if ((in_single_quote || in_double_quote) && c == '\\') {
                escaped = true;
                continue;
            }
            if (c == '\'' && !in_double_quote) {
                in_single_quote = !in_single_quote;
                continue;
            }
            if (c == '"' && !in_single_quote) {
                in_double_quote = !in_double_quote;
                continue;
            }
            if (!in_single_quote && !in_double_quote) {
                output.append_c (c);
            }
        }

        return output.str;
    }

    public static bool has_bare_function_call (string code, string name) {
        int start = 0;
        while (start < code.length) {
            int index = code.index_of (name, start);
            if (index < 0) {
                return false;
            }

            bool valid_left = index == 0 || (!is_identifier_char (code[index - 1]) && code[index - 1] != '.');
            int next_index = index + name.length;
            bool valid_right = next_index >= code.length || !is_identifier_char (code[next_index]);
            if (valid_left && valid_right) {
                while (next_index < code.length && (code[next_index] == ' ' || code[next_index] == '\t')) {
                    next_index++;
                }
                if (next_index < code.length && code[next_index] == '(') {
                    return true;
                }
            }

            start = index + name.length;
        }
        return false;
    }

    public static bool has_bare_keyword (string code, string name) {
        int start = 0;
        while (start < code.length) {
            int index = code.index_of (name, start);
            if (index < 0) {
                return false;
            }

            bool valid_left = index == 0 || !is_identifier_char (code[index - 1]);
            int next_index = index + name.length;
            bool valid_right = next_index >= code.length || !is_identifier_char (code[next_index]);
            if (valid_left && valid_right) {
                return true;
            }

            start = index + name.length;
        }
        return false;
    }

    public static string detect_indent_unit (string source) {
        foreach (string line in source.split ("\n")) {
            if (line.strip () == "") {
                continue;
            }

            var indent = new StringBuilder ();
            for (int i = 0; i < line.length; i++) {
                char c = line[i];
                if (c != ' ' && c != '\t') {
                    break;
                }
                indent.append_c (c);
            }

            if (indent.len == 0) {
                continue;
            }

            string candidate = indent.str;
            int tab_index = candidate.index_of ("\t");
            if (tab_index >= 0) {
                return "\t";
            }
            return candidate.length > 4 ? "    " : candidate;
        }

        return "    ";
    }

    public static string discard_unused_zval_wrapper (string expression) {
        string clean = expression.strip ();
        foreach (string prefix in new string[] {
            "Php.zval.integer (",
            "Php.zval.double_number (",
            "Php.zval.boolean (",
            "Php.zval.string_value ("
        }) {
            if (!clean.has_prefix (prefix) || !clean.has_suffix (")")) {
                continue;
            }
            int open_index = prefix.length - 1;
            int close_index = find_matching_paren (clean, open_index);
            if (close_index == clean.length - 1) {
                return clean.substring (open_index + 1, close_index - open_index - 1).strip ();
            }
        }
        return expression;
    }

    public static int find_top_level_assignment_index (string expression) {
        bool in_single_quote = false;
        bool in_double_quote = false;
        int depth = 0;

        for (int i = 0; i < expression.length; i++) {
            char c = expression[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 == '=' && depth == 0) {
                    char previous = i > 0 ? expression[i - 1] : '\0';
                    char next = i + 1 < expression.length ? expression[i + 1] : '\0';
                    if (previous != '=' && previous != '!' && previous != '<' && previous != '>' && next != '=') {
                        return i;
                    }
                }
            }
        }

        return -1;
    }

    public static bool is_native_string_list_expression (string expression) {
        string clean = expression.strip ();
        return clean.has_prefix ("PhpEmbed.preg_split_string_list_static")
            || clean.has_prefix ("PhpEmbed.array_slice_string_list_static")
            || clean.has_prefix ("PhpNative.preg_split_string_list_static")
            || clean.has_prefix ("PhpNative.array_slice_string_list_static");
    }

    public static string extract_php_namespace (string source) {
        foreach (string raw_line in source.split ("\n")) {
            string line = raw_line.strip ();
            if (line.has_prefix ("namespace ") && line.has_suffix (";")) {
                return line.substring ("namespace ".length, line.length - "namespace ".length - 1).strip ();
            }
        }
        return "";
    }

    public static bool is_php_class_name (string class_name) {
        string clean = class_name.has_prefix ("\\") ? class_name.substring (1) : class_name;
        if (clean == "") {
            return false;
        }
        foreach (string part in clean.split ("\\")) {
            if (!is_identifier (part)) {
                return false;
            }
        }
        return true;
    }

    public static string php_class_name_to_vala (string class_name) {
        return (class_name.has_prefix ("\\") ? class_name.substring (1) : class_name).replace ("\\", ".");
    }

    public static bool is_decimal_concat_false_positive (string left, string right) {
        string l = left.strip ();
        string r = right.strip ();
        return l.length > 0 && r.length > 0 && l[l.length - 1].isdigit () && r[0].isdigit ();
    }

    public static bool is_php_construct_runtime_pseudo_function (string function_name) {
        return function_name == "isset" || function_name == "empty";
    }

    public static bool is_microtime_true_expression (string expression) {
        string clean = expression.replace (" ", "").replace ("\t", "");
        return clean == "microtime(true)";
    }

    public static bool is_numeric_literal (string expression) {
        string clean = expression.strip ();
        if (clean == "") {
            return false;
        }

        int start = 0;
        if (clean[0] == '-' || clean[0] == '+') {
            start = 1;
        }
        if (start >= clean.length) {
            return false;
        }

        bool has_digit = false;
        bool has_dot = false;
        for (int i = start; i < clean.length; i++) {
            char c = clean[i];
            if (c >= '0' && c <= '9') {
                has_digit = true;
                continue;
            }
            if (c == '.' && !has_dot) {
                has_dot = true;
                continue;
            }
            return false;
        }

        return has_digit;
    }

    public static bool is_integer_literal (string expression) {
        string clean = expression.strip ();
        if (clean == "") {
            return false;
        }
        int start = 0;
        if (clean[0] == '-' || clean[0] == '+') {
            start = 1;
        }
        if (start >= clean.length) {
            return false;
        }
        for (int i = start; i < clean.length; i++) {
            char c = clean[i];
            if (c < '0' || c > '9') {
                return false;
            }
        }
        return true;
    }

    public static string? zval_wrapped_identifier (string expression) {
        string clean = expression.strip ();
        if (is_identifier (clean)) {
            return clean;
        }
        foreach (string prefix in new string[] {
            "Php.zval.integer (",
            "Php.zval.double_number (",
            "Php.zval.boolean (",
            "Php.zval.string_value ("
        }) {
            if (!clean.has_prefix (prefix) || !clean.has_suffix (")")) {
                continue;
            }
            int open_index = prefix.length - 1;
            int close_index = find_matching_paren (clean, open_index);
            if (close_index != clean.length - 1) {
                continue;
            }
            string inner = clean.substring (open_index + 1, close_index - open_index - 1).strip ();
            if (is_identifier (inner)) {
                return inner;
            }
        }
        return null;
    }

    public static string? unwrap_zval_constructor_arg (string expression, string prefix) {
        string clean = expression.strip ();
        if (!clean.has_prefix (prefix) || !clean.has_suffix (")")) {
            return null;
        }
        int open_index = prefix.length - 1;
        int close_index = find_matching_paren (clean, open_index);
        if (close_index != clean.length - 1) {
            return null;
        }
        return clean.substring (open_index + 1, close_index - open_index - 1).strip ();
    }

    public static string extract_vala_call_name (string expression) {
        string clean = expression.strip ();
        int open_index = clean.index_of ("(");
        if (open_index <= 0) {
            return "";
        }
        int close_index = find_matching_paren (clean, open_index);
        if (close_index != clean.length - 1) {
            return "";
        }
        return clean.substring (0, open_index).strip ();
    }

    public static string join_strings (ArrayList<string> values, string separator) {
        var output = new StringBuilder ();
        foreach (string value in values) {
            if (output.len > 0) {
                output.append (separator);
            }
            output.append (value);
        }
        return output.str;
    }

    public static string? parse_assignment_name (string line) {
        if (!line.has_prefix ("$") || !line.has_suffix (";")) {
            return null;
        }

        int equals_index = line.index_of ("=");
        if (equals_index <= 1) {
            return null;
        }

        string name = line.substring (1, equals_index - 1).strip ();
        if (!is_identifier (name)) {
            return null;
        }

        return name;
    }

    public static string parse_assignment_expression (string line) {
        int equals_index = line.index_of ("=");
        string expression = line.substring (equals_index + 1).strip ();
        return trim_trailing_semicolon (expression);
    }

    public static int find_matching_bracket (string value, int start_index) {
        bool in_single_quote = false;
        bool in_double_quote = false;
        int depth = 0;
        for (int i = start_index + 1; 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 == '[') {
                    depth++;
                } else if (c == ']') {
                    if (depth == 0) {
                        return i;
                    }
                    depth--;
                }
            }
        }
        return -1;
    }

    public static bool is_identifier (string name) {
        if (name == "") {
            return false;
        }

        for (int i = 0; i < name.length; i++) {
            char c = name[i];
            if (i == 0) {
                if (!(c.isalpha () || c == '_')) {
                    return false;
                }
            } else if (!is_identifier_char (c)) {
                return false;
            }
        }

        return true;
    }

    public static int find_matching_paren (string value, int start_index) {
        if (start_index < 0) {
            return -1;
        }
        bool in_single_quote = false;
        bool in_double_quote = false;
        bool escaped = false;
        int depth = 0;
        for (int i = start_index; i < value.length; i++) {
            char c = value[i];
            if ((in_single_quote || in_double_quote) && escaped) {
                escaped = false;
            } else if ((in_single_quote || in_double_quote) && c == '\\') {
                escaped = true;
            } else 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 == '(') {
                    depth++;
                } else if (c == ')') {
                    depth--;
                    if (depth == 0) {
                        return i;
                    }
                }
            }
        }
        return -1;
    }

    public static int find_matching_brace (string value, int start_index) {
        if (start_index < 0) {
            return -1;
        }
        bool in_single_quote = false;
        bool in_double_quote = false;
        bool escaped = false;
        int depth = 0;
        for (int i = start_index; i < value.length; i++) {
            char c = value[i];
            if ((in_single_quote || in_double_quote) && escaped) {
                escaped = false;
            } else if ((in_single_quote || in_double_quote) && c == '\\') {
                escaped = true;
            } else 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 == '{') {
                    depth++;
                } else if (c == '}') {
                    depth--;
                    if (depth == 0) {
                        return i;
                    }
                }
            }
        }
        return -1;
    }

    public static string normalize_php_boolean_comparisons (string expression) {
        return expression
            .replace ("!==false", "!=-1")
            .replace ("!== false", "!= -1")
            .replace ("!=false", "!=-1")
            .replace ("!= false", "!= -1")
            .replace ("===false", "==-1")
            .replace ("=== false", "== -1")
            .replace ("==false", "==-1")
            .replace ("== false", "== -1");
    }

    public static string convert_string_literal (string expression) {
        if (expression.length < 2) {
            return expression;
        }

        bool single_quoted = expression.has_prefix ("'") && expression.has_suffix ("'");
        bool double_quoted = expression.has_prefix ("\"") && expression.has_suffix ("\"");
        if (!single_quoted && !double_quoted) {
            return expression;
        }

        string value = expression.substring (1, expression.length - 2);
        if (single_quoted) {
            return "\"%s\"".printf (escape_vala_string (value));
        }

        return "\"%s\"".printf (escape_vala_string (decode_php_double_quoted_string (value)));
    }

    public static string decode_php_double_quoted_string (string value) {
        return value
            .replace ("\\r", "\r")
            .replace ("\\n", "\n")
            .replace ("\\t", "\t")
            .replace ("\\\"", "\"")
            .replace ("\\\\", "\\");
    }

    public static string normalize_runtime_type (string type_name) {
        return type_name
            .replace ("owned ", "")
            .replace ("unowned ", "")
            .replace ("out ", "")
            .replace ("?", "")
            .strip ();
    }

    public static bool is_out_runtime_parameter_type (string type_name) {
        return type_name.strip ().has_prefix ("out ");
    }

    public static bool is_runtime_object_type (string type_name) {
        string clean = normalize_runtime_type (type_name);
        return clean.index_of (".") > 0
            && clean != "Php.zval"
            && !clean.has_suffix ("[]");
    }

    public static bool is_static_variant_for_php_function (string function_name, string runtime_function_name) {
        if (!runtime_function_name.has_suffix ("_static")) {
            return false;
        }

        string stem = runtime_function_name.substring (0, runtime_function_name.length - "_static".length);
        if (stem == function_name) {
            return true;
        }
        if (!stem.has_prefix (function_name + "_")) {
            return false;
        }

        string shape = stem.substring (function_name.length + 1);
        foreach (string token in shape.split ("_")) {
            if (!is_static_shape_token (token)) {
                return false;
            }
        }
        return true;
    }

    public static bool is_zval_variant_for_php_function (string function_name, string runtime_function_name) {
        if (!runtime_function_name.has_suffix ("_zval")) {
            return false;
        }

        string stem = runtime_function_name.substring (0, runtime_function_name.length - "_zval".length);
        if (stem == function_name) {
            return true;
        }
        if (!stem.has_prefix (function_name + "_")) {
            return false;
        }

        string shape = stem.substring (function_name.length + 1);
        foreach (string token in shape.split ("_")) {
            if (!is_static_shape_token (token)) {
                return false;
            }
        }
        return true;
    }

    public static bool is_static_shape_token (string token) {
        switch (token) {
        case "array":
        case "bool":
        case "boolean":
        case "char":
        case "chars":
        case "double":
        case "float":
        case "int":
        case "integer":
        case "list":
        case "long":
        case "numeric":
        case "output":
        case "status":
        case "string":
        case "values":
            return true;
        default:
            return false;
        }
    }

    public static string replace_function_token (string expression, string function_name, string replacement_name) {
        var output = new StringBuilder ();
        int i = 0;
        while (i < expression.length) {
            if (expression.substring (i).has_prefix (function_name + "(")
                && (i == 0 || (!is_identifier_char (expression[i - 1]) && expression[i - 1] != '.'))) {
                output.append (replacement_name);
                output.append_c ('(');
                i += function_name.length + 1;
                continue;
            }
            output.append_c (expression[i]);
            i++;
        }
        return output.str;
    }

    public static string escape_vala_string (string value) {
        return value
            .replace ("\\", "\\\\")
            .replace ("\"", "\\\"")
            .replace ("\r", "\\r")
            .replace ("\n", "\\n")
            .replace ("\t", "\\t");
    }

    public static string trim_trailing_semicolon (string value) {
        string result = value.strip ();
        if (result.has_suffix (";")) {
            return result.substring (0, result.length - 1).strip ();
        }
        return result;
    }

    public static string replace_php_variables (string expression) {
        var output = new StringBuilder ();
        bool in_single_quote = false;
        bool in_double_quote = false;

        for (int i = 0; i < expression.length; i++) {
            char c = expression[i];

            if (c == '\'' && !in_double_quote) {
                in_single_quote = !in_single_quote;
                output.append_c (c);
                continue;
            }

            if (c == '"' && !in_single_quote) {
                in_double_quote = !in_double_quote;
                output.append_c (c);
                continue;
            }

            if (c == '$' && !in_single_quote && !in_double_quote) {
                continue;
            }

            output.append_c (c);
        }

        return output.str;
    }

    public static bool is_vala_reserved_identifier (string name) {
        switch (name) {
        case "abstract":
        case "as":
        case "async":
        case "base":
        case "bool":
        case "break":
        case "case":
        case "catch":
        case "char":
        case "class":
        case "const":
        case "construct":
        case "continue":
        case "default":
        case "delegate":
        case "delete":
        case "do":
        case "double":
        case "dynamic":
        case "else":
        case "enum":
        case "errordomain":
        case "extern":
        case "false":
        case "finally":
        case "float":
        case "for":
        case "foreach":
        case "get":
        case "if":
        case "in":
        case "inline":
        case "int":
        case "interface":
        case "internal":
        case "is":
        case "lock":
        case "namespace":
        case "new":
        case "null":
        case "out":
        case "override":
        case "owned":
        case "private":
        case "protected":
        case "public":
        case "ref":
        case "return":
        case "set":
        case "signal":
        case "sizeof":
        case "static":
        case "string":
        case "struct":
        case "switch":
        case "this":
        case "throw":
        case "throws":
        case "true":
        case "try":
        case "typeof":
        case "unowned":
        case "using":
        case "var":
        case "virtual":
        case "void":
        case "volatile":
        case "weak":
        case "while":
        case "yield":
            return true;
        default:
            return false;
        }
    }

    public static string replace_identifier_token (string expression, string name, string replacement) {
        var output = new StringBuilder ();
        int i = 0;
        while (i < expression.length) {
            if (expression.substring (i).has_prefix (name)
                && (i == 0 || !is_identifier_char (expression[i - 1]))
                && (i + name.length >= expression.length || !is_identifier_char (expression[i + name.length]))) {
                output.append (replacement);
                i += name.length;
                continue;
            }
            output.append_c (expression[i]);
            i++;
        }
        return output.str;
    }

    public static string convert_get_bracket_accesses (string expression) {
        string result = expression;
        bool changed = true;
        while (changed) {
            changed = false;
            int bracket = result.index_of (")[");
            if (bracket < 0) {
                break;
            }
            int open = bracket + 1;
            int close = find_matching_bracket (result, open);
            if (close < 0) {
                break;
            }
            string key = result.substring (open + 1, close - open - 1).strip ();
            result = result.substring (0, bracket + 1) + ".get (" + key + ")" + result.substring (close + 1);
            changed = true;
        }
        return result;
    }

    public static string replace_concat_operator (string expression) {
        var output = new StringBuilder ();
        bool in_single_quote = false;
        bool in_double_quote = false;

        for (int i = 0; i < expression.length; i++) {
            char c = expression[i];

            if (c == '\'' && !in_double_quote) {
                in_single_quote = !in_single_quote;
                output.append_c (c);
                continue;
            }

            if (c == '"' && !in_single_quote) {
                in_double_quote = !in_double_quote;
                output.append_c (c);
                continue;
            }

            if (c == '.' && !in_single_quote && !in_double_quote
                && !is_decimal_point (expression, i)
                && !is_qualified_name_dot (expression, i)) {
                output.append ("+");
                continue;
            }

            output.append_c (c);
        }

        return output.str;
    }

    public static bool is_decimal_point (string expression, int index) {
        if (index <= 0 || index >= expression.length - 1) {
            return false;
        }

        return expression[index - 1].isdigit () && expression[index + 1].isdigit ();
    }

    public static bool is_qualified_name_dot (string expression, int index) {
        if (index <= 0 || index >= expression.length - 1) {
            return false;
        }

        return is_identifier_char (expression[index - 1])
            && is_identifier_char (expression[index + 1]);
    }

    public static bool is_identifier_char (char c) {
        return c.isalnum () || c == '_';
    }

    public static ArrayList<string> split_top_level (string expression, char separator) {
        var parts = new ArrayList<string> ();
        var current = new StringBuilder ();
        bool in_single_quote = false;
        bool in_double_quote = false;
        int depth = 0;

        for (int i = 0; i < expression.length; i++) {
            char c = expression[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 == separator && depth == 0) {
                    if (separator == '-' && i + 1 < expression.length && expression[i + 1] == '>') {
                        current.append_c (c);
                        continue;
                    }
                    if ((separator == '-' || separator == '+') && is_unary_sign_position (expression, i)) {
                        current.append_c (c);
                        continue;
                    }
                    parts.add (current.str);
                    current.truncate (0);
                    continue;
                }
            }

            current.append_c (c);
        }

        parts.add (current.str);
        return parts;
    }

    public static bool is_unary_sign_position (string expression, int index) {
        if (index <= 0) {
            return true;
        }
        for (int i = index - 1; i >= 0; i--) {
            char previous = expression[i];
            if (previous.isspace ()) {
                continue;
            }
            return previous == '('
                || previous == '['
                || previous == '{'
                || previous == ','
                || previous == '+'
                || previous == '-'
                || previous == '*'
                || previous == '/'
                || previous == '%'
                || previous == '.'
                || previous == '='
                || previous == '<'
                || previous == '>'
                || previous == '!'
                || previous == '&'
                || previous == '|'
                || previous == '?'
                || previous == ':';
        }
        return true;
    }

    public static ArrayList<string> split_division (string expression) {
        var parts = new ArrayList<string> ();
        var current = new StringBuilder ();
        bool in_single_quote = false;
        bool in_double_quote = false;
        int depth = 0;

        for (int i = 0; i < expression.length; i++) {
            char c = expression[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 == '/' && depth == 0) {
                    parts.add (current.str);
                    current.truncate (0);
                    continue;
                }
            }

            current.append_c (c);
        }

        parts.add (current.str);
        return parts;
    }

    public static string hash_key_type (string type_name) {
        var inner = generic_inner (type_name, "Gee.HashMap<");
        var parts = split_generic_pair (inner);
        return parts[0];
    }

    public static string hash_value_type (string type_name) {
        var inner = generic_inner (type_name, "Gee.HashMap<");
        var parts = split_generic_pair (inner);
        return parts[1];
    }

    public static string generic_inner (string type_name, string prefix) {
        if (!type_name.has_prefix (prefix) || !type_name.has_suffix (">")) {
            return "";
        }
        return type_name.substring (prefix.length, type_name.length - prefix.length - 1);
    }

    public static string[] split_generic_pair (string inner) {
        int depth = 0;
        for (int i = 0; i < inner.length; i++) {
            char c = inner[i];
            if (c == '<') {
                depth++;
            } else if (c == '>') {
                depth--;
            } else if (c == ',' && depth == 0) {
                string[] result = { inner.substring (0, i).strip (), inner.substring (i + 1).strip () };
                return result;
            }
        }
        string[] fallback = { "string", "var" };
        return fallback;
    }

    public static string format_for_type (string type_name) {
        if (type_name == "int") {
            return "%d";
        }
        if (type_name == "double") {
            return "%g";
        }
        return "%s";
    }

    public static bool is_string_literal (string expression) {
        if (expression.length < 2) {
            return false;
        }

        char quote = expression[0];
        if (quote != '\"' && quote != '\'') {
            return false;
        }

        bool escaped = false;
        for (int i = 1; i < expression.length; i++) {
            char c = expression[i];
            if (escaped) {
                escaped = false;
                continue;
            }
            if (c == '\\') {
                escaped = true;
                continue;
            }
            if (c == quote) {
                return i == expression.length - 1;
            }
        }

        return false;
    }

    public static static ArrayList<string> split_top_level_operator (string expression, string op) {
        var parts = new ArrayList<string> ();
        var current = new StringBuilder ();
        bool in_single_quote = false;
        bool in_double_quote = false;
        bool escaped = false;
        int depth = 0;
        int i = 0;
        while (i < expression.length) {
            char c = expression[i];
            if ((in_single_quote || in_double_quote) && escaped) {
                escaped = false;
            } else if ((in_single_quote || in_double_quote) && c == '\\') {
                escaped = true;
            } else 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 (depth == 0 && expression.substring (i).has_prefix (op)) {
                    parts.add (current.str);
                    current.truncate (0);
                    i += op.length;
                    continue;
                }
            }
            current.append_c (c);
            i++;
        }
        parts.add (current.str);
        return parts;
    }

    public static static int find_top_level_operator_index (string expression, string op) {
        bool in_single_quote = false;
        bool in_double_quote = false;
        bool escaped = false;
        int depth = 0;
        for (int i = 0; i < expression.length; i++) {
            char c = expression[i];
            if ((in_single_quote || in_double_quote) && escaped) {
                escaped = false;
            } else if ((in_single_quote || in_double_quote) && c == '\\') {
                escaped = true;
            } else 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 (depth == 0 && expression.substring (i).has_prefix (op)) {
                    if (op == ">" && i > 0 && (expression[i - 1] == '=' || expression[i - 1] == '-')) {
                        continue;
                    }
                    if (op == "-" && i + 1 < expression.length && expression[i + 1] == '>') {
                        continue;
                    }
                    return i;
                }
            }
        }
        return -1;
    }

    public static static bool is_standalone_php_variable_access (string expression) {
        string clean = expression.strip ();
        if (!clean.has_prefix ("$")) {
            return false;
        }
        bool in_single_quote = false;
        bool in_double_quote = false;
        int paren_depth = 0;
        int bracket_depth = 0;
        for (int i = 0; i < clean.length; i++) {
            char c = clean[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 == '(') {
                    paren_depth++;
                } else if (c == ')') {
                    paren_depth--;
                } else if (c == '[') {
                    bracket_depth++;
                } else if (c == ']') {
                    bracket_depth--;
                } else if (paren_depth == 0 && bracket_depth == 0) {
                    if (c == '-' && i + 1 < clean.length && clean[i + 1] == '>') {
                        i++;
                        continue;
                    }
                    if (c == '=' || c == '!' || c == '<' || c == '>' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '.' || c == '?' || c == ':') {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    public static static int find_top_level_double_arrow_index (string expression) {
        bool in_single_quote = false;
        bool in_double_quote = false;
        int depth = 0;
        for (int i = 0; i < expression.length - 1; i++) {
            char c = expression[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 (depth == 0 && c == '=' && expression[i + 1] == '>') {
                    return i;
                }
            }
        }
        return -1;
    }

    public static static int find_top_level_char_index (string expression, char needle) {
        bool in_single_quote = false;
        bool in_double_quote = false;
        int depth = 0;
        for (int i = 0; i < expression.length; i++) {
            char c = expression[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 (depth == 0 && c == needle) {
                    return i;
                }
            }
        }
        return -1;
    }

    public static static ArrayList<string> split_concat (string expression) {
        var parts = new ArrayList<string> ();
        var current = new StringBuilder ();
        bool in_single_quote = false;
        bool in_double_quote = false;
        int depth = 0;

        for (int i = 0; i < expression.length; i++) {
            char c = expression[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 == '.' && depth == 0 && !is_decimal_point (expression, i)) {
                    parts.add (current.str);
                    current.truncate (0);
                    continue;
                }
            }

            current.append_c (c);
        }

        parts.add (current.str);
        return parts;
    }

}
