<?php
/**
 * Durable Cache — write-through storage for non-recoverable data.
 *
 * Stores data in both wp_options (reliable, survives cache flushes)
 * and WordPress object cache (fast RAM reads). Reads hit the cache
 * first; on miss they fall back to the database and re-populate
 * the cache for the next read.
 *
 * Use this instead of raw set_transient/get_transient whenever the
 * stored data CANNOT be regenerated from another source (chat history,
 * notification queues, OAuth sessions, etc.).
 *
 * Cache-only data (store overviews, model lists, etc.) that CAN be
 * regenerated should continue using regular transients.
 *
 * @package Niwa_AI
 * @since   0.10.74
 */

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

class Niwa_AI_Durable_Cache {

	/**
	 * Object-cache group used by this class.
	 */
	const CACHE_GROUP = 'niwa_ai_durable';

	/**
	 * Store a value in both the database and the object cache.
	 *
	 * @param string $key   Unique key (will be used as the wp_option name).
	 * @param mixed  $value Data to store (must be serializable).
	 * @param int    $ttl   Time-to-live in seconds.
	 */
	public static function set( $key, $value, $ttl ) {
		$ttl = max( 1, intval( $ttl ) );
		$wrapped = array(
			'd' => $value,
			'e' => time() + $ttl,
		);

		// Primary: database (reliable). update_option() also returns false when
		// the same value is already stored, so verify the persisted value.
		$updated = update_option( $key, $wrapped, false );
		wp_cache_delete( $key, 'options' );
		$stored = get_option( $key, false );
		if ( ! $updated && $stored !== $wrapped ) {
			wp_cache_delete( $key, self::CACHE_GROUP );
			return false;
		}

		// Secondary: object cache (fast).
		wp_cache_set( $key, $wrapped, self::CACHE_GROUP, $ttl );
		return true;
	}

	/**
	 * Retrieve a value, trying the object cache first, then the database.
	 *
	 * @param string $key Unique key.
	 * @return mixed The stored value, or false if not found / expired.
	 */
	public static function get( $key ) {
		// Fast path: object cache.
		$cached = wp_cache_get( $key, self::CACHE_GROUP );
		if ( is_array( $cached ) && isset( $cached['e'] ) ) {
			if ( $cached['e'] > time() ) {
				return $cached['d'];
			}
			// Expired in cache — clean up.
			wp_cache_delete( $key, self::CACHE_GROUP );
		}

		// Slow path: database.
		$stored = get_option( $key, false );
		if ( ! is_array( $stored ) || ! isset( $stored['e'] ) ) {
			return false;
		}

		if ( $stored['e'] <= time() ) {
			delete_option( $key );
			return false;
		}

		// Re-populate cache for next read.
		$remaining_ttl = max( 1, $stored['e'] - time() );
		wp_cache_set( $key, $stored, self::CACHE_GROUP, $remaining_ttl );

		return $stored['d'];
	}

	/**
	 * Delete a value from both the database and the object cache.
	 *
	 * @param string $key Unique key.
	 */
	public static function delete( $key ) {
		delete_option( $key );
		wp_cache_delete( $key, self::CACHE_GROUP );
	}
}
