<?php
/**
 * Helper functions for Altify AI plugin.
 *
 * @package Altify_AI
 */

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

if (!function_exists('altify_ai_get_default_settings')) {
	/**
	 * Get plugin default settings.
	 *
	 * @return array
	 */
	function altify_ai_get_default_settings() {
		return array(
			'openai_api_key'         => '',
			'model'                  => 'gpt-4o-mini',
			'temperature'            => 0.2,
			'max_tokens'             => 60,
			'alt_language'           => 'auto',
			'alt_language_custom'    => '',
			'offline_mode'           => 0,
			'use_vision_analysis'    => 0,
			'max_alt_length'         => 125,
			'only_fill_empty_alt'    => 1,
			'overwrite_existing_alt' => 0,
			'requests_per_minute'    => 60,
			'enable_woo_support'     => 1,
			'auto_generate_on_upload' => 1,
			'upload_generation_mode'  => 'offline_first',
			'daily_scan_enabled'      => 1,
			'daily_scan_time'         => '02:00',
			'daily_scan_scope'        => 'missing',
			'daily_scan_max_images'   => 50,
			'daily_scan_mode'         => 'offline_first',
		);
	}
}

if (!function_exists('altify_ai_get_settings')) {
	/**
	 * Get merged plugin settings.
	 *
	 * @return array
	 */
	function altify_ai_get_settings() {
		$saved    = get_option('altify_ai_settings', array());
		$saved    = is_array($saved) ? $saved : array();
		$defaults = altify_ai_get_default_settings();

		return wp_parse_args($saved, $defaults);
	}
}

if (!function_exists('altify_ai_get_setting')) {
	/**
	 * Get a single plugin setting.
	 *
	 * @param string $key     Setting key.
	 * @param mixed  $default Fallback value.
	 *
	 * @return mixed
	 */
	function altify_ai_get_setting($key, $default = null) {
		$settings = altify_ai_get_settings();
		return isset($settings[$key]) ? $settings[$key] : $default;
	}
}

if (!function_exists('altify_ai_bool')) {
	/**
	 * Cast value into bool-like integer.
	 *
	 * @param mixed $value Value.
	 *
	 * @return int
	 */
	function altify_ai_bool($value) {
		return !empty($value) ? 1 : 0;
	}
}

if (!function_exists('altify_ai_is_image_attachment')) {
	/**
	 * Check if attachment is an image.
	 *
	 * @param int $attachment_id Attachment ID.
	 *
	 * @return bool
	 */
	function altify_ai_is_image_attachment($attachment_id) {
		$attachment_id = absint($attachment_id);
		if (!$attachment_id) {
			return false;
		}

		if (wp_attachment_is_image($attachment_id)) {
			return true;
		}

		$mime = get_post_mime_type($attachment_id);
		return is_string($mime) && 0 === strpos($mime, 'image/');
	}
}

if (!function_exists('altify_ai_strip_quotes')) {
	/**
	 * Remove quote characters from string.
	 *
	 * @param string $text Input string.
	 *
	 * @return string
	 */
	function altify_ai_strip_quotes($text) {
		$text = (string) $text;
		return str_replace(array('"', "'", '`', '“', '”', '‘', '’'), '', $text);
	}
}

if (!function_exists('altify_ai_truncate_text')) {
	/**
	 * Truncate text to max characters.
	 *
	 * @param string $text       Input text.
	 * @param int    $max_length Max chars.
	 *
	 * @return string
	 */
	function altify_ai_truncate_text($text, $max_length) {
		$text       = (string) $text;
		$max_length = max(1, absint($max_length));

		if (function_exists('mb_strlen') && function_exists('mb_substr')) {
			if (mb_strlen($text) <= $max_length) {
				return $text;
			}

			$text = mb_substr($text, 0, $max_length);
			return trim($text);
		}

		if (strlen($text) <= $max_length) {
			return $text;
		}

		$text = substr($text, 0, $max_length);
		return trim($text);
	}
}

if (!function_exists('altify_ai_sanitize_alt_text')) {
	/**
	 * Sanitize ALT text output.
	 *
	 * @param string $text       ALT text.
	 * @param int    $max_length Max chars.
	 *
	 * @return string
	 */
	function altify_ai_sanitize_alt_text($text, $max_length) {
		$text = html_entity_decode((string) $text, ENT_QUOTES, 'UTF-8');
		$text = wp_strip_all_tags((string) $text, true);
		$text = altify_ai_strip_quotes($text);
		$text = str_replace(array('_', '/', '\\'), ' ', $text);
		$text = preg_replace('/\\.(jpg|jpeg|png|gif|webp|svg|bmp|avif)$/i', '', (string) $text);
		$text = preg_replace('/[^\\p{L}\\p{N}\\s,\\.\\-]/u', ' ', (string) $text);
		$text = preg_replace('/([,\\.\\-])\\1+/', '$1', (string) $text);
		$text = preg_replace('/\s+/', ' ', $text);
		$text = preg_replace('/^[\\s,\\.\\-]+|[\\s,\\.\\-]+$/u', '', (string) $text);
		$text = trim((string) $text);
		$text = sanitize_text_field($text);
		return altify_ai_truncate_text($text, $max_length);
	}
}

if (!function_exists('altify_ai_clean_filename_to_text')) {
	/**
	 * Build readable text from filename.
	 *
	 * @param string $filename Attachment filename.
	 *
	 * @return string
	 */
	function altify_ai_clean_filename_to_text($filename) {
		$filename = (string) $filename;
		$base     = pathinfo($filename, PATHINFO_FILENAME);

		$base = strtolower((string) $base);
		$base = str_replace(array('%20', '+'), ' ', $base);
		$base = preg_replace('/\\b\\d{2,5}x\\d{2,5}\\b/i', ' ', (string) $base);
		$base = preg_replace('/\\b[a-f0-9]{8,}\\b/i', ' ', (string) $base);
		$base = preg_replace('/\\b(?:img|dsc|pxl|wp-image|screenshot|photo|scaled|edited|large|medium|small)[-_]?\\d*\\b/i', ' ', (string) $base);
		$base = preg_replace('/[-_.]+/', ' ', (string) $base);
		$base = preg_replace('/\\d+/', ' ', (string) $base);
		$base = preg_replace('/[^\\p{L}\\s]/u', ' ', (string) $base);
		$base = preg_replace('/\s+/', ' ', (string) $base);
		$base = trim((string) $base);
		$base = sanitize_text_field($base);

		if ('' === $base) {
			$base = __('Image', 'altify-ai-auto-alt-text-generator');
		}

		return ucwords($base);
	}
}

if (!function_exists('altify_ai_is_code_like_token')) {
	/**
	 * Check whether token is likely a camera/generated code (for example: dg1244).
	 *
	 * @param string $token Word token.
	 *
	 * @return bool
	 */
	function altify_ai_is_code_like_token($token) {
		$token = strtolower(trim((string) $token));
		if ('' === $token) {
			return false;
		}

		return (bool) preg_match('/^(?:[a-z]{1,4}\d{2,}|\d{3,}[a-z]{0,3})$/', $token);
	}
}

if (!function_exists('altify_ai_cleanup_alt_candidate')) {
	/**
	 * Normalize candidate text before quality checks.
	 *
	 * @param string $text Candidate text.
	 *
	 * @return string
	 */
	function altify_ai_cleanup_alt_candidate($text) {
		$text = altify_ai_sanitize_alt_text((string) $text, 300);
		$text = strtolower($text);
		$text = str_replace(array('-', '_'), ' ', $text);
		$text = preg_replace('/\b(?:scaled|edited|large|medium|small)\b/i', ' ', (string) $text);
		$text = preg_replace('/\s+/', ' ', (string) $text);
		$text = trim((string) $text);

		return $text;
	}
}

if (!function_exists('altify_ai_is_low_quality_alt_text')) {
	/**
	 * Detect generic/non-descriptive ALT candidates.
	 *
	 * @param string $text Candidate ALT text.
	 *
	 * @return bool
	 */
	function altify_ai_is_low_quality_alt_text($text) {
		$text = altify_ai_cleanup_alt_candidate($text);
		if ('' === $text) {
			return true;
		}

		$lower = $text;
		$generic = array(
			'image',
			'photo',
			'picture',
			'scaled',
			'edited',
			'img',
			'dsc',
			'wp image',
			'screenshot',
		);
		if (in_array($lower, $generic, true)) {
			return true;
		}

		if (preg_match('/^[a-z]{0,4}\s*\d+$/i', $lower) || preg_match('/^\d+\s*[a-z]{0,4}$/i', $lower)) {
			return true;
		}

		$tokens = preg_split('/\s+/', $lower);
		$tokens = is_array($tokens) ? array_filter($tokens) : array();

		$generic_tokens = array('image', 'photo', 'picture', 'img', 'dsc', 'pxl', 'wp', 'screenshot', 'scaled', 'edited');
		$semantic_count = 0;
		foreach ($tokens as $token) {
			$token = trim((string) $token);
			if ('' === $token) {
				continue;
			}
			if (in_array($token, $generic_tokens, true)) {
				continue;
			}
			if ((bool) preg_match('/^\d+$/', $token)) {
				continue;
			}
			if (altify_ai_is_code_like_token($token)) {
				continue;
			}
			$semantic_count++;
		}

		if (0 === $semantic_count) {
			return true;
		}

		return false;
	}
}

if (!function_exists('altify_ai_generate_offline_alt_text')) {
	/**
	 * Build ALT text without AI using metadata context.
	 *
	 * @param array  $context    Attachment context.
	 * @param string $language   Resolved language label.
	 * @param int    $max_length Max chars.
	 *
	 * @return string
	 */
	function altify_ai_generate_offline_alt_text($context, $language, $max_length) {
		$context    = is_array($context) ? $context : array();
		$language   = strtolower((string) $language);
		$max_length = max(20, absint($max_length));
		$is_nepali  = ('nepali' === $language || 'ne' === $language);

		$candidates = array();

		if (!empty($context['title'])) {
			$candidates[] = (string) $context['title'];
		}

		if (!empty($context['caption'])) {
			$candidates[] = (string) $context['caption'];
		}

		if (!empty($context['description'])) {
			$description = (string) $context['description'];
			$parts       = preg_split('/[.!?]/', $description);
			$first_part  = isset($parts[0]) ? trim((string) $parts[0]) : $description;
			$candidates[] = $first_part;
		}

		if (!empty($context['filename'])) {
			$candidates[] = (string) $context['filename'];
		}

		$base = '';
		foreach ($candidates as $candidate) {
			$clean = altify_ai_cleanup_alt_candidate($candidate);
			$clean = altify_ai_sanitize_alt_text($clean, $max_length);
			if (altify_ai_is_low_quality_alt_text($clean)) {
				continue;
			}
			$base = $clean;
			break;
		}

		if ('' === $base) {
			$base = $is_nepali ? 'तस्बिर' : __('Image', 'altify-ai-auto-alt-text-generator');
		}

		if (!empty($context['product_name'])) {
			$product_name = altify_ai_sanitize_alt_text((string) $context['product_name'], $max_length);
			if ('' !== $product_name && false === stripos($base, $product_name)) {
				$combined = $product_name . ' ' . $base;
				$base     = altify_ai_sanitize_alt_text($combined, $max_length);
			}
		}

		return altify_ai_truncate_text($base, $max_length);
	}
}

if (!function_exists('altify_ai_get_language_map')) {
	/**
	 * Get built-in language options (ISO code => label).
	 *
	 * @return array
	 */
	function altify_ai_get_language_map() {
		return array(
			'af' => 'Afrikaans',
			'ar' => 'Arabic',
			'bg' => 'Bulgarian',
			'bn' => 'Bengali',
			'ca' => 'Catalan',
			'cs' => 'Czech',
			'da' => 'Danish',
			'de' => 'German',
			'el' => 'Greek',
			'en' => 'English',
			'es' => 'Spanish',
			'et' => 'Estonian',
			'fa' => 'Persian',
			'fi' => 'Finnish',
			'fil' => 'Filipino',
			'fr' => 'French',
			'gu' => 'Gujarati',
			'he' => 'Hebrew',
			'hi' => 'Hindi',
			'hr' => 'Croatian',
			'hu' => 'Hungarian',
			'id' => 'Indonesian',
			'it' => 'Italian',
			'ja' => 'Japanese',
			'kn' => 'Kannada',
			'ko' => 'Korean',
			'lt' => 'Lithuanian',
			'lv' => 'Latvian',
			'ml' => 'Malayalam',
			'mr' => 'Marathi',
			'ms' => 'Malay',
			'my' => 'Burmese',
			'ne' => 'Nepali',
			'nl' => 'Dutch',
			'no' => 'Norwegian',
			'pa' => 'Punjabi',
			'pl' => 'Polish',
			'pt' => 'Portuguese',
			'ro' => 'Romanian',
			'ru' => 'Russian',
			'sk' => 'Slovak',
			'sl' => 'Slovenian',
			'sq' => 'Albanian',
			'sr' => 'Serbian',
			'sv' => 'Swedish',
			'sw' => 'Swahili',
			'ta' => 'Tamil',
			'te' => 'Telugu',
			'th' => 'Thai',
			'tr' => 'Turkish',
			'uk' => 'Ukrainian',
			'ur' => 'Urdu',
			'vi' => 'Vietnamese',
			'zh' => 'Chinese',
		);
	}
}

if (!function_exists('altify_ai_get_language_options')) {
	/**
	 * Get language dropdown options.
	 *
	 * @return array
	 */
	function altify_ai_get_language_options() {
		$options = array(
			'auto' => __('Auto (Site Language)', 'altify-ai-auto-alt-text-generator'),
		);

		$language_map = altify_ai_get_language_map();
		foreach ($language_map as $code => $label) {
			$options[$code] = $label;
		}

		$options['custom'] = __('Custom (enter below)', 'altify-ai-auto-alt-text-generator');

		return $options;
	}
}

if (!function_exists('altify_ai_resolve_language')) {
	/**
	 * Resolve configured language to a readable language label.
	 *
	 * @param string $language_setting Setting value.
	 * @param string $custom_language  Custom language label.
	 *
	 * @return string
	 */
	function altify_ai_resolve_language($language_setting, $custom_language = '') {
		$language_setting = strtolower((string) $language_setting);
		$custom_language  = sanitize_text_field((string) $custom_language);
		$language_map     = altify_ai_get_language_map();

		// Backward compatibility with older saved values.
		if ('english' === $language_setting) {
			$language_setting = 'en';
		} elseif ('nepali' === $language_setting) {
			$language_setting = 'ne';
		}

		if ('custom' === $language_setting && '' !== trim($custom_language)) {
			return $custom_language;
		}

		if (isset($language_map[$language_setting])) {
			return $language_map[$language_setting];
		}

		$locale = get_locale();
		$locale = is_string($locale) ? strtolower($locale) : '';
		if (preg_match('/^([a-z]{2,3})/', $locale, $matches)) {
			$locale_code = isset($matches[1]) ? $matches[1] : '';
			if (isset($language_map[$locale_code])) {
				return $language_map[$locale_code];
			}
		}

		return 'English';
	}
}

if (!function_exists('altify_ai_get_attachment_context')) {
	/**
	 * Get context used for ALT generation.
	 *
	 * @param int   $attachment_id Attachment ID.
	 * @param array $extra_context Extra context (product_name, product_categories).
	 *
	 * @return array
	 */
	function altify_ai_get_attachment_context($attachment_id, $extra_context = array()) {
		$attachment_id = absint($attachment_id);
		$post          = get_post($attachment_id);

		if (!$post) {
			return array();
		}

		$file_path = get_attached_file($attachment_id);
		if (!$file_path) {
			$file_path = (string) get_post_meta($attachment_id, '_wp_attached_file', true);
		}

		$raw_filename = wp_basename((string) $file_path);
		$filename     = altify_ai_clean_filename_to_text($raw_filename);
		$title        = sanitize_text_field((string) get_the_title($attachment_id));
		$caption      = sanitize_text_field((string) wp_get_attachment_caption($attachment_id));
		$description  = sanitize_text_field((string) wp_strip_all_tags($post->post_content));

		$context = array(
			'filename'           => $filename,
			'title'              => $title,
			'caption'            => $caption,
			'description'        => $description,
			'product_name'       => '',
			'product_categories' => '',
		);

		if (!empty($extra_context['product_name'])) {
			$context['product_name'] = sanitize_text_field((string) $extra_context['product_name']);
		}

		if (!empty($extra_context['product_categories'])) {
			if (is_array($extra_context['product_categories'])) {
				$categories = array_map('sanitize_text_field', $extra_context['product_categories']);
				$categories = array_filter($categories);
				$context['product_categories'] = implode(', ', $categories);
			} else {
				$context['product_categories'] = sanitize_text_field((string) $extra_context['product_categories']);
			}
		}

		return $context;
	}
}

if (!function_exists('altify_ai_hash_prompt_payload')) {
	/**
	 * Hash prompt payload for cache checks.
	 *
	 * @param array $payload Data to hash.
	 *
	 * @return string
	 */
	function altify_ai_hash_prompt_payload($payload) {
		$encoded = wp_json_encode($payload);
		if (!$encoded) {
			$encoded = serialize($payload); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
		}

		return sha1((string) $encoded);
	}
}

if (!function_exists('altify_ai_get_attachment_file_signature')) {
	/**
	 * Build a stable signature of the attachment file for cache invalidation.
	 *
	 * @param int $attachment_id Attachment ID.
	 *
	 * @return string
	 */
	function altify_ai_get_attachment_file_signature($attachment_id) {
		$attachment_id = absint($attachment_id);
		if (!$attachment_id) {
			return '';
		}

		$file = get_attached_file($attachment_id);
		if (!$file) {
			$file = (string) get_post_meta($attachment_id, '_wp_attached_file', true);
		}
		$file = (string) $file;
		if ('' !== $file && function_exists('path_is_absolute') && !path_is_absolute($file)) {
			$uploads = wp_get_upload_dir();
			if (!empty($uploads['basedir']) && is_string($uploads['basedir'])) {
				$file = trailingslashit($uploads['basedir']) . ltrim($file, '/\\');
			}
		}
		if ('' === $file || !file_exists($file) || !is_readable($file)) {
			return '';
		}

		$filesize = filesize($file);
		$filetime = filemtime($file);
		if (false === $filesize || false === $filetime) {
			return sha1($file);
		}

		return sha1($file . '|' . $filesize . '|' . $filetime);
	}
}

if (!function_exists('altify_ai_fallback_alt_text')) {
	/**
	 * Build filename-based fallback ALT text.
	 *
	 * @param int    $attachment_id Attachment ID.
	 * @param array  $context       Attachment context.
	 * @param string $language      Resolved language label.
	 * @param int    $max_length    Max chars.
	 *
	 * @return string
	 */
	function altify_ai_fallback_alt_text($attachment_id, $context, $language, $max_length) {
		$attachment_id = absint($attachment_id);
		$context       = is_array($context) ? $context : array();
		if (empty($context['filename'])) {
			$file = get_attached_file($attachment_id);
			if (!$file) {
				$file = (string) get_post_meta($attachment_id, '_wp_attached_file', true);
			}
			if ('' !== (string) $file) {
				$context['filename'] = altify_ai_clean_filename_to_text((string) wp_basename((string) $file));
			}
		}

		return altify_ai_generate_offline_alt_text($context, $language, $max_length);
	}
}

if (!function_exists('altify_ai_build_prompt')) {
	/**
	 * Build prompt text with rules and context.
	 *
	 * @param array  $context    Context array.
	 * @param string $language   Resolved language label.
	 * @param int    $max_length Max chars.
	 *
	 * @return string
	 */
	function altify_ai_build_prompt($context, $language, $max_length) {
		$context    = is_array($context) ? $context : array();
		$language   = sanitize_text_field((string) $language);
		$language   = ('' !== $language) ? $language : 'English';
		$max_length = max(20, absint($max_length));
		$normalized = strtolower($language);

		$lines   = array();
		$lines[] = 'Generate one image ALT text only.';
		$lines[] = 'Rules:';
		$lines[] = '- Maximum ' . $max_length . ' characters.';
		$lines[] = '- No quotation marks.';
		$lines[] = '- Descriptive and natural.';
		$lines[] = '- No keyword stuffing.';

		if ('nepali' === $normalized || 'ne' === $normalized) {
			$lines[] = '- Write in simple Nepali using easy words.';
		} elseif ('english' === $normalized || 'en' === $normalized) {
			$lines[] = '- Write in English.';
		} else {
			$lines[] = '- Write in ' . $language . '.';
			$lines[] = '- Keep wording natural and easy to understand.';
		}

		$lines[] = 'Context:';
		$lines[] = 'Filename: ' . (!empty($context['filename']) ? $context['filename'] : 'N/A');
		$lines[] = 'Title: ' . (!empty($context['title']) ? $context['title'] : 'N/A');
		$lines[] = 'Caption: ' . (!empty($context['caption']) ? $context['caption'] : 'N/A');
		$lines[] = 'Description: ' . (!empty($context['description']) ? $context['description'] : 'N/A');

		if (!empty($context['product_name'])) {
			$lines[] = 'Product Name: ' . $context['product_name'];
		}

		if (!empty($context['product_categories'])) {
			$lines[] = 'Product Categories: ' . $context['product_categories'];
		}

		$lines[] = 'Return only the ALT text string.';

		return implode("\n", $lines);
	}
}

if (!function_exists('altify_ai_get_generation_mode_options')) {
	/**
	 * Get generation mode options.
	 *
	 * @return array
	 */
	function altify_ai_get_generation_mode_options() {
		return array(
			'offline_first' => __('Offline first', 'altify-ai-auto-alt-text-generator'),
			'ai_first'      => __('AI first', 'altify-ai-auto-alt-text-generator'),
			'offline_only'  => __('Offline only', 'altify-ai-auto-alt-text-generator'),
			'ai_only'       => __('AI only', 'altify-ai-auto-alt-text-generator'),
		);
	}
}

if (!function_exists('altify_ai_normalize_generation_mode')) {
	/**
	 * Normalize generation mode value.
	 *
	 * @param string $mode    Candidate mode.
	 * @param string $default Fallback mode.
	 *
	 * @return string
	 */
	function altify_ai_normalize_generation_mode($mode, $default = 'offline_first') {
		$mode    = sanitize_key((string) $mode);
		$default = sanitize_key((string) $default);
		$options = altify_ai_get_generation_mode_options();

		if (!isset($options[$default])) {
			$default = 'offline_first';
		}

		if (!isset($options[$mode])) {
			return $default;
		}

		return $mode;
	}
}

if (!function_exists('altify_ai_get_generation_mode_label')) {
	/**
	 * Get translated label for generation mode.
	 *
	 * @param string $mode Generation mode key.
	 *
	 * @return string
	 */
	function altify_ai_get_generation_mode_label($mode) {
		$mode    = altify_ai_normalize_generation_mode($mode, 'offline_first');
		$options = altify_ai_get_generation_mode_options();
		return isset($options[$mode]) ? $options[$mode] : $mode;
	}
}

if (!function_exists('altify_ai_get_scan_scope_options')) {
	/**
	 * Get scan scope options.
	 *
	 * @return array
	 */
	function altify_ai_get_scan_scope_options() {
		return array(
			'missing' => __('Missing ALT only (default)', 'altify-ai-auto-alt-text-generator'),
			'all'     => __('All images', 'altify-ai-auto-alt-text-generator'),
		);
	}
}

if (!function_exists('altify_ai_normalize_scan_scope')) {
	/**
	 * Normalize scan scope value.
	 *
	 * @param string $scope Candidate scope.
	 *
	 * @return string
	 */
	function altify_ai_normalize_scan_scope($scope) {
		$scope = sanitize_key((string) $scope);
		return ('all' === $scope) ? 'all' : 'missing';
	}
}

if (!function_exists('altify_ai_get_scan_scope_label')) {
	/**
	 * Get translated label for scan scope.
	 *
	 * @param string $scope Scan scope.
	 *
	 * @return string
	 */
	function altify_ai_get_scan_scope_label($scope) {
		$scope   = altify_ai_normalize_scan_scope($scope);
		$options = altify_ai_get_scan_scope_options();
		return isset($options[$scope]) ? $options[$scope] : $scope;
	}
}

if (!function_exists('altify_ai_normalize_time_hhmm')) {
	/**
	 * Normalize HH:MM time string.
	 *
	 * @param string $value   Raw time value.
	 * @param string $default Default time.
	 *
	 * @return string
	 */
	function altify_ai_normalize_time_hhmm($value, $default = '02:00') {
		$value   = sanitize_text_field((string) $value);
		$default = sanitize_text_field((string) $default);

		if (!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $default)) {
			$default = '02:00';
		}

		if (!preg_match('/^(?:[01]\d|2[0-3]):[0-5]\d$/', $value)) {
			return $default;
		}

		return $value;
	}
}

if (!function_exists('altify_ai_get_next_daily_scan_timestamp')) {
	/**
	 * Build the next local timestamp for a daily scan.
	 *
	 * @param string $time_hhmm Daily time in HH:MM.
	 *
	 * @return int
	 */
	function altify_ai_get_next_daily_scan_timestamp($time_hhmm) {
		$time_hhmm = altify_ai_normalize_time_hhmm($time_hhmm, '02:00');
		$parts     = explode(':', $time_hhmm);
		$hour      = isset($parts[0]) ? absint($parts[0]) : 2;
		$minute    = isset($parts[1]) ? absint($parts[1]) : 0;

		$timezone = wp_timezone();
		$now      = new DateTimeImmutable('now', $timezone);
		$next_run = $now->setTime($hour, $minute, 0);

		if ($next_run->getTimestamp() <= $now->getTimestamp()) {
			$next_run = $next_run->modify('+1 day');
		}

		return (int) $next_run->getTimestamp();
	}
}

if (!function_exists('altify_ai_get_date_query')) {
	/**
	 * Build WP date_query from optional date strings.
	 *
	 * @param string $date_from YYYY-MM-DD.
	 * @param string $date_to   YYYY-MM-DD.
	 *
	 * @return array
	 */
	function altify_ai_get_date_query($date_from, $date_to) {
		$date_from = sanitize_text_field((string) $date_from);
		$date_to   = sanitize_text_field((string) $date_to);

		$query = array();

		if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_from)) {
			$query['after'] = $date_from . ' 00:00:00';
		}

		if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_to)) {
			$query['before'] = $date_to . ' 23:59:59';
		}

		if (!empty($query)) {
			$query['inclusive'] = true;
			return array($query);
		}

		return array();
	}
}
