<?php
/**
 * CSS generator.
 *
 * $default_queries = array(
 *     'desktop' => '@media ( min-width: 960px )',
 *     'mobile'  => '@media ( max-width: 960px )'
 * );
 *
 * $css = new CSS_Utility( $block, $default_queries );
 *
 * $css->add_item( array(
 *  'global'     => 'global', // Put your media query selector here. It's global by default.
 *  'selector'   => ' .wp-block', // ID of the Block will be prefixed to your selector. If you want to target the root, drop this property. You can also use the keyword [id] if you want to add some prefix to your ID, see AMP CSS in Circular Counter Block.
 *  'properties' => array(
 *      array(
 *          'property'    => 'margin',
 *          'value'       => 'spacing',
 *          'unit'        => 'px',
 *          'default'     => 20,
 *          'format'      => function( $value, $attrs ) {
 *              return $value / 2;
 *          },
 *          'condition'   => function( $attrs ) {
 *              return true;
 *          },
 *          'hasSync'     => 'spacing', // If global sync is available, add the used CSS variable.
 *      ),
 *      array(
 *          'property'       => 'margin',
 *          'pattern'        => '20px marginLeftRight',
 *          'pattern_values' => array(
 *              'marginLeftRight' => array(
 *                  'value'   => 'marginLeftRight',
 *                  'unit'    => 'px',
 *                  'default' => 20,
 *                  'format'  => function( $value, $attrs ) {
 *                      return $value / 2;
 *                  },
 *              ),
 *          ),
 *          'condition'      => function( $attrs ) {
 *              return true;
 *          },
 *      ),
 *  ),
 *  'media'      => 'desktop', // Media query selector.
 * ) );
 *
 * $style = $css->generate();
 *
 * Inspired by https://github.com/kirki-framework/wp-css-generator
 *
 * @package ThemeIsle\GutenbergBlocks\CSS
 */

namespace ThemeIsle\GutenbergBlocks\CSS;

/**
 * Class Block_Frontend
 */
class CSS_Utility {

	/**
	 * Variable to hold block array.
	 *
	 * @var array
	 */
	public $block = array();

	/**
	 * Variable to hold media queries.
	 *
	 * @var array
	 */
	public $media_queries = array();

	/**
	 * Variable to hold media items.
	 *
	 * @var array
	 */
	public $media_items = array();

	/**
	 * Variable to hold CSS array.
	 *
	 * @var array
	 */
	public $css_array = array();

	/**
	 * Variable to hold custom block ID.
	 *
	 * @var string
	 */
	public $block_id;

	/**
	 * Constructor
	 *
	 * @access public
	 * @param array $block Block object.
	 * @param array $media_queries Media queries array.
	 */
	public function __construct( $block, $media_queries = array() ) {
		$this->block         = $block;
		$this->media_queries = $media_queries;
	}

	/**
	 * Define custom Block ID.
	 *
	 * @access public
	 * @since 1.7.0
	 * @param string $id Block ID.
	 */
	public function set_id( $id ) {
		$this->block_id = $id;
	}

	/**
	 * Filter media queries.
	 *
	 * @access public
	 * @since 2.1.0
	 * @param string $query Media Query.
	 */
	public function get_media_query( $query ) {
		if ( isset( $this->media_queries[ $query ] ) && 'global' !== $this->media_queries[ $query ] ) {
			return $this->media_queries[ $query ];
		}

		return $query;
	}

	/**
	 * Add a style to CSS array.
	 *
	 * @access public
	 * @since 1.6.0
	 * @param array $params CSS object parameters.
	 */
	public function add_item( $params ) {
		$params = wp_parse_args(
			$params,
			array(
				'query'      => 'global',
				'selector'   => '',
				'properties' => '',
			)
		);

		$params['query'] = $this->get_media_query( $params['query'] );

		if ( ! isset( $this->css_array[ $params['query'] ] ) ) {
			$this->css_array[ $params['query'] ] = array();
		}

		$params['properties'] = $this->filter_out_media_queries( $params['selector'], $params['properties'] );

		if ( ! isset( $this->css_array[ $params['query'] ][ $params['selector'] ] ) ) {
			$this->css_array[ $params['query'] ][ $params['selector'] ] = $params['properties'];
		} else {
			$this->css_array[ $params['query'] ][ $params['selector'] ] = array_merge(
				$this->css_array[ $params['query'] ][ $params['selector'] ],
				$params['properties']
			);
		}
	}

	/**
	 * Filter out media query items.
	 *
	 * @access public
	 * @since 2.1.0
	 * @param string $selector CSS selector.
	 * @param array  $properties CSS properties.
	 */
	public function filter_out_media_queries( $selector, $properties ) {
		$query_items = array_filter(
			$properties,
			function ( $ar ) {
				return isset( $ar['media'] );
			}
		);

		if ( 0 !== count( $query_items ) ) {
			if ( ! isset( $this->media_items[ $selector ] ) ) {
				$this->media_items[ $selector ] = $query_items;
			} else {
				$this->media_items[ $selector ] = array_merge( $this->media_items, $query_items );
			}
		}

		$properties = array_diff_key( $properties, array_flip( array_keys( $query_items ) ) );

		return $properties;
	}

	/**
	 * Process media items pre-generation.
	 *
	 * @access public
	 * @since 2.1.0
	 */
	public function process_media_items() {
		foreach ( $this->media_items as $selector => $items ) {
			foreach ( $items as $item ) {
				$media_query = $this->get_media_query( $item['media'] );

				if ( ! isset( $this->css_array[ $media_query ] ) ) {
					$this->css_array[ $media_query ] = array();
				}

				if ( ! isset( $this->css_array[ $media_query ][ $selector ] ) ) {
					$this->css_array[ $media_query ][ $selector ] = array();
				}

				array_push( $this->css_array[ $media_query ][ $selector ], $item );
			}
		}
	}

	/**
	 * Generate CSS from provided values.
	 *
	 * @access public
	 * @since 1.6.0
	 */
	public function generate() {
		$this->process_media_items();

		$style = '';

		$attrs = $this->block['attrs'];

		if ( empty( $this->block_id ) ) {
			if ( isset( $attrs['id'] ) ) {
				$this->block_id = $attrs['id'];
			}
		}

		foreach ( $this->css_array as $media_query => $css_items ) {
			$style .= ( 'global' !== $media_query ) ? $media_query . '{' : '';

			foreach ( $css_items as $selector => $properties ) {
				$item_style = '';

				foreach ( $properties as $property ) {
					$property = wp_parse_args(
						$property,
						array(
							'unit' => '',
						)
					);

					// If the item supports global default, check if the global default is active.
					if ( isset( $property['property'] ) && isset( $property['value'] ) && isset( $property['hasSync'] ) && ! empty( $property['hasSync'] ) && ( isset( $attrs['isSynced'] ) && in_array( $property['value'], $attrs['isSynced'] ) ) ) {
						$item_style .= $property['property'] . ': var( --' . $property['hasSync'] . ( isset( $property['default'] ) ? ', ' . $property['default'] : '' ) . ' );';
						continue;
					}

					// If the item contains a condition, check if it is true or bail out.
					if ( isset( $property['condition'] ) && is_callable( $property['condition'] ) && ! $property['condition']( $attrs ) ) {
						continue;
					}

					if ( isset( $property['property'] ) && ( ( isset( $property['value'] ) && isset( $attrs[ $property['value'] ] ) ) || isset( $property['default'] ) ) ) {
						$value = ( ( isset( $property['value'] ) && isset( $attrs[ $property['value'] ] ) ) ? $attrs[ $property['value'] ] : $property['default'] );

						if ( isset( $property['format'] ) && is_callable( $property['format'] ) ) {
							$value = $property['format']( $value, $attrs );
						}

						$value       = $value . $property['unit'];
						$item_style .= $property['property'] . ': ' . $value . ';';
					}

					if ( isset( $property['property'] ) && ( isset( $property['pattern'] ) && isset( $property['pattern_values'] ) ) ) {
						$pattern = $property['pattern'];

						foreach ( $property['pattern_values'] as $value_key => $pattern_value ) {
							$pattern_value = wp_parse_args(
								$pattern_value,
								array(
									'unit' => '',
								)
							);

							if ( ( ( isset( $pattern_value['value'] ) && isset( $attrs[ $pattern_value['value'] ] ) ) || isset( $pattern_value['default'] ) ) ) {
								$value = ( ( isset( $pattern_value['value'] ) && isset( $attrs[ $pattern_value['value'] ] ) ) ? $attrs[ $pattern_value['value'] ] : $pattern_value['default'] );

								if ( isset( $pattern_value['format'] ) && is_callable( $pattern_value['format'] ) ) {
									$value = $pattern_value['format']( $value, $attrs );
								}

								$value   = $value . $pattern_value['unit'];
								$pattern = preg_replace( '/\b' . $value_key . '\b/', $value, $pattern );
							}
						}

						$item_style .= $property['property'] . ': ' . $pattern . ';';
					}
				}

				if ( '' !== $item_style ) {
					if ( ! ( ! isset( $attrs['id'] ) && ! empty( $selector ) ) ) {
						$selector = strpos( $selector, '[id]' ) !== false ? str_replace( '[id]', '#' . $this->block_id, $selector ) : '#' . $this->block_id . $selector;
					}

					$style .= $selector . ' {' . $item_style . '}';
				}
			}

			$style .= ( 'global' !== $media_query ) ? '}' : '';
		}

		return $style;
	}

	/**
	 * Get the CSS string for padding, margin that comes from BoxControl.
	 *
	 * @param array $box The box.
	 * @param array $box_default The default box.
	 * @return string
	 */
	public static function box_values( $box, $box_default = array() ) {
		$box = array_map(
			function ( $value ) {
				return is_numeric( $value ) ? $value . 'px' : $value;
			},
			$box
		);

		return self::render_box(
			array_merge(
				array(
					'left'   => '0px',
					'right'  => '0px',
					'top'    => '0px',
					'bottom' => '0px',
				),
				$box_default,
				$box
			)
		);
	}

	/**
	 * Make a string into a box type.
	 *
	 * @param string $value The value.
	 *
	 * @return array|string[]
	 */
	public static function make_box( $value = '0px' ) {
		return array(
			'left'   => $value,
			'right'  => $value,
			'top'    => $value,
			'bottom' => $value,
		);
	}

	/**
	 * Merge tge gives views to a single box value.
	 *
	 * @param mixed ...$views The values from other viewports.
	 *
	 * @return array|string[]
	 */
	public static function merge_views( ...$views ) {
		$result = self::make_box();

		$valid = array_filter(
			$views,
			function ( $view ) {
				return isset( $view ) && is_array( $view );
			}
		);

		foreach ( $valid as $arr ) {
			if ( isset( $arr['top'] ) ) {
				$result['top'] = $arr['top'];
			}
			if ( isset( $arr['bottom'] ) ) {
				$result['bottom'] = $arr['bottom'];
			}
			if ( isset( $arr['right'] ) ) {
				$result['right'] = $arr['right'];
			}
			if ( isset( $arr['left'] ) ) {
				$result['left'] = $arr['left'];
			}
		}

		return $result;
	}

	/**
	 * Convert a defined box to a CSS string value.
	 *
	 * @param array $box The box value.
	 *
	 * @return string
	 */
	public static function render_box( $box ) {
		if ( ! is_array( $box ) || count( $box ) === 0 ) {
			return '';
		}

		return $box['top'] . ' ' . $box['right'] . ' ' . $box['bottom'] . ' ' . $box['left'];
	}
}
