/**
 * This file is part of the Zephir.
 *
 * (c) Phalcon Team <team@zephir-lang.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code. If you did not receive
 * a copy of the license it is available through the world-wide-web at the
 * following url: https://docs.zephir-lang.com/en/latest/license
 */

#ifndef ZEPHIR_KERNEL_MAIN_H
#define ZEPHIR_KERNEL_MAIN_H

#include <php.h>
#include <Zend/zend_interfaces.h>
#include <ext/spl/spl_exceptions.h>
#include <ext/spl/spl_iterators.h>
#include <Zend/zend_string.h>
#include <Zend/zend.h>

extern zend_string* i_parent;
extern zend_string* i_static;
extern zend_string* i_self;

/* Main macros */
#define PH_DEBUG 0

#define PH_NOISY 256
#define PH_SILENT 1024
#define PH_READONLY 4096

#define PH_NOISY_CC PH_NOISY
#define PH_SILENT_CC PH_SILENT

#define PH_SEPARATE 256
#define PH_COPY 1024
#define PH_CTOR 4096

#ifndef ZEND_ACC_FINAL_CLASS
 #define ZEND_ACC_FINAL_CLASS ZEND_ACC_FINAL
#endif

/* readonly properties (issue #2614) are PHP 8.1+; on 8.0 the flag does not
 * exist, so it degrades to a no-op (the property compiles as a normal typed
 * property, without write-once enforcement). Keeps generated C version-uniform. */
#ifndef ZEND_ACC_READONLY
 #define ZEND_ACC_READONLY 0
#endif

#define SL(str) ZEND_STRL(str)
#define SS(str) ZEND_STRS(str)
#define ISL(str) (zephir_interned_##str), (sizeof(#str)-1)
#define ISS(str) (zephir_interned_##str), (sizeof(#str))

#include <Zend/zend_constants.h>
#include "kernel/exception.h"

/* class/interface registering */
#define ZEPHIR_REGISTER_CLASS(ns, class_name, lower_ns, name, methods, flags)		\
	{																				\
		zend_class_entry ce;														\
		memset(&ce, 0, sizeof(zend_class_entry));									\
		INIT_NS_CLASS_ENTRY(ce, #ns, #class_name, methods);							\
		lower_ns## _ ##name## _ce = zend_register_internal_class(&ce);				\
		if (UNEXPECTED(!lower_ns## _ ##name## _ce)) {								\
			const char *_n = (#ns);													\
			const char *_c = (#class_name);											\
			zend_error(E_ERROR, "%s\\%s: class registration has failed.", _n, _c);	\
			return FAILURE;															\
		}																			\
		lower_ns## _ ##name## _ce->ce_flags |= flags;								\
	}

#define ZEPHIR_REGISTER_CLASS_EX(ns, class_name, lower_ns, lcname, parent_ce, methods, flags) \
	{ \
		zend_class_entry ce; \
		if (!parent_ce) { \
			fprintf(stderr, "Can't register class %s::%s with null parent\n", #ns, #class_name); \
			return FAILURE; \
		} \
		memset(&ce, 0, sizeof(zend_class_entry)); \
		INIT_NS_CLASS_ENTRY(ce, #ns, #class_name, methods); \
		lower_ns## _ ##lcname## _ce = zend_register_internal_class_ex(&ce, parent_ce); \
		if (!lower_ns## _ ##lcname## _ce) { \
			fprintf(stderr, "Zephir Error: Class to extend '%s' was not found when registering class '%s'\n", (parent_ce ? ZSTR_VAL(parent_ce->name) : "(null)"), ZEND_NS_NAME(#ns, #class_name)); \
			return FAILURE; \
		} \
		lower_ns## _ ##lcname## _ce->ce_flags |= flags;  \
	}

/* Registers a real zend trait (ZEND_ACC_TRAIT) so PHP userland can `use` it */
#define ZEPHIR_REGISTER_TRAIT(ns, class_name, lower_ns, name, methods)				\
	{																				\
		zend_class_entry ce;														\
		memset(&ce, 0, sizeof(zend_class_entry));									\
		INIT_NS_CLASS_ENTRY(ce, #ns, #class_name, methods);							\
		lower_ns## _ ##name## _ce = zend_register_internal_class(&ce);				\
		if (UNEXPECTED(!lower_ns## _ ##name## _ce)) {								\
			const char *_n = (#ns);													\
			const char *_c = (#class_name);											\
			zend_error(E_ERROR, "%s\\%s: trait registration has failed.", _n, _c);	\
			return FAILURE;															\
		}																			\
		lower_ns## _ ##name## _ce->ce_flags |= ZEND_ACC_TRAIT;						\
	}

#define ZEPHIR_REGISTER_INTERFACE(ns, classname, lower_ns, name, methods) \
	{ \
		zend_class_entry ce; \
		memset(&ce, 0, sizeof(zend_class_entry)); \
		INIT_NS_CLASS_ENTRY(ce, #ns, #classname, methods); \
		lower_ns## _ ##name## _ce = zend_register_internal_interface(&ce); \
	}

#define ZEPHIR_REGISTER_INTERFACE_EX(ns, classname, lower_ns, lcname, parent_ce, methods) \
	{ \
		zend_class_entry ce; \
		if (!parent_ce) { \
			fprintf(stderr, "Can't register interface %s with null parent\n", ZEND_NS_NAME(#ns, #classname)); \
			return FAILURE; \
		} \
		memset(&ce, 0, sizeof(zend_class_entry)); \
		INIT_NS_CLASS_ENTRY(ce, #ns, #classname, methods); \
		lower_ns## _ ##lcname## _ce = zend_register_internal_interface(&ce); \
		if (parent_ce) { \
			zend_do_inheritance(ce, parent_ce); \
		} \
		if (!lower_ns## _ ##lcname## _ce) { \
			fprintf(stderr, "Can't register interface %s with parent %s\n", ZEND_NS_NAME(#ns, #classname), (parent_ce ? ZSTR_VAL(parent_ce->name) : "(null)")); \
			return FAILURE; \
		} \
	}

/** Return zval with always ctor */
#define RETURN_CTOR(var)        \
	do {                        \
		RETVAL_ZVAL(var, 1, 0); \
		ZEPHIR_MM_RESTORE();    \
		return;                 \
	} while (0)

/** Return zval with always ctor, without restoring the memory stack */
#define RETURN_CTORW(var)       \
	do {                        \
		RETVAL_ZVAL(var, 1, 0); \
		return;                 \
	} while (0)

/** Return zval checking if it's needed to ctor */
#define RETURN_CCTOR(v)            \
	do {                           \
		ZVAL_DUP(return_value, v); \
		ZEPHIR_MM_RESTORE();       \
		return;                    \
	} while (0)

/** Return zval checking if it's needed to ctor, without restoring the memory stack  */
#define RETURN_CCTORW(v)           \
	do {                           \
		ZVAL_DUP(return_value, v); \
		return;                    \
	} while (0)

/** Return zval with always ctor, without restoring the memory stack */
#define RETURN_THISW() \
	RETURN_ZVAL(getThis(), 1, 0);

/** Return this pointer */
#define RETURN_THIS() { \
		RETVAL_ZVAL(getThis(), 1, 0); \
	} \
	ZEPHIR_MM_RESTORE(); \
	return;

#define RETURN_LCTORW(var) RETURN_CCTORW(var);

#define RETURN_LCTOR(var) RETURN_CCTOR(var);

/**
 * Returns a zval in an object member
 */
#define RETURN_MEMBER(object, member_name) \
	zephir_return_property(return_value, object, SL(member_name)); \
	return;

/** Return without change return_value */
#define RETURN_MM()                 { ZEPHIR_MM_RESTORE(); return; }

/** Return null restoring memory frame */
#define RETURN_MM_BOOL(value)       { RETVAL_BOOL(value); ZEPHIR_MM_RESTORE(); return; }

/** Return string restoring memory frame */
#define RETURN_MM_STRING(str)       { RETVAL_STRING(str); ZEPHIR_MM_RESTORE(); return; }
#define RETURN_MM_EMPTY_STRING()    { RETVAL_EMPTY_STRING(); ZEPHIR_MM_RESTORE(); return; }

/** Return zend_string restoring memory frame */
#define RETURN_MM_STR(s)            { RETVAL_STR(s); ZEPHIR_MM_RESTORE(); return; }

/* Return long */
#define RETURN_MM_LONG(value)       { RETVAL_LONG(value); ZEPHIR_MM_RESTORE(); return; }

/* Return double */
#define RETURN_MM_DOUBLE(value)     { RETVAL_DOUBLE(value); ZEPHIR_MM_RESTORE(); return; }

/**
 * Returns a zval in an object member
 */
#define RETURN_MM_MEMBER(object, member_name) \
  zephir_return_property(return_value, object, SL(member_name)); \
  RETURN_MM();

/**
 * Throws a TypeError that matches PHP's userland return-type message format:
 *   "Class\Method(): Return value must be of type <expected>, <actual> returned"
 *
 * Used by RETURN_*_MEMBER_TYPED macros to enforce strict scalar return types
 * on methods that return a runtime value (e.g. `return this->property`) where
 * the static checker in src/Statements/ReturnStatement.php can't prove the
 * type matches. Without this, internal C extensions bypass the engine's
 * return-type verification (which only runs in ZEND_DEBUG builds).
 *
 * See https://github.com/zephir-lang/zephir/issues/1991
 */
static inline void zephir_throw_return_type_error(uint32_t expected_type, zval *retval)
{
	zend_execute_data *ex = EG(current_execute_data);
	const char *expected = zend_get_type_by_const(expected_type);
	const char *actual   = zend_zval_type_name(retval);

	if (ex && ex->func && ex->func->common.function_name) {
		zend_string *fname = ex->func->common.function_name;
		zend_class_entry *scope = ex->func->common.scope;
		if (scope) {
			zend_type_error("%s::%s(): Return value must be of type %s, %s returned",
				ZSTR_VAL(scope->name), ZSTR_VAL(fname), expected, actual);
		} else {
			zend_type_error("%s(): Return value must be of type %s, %s returned",
				ZSTR_VAL(fname), expected, actual);
		}
	} else {
		zend_type_error("Return value must be of type %s, %s returned",
			expected, actual);
	}
}

/**
 * Same as RETURN_MEMBER but verifies that the property's runtime type matches
 * the method's declared return type. Throws TypeError on mismatch.
 * Used when the method body is `return this->prop` and the method declares
 * a strict scalar return type like `-> string`.
 */
#define RETURN_MEMBER_TYPED(object, member_name, expected_type) \
  do { \
    zephir_return_property(return_value, object, SL(member_name)); \
    if (UNEXPECTED(Z_TYPE_P(return_value) != (expected_type))) { \
      zephir_throw_return_type_error((expected_type), return_value); \
      return; \
    } \
    return; \
  } while (0)

/** Memory-grow-aware variant of RETURN_MEMBER_TYPED. */
#define RETURN_MM_MEMBER_TYPED(object, member_name, expected_type) \
  do { \
    zephir_return_property(return_value, object, SL(member_name)); \
    if (UNEXPECTED(Z_TYPE_P(return_value) != (expected_type))) { \
      zephir_throw_return_type_error((expected_type), return_value); \
      ZEPHIR_MM_RESTORE(); \
      return; \
    } \
    RETURN_MM(); \
  } while (0)

#define RETURN_ON_FAILURE(what) \
	do { \
		if (what == FAILURE) { \
			return; \
		} \
	} while (0)

#define RETURN_MM_ON_FAILURE(what) \
	do { \
		if (what == FAILURE) { \
			return; \
		} \
	} while (0)

/** Return null restoring memory frame */
#define RETURN_MM_NULL()            { RETVAL_NULL(); ZEPHIR_MM_RESTORE(); return; }

/* Globals functions */
int zephir_get_global(zval *arr, const char *global, unsigned int global_length);

/* Count */
void zephir_fast_count(zval *result, zval *array);
int zephir_fast_count_ev(zval *array);
int zephir_fast_count_int(zval *value);

int zephir_is_callable(zval *var);
int zephir_is_scalar(zval *var);

int zephir_function_exists(const zval *function_name);
int zephir_function_exists_ex(const char *func_name, unsigned int func_len);

zend_class_entry* zephir_get_internal_ce(const char *class_name, unsigned int class_name_len);

/* types */
void zephir_gettype(zval *return_value, zval *arg);

/* Utils functions */
static inline int zephir_maybe_separate_zval(zval* z)
{
	if (!Z_REFCOUNTED_P(z) || (Z_REFCOUNT_P(z) > 1 && !Z_ISREF_P(z))) {
		if (!(Z_COPYABLE_P(z) && !Z_REFCOUNTED_P(z))) {
			Z_DELREF_P(z);
		}
		zval_copy_ctor_func(z);
		return 1;
	}

	return 0;
}

#define ZEPHIR_SET_SYMBOL(symbol_table, name, value) { \
	Z_TRY_ADDREF_P(value); \
	zend_hash_str_update(symbol_table, name, sizeof(name) - 1, value); \
}

int zephir_is_iterable_ex(zval *arr, int duplicate);

/** Check if an array is iterable or not */
#define zephir_is_iterable(var, duplicate, file, line) \
	if (!zephir_is_iterable_ex(var, duplicate)) { \
		ZEPHIR_THROW_EXCEPTION_DEBUG_STRW(zend_ce_exception, "The argument is not initialized or iterable()", file, line); \
		ZEPHIR_MM_RESTORE(); \
		return; \
	}

/* Fetch Parameters */
int zephir_fetch_parameters(int num_args, int required_args, int optional_args, ...);

/** Low overhead parse/fetch parameters */
#define zephir_fetch_params(memory_grow, required_params, optional_params, ...) \
	if (zephir_fetch_parameters(ZEND_NUM_ARGS(), required_params, optional_params, __VA_ARGS__) == FAILURE) { \
		if (memory_grow) { \
			RETURN_MM_NULL(); \
		} else { \
			RETURN_NULL(); \
		} \
	}

#define zephir_fetch_params_without_memory_grow(required_params, optional_params, ...) \
	if (zephir_fetch_parameters(ZEND_NUM_ARGS(), required_params, optional_params, __VA_ARGS__) == FAILURE) { \
		RETURN_NULL(); \
	}

/* Fetch the fixed (leading) parameters of a variadic method. Unlike
 * zephir_fetch_parameters() this does not reject calls that pass more
 * arguments than declared; the extra arguments are collected separately
 * via zephir_get_args_from(). */
int zephir_fetch_parameters_variadic(int num_args, int required_args, int optional_args, ...);

#define zephir_fetch_params_variadic(memory_grow, required_params, optional_params, ...) \
	if (zephir_fetch_parameters_variadic(ZEND_NUM_ARGS(), required_params, optional_params, __VA_ARGS__) == FAILURE) { \
		if (memory_grow) { \
			RETURN_MM_NULL(); \
		} else { \
			RETURN_NULL(); \
		} \
	}

#define ZEPHIR_CREATE_OBJECT(obj, class_type) \
	{ \
		zend_object *object = zend_objects_new(class_type); \
		ZVAL_UNDEF(obj); \
		ZVAL_OBJ(obj, object); \
		object_properties_init(object, class_type); \
	}

#define ZEPHIR_MAKE_REF(obj) ZVAL_NEW_REF(obj, obj);
#define ZEPHIR_UNREF(obj) ZVAL_UNREF(obj);

#define ZEPHIR_GET_CONSTANT(return_value, const_name) do { \
	zval *_constant_ptr = zend_get_constant_str(SL(const_name)); \
	if (_constant_ptr == NULL) { \
	    zval _null; \
		ZVAL_NULL(&_null); \
		ZVAL_COPY(return_value, &_null); \
		zval_ptr_dtor(&_null); \
	} else { \
	    ZVAL_COPY(return_value, _constant_ptr); \
	} \
} while(0)

#define ZEPHIR_GET_IMKEY(var, it) it->funcs->get_current_key(it, &var);

/* Declare class constants */
int zephir_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value);
int zephir_declare_class_constant_array(zend_class_entry *ce, const char *name, size_t name_length, zval *value);
int zephir_declare_class_constant_null(zend_class_entry *ce, const char *name, size_t name_length);
int zephir_declare_class_constant_long(zend_class_entry *ce, const char *name, size_t name_length, zend_long value);
int zephir_declare_class_constant_bool(zend_class_entry *ce, const char *name, size_t name_length, zend_bool value);
int zephir_declare_class_constant_double(zend_class_entry *ce, const char *name, size_t name_length, double value);
int zephir_declare_class_constant_stringl(zend_class_entry *ce, const char *name, size_t name_length, const char *value, size_t value_length);
int zephir_declare_class_constant_string(zend_class_entry *ce, const char *name, size_t name_length, const char *value);

/* Declare a class property whose default is an array (persisted immutable, e.g. on a trait ce) */
int zephir_declare_property_array(zend_class_entry *ce, const char *name, size_t name_length, zval *value, int access_type);

/* Declare a class property carrying a PHP type (issue #2608). `type_mask` is a
 * MAY_BE_* bitmask (with MAY_BE_NULL folded in for `?type`); when `class_name`
 * is non-NULL the property is a class type resolved lazily by the engine. */
zend_property_info *zephir_declare_typed_property(zend_class_entry *ce, const char *name, size_t name_length, zval *value, int access_type, uint32_t type_mask, const char *class_name, size_t class_name_length);

/* Declare a union-typed class property (issue #2613), e.g. `int | float` or
 * `<A> | <B> | null`. `type_mask` carries the scalar/null MAY_BE_* bits; the
 * `num_classes` class names (0, 1 or many) form the object part of the union. */
zend_property_info *zephir_declare_typed_property_union(zend_class_entry *ce, const char *name, size_t name_length, zval *value, int access_type, uint32_t type_mask, const char **class_names, uint32_t num_classes);

int zephir_is_php_version(unsigned int id);

/** Method declaration for API generation */
#define ZEPHIR_DOC_METHOD(class_name, method)

#ifndef ZEPHIR_RELEASE
#define ZEPHIR_DEBUG_PARAMS , const char *file, int line
#define ZEPHIR_DEBUG_PARAMS_DUMMY , "", 0
#else
#define ZEPHIR_DEBUG_PARAMS , const char *file, int line
#define ZEPHIR_DEBUG_PARAMS_DUMMY , "", 0
#endif

void zephir_get_args(zval* return_value);
void zephir_get_arg(zval* return_value, zend_long idx);

/* Collect the arguments starting at the 0-based index `skip` into an array.
 * Used to populate the array of a variadic parameter from the trailing
 * arguments that follow the fixed (declared) parameters. */
void zephir_get_args_from(zval* return_value, uint32_t skip);

void zephir_module_init();

/**
 * Z_PARAM_ARRAY(dest) expands to a call to zend_parse_arg_array(_arg, &dest, ...).
 * The inline function has taken `zval **dest` since at least PHP 7.0, so the
 * variable passed to the macro must be a `zval *` for `&dest` to have the
 * correct type. Zephir always emits a `<name>_param` companion of type `zval *`
 * for array parameters; we forward that here.
 *
 * Historical note: previous versions of this header conditionally selected
 * between `dest` (a `zval` value) and `dest_ptr` (a `zval *`) based on a
 * config.m4 autoconf probe (`ZEPHIR_ARRAY_PARAM_DOUBLE_PTR`). The `dest`
 * branch was always wrong — the underlying signature has been `zval **dest`
 * since PHP 7.0 — but it only surfaced as a warning once GCC 14 promoted
 * `-Wincompatible-pointer-types` to default-on. Downstream projects that
 * ship a stale `config.m4` (e.g. cphalcon) didn't get the probe defined and
 * fell into the broken branch, producing hundreds of warnings on PHP 8.5
 * (see https://github.com/zephir-lang/zephir/issues/2462).
 *
 * The probe is no longer needed and has been removed from
 * templates/engine/config.m4. The legacy `ZEPHIR_ARRAY_PARAM_DOUBLE_PTR`
 * macro is now a no-op — if a stale generated `config.m4` still emits it,
 * the definition is harmless.
 */
#define ZEPHIR_Z_PARAM_ARRAY(dest, dest_ptr)              Z_PARAM_ARRAY(dest_ptr)
#define ZEPHIR_Z_PARAM_ARRAY_OR_NULL(dest, dest_ptr)      Z_PARAM_ARRAY_OR_NULL(dest_ptr)

#endif /* ZEPHIR_KERNEL_MAIN_H */
