//! Macros for the `php-ext` crate.
#![allow(clippy::needless_continue)] // TODO: Remove this once darling is updated to remove clippy issues
mod class;
mod constant;
mod enum_;
mod extern_;
mod fastcall;
mod function;
mod helpers;
mod impl_;
mod impl_interface;
mod interface;
mod module;
mod parsing;
mod syn_ext;
mod zval;

use darling::FromMeta;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{
    DeriveInput, ItemConst, ItemEnum, ItemFn, ItemForeignMod, ItemImpl, ItemStruct, ItemTrait,
};

extern crate proc_macro;

// BEGIN DOCS FROM classes.md
/// # `#[php_class]` Attribute
///
/// Structs can be exported to PHP as classes with the `#[php_class]` attribute
/// macro. This attribute derives the `RegisteredClass` trait on your struct, as
/// well as registering the class to be registered with the `#[php_module]`
/// macro.
///
/// ## Options
///
/// There are additional macros that modify the class. These macros **must** be
/// placed underneath the `#[php_class]` attribute.
///
/// - `name` - Changes the name of the class when exported to PHP. The Rust
///   struct name is kept the same. If no name is given, the name of the struct
///   is used. Useful for namespacing classes.
/// - `change_case` - Changes the case of the class name when exported to PHP.
/// - `readonly` - Marks the class as readonly (PHP 8.2+). All properties in a
///   readonly class are implicitly readonly.
/// - `flags` - Sets class flags using `ClassFlags`, e.g. `#[php(flags =
///   ClassFlags::Final)]` for a final class.
/// - `#[php(extends(...))]` - Sets the parent class of the class. Can only be
///   used once. Two forms are supported:
///   - Simple type form: `#[php(extends(MyBaseClass))]` - For Rust-defined
///     classes that implement `RegisteredClass`.
///   - Explicit form: `#[php(extends(ce = ce_fn, stub = "ParentClass"))]` - For
///     built-in PHP classes. `ce_fn` must be a function with the signature
///     `fn() -> &'static ClassEntry`.
/// - `#[php(implements(...))]` - Implements the given interface on the class.
///   Can be used multiple times. Two forms are supported:
///   - Simple type form: `#[php(implements(MyInterface))]` — For Rust-defined
///     interfaces that implement `RegisteredClass`.
///   - Explicit form: `#[php(implements(ce = ce_fn, stub = "InterfaceName"))]`
///     — For built-in PHP interfaces. `ce_fn` must be a valid function with the
///     signature `fn() -> &'static ClassEntry`.
///
/// You may also use the `#[php(prop)]` attribute on a struct field to use the
/// field as a PHP property. By default, the field will be accessible from PHP
/// publicly with the same name as the field. Property types must implement
/// `IntoZval` and `FromZval`.
///
/// You can customize properties with these options:
///
/// - `name` - Allows you to rename the property, e.g. `#[php(prop, name =
///   "new_name")]`
/// - `change_case` - Allows you to rename the property using rename rules, e.g.
///   `#[php(prop, change_case = PascalCase)]`
/// - `static` - Makes the property static (shared across all instances), e.g.
///   `#[php(prop, static)]`
/// - `flags` - Sets property visibility flags, e.g. `#[php(prop, flags =
///   ext_php_rs::flags::PropertyFlags::Private)]`
///
/// ### Known limitation: `#[php(prop)]` on owned refcounted types accessed via
/// `Exception::getMessage`-style C methods leaks one `zend_string` per call.
///
/// The generated read-property handler writes a fresh `zend_string` with
/// refcount=1 into the `rv` slot via `set_zval`. PHP's `Exception::getMessage`
/// (and siblings such as `getFile`) read this with `zval_get_string + RETURN_STR`,
/// which addrefs to 2 and transfers the pointer to `return_value` without
/// changing the refcount. The stack `rv` then goes out of scope without
/// `zval_ptr_dtor`, orphaning one refcount per call.
///
/// This affects any `#[php_class]` extending `\Exception` with a `#[php(prop)]`
/// field whose type allocates a `zend_string` (e.g. `String`, `Vec<u8>` when
/// converted to a binary string, or any `IntoZval` impl producing `IS_STRING_EX`)
/// — most commonly when the field shadows the parent `\Exception::$message`.
/// Direct property access (`$obj->field`) via the `FETCH_OBJ_R` opcode is
/// **not** affected because the bytecode handler properly consumes the rv ref.
///
/// **Workarounds, in order of preference:**
///
/// 1. **Do not shadow the inherited property name.** Rename the field
///    (e.g. `payload` instead of `message`) and expose it through a
///    `#[php_method]` getter. The method-return path is not affected by
///    this leak.
///    Note: `\Exception::getMessage` is `final` in PHP, so overriding it
///    directly via `#[php_method] fn get_message(...)` is rejected at
///    class registration.
///
/// 2. **Write the value into the parent's real property slot via
///    `zend_update_property_stringl`** (raw FFI). PHP's `getMessage`
///    then reads from real storage through `zend_std_read_property`,
///    bypassing the leaky `rv` path entirely. This requires dropping
///    `#[php(prop)]` from the shadow field and populating the parent
///    slot at construction time. See `biscuit-php` (`src/errors.rs`)
///    for a worked example.
///
/// Tracked by the
/// `prop_string_field_does_not_leak_on_repeated_get_message` regression test.
///
/// ## Restrictions
///
/// ### No lifetime parameters
///
/// Rust lifetimes are used by the Rust compiler to reason about a program's
/// memory safety. They are a compile-time only concept;
/// there is no way to access Rust lifetimes at runtime from a dynamic language
/// like PHP.
///
/// As soon as Rust data is exposed to PHP,
/// there is no guarantee which the Rust compiler can make on how long the data
/// will live. PHP is a reference-counted language and those references can be
/// held for an arbitrarily long time, which is untraceable by the Rust
/// compiler. The only possible way to express this correctly is to require that
/// any `#[php_class]` does not borrow data for any lifetime shorter than the
/// `'static` lifetime, i.e. the `#[php_class]` cannot have any lifetime
/// parameters.
///
/// When you need to share ownership of data between PHP and Rust,
/// instead of using borrowed references with lifetimes, consider using
/// reference-counted smart pointers such as [Arc](https://doc.rust-lang.org/std/sync/struct.Arc.html).
///
/// ### No generic parameters
///
/// A Rust struct `Foo<T>` with a generic parameter `T` generates new compiled
/// implementations each time it is used with a different concrete type for `T`.
/// These new implementations are generated by the compiler at each usage site.
/// This is incompatible with wrapping `Foo` in PHP,
/// where there needs to be a single compiled implementation of `Foo` which is
/// integrated with the PHP interpreter.
///
/// ## Example
///
/// This example creates a PHP class `Human`, adding a PHP property `address`.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_class]
/// pub struct Human {
///     name: String,
///     age: i32,
///     #[php(prop)]
///     address: String,
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.class::<Human>()
/// }
/// # fn main() {}
/// ```
///
/// Create a custom exception `RedisException`, which extends `Exception`, and
/// put it in the `Redis\Exception` namespace:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{
///     prelude::*,
///     exception::PhpException,
///     zend::ce
/// };
///
/// #[php_class]
/// #[php(name = "Redis\\Exception\\RedisException")]
/// #[php(extends(ce = ce::exception, stub = "\\Exception"))]
/// #[derive(Default)]
/// pub struct RedisException;
///
/// // Throw our newly created exception
/// #[php_function]
/// pub fn throw_exception() -> PhpResult<i32> {
///     Err(PhpException::from_class::<RedisException>("Not good!".into()))
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .class::<RedisException>()
///         .function(wrap_function!(throw_exception))
/// }
/// # fn main() {}
/// ```
///
/// ### Extending a Rust-defined Class
///
/// When extending another Rust-defined class, you can use the simpler type
/// syntax:
///
/// ```rust,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_class]
/// #[derive(Default)]
/// pub struct Animal;
///
/// #[php_impl]
/// impl Animal {
///     pub fn speak(&self) -> &'static str {
///         "..."
///     }
/// }
///
/// #[php_class]
/// #[php(extends(Animal))]
/// #[derive(Default)]
/// pub struct Dog;
///
/// #[php_impl]
/// impl Dog {
///     pub fn speak(&self) -> &'static str {
///         "Woof!"
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .class::<Animal>()
///         .class::<Dog>()
/// }
/// # fn main() {}
/// ```
///
/// #### Sharing Methods Between Parent and Child Classes
///
/// When both parent and child are Rust-defined classes, methods defined only in
/// the parent won't automatically work when called on a child instance. This is
/// because each Rust type has its own object handlers.
///
/// The recommended workaround is to use a Rust trait for shared behavior:
///
/// ```rust,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// /// Trait for shared behavior
/// trait AnimalBehavior {
///     fn speak(&self) -> &'static str {
///         "..."
///     }
/// }
///
/// #[php_class]
/// #[derive(Default)]
/// pub struct Animal;
///
/// impl AnimalBehavior for Animal {}
///
/// #[php_impl]
/// impl Animal {
///     pub fn speak(&self) -> &'static str {
///         AnimalBehavior::speak(self)
///     }
/// }
///
/// #[php_class]
/// #[php(extends(Animal))]
/// #[derive(Default)]
/// pub struct Dog;
///
/// impl AnimalBehavior for Dog {
///     fn speak(&self) -> &'static str {
///         "Woof!"
///     }
/// }
///
/// #[php_impl]
/// impl Dog {
///     // Re-export the method so it works on Dog instances
///     pub fn speak(&self) -> &'static str {
///         AnimalBehavior::speak(self)
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .class::<Animal>()
///         .class::<Dog>()
/// }
/// # fn main() {}
/// ```
///
/// This pattern ensures that:
/// - `$animal->speak()` returns `"..."`
/// - `$dog->speak()` returns `"Woof!"`
/// - `$dog instanceof Animal` is `true`
///
/// ## Implementing an Interface
///
/// To implement an interface, use `#[php(implements(...))]`. For built-in PHP
/// interfaces, use the explicit form with `ce` and `stub`. For Rust-defined
/// interfaces, you can use the simple type form. The following example implements [`ArrayAccess`](https://www.php.net/manual/en/class.arrayaccess.php):
///
/// ````rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{
///     prelude::*,
///     exception::PhpResult,
///     types::Zval,
///     zend::ce,
/// };
///
/// #[php_class]
/// #[php(implements(ce = ce::arrayaccess, stub = "\\ArrayAccess"))]
/// #[derive(Default)]
/// pub struct EvenNumbersArray;
///
/// /// Returns `true` if the array offset is an even number.
/// /// Usage:
/// /// ```php
/// /// $arr = new EvenNumbersArray();
/// /// var_dump($arr[0]); // true
/// /// var_dump($arr[1]); // false
/// /// var_dump($arr[2]); // true
/// /// var_dump($arr[3]); // false
/// /// var_dump($arr[4]); // true
/// /// var_dump($arr[5] = true); // Fatal error:  Uncaught Exception: Setting values is not supported
/// /// ```
/// #[php_impl]
/// impl EvenNumbersArray {
///     pub fn __construct() -> EvenNumbersArray {
///         EvenNumbersArray {}
///     }
///     // We need to use `Zval` because ArrayAccess needs $offset to be a `mixed`
///     pub fn offset_exists(&self, offset: &'_ Zval) -> bool {
///         offset.is_long()
///     }
///     pub fn offset_get(&self, offset: &'_ Zval) -> PhpResult<bool> {
///         let integer_offset = offset.long().ok_or("Expected integer offset")?;
///         Ok(integer_offset % 2 == 0)
///     }
///     pub fn offset_set(&mut self, _offset: &'_ Zval, _value: &'_ Zval) -> PhpResult {
///         Err("Setting values is not supported".into())
///     }
///     pub fn offset_unset(&mut self, _offset: &'_ Zval) -> PhpResult {
///         Err("Setting values is not supported".into())
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.class::<EvenNumbersArray>()
/// }
/// # fn main() {}
/// ````
///
/// ## Static Properties
///
/// Static properties are shared across all instances of a class. Use
/// `#[php(prop, static)]` to declare a static property. Unlike instance
/// properties, static properties are managed entirely by PHP and do not use
/// Rust property handlers.
///
/// You can specify a default value using the `default` attribute:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
/// use ext_php_rs::class::RegisteredClass;
///
/// #[php_class]
/// pub struct Counter {
///     #[php(prop)]
///     pub instance_value: i32,
///     #[php(prop, static, default = 0)]
///     pub count: i32,
///     #[php(prop, static, flags = ext_php_rs::flags::PropertyFlags::Private)]
///     pub internal_state: String,
/// }
///
/// #[php_impl]
/// impl Counter {
///     pub fn __construct(value: i32) -> Self {
///         Self {
///             instance_value: value,
///             count: 0,
///             internal_state: String::new(),
///         }
///     }
///
///     /// Increment the static counter from Rust
///     pub fn increment() {
///         let ce = Self::get_metadata().ce();
///         let current: i64 = ce.get_static_property("count").unwrap_or(0);
///         ce.set_static_property("count", current + 1).unwrap();
///     }
///
///     /// Get the current count
///     pub fn get_count() -> i64 {
///         let ce = Self::get_metadata().ce();
///         ce.get_static_property("count").unwrap_or(0)
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.class::<Counter>()
/// }
/// # fn main() {}
/// ```
///
/// From PHP, you can access static properties directly on the class:
///
/// ```php
/// // No need to initialize - count already has default value of 0
/// Counter::increment();
/// Counter::increment();
/// echo Counter::$count; // 2
/// echo Counter::getCount(); // 2
/// ```
///
/// ## Abstract Classes
///
/// Abstract classes cannot be instantiated directly and may contain abstract
/// methods that must be implemented by subclasses. Use `#[php(flags =
/// ClassFlags::Abstract)]` to declare an abstract class:
///
/// ```rust,ignore
/// use ext_php_rs::prelude::*;
/// use ext_php_rs::flags::ClassFlags;
///
/// #[php_class]
/// #[php(flags = ClassFlags::Abstract)]
/// pub struct AbstractAnimal;
///
/// #[php_impl]
/// impl AbstractAnimal {
///     // Protected constructor for subclasses
///     #[php(vis = "protected")]
///     pub fn __construct() -> Self {
///         Self
///     }
///
///     // Abstract method - must be implemented by subclasses.
///     // Body is never called; use unimplemented!() as a placeholder.
///     #[php(abstract)]
///     pub fn speak(&self) -> String {
///         unimplemented!()
///     }
///
///     // Concrete method - inherited by subclasses
///     pub fn breathe(&self) {
///         println!("Breathing...");
///     }
/// }
/// ```
///
/// From PHP, you can extend this abstract class:
///
/// ```php
/// class Dog extends AbstractAnimal {
///     public function __construct() {
///         parent::__construct();
///     }
///
///     public function speak(): string {
///         return "Woof!";
///     }
/// }
///
/// $dog = new Dog();
/// echo $dog->speak(); // "Woof!"
/// $dog->breathe();    // "Breathing..."
///
/// // This would cause an error:
/// // $animal = new AbstractAnimal(); // Cannot instantiate abstract class
/// ```
///
/// See the [impl documentation](./impl.md#abstract-methods) for more details on
/// abstract methods.
///
/// ## Final Classes
///
/// Final classes cannot be extended. Use `#[php(flags = ClassFlags::Final)]` to
/// declare a final class:
///
/// ```rust,ignore
/// use ext_php_rs::prelude::*;
/// use ext_php_rs::flags::ClassFlags;
///
/// #[php_class]
/// #[php(flags = ClassFlags::Final)]
/// pub struct FinalClass;
/// ```
///
/// ## Readonly Classes (PHP 8.2+)
///
/// PHP 8.2 introduced [readonly classes](https://www.php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.readonly),
/// where all properties are implicitly readonly. You can create a readonly
/// class using the `#[php(readonly)]` attribute:
///
/// ```rust,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_class]
/// #[php(readonly)]
/// pub struct ImmutablePoint {
///     x: f64,
///     y: f64,
/// }
///
/// #[php_impl]
/// impl ImmutablePoint {
///     pub fn __construct(x: f64, y: f64) -> Self {
///         Self { x, y }
///     }
///
///     pub fn get_x(&self) -> f64 {
///         self.x
///     }
///
///     pub fn get_y(&self) -> f64 {
///         self.y
///     }
///
///     pub fn distance_from_origin(&self) -> f64 {
///         (self.x * self.x + self.y * self.y).sqrt()
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.class::<ImmutablePoint>()
/// }
/// # fn main() {}
/// ```
///
/// From PHP:
///
/// ```php
/// $point = new ImmutablePoint(3.0, 4.0);
/// echo $point->getX(); // 3.0
/// echo $point->getY(); // 4.0
/// echo $point->distanceFromOrigin(); // 5.0
///
/// // On PHP 8.2+, you can verify the class is readonly:
/// $reflection = new ReflectionClass(ImmutablePoint::class);
/// var_dump($reflection->isReadOnly()); // true
/// ```
///
/// The `readonly` attribute is compatible with other class attributes:
///
/// ```rust,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
/// use ext_php_rs::flags::ClassFlags;
///
/// // Readonly + Final class
/// #[php_class]
/// #[php(readonly)]
/// #[php(flags = ClassFlags::Final)]
/// pub struct FinalImmutableData {
///     value: String,
/// }
/// # fn main() {}
/// ```
///
/// **Note:** The `readonly` attribute requires PHP 8.2 or later. Using it when
/// compiling against an earlier PHP version will result in a compile error.
///
/// ### Conditional Compilation for Multi-Version Support
///
/// If your extension needs to support both PHP 8.1 and PHP 8.2+, you can use
/// conditional compilation to only enable readonly on supported versions.
///
/// First, add `ext-php-rs-build` as a build dependency in your `Cargo.toml`:
///
/// ```toml
/// [build-dependencies]
/// ext-php-rs-build = "0.1"
/// anyhow = "1"
/// ```
///
/// Then create a `build.rs` that detects the PHP version and emits cfg flags:
///
/// ```rust,ignore
/// use ext_php_rs_build::{find_php, PHPInfo, ApiVersion, emit_php_cfg_flags, emit_check_cfg};
///
/// fn main() -> anyhow::Result<()> {
///     let php = find_php()?;
///     let info = PHPInfo::get(&php)?;
///     let version: ApiVersion = info.zend_version()?.try_into()?;
///
///     emit_check_cfg();
///     emit_php_cfg_flags(version);
///     Ok(())
/// }
/// ```
///
/// Now you can use `#[cfg(php82)]` to conditionally apply the readonly
/// attribute:
///
/// ```rust,ignore
/// #[php_class]
/// #[cfg_attr(php82, php(readonly))]
/// pub struct MaybeReadonlyClass {
///     value: String,
/// }
/// ```
///
/// The `ext-php-rs-build` crate provides several useful utilities:
///
/// - `find_php()` - Locates the PHP executable (respects the `PHP` env var)
/// - `PHPInfo::get()` - Runs `php -i` and parses the output
/// - `ApiVersion` - Enum representing PHP versions (Php80, Php81, Php82, etc.)
/// - `emit_php_cfg_flags()` - Emits `cargo:rustc-cfg=phpXX` for all supported
///   versions
/// - `emit_check_cfg()` - Emits check-cfg to avoid unknown cfg warnings
///
/// This is **optional** - if your extension only targets PHP 8.2+, you can use
/// `#[php(readonly)]` directly without any build script setup.
///
/// ## Cloning
///
/// PHP's native `clone` operator is supported for `#[php_class]` structs that
/// derive `Clone`. Add `#[derive(Clone)]` to your struct and the cloned PHP
/// object will contain a proper copy of the Rust data:
///
/// ```rust,ignore
/// use ext_php_rs::prelude::*;
///
/// #[php_class]
/// #[derive(Clone)]
/// pub struct Style {
///     #[php(prop)]
///     pub font_size: f64,
///     #[php(prop)]
///     pub color: String,
/// }
///
/// #[php_impl]
/// impl Style {
///     pub fn __construct(font_size: f64, color: String) -> Self {
///         Self { font_size, color }
///     }
/// }
/// ```
///
/// ```php
/// $style = new Style(12.0, 'red');
/// $copy = clone $style;
///
/// $copy->fontSize = 16.0;
/// echo $style->fontSize;  // 12.0 — original is unchanged
/// ```
///
/// Structs that do **not** derive `Clone` will throw an error when cloned:
///
/// ```php
/// // If MyClass doesn't #[derive(Clone)]:
/// $obj = new MyClass();
/// $copy = clone $obj; // Error: Trying to clone an uncloneable object of class MyClass
/// ```
///
/// ## Implementing Iterator
///
/// To make a Rust class usable with PHP's `foreach` loop, implement the
/// [`Iterator`](https://www.php.net/manual/en/class.iterator.php) interface.
/// This requires implementing five methods: `current()`, `key()`, `next()`,
/// `rewind()`, and `valid()`.
///
/// The following example creates a `RangeIterator` that iterates over a range
/// of integers:
///
/// ````rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{prelude::*, zend::ce};
///
/// #[php_class]
/// #[php(implements(ce = ce::iterator, stub = "\\Iterator"))]
/// pub struct RangeIterator {
///     start: i64,
///     end: i64,
///     current: i64,
///     index: i64,
/// }
///
/// #[php_impl]
/// impl RangeIterator {
///     /// Create a new range iterator from start to end (inclusive).
///     pub fn __construct(start: i64, end: i64) -> Self {
///         Self {
///             start,
///             end,
///             current: start,
///             index: 0,
///         }
///     }
///
///     /// Return the current element.
///     pub fn current(&self) -> i64 {
///         self.current
///     }
///
///     /// Return the key of the current element.
///     pub fn key(&self) -> i64 {
///         self.index
///     }
///
///     /// Move forward to next element.
///     pub fn next(&mut self) {
///         self.current += 1;
///         self.index += 1;
///     }
///
///     /// Rewind the Iterator to the first element.
///     pub fn rewind(&mut self) {
///         self.current = self.start;
///         self.index = 0;
///     }
///
///     /// Checks if current position is valid.
///     pub fn valid(&self) -> bool {
///         self.current <= self.end
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.class::<RangeIterator>()
/// }
/// # fn main() {}
/// ````
///
/// Using the iterator in PHP:
///
/// ```php
/// <?php
///
/// $range = new RangeIterator(1, 5);
///
/// // Use with foreach
/// foreach ($range as $key => $value) {
///     echo "$key => $value\n";
/// }
/// // Output:
/// // 0 => 1
/// // 1 => 2
/// // 2 => 3
/// // 3 => 4
/// // 4 => 5
///
/// // Works with iterator functions
/// $arr = iterator_to_array(new RangeIterator(10, 12));
/// // [0 => 10, 1 => 11, 2 => 12]
///
/// $count = iterator_count(new RangeIterator(1, 100));
/// // 100
/// ```
///
/// ### Iterator with Mixed Types
///
/// You can return different types for keys and values. The following example
/// uses string keys:
///
/// ````rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{prelude::*, zend::ce};
///
/// #[php_class]
/// #[php(implements(ce = ce::iterator, stub = "\\Iterator"))]
/// pub struct MapIterator {
///     keys: Vec<String>,
///     values: Vec<String>,
///     index: usize,
/// }
///
/// #[php_impl]
/// impl MapIterator {
///     pub fn __construct() -> Self {
///         Self {
///             keys: vec!["first".into(), "second".into(), "third".into()],
///             values: vec!["one".into(), "two".into(), "three".into()],
///             index: 0,
///         }
///     }
///
///     pub fn current(&self) -> Option<String> {
///         self.values.get(self.index).cloned()
///     }
///
///     pub fn key(&self) -> Option<String> {
///         self.keys.get(self.index).cloned()
///     }
///
///     pub fn next(&mut self) {
///         self.index += 1;
///     }
///
///     pub fn rewind(&mut self) {
///         self.index = 0;
///     }
///
///     pub fn valid(&self) -> bool {
///         self.index < self.keys.len()
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.class::<MapIterator>()
/// }
/// # fn main() {}
/// ````
///
/// ```php
/// <?php
///
/// $map = new MapIterator();
/// foreach ($map as $key => $value) {
///     echo "$key => $value\n";
/// }
/// // Output:
/// // first => one
/// // second => two
/// // third => three
/// ```
// END DOCS FROM classes.md
#[proc_macro_attribute]
pub fn php_class(args: TokenStream, input: TokenStream) -> TokenStream {
    php_class_internal(args.into(), input.into()).into()
}

#[allow(clippy::needless_pass_by_value)]
fn php_class_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemStruct);
    if !args.is_empty() {
        return err!(input => "`#[php_class(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
    }

    class::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

// BEGIN DOCS FROM enum.md
/// # `#[php_enum]` Attribute
///
/// Enums can be exported to PHP as enums with the `#[php_enum]` attribute
/// macro. This attribute derives the `RegisteredClass` and `PhpEnum` traits on
/// your enum. To register the enum use the `enumeration::<EnumName>()` method
/// on the `ModuleBuilder` in the `#[php_module]` macro.
///
/// ## Options
///
/// The `#[php_enum]` attribute can be configured with the following options:
/// - `#[php(name = "EnumName")]` or `#[php(change_case = snake_case)]`: Sets
///   the name of the enum in PHP. The default is the `PascalCase` name of the
///   enum.
/// - `#[php(allow_native_discriminants)]`: Allows the use of native Rust
///   discriminants (e.g., `Hearts = 1`).
///
/// The cases of the enum can be configured with the following options:
/// - `#[php(name = "CaseName")]` or `#[php(change_case = snake_case)]`: Sets
///   the name of the enum case in PHP. The default is the `PascalCase` name of
///   the case.
/// - `#[php(value = "value")]` or `#[php(value = 123)]`: Sets the discriminant
///   value for the enum case. This can be a string or an integer. If not set,
///   the case will be exported as a simple enum case without a discriminant.
///
/// ### Example
///
/// This example creates a PHP enum `Suit`.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_enum]
/// pub enum Suit {
///     Hearts,
///     Diamonds,
///     Clubs,
///     Spades,
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.enumeration::<Suit>()
/// }
/// # fn main() {}
/// ```
///
/// ## Backed Enums
/// Enums can also be backed by either `i64` or `&'static str`. Those values can
/// be set using the `#[php(value = "value")]` or `#[php(value = 123)]`
/// attributes on the enum variants.
///
/// All variants must have a value of the same type, either all `i64` or all
/// `&'static str`.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_enum]
/// pub enum Suit {
///     #[php(value = "hearts")]
///     Hearts,
///     #[php(value = "diamonds")]
///     Diamonds,
///     #[php(value = "clubs")]
///     Clubs,
///     #[php(value = "spades")]
///     Spades,
/// }
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.enumeration::<Suit>()
/// }
/// # fn main() {}
/// ```
///
/// ### 'Native' Discriminators
/// Native rust discriminants are currently not supported and will not be
/// exported to PHP.
///
/// To avoid confusion a compiler error will be raised if you try to use a
/// native discriminant. You can ignore this error by adding the
/// `#[php(allow_native_discriminants)]` attribute to your enum.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_enum]
/// #[php(allow_native_discriminants)]
/// pub enum Suit {
///     Hearts = 1,
///     Diamonds = 2,
///     Clubs = 3,
///     Spades = 4,
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.enumeration::<Suit>()
/// }
/// # fn main() {}
/// ```
///
///
/// TODO: Add backed enums example
// END DOCS FROM enum.md
#[proc_macro_attribute]
pub fn php_enum(args: TokenStream, input: TokenStream) -> TokenStream {
    php_enum_internal(args.into(), input.into()).into()
}

fn php_enum_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemEnum);

    enum_::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

// BEGIN DOCS FROM interface.md
/// # `#[php_interface]` Attribute
///
/// You can export a `Trait` block to PHP. This exports all methods as well as
/// constants to PHP on the interface. Trait method SHOULD NOT contain default
/// implementations, as these are not supported in PHP interfaces.
///
/// ## Options
///
/// By default all constants are renamed to `UPPER_CASE` and all methods are
/// renamed to `camelCase`. This can be changed by passing the
/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
/// the `impl` block. The options are:
///
/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
///   case.
/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
///   snake case.
///
/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
/// for a list of all available cases.
///
/// ## Methods
///
/// See the [`php_impl`](./impl.md#)
///
/// ## Constants
///
/// See the [`php_impl`](./impl.md#)
///
/// ## Example
///
/// Define an example trait with methods and constant:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{prelude::*, types::ZendClassObject};
///
///
/// #[php_interface]
/// #[php(name = "Rust\\TestInterface")]
/// trait Test {
///     const TEST: &'static str = "TEST";
///
///     fn co();
///
///     #[php(defaults(value = 0))]
///     fn set_value(&mut self, value: i32);
/// }
///
/// #[php_module]
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .interface::<PhpInterfaceTest>()
/// }
///
/// # fn main() {}
/// ```
///
/// Using our newly created interface in PHP:
///
/// ```php
/// <?php
///
/// assert(interface_exists("Rust\TestInterface"));
///
/// class B implements Rust\TestInterface {
///
///     public static function co() {}
///
///     public function setValue(?int $value = 0) {
///
///     }
/// }
/// ```
///
/// ## Interface Inheritance
///
/// PHP interfaces can extend other interfaces. You can achieve this in two
/// ways:
///
/// ### Using `#[php(extends(...))]`
///
/// Use the `extends` attribute to extend a built-in PHP interface or another
/// Rust-defined interface.
///
/// For built-in PHP interfaces, use the explicit form:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
/// use ext_php_rs::zend::ce;
///
/// #[php_interface]
/// #[php(extends(ce = ce::throwable, stub = "\\Throwable"))]
/// #[php(name = "MyException")]
/// trait MyExceptionInterface {
///     fn get_error_code(&self) -> i32;
/// }
///
/// # fn main() {}
/// ```
///
/// For Rust-defined interfaces, you can use the simpler type syntax:
///
/// ```rust,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_interface]
/// trait BaseInterface {
///     fn base_method(&self) -> i32;
/// }
///
/// #[php_interface]
/// #[php(extends(BaseInterface))]
/// trait ExtendedInterface {
///     fn extended_method(&self) -> String;
/// }
///
/// # fn main() {}
/// ```
///
/// ### Using Rust Trait Bounds
///
/// You can also use Rust's trait bound syntax. When a trait marked with
/// `#[php_interface]` has supertraits, the PHP interface will automatically
/// extend those parent interfaces:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_interface]
/// #[php(name = "Rust\\ParentInterface")]
/// trait ParentInterface {
///     fn parent_method(&self) -> String;
/// }
///
/// // ChildInterface extends ParentInterface in PHP
/// #[php_interface]
/// #[php(name = "Rust\\ChildInterface")]
/// trait ChildInterface: ParentInterface {
///     fn child_method(&self) -> String;
/// }
///
/// #[php_module]
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .interface::<PhpInterfaceParentInterface>()
///         .interface::<PhpInterfaceChildInterface>()
/// }
///
/// # fn main() {}
/// ```
///
/// In PHP:
///
/// ```php
/// <?php
///
/// // ChildInterface extends ParentInterface
/// assert(is_a('Rust\ChildInterface', 'Rust\ParentInterface', true));
/// ```
///
/// # `#[php_impl_interface]` Attribute
///
/// The `#[php_impl_interface]` attribute allows a Rust class to implement a
/// custom PHP interface defined with `#[php_interface]`. This creates a
/// relationship where PHP's `instanceof` and `is_a()` recognize the
/// implementation.
///
/// **Key feature**: The macro automatically registers the trait methods as PHP
/// methods on the class. You don't need to duplicate them in a separate
/// `#[php_impl]` block.
///
/// ## Example
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// // Define a custom interface
/// #[php_interface]
/// #[php(name = "Rust\\Greetable")]
/// trait Greetable {
///     fn greet(&self) -> String;
/// }
///
/// // Define a class
/// #[php_class]
/// #[php(name = "Rust\\Greeter")]
/// pub struct Greeter {
///     name: String,
/// }
///
/// #[php_impl]
/// impl Greeter {
///     pub fn __construct(name: String) -> Self {
///         Self { name }
///     }
///
///     // Note: No need to add greet() here - it's automatically
///     // registered by #[php_impl_interface] below
/// }
///
/// // Implement the interface for the class
/// // This automatically registers greet() as a PHP method
/// #[php_impl_interface]
/// impl Greetable for Greeter {
///     fn greet(&self) -> String {
///         format!("Hello, {}!", self.name)
///     }
/// }
///
/// #[php_module]
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .interface::<PhpInterfaceGreetable>()
///         .class::<Greeter>()
/// }
///
/// # fn main() {}
/// ```
///
/// Using in PHP:
///
/// ```php
/// <?php
///
/// $greeter = new Rust\Greeter("World");
///
/// // instanceof works
/// assert($greeter instanceof Rust\Greetable);
///
/// // is_a() works
/// assert(is_a($greeter, 'Rust\Greetable'));
///
/// // The greet() method is available (registered by #[php_impl_interface])
/// echo $greeter->greet(); // Output: Hello, World!
///
/// // Can be used as type hint
/// function greet(Rust\Greetable $obj): void {
///     echo $obj->greet();
/// }
///
/// greet($greeter);
/// ```
///
/// ## When to Use
///
/// - Use `#[php_impl_interface]` for custom interfaces you define with
///   `#[php_interface]`
/// - Use `#[php(implements(ce = ...))]` on `#[php_class]` for built-in PHP
///   interfaces like `Iterator`, `ArrayAccess`, `Countable`, etc.
///
/// See the [Classes documentation](./classes.md#implementing-an-interface) for
/// examples of implementing built-in interfaces.
///
/// ## Cross-Crate Support
///
/// The `#[php_impl_interface]` macro supports cross-crate interface discovery
/// via the [`inventory`](https://crates.io/crates/inventory) crate. This means you can define
/// an interface in one crate and implement it in another crate, and the
/// implementation will be automatically discovered at link time.
///
/// ### Example: Defining an Interface in a Library Crate
///
/// First, create a library crate that defines the interface:
///
/// ```toml
/// # my-interfaces/Cargo.toml
/// [package]
/// name = "my-interfaces"
/// version = "0.1.0"
///
/// [dependencies]
/// ext-php-rs = "0.15"
/// ```
///
/// ```rust,no_run,ignore,ignore
/// // my-interfaces/src/lib.rs
/// use ext_php_rs::prelude::*;
///
/// /// A serializable interface that can convert objects to JSON.
/// #[php_interface]
/// #[php(name = "MyInterfaces\\Serializable")]
/// pub trait Serializable {
///     fn to_json(&self) -> String;
/// }
///
/// // Re-export the generated PHP interface struct for consumers
/// pub use PhpInterfaceSerializable;
/// ```
///
/// ### Example: Implementing the Interface in Another Crate
///
/// Now create your extension crate that implements the interface:
///
/// ```toml
/// # my-extension/Cargo.toml
/// [package]
/// name = "my-extension"
/// version = "0.1.0"
///
/// [lib]
/// crate-type = ["cdylib"]
///
/// [dependencies]
/// ext-php-rs = "0.15"
/// my-interfaces = { path = "../my-interfaces" }
/// ```
///
/// ```rust,no_run,ignore,ignore
/// // my-extension/src/lib.rs
/// use ext_php_rs::prelude::*;
/// use my_interfaces::Serializable;
///
/// #[php_class]
/// #[php(name = "MyExtension\\User")]
/// pub struct User {
///     name: String,
///     email: String,
/// }
///
/// #[php_impl]
/// impl User {
///     pub fn __construct(name: String, email: String) -> Self {
///         Self { name, email }
///     }
///
///     // Note: No need to add to_json() here - it's automatically
///     // registered by #[php_impl_interface] below
/// }
///
/// // Register the interface implementation
/// // This automatically registers to_json() as a PHP method
/// #[php_impl_interface]
/// impl Serializable for User {
///     fn to_json(&self) -> String {
///         format!(r#"{{"name":"{}","email":"{}"}}"#, self.name, self.email)
///     }
/// }
///
/// #[php_module]
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         // Register the interface from the library crate
///         .interface::<my_interfaces::PhpInterfaceSerializable>()
///         .class::<User>()
/// }
/// ```
///
/// ### Using in PHP
///
/// ```php
/// <?php
///
/// use MyExtension\User;
/// use MyInterfaces\Serializable;
///
/// $user = new User("John", "john@example.com");
///
/// // instanceof works across crates
/// assert($user instanceof Serializable);
///
/// // Type hints work
/// function serialize_object(Serializable $obj): string {
///     return $obj->toJson();
/// }
///
/// echo serialize_object($user);
/// // Output: {"name":"John","email":"john@example.com"}
/// ```
///
/// ### Important Notes
///
/// 1. **Automatic method registration**: The `#[php_impl_interface]` macro
///    automatically registers all trait methods as PHP methods on the class.
///    You don't need to duplicate them in a `#[php_impl]` block.
///
/// 2. **Interface registration**: The interface must be registered in the
///    `#[php_module]` function using `.interface::<PhpInterfaceName>()`.
///
/// 3. **Link-time discovery**: The `inventory` crate uses link-time
///    registration for interface discovery, so all implementations are
///    automatically discovered when the final binary is linked.
// END DOCS FROM interface.md
#[proc_macro_attribute]
pub fn php_interface(args: TokenStream, input: TokenStream) -> TokenStream {
    php_interface_internal(args.into(), input.into()).into()
}

fn php_interface_internal(_args: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemTrait);

    interface::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

// BEGIN DOCS FROM function.md
/// # `#[php_function]` Attribute
///
/// Used to annotate functions which should be exported to PHP. Note that this
/// should not be used on class methods - see the `#[php_impl]` macro for that.
///
/// See the [list of types](../types/index.md) that are valid as parameter and
/// return types.
///
/// ## Optional parameters
///
/// Optional parameters can be used by setting the Rust parameter type to a
/// variant of `Option<T>`. The macro will then figure out which parameters are
/// optional by using the last consecutive arguments that are a variant of
/// `Option<T>` or have a default value.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_function]
/// pub fn greet(name: String, age: Option<i32>) -> String {
///     let mut greeting = format!("Hello, {}!", name);
///
///     if let Some(age) = age {
///         greeting += &format!(" You are {} years old.", age);
///     }
///
///     greeting
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.function(wrap_function!(greet))
/// }
/// # fn main() {}
/// ```
///
/// Default parameter values can also be set for optional parameters. This is
/// done through the `#[php(defaults)]` attribute option. When an optional
/// parameter has a default, it does not need to be a variant of `Option`:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_function]
/// #[php(defaults(offset = 0))]
/// pub fn rusty_strpos(haystack: &str, needle: &str, offset: i64) -> Option<usize> {
///     let haystack: String = haystack.chars().skip(offset as usize).collect();
///     haystack.find(needle)
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.function(wrap_function!(rusty_strpos))
/// }
/// # fn main() {}
/// ```
///
/// Note that if there is a non-optional argument after an argument that is a
/// variant of `Option<T>`, the `Option<T>` argument will be deemed a nullable
/// argument rather than an optional argument.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// /// `age` will be deemed required and nullable rather than optional.
/// #[php_function]
/// pub fn greet(name: String, age: Option<i32>, description: String) -> String {
///     let mut greeting = format!("Hello, {}!", name);
///
///     if let Some(age) = age {
///         greeting += &format!(" You are {} years old.", age);
///     }
///
///     greeting += &format!(" {}.", description);
///     greeting
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.function(wrap_function!(greet))
/// }
/// # fn main() {}
/// ```
///
/// You can also specify the optional arguments if you want to have nullable
/// arguments before optional arguments. This is done through an attribute
/// parameter:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// /// `age` will be deemed required and nullable rather than optional,
/// /// while description will be optional.
/// #[php_function]
/// #[php(optional = "description")]
/// pub fn greet(name: String, age: Option<i32>, description: Option<String>) -> String {
///     let mut greeting = format!("Hello, {}!", name);
///
///     if let Some(age) = age {
///         greeting += &format!(" You are {} years old.", age);
///     }
///
///     if let Some(description) = description {
///         greeting += &format!(" {}.", description);
///     }
///
///     greeting
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.function(wrap_function!(greet))
/// }
/// # fn main() {}
/// ```
///
/// ## Variadic Functions
///
/// Variadic functions can be implemented by specifying the last argument in the
/// Rust function to the type `&[&Zval]`. This is the equivalent of a PHP
/// function using the `...$args` syntax.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{prelude::*, types::Zval};
///
/// /// This can be called from PHP as `add(1, 2, 3, 4, 5)`
/// #[php_function]
/// pub fn add(number: u32, numbers:&[&Zval]) -> u32 {
///     // numbers is a slice of 4 Zvals all of type long
///     number
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.function(wrap_function!(add))
/// }
/// # fn main() {}
/// ```
///
/// ## Performance
///
/// The `#[php_function]` macro generates a zero-allocation fast path that reads
/// arguments directly from the PHP call frame, matching how native C extensions
/// parse parameters. This applies automatically — no configuration needed.
///
/// The same fast path is used for class methods defined with `#[php_impl]`,
/// including instance methods (`&self`, `&mut self`) and static methods.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// # use ext_php_rs::prelude::*;
/// // Fast path: standalone functions
/// #[php_function]
/// pub fn fast(name: String, age: Option<i32>) -> String { name }
///
/// // Fast path: parameters with defaults
/// #[php_function]
/// #[php(defaults(offset = 0))]
/// pub fn also_fast(haystack: &str, offset: i64) -> i64 { offset }
/// # fn main() {}
/// ```
///
/// ```rust,ignore
/// #[php_impl]
/// impl MyClass {
///     // Fast path: static methods
///     pub fn create(n: i32) -> Self { /* ... */ }
///
///     // Fast path: instance methods
///     pub fn get_value(&self, offset: i32) -> i32 { /* ... */ }
/// }
/// ```
///
/// The only case that falls back to the runtime argument parser is when using
/// variadic arguments (`&[&Zval]`, the Rust equivalent of PHP's `...$args`):
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// # use ext_php_rs::{prelude::*, types::Zval};
/// // Slower path: variadic arguments require runtime parsing
/// #[php_function]
/// pub fn add(first: u32, rest: &[&Zval]) -> u32 { first }
/// # fn main() {}
/// ```
///
/// ## Returning `Result<T, E>`
///
/// You can also return a `Result` from the function. The error variant will be
/// translated into an exception and thrown. See the section on
/// [exceptions](../exceptions.md) for more details.
// END DOCS FROM function.md
#[proc_macro_attribute]
pub fn php_function(args: TokenStream, input: TokenStream) -> TokenStream {
    php_function_internal(args.into(), input.into()).into()
}

#[allow(clippy::needless_pass_by_value)]
fn php_function_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemFn);
    if !args.is_empty() {
        return err!(input => "`#[php_function(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
    }

    function::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

// BEGIN DOCS FROM constant.md
/// # `#[php_const]` Attribute
///
/// Exports a Rust constant as a global PHP constant. The constant can be any
/// type that implements `IntoConst`.
///
/// The `wrap_constant!()` macro can be used to simplify the registration of
/// constants. It sets the name and doc comments for the constant.
///
/// You can rename the const with options:
///
/// - `name` - Allows you to rename the property, e.g. `#[php(name =
///   "new_name")]`
/// - `change_case` - Allows you to rename the property using rename rules, e.g.
///   `#[php(change_case = PascalCase)]`
///
/// ## Examples
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_const]
/// const TEST_CONSTANT: i32 = 100;
///
/// #[php_const]
/// #[php(name = "I_AM_RENAMED")]
/// const TEST_CONSTANT_THE_SECOND: i32 = 42;
///
/// #[php_const]
/// const ANOTHER_STRING_CONST: &'static str = "Hello world!";
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .constant(wrap_constant!(TEST_CONSTANT))
///         .constant(wrap_constant!(TEST_CONSTANT_THE_SECOND))
///         .constant(("MANUAL_CONSTANT", ANOTHER_STRING_CONST, &[]))
/// }
/// # fn main() {}
/// ```
///
/// ## PHP usage
///
/// ```php
/// <?php
///
/// var_dump(TEST_CONSTANT); // int(100)
/// var_dump(I_AM_RENAMED); // int(42)
/// var_dump(MANUAL_CONSTANT); // string(12) "Hello world!"
/// ```
// END DOCS FROM constant.md
#[proc_macro_attribute]
pub fn php_const(args: TokenStream, input: TokenStream) -> TokenStream {
    php_const_internal(args.into(), input.into()).into()
}

#[allow(clippy::needless_pass_by_value)]
fn php_const_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemConst);
    if !args.is_empty() {
        return err!(input => "`#[php_const(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
    }

    constant::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

// BEGIN DOCS FROM module.md
/// # `#[php_module]` Attribute
///
/// The module macro is used to annotate the `get_module` function, which is
/// used by the PHP interpreter to retrieve information about your extension,
/// including the name, version, functions and extra initialization functions.
/// Regardless if you use this macro, your extension requires a `extern "C" fn
/// get_module()` so that PHP can get this information.
///
/// The function is renamed to `get_module` if you have used another name. The
/// function is passed an instance of `ModuleBuilder` which allows you to
/// register the following (if required):
///
/// - Functions, classes, and constants
/// - Extension and request startup and shutdown functions.
///   - Read more about the PHP extension lifecycle [here](https://www.phpinternalsbook.com/php7/extensions_design/php_lifecycle.html).
/// - PHP extension information function
///   - Used by the `phpinfo()` function to get information about your
///     extension.
///
/// Classes and constants are not registered with PHP in the `get_module`
/// function. These are registered inside the extension startup function.
///
/// ## Usage
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{
///     prelude::*,
///     zend::ModuleEntry,
///     info_table_start,
///     info_table_row,
///     info_table_end
/// };
///
/// #[php_const]
/// pub const MY_CUSTOM_CONST: &'static str = "Hello, world!";
///
/// #[php_class]
/// pub struct Test {
///     a: i32,
///     b: i32
/// }
/// #[php_function]
/// pub fn hello_world() -> &'static str {
///     "Hello, world!"
/// }
///
/// /// Used by the `phpinfo()` function and when you run `php -i`.
/// /// This will probably be simplified with another macro eventually!
/// pub extern "C" fn php_module_info(_module: *mut ModuleEntry) {
///     info_table_start!();
///     info_table_row!("my extension", "enabled");
///     info_table_end!();
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .constant(wrap_constant!(MY_CUSTOM_CONST))
///         .class::<Test>()
///         .function(wrap_function!(hello_world))
///         .info_function(php_module_info)
/// }
/// # fn main() {}
/// ```
// END DOCS FROM module.md
#[proc_macro_attribute]
pub fn php_module(args: TokenStream, input: TokenStream) -> TokenStream {
    php_module_internal(args.into(), input.into()).into()
}

#[allow(clippy::needless_pass_by_value)]
fn php_module_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemFn);
    if !args.is_empty() {
        return err!(input => "`#[php_module(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
    }

    module::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

// BEGIN DOCS FROM impl.md
/// # `#[php_impl]` Attribute
///
/// You can export an entire `impl` block to PHP. This exports all methods as
/// well as constants to PHP on the class that it is implemented on. This
/// requires the `#[php_class]` macro to already be used on the underlying
/// struct. Trait implementations cannot be exported to PHP. Only one `impl`
/// block can be exported per class.
///
/// If you do not want a function exported to PHP, you should place it in a
/// separate `impl` block.
///
/// If you want to use async Rust, use `#[php_async_impl]`, instead: see [here
/// &raquo;](./async_impl.md) for more info.
///
/// ## Options
///
/// By default all constants are renamed to `UPPER_CASE` and all methods are
/// renamed to camelCase. This can be changed by passing the
/// `change_method_case` and `change_constant_case` as `#[php]` attributes on
/// the `impl` block. The options are:
///
/// - `#[php(change_method_case = "snake_case")]` - Renames the method to snake
///   case.
/// - `#[php(change_constant_case = "snake_case")]` - Renames the constant to
///   snake case.
///
/// See the [`name` and `change_case`](./php.md#name-and-change_case) section
/// for a list of all available cases.
///
/// ## Methods
///
/// Methods basically follow the same rules as functions, so read about the
/// [`php_function`] macro first. The primary difference between functions and
/// methods is they are bounded by their class object.
///
/// Both instance and static methods benefit from the same zero-allocation fast
/// path as standalone functions. See the
/// [Performance](./function.md#performance) section for details.
///
/// Class methods can take a `&self` or `&mut self` parameter. They cannot take
/// a consuming `self` parameter. Static methods can omit this `self` parameter.
///
/// To access the underlying Zend object, you can take a reference to a
/// `ZendClassObject<T>` in place of the self parameter, where the parameter
/// must be named `self_`. This can also be used to return a reference to
/// `$this`.
///
/// The rest of the options are passed as separate attributes:
///
/// - `#[php(defaults(i = 5, b = "hello"))]` - Sets the default value for
///   parameter(s).
/// - `#[php(optional = i)]` - Sets the first optional parameter. Note that this
///   also sets the remaining parameters as optional, so all optional parameters
///   must be a variant of `Option<T>`.
/// - `#[php(vis = "public")]`, `#[php(vis = "protected")]` and `#[php(vis =
///   "private")]` - Sets the visibility of the method.
/// - `#[php(name = "method_name")]` - Renames the PHP method to a different
///   identifier, without renaming the Rust method name.
/// - `#[php(final)]` - Makes the method final (cannot be overridden in
///   subclasses).
/// - `#[php(abstract)]` - Makes the method abstract (must be implemented by
///   subclasses). Can only be used in abstract classes.
///
/// The `#[php(defaults)]` and `#[php(optional)]` attributes operate the same as
/// the equivalent function attribute parameters.
///
/// ### Static Methods
///
/// Methods that do not take a `&self` or `&mut self` parameter are
/// automatically exported as static methods. These can be called on the class
/// itself without creating an instance.
///
/// ```rust,ignore
/// #[php_impl]
/// impl MyClass {
///     // Static method - no self parameter
///     pub fn create_default() -> Self {
///         Self { /* ... */ }
///     }
///
///     // Instance method - takes &self
///     pub fn get_value(&self) -> i32 {
///         self.value
///     }
/// }
/// ```
///
/// From PHP:
///
/// ```php
/// $obj = MyClass::createDefault(); // Static call
/// $val = $obj->getValue();         // Instance call
/// ```
///
/// ### Final Methods
///
/// Methods marked with `#[php(final)]` cannot be overridden in subclasses. This
/// is useful when you want to prevent modification of critical functionality.
///
/// ```rust,ignore
/// use ext_php_rs::prelude::*;
///
/// #[php_class]
/// pub struct SecureClass;
///
/// #[php_impl]
/// impl SecureClass {
///     #[php(final)]
///     pub fn secure_method(&self) -> &str {
///         "This cannot be overridden"
///     }
///
///     // Final static methods are also supported
///     #[php(final)]
///     pub fn secure_static() -> i32 {
///         42
///     }
/// }
/// ```
///
/// ### Abstract Methods
///
/// Methods marked with `#[php(abstract)]` must be implemented by subclasses.
/// Abstract methods can only be defined in abstract classes (classes with
/// `ClassFlags::Abstract`).
///
/// ```rust,ignore
/// use ext_php_rs::prelude::*;
/// use ext_php_rs::flags::ClassFlags;
///
/// #[php_class]
/// #[php(flags = ClassFlags::Abstract)]
/// pub struct AbstractShape;
///
/// #[php_impl]
/// impl AbstractShape {
///     // Protected constructor for subclasses
///     #[php(vis = "protected")]
///     pub fn __construct() -> Self {
///         Self
///     }
///
///     // Abstract method - subclasses must implement this.
///     // The body is never called; use unimplemented!() as a placeholder.
///     #[php(abstract)]
///     pub fn area(&self) -> f64 {
///         unimplemented!()
///     }
///
///     // Concrete method in abstract class
///     pub fn describe(&self) -> String {
///         format!("A shape with area {}", self.area())
///     }
/// }
/// ```
///
/// **Note:** Abstract method bodies are never called - they exist only because
/// Rust syntax requires a body for methods in `impl` blocks. Use
/// `unimplemented!()` as a clear placeholder.
///
/// **Note:** If you try to use `#[php(abstract)]` on a method in a non-abstract
/// class, you will get a compile-time error.
///
/// **Note:** PHP does not support abstract static methods. If you need static
/// behavior that can be customized by subclasses, use a regular instance method
/// or the [late static binding](https://www.php.net/manual/en/language.oop5.late-static-bindings.php)
/// pattern in PHP.
///
/// ### Constructors
///
/// By default, if a class does not have a constructor, it is not constructable
/// from PHP. It can only be returned from a Rust function to PHP.
///
/// Constructors are Rust methods which can take any amount of parameters and
/// returns either `Self` or `Result<Self, E>`, where `E: Into<PhpException>`.
/// When the error variant of `Result` is encountered, it is thrown as an
/// exception and the class is not constructed.
///
/// Constructors are designated by either naming the method `__construct` or by
/// annotating a method with the `#[php(constructor)]` attribute. Note that when
/// using the attribute, the function is not exported to PHP like a regular
/// method.
///
/// Constructors cannot use the visibility or rename attributes listed above.
///
/// ## Constants
///
/// Constants are defined as regular Rust `impl` constants. Any type that
/// implements `IntoZval` can be used as a constant. Constant visibility is not
/// supported at the moment, and therefore no attributes are valid on constants.
///
/// ## Property getters and setters
///
/// You can add properties to classes which use Rust functions as getters and/or
/// setters. This is done with the `#[php(getter)]` and `#[php(setter)]`
/// attributes. By default, the `get_` or `set_` prefix is trimmed from the
/// start of the function name, and the remainder is used as the property name.
///
/// If you want to use a different name for the property, you can pass a `name`
/// or `change_case` option to the `#[php]` attribute which will change the
/// property name.
///
/// Properties do not necessarily have to have both a getter and a setter, if
/// the property is immutable the setter can be omitted, and vice versa for
/// getters.
///
/// The `#[php(getter)]` and `#[php(setter)]` attributes are mutually exclusive
/// on methods. Properties cannot have multiple getters or setters, and the
/// property name cannot conflict with field properties defined on the struct.
///
/// As the same as field properties, method property types must implement both
/// `IntoZval` and `FromZval`.
///
/// ### Overriding field properties with getters/setters
///
/// If you have a field property defined with `#[php(prop)]` on your struct, you
/// can override its access by defining a getter or setter method with the same
/// property name. The method-based property will take precedence:
///
/// ```rust,ignore
/// use ext_php_rs::prelude::*;
///
/// #[php_class]
/// pub struct Book {
///     #[php(prop)]
///     pub title: String,  // Direct field access
/// }
///
/// #[php_impl]
/// impl Book {
///     pub fn __construct(title: String) -> Self {
///         Self { title }
///     }
///
///     // This getter overrides $book->title access
///     #[php(getter)]
///     pub fn get_title(&self) -> String {
///         format!("Title: {}", self.title)
///     }
/// }
/// ```
///
/// In PHP, accessing `$book->title` will now call the `get_title()` method
/// instead of directly accessing the field:
///
/// ```php
/// $book = new Book("The Rust Book");
/// echo $book->title; // Output: "Title: The Rust Book"
/// ```
///
/// This is useful when you need to add validation, transformation, or side
/// effects to property access while still having the convenience of a public
/// field in Rust.
///
/// ## Example
///
/// Continuing on from our `Human` example in the structs section, we will
/// define a constructor, as well as getters for the properties. We will also
/// define a constant for the maximum age of a `Human`.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{prelude::*, types::ZendClassObject};
///
/// #[php_class]
/// #[derive(Debug, Default)]
/// pub struct Human {
///     name: String,
///     age: i32,
///     #[php(prop)]
///     address: String,
/// }
///
/// #[php_impl]
/// impl Human {
///     const MAX_AGE: i32 = 100;
///
///     // No `#[constructor]` attribute required here - the name is `__construct`.
///     pub fn __construct(name: String, age: i32) -> Self {
///         Self {
///             name,
///             age,
///             address: String::new()
///         }
///     }
///
///     #[php(getter)]
///     pub fn get_name(&self) -> String {
///         self.name.to_string()
///     }
///
///     #[php(setter)]
///     pub fn set_name(&mut self, name: String) {
///         self.name = name;
///     }
///
///     #[php(getter)]
///     pub fn get_age(&self) -> i32 {
///         self.age
///     }
///
///     pub fn introduce(&self) {
///         println!("My name is {} and I am {} years old. I live at {}.", self.name, self.age, self.address);
///     }
///
///     pub fn get_raw_obj(self_: &mut ZendClassObject<Human>) -> &mut ZendClassObject<Human> {
///         dbg!(self_)
///     }
///
///     pub fn get_max_age() -> i32 {
///         Self::MAX_AGE
///     }
/// }
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module.class::<Human>()
/// }
/// # fn main() {}
/// ```
///
/// Using our newly created class in PHP:
///
/// ```php
/// <?php
///
/// $me = new Human('David', 20);
///
/// $me->introduce(); // My name is David and I am 20 years old.
/// var_dump(Human::get_max_age()); // int(100)
/// var_dump(Human::MAX_AGE); // int(100)
/// ```
///
/// [`php_async_impl`]: ./async_impl.md
// END DOCS FROM impl.md
#[proc_macro_attribute]
pub fn php_impl(args: TokenStream, input: TokenStream) -> TokenStream {
    php_impl_internal(args.into(), input.into()).into()
}

#[allow(clippy::needless_pass_by_value)]
fn php_impl_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemImpl);
    if !args.is_empty() {
        return err!(input => "`#[php_impl(<args>)]` args are no longer supported. Please use `#[php(<args>)]` instead.").to_compile_error();
    }

    impl_::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

/// # `#[php_impl_interface]` Attribute
///
/// Marks a trait implementation as implementing a PHP interface. This allows
/// Rust structs marked with `#[php_class]` to implement Rust traits marked
/// with `#[php_interface]`, and have PHP recognize the relationship.
///
/// **Key feature**: The macro automatically registers the trait methods as PHP
/// methods on the class. You don't need to duplicate them in a separate
/// `#[php_impl]` block.
///
/// ## Usage
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[php_interface]
/// trait MyInterface {
///     fn my_method(&self) -> String;
/// }
///
/// #[php_class]
/// struct MyClass;
///
/// // The trait method my_method() is automatically registered as a PHP method
/// #[php_impl_interface]
/// impl MyInterface for MyClass {
///     fn my_method(&self) -> String {
///         "Hello from MyClass!".to_string()
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .interface::<PhpInterfaceMyInterface>()
///         .class::<MyClass>()
/// }
/// # fn main() {}
/// ```
///
/// After registration, PHP's `is_a($obj, 'MyInterface')` will return `true`
/// for instances of `MyClass`, and `$obj->myMethod()` will be callable.
///
/// ## Options
///
/// ### `change_method_case`
///
/// If the interface uses a non-default `change_method_case` (e.g.,
/// `#[php(change_method_case = "snake_case")]`), you must specify the same
/// setting on `#[php_impl_interface]` to ensure method names match:
///
/// ```rust,no_run,ignore
/// #[php_interface]
/// #[php(change_method_case = "snake_case")]
/// trait MyInterface {
///     fn my_method(&self) -> String;
/// }
///
/// #[php_impl_interface(change_method_case = "snake_case")]
/// impl MyInterface for MyClass {
///     fn my_method(&self) -> String {
///         "Hello!".to_string()
///     }
/// }
/// ```
///
/// The default is `camelCase` (matching the interface default).
///
/// ## Requirements
///
/// - The trait must be marked with `#[php_interface]`
/// - The struct must be marked with `#[php_class]`
/// - The interface must be registered before the class in the module builder
#[proc_macro_attribute]
pub fn php_impl_interface(args: TokenStream, input: TokenStream) -> TokenStream {
    php_impl_interface_internal(args.into(), input.into()).into()
}

#[allow(clippy::needless_pass_by_value)]
fn php_impl_interface_internal(args: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemImpl);
    let attr_args = match darling::ast::NestedMeta::parse_meta_list(args) {
        Ok(v) => v,
        Err(e) => return e.to_compile_error(),
    };
    let args = match impl_interface::PhpImplInterfaceArgs::from_list(&attr_args) {
        Ok(v) => v,
        Err(e) => return e.write_errors(),
    };

    impl_interface::parser(args, &input).unwrap_or_else(|e| e.to_compile_error())
}

// BEGIN DOCS FROM extern.md
/// # `#[php_extern]` Attribute
///
/// Attribute used to annotate `extern` blocks which are deemed as PHP
/// functions.
///
/// This allows you to 'import' PHP functions into Rust so that they can be
/// called like regular Rust functions. Parameters can be any type that
/// implements [`IntoZval`], and the return type can be anything that implements
/// [`From<Zval>`] (notice how [`Zval`] is consumed rather than borrowed in this
/// case).
///
/// Unlike most other attributes, this does not need to be placed inside a
/// `#[php_module]` block.
///
/// # Panics
///
/// The function can panic when called under a few circumstances:
///
/// * The function could not be found or was not callable.
/// * One of the parameters could not be converted into a [`Zval`].
/// * The actual function call failed internally.
/// * The output [`Zval`] could not be parsed into the output type.
///
/// The last point can be important when interacting with functions that return
/// unions, such as [`strpos`] which can return an integer or a boolean. In this
/// case, a [`Zval`] should be returned as parsing a boolean to an integer is
/// invalid, and vice versa.
///
/// # Example
///
/// This `extern` block imports the [`strpos`] function from PHP. Notice that
/// the string parameters can take either [`String`] or [`&str`], the optional
/// parameter `offset` is an [`Option<i64>`], and the return value is a [`Zval`]
/// as the return type is an integer-boolean union.
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::{
///     prelude::*,
///     types::Zval,
/// };
///
/// #[php_extern]
/// extern "C" {
///     fn strpos(haystack: &str, needle: &str, offset: Option<i64>) -> Zval;
/// }
///
/// #[php_function]
/// pub fn my_strpos() {
///     assert_eq!(unsafe { strpos("Hello", "e", None) }.long(), Some(1));
/// }
///
/// #[php_module]
/// pub fn module(module: ModuleBuilder) -> ModuleBuilder {
///     module.function(wrap_function!(my_strpos))
/// }
/// # fn main() {}
/// ```
///
/// [`strpos`]: https://www.php.net/manual/en/function.strpos.php
/// [`IntoZval`]: crate::convert::IntoZval
/// [`Zval`]: crate::types::Zval
// END DOCS FROM extern.md
#[proc_macro_attribute]
pub fn php_extern(args: TokenStream, input: TokenStream) -> TokenStream {
    php_extern_internal(args.into(), input.into()).into()
}

#[allow(clippy::needless_pass_by_value)]
fn php_extern_internal(_: TokenStream2, input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemForeignMod);

    extern_::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

// BEGIN DOCS FROM zval_convert.md
/// # `ZvalConvert` Derive Macro
///
/// The `#[derive(ZvalConvert)]` macro derives the `FromZval` and `IntoZval`
/// traits on a struct or enum.
///
/// ## Structs
///
/// When used on a struct, the `FromZendObject` and `IntoZendObject` traits are
/// also implemented, mapping fields to properties in both directions. All
/// fields on the struct must implement `FromZval` as well. Generics are allowed
/// on structs that use the derive macro, however, the implementation will add a
/// `FromZval` bound to all generics types.
///
/// ### Examples
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[derive(ZvalConvert)]
/// pub struct ExampleClass<'a> {
///     a: i32,
///     b: String,
///     c: &'a str
/// }
///
/// #[php_function]
/// pub fn take_object(obj: ExampleClass) {
///     dbg!(obj.a, obj.b, obj.c);
/// }
///
/// #[php_function]
/// pub fn give_object() -> ExampleClass<'static> {
///     ExampleClass {
///         a: 5,
///         b: "String".to_string(),
///         c: "Borrowed",
///     }
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .function(wrap_function!(take_object))
///         .function(wrap_function!(give_object))
/// }
/// # fn main() {}
/// ```
///
/// Calling from PHP:
///
/// ```php
/// <?php
///
/// $obj = new stdClass;
/// $obj->a = 5;
/// $obj->b = 'Hello, world!';
/// $obj->c = 'another string';
///
/// take_object($obj);
/// var_dump(give_object());
/// ```
///
/// Another example involving generics:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// // T must implement both `PartialEq<i32>` and `FromZval`.
/// #[derive(Debug, ZvalConvert)]
/// pub struct CompareVals<T: PartialEq<i32>> {
///     a: T,
///     b: T
/// }
///
/// #[php_function]
/// pub fn take_object(obj: CompareVals<i32>) {
///     dbg!(obj);
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .function(wrap_function!(take_object))
/// }
/// # fn main() {}
/// ```
///
/// ## Enums
///
/// When used on an enum, the `FromZval` implementation will treat the enum as a
/// tagged union with a mixed datatype. This allows you to accept multiple types
/// in a parameter, for example, a string and an integer.
///
/// The enum variants must not have named fields, and each variant must have
/// exactly one field (the type to extract from the zval). Optionally, the enum
/// may have one default variant with no data contained, which will be used when
/// the rest of the variants could not be extracted from the zval.
///
/// The ordering of the variants in the enum is important, as the `FromZval`
/// implementation will attempt to parse the zval data in order. For example, if
/// you put a `String` variant before an integer variant, the integer would be
/// converted to a string and passed as the string variant.
///
/// ### Examples
///
/// Basic example showing the importance of variant ordering and default field:
///
/// ```rust,no_run,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// # extern crate ext_php_rs;
/// use ext_php_rs::prelude::*;
///
/// #[derive(Debug, ZvalConvert)]
/// pub enum UnionExample<'a> {
///     Long(u64), // Long
///     ProperStr(&'a str), // Actual string - not a converted value
///     ParsedStr(String), // Potentially parsed string, i.e. a double
///     None // Zval did not contain anything that could be parsed above
/// }
///
/// #[php_function]
/// pub fn test_union(val: UnionExample) {
///     dbg!(val);
/// }
///
/// #[php_function]
/// pub fn give_union() -> UnionExample<'static> {
///     UnionExample::Long(5)
/// }
///
/// #[php_module]
/// pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
///     module
///         .function(wrap_function!(test_union))
///         .function(wrap_function!(give_union))
/// }
/// # fn main() {}
/// ```
///
/// Use in PHP:
///
/// ```php
/// test_union(5); // UnionExample::Long(5)
/// test_union("Hello, world!"); // UnionExample::ProperStr("Hello, world!")
/// test_union(5.66666); // UnionExample::ParsedStr("5.6666")
/// test_union(null); // UnionExample::None
/// var_dump(give_union()); // int(5)
/// ```
// END DOCS FROM zval_convert.md
#[proc_macro_derive(ZvalConvert)]
pub fn zval_convert_derive(input: TokenStream) -> TokenStream {
    zval_convert_derive_internal(input.into()).into()
}

fn zval_convert_derive_internal(input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as DeriveInput);

    zval::parser(input).unwrap_or_else(|e| e.to_compile_error())
}

/// Defines an `extern` function with the Zend fastcall convention based on
/// operating system.
///
/// On Windows, Zend fastcall functions use the vector calling convention, while
/// on all other operating systems no fastcall convention is used (just the
/// regular C calling convention).
///
/// This macro wraps a function and applies the correct calling convention.
///
/// ## Examples
///
/// ```rust,ignore
/// # #![cfg_attr(windows, feature(abi_vectorcall))]
/// use ext_php_rs::zend_fastcall;
///
/// zend_fastcall! {
///     pub extern fn test_hello_world(a: i32, b: i32) -> i32 {
///         a + b
///     }
/// }
/// ```
///
/// On Windows, this function will have the signature `pub extern "vectorcall"
/// fn(i32, i32) -> i32`, while on macOS/Linux the function will have the
/// signature `pub extern "C" fn(i32, i32) -> i32`.
///
/// ## Support
///
/// The `vectorcall` ABI is currently only supported on Windows with nightly
/// Rust and the `abi_vectorcall` feature enabled.
#[proc_macro]
pub fn zend_fastcall(input: TokenStream) -> TokenStream {
    zend_fastcall_internal(input.into()).into()
}

fn zend_fastcall_internal(input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as ItemFn);

    fastcall::parser(input)
}

/// Wraps a function to be used in the [`Module::function`] method.
#[proc_macro]
pub fn wrap_function(input: TokenStream) -> TokenStream {
    wrap_function_internal(input.into()).into()
}

fn wrap_function_internal(input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as syn::Path);

    match function::wrap(&input) {
        Ok(parsed) => parsed,
        Err(e) => e.to_compile_error(),
    }
}

/// Wraps a constant to be used in the [`ModuleBuilder::constant`] method.
#[proc_macro]
pub fn wrap_constant(input: TokenStream) -> TokenStream {
    wrap_constant_internal(input.into()).into()
}

fn wrap_constant_internal(input: TokenStream2) -> TokenStream2 {
    let input = parse_macro_input2!(input as syn::Path);

    match constant::wrap(&input) {
        Ok(parsed) => parsed,
        Err(e) => e.to_compile_error(),
    }
}

macro_rules! parse_macro_input2 {
    ($tokenstream:ident as $ty:ty) => {
        match syn::parse2::<$ty>($tokenstream) {
            Ok(data) => data,
            Err(err) => {
                return proc_macro2::TokenStream::from(err.to_compile_error());
            }
        }
    };
    ($tokenstream:ident) => {
        $crate::parse_macro_input!($tokenstream as _)
    };
}

pub(crate) use parse_macro_input2;

macro_rules! err {
    ($span:expr => $($msg:tt)*) => {
        ::syn::Error::new(::syn::spanned::Spanned::span(&$span), format!($($msg)*))
    };
    ($($msg:tt)*) => {
        ::syn::Error::new(::proc_macro2::Span::call_site(), format!($($msg)*))
    };
}

/// Bails out of a function with a syn error.
macro_rules! bail {
    ($span:expr => $($msg:tt)*) => {
        return Err($crate::err!($span => $($msg)*))
    };
    ($($msg:tt)*) => {
        return Err($crate::err!($($msg)*))
    };
}

pub(crate) use bail;
pub(crate) use err;

pub(crate) mod prelude {
    pub(crate) trait OptionTokens {
        fn option_tokens(&self) -> proc_macro2::TokenStream;
    }

    impl<T: quote::ToTokens> OptionTokens for Option<T> {
        fn option_tokens(&self) -> proc_macro2::TokenStream {
            if let Some(token) = self {
                quote::quote! { ::std::option::Option::Some(#token) }
            } else {
                quote::quote! { ::std::option::Option::None }
            }
        }
    }

    pub(crate) use crate::{bail, err};
    pub(crate) type Result<T> = std::result::Result<T, syn::Error>;
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::PathBuf;

    type AttributeFn =
        fn(proc_macro2::TokenStream, proc_macro2::TokenStream) -> proc_macro2::TokenStream;
    type FunctionLikeFn = fn(proc_macro2::TokenStream) -> proc_macro2::TokenStream;

    #[rustversion::attr(nightly, test)]
    #[allow(dead_code)]
    pub fn test_macrotest_expand() {
        macrotest::expand("tests/expand/*.rs");
    }

    #[test]
    pub fn test_expand() {
        for entry in glob::glob("tests/expand/*.rs").expect("Failed to read expand tests glob") {
            let entry = entry.expect("Failed to read expand test file");
            runtime_expand_attr(&entry);
            runtime_expand_func(&entry);
            runtime_expand_derive(&entry);
        }
    }

    fn runtime_expand_attr(path: &PathBuf) {
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
        runtime_macros::emulate_attributelike_macro_expansion(
            file,
            &[
                ("php_class", php_class_internal as AttributeFn),
                ("php_const", php_const_internal as AttributeFn),
                ("php_enum", php_enum_internal as AttributeFn),
                ("php_interface", php_interface_internal as AttributeFn),
                ("php_extern", php_extern_internal as AttributeFn),
                ("php_function", php_function_internal as AttributeFn),
                ("php_impl", php_impl_internal as AttributeFn),
                (
                    "php_impl_interface",
                    php_impl_interface_internal as AttributeFn,
                ),
                ("php_module", php_module_internal as AttributeFn),
            ],
        )
        .expect("Failed to expand attribute macros in test file");
    }

    fn runtime_expand_func(path: &PathBuf) {
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
        runtime_macros::emulate_functionlike_macro_expansion(
            file,
            &[
                ("zend_fastcall", zend_fastcall_internal as FunctionLikeFn),
                ("wrap_function", wrap_function_internal as FunctionLikeFn),
                ("wrap_constant", wrap_constant_internal as FunctionLikeFn),
            ],
        )
        .expect("Failed to expand function-like macros in test file");
    }

    fn runtime_expand_derive(path: &PathBuf) {
        let file = std::fs::File::open(path).expect("Failed to open expand test file");
        runtime_macros::emulate_derive_macro_expansion(
            file,
            &[("ZvalConvert", zval_convert_derive_internal)],
        )
        .expect("Failed to expand derive macros in test file");
    }
}
