static string vala_escape(string value) {
    var builder = new StringBuilder();
    foreach (char c in value.to_utf8()) {
        switch (c) {
            case '\\': builder.append("\\\\"); break;
            case '"': builder.append("\\\""); break;
            case '\n': builder.append("\\n"); break;
            case '\r': builder.append("\\r"); break;
            case '\t': builder.append("\\t"); break;
            default:
                if ((uint8) c < 0x20) {
                    builder.append_printf("\\x%02x", (uint8) c);
                } else {
                    builder.append_c(c);
                }
                break;
        }
    }
    return builder.str;
}

static string zval_factory(PhpefRuntimeCore.Zval value) throws Error {
    switch (value.kind) {
        case PhpefRuntimeCore.ValueKind.NULL:
            return "PhpefRuntimeCore.Zval.from_null()";
        case PhpefRuntimeCore.ValueKind.BOOL:
            return value.as_bool() ? "PhpefRuntimeCore.Zval.from_bool(true)" : "PhpefRuntimeCore.Zval.from_bool(false)";
        case PhpefRuntimeCore.ValueKind.INT:
            if (value.as_int() == int64.MIN) {
                return "PhpefRuntimeCore.Zval.from_int(int64.MIN)";
            }
            return "PhpefRuntimeCore.Zval.from_int(" + value.as_int().to_string() + ")";
        case PhpefRuntimeCore.ValueKind.FLOAT:
            var float_text = value.as_float().to_string();
            if (float_text == "inf") {
                return "PhpefRuntimeCore.Zval.from_float(double.INFINITY)";
            }
            if (float_text == "-inf") {
                return "PhpefRuntimeCore.Zval.from_float(-double.INFINITY)";
            }
            if (float_text == "nan") {
                return "PhpefRuntimeCore.Zval.from_float(double.NAN)";
            }
            return "PhpefRuntimeCore.Zval.from_float(" + float_text + ")";
        case PhpefRuntimeCore.ValueKind.STRING:
            return "PhpefRuntimeCore.Zval.from_string(\"" + vala_escape(value.as_string() ?? "") + "\")";
        default:
            throw new FileError.FAILED("unsupported constant Zval kind %d".printf((int) value.kind));
    }
}

static PhpefRuntimeCore.Zval? category_by_name(PhpefRuntimeCore.Zval categorized, string wanted_name) {
    foreach (var category_entry in categorized.as_array_entries()) {
        if (category_entry.key != null && category_entry.key.to_php_string() == wanted_name) {
            return category_entry.value;
        }
    }
    return null;
}

static bool string_array_contains(string[] values, string wanted) {
    foreach (var value in values) {
        if (value == wanted) {
            return true;
        }
    }
    return false;
}

int main(string[] args) {
    // phpef-runtime-core currently emits noisy ownership warnings while Vala
    // iterates large arrays returned by as_array_entries(). The data is valid;
    // keep this build generator quiet until core ownership annotations are fixed.
    Log.set_handler("GLib-GObject", LogLevelFlags.LEVEL_CRITICAL, (domain, level, message) => {});

    if (args.length != 2) {
        stderr.printf("usage: %s <output.vala>\n", args[0]);
        return 2;
    }

    try {
        PhpefRuntimeEmbed.engine_start(args);
        var categorized = PhpefRuntimeEmbed.phpfunc_get_defined_constants(PhpefRuntimeCore.Zval.from_bool(true));
        string[] category_names = { "Core", "standard" };
        string[] emitted_names = {};

        var generated = new StringBuilder();
        generated.append("namespace PhpefRuntimeNativeStandard {\n\n");
        generated.append("    // Generated at build time from PhpefRuntimeEmbed.phpfunc_get_defined_constants(true).\n");
        generated.append("    // Categories: Core, standard. Keep constant ownership in runtime packages, not phpefc.\n");
        generated.append("    internal PhpefRuntimeCore.Zval native_standard_constant_by_name(string name) {\n");
        generated.append("        switch (name) {\n");

        foreach (var category_name in category_names) {
            var category = category_by_name(categorized, category_name);
            if (category == null || category.kind != PhpefRuntimeCore.ValueKind.ARRAY) {
                stderr.printf("get_defined_constants(true) did not contain a %s category\n", category_name);
                PhpefRuntimeEmbed.engine_stop();
                return 1;
            }

            var entries = category.as_array_entries();
            if (entries.length == 0) {
                stderr.printf("get_defined_constants(true) %s category was empty\n", category_name);
                PhpefRuntimeEmbed.engine_stop();
                return 1;
            }

            foreach (var constant_entry in entries) {
                if (constant_entry.key == null) {
                    continue;
                }
                var name = constant_entry.key.to_php_string();
                if (string_array_contains(emitted_names, name)) {
                    continue;
                }
                emitted_names += name;
                generated.append("            case \"");
                generated.append(vala_escape(name));
                generated.append("\":\n                return ");
                generated.append(zval_factory(constant_entry.value));
                generated.append(";\n");
            }
        }
        generated.append("        }\n\n");
        generated.append("        return PhpefRuntimeCore.Zval.from_null();\n");
        generated.append("    }\n\n");
        generated.append("}\n");

        FileUtils.set_contents(args[1], generated.str);
        PhpefRuntimeEmbed.engine_stop();
        return 0;
    } catch (Error e) {
        stderr.printf("standard constant generation failed: %s\n", e.message);
        PhpefRuntimeEmbed.engine_stop();
        return 1;
    }
}
