<?php

/**
 * @package SP Page Builder
 * @author JoomShaper http: //www.joomshaper.com
 * @copyright Copyright (c) 2010 - 2023 JoomShaper
 * @license http: //www.gnu.org/licenses/gpl-2.0.html GNU/GPLv2 or later
 */

// No direct access
defined('_JEXEC') or die('Restricted access');

/**
 * Helper class for handling the lodash string.
 *
 * @since	4.0.0
 */
final class Lodash extends HelperBase
{
	/**
	 * Device Size
	 *
	 * @var array
	 */
	private $sizes = ["lg", "md", "sm", "xs"];
	/**
	 * The constructor function for the lodash.
	 *
	 * @param string $id 	The unique CSS ID.
	 * @param bool $force	Flag to set the ID as whatever it is provided, no sanitization is required.
	 */
	public function __construct(string $id = '', bool $force = false)
	{
		parent::__construct($id, $force);
	}

	/**
	 * Manage border style css property
	 *
	 * @param 	string 	$property	CSS Property.
	 * @param 	string 	$selector	CSS Selector.
	 * @param 	string 	$data		Value for CSS Property.
	 * @param 	string 	$static		Provided any static style value. If provided that would be contacted.
	 * 
	 * @return 	string 	CSS Value for Border.
	 * @since 	4.0.0
	 */
	public function border(string $property, string $selector, string $data, $static = ''): string
	{
		$selector = $this->generateSelector($selector);

		$css   = [];
		$css[] = $selector . ' {';
		if (!empty($static)) {
			$css[] = $static;
		}
		$css[] = '<# if(_.isObject(' . $data . ') && !_.isEmpty(' . $data . ')) { #>';
		$css[] = $property . ': ' . $this->inlineBlock($data . '[window.builderDefaultDevice]') . ';';
		$css[] = '<# } else { #>';
		$css[] = $property . ': ' . $this->inlineBlock($data) . ';';
		$css[] = '<# } #>';
		$css[] = '}';

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

	/**
	 * manage alignment css
	 *
	 * @param	string 	$property
	 * @param 	string 	$selector
	 * @param 	string 	$data
	 * 
	 * @return 	string
	 * @since 	4.0.0
	 */
	public function alignment(string $property, string $selector, string $data): string
	{
		$selector = $this->generateSelector($selector);
		$css = [];

		if (isset($property) && isset($selector) && isset($data)) {
			$css[] = $selector . ' {';
			$css[] = '<# if(_.isObject(' . $data . ') && !_.isEmpty(' . $data . ')) { #>';
			$css[] = $property . ': ' . $this->inlineBlock($data . '[window.builderDefaultDevice]') . ';';
			$css[] = '<# } else { #>';
			$css[] = '<# if ( ' . $data . '== "sppb-text-center" ||  ' . $data . '== "center" ) { #>';
			$css[] = $property . ': center;';
			$css[] = '<# } else if ( ' . $data . '== "sppb-text-right" || ' . $data . '== "right" ) { #>';
			$css[] = $property . ': right;';
			$css[] = '<# } else if ( ' . $data . '== "sppb-text-left" || ' . $data . '== "left") { #>';
			$css[] = $property . ': left;';
			$css[] = '<# } #>';
			$css[] = '<# } #>';
			$css[] = '}';

			// render media query css
			$css[] = '<# if(_.isObject(' . $data . ') && !_.isEmpty(' . $data . ')) { #>';
			$css[] = $this->mediaQuery($property, $selector, $data);
			$css[] = '<# } #>';
		}

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

	/**
	 * Flex Alignment function
	 *
	 * @param 	string 	$selector
	 * @param 	string 	$data
	 * 
	 * @return 	string
	 * @since 	4.0.0
	 */
	public function flexAlignment(string $selector, string $data): string
	{
		$selector = $this->generateSelector($selector);
		$css = [];

		$css[] = $selector . ' {';
		$css[] = '<# if(_.isObject(' . $data . ') && !_.isEmpty(' . $data . ')) { #>';
		$css[] = '<# if (' . $data . '[window.builderDefaultDevice] == "left") { #>';
		$css[] = '-webkit-box-pack: start;';
		$css[] = '-ms-flex-pack: start;';
		$css[] = 'justify-content: flex-start;';
		$css[] = '<# } else if (' . $data . '[window.builderDefaultDevice] == "right") { #>';
		$css[] = '-webkit-box-pack: end;';
		$css[] = '-ms-flex-pack: end;';
		$css[] = 'justify-content: flex-end;';
		$css[] = '<# } else if (' . $data . '[window.builderDefaultDevice] == "center") { #>';
		$css[] = '-webkit-box-pack: center;';
		$css[] = '-ms-flex-pack: center;';
		$css[] = 'justify-content: center;';
		$css[] = '<# } #>';
		$css[] = '<# } else {#>';
		$css[] = '<# if (' . $data . '== "left" || ' . $data . ' == "sppb-text-left")  { #>';
		$css[] = '-webkit-box-pack: start;';
		$css[] = '-ms-flex-pack: start;';
		$css[] = 'justify-content: flex-start;';
		$css[] = '<# } else if (' . $data . '== "right" || ' . $data . ' == "sppb-text-right") { #>';
		$css[] = '-webkit-box-pack: end;';
		$css[] = '-ms-flex-pack: end;';
		$css[] = 'justify-content: flex-end;';
		$css[] = '<# } else if (' . $data . '== "center" || ' . $data . ' == "sppb-text-center") { #>';
		$css[] = '-webkit-box-pack: center;';
		$css[] = '-ms-flex-pack: center;';
		$css[] = 'justify-content: center;';
		$css[] = '<# } #>';
		$css[] = '<# } #>';
		$css[] = '}';

		foreach ($this->sizes as $size) {
			$css[] = $this->mediaQueryDevice($size);
			$css[] = $selector . ' {';
			$css[] = '<# if (' . $data . '?.' . $size . ' == "left") { #>';
			$css[] = '-webkit-box-pack: start;';
			$css[] = '-ms-flex-pack: start;';
			$css[] = 'justify-content: flex-start;';
			$css[] = '<# } else if (' . $data . '?.' . $size . ' == "right") { #>';
			$css[] = '-webkit-box-pack: end;';
			$css[] = '-ms-flex-pack: end;';
			$css[] = 'justify-content: flex-end;';
			$css[] = '<# } else if (' . $data . '?.' . $size . ' == "center") { #>';
			$css[] = '-webkit-box-pack: center;';
			$css[] = '-ms-flex-pack: center;';
			$css[] = 'justify-content: center;';
			$css[] = '<# } #>';
			$css[] = '}';
			$css[] = '}';
		}

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

	/**
	 * Flex function
	 *
	 * @param 	string 	$property
	 * @param 	string 	$selector
	 * @param 	string 	$data
	 * @param 	string 	$unit
	 * 
	 * @return 	string
	 * @since 	4.0.0
	 */
	public function flex(string $property, string $selector, string $data, $unit = ''): string
	{

		$selector = $this->generateSelector($selector);

		$css   = [];
		$css[] = $selector . ' {';
		$css[] = '<# if (_.isObject(' . $data . ') && !_.isEmpty(' . $data . ') ) { #>';
		$css[] = $property . ': 0 0 ' . $this->inlineBlock($data . '?.[window.builderDefaultDevice]') . $unit . ';';
		$css[] = '<# } else { #>';
		$css[] = $property . ': 0 0 ' . $this->inlineBlock($data) . $unit . ';';
		$css[] = '<# } #>';
		$css[] = '}';

		foreach ($this->sizes as $size) {
			$css[] = $this->mediaQueryDevice($size);
			$css[] = $selector . ' {';
			$css[] = '<# if (_.isObject(' . $data . ') && !_.isEmpty(' . $data . ') ) { #>';
			$css[] = $property . ': 0 0 ' . $this->inlineBlock($data . '?.' . $size) . $unit . ';';
			$css[] = '<# } else { #>';
			$css[] = $property . ': 0 0 ' . $this->inlineBlock($data) . $unit . ';';
			$css[] = '<# } #>';
			$css[] = '}';
			$css[] = '}';
		}
		return implode("\n", $css);
	}

	/**
	 * Manage spacing property
	 *
	 * @param 	string  $property 	CSS property name.
	 * @param 	string  $selector 	CSS selector name.
	 * @param 	string  $data		CSS value.
	 * @param 	boolean $isStatic 	Check value is static.
	 * 
	 * @return 	string 				The generated spacing string.
	 * @since 	4.0.0
	 */
	public function spacing(string $property, string $selector, string $data, $isStatic = false): string
	{
		$selector = $this->generateSelector($selector);

		$css   = [];
		$css[] = '
		<#
		var value = window.getMarginPadding( ' . $data . ', `' . $property . '`);
		
		#>
		';
		$css[] = $selector . ' {';

		if ($isStatic) {
			$css[] = $property . ': ' . $data . ';';
		} else {
			$css[] = '<# if(_.isObject(value) && !_.isEmpty(value)) { #>';
			$css[] =  $this->inlineBlock('value?.[window.builderDefaultDevice]');
			$css[] = '<# } else { #>';
			$css[] = $this->inlineBlock('value');
			$css[] = '<# } #>';
		}

		$css[] = '}';

		foreach ($this->sizes as $size) {
			$css[] = $this->mediaQueryDevice($size);
			$css[] = $selector . ' {';
			$css[] = '<# if(_.isObject(value) && !_.isEmpty(value)) { #>';
			$css[] =  $this->inlineBlock('value?.' . $size);
			$css[] = '<# } else { #>';
			$css[] = $this->inlineBlock('value');
			$css[] = '<# } #>';
			$css[] = '}';
			$css[] = '}'; // end selector
		}

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

	/**
	 * manage unit
	 *
	 * @param	string 	$property 
	 * @param	string 	$selector
	 * @param	string 	$data
	 * @param	string 	$unit
	 * @param	boolean $responsive
	 * @param	string 	$prefix
	 * @param	string 	$static	Provided any static style value. If provided that would be contacted.
	 * 
	 * @return	string
	 * @since 4.0.0
	 */
	public function unit(string $property, string $selector, string $data, $unit = '', $responsive = true, $prefix = '', $static = ''): string
	{
		$selector = $this->generateSelector($selector);

		$css      = [];
		$css[]    = $selector . ' {';
		if (!empty($static)) {
			$css[] = $static;
		}
		$css[]    = '<# if(_.isObject(' . $data . ') && !_.isEmpty(' . $data . ')) { #>';
		$css[]    = $property . ': ' . $prefix . $this->inlineBlock($data . '?.[window.builderDefaultDevice]') . $unit . ';';
		$css[]    = '<# } else { #>';
		$css[]    = $property . ': ' . $prefix . $this->inlineBlock($data) . $unit . ';';
		$css[]    = '<# } #>';
		$css[]    = '}';

		if ($responsive) {
			$css[] = '<# if(_.isObject(' . $data . ') && !_.isEmpty(' . $data . ')) { #>';
			$css[] = $this->mediaQuery($property, $selector, $data, $unit, false, $prefix);
			$css[] = '<# } #>';
		}

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

	/**
	 * manage color
	 *
	 * @param 	string 	$property
	 * @param 	string 	$selector
	 * @param 	string 	$data
	 * @param 	array 	$dependencies
	 * 
	 * @return 	string
	 * @since 	4.0.0
	 */
	public function color(string $property, string $selector, string $data, $dependencies = []): string
	{
		$selector = $this->generateSelector($selector);

		$css = [];
		$css[]    = $selector . ' {';

		if ($property === 'background-color' || $property === 'background') {
			$css[] = '<# if (!_.isEmpty(' . $data . ')) { #>';
			$css[] = '<# if (_.isObject(' . $data . ') && ' . $data . '?.type == "solid") { #>';
			$css[] = 'background-color: ' . $this->inlineBlock($data . '.color') . ';';
			$css[] = '<# } else if (_.isObject(' . $data . ') && ' . $data . '?.type == "linear") { #>';
			$css[] = 'background: -webkit-linear-gradient(' . $this->inlineBlock($data . '?.deg || 0') . 'deg, ' .  $this->inlineBlock($data . '?.color || "#222"') . ' ' . $this->inlineBlock($data . '?.pos || 0') . '%, ' . $this->inlineBlock($data . '?.color2 || "#222"') . ' ' . $this->inlineBlock($data . '?.pos2 || 100') . '%' . ');';
			$css[] = 'background: linear-gradient(' . $this->inlineBlock($data . '?.deg || 0') . 'deg, ' .  $this->inlineBlock($data . '?.color || "#222"') . ' ' . $this->inlineBlock($data . '?.pos || 0') . '%, ' . $this->inlineBlock($data . '?.color2 || "#222"') . ' ' . $this->inlineBlock($data . '?.pos2 || 100') . '%' . ');';
			$css[] = '<# } else if (_.isObject(' . $data . ') && ' . $data . '?.type == "radial") { #>';
			$css[] = 'background: -webkit-radial-gradient(at ' . $this->inlineBlock($data . '?.radialPos || "center center"') . ', ' .  $this->inlineBlock($data . '?.color || "#222"') . ' ' . $this->inlineBlock($data . '?.pos || 0') . '%, ' . $this->inlineBlock($data . '?.color2 || "#222"') . ' ' . $this->inlineBlock($data . '?.pos2 || 100') . '%' . ');';
			$css[] = 'background: radial-gradient(at ' . $this->inlineBlock($data . '?.radialPos || "center center"') . ', ' .  $this->inlineBlock($data . '?.color || "#222"') . ' ' . $this->inlineBlock($data . '?.pos || 0') . '%, ' . $this->inlineBlock($data . '?.color2 || "#222"') . ' ' . $this->inlineBlock($data . '?.pos2 || 100') . '%' . ');';
			$css[] = '<# } else { #>';
			$css[] = 'background-color: ' . $this->inlineBlock($data) . ';';
			$css[] = '<# } #>';
			$css[] = '<# } #>';
		} else if ($property == 'color') {
			$css[] = '<# if (!_.isEmpty(' . $data . ')) { #>';
			$css[] = '<# if (_.isObject(' . $data . ') && ' . $data . '?.type == "solid") { #>';
			$css[] = 'color: ' . $this->inlineBlock($data . '.color') . ';';
			$css[] = '<# } else if (_.isObject(' . $data . ') && ' . $data . '?.type == "linear") { #>';
			$css[] = '-webkit-background-clip: text;';
			$css[] = 'background-clip: text;';
			$css[] = '-webkit-text-fill-color: transparent;';
			$css[] = 'background-image: -webkit-linear-gradient(' . $this->inlineBlock($data . '?.deg || 0') . 'deg, ' .  $this->inlineBlock($data . '?.color || "#398AF1"') . ' ' . $this->inlineBlock($data . '?.pos || 0') . '%, ' . $this->inlineBlock($data . '?.color2 || "#5EDCED"') . ' ' . $this->inlineBlock($data . '?.pos2 || 100') . '%' . ');';
			$css[] = 'background-image: linear-gradient(' . $this->inlineBlock($data . '?.deg || 0') . 'deg, ' .  $this->inlineBlock($data . '?.color || "#398AF1"') . ' ' . $this->inlineBlock($data . '?.pos || 0') . '%, ' . $this->inlineBlock($data . '?.color2 || "#5EDCED"') . ' ' . $this->inlineBlock($data . '?.pos2 || 100') . '%' . ');';
			$css[] = '<# } else if (_.isObject(' . $data . ') && ' . $data . '?.type == "radial") { #>';
			$css[] = '-webkit-background-clip: text;';
			$css[] = 'background-clip: text;';
			$css[] = '-webkit-text-fill-color: transparent;';
			$css[] = 'background-image: -webkit-radial-gradient(at ' . $this->inlineBlock($data . '?.radialPos || "center center"') . ', ' .  $this->inlineBlock($data . '?.color || "#398AF1"') . ' ' . $this->inlineBlock($data . '.pos || 0') . '%, ' . $this->inlineBlock($data . '?.color2 || "#5EDCED"') . ' ' . $this->inlineBlock($data . '.pos2 || 100') . '%' . ');';
			$css[] = 'background-image: radial-gradient(at ' . $this->inlineBlock($data . '?.radialPos || "center center"') . ', ' .  $this->inlineBlock($data . '?.color || "#398AF1"') . ' ' . $this->inlineBlock($data . '.pos || 0') . '%, ' . $this->inlineBlock($data . '?.color2 || "#5EDCED"') . ' ' . $this->inlineBlock($data . '.pos2 || 100') . '%' . ');';
			$css[] = '<# } else { #>';
			$css[] = 'color: ' . $this->inlineBlock($data) . ';';
			$css[] = '<# } #>';
			$css[] = '<# } #>';
		}

		$css[] = ' }';

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

	/**
	 * Load google fonts
	 *
	 * @param	string 		$data
	 * @param	boolean 	$isFallbacks
	 * 
	 * @return 	string
	 * @since 	4.0.0
	 */
	private function loadGoogleFonts(string $data, $isFallbacks = false): string
	{
		$content = ($isFallbacks) ? $data . ';' : $data . '.font;';

		$css  = '<# var sppb_head = window.frames["sp-pagebuilder-view"].window.document.head; #>';
		$css .= '<# var sppb_doc  = window.frames["sp-pagebuilder-view"].window.document;#>';
		$css .= '<# var sppb_fontFamily = ' . $content . ' #>';

		$css .= '<#
	
				const systemFonts = [
					"System",
					"Google Fonts",
					"Arial",
					"Tahoma",
					"Verdana",
					"Helvetica",
					"Times New Roman",
					"Trebuchet MS",
					"Georgia",
				];

				if (!_.isEmpty(sppb_fontFamily)){

					let fontLink = sppb_head.querySelector(
						"#google-font-" + sppb_fontFamily.toLowerCase().replace(/\s+/g, "_")
					);

					if (!fontLink && systemFonts.indexOf(sppb_fontFamily) === -1) {
						let googleFontLink = 
							"https://fonts.googleapis.com/css?family=" +
							sppb_fontFamily +
							":100,100italic,200,200italic,300,300italic,400,400italic,500,500italic,600,600italic,700,700italic,800,800italic,900,900italic&display=swap";
						let fontLink    = sppb_doc.createElement("link");
							fontLink.id = 
							"google-font-" + sppb_fontFamily.toLowerCase().replace(/\s+/g, "_");
						fontLink.href = googleFontLink;
						fontLink.rel  = "stylesheet";
						fontLink.type = "text/css";
						if (!disableGoogleFonts){
							sppb_head.appendChild(fontLink);
						}
					}
				} #>';

		return $css;
	}

	/**
	 * Manage typography
	 *
	 * @param 	string 	$selector
	 * @param 	string 	$data
	 * @param 	array 	$fallbacks
	 * 
	 * @return 	string
	 * @since 	4.0.0
	 */
	public function typography(string $selector, string $data,  array $fallbacks = []): string
	{
		$selector = $this->generateSelector($selector);

		$css = [];

		$font           = (!empty($fallbacks) && array_key_exists('font', $fallbacks)) ? $fallbacks['font'] : "undefined";
		$size           = (!empty($fallbacks) && array_key_exists('size', $fallbacks)) ? $fallbacks['size'] : "undefined";
		$line_height    = (!empty($fallbacks) && array_key_exists('line_height', $fallbacks)) ? $fallbacks['line_height'] : "undefined";
		$letter_spacing = (!empty($fallbacks) && array_key_exists('letter_spacing', $fallbacks)) ? $fallbacks['letter_spacing'] : "undefined";
		$custom_letter_spacing = (!empty($fallbacks) && array_key_exists('custom_letter_spacing', $fallbacks)) ? $fallbacks['custom_letter_spacing'] : "undefined";
		$uppercase      = (!empty($fallbacks) && array_key_exists('uppercase', $fallbacks)) ? $fallbacks['uppercase'] : "undefined";
		$italic         = (!empty($fallbacks) && array_key_exists('italic', $fallbacks)) ? $fallbacks['italic'] : "undefined";
		$underline      = (!empty($fallbacks) && array_key_exists('underline', $fallbacks)) ? $fallbacks['underline'] : "undefined";
		$weight         = (!empty($fallbacks) && array_key_exists('weight', $fallbacks)) ? $fallbacks['weight'] : "undefined";

		$css[] = '<# if (!_.isEmpty(' . $data . ') && _.isObject(' . $data . ') ) { #>';

		$css[] = $this->loadGoogleFonts($data);

		$css[] = $selector . ' {';

		$css[] = '<# if ( typeof ' . $data . '.font !== "undefined" &&  ' . $data . '.font ) { #>';
		$css[] = 'font-family: "' . $this->inlineBlock($data . '.font') . '";';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $data . '.underline !== "undefined" &&  ' . $data . '.underline ) { #>';
		$css[] = 'text-decoration: underline;';
		$css[] = '<# } else {#>';
		$css[] = 'text-decoration: none;';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $data . '.italic !== "undefined" &&  ' . $data . '.italic ) { #>';
		$css[] = 'font-style: italic;';
		$css[] = '<# } else {#>';
		$css[] = 'font-style: normal;';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $data . '.uppercase !== "undefined" && ' . $data . '.uppercase) { #>';
		$css[] = 'text-transform: uppercase;';
		$css[] = '<# } #>';

		$css[] = '<# if ( ' . $data . '.weight) { #>';
		$css[] = 'font-weight: ' . $this->inlineBlock($data . '.weight') . ';';
		$css[] = '<# } #>';

		$css[] = '<# if ( _.isObject(' . $data . '.size) && ' . $data . '.size[window.builderDefaultDevice] && ' . $data . '.size[window.builderDefaultDevice].value && ' . $data . '.size[window.builderDefaultDevice].unit) { #>';
		$css[] = 'font-size: ' . $this->inlineBlock($data . '.size[window.builderDefaultDevice].value') . $this->inlineBlock($data . '.size[window.builderDefaultDevice].unit') . ';';
		$css[] = '<# } #>';

		$css[] = '<# if ( _.isObject(' . $data . '.letter_spacing) && ' . $data . '.letter_spacing[window.builderDefaultDevice].value && ' . $data . '.letter_spacing[window.builderDefaultDevice].unit) { #>';
		$css[] = 'letter-spacing: ' . $this->inlineBlock($data . '.letter_spacing[window.builderDefaultDevice].value') . $this->inlineBlock($data . '.letter_spacing[window.builderDefaultDevice].unit') . ';';
		$css[] = '<# } #>';

		$css[] = '<# if ( _.isObject(' . $data . '.line_height) && ' . $data . '.line_height[window.builderDefaultDevice] && ' . $data . '.line_height[window.builderDefaultDevice].value && ' . $data . '.line_height[window.builderDefaultDevice].unit) { #>';
		$css[] = 'line-height: ' . $this->inlineBlock($data . '.line_height[window.builderDefaultDevice].value') . $this->inlineBlock($data . '.line_height[window.builderDefaultDevice].unit') . ';';
		$css[] = '<# } #>';

		$css[] = '}';


		$css[] = $this->mediaQuery('font-size', $selector, $data . '.size', '', true);
		$css[] = $this->mediaQuery('letter-spacing', $selector, $data . '.letter_spacing', '', true);
		$css[] = $this->mediaQuery('line-height', $selector, $data . '.line_height', '', true);


		$css[] = '<# } else { #>';

		$css[] = $this->loadGoogleFonts($font, true);

		// Fallback CSS
		$css[] = $selector . ' {';

		$css[] = '<# if ( typeof ' . $font . ' !== "undefined" &&  ' . $font . ' ) { #>';
		$css[] = 'font-family:"' . $this->inlineBlock($font) . '";';
		$css[] = '<# } #>';

		$css[] = '<# if(typeof ' . $size . ' !== "undefined" && _.isObject(' . $size . ') && !_.isString(' . $size . ') && !_.isEmpty(' . $size . ')) { #>';
		$css[] = 'font-size: ' . $this->inlineBlock($size . '[window.builderDefaultDevice]') . 'px;';
		$css[] = '<# } else if(typeof ' . $size . ' !== "undefined" && !_.isEmpty(' . $size . ')){ #>';
		$css[] = 'font-size: ' . $this->inlineBlock($size) . 'px;';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $line_height . ' !== "undefined" &&  ' . $line_height . ' ) { #>';
		$css[] = 'line-height: ' . $this->inlineBlock($line_height . '[window.builderDefaultDevice]') . 'px;';
		$css[] = '<# } else if(typeof ' . $size . ' !== "undefined" && !_.isEmpty(' . $size . ')){ #>';
		$css[] = 'line-height: ' . $this->inlineBlock($line_height) . 'px;';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $letter_spacing . ' !== "undefined" &&  ' . $letter_spacing . ' ) { #>';
		$css[] = '<# if (' . $letter_spacing . ' == "custom") {#>';
		$css[] = 'letter-spacing: ' . $this->inlineBlock($custom_letter_spacing) . ';';
		$css[] = '<# } else {#>';
		$css[] = 'letter-spacing: ' . $this->inlineBlock($letter_spacing) . ';';
		$css[] = '<# } #>';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $uppercase  . ' !== "undefined" &&  ' . $uppercase . ' ) { #>';
		$css[] = 'text-transform: uppercase;';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $italic  . ' !== "undefined" &&  ' . $italic  . ' ) { #>';
		$css[] = 'font-style: italic;';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $underline   . ' !== "undefined" &&  ' . $underline  . ' ) { #>';
		$css[] = 'text-decoration: "' . $this->inlineBlock($underline) . '";';
		$css[] = '<# } #>';

		$css[] = '<# if ( typeof ' . $weight . ' !== "undefined" &&  ' . $weight . ' ) { #>';
		$css[] = 'font-weight: ' . $this->inlineBlock($weight) . ';';
		$css[] = '<# } #>';

		$css[] = '}';

		$css[] = '<# } #>';


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

	/**
	 * transform
	 *
	 * @param 	string 	$property
	 * @param 	string 	$selector
	 * @param 	string 	$data
	 * @return 	string
	 * @since 	4.0.0
	 */
	public function transform(string $property, string $selector, string $data, string $unit = ''): string
	{
		$selector = $this->generateSelector($selector);

		$css = [];
		$css[]    = '<# if(!_.isEmpty(' . $data . ')) { #>';
		$css[]    = $selector . ' {';
		$css[]    = '-webkit-transform: ' . $property . '(' . $this->inlineBlock($data) . $unit . ');';
		$css[]    = 'transform: ' . $property . '(' . $this->inlineBlock($data) . $unit . ');';
		$css[]    = '}';
		$css[]    = '<# } #>';

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

	/**
	 * adjustment function
	 *
	 * @param 	string 	$property
	 * @param 	string 	$selector
	 * @param 	string 	$prefix
	 * @param 	string 	$data
	 * @param 	string 	$unit
	 * 
	 * @return 	void
	 * @since 	4.0.0
	 */
	public function adjustment(string $property, string $selector, string $prefix, string $data, string $unit)
	{
		$selector = $this->generateSelector($selector);

		$css = [];
		$css[]    = '<# if(!_.isEmpty(' . $data . ')) { #>';
		$css[]    = $selector . ' {';
		$css[]    = '<# if(_.isObject(data.' . $data . ') ) { #>';
		$css[]    = $property . ':' . $prefix . '(' . $this->inlineBlock($data) . $unit . ');';
		$css[]    = '}';
		$css[]    = '<# } #>';

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


	/**
	 * box shadow
	 *
	 * @param 	string 	$selector
	 * @param 	string 	$data
	 * 
	 * @return 	string
	 * @since 	4.0.0
	 */
	public function boxShadow(string $selector, string $data): string
	{
		$selector = $this->generateSelector($selector);

		$css = [];
		$css[]    = '<# if(!_.isEmpty(' . $data . ')) { #>';
		$css[]    = $selector . ' {';
		$css[]    = '<# if(_.isObject(' . $data . ')) { #>';
		$css[] 	  = 'box-shadow: ' . $this->inlineBlock($data . '.ho || 0') . 'px ' .  $this->inlineBlock($data . '.vo || 0') . 'px ' . $this->inlineBlock($data . '.blur || 0') . 'px ' . $this->inlineBlock($data . '.spread || 0') . 'px ' . $this->inlineBlock($data . '.color || "transparent"') . ';';
		$css[]    = '<# } else { #>';
		$css[]    = 'box-shadow: ' . $this->inlineBlock($data) . ';';
		$css[]    = '<# } #>';
		$css[]    = '}';
		$css[]    = '<# } #>';

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

	/**
	 * text shadow
	 *
	 * @param	string 	$selector
	 * @param	string 	$data
	 * 
	 * @return	string
	 * @since 	4.0.0
	 */
	public function textShadow(string $selector, string $data): string
	{
		$selector = $this->generateSelector($selector);

		$css = [];
		$css[]    = '<# if(!_.isEmpty(' . $data . ')) { #>';
		$css[]    = $selector . ' {';
		$css[]    = '<# if(_.isObject(' . $data . ') && ' . $data . '.enabled) { #>';
		$css[] 	  = 'text-shadow: ' . $this->inlineBlock($data . '.ho || 0') . 'px ' .  $this->inlineBlock($data . '.vo || 0') . 'px ' . $this->inlineBlock($data . '.blur || 0') . 'px ' . $this->inlineBlock($data . '.color || "transparent"') . ';';
		$css[]    = '<# } else { #>';
		$css[]    = 'text-shadow: ' . $this->inlineBlock($data) . ';';
		$css[]    = '<# } #>';
		$css[]    = '}';
		$css[]    = '<# } #>';

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


	/**
	 * Return all css with media query for given size.
	 *
	 * @param	string 		$property
	 * @param	string 		$selector
	 * @param	string 		$data
	 * @param	string 		$unit
	 * @param	boolean 	$isTypography
	 * @param	boolean 	$prefix 
	 * 
	 * @return	string
	 * @since	4.0.0
	 */
	private function mediaQuery(string $property, string $selector, string $data,  string $unit = '', bool $isTypography = false, string $prefix  = ''): string
	{
		$css = [];

		foreach ($this->sizes as $size) {
			$css[] = $this->mediaQueryDevice($size);
			$css[] = $selector . ' {';
			$css[] = '<# if(_.isObject(' . $data . ') && !_.isEmpty(' . $data . ')) { #>';

			if ($isTypography) {
				$css[] = $property . ': ' . $prefix . $this->inlineBlock($data . '.' . $size . '.value') . $this->inlineBlock($data . '.' . $size . '.unit') . ';';
			} else {
				$css[] = $property . ': ' . $prefix . $this->inlineBlock($data . '.' . $size) . (!empty($unit) ? $unit : "") . ';';
			}

			$css[] = '<# } #>';
			$css[] = '}'; // closing bracket for selector.
			$css[] = '}'; // closing bracket for media query.
		}

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

	/**
	 * Return media query 
	 *
	 * @param	string 	$device device size name
	 * 
	 * @return	string
	 * @since 	4.0.0
	 */
	private function mediaQueryDevice(string $device)
	{
		switch ($device) {
			case 'xxl':
				return "@media ( min-width: 1400px ) { ";
			case 'lg':
				return "@media ( max-width: 1199.98px ) { ";
			case 'md':
				return "@media ( max-width: 991.98px ) { ";
			case 'sm':
				return "@media ( max-width: 767.98px ) { ";
			case 'xs':
				return "@media ( max-width: 575.98px ) { ";
			default:
				return "";
		}
	}

	/**
	 * Generate the block of codes.
	 *
	 * @return	string
	 * @since	4.0.0
	 */
	private function block(string $content): string
	{
		return '<# ' . $content . ' #>';
	}


	/**
	 * Generate the inline block of codes.
	 *
	 * @return	string
	 * @since	4.0.0
	 */
	private function inlineBlock(string $content): string
	{
		return '{{' . $content . '}}';
	}

	/**
	 * Backdrop Filter
	 *
	 * @param string $property   CSS Property Name
	 * @param string $selector	 CSS Selector
	 * @param string $data		 CSS Value
	 * @param string $unit		 Unit of CSS Value
	 * @return string
	 * since 4.0.8
	 */
	public function backdrop_filter(string $property, string $selector, string $data, string $unit)
	{		
		$selector = $this->generateSelector($selector);

		$css 	  = [];
		$css[]    = '<# if(!_.isEmpty(' . $data . ')) { #>';
		$css[]    = $selector . ' {';
		$css[]    = '-webkit-backdrop-filter: ' . $property . '(' . $this->inlineBlock($data) . $unit . ');';
		$css[]    = 'backdrop-filter: ' . $property . '(' . $this->inlineBlock($data) . $unit . ');';
		$css[]    = '}';
		$css[]    = '<# } #>';

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

	/**
	 * Generate missing break points of field width for old layouts. 
	 *
	 * @param string $fieldWidth  given breakpoints of field width
	 * @return string
	 * @since 4.0.9
	 */
	public function generateMissingBreakpoints( string $fieldWidth )
	{
		$generateBreakPoints   = [];
		$generateBreakPoints[] = "const expectedBreakpoints = ['xxl', 'xl', 'lg'];";
		$generateBreakPoints[] = "_.forEach(expectedBreakpoints, (key) => {";
		$generateBreakPoints[] = "if(!_.has($fieldWidth, key)) {";
		$generateBreakPoints[] = "_.set($fieldWidth, key, _.get($fieldWidth, 'md', ''));";
		$generateBreakPoints[] = "}";					
		$generateBreakPoints[] = "})";					

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