<?php

namespace FleekDash\Core;

/**
 * Shared REST cache / ETag helpers for FleekDash API responses.
 *
 * @package FleekDash\Core
 */
final class RestApiCacheSupport
{
    public const API_NAMESPACE = 'fleekdash/v1';

    private const ETAG_WEAK_STRIP_REGEX = '/^W\/"(.*)"$/';

    private const ETAG_QUOTED_STRIP_REGEX = '/^"(.*)"$/';

    /**
     * @param mixed $payload Serializable REST response body (array|object|scalar).
     */
    public static function buildStrongEtagFromPayload(mixed $payload): string
    {
        $json = \wp_json_encode($payload);

        return '"' . md5($json ? (string) $json : '') . '"';
    }

    public static function newNotModifiedRestResponse(string $etag): \WP_REST_Response
    {
        $response = new \WP_REST_Response(null, 304);
        $response->set_headers([
            'ETag' => $etag,
            'Cache-Control' => 'public, max-age=300, must-revalidate',
        ]);

        return $response;
    }

    public static function emitPreServeCacheControlAndEtagHeaders(string $etag): void
    {
        if (headers_sent()) {
            return;
        }
        $clientCacheControl = $_SERVER['HTTP_CACHE_CONTROL'] ?? '';
        $shouldBypassCache = (
            stripos($clientCacheControl, 'no-cache') !== false
            || stripos($clientCacheControl, 'no-store') !== false
            || isset($_GET['_t'])
        );

        if ($shouldBypassCache) {
            header('Cache-Control: no-cache, no-store, must-revalidate');
            header('Pragma: no-cache');
            header('Expires: 0');
        } else {
            header('Cache-Control: public, max-age=300, must-revalidate');
        }

        header('ETag: ' . $etag);
    }

    public static function emitPreServe304Headers(string $etag): void
    {
        status_header(304);
        if (!headers_sent()) {
            header('ETag: ' . $etag);
            header('Cache-Control: public, max-age=300, must-revalidate');
        }
    }

    public static function isSessionSensitiveRoute(string $route): bool
    {
        return strpos($route, self::API_NAMESPACE . '/ai/') !== false;
    }

    /**
     * Theme/palette GET responses are user-mutable; browser or CDN caching causes stale UI after saves/deletes.
     */
    public static function isThemesUserMutableRestRoute(string $route): bool
    {
        $route = (string) $route;
        $marker = self::API_NAMESPACE . '/themes';
        $pos = strpos($route, $marker);
        if ($pos === false) {
            return false;
        }
        $after = substr($route, $pos + strlen($marker), 1);

        return $after === '' || $after === '/';
    }

    public static function isFleekdashCacheableEtagRoute(string $route): bool
    {
        $route = (string) $route;

        return strpos($route, self::API_NAMESPACE) !== false
            && !self::isLicenseRestRoute($route)
            && !self::isSessionSensitiveRoute($route)
            && !self::isThemesUserMutableRestRoute($route);
    }

    /**
     * License GET responses are user-specific and change on activate/deactivate without URL changes.
     * ETag 304 short-circuit would otherwise serve stale "inactive" payloads after activation (empty body on reload).
     */
    private static function isLicenseRestRoute(string $route): bool
    {
        return strpos((string) $route, self::API_NAMESPACE . '/license') !== false;
    }

    /**
     * @param array<string, mixed> $params
     */
    public static function buildEtagTransientKey(string $route, array $params): string
    {
        return 'fleekdash_etag_' . md5($route . '|' . \wp_json_encode($params));
    }

    public static function normalizeHttpEtagToken(string $tag): string
    {
        $tag = trim($tag);
        $weakStripped = preg_replace(self::ETAG_WEAK_STRIP_REGEX, '$1', $tag);
        $tag = is_string($weakStripped) ? $weakStripped : $tag;
        $quotedStripped = preg_replace(self::ETAG_QUOTED_STRIP_REGEX, '$1', $tag);

        return is_string($quotedStripped) ? $quotedStripped : $tag;
    }

    /**
     * True if any token in the If-None-Match list matches the server strong ETag (after normalization).
     */
    public static function httpEtagHeaderMatchesStrongTag(string $ifNoneMatchHeader, string $serverEtag): bool
    {
        $serverTag = self::normalizeHttpEtagToken($serverEtag);
        $clientEtags = array_map('trim', explode(',', $ifNoneMatchHeader));
        foreach ($clientEtags as $clientTag) {
            if (self::normalizeHttpEtagToken($clientTag) === $serverTag) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param \WP_HTTP_Response|\WP_REST_Response $response
     * @return \WP_HTTP_Response|\WP_REST_Response
     */
    public static function maybeNotModifiedIfEtagMatches(mixed $response, \WP_REST_Request $request): mixed
    {
        $out = $response;
        if (self::isFleekdashCacheableEtagRoute($request->get_route())) {
            $payload = $response instanceof \WP_REST_Response ? $response->get_data() : $response;
            $etag = self::buildStrongEtagFromPayload($payload);
            $ifNoneMatchRaw = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? (string) $_SERVER['HTTP_IF_NONE_MATCH'] : '';
            if ($ifNoneMatchRaw !== '' && self::httpEtagHeaderMatchesStrongTag($ifNoneMatchRaw, $etag)) {
                $out = self::newNotModifiedRestResponse($etag);
            }
        }

        return $out;
    }

    public static function shouldEmitPublicListCacheControl(\WP_REST_Request $request, string $route): bool
    {
        $emit = true;

        $no_cache = $request->get_param('no_cache');
        if (!empty($no_cache) && (string) $no_cache !== '0') {
            $emit = false;
        }

        if ($emit && self::isThemesUserMutableRestRoute($route)) {
            $emit = false;
        }

        if ($emit) {
            $cacheablePatterns = [
                '/wp/v2/posts',
                '/wp/v2/pages',
                '/wp/v2/comments',
                '/wp/v2/media',
                '/wp/v2/categories',
                '/wp/v2/tags',
                self::API_NAMESPACE,
            ];
            if (\function_exists('apply_filters')) {
                $cacheablePatterns = \apply_filters('fleekdash_cacheable_rest_patterns', $cacheablePatterns, $request);
            }
            $matchesPattern = false;
            foreach ($cacheablePatterns as $pattern) {
                if (strpos($route, $pattern) !== false) {
                    $matchesPattern = true;
                    break;
                }
            }
            if (!$matchesPattern || self::isSessionSensitiveRoute($route)) {
                $emit = false;
            }
        }

        return $emit;
    }
}
