<?php

namespace WordpressModels\Rest;

use WP_Error;

/**
 * Trait for the WP_Rest_Server class to add the `args_callback` argument to route arguments.
 *
 * The argument is used to define a callback function that returns the arguments for the route. This allows
 * for dynamic arguments to be defined for the route, instead of static ones. Building up rest routes may
 * be heavy on server startup, so this allows to defer the building of the arguments until the route is actually
 * called.
 *
 * The callback function should return an array of arguments (in format of the original `args` arguments). This
 * may be a Wordpress JSON Schema definition, or a simple array of arguments. The callback function should not
 * return a WP_Error object, as this will be handled by the WP_Rest_Server class.
 *
 * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/adding-custom-endpoints/#registering-a-route
 * @see https://developer.wordpress.org/rest-api/extending-the-rest-api/schema/
 *
 * @mixin \WP_REST_Server
 */
trait CallbackArgsMatchable
{

    protected function match_request_to_handler($request) {
        $method = $request->get_method();
        $path   = $request->get_route();

        $with_namespace = array();

        foreach ( $this->get_namespaces() as $namespace ) {
            if ( str_starts_with( trailingslashit( ltrim( $path, '/' ) ), $namespace ) ) {
                $with_namespace[] = $this->get_routes( $namespace );
            }
        }

        if ( $with_namespace ) {
            $routes = array_merge( ...$with_namespace );
        } else {
            $routes = $this->get_routes();
        }

        foreach ( $routes as $route => $handlers ) {
            $match = preg_match( '@^' . $route . '$@i', $path, $matches );

            if ( ! $match ) {
                continue;
            }

            $args = array();

            foreach ( $matches as $param => $value ) {
                if ( ! is_int( $param ) ) {
                    $args[ $param ] = $value;
                }
            }

            foreach ( $handlers as $handler ) {
                $callback = $handler['callback'];

                // Fallback to GET method if no HEAD method is registered.
                $checked_method = $method;
                if ( 'HEAD' === $method && empty( $handler['methods']['HEAD'] ) ) {
                    $checked_method = 'GET';
                }
                if ( empty( $handler['methods'][ $checked_method ] ) ) {
                    continue;
                }

                if ( ! is_callable( $callback ) ) {
                    return array( $route, $handler );
                }

                $request->set_url_params( $args );
                $request->set_attributes( $handler );

                $defaults = array();

                $args = $handler['args'];
                if (isset($handler['args_callback']) && is_callable($handler['args_callback'])) {
                    $args = call_user_func($handler['args_callback']);
                }

                foreach ($args as $arg => $options ) {
                    if ( isset( $options['default'] ) ) {
                        $defaults[ $arg ] = $options['default'];
                    }
                }

                $request->set_default_params( $defaults );

                return array( $route, $handler );
            }
        }

        return new WP_Error(
            'rest_no_route',
            __( 'No route was found matching the URL and request method.' ),
            array( 'status' => 404 )
        );
    }

}
