<?php
/**
 * Element loader.
 *
 * Discovers element classes in the /addons directory, then registers the ones
 * enabled in the admin panel.
 *
 * @package CraftAddonsForWPBakery
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class CAFW_Loader
 */
class CAFW_Loader {

	/**
	 * Discovered element instances keyed by slug.
	 *
	 * @var CAFW_Element_Base[]|null
	 */
	private $elements = null;

	/**
	 * Extra addon roots (besides the bundled /addons) to scan for elements.
	 * The Pro bootstrap registers its own directory here.
	 *
	 * @var string[]
	 */
	private $addon_roots = array();

	/**
	 * Register an additional directory of element folders to discover.
	 *
	 * @param string $dir Absolute path to an "addons" directory.
	 * @return void
	 */
	public function add_addon_root( $dir ) {
		$this->addon_roots[] = untrailingslashit( $dir );
	}

	/**
	 * Discover every element under the bundled /addons and any registered Pro
	 * roots, then instantiate it.
	 *
	 * Instantiation has no side effects - registration happens in register_enabled().
	 *
	 * @return CAFW_Element_Base[] Map of slug => element instance.
	 */
	public function discover() {
		if ( null !== $this->elements ) {
			return $this->elements;
		}

		$this->elements = array();
		$roots          = array_merge( array( CAFW_PATH . 'addons' ), $this->addon_roots );

		foreach ( $roots as $root ) {
			$element_dirs = glob( trailingslashit( $root ) . '*', GLOB_ONLYDIR );
			if ( empty( $element_dirs ) ) {
				continue;
			}

			foreach ( $element_dirs as $dir ) {
				$slug  = basename( $dir );
				$file  = trailingslashit( $dir ) . 'class-cafw-' . $slug . '.php';
				$class = 'CAFW_Element_' . str_replace( '-', '_', ucwords( $slug, '-' ) );

				if ( ! is_readable( $file ) || isset( $this->elements[ $slug ] ) ) {
					continue;
				}

				require_once $file;

				if ( class_exists( $class ) ) {
					$this->elements[ $slug ] = new $class();
				}
			}
		}

		return $this->elements;
	}

	/**
	 * Whether Pro elements may be registered. Gated on the premium build (the
	 * /pro folder is @fs_premium_only, so it only exists in the premium build),
	 * mirroring how AffiliateX gates with is__premium_only(). Freemius handles
	 * licence prompts separately.
	 *
	 * Filterable via "cafw_pro_unlocked" for local testing overrides.
	 *
	 * @return bool
	 */
	public static function pro_unlocked() {
		$unlocked = function_exists( 'cafw_fs' ) && cafw_fs()->is__premium_only();

		return (bool) apply_filters( 'cafw_pro_unlocked', $unlocked );
	}

	/**
	 * Register the available elements with WordPress and WPBakery.
	 *
	 * Every licensed element is always registered (shortcode + vc_map) so that
	 * content already placed on a page keeps rendering everywhere - frontend and
	 * editor alike - and never falls back to raw "[cafw_... ...]" text. The
	 * enable/disable toggle does not break content; it only hides the element
	 * from the editor's "Add Element" panel (see disabled_editor_tags(), applied
	 * as CSS). Pro elements stay unregistered while no licence is active.
	 *
	 * @return void
	 */
	public function register_enabled() {
		$pro_unlocked = self::pro_unlocked();

		foreach ( $this->discover() as $element ) {
			if ( $element->is_pro() && ! $pro_unlocked ) {
				continue; // Locked: no licence.
			}
			$element->register();
		}
	}

	/**
	 * Shortcode tags of elements that are toggled off, so the editor can hide
	 * their button in the "Add Element" panel (they stay registered and render
	 * existing content; they just can't be inserted anew).
	 *
	 * @return string[]
	 */
	public function disabled_editor_tags() {
		$pro_unlocked = self::pro_unlocked();
		$tags         = array();

		foreach ( $this->discover() as $slug => $element ) {
			if ( $element->is_pro() && ! $pro_unlocked ) {
				continue;
			}
			if ( ! CAFW_Settings::is_element_enabled( $slug ) ) {
				$tags[] = $element->get_shortcode_tag();
			}
		}

		return $tags;
	}

	/**
	 * Get all discovered elements (for the admin panel).
	 *
	 * @return CAFW_Element_Base[]
	 */
	public function get_elements() {
		return $this->discover();
	}
}
