<?php
/**
 * Kadence\Theme class
 *
 * @package kadence
 */

namespace Kadence;

use InvalidArgumentException;

/**
 * Main class for the theme.
 *
 * This class takes care of initializing theme features and available template tags.
 */
class Theme {
	/**
	 * Instance Control
	 *
	 * @var null
	 */
	private static $instance = null;

	/**
	 * Associative array of theme components, keyed by their slug.
	 *
	 * @var array
	 */
	public $components = array();

	/**
	 * The template tags instance, providing access to all available template tags.
	 *
	 * @var Template_Tags
	 */
	protected $template_tags;

	/**
	 * Main Kadence\Theme Class Instance.
	 *
	 * Insures that only one instance of Kadence Class exists in memory at any one
	 * time. Also prevents needing to define globals all over the place.
	 *
	 * @static
	 * @static var array $instance
	 *
	 * @return Kadence\Theme Class
	 */
	public static function instance() {

		// Return if already instantiated.
		if ( self::is_instantiated() ) {
			return self::$instance;
		}

		// Setup the singleton.
		self::setup_instance();

		self::$instance->initialize();

		// Return the instance.
		return self::$instance;

	}

	/**
	 * Setup the singleton instance
	 *
	 * @access private
	 */
	private static function setup_instance() {
		self::$instance = new Theme();
	}

	/**
	 * Return whether the main loading class has been instantiated or not.
	 *
	 * @access private
	 * @return boolean True if instantiated. False if not.
	 */
	private static function is_instantiated() {

		// Return true if instance is correct class.
		if ( ! empty( self::$instance ) && ( self::$instance instanceof Theme ) ) {
			return true;
		}

		// Return false if not instantiated correctly.
		return false;
	}

	/**
	 * Constructor.
	 *
	 * Sets the theme components.
	 *
	 * @param array $components Optional. List of theme components. Only intended for custom initialization, typically
	 *                          the theme components are declared by the theme itself. Each theme component must
	 *                          implement the Component_Interface interface.
	 *
	 * @throws InvalidArgumentException Thrown if one of the $components does not implement Component_Interface.
	 */
	public function __construct( array $components = array() ) {
		spl_autoload_register( array( $this, 'autoload' ) );

		if ( empty( $components ) ) {
			$components = $this->get_default_components();
		}

		// Set the components.
		foreach ( $components as $component ) {

			// Bail if a component is invalid.
			if ( ! $component instanceof Component_Interface ) {
				throw new InvalidArgumentException(
					sprintf(
						/* translators: 1: classname/type of the variable, 2: interface name */
						__( 'The theme component %1$s does not implement the %2$s interface.', 'kadence' ),
						gettype( $component ),
						Component_Interface::class
					)
				);
			}

			$this->components[ $component->get_slug() ] = $component;
		}

		// Instantiate the template tags instance for all theme templating components.
		$this->template_tags = new Template_Tags(
			array_filter(
				$this->components,
				function( Component_Interface $component ) {
					return $component instanceof Templating_Component_Interface;
				}
			)
		);
	}

	/**
	 * Custom autoloader function for theme classes.
	 *
	 * @access private
	 *
	 * @param string $class_name Class name to load.
	 * @return bool True if the class was loaded, false otherwise.
	 */
	private function autoload( $class_name ) {
		$namespace = 'Kadence';

		if ( strpos( $class_name, $namespace . '\\' ) !== 0 ) {
			return false;
		}
		$parts = explode( '\\', substr( $class_name, strlen( $namespace . '\\' ) ) );

		$path = get_template_directory() . '/inc/components';
		foreach ( $parts as $part ) {
			$path .= '/' . strtolower( $part );
		}
		$path .= '.php';
		if ( ! file_exists( $path ) ) {
			return false;
		}
		require_once $path; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude.FileIncludeFound

		return true;
	}
	/**
	 * Throw error on object clone.
	 *
	 * The whole idea of the singleton design pattern is that there is a single
	 * object therefore, we don't want the object to be cloned.
	 *
	 * @return void
	 */
	public function __clone() {
		// Cloning instances of the class is forbidden.
		_doing_it_wrong( __FUNCTION__, esc_html__( 'Something went wrong.', 'kadence' ), '1.0' );
	}

	/**
	 * Disable un-serializing of the class.
	 *
	 * @return void
	 */
	public function __wakeup() {
		// Unserializing instances of the class is forbidden.
		_doing_it_wrong( __FUNCTION__, esc_html__( 'Something went wrong.', 'kadence' ), '1.0' );
	}


	/**
	 * Adds the action and filter hooks to integrate with WordPress.
	 *
	 * This method must only be called once in the request lifecycle.
	 */
	public function initialize() {
		array_walk(
			$this->components,
			function( Component_Interface $component ) {
				$component->initialize();
			}
		);
	}

	/**
	 * Retrieves the template tags instance, the entry point exposing template tag methods.
	 *
	 * Calling `kadence()` is a short-hand for calling this method on the main theme instance. The instance then allows
	 * for actual template tag methods to be called. For example, if there is a template tag called `posted_on`, it can
	 * be accessed via `kadence()->posted_on()`.
	 *
	 * @return Template_Tags Template tags instance.
	 */
	public function template_tags() : Template_Tags {
		return $this->template_tags;
	}

	/**
	 * Retrieves the component for a given slug.
	 *
	 * This should typically not be used from outside of the theme classes infrastructure.
	 *
	 * @param string $slug Slug identifying the component.
	 * @return Component_Interface Component for the slug.
	 *
	 * @throws InvalidArgumentException Thrown when no theme component with the given slug exists.
	 */
	public function component( string $slug ) : Component_Interface {
		if ( ! isset( $this->components[ $slug ] ) ) {
			throw new InvalidArgumentException(
				sprintf(
					/* translators: %s: slug */
					__( 'No theme component with the slug %s exists.', 'kadence' ),
					$slug
				)
			);
		}

		return $this->components[ $slug ];
	}

	/**
	 * Gets the default theme components.
	 *
	 * This method is called if no components are passed to the constructor, which is the common scenario.
	 *
	 * @return array List of theme components to use by default.
	 */
	protected function get_default_components() : array {
		$components = array(
			new Options\Component(),
			new Localization\Component(),
			new Base_Support\Component(),
			new Editor\Component(),
			new Accessibility\Component(),
			new Image_Sizes\Component(),
			new AMP\Component(),
			new Microdata\Component(),
			//new PWA\Component(),
			new Comments\Component(),
			new Nav_Menus\Component(),
			new Custom_Header\Component(),
			new Custom_Footer\Component(),
			new Custom_Logo\Component(),
			new Color_Palette\Component(),
			new Styles\Component(),
			new Scripts\Component(),
			new Breadcrumbs\Component(),
			new Template_Parts\Component(),
			new Clean_Frontend\Component(),
			new Icons\Component(),
			new Layout\Component(),
			new Entry_Title\Component(),
			new Archive_Title\Component(),
			new Third_Party\Component(),
		);
		if ( class_exists( '\Elementor\Plugin' ) ) {
			$components[] = new Elementor\Component();
		}
		if ( class_exists( 'ElementorPro\Modules\ThemeBuilder\Module' ) ) {
			$components[] = new Elementor_Pro\Component();
		}
		if ( class_exists( 'FLThemeBuilderLayoutData' ) ) {
			$components[] = new BeaverThemer\Component();
		}
		if ( class_exists( 'FLBuilderModel' ) ) {
			$components[] = new Beaver\Component();
		}
		if ( class_exists( 'TUTOR\Tutor' ) ) {
			$components[] = new TutorLMS\Component();
		}
		if ( class_exists( 'woocommerce' ) ) {
			$components[] = new Woocommerce\Component();
		}
		if ( class_exists( 'HT_Knowledge_Base' ) ) {
			$components[] = new Heroic_Kb\Component();
		}
		if ( defined( 'JETPACK__VERSION' ) ) {
			$components[] = new Jetpack\Component();
		}
		if ( class_exists( 'LifterLMS' ) ) {
			$components[] = new LifterLMS\Component();
		}
		if ( defined( 'LEARNDASH_VERSION' ) ) {
			$components[] = new LearnDash\Component();
		}
		if ( class_exists( 'Essential_Real_Estate' ) ) {
			$components[] = new Essential_Real_Estate\Component();
		}
		if ( class_exists( 'Restrict_Content_Pro' ) ) {
			$components[] = new Restrict_Content_Pro\Component();
		}
		if ( class_exists( 'Estatik' ) ) {
			$components[] = new Estatik\Component();
		}
		if ( class_exists( 'BBPress' ) && apply_filters( 'kadence_theme_enable_bbpress_component', true ) ) {
			$components[] = new BBPress\Component();
		}
		if ( defined( 'BP_PLATFORM_VERSION' ) ) {
			$components[] = new BuddyBoss\Component();
		}
		if ( defined( 'POLYLANG_VERSION' ) ) {
			$components[] = new Polylang\Component();
		}
		if ( defined( 'TRIBE_EVENTS_FILE' ) ) {
			$components[] = new The_Events_Calendar\Component();
		}
		if ( defined( 'GIVE_VERSION' ) ) {
			$components[] = new Give\Component();
		}
		if ( defined( 'WPZOOM_RCB_VERSION' ) ) {
			$components[] = new Zoom_Recipe_Card\Component();
		}
		return $components;
	}
}
