<?php


namespace Neve\Core\Styles;


use Neve\Core\Settings\Config;
use Neve\Core\Settings\Mods;
use Neve\Views\Font_Manager;

class Css_Prop {
	/**
	 * Helper method to build the value based on 100 diff.
	 */
	public static function minus_100( $css_prop, $value, $meta, $device ) {
		return sprintf( "%s: %s%s;",
			( $css_prop ),
			( 100 - $value ),
			isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : 'px'
		);
	}

	/**
	 * Get suffix from controls that store data in the following format:
	 * { desktop: value, tablet: value, mobile: value, deskotp-unit: px, tablet-unit: px, mobile-unit: px }
	 *
	 * @param array $meta Subscribers meta data.
	 */
	public static function get_unit_responsive( $meta, $device ) {
		$all_value = Mods::get( $meta['key'], isset( $meta[ Dynamic_Selector::META_DEFAULT ] ) ? $meta[ Dynamic_Selector::META_DEFAULT ] : null );
		$suffix    = 'px';
		if ( isset( $all_value[ $device . '-unit' ] ) ) {
			$suffix = $all_value[ $device . '-unit' ];
		} elseif ( isset( $all_value['unit'] ) ) {
			$suffix = $all_value['unit'];
		}

		return $suffix;
	}

	/**
	 * Get suffix from controls that store data in the following format:
	 * { desktop: value, tablet: value, mobile: value, suffix : { deskop: px, tablet: px, mobile: px} }
	 *
	 * @param array $meta Subscribers meta data.
	 */
	public static function get_suffix_responsive( $meta, $device ) {
		$default_value = isset( $meta[ Dynamic_Selector::META_DEFAULT ] ) ? $meta[ Dynamic_Selector::META_DEFAULT ] : null;
		$all_value     = isset( $meta[ Dynamic_Selector::META_AS_JSON ] ) ? Mods::to_json( $meta['key'], $default_value ) : Mods::get( $meta['key'], $default_value );

		// The neve_responsive_range_control component double JSON stringified, therefore try to parse it again.
		if( ! is_array( $all_value ) ) {
			$maybe_parse_json = json_decode( $all_value , true);

			if( is_array( $maybe_parse_json ) ) {
				$all_value = $maybe_parse_json;
			}
		}

		return isset( $all_value['suffix'][ $device ] ) ? $all_value['suffix'][ $device ] : ( isset( $all_value['suffix'] ) && is_string( $all_value['suffix'] ) ? $all_value['suffix'] : 'px' );;
	}

	/**
	 * Transform rule meta into CSS rule string.
	 *
	 * @param string $css_prop CSS Prop.
	 * @param string|array $value Dynamic value.
	 * @param array $meta Rule meta.
	 * @param string $device Current device.
	 *
	 * @return string
	 */
	public static function transform( $css_prop, $value, $meta, $device ) {
		//If we have a custom filter, let's call it.
		if ( isset( $meta['filter'] ) ) {
			if ( is_callable( $meta['filter'] ) ) {
				return call_user_func_array( $meta['filter'], [ $css_prop, $value, $meta, $device ] );
			}
			if ( method_exists( __CLASS__, $meta['filter'] ) ) {
				return call_user_func_array( [ __CLASS__, $meta['filter'] ], [ $css_prop, $value, $meta, $device ] );
			}

			return '';
		}

		if ( isset( $meta['override'] ) ) {
			return sprintf( '%s:%s;', $css_prop, $meta['override'] );
		}

		switch ( $css_prop ) {
			case Config::CSS_PROP_BACKGROUND_COLOR:
			case Config::CSS_PROP_COLOR:
			case Config::CSS_PROP_FILL_COLOR:
			case Config::CSS_PROP_BORDER_COLOR:
				$mode   = ( false === strpos( $value, 'rgba' ) ) ? 'hex' : 'rgba';
				$is_var = ( strpos( $value, 'var' ) !== false );

				if ( $mode === 'hex' && ! $is_var ) {
					$value = strpos( $value, "#" ) === 0 ? $value : '#' . $value;
				}

				return sprintf( "%s: %s%s;", ( $css_prop ), neve_sanitize_colors( $value ), isset( $meta['important'] ) && $meta['important'] ? '!important' : '' );
			case Config::CSS_PROP_MAX_WIDTH:
			case Config::CSS_PROP_WIDTH:
			case Config::CSS_PROP_FLEX_BASIS:
			case Config::CSS_PROP_MARGIN_LEFT:
			case Config::CSS_PROP_MARGIN_RIGHT:
			case Config::CSS_PROP_MARGIN_TOP:
			case Config::CSS_PROP_MARGIN_BOTTOM:
			case Config::CSS_PROP_PADDING_LEFT:
			case Config::CSS_PROP_PADDING_RIGHT:
			case Config::CSS_PROP_HEIGHT:
			case Config::CSS_PROP_MIN_HEIGHT:
			case Config::CSS_PROP_LEFT:
			case Config::CSS_PROP_RIGHT:
				$suffix = isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : 'px';

				if ( $suffix === 'responsive_suffix' ) {
					$suffix = self::get_suffix_responsive( $meta, $device );
				}

				return sprintf( "%s: %s%s;",
					( $css_prop ),
					( $value ),
					$suffix
				);
			case Config::CSS_PROP_BORDER_RADIUS:
			case Config::CSS_PROP_BORDER_WIDTH:
			case Config::CSS_PROP_PADDING:
			case Config::CSS_PROP_MARGIN:
				$suffix = isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : 'px';
				if ( ! is_array( $value ) ) {
					return sprintf( "%s:%s%s;",
						$css_prop,
						absint( $value ), $suffix );
				}

				if ( ! isset( $meta['is_responsive'] ) || $meta['is_responsive'] === false ) {
					$suffix = isset( $value['unit'] ) ? $value['unit'] : 'px';
				}

				if ( $suffix === 'responsive_unit' ) {
					$suffix = self::get_unit_responsive( $meta, $device );
				}

				$non_empty_values = array_filter( $value, 'strlen' ); // @phpstan-ignore-line
				if ( count( $non_empty_values ) === 4 ) {
					return sprintf( "%s:%s%s %s%s %s%s %s%s;",
						$css_prop,
						(int) $value['top'],
						$suffix,
						(int) $value['right'],
						$suffix,
						(int) $value['bottom'],
						$suffix,
						(int) $value['left'],
						$suffix
					);
				}
				$rule     = '';
				$patterns = [
					Config::CSS_PROP_MARGIN        => 'margin-%s',
					Config::CSS_PROP_PADDING       => 'padding-%s',
					Config::CSS_PROP_BORDER_WIDTH  => 'border-%s-width',
					Config::CSS_PROP_BORDER_RADIUS => [
						'top'    => 'border-top-left-radius',
						'right'  => 'border-top-right-radius',
						'bottom' => 'border-bottom-right-radius',
						'left'   => 'border-bottom-left-radius',
					],
				];

				if ( isset( $non_empty_values['unit'] ) ) {
					unset ( $non_empty_values['unit'] );
				}

				foreach ( $non_empty_values as $position => $position_value ) {
					$rule .= sprintf( "%s:%s%s;",
						sprintf( ( is_array( $patterns[ $css_prop ] ) ? $patterns[ $css_prop ][ $position ] : $patterns[ $css_prop ] ), $position ),
						(int) $position_value,
						$suffix
					);
				}

				return $rule;
			//Line height uses an awkward format saved, and we can't define it as responsive because we would need to use the suffix part.
			case Config::CSS_PROP_LINE_HEIGHT:
			case Config::CSS_PROP_FONT_SIZE:

				$suffix = isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : 'em';
				// We consider the provided suffix as default, in case that we have a responsive setting with responsive suffix.
				if ( isset( $meta[ Dynamic_Selector::META_IS_RESPONSIVE ] ) && $meta[ Dynamic_Selector::META_IS_RESPONSIVE ] ) {
					$all_value = Mods::get( $meta['key'] );
					$suffix    = isset( $all_value['suffix'][ $device ] ) ? $all_value['suffix'][ $device ] : ( isset( $all_value['suffix'] ) ? $all_value['suffix'] : $suffix );
				}

				return sprintf( ' %s: %s%s;', $css_prop, $value, $suffix );
			//Letter spacing has a legacy value of non-responsive which we need to take into consideration.
			case Config::CSS_PROP_LETTER_SPACING:
				return sprintf( ' %s: %spx;', $css_prop, $value );
			case Config::CSS_PROP_CUSTOM_BTN_TYPE:
				if ( $value !== 'outline' ) {
					return 'border:none;';
				}

				return "border:1px solid;";
			case Config::CSS_PROP_FONT_WEIGHT:
				if ( isset( $meta['font'] ) ) {
					$font = strpos( $meta['font'], 'mods_' ) === 0 ? Mods::get( str_replace( 'mods_', '', $meta['font'] ) ) : $meta['font'];
					Font_Manager::add_google_font( $font, strval( $value ) );
				}

				return sprintf( ' %s: %s;', $css_prop, intval( $value ) );
			case Config::CSS_PROP_FONT_FAMILY:
				if ( $value === 'default' ) {
					return '';
				}
				Font_Manager::add_google_font( $value );

				return sprintf( ' %s: %s, var(--nv-fallback-ff);', $css_prop, $value );
			case Config::CSS_PROP_TEXT_TRANSFORM:
			case Config::CSS_PROP_BOX_SHADOW:
			case Config::CSS_PROP_MIX_BLEND_MODE:
			case Config::CSS_PROP_OPACITY:
			case Config::CSS_PROP_GRID_TEMPLATE_COLS:
				return sprintf( ' %s: %s;', $css_prop, $value );
			default:
				$is_font_family_var = strpos( strtolower( $css_prop ), 'fontfamily' ) > - 1;

				if ( $is_font_family_var ) {
					Font_Manager::add_google_font( $value );

					$value = self::format_font_family_value( $value );
				}

				if ( isset( $meta['directional-prop'] ) ) {
					return self::transform_directional_prop( $meta, $device, $value, $css_prop, $meta['directional-prop'] );
				}

				$suffix = self::get_suffix( $meta, $device, $value, $css_prop );

				return sprintf( ' %s: %s%s;', $css_prop, $value, $suffix );
		}
	}

	/**
	 * Get suffix for generic settings.
	 *
	 * @param array $meta Meta array.
	 * @param string $device Current device.
	 * @param mixed $value Value.
	 *
	 * @return string
	 *
	 * @since 3.0.0
	 */
	public static function get_suffix( $meta, $device, $value, $css_prop ) {
		$suffix = isset( $meta[ Dynamic_Selector::META_SUFFIX ] ) ? $meta[ Dynamic_Selector::META_SUFFIX ] : '';

		// If not responsive, most controls use 'unit' key inside value.
		if ( ! isset( $meta['is_responsive'] ) || $meta['is_responsive'] === false ) {
			$suffix = isset( $value['unit'] ) ? $value['unit'] : $suffix;
		}

		// If responsive, try to find the suffix.
		if ( isset( $meta[ Dynamic_Selector::META_IS_RESPONSIVE ] ) && $meta[ Dynamic_Selector::META_IS_RESPONSIVE ] ) {
			$all_value = Mods::get( $meta['key'] );
			$suffix    = isset( $all_value['suffix'][ $device ] ) ? $all_value['suffix'][ $device ] : ( isset( $all_value['suffix'] ) ? $all_value['suffix'] : $suffix );
		}

		if ( $suffix === 'responsive_unit' ) {
			$suffix = self::get_unit_responsive( $meta, $device );
		}

		if ( $suffix === 'responsive_suffix' ) {
			$suffix = self::get_suffix_responsive( $meta, $device );
		}

		// Enqueue any google fonts we might be missing.
		if ( isset ( $meta['font'] ) ) {
			$font = strpos( $meta['font'], 'mods_' ) === 0 ? Mods::get( str_replace( 'mods_', '', $meta['font'] ) ) : $meta['font'];
			Font_Manager::add_google_font( $font, strval( $value ) );
		}

		return $suffix === '—' ? '' : $suffix;
	}

	/**
	 * Transforms the directional properties.
	 *
	 * @param array $meta Meta array.
	 * @param string $device Current device.
	 * @param array|int $value Value.
	 * @param string $css_prop Css Property.
	 * @param string $type Type of directional property.
	 *
	 * @return string
	 */
	public static function transform_directional_prop( $meta, $device, $value, $css_prop, $type ) {
		$suffix   = self::get_suffix( $meta, $device, $value, $css_prop );
		$suffix   = $suffix ? $suffix : 'px';
		$template = '';

		// Make sure that this is directional, even if an int value is provided.
		if ( is_int( $value ) ) {
			$directions = Config::$directional_keys;
			$value      = array_fill_keys( $directions, $value );
		}

		// If we still don't have an array. Make sure to drop this setting.
		if ( ! is_array( $value ) ) {
			return '';
		}

		// Directional array without any other keys than the actual directions.
		$filtered = array_filter( $value, function ( $key ) {
			return in_array( $key, Config::$directional_keys, true );
		}, ARRAY_FILTER_USE_KEY );

		$number_of_directions = $type === Config::CSS_PROP_DIRECTIONAL_ONE_AXIS ? 4 : count( array_unique( $filtered ) );

		if ( $number_of_directions === 1 ) {

			if ( neve_value_is_zero( $value['top'] ) ) {
				$suffix = '';
			}

			if ( empty( $value['top'] ) && ! neve_value_is_zero( $value['top'] ) ) {
				return '';
			}

			$template .= $value['top'] . $suffix;

			return $css_prop . ':' . $template . ';';
		}

		if ( $number_of_directions === 2 && $value['top'] === $value['bottom'] && $value['right'] === $value['left'] ) {

			if ( isset( $value['is_outline_button_padding'] ) ) {
				return $css_prop . ':' . $value['top'] . ' ' . $value['right'] . ';';
			}

			if ( neve_value_is_zero( $value['top'] ) && neve_value_is_zero( $value['right'] ) ) {
				return '';
			}

			$top_suffix   = neve_value_is_zero( $value['top'] ) ? '' : $suffix;
			$right_suffix = neve_value_is_zero( $value['right'] ) ? '' : $suffix;

			$template .= $value['top'] . $top_suffix . ' ' . $value['right'] . $right_suffix;

			return $css_prop . ':' . $template . ';';
		}

		foreach ( Config::$directional_keys as $direction ) {
			if ( isset( $value['is_outline_button_padding'] ) ) {
				$template .= $value[$direction] . ' ';
				continue;
			}

			if ( ! isset( $value[ $direction ] ) || neve_value_is_zero( $value[ $direction ] ) ) {
				$template .= '0 ';
				continue;
			}

			$template .= $value[ $direction ] . $suffix . ' ';
		}

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

		$template = trim( $template ) . ';';

		return $css_prop . ':' . $template . ';';
	}

	/**
	 * Format the font family value.
	 *
	 * @param string $value the font family value.
	 */
	public static function format_font_family_value( $value ) {
		// At some point we were setting the DB values with quotes and removed that.
		// Make sure we drop the slashes and quotes.
		$value = str_replace( [ '"', '\\' ], '', $value );

		if ( strpos( $value, ',' ) !== false ) {
			$value = explode( ',', $value );

			$value = array_map( 'Neve\Core\Styles\CSS_Prop::quote_font_family', $value );

			return join( ',', $value );
		}

		return self::quote_font_family( $value );
	}

	/**
	 * Strip side spaces wrap font family in quotes.
	 *
	 * @param string $family the font family.
	 *
	 * @return string
	 */
	private static function quote_font_family( $family ) {
		// Make sure we don't have whitespace.
		$family = trim( $family );
		// Remove quotes. Because of previously faulty fix.
		$family = trim( $family, '"' );
		if ( strpos( $family, ' ' ) === false ) {
			return $family;
		}

		return '"' . $family . '"';
	}
}
