<?php

namespace Templately\Utils;

use Elementor\Plugin;
use Templately\Core\Importer\Utils\Utils;
use WP_Error;
use WP_REST_Response;
use function get_plugins;
use function is_plugin_active;

/**
 * Utility Helper for Templately
 *
 * This class contains some helper functions for easy access.
 *
 * @since 1.0.0
 */
class Helper extends Base {
	/**
	 * Check if development API should be used
	 *
	 * @return bool True if development API should be used
	 */
	public static function is_dev_api(){
		// Only check TEMPLATELY_DEV_API constant - no fallback mechanisms
		return defined( 'TEMPLATELY_DEV_API' ) && constant( 'TEMPLATELY_DEV_API' );
	}

	/**
	 * Get installed WordPress Plugin List
	 * @return array
	 */
	public static function get_plugins() {
		if (! function_exists('get_plugins')) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
		return get_plugins();
	}
	public static function is_plugins_installed($plugin_file) {
		$_plugins     = self::get_plugins();
		$is_installed = isset($_plugins[$plugin_file]);
		return $is_installed;
	}

	/**
	 * Get installed WordPress Plugin List
	 * @return boolean
	 */
	public static function is_plugin_active($plugin) {
		if (! function_exists('is_plugin_active')) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}
		return is_plugin_active($plugin);
	}

	/**
	 * Collect IP from request.
	 *
	 * @return string
	 */
	public static function get_ip() {
		$ip = '127.0.0.1'; // Local IP
		if (! empty($_SERVER['HTTP_CLIENT_IP'])) {
			$ip = $_SERVER['HTTP_CLIENT_IP'];
		} elseif (! empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
			$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
		} else {
			$ip = ! empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : $ip;
		}

		return sanitize_text_field($ip);
	}

	/**
	 * Get views for front-end display
	 *
	 * @param string $name  it will be file name only from the view's folder.
	 * @param array $data
	 * @return void
	 */
	public static function views($name, $data = []) {
		extract($data);
		$helper = self::class;
		$file = TEMPLATELY_PATH . 'views/' . $name . '.php';

		if (is_readable($file)) {
			include_once $file;
		}
	}

	/**
	 * Get API URL for Templately endpoints
	 *
	 * @param string $endpoint API endpoint path (e.g., 'v2/import/pack/123')
	 * @return string Complete API URL
	 */
	public static function get_api_url($endpoint): string {
		$base_url = self::is_dev_api() ? 'https://app.templately.dev' : 'https://app.templately.com';

		/**
		 * Filter the base URL for development API
		 *
		 * @since 3.5.0
		 * @param string $base_url The default base URL
		 */
		$base_url = apply_filters('templately_dev_api_base_url', $base_url);

		return "{$base_url}/api/{$endpoint}";
	}

	/**
	 * Make a unified API request to Templately API
	 *
	 * @param string $method HTTP method (GET or POST)
	 * @param string $api_url Complete API URL
	 * @param array $body Request body data (for POST requests)
	 * @param array $extra_headers Additional headers beyond the standard ones
	 * @param int $timeout Request timeout in seconds (default: 30)
	 * @return array|WP_Error Response array or WP_Error on failure
	 */
	private static function make_api_request($method, $api_url, $body = [], $extra_headers = [], $timeout = 30) {
		$api_key = Options::get_instance()->get('api_key');

		$headers = [
			'Authorization'        => 'Bearer ' . $api_key,
			'x-templately-ip'      => self::get_ip(),
			'x-templately-url'     => home_url('/'),
			'x-templately-version' => defined( 'TEMPLATELY_VERSION' ) ? constant( 'TEMPLATELY_VERSION' ) : '1.0.0',
		];

		// Add Content-Type for POST requests
		if (strtoupper($method) === 'POST') {
			$headers['Content-Type'] = 'application/json';
		}

		// Resolve requested platform: $_REQUEST wins (frontend-supplied), then caller's extra_headers, then default.
		if ( isset( $_REQUEST['requested_platform'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$extra_headers['x-templately-requested-platform'] = sanitize_text_field( wp_unslash( $_REQUEST['requested_platform'] ) );
		} elseif ( ! isset( $extra_headers['x-templately-requested-platform'] ) ) {
			$extra_headers['x-templately-requested-platform'] = 'templately';
		}

		// Merge additional headers
		$headers = array_merge($headers, $extra_headers);

		$args = [
			'timeout' => $timeout,
			'headers' => $headers,
		];

		// Apply filter to allow network admin or other functionality to modify request args
		$args = apply_filters( 'templately_api_request_params', $args, $method, $api_url );

		// Add body for POST requests
		if (strtoupper($method) === 'POST') {
			$args['body'] = is_array($body) ? json_encode($body) : $body;
		}

		// Make the appropriate request
		if (strtoupper($method) === 'POST') {
			$response = wp_remote_post($api_url, $args);
		} else {
			$response = wp_remote_get($api_url, $args);
		}

		// Check for verification header in the response
		self::check_verification_header($response);


		// Check for site disconnection in response body
		self::check_site_disconnection($response);

		return $response;
	}

	/**
	 * Make a GET request to Templately API
	 *
	 * @param string $endpoint API endpoint path (e.g., 'v2/import/pack/123')
	 * @param array $query_params Query parameters as key-value pairs
	 * @param array $extra_headers Additional headers beyond the standard ones
	 * @param int $timeout Request timeout in seconds (default: 30)
	 * @return array|WP_Error Response array or WP_Error on failure
	 */
	public static function make_api_get_request($endpoint, $query_params = [], $extra_headers = [], $timeout = 30) {
		$api_url = self::get_api_url($endpoint);

		// Add query parameters if provided
		if (!empty($query_params)) {
			$api_url = add_query_arg($query_params, $api_url);
		}

		return self::make_api_request('GET', $api_url, [], $extra_headers, $timeout);
	}

	/**
	 * Make a POST request to Templately API
	 *
	 * @param string $endpoint API endpoint path (e.g., 'v2/feedback/store')
	 * @param array $body Request body data
	 * @param array $extra_headers Additional headers beyond the standard ones
	 * @param int $timeout Request timeout in seconds (default: 30)
	 * @return array|WP_Error Response array or WP_Error on failure
	 */
	public static function make_api_post_request($endpoint, $body = [], $extra_headers = [], $timeout = 30) {
		$api_url = self::get_api_url($endpoint);
		return self::make_api_request('POST', $api_url, $body, $extra_headers, $timeout);
	}

	/**
	 * Sanitize Helper
	 *
	 * @param mixed $value
	 * @param string $type
	 *
	 * @return bool|string
	 */
	public static function sanitize($value, $type = 'text') {
		switch ($type) {
			case 'boolean':
				$sanitized_value = rest_sanitize_boolean($value);
				break;
			default:
				$sanitized_value = sanitize_text_field($value);
				break;
		}

		return $sanitized_value;
	}

	/**
	 * Escape a string for safe embedding inside a GraphQL or JSON string literal.
	 *
	 * GraphQL string escaping rules are identical to JSON string escaping (per the
	 * GraphQL spec), so wp_json_encode() is the authoritative escaper. We strip the
	 * outer quotes it adds and return only the escaped inner content, ready to be
	 * wrapped in your own quote pair.
	 *
	 * Handles pre-encoded JSON: when the caller has already run json_encode() +
	 * wp_slash() on a value (e.g. categories, dependencies in Items.php), the
	 * quotes are already escaped as \" and the string is ready to embed. Calling
	 * wp_json_encode() again would double-escape those backslashes. We detect this
	 * case by checking whether wp_unslash() produces valid JSON, and if so, return
	 * the value directly without further encoding.
	 *
	 * @param  string $value Raw string or wp_slash(json_encode()) output.
	 * @return string Escaped string, safe to place between double quotes in GraphQL/JSON.
	 */
	public static function esc_json_string( $value ) {
		$value = (string) $value;

		// If wp_slash() was applied to a JSON string upstream, the quotes are
		// already escaped (e.g. {\"key\":\"val\"}). Detect this by unslashing and
		// checking for valid JSON — if it matches, the value is already suitable
		// for embedding in a string literal; return it as-is to avoid doubling backslashes.
		$unslashed = wp_unslash( $value );
		if ( $unslashed !== $value ) {
			$decoded = json_decode( $unslashed, true );
			if ( json_last_error() === JSON_ERROR_NONE && null !== $decoded ) {
				return $value;
			}
		}

		$encoded = wp_json_encode( $value );
		// wp_json_encode wraps the value in "...", strip those outer quotes.
		return substr( $encoded, 1, -1 );
	}

	/**
	 * Check for X-Templately-Verified header and update user verification status
	 *
	 * @param array|WP_Error $response The HTTP response array from wp_remote_get/wp_remote_post
	 * @return void
	 */
	public static function check_verification_header($response) {
		// Only process if response is not a WP_Error and contains headers
		if (is_wp_error($response)) {
			return;
		}

		// Retrieve the X-Templately-Verified header
		$verification_header = wp_remote_retrieve_header($response, 'X-Templately-Verified');

		// Check if header exists and has a truthy value
		if (!empty($verification_header) && filter_var($verification_header, FILTER_VALIDATE_BOOLEAN)) {
			try {
				// Get current user data
				$options = Options::get_instance();
				$user = $options->get('user');

				// Only update if user data exists and is not already verified
				if (!empty($user) && is_array($user) && empty($user['is_verified'])) {
					// Set verification flag
					$user['is_verified'] = true;

					// Save updated user data
					$options->set('user', $user);

				}

				if (!empty($user['is_verified'])){
					if(!headers_sent()){
						header( 'X-Templately-Verified: true' );
						if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
							self::log('User verification status already updated via X-Templately-Verified header');
						}
					}

					return true;
				}
			} catch (\Exception $e) {
				// Log error if debug logging is enabled
				if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
					self::log('Error updating user verification status: ' . $e->getMessage());
				}
			}
		}

		return false;
	}

	/**
	 * Check for site disconnection status in API response body
	 *
	 * Detects SiteNotConnected errors and updates user disconnection status.
	 * Sends X-Templately-Disconnected header for frontend detection.
	 *
	 *
	 * @param array|WP_Error|mixed $response The response object or body array
	 * @return bool True if site is disconnected, false otherwise
	 */
	public static function check_site_disconnection($response) {
		if (is_wp_error($response)) {
			return false;
		}

		$response_body = $response;

		// If it's a raw WP response array with body, decode it
		if (is_array($response) && isset($response['body']) && is_string($response['body'])) {
			$response_body = json_decode(wp_remote_retrieve_body($response), true);
		}

		// Check if response body indicates site disconnection
		if (!is_array($response_body)) {
			return false;
		}

		$status = $response_body['status'] ?? null;
		$status_text = $response_body['statusText'] ?? null;

		// Check for SiteNotConnected error
		if ($status === 'error' && $status_text === 'SiteNotConnected') {
			try {
				// Get current user data
				$options = Options::get_instance();
				$user = $options->get('user');

				// Only update if user data exists
				if (!empty($user) && is_array($user)) {
					// Set disconnection flag
					$user['is_disconnected'] = true;

					// Save updated user data
					$options->set('user', $user);

					if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
						self::log('Site disconnection detected: SiteNotConnected status');
					}
				}

				// Send header for frontend detection
				if (!headers_sent()) {
					header('X-Templately-Disconnected: true');
				}

				return true;
			} catch (\Exception $e) {
				// Log error if debug logging is enabled
				if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
					self::log('Error updating site disconnection status: ' . $e->getMessage());
				}
			}
		}

		return false;
	}

	/**
	 * Clear site disconnection status
	 *
	 * Called after successful site migration to reset the disconnection flag.
	 *
	 * @return void
	 */
	public static function clear_site_disconnection() {
		try {
			$options = Options::get_instance();
			$user = $options->get('user');

			if (!empty($user) && is_array($user)) {
				$user['site_url']        = base64_encode( home_url('/') );
				$user['is_disconnected'] = false;
				$options->set('user', $user);

				if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
					self::log('Site disconnection status cleared and URL updated.');
				}
			}
		} catch (\Exception $e) {
			if (defined('TEMPLATELY_DEBUG_LOG') && constant('TEMPLATELY_DEBUG_LOG')) {
				self::log('Error clearing site disconnection status: ' . $e->getMessage());
			}
		}
	}

	/**
	 * API Error Formatter
	 *
	 * @param int $error_code
	 * @param mixed $error_message
	 * @param string $endpoint
	 * @param integer $status
	 * @param array $additional_data
	 * @return WP_Error
	 */
	public static function error($error_code, $error_message, $endpoint = '', $status = 500, $additional_data = []) {
		$additional_data['status'] = $status;
		if (! empty($endpoint)) {
			$additional_data['endpoint'] = $endpoint;
		}
		// Add browser padding to avoid browsers not serving small JSON responses
		$padding_length = 512;
		$additional_data['browser_padding'] = str_repeat(' ', $padding_length);

		return new WP_Error($error_code, $error_message, $additional_data);
	}

	/**
	 * API Response Formatter
	 *
	 * @param mixed $data
	 * @return WP_REST_Response
	 */
	public static function success($data) {
		return new WP_REST_Response($data, 200);
	}

	/**
	 * Normalize Favourites Data
	 *
	 * @param array $favourites
	 * @param array $_favourites
	 * @param boolean $undo
	 *
	 * @return array
	 */
	public function normalizeFavourites($favourites, $_favourites = [], $undo = false) {
		if ($undo) {
			$_favourites[$favourites['type']] = array_values(array_filter($_favourites[$favourites['type']], function ($item) use ($favourites) {
				return $item != $favourites['id'];
			}));
			return $_favourites;
		}

		array_map(function ($item) use (&$_favourites) {
			if (! is_null($item)) {
				$item = (array) $item;
				if (isset($_favourites[$item['type']])) {
					$_favourites[$item['type']][] = $item['id'];
				} else {
					$_favourites[$item['type']] = [$item['id']];
				}
			}
			return $_favourites;
		}, $favourites);

		return $_favourites;
	}

	public function normalizeReviews($favourites, $_favourites = [], $undo = false) {
		array_map(function ($item) use (&$_favourites) {
			if (! is_null($item)) {
				$item = (array) $item;
				if (!isset($_favourites[$item['type']])) {
					$_favourites[$item['type']] = [];
				}
				$_favourites[$item['type']][$item['type_id']] = $item['rating'];
			}
			return $_favourites;
		}, $favourites);

		return $_favourites;
	}

	/**
	 * Trigger Error
	 *
	 * @param object $triggered_by
	 * @return void
	 */
	public static function trigger_error($triggered_by, $method = 'get_instance') {
		$class = get_class($triggered_by);
		$trace = debug_backtrace(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
		$file = $trace[0]['file'];
		$line = $trace[0]['line'];
		trigger_error("Call to undefined method $class::$method() in $file on line $line", E_USER_ERROR);
	}

	/**
	 * Printing Error Logs in debug.log file.
	 *
	 * @param mixed $log The data to log
	 * @param string $context Optional context for categorizing log entries
	 * @param string $level Optional log level (debug, info, warning, error)
	 * @return void
	 */
	public static function log($log, $context = '', $level = 'info') {
		// Allow complete override of logging behavior
		$override_result = apply_filters('templately_log_override', null, $log, $context, $level);
		if ($override_result !== null) {
			return;
		}

		// Only log if WP_DEBUG_LOG is enabled
		if (!defined('WP_DEBUG_LOG') || !WP_DEBUG_LOG) {
			return;
		}

		// Format the log message
		$formatted_message = self::format_log_message($log, $context, $level);

		// Write to error log
		error_log($formatted_message);
	}

	/**
	 * Format log message with context and level
	 *
	 * @param mixed $log The data to log
	 * @param string $context Context for categorizing log entries
	 * @param string $level Log level
	 * @return string Formatted log message
	 */
	private static function format_log_message($log, $context = '', $level = 'info') {
		// Convert arrays and objects to readable format
		if (is_array($log) || is_object($log)) {
			$log_content = print_r($log, true);
		} else {
			$log_content = (string) ($log ?: '');
		}

		// Build the formatted message
		$timestamp = current_time('Y-m-d H:i:s');
		$level_upper = strtoupper($level);

		if (!empty($context)) {
			return "[{$timestamp}] [{$level_upper}] [{$context}] {$log_content}";
		} else {
			return "[{$timestamp}] [{$level_upper}] {$log_content}";
		}
	}

	public static function should_flush() {
		if (isset($_REQUEST['is_lightspeed']) && $_REQUEST['is_lightspeed'] === 'true') {
			return false;
		}
		return (!defined('TEMPLATELY_IGNORE_FLUSH_ALL') || !TEMPLATELY_IGNORE_FLUSH_ALL) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') === false;
	}

	public static function get_block_by_name($blocks, $search) {
		$queue = $blocks;

		while (!empty($queue)) {
			$current_block = array_shift($queue);

			if ($search === $current_block['blockName']) {
				return $current_block;
			}

			if (isset($current_block['innerBlocks'])) {
				// Add nested blocks to the end of the queue for processing
				$queue = array_merge($queue, $current_block['innerBlocks']);
			}
		}

		return false;
	}

	/**
	 * Only checks if user can install/activate plugins
	 *
	 * @param [type] $cap
	 * @param [type] ...$args
	 * @return void
	 */
	public static function current_user_can($cap, ...$args) {
		$user = wp_get_current_user();

		// Multisite super admin has all caps by definition, Unless specifically denied.
		if (is_multisite() && is_super_admin($user->ID)) {
			return true;
		}

		$caps = map_meta_cap($cap, $user->ID, ...$args);

		switch ($cap) {
			case 'install_plugins':
			case 'upload_plugins':
				$caps = ['install_plugins'];
				break;
			case 'install_themes':
			case 'upload_themes':
				$caps = ['install_themes'];
				break;
			case 'activate_plugins':
			case 'deactivate_plugins':
			case 'activate_plugin':
			case 'deactivate_plugin':
				$caps = ['activate_plugins'];
				break;
			default:
				break;
		}

		// Maintain BC for the argument passed to the "user_has_cap" filter.
		$args = array_merge(array($cap, $user->ID), $args);

		/**
		 * See WP_User::has_cap() for description.
		 */
		$capabilities = apply_filters('user_has_cap', $user->allcaps, $caps, $args, $user);

		// Everyone is allowed to exist.
		$capabilities['exist'] = true;

		// Nobody is allowed to do things they are not allowed to do.
		unset($capabilities['do_not_allow']);

		// Must have ALL requested caps.
		foreach ((array) $caps as $cap) {
			if (empty($capabilities[$cap])) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Calculates the elapsed time and checks if it is close to the maximum execution time.
	 * Returns true if the script should exit to avoid exceeding the limit.
	 *
	 * @return bool True if the script should exit, false otherwise.
	 */
	public static function fsi_should_exit() {
		if (defined('TEMPLATELY_START_TIME') && ini_get('max_execution_time')) {
			$max_time = ini_get('max_execution_time');
			$elapsed  = microtime(true) - TEMPLATELY_START_TIME;
			$delay    = max(5, $max_time * 20 / 100);

			// Check if elapsed time is close to max execution time
			if ($max_time - $elapsed <= $delay) {
				return ['max_time' => $max_time, 'elapsed' => $elapsed, 'delay' => $delay];
			}
		}
		return false;
	}

	/**
	 * Enable Elementor Container
	 * This function will enable the Elementor Container feature.
	 * Without this feature, some of the templates may not work properly.
	 *
	 * @return boolean
	 */
	public static function enable_elementor_container() {
		if (class_exists('Elementor\Plugin')) {
			$control_name = Plugin::instance()->experiments->get_feature_option_key('container');
			if (get_option($control_name) !== 'active') {
				update_option($control_name, 'active');
				return true;
			}
		}
		return false;
	}

	/**
	 * Undocumented function
	 *
	 * @param [type] $args
	 * @param [type] $defaults
	 * @return array
	 */
	public static function recursive_wp_parse_args($args, $defaults) {
		$args     = (array) $args;
		$defaults = (array) $defaults;
		$r = $defaults;
		foreach ($args as $key => $value) {
			if (is_array($value) && isset($r[$key])) {
				// also handle numeric array. if both $value and $r[ $key ] are numeric array. wp_is_numeric_array()
				if (wp_is_numeric_array($value) && wp_is_numeric_array($r[$key])) {
					foreach ($value as $k => $v) {
						if (!in_array($v, $r[$key])) {
							if (!isset($r[$key][$k])) {
								$r[$key][$k] = $v;
							} else {
								$r[$key][] = $v;
							}
						}
					}
				} else {
					$r[$key] = self::recursive_wp_parse_args($value, $r[$key]);
				}
			} else {
				$r[$key] = $value;
			}
		}
		return $r;
	}

}
