<?php
/**
 * CSRF protection.
 *
 * Token is per-session (stored in publisher_sessions.csrf_token).
 * Forms include it as hidden input; AJAX sends it via X-CSRF-Token header.
 */

declare(strict_types=1);

class Csrf
{
    /**
     * Get current session's CSRF token (for embedding in forms / meta tag).
     * Returns empty string for guests.
     */
    public static function token(): string
    {
        return (string)(Session::csrfToken() ?? '');
    }

    /**
     * Render <input type="hidden"> for forms.
     */
    public static function field(): string
    {
        return '<input type="hidden" name="csrf_token" value="' . e(self::token()) . '">';
    }

    /**
     * Render <meta> tag for AJAX (read by JS).
     */
    public static function metaTag(): string
    {
        return '<meta name="csrf-token" content="' . e(self::token()) . '">';
    }

    /**
     * Validate token from request. Aborts with 419 on failure.
     * Reads from POST['csrf_token'] OR header X-CSRF-Token.
     */
    public static function validate(): void
    {
        $sessionToken = self::token();
        if ($sessionToken === '') {
            jsonError('Session expired. Please log in again.', 419);
        }

        $submitted = $_POST['csrf_token']
            ?? $_SERVER['HTTP_X_CSRF_TOKEN']
            ?? '';

        if (!is_string($submitted) || !hash_equals($sessionToken, $submitted)) {
            Logger::warning('CSRF validation failed', [
                'ip'  => Logger::clientIp(),
                'uri' => $_SERVER['REQUEST_URI'] ?? '',
            ]);
            jsonError('Invalid CSRF token. Please refresh and try again.', 419);
        }
    }
}
