<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Rest;

use Numinix\SeekmodoWordPress\Config\Options;
use Numinix\SeekmodoWordPress\Gateway\BrowserToken;

/**
 * v0.6.0 (SM-606) — public-facing browser-token refresh route.
 *
 *   POST /wp-json/seekmodo/v1/browser-token
 *
 * Returns the page-level cached browser token + its expiry, in the
 * shape the bundled SDK's `seekmodo:refresh` route is expected to
 * return:
 *
 *   { "token": "<JWT>", "expires_at": 1718139600 }
 *
 * The route is the storefront's only line back to a fresh token once
 * the inline `<meta name="seekmodo:token">` rendered at page load
 * expires (5 min). Without it, a long-running tab would degrade to
 * "search unavailable" after the meta token's gateway-side TTL fires.
 *
 * Permissions:
 *
 *   - `__return_true`. The token returned has the same surface as the
 *     inline meta the public HTML already exposes — anonymous shoppers
 *     can call it from any origin, the same way the storefront input
 *     does. No CSRF nonce: a hostile script could just scrape the
 *     `<meta>` token instead, so requiring a nonce would only add UX
 *     friction without a security benefit.
 *
 * Failure modes:
 *
 *   - Unpaired site → 503. Bundle catches and renders the empty state.
 *   - Mint failure (gateway 5xx / breaker open) → 503.
 *
 * Caching: WP REST responses don't auto-cache; the per-tenant transient
 * inside {@see BrowserToken} already covers the cross-shopper case so
 * a flood of refresh calls coalesces to one mint per ~4 min.
 */
final class BrowserTokenRoute
{
    public static function register(): void
    {
        if (!function_exists('add_action')) {
            return;
        }
        add_action('rest_api_init', [self::class, 'registerRoutes']);
    }

    public static function registerRoutes(): void
    {
        if (!function_exists('register_rest_route')) {
            return;
        }
        register_rest_route(Routes::NAMESPACE_NAME, '/browser-token', [
            'methods'             => 'POST',
            'callback'            => [self::class, 'handle'],
            'permission_callback' => '__return_true',
        ]);
    }

    /**
     * @return array<string,mixed>|object
     */
    public static function handle()
    {
        if (!Options::isPaired()) {
            return self::error(503, 'unpaired');
        }
        $bt = BrowserToken::current();
        if (!is_array($bt) || !isset($bt['token'], $bt['expires_at'])) {
            return self::error(503, 'mint_failed');
        }

        return [
            'token'      => (string) $bt['token'],
            'expires_at' => (int) $bt['expires_at'],
            'session_id' => isset($bt['session_id']) ? (string) $bt['session_id'] : '',
        ];
    }

    /**
     * @return array<string,mixed>|object
     */
    private static function error(int $status, string $code)
    {
        if (class_exists('WP_REST_Response')) {
            $res = new \WP_REST_Response(['error' => $code], $status);

            return $res;
        }

        return ['error' => $code, 'status' => $status];
    }
}
