<?php
/**
 * OptionsService class for handling RewardsWP settings options.
 *
 * @since 1.0.0
 *
 * @package Awesomemotive\Rewardswp\Services
 */

namespace Awesomemotive\Rewardswp\Services;

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

/**
 * Service class to handle options management for RewardsWP.
 *
 * This class provides methods to retrieve, update and manage settings options,
 * with support for lazy updates and caching.
 *
 * @since 1.0.0
 */
class OptionsService {

	/**
	 * Option name used to save into the options table.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	const OPTION_NAME = 'rewardswp_settings';

	/**
	 * Whether it should autoload the options or not.
	 *
	 * @since 1.0.0
	 * @var bool
	 */
	private bool $autoload = false;

	/**
	 * Store the options cached version.
	 *
	 * The intention is to avoid multiple queries to the database.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private array $cache = [];

	/**
	 * Flag to enable or disable lazy updating.
	 *
	 * If true, options will only be saved on destruct.
	 *
	 * @since 1.0.0
	 * @var bool
	 */
	public bool $lazy_update = false;

	/**
	 * Tracks if there are any pending changes to be saved.
	 *
	 * @since 1.0.0
	 * @var bool
	 */
	private bool $has_pending_updates = false;

	/**
	 * Destructor to save the options if lazy update is enabled and there are pending updates.
	 *
	 * Useful if we want to use update_option() method for each option in loops for example,
	 * instead of executing multiple queries against the database, only one query will be done
	 * at the end of the execution, saving all updated options.
	 *
	 * @since 1.0.0
	 */
	public function __destruct() {
		// Early return if lazy_update is disabled or there are no pending updates.
		if ( ! $this->lazy_update || ! $this->has_pending_updates ) {
			return;
		}

		$this->save();
	}

	/**
	 * Retrieve the cached options or fetch them from the database if not cached.
	 *
	 * @since 1.0.0
	 *
	 * @return array The options array.
	 */
	public function get_options(): array {
		if ( empty( $this->cache ) ) {
			$this->cache = (array) maybe_unserialize( get_option( self::OPTION_NAME, [] ) );
		}

		return $this->cache;
	}

	/**
	 * Retrieve a single option value.
	 *
	 * @since 1.0.0
	 *
	 * @param string $name  The option name.
	 * @param string $group The option group. Default is an empty string.
	 *
	 * @return mixed|null The option value, or null if not found.
	 */
	public function get_option( string $name, string $group = '' ) {
		if ( '' === $group ) {
			return $this->get_options()[ $name ] ?? null;
		}

		return $this->get_options()[ $group ][ $name ] ?? null;
	}

	/**
	 * Update all options at once.
	 *
	 * @since 1.0.0
	 *
	 * @param array $options The options array to update.
	 * @return void
	 */
	public function update_options( array $options ): void {
		$this->cache = $options;
		$this->save();
	}

	/**
	 * Update a single option value.
	 *
	 * @since 1.0.0
	 *
	 * @param string $name  The option name.
	 * @param mixed  $value The value to save.
	 * @param string $group The option group. Default is an empty string.
	 * @return void
	 */
	public function update_option( string $name, $value, string $group = '' ): void {
		$this->cache = array_replace_recursive(
			$this->get_options(),
			'' === $group
				? [ $name => $value ]
				: [ $group => [ $name => $value ] ]
		);

		// If lazy update is enabled, mark as pending instead of saving immediately.
		if ( $this->lazy_update ) {
			$this->has_pending_updates = true;
			return; // Bail, preventing from saving right away.
		}

		$this->save();
	}

	/**
	 * Save the cached options to the database.
	 *
	 * @since 1.0.0
	 * @return void
	 */
	private function save(): void {
		update_option( self::OPTION_NAME, $this->cache, $this->autoload );
		$this->has_pending_updates = false;
	}
}
