<?php

namespace Dobare\Utils;

use Dobare\Abstracts\Dobare_REST_Controller;
use Dobare\Core\Dobare_Core;
use Dobare\Core\Dobare_Membership;
use Exception;

class Dobare_Utils
{

	/**
	 * In-request performance events collected for Dobare credit flows.
	 *
	 * @var array<int, array{name:string,dur_ms:float,context:array<string,mixed>}>
	 */
	private static $dobare_perf_events = array();

	/**
	 * Request start timestamp (seconds, microtime true) for perf total.
	 *
	 * @var float|null
	 */
	private static $dobare_perf_request_start = null;

	/**
	 * Cached perf enabled flag for the current request.
	 *
	 * @var bool|null
	 */
	private static $dobare_perf_enabled = null;

	/**
	 * Cached trace id for the current request.
	 *
	 * @var string|null
	 */
	private static $dobare_perf_trace_id = null;

	/**
	 * Determine whether Dobare credit perf logging is enabled for this request.
	 *
	 * Enable options (any one):
	 * - Define `DOBARE_CREDIT_PERF` as true in wp-config.php
	 * - Add query param `dobare_credit_perf=1`
	 * - Send request header `X-Dobare-Credit-Perf: 1`
	 * - If enabled once, it is persisted in WooCommerce session for a short window.
	 *
	 * @return bool
	 */
	public static function credit_perf_is_enabled(): bool
	{
		if (self::$dobare_perf_enabled !== null) {
			// If the value was computed before WooCommerce session initialization, allow a late enable.
			if (!self::$dobare_perf_enabled && function_exists('WC') && WC()->session) {
				$until = (int) WC()->session->get('dobare_credit_perf_until', 0);
				if ($until > time()) {
					self::$dobare_perf_enabled = true;
				}
			}
			return (bool) self::$dobare_perf_enabled;
		}

		$enabled = false;

		if (defined('DOBARE_CREDIT_PERF') && DOBARE_CREDIT_PERF) {
			$enabled = true;
		}

		// Header-based enable.
		if (!$enabled) {
			$header_value = isset($_SERVER['HTTP_X_DOBARE_CREDIT_PERF'])
				? sanitize_text_field(wp_unslash($_SERVER['HTTP_X_DOBARE_CREDIT_PERF']))
				: '';
			if ($header_value === '1' || strtolower($header_value) === 'true') {
				$enabled = true;
			}
		}

		// Query param based enable.
		if (!$enabled) {
			$param = isset($_GET['dobare_credit_perf']) ? sanitize_text_field(wp_unslash($_GET['dobare_credit_perf'])) : '';
			if ($param === '1' || strtolower($param) === 'true') {
				$enabled = true;
			}
		}

		// Session-based enable (persisted for a short window).
		if (!$enabled && function_exists('WC') && WC()->session) {
			$until = (int) WC()->session->get('dobare_credit_perf_until', 0);
			if ($until > time()) {
				$enabled = true;
			}
		}

		// If enabled by header/query, persist for subsequent checkout ajax calls.
		if ($enabled) {
			self::credit_perf_enable_for_session();
		}

		self::$dobare_perf_enabled = $enabled;

		return $enabled;
	}

	/**
	 * Persist perf logging enablement in WooCommerce session.
	 *
	 * @param int $ttl_seconds
	 * @return void
	 */
	public static function credit_perf_enable_for_session(int $ttl_seconds = 600): void
	{
		if (!function_exists('WC') || !WC()->session) {
			return;
		}

		$ttl_seconds = max(30, $ttl_seconds);
		WC()->session->set('dobare_credit_perf_until', time() + $ttl_seconds);
	}

	/**
	 * Ensure perf is initialized for the current request.
	 *
	 * @return void
	 */
	private static function credit_perf_bootstrap(): void
	{
		if (self::$dobare_perf_request_start === null) {
			self::$dobare_perf_request_start = microtime(true);
		}

		// Initialize trace id early when enabled.
		if (self::credit_perf_is_enabled()) {
			self::credit_perf_get_trace_id();
		}
	}

	/**
	 * Get (or create) a trace id used to correlate multiple requests in a single checkout flow.
	 *
	 * @return string
	 */
	public static function credit_perf_get_trace_id(): string
	{
		if (self::$dobare_perf_trace_id !== null && self::$dobare_perf_trace_id !== '') {
			return (string) self::$dobare_perf_trace_id;
		}

		$trace_id = '';
		if (function_exists('WC') && WC()->session) {
			$trace_id = (string) WC()->session->get('dobare_credit_trace_id', '');
		}

		if ($trace_id === '') {
			try {
				$trace_id = bin2hex(random_bytes(8));
			} catch (\Exception $e) {
				$trace_id = (string) wp_generate_password(16, false, false);
			}
		}

		if (function_exists('WC') && WC()->session) {
			WC()->session->set('dobare_credit_trace_id', $trace_id);
		}

		self::$dobare_perf_trace_id = $trace_id;

		return $trace_id;
	}

	/**
	 * Start a perf timer.
	 *
	 * @param string $name
	 * @return float Timestamp (seconds, microtime true)
	 */
	public static function credit_perf_start(string $name): float
	{
		self::credit_perf_bootstrap();

		return microtime(true);
	}

	/**
	 * End a perf timer and record it.
	 *
	 * @param string $name
	 * @param float  $started_at
	 * @param array  $context
	 * @return void
	 */
	public static function credit_perf_end(string $name, float $started_at, array $context = array()): void
	{
		self::credit_perf_bootstrap();

		$dur_ms = max(0, (microtime(true) - $started_at) * 1000);
		self::credit_perf_record($name, $dur_ms, $context);
	}

	/**
	 * Record a perf event.
	 *
	 * @param string $name
	 * @param float  $dur_ms
	 * @param array  $context
	 * @return void
	 */
	public static function credit_perf_record(string $name, float $dur_ms, array $context = array()): void
	{
		self::$dobare_perf_events[] = array(
			'name'    => $name,
			'dur_ms'  => round($dur_ms, 2),
			'context' => $context,
		);
	}

	/**
	 * Export perf payload for debug purposes.
	 *
	 * @return array{trace_id:string,total_ms:float,events:array<int,array{name:string,dur_ms:float,context:array<string,mixed>}>}
	 */
	public static function credit_perf_export(): array
	{
		self::credit_perf_bootstrap();

		$total_ms = 0.0;
		if (self::$dobare_perf_request_start !== null) {
			$total_ms = round(max(0, (microtime(true) - self::$dobare_perf_request_start) * 1000), 2);
		}

		return array(
			'trace_id' => self::credit_perf_get_trace_id(),
			'total_ms' => $total_ms,
			'events'   => self::$dobare_perf_events,
		);
	}

	/**
	 * Attach perf headers to a REST response.
	 *
	 * @param \WP_REST_Response $response
	 * @return \WP_REST_Response
	 */
	public static function credit_perf_attach_rest_headers(\WP_REST_Response $response): \WP_REST_Response
	{
		if (!self::credit_perf_is_enabled()) {
			return $response;
		}

		$trace_id = self::credit_perf_get_trace_id();
		$header_value = self::credit_perf_build_server_timing_header_value();

		$response->header('X-Dobare-Trace-Id', $trace_id);
		if ($header_value !== '') {
			$response->header('Server-Timing', $header_value);
		}

		return $response;
	}

	/**
	 * Emit perf headers for non-REST requests (e.g., WooCommerce checkout AJAX).
	 *
	 * @return void
	 */
	public static function credit_perf_emit_headers(): void
	{
		if (!self::credit_perf_is_enabled()) {
			return;
		}

		if (headers_sent()) {
			return;
		}

		$trace_id = self::credit_perf_get_trace_id();
		$header_value = self::credit_perf_build_server_timing_header_value();

		header('X-Dobare-Trace-Id: ' . $trace_id);
		if ($header_value !== '') {
			header('Server-Timing: ' . $header_value);
		}
	}

	/**
	 * Log perf payload to WooCommerce logs when enabled.
	 *
	 * @param string $scope
	 * @param array  $extra_context
	 * @return void
	 */
	public static function credit_perf_maybe_log(string $scope, array $extra_context = array()): void
	{
		if (!self::credit_perf_is_enabled()) {
			return;
		}

		$payload = self::credit_perf_export();
		$payload['scope'] = $scope;
		$payload['extra'] = $extra_context;

		$message = wp_json_encode($payload);
		if ($message === false) {
			$message = 'Dobare credit perf: failed to encode payload';
		}

		if (function_exists('wc_get_logger')) {
			$logger = wc_get_logger();
			$logger->info($message, array('source' => 'dobare-credit-perf'));
			return;
		}

		// Fallback: PHP error log.
		@error_log($message);
	}

	/**
	 * Build a compact Server-Timing header value based on recorded perf events.
	 *
	 * @return string
	 */
	private static function credit_perf_build_server_timing_header_value(): string
	{
		self::credit_perf_bootstrap();

		if (empty(self::$dobare_perf_events)) {
			return '';
		}

		$payload = self::credit_perf_export();
		$events = $payload['events'];

		// Take the top N events by duration to avoid huge headers.
		usort(
			$events,
			static function (array $a, array $b): int {
				return ($b['dur_ms'] <=> $a['dur_ms']);
			}
		);

		$max_events = 12;
		$events = array_slice($events, 0, $max_events);

		$parts = array();
		$used_names = array();

		$total_name = 'dobare_total';
		$parts[] = $total_name . ';dur=' . $payload['total_ms'];
		$used_names[$total_name] = 1;

		foreach ($events as $event) {
			$raw_name = isset($event['name']) ? (string) $event['name'] : 'event';
			$metric_name = 'dobare_' . self::credit_perf_metric_token($raw_name);
			if (isset($used_names[$metric_name])) {
				$used_names[$metric_name]++;
				$metric_name = $metric_name . '_' . (string) $used_names[$metric_name];
			} else {
				$used_names[$metric_name] = 1;
			}

			$dur = isset($event['dur_ms']) ? (float) $event['dur_ms'] : 0.0;
			$parts[] = $metric_name . ';dur=' . round($dur, 2);
		}

		return implode(', ', $parts);
	}

	/**
	 * Convert an arbitrary label into a valid Server-Timing metric token.
	 *
	 * @param string $name
	 * @return string
	 */
	private static function credit_perf_metric_token(string $name): string
	{
		$token = preg_replace('/[^A-Za-z0-9_\-\.]/', '_', $name);
		$token = is_string($token) ? $token : 'event';
		$token = trim($token, '_');

		if ($token === '') {
			$token = 'event';
		}

		// Keep headers short.
		if (strlen($token) > 40) {
			$token = substr($token, 0, 40);
		}

		return $token;
	}

	public static function dob_update_option($key, $value): bool
	{
		$result = \update_option('DOBARE-' . $key, $value);

		return $result;
	}

	public static function dob_get_option($key, $default_value = false)
	{
		// First try to get the option with the new 'DOBARE-' prefix to comply with WordPress.org plugin guidelines
		// WordPress.org requires prefixes to be longer than 3 characters to avoid conflicts with other plugins
		$value = \get_option('DOBARE-' . $key, null);

		// If the option doesn't exist with the new prefix, try the old prefix for backward compatibility
		if ($value === null) {
			// Check if the option exists with the old 'DOB-' prefix (which was too short per guidelines)
			$value = \get_option('DOB-' . $key, $default_value);

			// If found with old prefix, automatically migrate it to the new prefix
			// This ensures a smooth upgrade path for existing installations
			if ($value !== $default_value) {
				// Save the value with the new prefix
				self::dob_update_option($key, $value);
			}
			return $value;
		}

		return $value;
	}

	/**
	 * Setter helper for the auto apply suppression flag with logging.
	 *
	 * @param bool   $suppressed New suppression state.
	 * @param string $reason     Reason for the change.
	 * @param array  $context    Additional context.
	 * @return void
	 */
	public static function set_auto_apply_suppression(bool $suppressed): void
	{
		if (!function_exists('WC') || !WC()->session) {
			return;
		}

		$session = WC()->session;
		$session->set('dobare_suppress_auto_apply', $suppressed);
		$suppressed_since_key = 'dobare_suppress_auto_apply_since';
		$previous_since = (int) $session->get($suppressed_since_key, 0);
		$duration = null;

		if ($suppressed) {
			if ($previous_since === 0) {
				$session->set($suppressed_since_key, time());
			}
		} else {
			if ($previous_since > 0) {
				$duration = max(0, time() - $previous_since);
			}
			$session->set($suppressed_since_key, null);
		}
	}

	public static function set_dob_api_key(string $api_key): bool
	{
		self::dob_update_option('ONBOARDED', true);

		return self::dob_update_option(DOBARE_API_KEY_OPTION_KEY, $api_key);
	}

	public static function get_dob_api_key()
	{
		// Try to get API key with new key name
		$api_key = self::dob_get_option(DOBARE_API_KEY_OPTION_KEY);
		
		// BACKWARD COMPATIBILITY FALLBACK:
		// We changed the constant from "API-KEY" to "dobare_api_key" for WordPress.org compliance.
		// Since merchants are actively using the plugin, we must check the old key name
		// to prevent forcing them to logout and re-enter their API key.
		// This fallback ensures a smooth upgrade path for existing installations.
		if (empty($api_key)) {
			// Check for old key name "API-KEY"
			$old_api_key = self::dob_get_option('API-KEY');
			if (!empty($old_api_key)) {
				// Automatically migrate to new key name for future requests
				self::dob_update_option(DOBARE_API_KEY_OPTION_KEY, $old_api_key);
				return $old_api_key;
			}
		}
		
		return $api_key;
	}

	public static function has_been_onboarded(): bool
	{
		return self::dob_get_option('ONBOARDED');
	}

	/**
	 * Sets the state of the plugin by sending relevant data to a remote endpoint.
	 *
	 * @param string $state The current state to set.
	 *
	 * @return void
	 */
	public static function set_state(string $state): void
	{
		$user = wp_get_current_user();

		$body        = [
			'state'          => $state,
			'site_title'     => get_option('blogname', '-'),
			'site_domain'    => get_option('siteurl', '-'),
			'admin_email'    => get_option('admin_email', 'default@dobare.me'),
			'user_username' => $user->exists() ? $user->user_login : '-',
			'user_fullname' => $user->exists() ? (trim("{$user->first_name} {$user->last_name}") ?: '-') : '-',
			'ip_address'    => self::get_client_ip(),
			'plugin_version' => DOBARE_VERSION,
		];
		$business_id = self::get_dob_business_id();
		if (! empty($business_id)) {
			$body['business_id'] = $business_id;
		}
		try {
			Dobare_REST_Controller::dobare_remote_post('v1/integrations/wp/plugin-state/', $body);
		} catch (Exception $e) {
		}
	}

	/**
	 * Retrieves the client's IP address.
	 *
	 * @return string The client's IP address.
	 */
	private static function get_client_ip(): string
	{
		if (! empty($_SERVER['HTTP_CLIENT_IP'])) {
			$client_ip = sanitize_text_field(wp_unslash($_SERVER['HTTP_CLIENT_IP']));
			if (filter_var($client_ip, FILTER_VALIDATE_IP)) {
				return $client_ip;
			}
		}

		if (! empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
			$x_forwarded_for = sanitize_text_field(wp_unslash($_SERVER['HTTP_X_FORWARDED_FOR']));
			$ip_addresses    = explode(',', $x_forwarded_for);
			$first_ip        = sanitize_text_field(trim($ip_addresses[0]));
			if (filter_var($first_ip, FILTER_VALIDATE_IP)) {
				return $first_ip;
			}
		}

		$remote_addr = isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '0.0.0.0';
		if (filter_var($remote_addr, FILTER_VALIDATE_IP)) {
			return $remote_addr;
		}
		
		return '0.0.0.0';
	}

	public static function get_average_weekly_revenue()
	{
		$orders         = wc_get_orders(array(
			'limit'   => 1000,
			'status'  => array('wc-processing', 'wc-completed'),
			'orderby' => 'date',
			'order'   => 'DESC',
			'return'  => 'objects',

		));
		$weekly_revenue = array();

		foreach ($orders as $order_object) {
			$date      = $order_object->get_date_created();
			$year_week = $date->format('o-W');
			if (! isset($weekly_revenue[$year_week])) {
				$weekly_revenue[$year_week] = 0;
			}

			$weekly_revenue[$year_week] += $order_object->get_total();
		}
		$average_revenue = 0;
		if (! empty($weekly_revenue)) {
			$average_revenue = array_sum($weekly_revenue) / count($weekly_revenue);
		}

		return $average_revenue;
	}

	public static function normalize_response_body(array $response_body, int $status_code): array
	{
		return array(
			'meta' => array('status_code' => $status_code, 'paginated' => false),
			'data' => $response_body
		);
	}

	public static function normalize_error_body(array $error_messages, int $status_code): array
	{
		return array(
			'meta' => array(
				'status_code' => $status_code,
				'detail'      => array('global_error_messages' => $error_messages)
			),
		);
	}

	public static function get_sync_order_status()
	{
		return self::dob_get_option('sync_order_status', 'processing');
	}

	public static function set_sync_order_status($order_status): bool
	{
		return self::dob_update_option('sync_order_status', $order_status);
	}

	public static function apply_gift_credit(string $phone_number, bool $reload_checkout = true)
	{
		$timer_total = 0.0;
		if (self::credit_perf_is_enabled()) {
			$timer_total = self::credit_perf_start('apply_gift_credit');
		}

		if (!WC()->session || !WC()->cart) {
			if (self::credit_perf_is_enabled()) {
				self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'missing_wc'));
			}
			return;
		}

		// Respect temporary credit disable window
		if (self::is_credit_temporarily_disabled()) {
			self::remove_dobare_coupons();
			WC()->session->set('dobare_minimum_cart_met', false);
			self::remove_dobare_success_notices();
			if (self::credit_perf_is_enabled()) {
				self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'credit_disabled'));
			}
			return;
		}

		// Check for and then clear the validation failed flag to prevent interference.
		$validation_failed = (bool) WC()->session->get('dobare_validation_failed', false);
		if ($validation_failed) {
			WC()->session->set('dobare_validation_failed', null);
		}

		// Use session flag to prevent endless loops during cart calculations
		$processing_flag = WC()->session->get('dobare_processing_gift_credit', false);

		if ($processing_flag) {
			if (self::credit_perf_is_enabled()) {
				self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'processing_flag'));
			}
			return; // Exit early if already processing to prevent infinite loops
		}

		// Track the phone for which a Dobare coupon was last applied
		$applied_for_phone = WC()->session->get('dobare_last_phone_for_applied_credit', '');
		$minimum_cart_met = WC()->session->get('dobare_minimum_cart_met', false);

		$phone_changed = (!empty($applied_for_phone) && $applied_for_phone !== $phone_number);
		// Respect user suppression: if user removed coupon, do not auto re-apply unless phone changed
		$auto_suppressed = (bool) WC()->session->get('dobare_suppress_auto_apply', false);

		// Fast-path: if a Dobare coupon is already applied for this phone, avoid remote calls.
		$has_dobare_coupon = false;
		$applied_coupons = WC()->cart ? WC()->cart->get_applied_coupons() : array();
		foreach ($applied_coupons as $coupon_code) {
			if (self::is_dobare_coupon($coupon_code)) {
				$has_dobare_coupon = true;
				break;
			}
		}

		if ($has_dobare_coupon && ! $phone_changed) {
			// If a coupon exists but the minimum threshold is no longer met, ensure it is removed.
			$minimum_cart_total = self::get_minimum_cart_total_for_credit();
			$cart_total = WC()->cart ? floatval(WC()->cart->get_subtotal()) : 0;
			if ($cart_total < $minimum_cart_total) {
				self::remove_dobare_coupons();
				WC()->session->set('dobare_minimum_cart_met', false);
				if (self::credit_perf_is_enabled()) {
					self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'already_applied_below_minimum'));
				}
				return;
			}

			// Keep session state consistent to avoid repeated revalidation calls.
			WC()->session->set('dobare_credit_applied_for_phone', $phone_number);
			WC()->session->set('dobare_last_phone_for_applied_credit', $phone_number);
			if (! $minimum_cart_met) {
				WC()->session->set('dobare_minimum_cart_met', true);
			}

			if (self::credit_perf_is_enabled()) {
				self::credit_perf_record('coupon_strategy', 0.01, array('strategy' => 'already_applied'));
				self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'already_applied'));
			}
			return;
		}

		if ($auto_suppressed && ! $phone_changed) {
			if (self::credit_perf_is_enabled()) {
				self::credit_perf_record('coupon_strategy', 0.01, array('strategy' => 'suppressed_no_apply'));
				self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'suppressed'));
			}
			return;
		}

		// If phone changed, lift suppression so we can re-evaluate for the new phone
		if ($phone_changed && $auto_suppressed) {
			self::set_auto_apply_suppression(false);
		}

		$membership = Dobare_Core::get_membership($phone_number);

		if ($phone_changed || ! $membership instanceof Dobare_Membership || $membership->gift_credit_amount <= 0) {
			// Clear the minimum cart met flag when phone changes or membership is invalid
			if ($phone_changed) {
				WC()->session->set('dobare_minimum_cart_met', false);
				// Clear stale success notices when phone changes
				self::remove_dobare_success_notices();
			}
			// Only remove coupons if a final validation error did NOT just occur.
			// This prevents this function from clearing the coupon before the user sees the error.
			if (!$validation_failed) {
				self::remove_dobare_coupons();
			}
			// Update minimum cart met flag after removing coupons
			WC()->session->set('dobare_minimum_cart_met', false);
			// Reset last applied phone since coupon is no longer valid only if membership is invalid
			if (!$membership instanceof Dobare_Membership || $membership->gift_credit_amount <= 0) {
				WC()->session->set('dobare_last_phone_for_applied_credit', '');
			}
		}

		// Persist the current normalized phone for evaluation across requests
		WC()->session->set('dobare_credit_applied_for_phone', $phone_number);

		if ($membership instanceof Dobare_Membership && $membership->gift_credit_amount > 0) {
			// Check minimum cart total requirement
			$minimum_cart_total = self::get_minimum_cart_total_for_credit();
			// Use subtotal (before discounts) for minimum validation to ensure fair comparison
			$cart_total = WC()->cart ? floatval(WC()->cart->get_subtotal()) : 0;

			if ($cart_total < $minimum_cart_total) {
				// Remove coupons (callee manages processing flag)
				self::remove_dobare_coupons();
				// Update minimum cart met flag after removing coupons
				WC()->session->set('dobare_minimum_cart_met', false);


				// Show notice about minimum cart total requirement if minimum is greater than 0
				if ($minimum_cart_total > 0) {
					self::add_minimum_cart_notice($minimum_cart_total, $cart_total);
				}
				if (self::credit_perf_is_enabled()) {
					self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'below_minimum'));
				}
				return;
			}

			/**
			 * Filter whether Dobare credit should be applied to the current cart.
			 *
			 * Allows site owners to short-circuit the automatic coupon application or mutate the
			 * Dobare_Membership instance before the coupon is created. Returning false prevents
			 * Dobare credit from being applied for the current request.
			 *
			 * @param bool                 $should_apply   Whether credit should be applied (default true).
			 * @param Dobare_Membership    $membership     The resolved membership instance.
			 * @param string               $phone_number   Normalized phone number tied to the credit.
			 * @param \WC_Cart|null        $cart          The active WooCommerce cart instance.
			 * @param \WC_Session_Handler|\WC_Session|null $session       The WooCommerce session handler.
			 * @param array                $context        {
			 *     Optional extra details about the current evaluation.
			 *
			 *     @type float $cart_subtotal      Current cart subtotal before discounts.
			 *     @type int   $minimum_cart_total Minimum subtotal required for Dobare credit.
			 * }
			 */
			$filter_context = array(
				'cart_subtotal'      => $cart_total,
				'minimum_cart_total' => $minimum_cart_total
			);
			$should_apply_credit = (bool) apply_filters(
				'dobare_should_apply_credit',
				true,
				$membership,
				$phone_number,
				WC()->cart,
				WC()->session,
				$filter_context
			);

			if (! $should_apply_credit) {
				self::remove_dobare_coupons();
				WC()->session->set('dobare_minimum_cart_met', false);
				self::remove_dobare_success_notices();
				if (self::credit_perf_is_enabled()) {
					self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'filtered_out'));
				}
				return;
			}

			$applied_coupons       = WC()->cart->get_applied_coupons();
			$dobare_coupon_applied = false;
			foreach ($applied_coupons as $coupon_code) {
				if (self::is_dobare_coupon($coupon_code)) {
					$dobare_coupon_applied = true;
					break;
				}
			}

			if (! $dobare_coupon_applied) {
				if (self::credit_perf_is_enabled()) {
					self::credit_perf_record('coupon_strategy', 0.01, array('strategy' => 'created_new'));
				}
				$timer_coupon_create = 0.0;
				if (self::credit_perf_is_enabled()) {
					$timer_coupon_create = self::credit_perf_start('create_coupon_for_membership');
				}

				$coupon_code = Dobare_Core::create_coupon_for_membership($membership);

				if (self::credit_perf_is_enabled()) {
					self::credit_perf_end('create_coupon_for_membership', (float) $timer_coupon_create, array('created' => !empty($coupon_code)));
				}

				if (! empty($coupon_code)) {
					// Set processing flag before coupon operations
					WC()->session->set('dobare_processing_gift_credit', true);

					$timer_apply_coupon = 0.0;
					if (self::credit_perf_is_enabled()) {
						$timer_apply_coupon = self::credit_perf_start('wc_cart_apply_coupon');
					}

					$applied = WC()->cart->apply_coupon($coupon_code);

					if (self::credit_perf_is_enabled()) {
						self::credit_perf_end('wc_cart_apply_coupon', (float) $timer_apply_coupon, array('applied' => (bool) $applied));
					}

					if ($applied) {
						// Remove any existing minimum cart notices since credit is now applied
						self::remove_dobare_minimum_cart_notices();
						// Remove any stale success notices before adding a fresh one
						self::remove_dobare_success_notices();
						// Update minimum cart met flag after successfully applying coupon
						WC()->session->set('dobare_minimum_cart_met', true);
						// Track the phone for which the coupon was applied
						WC()->session->set('dobare_last_phone_for_applied_credit', $phone_number);
						// Add tagged success notice for easy identification and removal
						$notice_data = array(
							'dobare_notice_type' => 'credit_applied_success'
						);
						wc_add_notice('اعتبار هدیه شما اعمال شد.', 'success', $notice_data);
					} else {
						// If coupon application failed, ensure minimum cart met flag is false
						WC()->session->set('dobare_minimum_cart_met', false);
					}

					WC()->session->set('dobare_processing_gift_credit', false);
				}
			}
		}

		if (self::credit_perf_is_enabled()) {
			self::credit_perf_end('apply_gift_credit', (float) $timer_total, array('result' => 'ok'));
		}
	}

	public static function remove_dobare_coupons()
	{
		if (!WC()->session || !WC()->cart) {
			return;
		}

		// Use session flag to prevent endless loops - same as apply_gift_credit
		$processing_flag = WC()->session->get('dobare_processing_gift_credit', false);

		if ($processing_flag) {
			return; // Exit early if already processing to prevent infinite loops
		}

		$applied_coupons = WC()->cart->get_applied_coupons();
		$removed_any = false;

		// Set processing flag once before all coupon operations
		$dobare_coupons_to_remove = array();
		foreach ($applied_coupons as $coupon_code) {
			if (self::is_dobare_coupon($coupon_code)) {
				$dobare_coupons_to_remove[] = $coupon_code;
			}
		}

		if (!empty($dobare_coupons_to_remove)) {
			WC()->session->set('dobare_processing_gift_credit', true);

			foreach ($dobare_coupons_to_remove as $coupon_code) {
				WC()->cart->remove_coupon($coupon_code);
				$removed_any = true;
			}

			WC()->session->set('dobare_processing_gift_credit', false);
		}

		// If we removed dobare coupons, also remove any related notices
		// and update the minimum cart met flag
		if ($removed_any) {
			self::remove_dobare_minimum_cart_notices();
			self::remove_dobare_success_notices();
			// Update session flag since coupons were removed
			WC()->session->set('dobare_minimum_cart_met', false);
			// Keep last applied phone so we can detect actual changes reliably
		} else {
		}
	}

	/**
	 * Add minimum cart notice and prevent duplicates
	 *
	 * @param int $minimum_cart_total Minimum required cart total
	 * @param float $current_cart_total Current cart total
	 * @return void
	 */
	public static function add_minimum_cart_notice(int $minimum_cart_total, float $current_cart_total): void
	{
		// Remove any existing dobare minimum cart notices first
		self::remove_dobare_minimum_cart_notices();

		// Create the notice message
		$formatted_minimum = number_format($minimum_cart_total);
		$formatted_current = number_format($current_cart_total);
		$currency_word    = self::get_currency_word();
		$notice_message   = sprintf(
			'برای استفاده از اعتبار هدیه، حداقل مبلغ سبد خرید باید %s %s باشد. مبلغ فعلی سبد شما: %s %s',
			$formatted_minimum,
			$currency_word,
			$formatted_current,
			$currency_word
		);

		// If the exact notice already exists in this request cycle, avoid re-adding it
		if (function_exists('wc_has_notice') && wc_has_notice($notice_message, 'notice')) {
			return;
		}

		// Add unique identifier to the notice data for easy removal later
		$notice_data = array(
			'dobare_notice_type' => 'minimum_cart_total'
		);

		wc_add_notice($notice_message, 'notice', $notice_data);
	}

	/**
	 * Get readable currency word for copywriting based on WooCommerce currency
	 *
	 * Defaults to "تومان" unless the store currency is Rial (IRR or symbol ریال/﷼)
	 *
	 * @return string
	 */
	public static function get_currency_word(): string
	{
		// Prefer WooCommerce currency code when available
		if (function_exists('get_woocommerce_currency')) {
			$currency_code = strtoupper((string) get_woocommerce_currency());
			if ($currency_code === 'IRR') {
				return 'ریال';
			}
		}

		// Fallback to currency symbol heuristics
		if (function_exists('get_woocommerce_currency_symbol')) {
			$symbol = (string) get_woocommerce_currency_symbol();
			if (strpos($symbol, '﷼') !== false || strpos($symbol, 'ریال') !== false || strpos($symbol, 'ريال') !== false) {
				return 'ریال';
			}
		}

		return 'تومان';
	}

	/**
	 * Remove dobare minimum cart total notices
	 *
	 * @return void
	 */
	public static function remove_dobare_minimum_cart_notices(): void
	{
		$all_notices = wc_get_notices();

		if (empty($all_notices)) {
			return;
		}

		$legacy_phrase = 'حداقل مبلغ سبد خرید';
		$alt_phrase_1 = 'برای استفاده از اعتبار هدیه';
		$alt_phrase_2 = 'minimum cart total';
		foreach ($all_notices as $type => $type_notices) {
			if (!is_array($type_notices)) {
				continue;
			}

			$filtered = array();
			foreach ($type_notices as $notice) {
				$notice_data = isset($notice['data']) ? $notice['data'] : array();
				$notice_text = isset($notice['notice']) ? (string) $notice['notice'] : '';
				$has_tag = isset($notice_data['dobare_notice_type']) && $notice_data['dobare_notice_type'] === 'minimum_cart_total';
				$looks_like_legacy_minimum_notice = (!empty($notice_text) && (
					strpos($notice_text, $legacy_phrase) !== false ||
					strpos($notice_text, $alt_phrase_1) !== false ||
					stripos($notice_text, $alt_phrase_2) !== false
				));

				// Skip our tagged minimum notices and any legacy ones that match our message pattern
				if ($has_tag || $looks_like_legacy_minimum_notice) {
					continue;
				}

				$filtered[] = $notice;
			}

			$all_notices[$type] = $filtered;
		}

		// Always rebuild the WooCommerce notices store to ensure removal persists across requests
		wc_clear_notices();
		foreach ($all_notices as $type => $type_notices) {
			foreach ($type_notices as $notice) {
				$notice_data = isset($notice['data']) ? $notice['data'] : array();
				wc_add_notice($notice['notice'], $type, $notice_data);
			}
		}
	}

	/**
	 * Remove dobare success notices (credit applied, coupon applied, etc.)
	 *
	 * @return void
	 */
	public static function remove_dobare_success_notices(): void
	{
		$all_notices = wc_get_notices();

		if (empty($all_notices)) {
			return;
		}

		$success_phrase_1 = 'اعتبار هدیه شما اعمال شد';
		$success_phrase_2 = 'کد تخفیف با موفقیت اعمال شد';
		$removed_count = 0;
		
		foreach ($all_notices as $type => $type_notices) {
			if (!is_array($type_notices)) {
				continue;
			}

			$filtered = array();
			foreach ($type_notices as $notice) {
				$notice_data = isset($notice['data']) ? $notice['data'] : array();
				$notice_text = isset($notice['notice']) ? (string) $notice['notice'] : '';
				$has_tag = isset($notice_data['dobare_notice_type']) && $notice_data['dobare_notice_type'] === 'credit_applied_success';
				$looks_like_dobare_success = (!empty($notice_text) && (
					strpos($notice_text, $success_phrase_1) !== false ||
					strpos($notice_text, $success_phrase_2) !== false
				));

				// Skip our tagged success notices and any that match our message patterns
				if ($has_tag || $looks_like_dobare_success) {
					$removed_count++;
					continue;
				}

				$filtered[] = $notice;
			}

			$all_notices[$type] = $filtered;
		}

		// Always rebuild the WooCommerce notices store to ensure removal persists across requests
		wc_clear_notices();
		foreach ($all_notices as $type => $type_notices) {
			foreach ($type_notices as $notice) {
				$notice_data = isset($notice['data']) ? $notice['data'] : array();
				wc_add_notice($notice['notice'], $type, $notice_data);
			}
		}
	}

	/**
	 * Add the phone prompt notice (requesting user to input phone to see gift credit)
	 * Ensures duplicates are prevented by removing existing ones first.
	 *
	 * @return void
	 */
	public static function add_phone_prompt_notice(): void
	{
		self::remove_dobare_phone_prompt_notices();
		$notice_message = 'برای مشاهده اعتبار هدیه خود در باشگاه مشتریان شماره تلفن خود را در فرم صورتحساب وارد کنید';
		$notice_data = array(
			'dobare_notice_type' => 'phone_prompt',
		);
		wc_add_notice($notice_message, 'notice', $notice_data);
	}

	/**
	 * Remove phone prompt notices added by this plugin (identified by data key)
	 *
	 * @return void
	 */
	public static function remove_dobare_phone_prompt_notices(): void
	{
		$all_notices = wc_get_notices();

		if (empty($all_notices) || ! isset($all_notices['notice'])) {
			return;
		}

		$filtered_notices = array();
		foreach ($all_notices['notice'] as $notice) {
			$notice_data = isset($notice['data']) ? $notice['data'] : array();
			// Skip phone prompt notices
			if (isset($notice_data['dobare_notice_type']) && $notice_data['dobare_notice_type'] === 'phone_prompt') {
				continue;
			}
			$filtered_notices[] = $notice;
		}

		$all_notices['notice'] = $filtered_notices;
		if (function_exists('wc_set_notices')) {
			wc_set_notices($all_notices);
		} else {
			// Fallback: clear and re-add filtered notices
			wc_clear_notices();
			foreach ($all_notices as $type => $type_notices) {
				foreach ($type_notices as $notice) {
					$notice_data = isset($notice['data']) ? $notice['data'] : array();
					wc_add_notice($notice['notice'], $type, $notice_data);
				}
			}
		}
	}

	public static function has_valid_phone_for_abandoned_cart(): bool
	{
		return null !== self::get_abandoned_cart_phone();
	}

	public static function get_abandoned_cart_phone(): ?string
	{
		if (! function_exists('WC')) {
			return null;
		}

		$session = WC()->session;
		if (! $session) {
			return null;
		}

		$raw_phone = $session->get('dobare_credit_applied_for_phone');
		if (! is_string($raw_phone) || $raw_phone === '') {
			return null;
		}

		$normalized = self::validate_and_normalize_phone_number($raw_phone);

		return $normalized !== '' ? $normalized : null;
	}

	public static function is_valid_phone_format(string $phone): bool
	{
		return self::validate_and_normalize_phone_number($phone) !== '';
	}

	public static function validate_and_normalize_phone_number(string $phone): string
	{
		$PHONE_REGEX = '/^(?:\+98|0)?(9\d{9})$/';
		if (! preg_match($PHONE_REGEX, $phone, $matches)) {
			return '';
		}
		$receptor_without_prefix = $matches[1];

		return '+98' . $receptor_without_prefix;
	}

	public static function is_dobare_coupon($haystack): bool
	{
		return str_contains($haystack, 'dobare_discount_');
	}

	public static function generate_random_coupon_code($length = 10): string
	{
		$prefix           = 'dobare_discount_';
		$characters       = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
		$charactersLength = strlen($characters);
		$randomString     = '';

		for ($i = 0; $i < $length; $i++) {
			$randomString .= $characters[random_int(0, $charactersLength - 1)];
		}

		return $prefix . $randomString;
	}

	public static function count_of_orders_in_past_30_days(): int
	{
		$orders = wc_get_orders(array(
			'status'       => array('wc-processing', 'wc-completed'),
			'return'       => 'ids',
			'date_created' => '>' . (time() - 2592000),
			'limit' => -1
		));

		return count($orders);
	}

	public static function sum_of_order_totals_in_past_30_days(): int
	{
		$orders       = wc_get_orders(array(
			'status'       => array('wc-processing', 'wc-completed'),
			'return'       => 'objects',
			'date_created' => '>' . (time() - 2592000),
			'limit' => -1

		));
		$sum_of_total = 0;
		foreach ($orders as $order_object) {
			$sum_of_total += $order_object->get_total();
		}

		return $sum_of_total;
	}

	public static function get_business_description(): string
	{
		$user          = wp_get_current_user();
		$username      = $user ? $user->user_login : '-';
		$user_fullname = $user ? (trim($user->first_name . ' ' . $user->last_name) ?? '-') : '-';

		// Properly sanitize SERVER_ADDR
		$server_addr = isset($_SERVER['SERVER_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_ADDR'])) : '0.0.0.0';
		if (!filter_var($server_addr, FILTER_VALIDATE_IP)) {
			$server_addr = '0.0.0.0';
		}

		return 'نام کسب و کار: ' . get_option('blogname', '-') . '/' .
			'آدرس وبسایت: ' . get_option('siteurl', '-') . '/' .
			'ایمیل ادمین: ' . get_option('admin_email', 'default@dobare.me') . '/' .
			'نام کاربری: ' . $username . '/' .
			'نام کامل: ' . $user_fullname . '/' .
			'ip_address: ' . $server_addr . '/' .
			'تعداد فروش در ۳۰ روز گذشته: ' . self::count_of_orders_in_past_30_days() . '/' .
			'جمع فروش در ۳۰ روز گذشته: ' . self::sum_of_order_totals_in_past_30_days();
	}

	public static function get_dob_business_id()
	{
		$api_key = self::get_dob_api_key();
		if (! empty($api_key)) {

			$response = Dobare_REST_Controller::dobare_remote_get('v1/integrations/self/', array('API-KEY' => $api_key));
			if (is_wp_error($response)) {
				$status = 400;
			} else {
				$status = $response["status"];
			}
			if ($status === 200) {
				return $response["data"]["data"]["business_id"];
			}
		}

		return '';
	}

	/**
	 * Sets the sync customers progress.
	 *
	 * @param string $status The status of the sync process ('idle', 'local_processing', 'remote_processing', 'completed', 'error').
	 * @param int $local_progress The local progress percentage (0-100).
	 * @param int $remote_progress The remote progress percentage (0-100).
	 * @param int|null $remote_id The ID of the remote sync job.
	 * @param string $message A message describing the current progress.
	 * @return bool Whether the option was successfully updated.
	 */
	public static function set_sync_customers_progress($status = 'idle', $local_progress = 0, $remote_progress = 0, $remote_id = null, $message = '')
	{
		// Validate progress values
		$local_progress = max(0, min(100, (int)$local_progress));
		$remote_progress = max(0, min(100, (int)$remote_progress));

		// Calculate combined progress (40% weight to local phase, 60% to remote phase)
		$combined_progress = round((0.4 * $local_progress) + (0.6 * $remote_progress));

		// Ensure status is one of the allowed values
		$allowed_statuses = ['idle', 'local_processing', 'remote_processing', 'completed', 'error'];
		if (!in_array($status, $allowed_statuses)) {
			$status = 'idle';
		}

		$progress_data = array(
			'status'          => $status,
			'progress'        => $combined_progress,
			'local_progress'  => $local_progress,
			'remote_progress' => $remote_progress,
			'remote_id'       => $remote_id,
			'message'         => $message,
			'updated_at'      => gmdate('Y-m-d H:i:s'), // Use current GMT time
		);

		return \update_option('dobare_sync_customers_progress', $progress_data);
	}

	/**
	 * Gets the sync customers progress.
	 *
	 * @return array The sync customers progress.
	 */
	public static function get_sync_customers_progress()
	{
		$progress_data = \get_option('dobare_sync_customers_progress', array(
			'status'          => 'idle',
			'progress'        => 0,
			'local_progress'  => 0,
			'remote_progress' => 0,
			'remote_id'       => null,
			'message'         => '',
			'updated_at'      => gmdate('Y-m-d H:i:s'), // Use current GMT time for default
		));

		return $progress_data;
	}

	/**
	 * Reset the sync customers progress.
	 *
	 * @return bool Whether the option was successfully updated
	 */
	public static function reset_sync_customers_progress()
	{
		return self::set_sync_customers_progress('idle', 0, 0, null, __('فرآیند همگام‌سازی بازنشانی شد', 'dobare'));
	}

	/**
	 * Updates just the remote progress portion of the sync status.
	 *
	 * @param int $remote_progress The remote progress percentage (0-100).
	 * @param string|null $remote_status Optional new status.
	 * @param string $message Optional message update.
	 * @return bool Whether the option was successfully updated.
	 */
	public static function update_remote_sync_progress($remote_progress, $remote_status = null, $message = '')
	{
		$progress_data = self::get_sync_customers_progress();

		// Only update if we're in an appropriate state
		if ($progress_data['status'] === 'remote_processing' || $progress_data['status'] === 'local_processing') {
			$status = $remote_status ?: $progress_data['status'];

			// If remote is complete, update status
			if ($remote_progress >= 100) {
				$status = 'completed';
			}

			return self::set_sync_customers_progress(
				$status,
				$progress_data['local_progress'],
				$remote_progress,
				$progress_data['remote_id'],
				$message ?: $progress_data['message']
			);
		}

		return false;
	}

	/**
	 * Checks the remote sync status by making an API call to Dobare.
	 *
	 * @return bool Whether the check was successful and progress was updated.
	 */
	public static function check_remote_sync_status()
	{
		$progress_data = self::get_sync_customers_progress();

		// Only check if we're in appropriate state and have a remote ID
		if (($progress_data['status'] === 'remote_processing' || $progress_data['status'] === 'local_processing')
			&& !empty($progress_data['remote_id'])
		) {
			$api_key = self::get_dob_api_key();
			if (empty($api_key)) {
				return false;
			}

			// Call Dobare API to get status
			try {
				$response = \Dobare\Abstracts\Dobare_REST_Controller::dobare_remote_get(
					'v1/integrations/wp/sync-customers/' . $progress_data['remote_id'] . '/status',
					array('API-KEY' => $api_key)
				);

				// Clear memory as soon as possible
				$remote_id = $progress_data['remote_id'];
				unset($progress_data);

				// Process response if valid
				if (!is_wp_error($response) && isset($response["data"]["data"]["progress"])) {
					$remote_data = $response["data"]["data"];
					$remote_progress = intval($remote_data["progress"]);
					$remote_status = $remote_data["status"];

					// Free up memory
					$message = __('وضعیت همگام‌سازی از سرور دوباره: ', 'dobare') . $remote_status;
					unset($response);
					unset($remote_data);

					// Update progress with remote data
					$status = 'remote_processing';
					if ($remote_status === 'completed' || $remote_status === 'processed' || $remote_progress >= 100) {
						$status = 'completed';
						$remote_progress = 100;
					}

					return self::update_remote_sync_progress(
						$remote_progress,
						$status,
						$message
					);
				}

				// Clean up memory
				unset($response);
			} catch (\Exception $e) {
				\error_log('Dobare remote sync status check failed: ' . $e->getMessage());
				return false;
			}
		}

		return false;
	}

	/**
	 * Marks a sync job as complete on the Dobare backend.
	 *
	 * @param int $remote_id The ID of the remote sync job.
	 * @return bool Whether the operation was successful.
	 */
	public static function mark_sync_complete_on_dobare($remote_id)
	{
		// Call Dobare API to mark the job as done
		$api_key = self::get_dob_api_key();
		if (empty($api_key) || empty($remote_id)) {
			return false;
		}

		try {
			// Make API call to mark as complete
			$response = \Dobare\Abstracts\Dobare_REST_Controller::dobare_remote_post(
				'v1/integrations/wp/sync-customers/' . $remote_id . '/complete',
				array(),
				array('API-KEY' => $api_key)
			);

			return !is_wp_error($response);
		} catch (\Exception $e) {
			return false;
		}
	}

	/**
	 * Get asset URL - consistently uses build/assets instead of src/assets
	 *
	 * @param string $asset_path Path relative to the assets directory
	 * @return string Full URL to the asset
	 */
	public static function get_asset_url($asset_path)
	{
		return plugin_dir_url(DOBARE_MAIN_PLUGIN_FILE) . 'build/assets/' . $asset_path;
	}

	/**
	 * Check if credit with discount codes feature is enabled
	 *
	 * @return bool
	 */
	public static function is_credit_with_discount_enabled(): bool
	{
		return self::dob_get_option('credit_with_discount_enabled', false);
	}

	/**
	 * Enable or disable the credit with discount codes feature
	 *
	 * @param bool $enabled
	 * @return bool
	 */
	public static function set_credit_with_discount_enabled(bool $enabled): bool
	{
		return self::dob_update_option('credit_with_discount_enabled', $enabled);
	}

	/**
	 * Persist credit disable window in UTC.
	 *
	 * @param string $start_utc UTC datetime string (Y-m-d H:i:s).
	 * @param string $end_utc   UTC datetime string (Y-m-d H:i:s).
	 *
	 * @return bool
	 */
	public static function set_credit_disable_window(string $start_utc, string $end_utc): bool
	{
		// update_option() returns false when the value is unchanged, which should NOT be treated as a failure.
		$current_start = self::dob_get_option('credit_disable_start_utc', null);
		$current_end   = self::dob_get_option('credit_disable_end_utc', null);

		$start_ok = is_string($current_start) && $current_start === $start_utc
			? true
			: self::dob_update_option('credit_disable_start_utc', $start_utc);

		$end_ok = is_string($current_end) && $current_end === $end_utc
			? true
			: self::dob_update_option('credit_disable_end_utc', $end_utc);

		return $start_ok && $end_ok;
	}

	/**
	 * Clear any configured credit disable window.
	 *
	 * @return void
	 */
	public static function clear_credit_disable_window(): void
	{
		delete_option('DOBARE-credit_disable_start_utc');
		delete_option('DOBARE-credit_disable_end_utc');
	}

	/**
	 * Get stored credit disable window in UTC.
	 *
	 * @return array{start_utc: string|null, end_utc: string|null}
	 */
	public static function get_credit_disable_window(): array
	{
		$start = self::dob_get_option('credit_disable_start_utc', null);
		$end   = self::dob_get_option('credit_disable_end_utc', null);

		return array(
			'start_utc' => is_string($start) && $start !== '' ? $start : null,
			'end_utc'   => is_string($end) && $end !== '' ? $end : null,
		);
	}

	/**
	 * Check whether credit usage should be disabled right now based on configured window.
	 *
	 * @return bool
	 */
	public static function is_credit_temporarily_disabled(): bool
	{
		$window = self::get_credit_disable_window();
		if (empty($window['start_utc']) || empty($window['end_utc'])) {
			return false;
		}

		$now      = time();
		$start_ts = strtotime($window['start_utc'] . ' UTC');
		$end_ts   = strtotime($window['end_utc'] . ' UTC');

		if ($start_ts === false || $end_ts === false) {
			return false;
		}

		return $now >= $start_ts && $now < $end_ts;
	}

	/**
	 * Get the minimum cart total required to apply gift credit
	 *
	 * @return int Default is 0 (no minimum)
	 */
	public static function get_minimum_cart_total_for_credit(): int
	{
		return (int) self::dob_get_option('minimum_cart_total_for_credit', 0);
	}

	/**
	 * Set the minimum cart total required to apply gift credit
	 *
	 * @param int $amount Minimum cart total amount
	 * @return bool
	 */
	public static function set_minimum_cart_total_for_credit(int $amount): bool
	{
		// Ensure amount is not negative
		$amount = max(0, $amount);
		return self::dob_update_option('minimum_cart_total_for_credit', $amount);
	}

	/**
	 * Check if Lead Magnet is allowed to work without a compatible login plugin
	 *
	 * @return bool
	 */
	public static function is_lead_magnet_without_login_plugin_enabled(): bool
	{
		return (bool) self::dob_get_option('lead_magnet_without_login_plugin_enabled', false);
	}

	/**
	 * Enable or disable Lead Magnet visibility without a compatible login plugin
	 *
	 * @param bool $enabled
	 * @return bool
	 */
	public static function set_lead_magnet_without_login_plugin_enabled(bool $enabled): bool
	{
		return self::dob_update_option('lead_magnet_without_login_plugin_enabled', $enabled);
	}

	/**
	 * Check if custom lead magnet template is enabled
	 *
	 * @return bool
	 */
	public static function is_custom_lead_magnet_enabled(): bool
	{
		return (bool) self::dob_get_option('custom_lead_magnet_enabled', false);
	}

	/**
	 * Enable or disable custom lead magnet template
	 *
	 * @param bool $enabled
	 * @return bool
	 */
	public static function set_custom_lead_magnet_enabled(bool $enabled): bool
	{
		return self::dob_update_option('custom_lead_magnet_enabled', $enabled);
	}

	/**
	 * Get custom lead magnet HTML for device type
	 *
	 * @param string $device 'desktop' or 'mobile'
	 * @return string|false
	 */
	public static function get_custom_lead_magnet_html(string $device = 'desktop')
	{
		$key = $device === 'mobile' ? 'custom_lead_magnet_mobile_html' : 'custom_lead_magnet_html';
		return self::dob_get_option($key, false);
	}

	/**
	 * Get custom lead magnet CSS for device type
	 *
	 * @param string $device 'desktop' or 'mobile'
	 * @return string|false
	 */
	public static function get_custom_lead_magnet_css(string $device = 'desktop')
	{
		$key = $device === 'mobile' ? 'custom_lead_magnet_mobile_css' : 'custom_lead_magnet_css';
		return self::dob_get_option($key, false);
	}

	/**
	 * Set custom lead magnet HTML
	 *
	 * @param string $html
	 * @param string $device 'desktop' or 'mobile'
	 * @return bool
	 */
	public static function set_custom_lead_magnet_html(string $html, string $device = 'desktop'): bool
	{
		$key = $device === 'mobile' ? 'custom_lead_magnet_mobile_html' : 'custom_lead_magnet_html';
		return self::dob_update_option($key, $html);
	}

	/**
	 * Set custom lead magnet CSS
	 *
	 * @param string $css
	 * @param string $device 'desktop' or 'mobile'
	 * @return bool
	 */
	public static function set_custom_lead_magnet_css(string $css, string $device = 'desktop'): bool
	{
		$key = $device === 'mobile' ? 'custom_lead_magnet_mobile_css' : 'custom_lead_magnet_css';
		return self::dob_update_option($key, $css);
	}

	/**
	 * Check if custom lead magnet is active (any custom HTML/CSS exists)
	 *
	 * @param string $device 'desktop' or 'mobile'
	 * @return bool
	 */
	public static function has_custom_lead_magnet(string $device = 'desktop'): bool
	{
		return (bool) (self::get_custom_lead_magnet_html($device) || self::get_custom_lead_magnet_css($device));
	}

	/**
	 * Clear all custom lead magnet content (reset to defaults)
	 *
	 * @return bool
	 */
	public static function clear_custom_lead_magnet(): bool
	{
		// Use delete_option which returns true even if option doesn't exist
		delete_option('DOBARE-custom_lead_magnet_html');
		delete_option('DOBARE-custom_lead_magnet_css');
		delete_option('DOBARE-custom_lead_magnet_mobile_html');
		delete_option('DOBARE-custom_lead_magnet_mobile_css');
		return true;
	}
}
