<?php
/**
 * Html Builder Base Class
 *
 * @package errigal
 * @since 1.2.0
 */

namespace Errigal\Shared;

defined( 'ABSPATH' ) || exit;

use Errigal\Constant\Errigal_Html_Option as Html_Option;
use Errigal\Shared\{
	Errigal_Html_Element as Html_Element,
	Errigal_Utility as Utility,
	Errigal_Validator as Validator,
};
/**
 * This class is in charge of providing shared HTML builder functions.
 *
 * @since 1.2.0
 */
abstract class Errigal_Html_Builder_Base {
	/**
	 * Arguments.
	 *
	 * @since 1.2.0
	 *
	 * @var array
	 */
	private $args;

	/**
	 * Constructor.
	 *
	 * @since 1.2.0
	 *
	 * @param array $args Optional. Associative array of arguments.
	 */
	public function __construct( $args = array() ) {
		$this->args = Validator::is_associative_array( $args )
			? $args
			: array();
	}

	/**
	 * Creates HTML builder.
	 *
	 * @since 1.2.0
	 *
	 * @param array $args Optional. Associative array of arguments. Default empty array.
	 * @return self
	 */
	final public static function create( $args = array() ) {
		return new static( $args );
	}

	/**
	 * Determines whether $html_builder is instance of self.
	 *
	 * @since 1.2.0
	 *
	 * @param self $html_builder HTML builder.
	 * @return bool
	 */
	final public static function is_html_builder( $html_builder ) {
		return $html_builder instanceof self;
	}

	/**
	 * Gets arguments.
	 *
	 * @since 1.2.0
	 *
	 * @return string
	 */
	final public function get_args() {
		return $this->args;
	}

	/**
	 * Gets argument value.
	 *
	 * @since 1.2.0
	 *
	 * @param string ...$keys Variable list of keys for nested lookup.
	 * @return mixed|null
	 */
	final public function get_arg( ...$keys ) {
		if ( ! Validator::is_string_list( $keys ) ) {
			return null;
		}

		$args = $this->get_args();

		if ( ! Validator::is_associative_array( $args ) ) {
			return null;
		}

		foreach ( $keys as $key ) {
			if ( ! Validator::has_array_key( $args, $key ) ) {
				return null;
			}

			$args = $args[ $key ];
		}

		return $args;
	}

	/**
	 * Sets or updates argument value.
	 *
	 * @since 1.2.0
	 *
	 * @param mixed  $value   Value to set.
	 * @param string ...$keys Variable list of keys representing nested path.
	 * @return void
	 */
	final public function set_arg( $value, ...$keys ) {
		if ( ! Validator::is_string_list( $keys ) ) {
			return;
		}

		if ( ! Validator::is_associative_array( $args ) ) {
			$this->args = array();
		}

		$ref        = &$this->args;
		$last_index = count( $keys ) - 1;

		foreach ( $keys as $i => $key ) {
			if ( $i === $last_index ) {
				$ref[ $key ] = $value;

				return;
			}

			if (
				! Validator::has_array_key( $args, $key ) ||
				! is_array( $ref[ $key ] )
			) {
				$ref[ $key ] = array();
			}

			$ref = &$ref[ $key ];
		}
	}

	/**
	 * Creates element.
	 *
	 * @since 1.2.0
	 *
	 * @param string|null $tag                  Tag name.
	 * @param array       $args {
	 *     Optional. Associative array of arguments for HTML element. Default empty array.
	 *
	 *     @type array  $attrs              Optional. HTML attributes. Default empty array.
	 *     @type array  $allowed_html       Optional. Allowed HTML elements and attributes. Default empty array.
	 *     @type string $html_element_class Optional. HTML element class.
	 *                                      Default fully qualified class name of Errigal_Html_Element.
	 * }
	 * @param array       $default_allowed_html Optional. Default allowed HTML elements and attributes.
	 *                                          Default empty array.
	 * @return Html_Element
	 */
	protected function create_element( $tag, $args = array(), $default_allowed_html = array() ) {
		$args = $args ?? array();
		$args = wp_parse_args(
			$args,
			array(
				Html_Option::ATTRS              => array(),
				Html_Option::ALLOWED_HTML       => array(),
				Html_Option::HTML_ELEMENT_CLASS => Html_Element::class,
			)
		);

		if ( Validator::is_non_empty_array( $default_allowed_html ) ) {
			$args[ Html_Option::ALLOWED_HTML ] = Utility::array_merge_deep(
				$default_allowed_html,
				$args[ Html_Option::ALLOWED_HTML ]
			);
		}

		$html_element = $args[ Html_Option::HTML_ELEMENT_CLASS ];

		if (
			Validator::is_non_empty_string( $html_element ) &&
			is_a( $html_element, Html_Element::class, true ) &&
			class_exists( $html_element )
		) {
			return new $html_element( $this, $tag, $args );
		}

		return new Html_Element( $this, $tag, $args );
	}
}
