<?php
/**
 * Thrive Themes - https://thrivethemes.com
 *
 * @package thrive-theme
 */

namespace Thrive\Theme\AMP;

use Thrive_Css_Helper as CSS_Helper;

use Thrive_Section;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Silence is golden!
}

class Styles {

	/* The template styles that we're saving have to contain one of these classes */
	const SELECTORS_TO_SAVE = [
		'#wrapper ',
		'#content ',
		'.main-content-background ',
		'.content-section ',
		'.top-section',
		'.bottom-section',
		'.thrv_header',
		'.thrv_footer',
	];

	/* These styles always have to be skipped ( even if they are allowed through TEMPLATE_STYLES_TO_SAVE ) */
	const SELECTORS_TO_FILTER = [
		'.thrv_widget_menu',
		'.tve_w_menu',
		'.tcb-post-list',
		'.tve-toggle-column',
		'.tve-toggle-grid',
	];

	/**
	 * @param $post_id
	 *
	 * @return array|mixed
	 */
	public static function get_styles( $post_id ) {
		$style  = CSS_Helper::DEFAULT_STYLE_ARRAY;
		$lp_key = tve_post_is_landing_page();

		/*
		 * For each string of styles:
		 * 1) Turn the string into an array that has the media query part as the keys and the rules as the values
		 * 1.1) Filter the rules [optional]
		 * 2) Merge the current array into the accumulator array
		 */
		foreach ( static::get_styles_for_context( $post_id, $lp_key ) as $key => $style_string ) {
			$style_to_merge = CSS_Helper::get_style_array_from_string( $style_string );

			if ( ! empty( $style_to_merge['css'] ) && in_array( $key, [ 'post', THRIVE_HEADER_SECTION, THRIVE_FOOTER_SECTION ], true ) ) {
				$style_to_merge['css'] = static::filter_post_rules( $style_to_merge['css'] );
			}

			$style = CSS_Helper::merge_styles( $style, $style_to_merge );
		}

		$style = CSS_Helper::merge_styles( $style, [ 'css' => static::get_typography_css() ] );

		$full_style_string = thrive_css_helper()
			->set_css( $style['css'] )
			->generate_style( true, true, false )
			->get_style();

		$full_style_string = static::get_global_variables() . static::get_base_styles() . $full_style_string;
		$full_style_string = static::filter_css_string( $full_style_string );

		return static::compress_css( $full_style_string );
	}

	/**
	 * Return only the styles that make sense in this context.
	 *
	 * @param $post_id
	 * @param $lp_key
	 *
	 * @return array
	 */
	public static function get_styles_for_context( $post_id, $lp_key ) {
		/* these are always returned because they apply in any context */
		$styles = [
			'post'                => static::get_post_styles( $post_id, $lp_key ),
			'shared'              => static::get_shared_styles(),
			THRIVE_HEADER_SECTION => static::get_hf_styles( $post_id, THRIVE_HEADER_SECTION, $lp_key ),
			THRIVE_FOOTER_SECTION => static::get_hf_styles( $post_id, THRIVE_FOOTER_SECTION, $lp_key ),
		];

		/* the template styles are added only if we're not on a landing page */
		if ( empty( $lp_key ) ) {
			$styles = array_merge( $styles, [
				'template'         => static::get_template_styles(),
				'template_dynamic' => static::get_template_dynamic_styles(),
				'top_section'      => static::get_section_styles( 'top' ),
				'bottom_section'   => static::get_section_styles( 'bottom' ),
			] );
		}

		return $styles;
	}

	/**
	 * Get basic css for the elements
	 *
	 * @return string
	 */
	public static function get_base_styles() {
		return Main::get_amp_file( 'assets/base.css' );
	}

	/**
	 * @param $post_id
	 * @param $hf_key
	 * @param $lp_key
	 *
	 * @return string
	 */
	public static function get_hf_styles( $post_id, $hf_key, $lp_key ) {
		$css = '';

		if ( empty( $lp_key ) ) {
			/* for non-LPs, get the styles from the template's HF */
			$hf = thrive_template()->get_section( $hf_key );

			/* if the header/footer is dynamic, get the css; if it's static, it is automatically included in the template css, nothing to do here */
			if ( ! empty( $hf['id'] ) ) {
				$css = static::get_hf_css( $hf['id'] );
			}
		} else {
			/* for landing pages, fetch the styles from the LP meta */
			$hf_id = get_post_meta( $post_id, '_tve_' . $hf_key, true );

			$css = static::get_hf_css( $hf_id );
		}

		return $css;
	}

	/**
	 * Get css for the symbol ID - copied from symbol PHP
	 *
	 * @param $symbol_id
	 *
	 * @return string
	 */
	public static function get_hf_css( $symbol_id ) {
		$symbol_css = trim( get_post_meta( $symbol_id, 'tve_custom_css', true ) );

		$symbol_css = apply_filters( 'tcb_symbol_css_before', $symbol_css, $symbol_id );

		return $symbol_css;
	}

	/**
	 * @param $post_id
	 * @param $lp_key
	 *
	 * @return mixed|string
	 */
	public static function get_post_styles( $post_id, $lp_key ) {

		$css_meta_key = empty( $lp_key ) ? 'tve_custom_css' : 'tve_custom_css_' . $lp_key;

		return get_post_meta( $post_id, $css_meta_key, true );
	}

	/**
	 * @return mixed|string
	 */
	public static function get_template_styles() {
		$template_style = thrive_template()->get_meta( 'style' );

		$css = empty( $template_style['css'] ) ? [] : $template_style['css'];

		$css = static::filter_template_rules( $css );

		return thrive_css_helper()
			->set_css( $css )
			->generate_style( true, true, false )
			->get_style();
	}

	/**
	 * @return string
	 */
	public static function get_template_dynamic_styles() {
		$template_style = thrive_template()->get_meta( 'style' );

		$dynamic_css = empty( $template_style['dynamic'] ) ? [] : $template_style['dynamic'];

		$dynamic_css = static::filter_template_rules( $dynamic_css );

		return thrive_css_helper()
			->set_css( $dynamic_css )
			->generate_dynamic_style()
			->get_style();
	}

	/**
	 * @param $type
	 *
	 * @return mixed
	 */
	public static function get_section_styles( $type ) {
		$section_data = thrive_template()->get_section( $type );

		$section = new Thrive_Section( $section_data['id'], $section_data );

		return $section->style( false, true );
	}

	/**
	 * Iterate through the template CSS and keep only the allowed rules
	 *
	 * @param $css
	 *
	 * @return mixed
	 */
	public static function filter_template_rules( $css ) {
		return static::filter_rules( $css, 'is_template_rule_forbidden' );
	}

	/**
	 * Eliminate some rules from the post CSS ( mostly the ones that apply to blacklisted elements ).
	 *
	 * @param $css
	 *
	 * @return mixed
	 */
	public static function filter_post_rules( $css ) {
		return static::filter_rules( $css, 'is_rule_forbidden' );
	}

	/**
	 * Delete selectors and their css text if the given callback returns true
	 *
	 * @param $css
	 * @param $should_filter_fn
	 * @param $migrate_important - if we find '!important' in the css text, prefix the selector with ':not(#s)' because we're removing '!important' later
	 *
	 * @return mixed
	 */
	public static function filter_rules( $css, $should_filter_fn, $migrate_important = true ) {
		foreach ( $css as $media => $rules_string ) {
			foreach ( CSS_Helper::get_rules_from_string( $rules_string ) as $rule ) {
				$full_original_rule = $rule['selector'] . '{' . $rule['css_text'] . '}';

				if ( static::$should_filter_fn( $rule['selector'] ) ) {
					$css[ $media ] = str_replace( $full_original_rule, '', $css[ $media ] );
				} else if ( $migrate_important && strpos( $rule['css_text'], '!important' ) !== false ) {
					$css[ $media ] = str_replace( $full_original_rule, ':not(#s) ' . ltrim( $full_original_rule ), $css[ $media ] );
				}
			}
		}

		return $css;
	}

	/**
	 * We only keep a few important rules from the template:
	 * An accepted rule must be listed in TEMPLATE_STYLES_TO_SAVE and not be listed in SELECTORS_TO_FILTER
	 *
	 * @param $selector
	 *
	 * @return bool
	 */
	public static function is_template_rule_forbidden( $selector ) {
		$forbidden = true;

		if ( static::has_at_least_one_needle_in_haystack( $selector, static::SELECTORS_TO_SAVE ) ) {
			$forbidden = static::is_rule_forbidden( $selector );
		}

		return $forbidden;
	}

	/**
	 * A rule is forbidden if it contains one of the blacklisted classes or one of the selectors that we want to filter out
	 *
	 * @param $selector
	 *
	 * @return bool
	 */
	public static function is_rule_forbidden( $selector ) {
		return static::has_at_least_one_needle_in_haystack( $selector, Parser::BLACKLISTED_CLASSES ) ||
		       static::has_at_least_one_needle_in_haystack( $selector, static::SELECTORS_TO_FILTER );
	}

	/**
	 * Check if at least one of the needles exists in the haystack
	 *
	 * @param string $haystack
	 * @param array  $needles
	 *
	 * @return bool
	 */
	public static function has_at_least_one_needle_in_haystack( $haystack, $needles = [] ) {
		$needle_exists = false;

		foreach ( $needles as $needle ) {
			if ( strpos( $haystack, $needle ) !== false ) {
				$needle_exists = true;
				break;
			}
		}

		return $needle_exists;
	}

	/**
	 * @return array
	 */
	public static function get_typography_css() {
		$styles = tcb_default_style_provider()->get_processed_styles( null, 'object', false );

		return empty( $styles['media'] ) ? [] : $styles['media'];
	}

	/**
	 * Get all the shared and global styles.
	 * Provide the content as a parameter in order for the function to only print the global styles that are currently used.
	 *
	 * @return string
	 */
	public static function get_shared_styles() {
		return strip_tags( tve_get_shared_styles( Main::$content ) );
	}

	/**
	 * Get all the global variables.
	 * This copies tve_load_global_variables() from TAR except for the <style> HTML part
	 * @return string
	 */
	public static function get_global_variables() {

		$global_colors    = get_option( apply_filters( 'tcb_global_colors_option_name', 'thrv_global_colours' ), [] );
		$global_gradients = get_option( apply_filters( 'tcb_global_gradients_option_name', 'thrv_global_gradients' ), [] );

		$global_variable_string = ':root{';

		foreach ( $global_colors as $color ) {
			$global_variable_string .= TVE_GLOBAL_COLOR_VAR_CSS_PREFIX . $color['id'] . ':' . $color['color'] . ';';
		}
		foreach ( $global_gradients as $gradient ) {
			$global_variable_string .= TVE_GLOBAL_GRADIENT_VAR_CSS_PREFIX . $gradient['id'] . ':' . $gradient['gradient'] . ';';
		}

		ob_start();

		/* Insert extra global variables in the tve_global_variables style node */
		do_action( 'tcb_get_extra_global_variables' );

		$global_variable_string .= ob_get_clean();

		$global_variable_string .= '}';

		return $global_variable_string;
	}

	/**
	 * @return string
	 */
	public static function get_boilerplate_styles() {
		return Main::get_amp_file( 'boilerplate-css.php' );
	}

	/**
	 * Remove specific things from the CSS string: '!important', the template body class, [to be continued]
	 *
	 * @param $css
	 *
	 * @return mixed
	 */
	public static function filter_css_string( $css ) {
		$replacement_array = [
			'search'  => [
				'!important', /* remove '!important' */
				thrive_template()->body_class( false, 'string' ), /* remove the template body class */
				'dynamic_author=1#038;}', /* fix for encoded trailing ampersand */
			],
			'replace' => [
				'',
				'',
				'dynamic_author=1"); }',
			],
		];

		return str_replace( $replacement_array['search'], $replacement_array['replace'], $css );
	}

	/**
	 * Search the selectors for keywords that we can shorten ( for instance, shorten the data-css identifiers ) and add them to an array.
	 * The map is then used to shorten the stylesheet, and will also be used to make the same replacements in the post content.
	 *
	 * @param $css_string
	 *
	 * @return mixed
	 */
	public static function compress_css( $css_string ) {

		if ( preg_match_all( '/\[data-css="(.+?)"\]/m', $css_string, $matches ) && ! empty( $matches[1] ) ) {
			$css_identifiers = $matches[1];

			/* remove duplicates and re-index by using array_values() */
			$css_identifiers = array_values( array_unique( $css_identifiers ) );

			/* add to the replacements map */
			foreach ( $css_identifiers as $index => $css_identifier ) {
				static::$compression_map[ $css_identifier ] = 'c' . $index;
			}
		}

		/* replace everything in the map with their equivalent */
		$css_string = str_replace( array_keys( static::$compression_map ), array_values( static::$compression_map ), $css_string );

		/* mirror the changes in the content in order for the CSS to keep applying */
		Main::$content = str_replace( array_keys( static::$compression_map ), array_values( static::$compression_map ), Main::$content );

		return $css_string;
	}

	/**
	 * Store selectors that we want to shorten here in key - value pairs
	 * Example: [
	 * 'tve-u-170390ed004' => c1,
	 * 'tve-u-170390ab002' => c2,
	 *
	 * @var array
	 */
	public static $compression_map = [
		/* these are added now because they're constant */
		':not(#tve)'            => ':not(#s)',
		'thrv_wrapper'          => 'thw', /* the 't' is short for thrive */
		'tcb-button-link'       => 'tbl',
		'thrv-content-box'      => 'tcbx',
		'tcb-flex-col'          => 'tfc',
		'theme-section'         => 'ts',
		'theme-sidebar-section' => 'tss',
		'theme-content-section' => 'tcs',
		'theme-top-section'     => 'tts',
		'theme-bottom-section'  => 'tbs',
		'sidebar-section'       => 'tss',
		'content-section'       => 'tcs',
		'top-section'           => 'tts',
		'bottom-section'        => 'tbs',
		'section-background'    => 'tsb',
		'section-content'       => 'tsc',
		'thrv_symbol_'          => 'tsy',
		'symbol-section-in'     => 'tsyi',
		'symbol-section-out'    => 'tsyo',
		/* further on, this list is populated dynamically: */
		/* @see compress_css */
		//todo add more constant selectors
	];
}
