<?php

declare(strict_types=1);

namespace ShieldX\Core;

defined('ABSPATH') || exit;

final class RequestCache
{
    /** @var array<string,object> */
    private static $instances = [];

    /** @var array<string,array<string,bool>> */
    private static $optionClasses = [];

    /** @var bool */
    private static $hooksRegistered = false;

    /** @var string|null */
    private static $testOptionsSignature;

    private function __construct()
    {
    }

    public static function registerOptionInvalidationHooks(): void
    {
        if (self::$hooksRegistered || !function_exists('add_action')) {
            return;
        }

        add_action('updated_option', [self::class, 'forgetUpdatedOption'], 10, 3);
        add_action('added_option', [self::class, 'forgetAddedOption'], 10, 2);
        add_action('deleted_option', [self::class, 'forgetDeletedOption'], 10, 1);

        self::$hooksRegistered = true;
    }

    public static function get(string $key): ?object
    {
        self::resetWhenTestOptionsChange();

        return self::$instances[$key] ?? null;
    }

    public static function remember(string $key, object $instance): object
    {
        self::resetWhenTestOptionsChange();
        self::$instances[$key] = $instance;

        return $instance;
    }

    public static function rememberOption(string $key, string $optionName, object $instance): object
    {
        self::resetWhenTestOptionsChange();
        self::$optionClasses[$optionName][$key] = true;
        self::$instances[$key] = $instance;

        return $instance;
    }

    public static function forget(string $key): void
    {
        unset(self::$instances[$key]);
    }

    public static function forgetOption(string $optionName): void
    {
        self::resetWhenTestOptionsChange();
        $classes = self::$optionClasses[$optionName] ?? [];

        foreach (array_keys($classes) as $key) {
            unset(self::$instances[$key]);
        }

        unset(self::$optionClasses[$optionName]);
    }

    public static function clear(): void
    {
        self::$instances = [];
        self::$optionClasses = [];
        self::$testOptionsSignature = null;
    }

    /**
     * @param mixed $optionName
     * @param mixed $oldValue
     * @param mixed $newValue
     */
    public static function forgetUpdatedOption($optionName, $oldValue = null, $newValue = null): void
    {
        unset($oldValue, $newValue);
        self::forgetOption((string) $optionName);
    }

    /**
     * @param mixed $optionName
     * @param mixed $value
     */
    public static function forgetAddedOption($optionName, $value = null): void
    {
        unset($value);
        self::forgetOption((string) $optionName);
    }

    /**
     * @param mixed $optionName
     */
    public static function forgetDeletedOption($optionName): void
    {
        self::forgetOption((string) $optionName);
    }

    private static function resetWhenTestOptionsChange(): void
    {
        if (!array_key_exists('shieldx_test_options', $GLOBALS)) {
            return;
        }

        $encoded = wp_json_encode($GLOBALS['shieldx_test_options']);
        $signature = is_string($encoded) ? md5($encoded) : '';
        if (self::$testOptionsSignature === null) {
            self::$testOptionsSignature = $signature;

            return;
        }

        if ($signature === self::$testOptionsSignature) {
            return;
        }

        self::$instances = [];
        self::$optionClasses = [];
        self::$testOptionsSignature = $signature;
    }
}
