<?php
/**
 * BuddyX\Buddyx\Template_Tags class
 *
 * @package buddyx
 */

namespace BuddyX\Buddyx;

use InvalidArgumentException;
use BadMethodCallException;
use RuntimeException;

/**
 * Template tags entry point.
 *
 * This class provides access to all available template tag methods.
 *
 * Its instance can be accessed through `buddyx()`. For example, if there is a template tag called `posted_on`, it can
 * be accessed via `buddyx()->posted_on()`.
 */
class Template_Tags {

	/**
	 * Associative array of all available template tags.
	 *
	 * Method names are the keys, their callback information the values.
	 *
	 * @var array
	 */
	protected $template_tags = array();

	/**
	 * Constructor.
	 *
	 * Sets the theme components.
	 *
	 * @param array $components Optional. List of theme templating components. Each of these must implement the
	 *                          Templating_Component_Interface interface.
	 *
	 * @throws InvalidArgumentException Thrown if one of the $components does not implement
	 *                                  Templating_Component_Interface.
	 */
	public function __construct( array $components = array() ) {

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

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

			$this->set_template_tags( $component );
		}
	}

	/**
	 * Magic call method.
	 *
	 * Will proxy to the template tag $method, unless it is not available, in which case an exception will be thrown.
	 *
	 * @param string $method Template tag name.
	 * @param array  $args   Template tag arguments.
	 * @return mixed Template tag result, or null if template tag only outputs markup.
	 *
	 * @throws BadMethodCallException Thrown if the template tag does not exist.
	 */
	public function __call( string $method, array $args ) {
		if ( ! isset( $this->template_tags[ $method ] ) ) {
			throw new BadMethodCallException(
				sprintf(
					/* translators: %s: template tag name */
					esc_html__( 'The template tag %s does not exist.', 'buddyx' ),
					'buddyx()->' . esc_html( $method ) . '()'
				)
			);
		}

		return call_user_func_array( $this->template_tags[ $method ]['callback'], $args );
	}

	/**
	 * Sets template tags for a given theme templating component.
	 *
	 * @param Templating_Component_Interface $component Theme templating component.
	 *
	 * @throws InvalidArgumentException Thrown when one of the template tags is invalid.
	 * @throws RuntimeException         Thrown when one of the template tags conflicts with an existing one.
	 */
	protected function set_template_tags( Templating_Component_Interface $component ) {
		$tags = $component->template_tags();

		foreach ( $tags as $method_name => $callback ) {
			if ( is_callable( $callback ) ) {
				$callback = array( 'callback' => $callback );
			}

			if ( ! is_array( $callback ) || ! isset( $callback['callback'] ) ) {
				throw new InvalidArgumentException(
					sprintf(
						/* translators: 1: template tag method name, 2: component class name */
						esc_html__( 'The template tag method %1$s registered by theme component %2$s must either be a callable or an array.', 'buddyx' ),
						esc_html( $method_name ),
						esc_html( get_class( $component ) )
					)
				);
			}

			if ( isset( $this->template_tags[ $method_name ] ) ) {
				throw new RuntimeException(
					sprintf(
						/* translators: 1: template tag method name, 2: component class name */
						esc_html__( 'The template tag method %1$s registered by theme component %2$s conflicts with an already registered template tag of the same name.', 'buddyx' ),
						esc_html( $method_name ),
						esc_html( get_class( $component ) )
					)
				);
			}

			$this->template_tags[ $method_name ] = $callback;
		}
	}

	/**
	 * Gets the theme version.
	 *
	 * @return string Theme version number.
	 */
	public function get_version(): string {
		static $theme_version = null;

		if ( null === $theme_version ) {
			$theme_version = wp_get_theme( get_template() )->get( 'Version' );
		}

		return $theme_version;
	}

	/**
	 * Gets the version for a given asset.
	 *
	 * Returns filemtime when WP_DEBUG is true, otherwise the theme version.
	 *
	 * @param string $filepath Asset file path.
	 * @return string Asset version number.
	 */
	public function get_asset_version( string $filepath ): string {
		if ( WP_DEBUG && file_exists( $filepath ) ) {
			return (string) filemtime( $filepath );
		}

		return $this->get_version();
	}

	/**
	 * Gets a theme asset from the assets directory.
	 *
	 * @param string $filename The name of the asset file (with extension).
	 * @param string $type The asset type/subdirectory (e.g., 'images', 'svg').
	 * @param bool   $content Whether to return the file contents (true) or URL (false).
	 * @return string|null The asset URL/contents or null if not found.
	 *
	 * @throws RuntimeException If the asset file cannot be read.
	 */
	public function get_theme_asset( string $filename, string $type = 'images', bool $content = false ): ?string {
		// Constrain inputs to a single asset filename and a flat subdirectory to
		// prevent path traversal (e.g. "../../wp-config.php").
		$filename = basename( $filename );
		$type     = trim( $type, '/' );
		if ( '' === $filename || preg_match( '#[^A-Za-z0-9_\-]#', $type ) ) {
			return null;
		}

		$assets_root = wp_normalize_path( get_template_directory() . '/assets/' );
		$asset_path  = wp_normalize_path( $assets_root . $type . '/' . $filename );
		$asset_uri   = get_template_directory_uri() . '/assets/' . $type . '/' . $filename;

		// Final path must resolve inside the theme assets directory.
		if ( 0 !== strpos( $asset_path, $assets_root ) || ! file_exists( $asset_path ) ) {
			return null;
		}

		if ( $content ) {
			try {
				// Initialize WordPress Filesystem.
				global $wp_filesystem;
				if ( empty( $wp_filesystem ) ) {
					require_once ABSPATH . '/wp-admin/includes/file.php';
					WP_Filesystem();
				}

				if ( ! $wp_filesystem ) {
					throw new RuntimeException(
						esc_html__( 'WordPress filesystem is not initialized properly.', 'buddyx' )
					);
				}

				$file_contents = $wp_filesystem->get_contents( $asset_path );
				if ( false === $file_contents ) {
					throw new RuntimeException(
						sprintf(
						/* translators: %s: asset file path */
							esc_html__( 'Error reading asset file: %s', 'buddyx' ),
							esc_html( $asset_path )
						)
					);
				}
				return $file_contents;
			} catch ( \Exception $e ) {
				throw new RuntimeException(
					sprintf(
					/* translators: %s: error message */
						esc_html__( 'Failed to get asset contents: %s', 'buddyx' ),
						esc_html( $e->getMessage() )
					)
				);
			}
		}

		return $asset_uri;
	}
}
