<?php
/**
 * The SplendorStudio Widgets API extends the WordPress Widgets API. Where WordPress uses 'sidebar', SplendorStudio uses
 * 'widget_area' as it is more appropriate when using it to define an area which is not besides the main
 * content (e.g. a mega footer widget area).
 *
 * @package SplendorStudio\Framework\API\Widgets
 *
 * @since 1.0.0
 */

/**
 * Register a widget area.
 *
 * Since a SplendorStudio widget area is using the WordPress sidebar, this function registers a WordPress sidebar using
 * {@link https://codex.wordpress.org/Function_Reference/register_sidebar register_sidebar()}, with additional
 * arguments.
 *
 * Note that the 'class', before_widget', 'after_widget', 'before_title' and 'after_title' arguments are not
 * supported. SplendorStudio widgets are built using the SplendorStudio HTML API which allows full control over HTML markup
 * and attributes.
 *
 * When allowing for automatic generation of the name and ID parameters, keep in mind that the incrementor for
 * your widget area can change over time depending on what other plugins and themes are installed.
 *
 * @since 1.0.0
 *
 * @param array $args {
 *      Optional. Arguments used by the widget area.
 *
 *      @type string $id                         The unique identifier by which the widget area will be called.
 *      @type string $name                       Optional. The name or title of the widget area displayed in the
 *                                               admin dashboard.
 *      @type string $description                Optional. The widget area description.
 *      @type string $fancify_lite_type                 Optional. The widget area type. Accepts 'stack', 'grid' or 'offcanvas'.
 *                                               Default is stack.
 *      @type bool   $fancify_lite_show_widget_title    Optional. Whether to show the widget title or not. Default true.
 *      @type bool   $fancify_lite_show_widget_badge    Optional. Whether to show the widget badge or not. Default false.
 *      @type bool   $fancify_lite_widget_badge_content Optional. The badge content. This may contain widget shortcodes
 *                                               {@see fancify_lite_widget_shortcodes()}. Default is 'Hello'.
 * }
 *
 * @return string The widget area ID is added to the $wp_registered_sidebars globals when the widget area is setup.
 */
function fancify_lite_register_widget_area( $args = array() ) {
	// Stop here if the id isn't set.
	$id = fancify_lite_get( 'id', $args );

	if ( ! $id ) {
		return '';
	}

	/**
	 * Filter the default arguments used by the widget area.
	 *
	 * @since 1.0.0
	 */
	$defaults = apply_filters( 'fancify_lite_widgets_area_default_args', array(
		'fancify_lite_type'                 => 'stack',
		'fancify_lite_show_widget_title'    => true,
		'fancify_lite_show_widget_badge'    => false,
		'fancify_lite_widget_badge_content' => __( 'Hello', 'fancify-lite' ),
	) );

	/**
	 * Filter the arguments used by the widget area.
	 *
	 * The dynamic portion of the hook name, $id, refers to the widget area id.
	 *
	 * @since 1.0.0
	 */
	$args = Fancify_Lite_Filters::apply_filters( "fancify_lite_widgets_area_args[_{$id}]", array_merge( $defaults, $args ) );

	return register_sidebar( $args );
}

/**
 * Check whether a widget area is in use.
 *
 * Since a SplendorStudio widget area is using the WordPress sidebar, this function checks if the defined sidebar
 * is in use using {@link https://codex.wordpress.org/Function_Reference/is_active_sidebar is_active_sidebar()}.
 *
 * @since 1.0.0
 *
 * @param string $id The ID of the registered widget area.
 *
 * @return bool True if the widget area is in use, false otherwise.
 */
function fancify_lite_is_active_widget_area( $id ) {
	return is_active_sidebar( $id );
}

/**
 * Check whether a widget area is registered.
 *
 * While {@see fancify_lite_is_active_widget_area()} checks if a widget area contains widgets, this function only checks if a
 * widget area is registered.
 *
 * @since 1.0.0
 *
 * @param string $id The ID of the registered widget area.
 *
 * @return bool True if the widget area is registered, false otherwise.
 */
function fancify_lite_has_widget_area( $id ) {
	global $wp_registered_sidebars;

	return isset( $wp_registered_sidebars[ $id ] );
}

/**
 * Get the output of a widget area.
 *
 * @since 1.5.0
 *
 * @param string $id The ID of the registered widget area.
 *
 * @return string|bool The output, if a widget area was found and called. False if not found.
 */
function fancify_lite_get_widget_area_output( $id ) {

	// Stop here if the widget area is not registered.
	if ( ! fancify_lite_has_widget_area( $id ) ) {
		return false;
	}

	fancify_lite_setup_widget_area( $id );

	/**
	 * Fires after a widget area is initialized.
	 *
	 * @since 1.0.0
	 */
	do_action( 'fancify_lite_widget_area_init' );

	ob_start();

	/**
	 * Fires when {@see fancify_lite_get_widget_area_output()} is called.
	 *
	 * @since 1.0.0
	 */
	do_action( 'fancify_lite_widget_area' );

	$output = ob_get_clean();

	// Reset widget area global to reduce memory usage.
	fancify_lite_reset_widget_area();

	/**
	 * Fires after a widget area is reset.
	 *
	 * @since 1.0.0
	 */
	do_action( 'fancify_lite_widget_area_reset' );

	return $output;
}

/**
 * Retrieve data from the current widget area in use.
 *
 * @since 1.0.0
 *
 * @param string|bool $needle Optional. The searched widget area needle.
 *
 * @return string|bool The current widget area data, or field data if the needle is specified. False if not found.
 */
function fancify_lite_get_widget_area( $needle = false ) {
	global $fancify_lite_widget_area;

	if ( ! $needle ) {
		return $fancify_lite_widget_area ? $fancify_lite_widget_area : false;
	}

	return fancify_lite_get( $needle, $fancify_lite_widget_area, false );
}

/**
 * Whether there are more widgets available in the loop.
 *
 * @since 1.0.0
 *
 * @return bool True if widgets are available, false if end of loop.
 */
function fancify_lite_have_widgets() {
	global $fancify_lite_widget_area;

	if ( ! fancify_lite_get( 'widgets', $fancify_lite_widget_area ) ) {
		return false;
	}

	$widgets = array_keys( $fancify_lite_widget_area['widgets'] );

	if ( isset( $widgets[ $fancify_lite_widget_area['current_widget'] ] ) ) {
		return true;
	}

	// Reset last widget global to reduce memory usage.
	fancify_lite_reset_widget();

	return false;
}

/**
 * Sets up the current widget.
 *
 * This function also prepares the next widget integer.
 *
 * @since 1.0.0
 *
 * @return bool True on success, false on failure.
 */
function fancify_lite_setup_widget() {
	global $fancify_lite_widget_area;

	$widgets = array_keys( $fancify_lite_widget_area['widgets'] );

	// Retrieve widget id if exists.
	$id = fancify_lite_get( $fancify_lite_widget_area['current_widget'], $widgets );

	if ( ! $id ) {
		return false;
	}

	// Set next current widget integer.
	$fancify_lite_widget_area['current_widget'] = $fancify_lite_widget_area['current_widget'] + 1;

	fancify_lite_prepare_widget_data( $id );

	return true;
}

/**
 * Retrieve data from the current widget in use.
 *
 * @since 1.0.0
 *
 * @param string|bool $needle Optional. The searched widget needle.
 *
 * @return string|bool The current widget data, or field data if the needle is specified. False if not found.
 */
function fancify_lite_get_widget( $needle = false ) {
	global $fancify_lite_widget;

	if ( ! $needle ) {
		return $fancify_lite_widget ? $fancify_lite_widget : false;
	}

	return fancify_lite_get( $needle, $fancify_lite_widget, false );
}

/**
 * Search content for shortcodes and filter shortcodes through their hooks.
 *
 * Shortcodes must be delimited with curly brackets (e.g. {key}) and correspond to the searched array key from
 * the widget global.
 *
 * @since 1.0.0
 *
 * @param string|array $content Content containing the shortcode(s) delimited with curly brackets (e.g. {key}).
 *                        Shortcode(s) correspond to the searched array key and will be replaced by the array
 *                        value if found.
 *
 * @return string Content with shortcodes filtered out.
 */
function fancify_lite_widget_shortcodes( $content ) {
	if ( ! isset( $GLOBALS['fancify_lite_widget'] ) ) {
		return $content;
	}

	if ( is_array( $content ) ) {
		$content = build_query( $content );
	}

	return fancify_lite_array_shortcodes( $content, $GLOBALS['fancify_lite_widget'] );
}

/**
 * Set up widget area global data.
 *
 * @since  1.0.0
 * @ignore
 * @access private
 *
 * @param string $id Widget area ID.
 *
 * @return bool
 */
function fancify_lite_setup_widget_area( $id ) {
	global $fancify_lite_widget_area, $wp_registered_sidebars;

	if ( ! isset( $wp_registered_sidebars[ $id ] ) ) {
		return false;
	}

	// Add widget area delimiters. This is used to split wp sidebar as well as the widgets title.
	$wp_registered_sidebars[ $id ] = array_merge( // @codingStandardsIgnoreLine // phpcs:ignore WordPress.Variables.GlobalVariables.OverrideProhibited -- Valid use case.
		$wp_registered_sidebars[ $id ],
		array(
			'before_widget' => '<!--widget-%1$s-->',
			'after_widget'  => '<!--widget-end-->',
			'before_title'  => '<!--title-start-->',
			'after_title'   => '<!--title-end-->',
		)
	);

	// Start building widget area global before dynamic_sidebar is called.
	$fancify_lite_widget_area = $wp_registered_sidebars[ $id ];

	// Buffer sidebar, please make it easier for us wp.
	$sidebar = fancify_lite_render_function( 'dynamic_sidebar', $id );

	// Prepare sidebar split.
	$sidebar = preg_replace( '#(<!--widget-([a-z0-9-_]+)-->(.*?)<!--widget-end-->*?)#smU', '<!--split-sidebar-->$1<!--split-sidebar-->', $sidebar );

	// Split sidebar.
	$splited_sidebar = explode( '<!--split-sidebar-->', $sidebar );

	// Prepare widgets count.
	preg_match_all( '#<!--widget-end-->#', $sidebar, $counter );

	// Continue building widget area global with the split sidebar elements.
	$fancify_lite_widget_area['widgets_count']  = count( $counter[0] );
	$fancify_lite_widget_area['current_widget'] = 0;

	// Only add widgets if exists.
	if ( 3 === count( $splited_sidebar ) ) {
		$fancify_lite_widget_area['before_widgets'] = $splited_sidebar[0];
		$fancify_lite_widget_area['widgets']        = fancify_lite_setup_widgets( $splited_sidebar[1] );
		$fancify_lite_widget_area['after_widgets']  = $splited_sidebar[2];
	}

	return true;
}

/**
 * Setup widget area global widgets data.
 *
 * @since  1.0.0
 * @ignore
 * @access private
 *
 * @param string $widget_area_content Content of the widget area.
 *
 * @return array
 */
function fancify_lite_setup_widgets( $widget_area_content ) {
	global $wp_registered_widgets, $fancify_lite_widget_area;

	$fancify_lite_widgets = array();

	foreach ( explode( '<!--widget-end-->', $widget_area_content ) as $content ) {

		if ( ! preg_match( '#<!--widget-([a-z0-9-_]+?)-->#smU', $content, $matches ) ) {
			continue;
		}

		// Retrieve widget id.
		$id = $matches[1];

		// Stop here if the widget can't be found.
		$data = fancify_lite_get( $id, $wp_registered_widgets );

		if ( ! $data ) {
			continue;
		}

		// Start building the widget array.
		$widget = array();

		// Set defaults.
		$widget['options'] = array();
		$widget['type']    = null;
		$widget['title']   = '';

		// Add total count.
		$widget['count'] = $fancify_lite_widget_area['widgets_count'];

		// Add basic widget arguments.
		foreach ( array( 'id', 'name', 'classname', 'description' ) as $var ) {
			$widget[ $var ] = isset( $data[ $var ] ) ? $data[ $var ] : null;
		}

		// Add type and options.
		$object = current( $data['callback'] );

		if ( isset( $data['callback'] ) && is_array( $data['callback'] ) && $object ) {

			if ( is_a( $object, 'WP_Widget' ) ) {
				$widget['type'] = $object->id_base;

				if ( isset( $data['params'][0]['number'] ) ) {
					$number = $data['params'][0]['number'];
					$params = get_option( $object->option_name );

					if ( false === $params && isset( $object->alt_option_name ) ) {
						$params = get_option( $object->alt_option_name );
					}

					if ( isset( $params[ $number ] ) ) {
						$widget['options'] = $params[ $number ];
					}
				}
			}
		} elseif ( 'nav_menu-0' === $id ) { // Widget type fallback.
			$widget['type'] = 'nav_menu';
		}

		// Widget fallback name.
		if ( empty( $widget['name'] ) ) {
			$widget['name'] = ucfirst( $widget['type'] );
		}

		// Extract and add title.
		if ( preg_match( '#<!--title-start-->(.*)<!--title-end-->#s', $content, $matches ) ) {
			$widget['title'] = strip_tags( $matches[1] );
		}

		// Remove title from content.
		$content = preg_replace( '#(<!--title-start-->.*<!--title-end-->*?)#smU', '', $content );

		// Remove widget HTML delimiters.
		$content = preg_replace( '#(<!--widget-([a-z0-9-_]+)-->|<!--widgets-end-->)#', '', $content );

		$widget['content'] = $content;

		// Add widget control arguments and register widget.
		$fancify_lite_widgets[ $widget['id'] ] = array_merge( $widget, array(
			'show_title'    => $fancify_lite_widget_area['fancify_lite_show_widget_title'],
			'badge'         => $fancify_lite_widget_area['fancify_lite_show_widget_badge'],
			'badge_content' => $fancify_lite_widget_area['fancify_lite_widget_badge_content'],
		) );
	}

	return $fancify_lite_widgets;
}

/**
 * Setup widget global data.
 *
 * @since  1.0.0
 * @ignore
 * @access private
 *
 * @param string $id Widget ID.
 *
 * @return void
 */
function fancify_lite_prepare_widget_data( $id ) {
	global $fancify_lite_widget;
	$widgets       = fancify_lite_get_widget_area( 'widgets' );
	$fancify_lite_widget = $widgets[ $id ];
}

/**
 * Reset widget area global data.
 *
 * @since  1.0.0
 * @ignore
 * @access private
 *
 * @return void
 */
function fancify_lite_reset_widget_area() {
	unset( $GLOBALS['fancify_lite_widget_area'] );
}

/**
 * Reset widget global data.
 *
 * @since  1.0.0
 * @ignore
 * @access private
 *
 * @return void
 */
function fancify_lite_reset_widget() {
	unset( $GLOBALS['fancify_lite_widget'] );
}

/**
 * Build widget area sub-filters.
 *
 * @since  1.0.0
 * @ignore
 * @access private
 *
 * @return string
 */
function fancify_lite_widget_area_subfilters() {
	global $fancify_lite_widget_area;

	// Add sidebar id.
	return '[_' . $fancify_lite_widget_area['id'] . ']';
}

/**
 * Build widget sub-filters.
 *
 * @since  1.0.0
 * @ignore
 * @access private
 *
 * @return string
 */
function fancify_lite_widget_subfilters() {
	global $fancify_lite_widget_area, $fancify_lite_widget;

	$subfilters = array(
		$fancify_lite_widget_area['id'], // Add sidebar id.
		$fancify_lite_widget['type'], // Add widget type.
		$fancify_lite_widget['id'], // Add widget id.
	);

	return '[_' . implode( '][_', $subfilters ) . ']';
}

add_action( 'the_widget', 'fancify_lite_force_the_widget', 10, 3 );
/**
 * Force atypical widget added using the_widget() to have a correctly registered id.
 *
 * @since  1.0.0
 * @ignore
 * @access private
 *
 * @param string $widget   The widget's PHP class name (see class-wp-widget.php).
 * @param array  $instance Optional. The widget's instance settings. Default empty array.
 * @param array  $args     Array of arguments to configure the display of the widget.
 *
 * @return void
 */
function fancify_lite_force_the_widget( $widget, $instance, $args ) {
	global $wp_widget_factory;

	$widget_obj = $wp_widget_factory->widgets[ $widget ];

	if ( ! $widget_obj instanceof WP_Widget ) {
		return;
	}

	// Stop here if the widget correctly contains an id.
	if ( false !== stripos( fancify_lite_get( 'before_widget', $args ), $widget_obj->id ) ) {
		return;
	}

	printf( '<!--widget-%1$s-->', esc_attr( $widget_obj->id ) );
}
