using Gee;

public class PhpToValaPhpStubGenerator : Object {
    private PhpToValaTranspiler transpiler;

    public PhpToValaPhpStubGenerator (PhpToValaTranspiler transpiler) {
        this.transpiler = transpiler;
    }

    public string build () throws Error {
        SourceParts? last_source_parts = transpiler.get_last_source_parts ();
        if (last_source_parts == null) {
            throw new PhpToValaError.UNSUPPORTED ("Cannot emit PHP stub before transpile()");
        }

        SourceParts source_parts = (SourceParts) last_source_parts;
        var output = new StringBuilder ();
        output.append ("<?php\n");
        output.append ("/*\n");
        output.append (" * php2vala generated PHP stub. Declarative only; do not execute as application code.\n");
        foreach (string raw_line in transpiler.get_last_runtime_metadata ().split ("\n")) {
            output.append (" * ");
            output.append (raw_line);
            output.append ("\n");
        }
        output.append_printf (" * php2vala optimizer: %s\n", transpiler.get_last_optimizer_status ());
        output.append (" */\n\n");

        if (source_parts.namespace_name != "") {
            output.append_printf ("namespace %s;\n\n", source_parts.namespace_name);
        }

        foreach (LocalFunction local_function in source_parts.functions) {
            append_function (output, local_function, "");
            output.append ("\n");
        }
        foreach (PhpClass php_class in source_parts.classes) {
            append_doc_comment (output, php_class.doc_comment, "");
            output.append_printf ("class %s {\n", php_class.name);
            foreach (PhpClassConstant constant in php_class.constants) {
                output.append_printf ("    public const %s = %s;\n", constant.name, constant.value);
            }
            if (php_class.constants.size > 0 && php_class.methods.size > 0) {
                output.append ("\n");
            }
            foreach (LocalFunction method in php_class.methods) {
                append_function (output, method, "    ");
            }
            output.append ("}\n\n");
        }

        return output.str;
    }

    private void append_function (StringBuilder output, LocalFunction local_function, string indent) {
        append_doc_comment (output, local_function.doc_comment, indent);
        if (transpiler.should_emit_cross_file_stub_metadata (local_function, indent)) {
            output.append (indent);
            output.append ("/**\n");
            output.append (indent);
            output.append_printf (" * @php2vala-static %s\n", transpiler.file_func_native_function_name (local_function));
            output.append (indent);
            output.append_printf (" * @php2vala-static-return %s\n", transpiler.file_func_infer_native_return_type (local_function));
            output.append (indent);
            output.append_printf (" * @php2vala-zval %s\n", transpiler.file_func_zval_function_name (local_function));
            output.append (indent);
            output.append (" */\n");
        }
        output.append (indent);
        if (indent != "") {
            output.append ("public ");
            if (local_function.is_static) {
                output.append ("static ");
            }
        }
        output.append ("function ");
        output.append (local_function.name);
        output.append (" (");
        output.append (parameter_list (local_function));
        output.append (")");
        output.append (": ");
        output.append (local_function.return_type ?? "mixed");
        output.append (" {}\n");
    }

    private void append_doc_comment (StringBuilder output, string doc_comment, string indent) {
        string clean_doc_comment = doc_comment.strip ();
        if (clean_doc_comment == "") {
            return;
        }
        foreach (string line in clean_doc_comment.split ("\n")) {
            string clean_line = line.strip ();
            output.append (indent);
            if (clean_line.has_prefix ("*")) {
                output.append (" ");
            }
            output.append (clean_line);
            output.append ("\n");
        }
    }

    private string parameter_list (LocalFunction local_function) {
        var output = new StringBuilder ();
        for (int i = 0; i < local_function.parameters.size; i++) {
            LocalParameter parameter = local_function.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;
    }
}
