<?php
/**
 * PA Assets Manager.
 */

namespace PremiumAddons\Includes;

use Elementor\Plugin;
use PremiumAddons\Includes\Helper_Functions;
use PremiumAddons\Admin\Includes\Admin_Helper;

require_once PREMIUM_ADDONS_PATH . 'widgets/dep/urlopen.php';

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

/**
 * PA Assets Manager Class.
 */
class Assets_Manager {

	/**
	 * Class Instance.
	 *
	 * @var object|null instance.
	 */
	private static $instance = null;

	/**
	 * Post Id.
	 * Option Id.
	 *
	 * @var string|null post_id.
	 */
	public static $post_id = null;

	/**
	 * Templates ids loaded in a post.
	 *
	 * @var array temp_ids.
	 */
	public static $temp_ids = array();

	/**
	 * All elements loaded in a post.
	 *
	 * @var array temp_elements.
	 */
	public static $temp_elements = array();

	/**
	 * Is page assets updated.
	 *
	 * @var boolean is_updated.
	 */
	public static $is_updated = null;

	/**
	 * Class Constructor.
	 */
	public function __construct() {

		add_action( 'elementor/editor/after_save', array( $this, 'handle_post_save' ), 10, 2 );

		// Check if the elments are cached.
		add_action( 'wp', array( $this, 'set_assets_vars' ) );

		// Save the elements on the current page.
		add_filter( 'elementor/frontend/builder_content_data', array( $this, 'manage_post_data' ), 10, 2 );

		add_action( 'wp_footer', array( $this, 'cache_post_assets' ) );

		add_action( 'wp_trash_post', array( $this, 'delete_cached_options' ) );
	}

	/**
	 * Sets Edit Time upon editor save.
	 *
	 * @access public
	 * @since 4.6.1
	 */
	public function handle_post_save( $post_id ) {

		if ( wp_doing_cron() ) {
			return;
		}

		// The post is saved, then we need to remove the assets related to it.
		$this->set_post_id( $post_id );
		self::remove_files();

		update_option( 'pa_edit_time', strtotime( 'now' ) );
	}

	/**
	 * Mange Post Data.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param array      $data  post data.
	 * @param int|string $post_id  post id.
	 *
	 * @return array
	 */
	public function manage_post_data( $data, $post_id ) {

		if ( ! self::$is_updated ) {
			$pa_elems = $this->extract_pa_elements( $data );

			self::$temp_ids[]    = $post_id;
			self::$temp_elements = array_unique( array_merge( self::$temp_elements, $pa_elems ) );
		}

		return $data;
	}

	/**
	 * Set post unique id.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param int|string $id  post id.
	 */
	public function set_post_id( $id = 'default' ) {

		$post_id = 'default' === $id ? 'pa_assets_' . get_queried_object_id() : 'pa_assets_' . $id;

		if ( null === self::$post_id ) {
			self::$post_id = Helper_Functions::generate_unique_id( $post_id );
		}
	}

	/**
	 * Extracts PA Elements.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param array $data  post data.
	 *
	 * @return array
	 */
	public function extract_pa_elements( $data ) {

		if ( empty( $data ) ) {
			return array();
		}

		$pa_names = Admin_Helper::get_pa_elements_names();

		$social_revs = array(
			'premium-yelp-reviews',
			'premium-google-reviews',
			'premium-facebook-reviews',
		);

		$pa_elems = array();

		Plugin::$instance->db->iterate_data(
			$data,
			function ( $element ) use ( &$pa_elems, $pa_names, $social_revs ) {

				if ( isset( $element['elType'] ) ) {

					if ( 'widget' === $element['elType'] && isset( $element['widgetType'] ) ) {

						$widget_type = ( 'global' === $element['widgetType'] && ! empty( $element['templateID'] ) ) ? $this->get_global_widget_type( $element['templateID'] ) : $element['widgetType'];

						if ( in_array( $widget_type, $pa_names, true ) && ! in_array( $widget_type, $pa_elems, true ) ) {

							$widget_type = in_array( $widget_type, $social_revs, true ) ? 'premium-reviews' : $widget_type;

							if ( in_array( $widget_type, array( 'premium-twitter-feed', 'premium-facebook-feed' ), true ) && ! in_array( 'social-common', $pa_elems, true ) ) {
								array_push( $pa_elems, 'social-common' );
							}

							array_push( $pa_elems, $widget_type );

							if ( 'premium-woo-products' === $widget_type ) {
								$papro_activated = apply_filters( 'papro_activated', false );

								if ( $papro_activated ) {
									array_push( $pa_elems, 'premium-woo-products-pro' );
								}
							}
						}
					}
				}
			}
		);

		return $pa_elems;
	}

	/**
	 * Get Global Wiget Type.
	 *
	 * @access public
	 * @since 4.6.1
	 * @link https://code.elementor.com/methods/elementor-templatelibrary-manager-get_template_data/
	 * @param int $temp_id  template it.
	 *
	 * @return string|void
	 */
	public function get_global_widget_type( $temp_id ) {

		$temp_data = Plugin::$instance->templates_manager->get_template_data(
			array(
				'source'      => 'local',
				'template_id' => $temp_id,
			)
		);

		if ( is_wp_error( $temp_data ) || ! $temp_data || empty( $temp_data ) ) {
			return;
		}

		if ( ! isset( $temp_data['content'] ) || empty( $temp_data['content'] ) ) {
			return;
		}

		return $temp_data['content'][0]['widgetType'];
	}

	/**
	 * Sets Assets Variables.
	 * Sets Post ID & Is_updated Flag.
	 *
	 * @access public
	 * @since 4.6.1
	 */
	public function set_assets_vars() {

		$is_edit_mode = Helper_Functions::is_edit_mode();

		if ( ! $this->is_built_with_elementor() || $is_edit_mode ) {
			return;
		}

		$this->set_post_id();

		self::$is_updated = self::is_ready_for_generate();
	}

	/**
	 * Is Built With Elementor.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @return boolean
	 */
	public function is_built_with_elementor() {

		if ( ! class_exists( 'Elementor\Plugin' ) ) {
			return false;
		}

		$type = get_post_type();

		if ( 'page' !== $type && 'post' !== $type ) {
			return false;
		}

		$current_id = get_the_ID();

		if ( ! $current_id || $current_id < 0 ) {
			return false;
		}

		$document = Plugin::$instance->documents->get( get_the_ID() );

		if ( ! $document ) {
			return false;
		}

		return $document->is_built_with_elementor();
	}

	/**
	 * Check if assets is updated.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @return boolean
	 */
	public static function is_ready_for_generate() {

		$editor_time = get_option( 'pa_edit_time', false );

		// If no post/page was saved after the feature is enabled.
		if ( ! $editor_time ) {
			update_option( 'pa_edit_time', strtotime( 'now' ) );
		}

		$post_edit_time = get_option( 'pa_edit_' . self::$post_id, false );

		// If the time of the last update is not equal to the time the current post was last changed. This means another post was saved, then load the default assets.
		// In this case, we need to load the default assets until the elements in the page needs to be cached first.
		if ( ! $post_edit_time || (int) $editor_time !== (int) $post_edit_time ) {
			// A change was made in the page elements, then we need to force the assets to be regenerated
			self::remove_files();
			return false;
		}

		return true;
	}

	/**
	 * Cached post assets.
	 *
	 * Update post options in db on page load.
	 *
	 * @access public
	 * @since 4.6.1
	 */
	public function cache_post_assets() {

		$is_edit_mode = Helper_Functions::is_edit_mode();
		$cond         = $this->is_built_with_elementor() && ! $is_edit_mode;

		if ( ! self::$is_updated && $cond ) {
			update_option( 'pa_elements_' . self::$post_id, self::$temp_elements, false );
			update_option( 'pa_edit_' . self::$post_id, get_option( 'pa_edit_time' ), false );
		}
	}

	/**
	 * Delete Cached Options.
	 * Delete post options from db on post delete.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param int $post_id  post id.
	 */
	public function delete_cached_options( $post_id ) {

		$id = substr( md5( 'pa_assets_' . $post_id ), 0, 9 );

		delete_option( 'pa_elements_' . $id );
		delete_option( 'pa_edit_' . $id );
	}

	/**
	 * Generate Assets files.
	 * Adds assets into pa-frontend(|-rtl).min.(js|css).
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param string $ext  assets extensions (js|css).
	 */
	public static function generate_asset_file( $ext ) {

		$direction      = is_rtl() && 'css' === $ext ? 'rtl-' : '';
		$main_file_name = Helper_Functions::get_safe_path( PREMIUM_ASSETS_PATH . '/pa-frontend-' . $direction . self::$post_id . '.min.' . $ext );

		// If the file already exists, then there is no need to regenerate a new one.
		if ( file_exists( $main_file_name ) ) {
			return;
		}

		$content = self::get_asset_file_content( $ext );

		// If no premium elements exist on the page, then don't generate files
		if ( empty( $content ) || 'empty' === $content ) {
			return 'empty';
		}

		if ( 'css' === $ext && is_rtl() ) {
			$rtl_file_name = Helper_Functions::get_safe_path( PREMIUM_ASSETS_PATH . '/pa-frontend-rtl-' . self::$post_id . '.min.css' );
		}

		if ( ! file_exists( PREMIUM_ASSETS_PATH ) ) {
			wp_mkdir_p( PREMIUM_ASSETS_PATH );
		}

		if ( 'css' === $ext ) {

			if ( is_rtl() ) {

				if ( empty( $content['rtl'] ) ) {
					return 'empty';
				}

				// Make sure to delete the file before creating the new one.
                file_put_contents( $rtl_file_name, '@charset "UTF-8";' . $content['rtl'] );  // phpcs:ignore

			} else {

				if ( empty( $content['main'] ) ) {
					return 'empty';
				}

                file_put_contents( $main_file_name, '@charset "UTF-8";' . $content['main'] ); // phpcs:ignore
			}
		} else {
			file_put_contents( $main_file_name, $content );  // phpcs:ignore
		}
	}


	/**
	 * Clear cached file.
	 * Delete file if it exists.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param string $file_name  file name.
	 */
	public static function clear_cached_file( $file_name ) {

		if ( file_exists( $file_name ) ) {
			wp_delete_file( $file_name );
		}
	}

	/**
	 * Remove files
	 *
	 * @since 4.6.1
	 */
	public static function remove_files() {

		$ext = array( 'css', 'js' );

		foreach ( $ext as $e ) {

			$path = PREMIUM_ASSETS_PATH . '/pa-frontend-' . self::$post_id . '.min.' . $e;

			if ( 'css' === $e ) {
				$rtl_path = PREMIUM_ASSETS_PATH . '/pa-frontend-rtl-' . self::$post_id . '.min.' . $e;
				self::clear_cached_file( $rtl_path );
			}

			self::clear_cached_file( $path );
		}
	}

	/**
	 * Get Asset File Content.
	 *
	 * Collects pa/papro widgets assets.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param string $ext js|css.
	 *
	 * @return string|array $content
	 */
	public static function get_asset_file_content( $ext ) {

		// Get the cached elements of the current post/page.
		$pa_elements = get_option( 'pa_elements_' . self::$post_id, array() );

		if ( empty( $pa_elements ) ) {
			return '';
		}

		$content = '';

		if ( 'css' === $ext ) {
			$rtl_content = '';
		}

		$pa_elements = self::prepare_pa_elements( $pa_elements, $ext );

		foreach ( $pa_elements as $element ) {

			$path = self::get_file_path( $element, $ext );

			if ( ! $path ) {
				continue;
			}

			$file_content = self::get_file_content( $path );

			if ( 'not_found' === $file_content ) {
				continue;
			}

			if ( 'empty' === $file_content ) {
				return 'empty';
			}

			$content .= $file_content;

			if ( 'css' === $ext && is_rtl() ) {
				$rtl_path     = self::get_file_path( $element, $ext, '-rtl' );
				$rtl_content .= self::get_file_content( $rtl_path );
			}
		}

		if ( 'css' === $ext ) {

			$content = array(
				'main' => $content,
				'rtl'  => $rtl_content,
			);

			// Fix: at-rule or selector expected css error.
			$content = str_replace( '@charset "UTF-8";', '', $content );
		}

		return $content;
	}

	/**
	 * Prepare PA Elements.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param array  $elements  post elements.
	 * @param string $ext  js|css.
	 *
	 * @return array
	 */
	public static function prepare_pa_elements( $elements, $ext ) {

		if ( 'css' === $ext ) {
			$common_assets = self::has_free_elements( $elements ) ? array( 'common' ) : array();
			$common_assets = self::has_pro_elements( $elements ) ? array_merge( $common_assets, array( 'common-pro' ) ) : $common_assets;

			$elements       = array_merge( $elements, $common_assets );
			$indep_elements = array(
				'premium-world-clock',
			);

		} else {
			$indep_elements = array(
				'social-common',
				'premium-hscroll',
				'premium-facebook-feed',
				'premium-behance-feed',
				'premium-lottie',
				'premium-vscroll',
				'premium-hscroll',
				'premium-nav-menu',
				'premium-addon-maps',
				'premium-woo-products',
				'premium-woo-products-pro',
				'premium-mini-cart',
				'premium-woo-cta',
				// 'premium-addon-testimonials',
				'premium-smart-post-listing',
				'premium-addon-pricing-table',
				'premium-addon-image-separator',
				'premium-notifications',
			);

		}

		$elements = array_diff( $elements, $indep_elements );

		return $elements;
	}

	/**
	 * Get File Content.
	 *
	 * @param string $path file path.
	 *
	 * @return string
	 */
	public static function get_file_content( $path ) {

		$file_content = rplg_urlopen( $path );

		if ( isset( $file_content['code'] ) ) {

			if ( 404 === $file_content['code'] ) {
				return 'not_found';
			}

			if ( in_array( $file_content['code'], array( 0, 401, 403 ), true ) ) {
				return 'empty';
			}
		}

		return self::clean_content( $file_content['data'] );
	}

	/**
	 * Clean Content
	 * Removes Page Html if it's returned as result.
	 *
	 * @param string $content file content.
	 *
	 * @return string
	 */
	public static function clean_content( $content ) {

		if ( stripos( $content, '<!DOCTYPE' ) ) {
			$content = 'empty';
		}

		// if ( strpos( $content, '<!doctype html>' ) ) {
		// $content = explode( '<!doctype html>', $content )[0];
		// }

		return $content;
	}

	/**
	 * Get File Path.
	 * Construct file path.
	 *
	 * @param string $element  pa element name.
	 * @param string $ext      file extension ( js|css).
	 * @param string $dir      post dir (-rtl|'').
	 *
	 * @return string file path.
	 */
	public static function get_file_path( $element, $ext, $dir = '' ) {

		$is_pro = self::is_pro_widget( $element );

		$papro_activated = apply_filters( 'papro_activated', false ) && version_compare( PREMIUM_PRO_ADDONS_VERSION, '2.7.1', '>' );

		if ( ! $papro_activated && $is_pro ) {
			return false;
		}

		$element = str_replace( '-addon', '', $element );

		$path = $is_pro ? PREMIUM_PRO_ADDONS_URL : PREMIUM_ADDONS_URL;

		return $path . 'assets/frontend/min-' . $ext . '/' . $element . $dir . '.min.' . $ext;
	}

	/**
	 * Is Pro Widget.
	 * Checks if the widget is pro.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param string $widget  widget name.
	 *
	 * @return bool
	 */
	public static function is_pro_widget( $widget ) {

		$pro_names = array_merge( array( 'common-pro', 'premium-reviews', 'premium-woo-products-pro', 'social-common' ), self::get_pro_widgets_names() );

		return in_array( $widget, $pro_names, true );
	}

	/**
	 * Has Pro Elements.
	 * Check if the post has pa pro elements.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param array $post_elems post elements.
	 *
	 * @return boolean
	 */
	public static function has_pro_elements( $post_elems ) {

		$papro_elems = self::get_pro_widgets_names();
		$has_pro     = array_intersect( $post_elems, $papro_elems ) ? true : false;

		return $has_pro;
	}

	/**
	 * Has Free Elements.
	 * Check if the post has pa elements.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @param array $post_elems post elements.
	 *
	 * @return boolean
	 */
	public static function has_free_elements( $post_elems ) {

		$pa_elems = Admin_Helper::get_free_widgets_names();

		// add some other pro widgets.
		$pa_elems = array_merge(
			$pa_elems,
			array(
				'premium-smart-post-listing',
				'premium-addon-instagram-feed',
				'premium-notbar',
				'premium-addon-flip-box',
				'premium-addon-icon-box',
				'premium-addon-magic-section',
				'premium-whatsapp-chat',
			)
		);

		$has_free = array_intersect( $post_elems, $pa_elems ) ? true : false;

		return $has_free;
	}

	/**
	 * Get Pro Widgets Names.
	 *
	 * @access public
	 * @since 4.6.1
	 *
	 * @return array
	 */
	public static function get_pro_widgets_names() {

		$pro_elems = Admin_Helper::get_pro_elements();
		$pro_names = array();

		foreach ( $pro_elems as $element ) {
			if ( isset( $element['name'] ) ) {
				array_push( $pro_names, $element['name'] );
			}
		}

		return $pro_names;
	}

	/**
	 * Creates and returns an instance of the class.
	 *
	 * @since  4.6.1
	 * @access public
	 *
	 * @return object
	 */
	public static function get_instance() {

		if ( ! isset( self::$instance ) ) {

			self::$instance = new self();

		}

		return self::$instance;
	}
}
