<?php

declare(strict_types=1);

namespace CliqThemes\UI\REST;

use CliqThemes\UI\Contracts\DataProvider;
use CliqThemes\UI\Data\DataProviderRegistry;
use WP_REST_Server;

/**
 * Registers REST API routes that expose registered data providers.
 *
 * The registrar defers WordPress API calls until `rest_api_init` so it can safely run in
 * environments where the REST subsystem is unavailable (for example during CLI tasks).
 */
final class RouteRegistrar
{
    private DataProviderRegistry $providers;

    private string $namespace;

    /**
     * @var callable|null
     */
    private $permissionCallback;

    private bool $registered = false;

    /**
     * @param string        $namespace          REST namespace under which provider routes will be registered.
     * @param callable|null $permissionCallback Optional capability check applied to every route.
     */
    public function __construct(
        DataProviderRegistry $providers,
        string $namespace = 'cliqthemes-ui/v1',
        ?callable $permissionCallback = null
    ) {
        $this->providers = $providers;
        $this->namespace = trim($namespace, '/');
        $this->permissionCallback = $permissionCallback;
    }

    /**
     * Overrides the permission callback that will be used for all registered routes.
     */
    public function setPermissionCallback(callable $callback): void
    {
        $this->permissionCallback = $callback;
    }

    /**
     * Hooks WordPress to ensure routes are registered when the REST API initialises.
     */
    public function register(): void
    {
        if ($this->registered) {
            return;
        }

        if (! function_exists('add_action')) {
            return;
        }

        $this->registered = true;

        add_action('rest_api_init', [$this, 'registerRoutes']);
    }

    /**
     * Iterates through every provider and wires up CRUD routes for it.
     */
    public function registerRoutes(): void
    {
        if (! function_exists('register_rest_route')) {
            return;
        }

        foreach ($this->providers->all() as $identifier => $provider) {
            if (! $provider instanceof DataProvider) {
                continue;
            }

            $routeId = $this->sanitizeIdentifier((string) $identifier);
            $controller = new DataController($provider, (string) $identifier);

            register_rest_route(
                $this->namespace,
                '/' . $routeId,
                [
                    [
                        'methods' => WP_REST_Server::READABLE,
                        'callback' => [$controller, 'listItems'],
                        'permission_callback' => $this->permissionCallback(),
                        'args' => $this->collectionArgs(),
                    ],
                    [
                        'methods' => WP_REST_Server::CREATABLE,
                        'callback' => [$controller, 'createItem'],
                        'permission_callback' => $this->permissionCallback(),
                        'args' => $this->mutationArgs(),
                    ],
                ]
            );

            if (method_exists($provider, 'bulkUpdate')) {
                register_rest_route(
                    $this->namespace,
                    '/' . $routeId . '/bulk',
                    [
                        [
                            'methods' => WP_REST_Server::EDITABLE,
                            'callback' => [$controller, 'bulkUpdateItems'],
                            'permission_callback' => $this->permissionCallback(),
                            'args' => [
                                'ids' => [
                                    'type' => 'array',
                                    'items' => ['type' => ['integer', 'string']],
                                    'minItems' => 1,
                                    'required' => true,
                                ],
                                'action' => [
                                    'type' => 'string',
                                    'required' => true,
                                ],
                                'field' => [
                                    'type' => 'string',
                                    'required' => true,
                                ],
                                'mode' => [
                                    'type' => 'string',
                                    'enum' => ['replace', 'append'],
                                    'default' => 'replace',
                                ],
                            ],
                        ],
                    ]
                );
            }

            register_rest_route(
                $this->namespace,
                '/' . $routeId . '/(?P<record_id>[^/]+)',
                [
                    [
                        'methods' => WP_REST_Server::READABLE,
                        'callback' => [$controller, 'getItem'],
                        'permission_callback' => $this->permissionCallback(),
                        'args' => $this->itemArgs(),
                    ],
                    [
                        'methods' => WP_REST_Server::EDITABLE,
                        'callback' => [$controller, 'updateItem'],
                        'permission_callback' => $this->permissionCallback(),
                        'args' => $this->mutationArgs(),
                    ],
                    [
                        'methods' => WP_REST_Server::DELETABLE,
                        'callback' => [$controller, 'deleteItem'],
                        'permission_callback' => $this->permissionCallback(),
                        'args' => $this->itemArgs(),
                    ],
                ]
            );
        }
    }

    /**
     * Returns the query parameter definitions for collection routes.
     *
     * @return array<string,array<string,mixed>>
     */
    private function collectionArgs(): array
    {
        return [
            'per_page' => [
                'type' => 'integer',
                'description' => 'Number of items to return per page.',
                'default' => 20,
                'minimum' => 1,
                'maximum' => 100,
            ],
            'page' => [
                'type' => 'integer',
                'description' => 'Page of results to fetch.',
                'default' => 1,
                'minimum' => 1,
            ],
            'order_by' => [
                'type' => 'string',
                'description' => 'Column to order by.',
                'required' => false,
            ],
            'order' => [
                'type' => 'string',
                'enum' => ['asc', 'desc'],
                'description' => 'Sort direction.',
                'default' => 'asc',
            ],
            'filters' => [
                'type' => 'object',
                'description' => 'Arbitrary filter arguments supported by the provider.',
                'required' => false,
            ],
        ];
    }

    /**
     * Returns the parameter definitions for single record routes.
     *
     * @return array<string,array<string,mixed>>
     */
    private function itemArgs(): array
    {
        return [
            'record_id' => [
                'type' => 'string',
                'description' => 'Unique identifier for the record.',
                'required' => true,
            ],
        ];
    }

    /**
     * Returns the body parameter definitions for create and update routes.
     *
     * @return array<string,array<string,mixed>>
     */
    private function mutationArgs(): array
    {
        return [
            'payload' => [
                'type' => 'object',
                'description' => 'Record payload for create or update operations.',
                'required' => false,
            ],
        ];
    }

    /**
     * Resolves the permission callback, falling back to a default capability check.
     */
    private function permissionCallback(): callable
    {
        if ($this->permissionCallback) {
            return $this->permissionCallback;
        }

        return static function (): bool {
            // Fail closed: no capability API means no way to authorise, which is
            // a reason to refuse rather than to allow.
            return function_exists('current_user_can') && current_user_can('manage_options');
        };
    }

    /**
     * Normalises provider identifiers into REST-safe strings compatible with WordPress routing.
     */
    private function sanitizeIdentifier(string $identifier): string
    {
        if (function_exists('sanitize_title_with_dashes')) {
            $sanitized = sanitize_title_with_dashes($identifier);

            if ($sanitized !== '') {
                return $sanitized;
            }
        }

        $sanitized = strtolower(preg_replace('/[^a-zA-Z0-9\-]+/', '-', $identifier) ?? '');

        return trim($sanitized, '-') ?: md5($identifier);
    }
}
