<?php
/**
 * Init the Kirki PRO's responsive package.
 *
 * @package kirki-pro-responsive
 * @since 1.0.0
 */

namespace Kirki\Pro\Responsive;

use Kirki\Pro\Field\Responsive;

/**
 * Manage the responsive package.
 */
class Init {

	/**
	 * Control types with horizontal layout.
	 *
	 * @var array
	 */
	private $horizontal_types = [
		'kirki-checkbox',
		'kirki-toggle',
		'kirki-react-colorful',
	];

	/**
	 * The class constructor.
	 */
	public function __construct() {

		add_action( 'customize_register', [ $this, 'register_control_type' ] );
		add_filter( 'kirki_control_types', [ $this, 'control_type' ] );

		add_filter( 'kirki_field_exclude_init', array( $this, 'exclude_init' ), 99999, 4 );
		add_action( 'kirki_field_custom_init', [ $this, 'field_init' ], 99999, 3 );

		/**
		 * We use 8 as the priority because we want this to run before "kirki_get_value" method
		 * in wp-content/plugins/kirki-dev/packages/kirki-framework/data-option/src/Option.php file.
		 */
		add_filter( 'kirki_get_value', [ $this, 'kirki_get_value' ], 8, 4 );

	}

	/**
	 * Register control type.
	 *
	 * @param WP_Customize_Manager $wp_customize Instance of WP_Customize_Manager.
	 */
	public function register_control_type( $wp_customize ) {

		$wp_customize->register_control_type( '\Kirki\Pro\Control\Responsive' );

	}

	/**
	 * The control type.
	 *
	 * @param array $control_types The existing control types.
	 */
	public function control_type( $control_types ) {

		$control_types['kirki-responsive'] = 'Kirki\Pro\Control\Responsive';

		return $control_types;

	}

	/**
	 * Parse the "default" argument.
	 * This method will format the "default" argument to contains the devices that wraps the default value.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args The field arguments.
	 * @return array $args The modifiled field arguments.
	 */
	public function parse_default_arg( $args ) {

		$has_responsive_default = true;

		if ( isset( $args['default'] ) ) {
			if ( is_array( $args['default'] ) ) {
				if ( ! isset( $args['default']['desktop'] ) && ! isset( $args['default']['tablet'] ) && ! isset( $args['default']['mobile'] ) ) {
					$has_responsive_default = false;
				}
			} else {
				$has_responsive_default = false;
			}
		}

		if ( ! $has_responsive_default ) {
			if ( isset( $args['default'] ) ) {
				$args['default'] = array(
					'desktop' => $args['default'],
				);
			} else {
				$args['default'] = array(
					'desktop' => '',
				);
			}
		}

		return $args;

	}

	/**
	 * Parse the output argument.
	 * This method will format the "output" argument to modify the "media_query" based on the targetted device.
	 * This method will be called inside of "default" argument loop when the control is using responsive mode.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $args The field arguments.
	 * @param string $device The targetted device.
	 *
	 * @return array $args The modified field arguments.
	 */
	public function parse_output_arg( $args, $device ) {

		if ( isset( $args['output'] ) && ! empty( $args['output'] ) ) {
			foreach ( $args['output'] as $index => $output ) {
				if ( isset( $output['media_query'] ) ) {
					if ( isset( $output['media_query'][ $device ] ) ) {
						$args['output'][ $index ]['media_query'] = $output['media_query'][ $device ];
					} else {
						// If current device is not set in the "media_query", then the output won't work.
						unset( $args['output'][ $index ] );
					}
				} else {
					// If "media_query" is not provided in the "output" arg, then the output won't work.
					unset( $args['output'][ $index ] );
				}
			}
		}

		return $args;

	}

	/**
	 * Exclude responsive field (field here means group of controls) from default field's init call.
	 *
	 * @see wp-content/plugins/kirki-dev/packages/kirki-framework/field/src/Field.php
	 * @since 1.0.0
	 *
	 * @param bool   $condition The existing condition.
	 * @param Object $field The field object.
	 * @param array  $args The field args.
	 *
	 * @return bool
	 */
	public function exclude_init( $condition, $field, $args ) {

		if ( isset( $args['responsive'] ) && $args['responsive'] ) {
			return true;
		}

		return $condition;

	}

	/**
	 * Replace the default field init with custom init.
	 *
	 * @see wp-content/plugins/kirki-dev/packages/kirki-framework/field/src/Field.php
	 * @since 1.0.0
	 *
	 * @param Object $field The field object.
	 * @param array  $args The Kirki field args.
	 * @param string $control_class The control class name if it exists.
	 */
	public function field_init( $field, $args, $control_class ) {

		// Stop if this field doesn't have the "responsive" argument.
		if ( ! isset( $args['responsive'] ) || ! $args['responsive'] ) {
			return;
		}

		$this->register_real_controls( $field, $args, $control_class );

	}

	/**
	 * Register the real controls.
	 *
	 * @since 1.0.0
	 *
	 * @param Object $field The field object.
	 * @param array  $args The Kirki field args.
	 * @param string $control_class The control class name if it exists.
	 */
	public function register_real_controls( $field, $args, $control_class = '' ) {

		$defaults = $this->parse_default_arg( $args );
		$defaults = $defaults['default'];
		$defaults = array_reverse( $defaults );
		$devices  = [];

		$inside_horizontal_layout = property_exists( $field, 'type' ) && in_array( $field->type, $this->horizontal_types, true ) ? true : false;

		foreach ( $defaults as $device => $value ) {
			array_push( $devices, $device );
		}

		$devices = array_reverse( $devices );

		$devices_control_id = 'kirki_responsive__' . $args['settings'];

		$loop_count = 0;

		// by Duke: Fix media query issue (set mobile last to overwrite others)
		$defaults = array_reverse($defaults);

		foreach ( $defaults as $device => $value ) {
			$loop_count++;

			if ( ! $inside_horizontal_layout && 1 === $loop_count ) {
				$this->add_devices_control( $devices_control_id, $args, $devices, $inside_horizontal_layout );
			}

			$new_control_args = $args;

			$new_control_args['default']  = $value;
			$new_control_args['settings'] = $args['settings'] . '[' . $device . ']';
			$new_control_args['device']   = $device;

			if ( ! isset( $new_control_args['wrapper_attrs'] ) ) {
				$new_control_args['wrapper_attrs'] = [];
			}

			$new_control_args['wrapper_attrs']['data-kirki-parent-responsive-id'] = $devices_control_id;
			$new_control_args['wrapper_attrs']['data-kirki-device-preview']       = $device;

			if ( isset( $new_control_args['label'] ) ) {
				unset( $new_control_args['label'] );
			}

			if ( isset( $new_control_args['description'] ) ) {
				unset( $new_control_args['description'] );
			}

			if ( isset( $new_control_args['responsive'] ) ) {
				unset( $new_control_args['responsive'] );
			}

			$wrapper_class = '';

			/**
			 * If `$control_class` is empty, then we assume this is a "parent field" and not the "real controls".
			 * A "parent field" here means: a parent of group of controls such as field-dimensions, field-typography, etc.
			 */
			if ( ! $control_class ) {
				$wrapper_class .= ' customize-control-kirki-hidden-field';
			}

			if ( $inside_horizontal_layout ) {
				$wrapper_class .= ' customize-control-kirki-responsive-horizontal';
			}

			if ( ! empty( $wrapper_class ) ) {
				if ( isset( $new_control_args['wrapper_attrs']['class'] ) ) {
					$new_control_args['wrapper_attrs']['class'] .= $wrapper_class;
				} else {
					$new_control_args['wrapper_attrs']['class'] = '{default_class}' . $wrapper_class;
				}
			}

			$new_control_args = $this->parse_output_arg( $new_control_args, $device );
			$field_classname  = get_class( $field );

			new $field_classname( $new_control_args );

			if ( $inside_horizontal_layout && count( $devices ) === $loop_count ) {
				$this->add_devices_control( $devices_control_id, $args, $devices, $inside_horizontal_layout );
			}
		}

	}

	/**
	 * Add devices control via $wp_customize.
	 *
	 * @param string $id The control's ID.
	 * @param array  $args The control arguments.
	 * @param array  $devices The specified devices.
	 * @param bool   $inside_horizontal_layout Whether or not this control is inside a horizontal layout.
	 */
	public function add_devices_control( $id, $args, $devices, $inside_horizontal_layout = false ) {

		unset( $args['responsive'] );

		if ( isset( $args['default'] ) ) {
			unset( $args['default'] );
		}

		if ( isset( $args['transport'] ) ) {
			unset( $args['transport'] );
		}

		if ( isset( $args['output'] ) ) {
			unset( $args['output'] );
		}

		if ( isset( $args['wrapper_attrs'] ) ) {
			if ( isset( $args['wrapper_attrs']['data-kirki-parent-responsive-id'] ) ) {
				unset( $args['wrapper_attrs']['data-kirki-parent-responsive-id'] );
			}

			if ( isset( $args['wrapper_attrs']['data-kirki-device-preview'] ) ) {
				unset( $args['wrapper_attrs']['data-kirki-device-preview'] );
			}
		}

		$args['settings'] = $id;
		$args['type']     = 'kirki-responsive';

		if ( ! $inside_horizontal_layout ) {
			if ( ! isset( $args['wrapper_opts'] ) ) {
				$args['wrapper_opts'] = [];
			}

			$args['wrapper_opts']['gap'] = 'small';
		}

		$args['choices'] = [
			'devices' => $devices,
		];

		new Responsive( $args );

	}

	/**
	 * Filter the value for responsive fields.
	 *
	 * @see wp-content/plugins/kirki-dev/packages/kirki-framework/data-option/src/Option.php
	 *
	 * @param mixed  $value The field value.
	 * @param string $field_name The field name.
	 * @param mixed  $default The default value.
	 * @param string $type The option type (theme_mod or option).
	 *
	 * @return mixed The filtered value.
	 */
	public function kirki_get_value( $value = '', $field_name = '', $default = '', $type = 'theme_mod' ) {

		/**
		 * The "option" will be handled by "kirki_get_value" method in
		 * wp-content/plugins/kirki-dev/packages/kirki-framework/data-option/src/Option.php file.
		 */
		if ( 'option' === $type ) {
			return $value;
		}

		// If the field name doesn't contain a '[', then it's not a sub-item of another field.
		if ( false === strpos( $field_name, '[' ) ) {
			return $value;
		}

		// If this is not part of a responsive field, then return the value.
		if ( ! in_array( $field_name, Responsive::$sub_field_names, true ) ) {
			return $value;
		}

		/**
		 * If we got here then this is part of an option array.
		 * We need to get the 1st level, and then find the item inside that array.
		 */
		$parts = \explode( '[', $field_name );
		$value = get_theme_mod( $parts[0], [] );

		foreach ( $parts as $key => $part ) {
			/**
			 * Skip the 1st item, it's already been dealt with
			 * when we got the value initially right before this loop.
			 */
			if ( 0 === $key ) {
				continue;
			}

			$part = str_replace( ']', '', $part );

			/**
			 * If the item exists in the value, then change $value to the item.
			 * This runs recursively for all parts until we get to the end.
			 */
			if ( is_array( $value ) && isset( $value[ $part ] ) ) {
				$value = $value[ $part ];
				continue;
			}

			/**
			 * If we got here, the item was not found in the value.
			 * We need to change the value accordingly depending on whether
			 * this is the last item in the loop or not.
			 */
			$value = ( isset( $parts[ $key + 1 ] ) ) ? [] : '';
		}

		$value = empty( $value ) ? $default : $value;

		return $value;

	}

}
