<?php
/**
 * The SplendorStudio HTML component contains a powerful set of functions to create flexible and easy overwritable HTML markup,
 * attributes and content.
 *
 * @package SplendorStudio\Framework\API\HTML
 *
 * @since   1.0.0
 */

/**
 * Register the output for the given ID.  This function enables the output to be:
 *
 *      1. modified by registering a callback to "{$id}_output"
 *      2. removed by using {@see fancify_lite_remove_output()}.
 *
 * When in development mode, HTML comments containing the ID are added before and after the output, i.e. making it
 * easier to identify the content ID when inspecting an element in your web browser.
 *
 * Notes:
 *      1. Since this function uses {@see apply_filters()}, the $id argument may contain sub-hook(s).
 *      2. You can pass additional arguments to the functions that are hooked to <tt>$id</tt>.
 *
 * @since 1.0.0
 *
 * @param string $id     A unique string used as a reference. The $id argument may contain sub-hook(s).
 * @param string $output The given content to output.
 *
 * @return string|void
 */
function fancify_lite_output($id, $output) {
	$args    = func_get_args();
	$args[0] = $id . '_output';

	$output = call_user_func_array('Fancify_Lite_Filters::apply_filters', $args);

	if (empty($output)) {
		return;
	}

	if (fancify_lite_is_html_dev_mode()) {
		$id     = esc_attr($id);
		$output = "<!-- open output: $id -->" . $output . "<!-- close output: $id -->";
	}

	return $output;
}

/**
 * Register and then echo the output for the given ID.  This function is a wrapper for {@see fancify_lite_output()}.  See
 * {@see fancify_lite_output()} for more details.
 *
 * @since 1.4.0
 * @uses  fancify_lite_output()  To register output by ID.
 *
 * @param string $id     A unique string used as a reference. The $id argument may contain sub-hook(s).
 * @param string $output The given content to output.
 */
function fancify_lite_output_e($id, $output) {
	$args = func_get_args();
	echo call_user_func_array('fancify_lite_output', $args); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped -- Escaped in fancify_lite_output.
}

/**
 * Removes the HTML output for the given $id, meaning the output will not render.
 *
 * @since 1.0.0
 *
 * @param string $id The output's ID.
 *
 * @return bool|Fancify_Lite_Anonymous_Filters
 */
function fancify_lite_remove_output($id) {
	return Fancify_Lite_Filters::add_filter($id . '_output', false, 99999999);
}

/**
 * Build the opening HTML element's markup.  This function fires 3 separate hooks:
 *
 *      1. "{id}_before_markup" - which fires first before the element.
 *      2. "{$id}_prepend_markup" - which fires after the element when the element is not self-closing.
 *      3. "{$id}_after_markup" - which fires after the element when the element is self-closing.
 *
 * These 3 hooks along with the attributes make it really easy to modify, replace, extend, remove or hook the
 * markup and/or attributes.
 *
 * When in development mode, the "data-markup-id" attribute is added to the element, i.e. making it
 * easier to identify the content ID when inspecting an element in your web browser.
 *
 * Notes:
 *      1. Since this function uses {@see apply_filters()}, the $id argument may contain sub-hook(s).
 *      2. You can pass additional arguments to the functions that are hooked to <tt>$id</tt>.
 *
 * @since 1.0.0
 *
 * @param string       $id         A unique string used as a reference. The $id argument may contain sub-hooks(s).
 * @param string|bool  $tag        The HTML tag. When set to false or an empty string, the HTML markup tag will not be
 *                                 built, but both action hooks will fire. If set to null, the function bails out, i.e.
 *                                 the HTML markup tag will not be built and neither action hook fires.
 * @param string|array $attributes Optional. Query string or array of attributes. The array key defines the
 *                                 attribute name and the array value defines the attribute value.
 *
 * @return string|void
 */
function fancify_lite_open_markup($id, $tag, $attributes = array()) {
	$args            = func_get_args();
	$attributes_args = $args;

	// Set markup tag filter id.
	$args[0] = $id . '_markup';

	// If there are attributes, remove them from $args.
	if ($attributes) {
		unset($args[2]);
	}

	// Filter the tag.
	$tag = call_user_func_array('Fancify_Lite_Filters::apply_filters', $args);

	// If the tag is set to null, bail out.
	if (null === $tag) {
		return;
	}

	global $fancify_lite_is_selfclose_markup;

	// Remove the $tag argument.
	unset($args[1]);
	unset($attributes_args[1]);

	// Set and then fire the before action hook.
	$args[0] = $id . '_before_markup';
	$output  = call_user_func_array('Fancify_Lite_Actions::render_action', $args);

	// Build the opening tag when tag is available.
	if ($tag) {
		$output .= '<' . esc_attr($tag) . ' ' . call_user_func_array('fancify_lite_add_attributes', $attributes_args) . (fancify_lite_is_html_dev_mode() ? ' data-markup-id="' . esc_attr($id) . '"' : null) . ($fancify_lite_is_selfclose_markup ? '/' : '') . '>';
	}

	// Set and then fire the after action hook.
	$args[0] = $id . ($fancify_lite_is_selfclose_markup ? '_after_markup' : '_prepend_markup');
	$output .= call_user_func_array('Fancify_Lite_Actions::render_action', $args);

	// Reset the global variable to reduce memory usage.
	unset($GLOBALS['fancify_lite_is_selfclose_markup']);

	return $output;
}

/**
 * Echo the opening HTML tag's markup.  This function is a wrapper for {@see fancify_lite_open_markup()}.  See
 * {@see fancify_lite_open_markup()} for more details.
 *
 * @since 1.4.0
 *
 * @param string       $id         A unique string used as a reference. The $id argument may contain sub-hooks(s).
 * @param string|bool  $tag        The HTML tag. When set to false or an empty string, the HTML markup tag will not be
 *                                 built, but both action hooks will fire. If set to null, the function bails out, i.e.
 *                                 the HTML markup tag will not be built and neither action hook fires.
 * @param string|array $attributes Optional. Query string or array of attributes. The array key defines the
 *                                 attribute name and the array value defines the attribute value.
 *
 * @return void
 */
function fancify_lite_open_markup_e($id, $tag, $attributes = array()) {
	$args = func_get_args();
	echo call_user_func_array('fancify_lite_open_markup', $args); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped -- Escaped in fancify_lite_open_markup().
}

/**
 * Build the self-closing HTML element's markup.
 *
 * This function is shortcut of {@see fancify_lite_open_markup()}. It should be used for self-closing HTML markup such as
 * images or inputs.
 *
 * Note: You can pass additional arguments to the functions that are hooked to <tt>$id</tt>.
 *
 * @since 1.0.0
 * @since 1.5.0 Unsets the global variable.
 *
 * @global bool        $fancify_lite_is_selfclose_markup When true, indicates a self-closing element should be built.
 *
 * @param string       $id         A unique string used as a reference. The $id argument may contain sub-hooks(s).
 * @param string|bool  $tag        The self-closing HTML tag. When set to false or an empty string, the HTML markup tag
 *                                 will not be built, but both action hooks will fire. If set to null, the function
 *                                 bails out, i.e. the HTML markup tag will not be built and neither action hook fires.
 * @param string|array $attributes Optional. Query string or array of attributes. The array key defines the attribute
 *                                 name and the array value defines the attribute value.
 *
 * @return string|void
 */
function fancify_lite_selfclose_markup($id, $tag, $attributes = array()) {
	global $fancify_lite_is_selfclose_markup;

	$fancify_lite_is_selfclose_markup = true;

	$args = func_get_args();
	$html = call_user_func_array('fancify_lite_open_markup', $args);

	// Reset the global variable to reduce memory usage.
	unset($GLOBALS['fancify_lite_is_selfclose_markup']);

	return $html;
}

/**
 * Echo the self-closing HTML element's markup. This function is a wrapper for {@see fancify_lite_selfclose_markup()}.  See
 * {@see fancify_lite_selfclose_markup()} for more details.
 *
 * @since 1.4.0
 *
 * @param string       $id         A unique string used as a reference. The $id argument may contain sub-hooks(s).
 * @param string|bool  $tag        The self-closing HTML tag. When set to false or an empty string, the HTML markup tag
 *                                 will not be built, but both action hooks will fire. If set to null, the function
 *                                 bails out, i.e. the HTML markup tag will not be built and neither action hook fires.
 * @param string|array $attributes Optional. Query string or array of attributes. The array key defines the attribute
 *                                 name and the array value defines the attribute value.
 *
 * @return void
 */
function fancify_lite_selfclose_markup_e($id, $tag, $attributes = array()) {
	$args = func_get_args();
	echo call_user_func_array('fancify_lite_selfclose_markup', $args); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped -- Escaped in fancify_lite_open_markup().
}

/**
 * Build the closing HTML element's markup.  This function fires 2 separate hooks:
 *
 *      1. "{id}_append_markup" - which fires first before the closing tag.
 *      2. "{$id}_after_markup" - which fires after the closing tag.
 *
 * Note: You can pass additional arguments to the functions that are hooked to <tt>$id</tt>.
 *
 * @since 1.0.0
 *
 * @param string $id  Identical to the opening markup ID.
 * @param string $tag The HTML tag. When set to false or an empty string, the HTML markup tag will not be built, but
 *                    both action hooks will fire. If set to null, the function bails out, i.e. the HTML markup
 *                    tag will not be built and neither action hook fires.
 *
 * @return string|void
 */
function fancify_lite_close_markup($id, $tag) {
	// Filter the tag.
	$tag = Fancify_Lite_Filters::apply_filters($id . '_markup', $tag);

	// If the tag is set to null, bail out.
	if (null === $tag) {
		return;
	}

	$args = func_get_args(); // phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.Changed

	// Remove the $tag argument.
	unset($args[1]);

	// Set and then fire the append action hook.
	$args[0] = $id . '_append_markup';
	$output  = call_user_func_array('Fancify_Lite_Actions::render_action', $args);

	// Build the closing tag when tag is available.
	if ($tag) {
		$output .= '</' . esc_attr($tag) . '>';
	}

	// Set and then fire the after action hook.
	$args[0] = $id . '_after_markup';
	$output .= call_user_func_array('Fancify_Lite_Actions::render_action', $args);

	return $output;
}

/**
 * Echo the closing HTML tag's markup.  This function is a wrapper for {@see fancify_lite_close_markup()}.  See
 * {@see fancify_lite_close_markup()} for more details.
 *
 * @since 1.4.0
 *
 * @param string $id  Identical to the opening markup ID.
 * @param string $tag The HTML tag. When set to false or an empty string, the HTML markup tag will not be built, but
 *                    both action hooks will fire. If set to null, the function bails out, i.e. the HTML markup
 *                    tag will not be built and neither action hook fires.
 *
 * @return void
 */
function fancify_lite_close_markup_e($id, $tag) {
	$args = func_get_args();
	echo call_user_func_array('fancify_lite_close_markup', $args); // phpcs:ignore WordPress.XSS.EscapeOutput.OutputNotEscaped -- Escaped in fancify_lite_close_markup().
}

/**
 * Modify the opening and closing or self-closing HTML tag.
 *
 * @since 1.0.0
 *
 * @param string          $id       The target markup's ID.
 * @param string|callback $markup   The replacement HTML tag. A callback is accepted if conditions need to be
 *                                  applied. If arguments are available, then they are passed to the callback.
 * @param int             $priority Optional. Used to specify the order in which the functions
 *                                  associated with a particular action are executed. Default is 10.
 * @param int             $args     Optional. The number of arguments the callback accepts. Default is 1.
 *
 * @return bool|Fancify_Lite_Anonymous_Filters
 */
function fancify_lite_modify_markup($id, $markup, $priority = 10, $args = 1) {
	return Fancify_Lite_Filters::add_filter($id . '_markup', $markup, $priority, $args);
}

/**
 * Remove the markup.
 *
 * This function will automatically remove the opening and the closing HTML tag. If the markup is self-closing,
 * the HTML tag will be removed accordingly.
 *
 * @since 1.0.0
 *
 * @param string $id             The target markup's ID.
 * @param bool   $remove_actions Optional. When true, the markup including the before and prepend/after hooks will be
 *                               removed. When false, only the HTML element will be removed.
 *
 * @return bool|Fancify_Lite_Anonymous_Filters
 */
function fancify_lite_remove_markup($id, $remove_actions = false) {
	return Fancify_Lite_Filters::add_filter($id . '_markup', $remove_actions ? null : false);
}

/**
 * Convert an array of attributes into a properly formatted HTML string.
 *
 * The attributes are registered in SplendorStudio via the given ID.  Using this ID, we can hook into the filter, i.e.
 * "$id_attributes", to modify, replace, extend, or remove one or more of the registered attributes.
 *
 * Since this function uses {@see apply_filters()}, the $id argument may contain sub-hook(s).
 *
 * Note: You can pass additional arguments to the functions that are hooked to <tt>$id</tt>.
 *
 * @since 1.0.0
 *
 * @param string       $id         A unique string used as a reference. The $id argument may contain sub-hook(s).
 * @param string|array $attributes Optional. Query string or array of attributes. The array key defines the
 *                                 attribute name and the array value define the attribute value. Setting
 *                                 the array value to '' will display the attribute value as empty
 *                                 (e.g. class=""). Setting it to 'false' will only display
 *                                 the attribute name (e.g. data-example). Setting it to 'null' will not
 *                                 display anything.
 *
 * @return string The HTML attributes.
 */
function fancify_lite_add_attributes($id, $attributes = array()) {
	$args    = func_get_args();
	$args[0] = $id . '_attributes';

	if (empty($args[1])) {
		$args[1] = array();
	}

	$args[1] = wp_parse_args($args[1]);

	$attributes = call_user_func_array('Fancify_Lite_Filters::apply_filters', $args);

	return fancify_lite_esc_attributes($attributes);
}

/**
 * Add a value to an existing attribute or add a new attribute.
 *
 * This function must be called before the targeted markup is called.
 *
 * The "data-markup-id" is added as a HTML attribute if the development mode is enabled. This makes it very
 * easy to find the content ID when inspecting an element in a web browser.
 *
 * @since 1.0.0
 * @since 1.5.0 Return the object.
 *
 * @param string $id        The markup ID.
 * @param string $attribute Name of the HTML attribute.
 * @param string $value     Value of the HTML attribute. If set to '' will display the attribute value as empty
 *                          (e.g. class=""). Setting it to 'false' will only display the attribute name
 *                          (e.g. data-example). Setting it to 'null' will not display anything.
 *
 * @return fancify_lite_Attribute
 */
function fancify_lite_add_attribute($id, $attribute, $value) {
	$attribute = new Fancify_Lite_Attribute($id, $attribute, $value);

	return $attribute->init('add');
}

/**
 * Check if development mode is enabled taking in consideration legacy constant.
 *
 * @since  1.5.0
 * @ignore
 * @access private
 *
 * @return bool
 */
function fancify_lite_is_html_dev_mode() {

	if (defined('FANCIFY_HTML_DEV_MODE')) {
		return (bool) FANCIFY_HTML_DEV_MODE;
	}

	return (bool) get_option('fancify_lite_dev_mode', false);
}
