<?php
/**
 * RewardsOption.php.
 *
 * @package RewardsWP
 */

namespace Awesomemotive\Rewardswp\Settings\Options\Referrals;

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

use Awesomemotive\Rewardswp\Abstracts\AbstractOption;
use Awesomemotive\Rewardswp\Interfaces\OptionInterface;
use Awesomemotive\Rewardswp\Services\RewardsService;

use function Awesomemotive\Rewardswp\Helpers\can_access_rewardswp_feature;

/**
 * Class RewardsOption.
 */
class RewardsOption extends AbstractOption implements OptionInterface {
	/**
	 * The contexts available.
	 *
	 * @since 1.0.0
	 *
	 * @var string
	 */
	const CONTEXTS = [ 'advocate', 'friend' ];

	/**
	 * Get the setting key used in the option array.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public function get_name(): string {
		return 'rewards';
	}

	/**
	 * Get the setting group.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public function get_group(): string {
		return 'referrals';
	}

	/**
	 * Get the default value for the setting.
	 *
	 * @since 1.0.0
	 *
	 * @return array
	 */
	public function get_default_value(): array {
		/**
		 * Context keys list.
		 *
		 * @var array $contexts
		 */
		$contexts = self::CONTEXTS;
		return array_fill_keys( $contexts, null );
	}

	/**
	 * Get the label for the setting.
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public function get_label(): string {
		return esc_html__( 'Rewards', 'rewardswp' );
	}

	/**
	 * Get the type of the setting (text, textarea, select, etc).
	 *
	 * @since 1.0.0
	 *
	 * @return string
	 */
	public function get_type(): string {
		return 'rewards_ui';
	}

	/**
	 * Get the current value for the option.
	 *
	 * Overrides parent to sanitize Pro-only fields when user lacks feature access.
	 * The DB value is preserved for future upgrades, but the returned value falls back to defaults.
	 *
	 * @since 1.1.0
	 *
	 * @return array
	 */
	public function get_value(): array {
		$value = parent::get_value();

		// If user has access to referral_reward_expiration, return value as-is.
		if ( can_access_rewardswp_feature( 'referral_reward_expiration' ) ) {
			return $value;
		}

		// User lacks access - reset reward_expiration to default for each context.
		$default_expiration = RewardsService::DEFAULT_REFERRAL_REWARD_EXPIRATION;

		foreach ( self::CONTEXTS as $context ) {
			if ( isset( $value[ $context ] ) && is_array( $value[ $context ] ) ) {
				$value[ $context ]['reward_expiration'] = $default_expiration;
			}
		}

		return $value;
	}

	/**
	 * Ensure an int is returned when sanitizing.
	 *
	 * @since 1.0.0
	 *
	 * @param mixed $value The value to be sanitized.
	 *
	 * @return array
	 */
	public function sanitize( $value ): array {
		$sanitized_rewards = $this->get_default_value();

		/**
		 * Context keys list.
		 *
		 * @var array $contexts
		 */
		$contexts = self::CONTEXTS;

		foreach ( $contexts as $context ) {
			// Check if context exists in the input data and ensure $value is an array.
			if ( ! is_array( $value ) || ! isset( $value[ $context ] ) || ! is_array( $value[ $context ] ) ) {
				$sanitized_rewards[ $context ] = null;
				continue;
			}

			$reward_type = sanitize_text_field( $value[ $context ]['type'] ?? '' );

			// Prevent 'points' type for friend rewards - points are only for advocates.
			if ( 'friend' === $context && 'points' === $reward_type ) {
				$sanitized_rewards[ $context ] = null;
				continue;
			}

			$args = [
				'name'                 => sanitize_text_field( $value[ $context ]['name'] ?? '' ),
				'type'                 => $reward_type,
				'auto_disabled'        => (bool) ( $value[ $context ]['auto_disabled'] ?? false ),
				'reward_expiration'    => sanitize_text_field( $value[ $context ]['reward_expiration'] ?? '' ),
				'reward_amount'        => floatval( $value[ $context ]['reward_amount'] ?? 0 ),
				'minimum_order_amount' => floatval( $value[ $context ]['minimum_order_amount'] ?? 0 ),
				'products_included'    => $this->sanitize_products_included( $value[ $context ]['products_included'] ?? [] ),
				'reward_description'   => sanitize_textarea_field( $value[ $context ]['reward_description'] ?? '' ),
				'redemption_limit'     => $this->sanitize_redemption_limit( $value[ $context ]['redemption_limit'] ?? '1' ),
			];

			// Force unlimited redemption for free_product rewards.
			// - Friend: Already limited to 1 claim per visit/IP naturally.
			// - Advocate: Always unlimited to avoid frustrating advocates.
			if ( 'free_product' === $reward_type ) {
				$args['redemption_limit'] = 'unlimited';
			}

			$sanitized_rewards[ $context ] = empty( array_filter( $args ) )
				? null
				: $args;
		}

		return $sanitized_rewards;
	}

	/**
	 * Sanitize products_included data.
	 *
	 * Handles both JSON string format from the frontend and array format.
	 * The frontend sends JSON like '[{"id":14,"name":"iPhone"}]'.
	 * Returns an array of product objects with id, name, and optionally in_stock.
	 *
	 * @since 1.0.0
	 * @since 1.2.0 Added support for in_stock property (Free Product stock status).
	 * @since 1.2.0 Added source property to track integration origin.
	 *
	 * @param mixed $products_data The products data to sanitize.
	 *
	 * @return array Array of product objects with id, name, source, and optionally in_stock.
	 */
	private function sanitize_products_included( $products_data ): array {
		// If it's a JSON string, decode it.
		if ( is_string( $products_data ) ) {
			$decoded = json_decode( $products_data, true );
			if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
				$products_data = $decoded;
			} else {
				// Try comma-separated string as fallback (legacy format).
				$products_data = array_filter( explode( ',', $products_data ) );
			}
		}

		if ( ! is_array( $products_data ) ) {
			return [];
		}

		$products = [];
		foreach ( $products_data as $product ) {
			if ( is_array( $product ) && isset( $product['id'] ) ) {
				// Handle object format: {id: 14, name: "iPhone"} - store as objects like SpendActionsOption.
				$sanitized = [
					'id'     => absint( $product['id'] ),
					'name'   => isset( $product['name'] ) ? sanitize_text_field( $product['name'] ) : '',
					'source' => $this->integration->get_source(),
				];

				// Preserve in_stock if present.
				if ( isset( $product['in_stock'] ) ) {
					$sanitized['in_stock'] = (bool) $product['in_stock'];
				}

				$products[] = $sanitized;
			} elseif ( is_numeric( $product ) ) {
				// Handle legacy integer/string ID format - convert to object format.
				$products[] = [
					'id'     => absint( $product ),
					'name'   => '', // Name will be loaded from products_cache.
					'source' => $this->integration->get_source(),
				];
			}
		}

		return array_values( array_filter( $products, fn( $p ) => ! empty( $p['id'] ) ) );
	}

	/**
	 * Sanitize redemption limit value.
	 *
	 * @since 1.2.0
	 *
	 * @param mixed $value The value to sanitize.
	 *
	 * @return string Valid redemption limit or default '1'.
	 */
	private function sanitize_redemption_limit( $value ): string {
		$valid_limits = [ '1', 'unlimited' ];

		$value = sanitize_text_field( $value );

		return in_array( $value, $valid_limits, true ) ? $value : '1';
	}
}
