<?php
/**
 * Cache module — Safe Mode (kill switch)
 *
 * Three-layer kill switch ensuring the cache can be bypassed even if the
 * site is broken:
 *
 *   1. PHP constant `BE_CACHE_SAFE_MODE` → checked by drop-in and PHP code.
 *   2. File flag at `{cache_dir}/.safe_mode` → checked by drop-in (very early).
 *   3. Admin-only query string `?be_no_cache=1` → bypass current request only.
 *
 * Layer 1 wins over the others. Layer 2 is the recommended user-facing toggle
 * (no need to edit wp-config). Layer 3 is for live debugging.
 *
 * @package BlaminhorEssentials
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Blaminhor_Essentials_Cache_Safe_Mode {

	/**
	 * Storage helper (resolved lazily because Storage may not exist yet at
	 * Safe_Mode construction time during the bootstrap order).
	 *
	 * @var Blaminhor_Essentials_Cache_Storage|null
	 */
	private $storage;

	/**
	 * @param Blaminhor_Essentials_Cache_Storage|null $storage Optional, used to compute the flag path.
	 */
	public function __construct( $storage = null ) {
		$this->storage = $storage;
	}

	public function set_storage( Blaminhor_Essentials_Cache_Storage $storage ) {
		$this->storage = $storage;
	}

	/**
	 * Hooks for the admin-only `?be_no_cache=1` bypass.
	 */
	public function register_hooks() {
		// We expose this only in admin or for logged-in users with manage_options
		// to avoid letting random visitors trivially defeat the cache.
		add_action( 'init', array( $this, 'maybe_set_runtime_constant' ), 0 );
	}

	/**
	 * If the admin requested ?be_no_cache=1, define the constant for this request.
	 * Doing it on `init` is too late for the drop-in but in time for our PHP
	 * branches (page cache OB, asset optimizers, etc.) to honour it.
	 */
	public function maybe_set_runtime_constant() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only flag, no state mutation.
		if ( ! isset( $_GET['be_no_cache'] ) ) {
			return;
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}
		if ( ! defined( 'BE_CACHE_SAFE_MODE' ) ) {
			define( 'BE_CACHE_SAFE_MODE', true );
		}
	}

	/**
	 * Whether the cache is bypassed for THIS request.
	 *
	 * @return bool
	 */
	public function is_active() {
		if ( defined( 'BE_CACHE_SAFE_MODE' ) && BE_CACHE_SAFE_MODE ) {
			return true;
		}
		if ( $this->storage && file_exists( $this->storage->safe_mode_flag_path() ) ) {
			return true;
		}
		return false;
	}

	/**
	 * Whether the persistent file flag is set (regardless of constants).
	 *
	 * @return bool
	 */
	public function flag_exists() {
		if ( ! $this->storage ) {
			return false;
		}
		return file_exists( $this->storage->safe_mode_flag_path() );
	}

	/**
	 * Create the file flag.
	 *
	 * @param string $reason Free-form reason recorded in the file.
	 * @return bool
	 */
	public function enable( $reason = '' ) {
		if ( ! $this->storage ) {
			return false;
		}
		$path = $this->storage->safe_mode_flag_path();
		// Ensure the parent directory exists.
		blaminhor_essentials_ensure_protected_directory( dirname( $path ) );
		$payload = wp_json_encode( array(
			'set_at' => gmdate( 'c' ),
			'reason' => is_string( $reason ) ? mb_substr( $reason, 0, 500 ) : '',
			'by'     => is_user_logged_in() ? get_current_user_id() : 0,
		) );
		return false !== @file_put_contents( $path, $payload, LOCK_EX );
	}

	/**
	 * Remove the file flag.
	 *
	 * @return bool True if the flag is gone after the call.
	 */
	public function disable() {
		if ( ! $this->storage ) {
			return false;
		}
		$path = $this->storage->safe_mode_flag_path();
		if ( ! file_exists( $path ) ) {
			return true;
		}
		return @unlink( $path );
	}
}
