<?php
declare( strict_types=1 );

namespace FiboFilters\WP;

use FiboFilters\Config\Env;

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

/**
 * Class Helper
 */
class Helper {
	/**
	 * Get local date including timezone
	 *
	 * @param int    $timestamp Timestamp.
	 * @param string $format Date format.
	 *
	 * @return string
	 * @throws \Exception
	 */
	public function local_date( int $timestamp, string $format = '' ): string {

		if ( empty( $format ) ) {
			$format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
		}
		$date = new \WC_DateTime( "@$timestamp" );
		$date->setTimezone( new \DateTimeZone( wc_timezone_string() ) );

		return $date->date_i18n( $format );
	}

	/**
	 * Check if plugin is active
	 *
	 * @param string $plugin_name Plugin name.
	 *
	 * @return bool
	 */
	public static function is_plugin_active( string $plugin_name ) {
		global $wpdb;

		$active = false;
		$val    = $wpdb->get_var( "SELECT option_value FROM $wpdb->options WHERE option_name = 'active_plugins'" );

		if ( ! empty( $val ) && strpos( $val, 'a:' ) !== false ) {
			$plugins = maybe_unserialize( $val );
			if ( in_array( $plugin_name, $plugins, true ) ) {
				$active = true;
			}
		}

		return $active;
	}

	/**
	 * Set lock
	 *
	 * @param string $key Lock key.
	 *
	 * @return bool
	 */
	public static function set_lock( string $key ): bool {
		global $wpdb;

		if ( empty( $key ) ) {
			return false;
		}

		$id = $wpdb->get_var( $wpdb->prepare( "SELECT SQL_NO_CACHE option_id FROM $wpdb->options WHERE option_name = %s", $key ) );

		if ( empty( $id ) ) {
			$result = $wpdb->query( $wpdb->prepare( "INSERT INTO $wpdb->options (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, 'no')", $key, (string) microtime( true ) ) );
		} else {
			$result = $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->options SET `option_value` = %s WHERE `option_id` = %d", (string) microtime( true ), $id ) );
		}

		return $result > 0;
	}

	/**
	 * Test if lock is locked
	 *
	 * The lock is locked until its expiry time has elapsed.
	 *
	 * @param string $key Lock key
	 * @param int    $expiration Expiration time (seconds)
	 *
	 * @return bool
	 */
	public function is_lock_locked( string $key, int $expiration ): bool {
		global $wpdb;

		$timestamp = $wpdb->get_var( $wpdb->prepare( "SELECT SQL_NO_CACHE option_value FROM $wpdb->options WHERE option_name = %s", $key ) );

		if ( empty( $timestamp ) ) {
			return false;
		}

		return microtime( true ) - (float) $expiration < (float) $timestamp;
	}

	/**
	 * Unlock lock
	 *
	 * @param string $key Lock key
	 *
	 * @return bool
	 */
	public function unlock_lock( string $key ): bool {
		global $wpdb;

		if ( empty( $key ) ) {
			return false;
		}

		$result = $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->options WHERE option_name = %s", $key ) );

		return $result > 0;
	}

	public function get_widget_areas(): array {
		global $wp_registered_sidebars;

		if ( empty( $wp_registered_sidebars ) ) {
			return [];
		}

		return array_map(
			function ( array $item ): array {
				return [
					'label' => $item['name'],
					'value' => $item['id'],
				];
			},
			array_values( $wp_registered_sidebars )
		);
	}

	/**
	 * Get Ids of widget blocks that belong to FiboFilters
	 *
	 * @return array
	 */
	public function get_fibofilters_widget_blocks(): array {

		$keyword   = 'wp:fibofilters/filters';
		$ff_blocks = [];

		$blocks = get_option( 'widget_block' );
		if ( ! empty( $blocks ) ) {
			foreach ( $blocks as $block_id => $block ) {

				if ( ! empty( $block['content'] ) && strpos( $block['content'], $keyword ) !== false ) {
					$ff_blocks[] = 'block-' . $block_id;
				}
			}
		}

		return $ff_blocks;
	}

	/**
	 * Get Ids of widget blocks that belong to FiboFilters.
	 *
	 * @param array $sidebars_widgets An associative array of sidebars and their widgets.
	 *
	 * @return array
	 */
	public function get_fibofilters_widget_classic( array $sidebars_widgets ): array {
		$widget_names = [];
		foreach ( $sidebars_widgets as $widgets ) {

			if ( ! is_array( $widgets ) ) {
				continue;
			}

			foreach ( $widgets as $widget_name ) {

				if ( ! empty( $widget_name ) && strpos( $widget_name, 'fibofilters-filters-widget' ) !== false ) {
					$widget_names[] = $widget_name;
				}
			}
		}

		return $widget_names;
	}

	/**
	 * Retrieve full list of sidebars and their widget instance IDs.
	 *
	 * @param bool $only_active Exclude inactive widgets.
	 *
	 * @return array
	 */
	public function get_sidebars_widgets( bool $only_active = true ): array {
		$sidebars_widgets = get_option( 'sidebars_widgets', [] );

		if ( is_array( $sidebars_widgets ) ) {
			if ( isset( $sidebars_widgets['array_version'] ) ) {
				unset( $sidebars_widgets['array_version'] );
			}
			if ( $only_active && ( $sidebars_widgets['wp_inactive_widgets'] ) ) {
				unset( $sidebars_widgets['wp_inactive_widgets'] );
			}
		}

		return apply_filters( 'sidebars_widgets', $sidebars_widgets );
	}

	/**
	 * Check if the specific widget is assigned to the specific widget area.
	 *
	 * @param string $widget_name Widget id registered as a instance of the class \WC_Widget.
	 * @param string $widget_area Widget area.
	 *
	 * @return bool
	 */
	public function is_widget_in_widget_area( string $widget_name, string $widget_area ): bool {
		$contain          = false;
		$sidebars_widgets = $this->get_sidebars_widgets();

		if ( empty( $sidebars_widgets ) || ! is_array( $sidebars_widgets ) ) {
			return false;
		}

		foreach ( $sidebars_widgets as $area_id => $widgets ) {
			if ( empty( $widgets ) ) {
				continue;
			}

			if ( $contain ) {
				break;
			}

			foreach ( $widgets as $widget ) {

				if ( $widget_area === $area_id && strpos( $widget, $widget_name ) !== false ) {
					$contain = true;
					break;
				}
			}
		}

		return $contain;
	}

	/**
	 * List widget areas that contains FiboFilters block.
	 * Returns also total blocks in the widget area.
	 *
	 * @return array
	 */
	public function get_widget_areas_containing_ff_elements(): array {
		global $wp_registered_sidebars;
		$sidebars_widgets      = $this->get_sidebars_widgets();
		$ff_blocks             = $this->get_fibofilters_widget_blocks();
		$ff_classic_widgets    = $this->get_fibofilters_widget_classic( $sidebars_widgets );
		$ff_widgets            = array_merge( $ff_blocks, $ff_classic_widgets );
		$areas_with_ff_widgets = [];

		if ( empty( $sidebars_widgets ) || ! is_array( $sidebars_widgets ) ) {
			return $areas_with_ff_widgets;
		}

		foreach ( $sidebars_widgets as $area_id => $widgets ) {
			if ( empty( $widgets ) ) {
				continue;
			}

			foreach ( $widgets as $widget ) {
				if ( in_array( $widget, $ff_widgets, true ) ) {
					if ( isset( $areas_with_ff_widgets[ $area_id ] ) ) {
						$areas_with_ff_widgets[ $area_id ]['total'] ++;
					} else {
						$areas_with_ff_widgets[ $area_id ] = [
							'name'  => ! empty( $wp_registered_sidebars[ $area_id ]['name'] ) ? $wp_registered_sidebars[ $area_id ]['name'] : '',
							'total' => 1,
						];
					}
				}
			}
		}

		return $areas_with_ff_widgets;
	}

	public function maybe_switch_to_locale(): bool {
		/**
		 * Some of the data passed to the plugin settings contains translations and if the user's language
		 * is different from the website language, we must change the language, load the translation files
		 * again and then get this data.
		 *
		 * Thanks to this, these phrases are in the language of the website and not the user.
		 */
		$user_locale    = get_user_locale();
		$locale         = get_locale();
		$restore_needed = false;

		if ( $locale !== $user_locale ) {
			$restore_needed = true;
			unload_textdomain( 'fibofilters', true );
			switch_to_locale( $locale );
			load_plugin_textdomain( Env::$plugin_slug, false, Env::$languages_path_rel );
		}

		return $restore_needed;
	}

	public function restore_previous_locale() {
		unload_textdomain( 'fibofilters', true );
		restore_previous_locale();
		load_plugin_textdomain( Env::$plugin_slug, false, Env::$languages_path_rel );
	}

	/**
	 * Get all shortcodes from content
	 *
	 * @param string $content Post/page content.
	 *
	 * @return array
	 */
	public function get_shortcodes_from_content( string $content ): array {
		$pattern    = get_shortcode_regex();
		$shortcodes = [];
		if ( preg_match_all( '/' . $pattern . '/s', $content, $matches ) ) {
			foreach ( $matches[0] as $key => $value ) {
				$shortcodes[ $key ] = [
					'full_text' => $value,
					'tag'       => $matches[2][ $key ],
					'atts'      => shortcode_parse_atts( $matches[3][ $key ] ),
					'content'   => $matches[5][ $key ],
				];

				if ( ! empty( $shortcodes[ $key ]['atts'] ) ) {
					foreach ( $shortcodes[ $key ]['atts'] as $attr_name => $attr_value ) {
						// Filter numeric keys as they are valueless/truthy attributes.
						if ( is_numeric( $attr_name ) ) {
							$shortcodes[ $key ]['atts'][ $attr_value ] = true;
							unset( $shortcodes[ $key ]['atts'][ $attr_name ] );
						}
					}
				}
			}
		}

		return $shortcodes;
	}

	/**
	 * Checking if a given content contains a specified shortcode
	 *
	 * The function is based on has_shortcode() from core, but here we do not check if a given shortcode is registered.
	 * This difference is useful when we want to check it before a theme or plugin has registered a given shortcode.
	 *
	 * @param string $content Content to search.
	 * @param string $tag_name Shortcode tag name.
	 */
	public function has_shortcode( string $content, string $tag_name ): bool {
		if ( ! str_contains( $content, '[' ) ) {
			return false;
		}

		preg_match_all( '/' . get_shortcode_regex( [ $tag_name ] ) . '/', $content, $matches, PREG_SET_ORDER );
		if ( empty( $matches ) ) {
			return false;
		}

		foreach ( $matches as $shortcode ) {
			if ( $tag_name === $shortcode[2] ) {
				return true;
			} elseif ( ! empty( $shortcode[5] ) && self::has_shortcode( $shortcode[5], $tag_name ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Get all occurrences of a given shortcode with attributes
	 *
	 * @param string $content Content to search.
	 * @param string $tag_name Shortcode tag name.
	 */
	public function get_shortcodes( string $content, string $tag_name ): array {
		$shortcodes = [];

		$pattern = get_shortcode_regex( [ $tag_name ] );

		if ( empty( $content ) ) {
			return $shortcodes;
		}

		if ( preg_match_all( '/' . $pattern . '/s', $content, $matches, PREG_SET_ORDER ) ) {
			foreach ( $matches as $shortcode ) {
				$tag = $shortcode[2];
				if ( $tag === $tag_name ) {
					$inner_content = $shortcode[5] ?? '';

					$shortcodes[] = [
						'tag'     => $tag,
						'atts'    => shortcode_parse_atts( $shortcode[3] ),
						'content' => $inner_content,
					];

					$inner_shortcodes = self::get_shortcodes( $inner_content, $tag_name );
					$shortcodes       = array_merge( $shortcodes, $inner_shortcodes );
				}
			}
		}

		return $shortcodes;
	}

	/**
	 * Check if WP-Cron has missed events
	 */
	public function has_wp_cron_missed_events(): bool {
		global $wp_version;

		// Check if WP version is 5.4 or higher, as WP_Site_Health was introduced in 5.4.
		if ( version_compare( $wp_version, '5.4.0' ) < 0 ) {
			return false;
		}

		if ( ! class_exists( 'WP_Site_Health' ) ) {
			require_once ABSPATH . 'wp-admin/includes/class-wp-site-health.php';
		}

		$site_health = \WP_Site_Health::get_instance();
		$data        = $site_health->get_test_scheduled_events();

		if ( $data['status'] === 'critical' || $data['status'] === 'recommended' && $site_health->has_missed_cron() ) {
			return true;
		}

		return false;
	}
}
