<?php
/**
 * AbstractTheme
 *
 * Base class for all theme renderers.
 * Each concrete theme sets $template_file and optionally overrides template_vars().
 *
 * @package Relatedly\Theming
 * @since   2.0.0
 */

namespace Relatedly\Theming;

use Relatedly\Core\Options;
use Relatedly\Labels\LabelProvider;

defined( 'ABSPATH' ) || exit;

/**
 * Base renderer for all Relatedly themes.
 *
 * @since 2.0.0
 */
abstract class AbstractTheme {

	/**
	 * Path relative to plugin templates directory.
	 *
	 * @since 2.0.0
	 * @var string
	 */
	protected string $template_file = '';

	/**
	 * Plugin settings repository.
	 *
	 * @since 2.0.0
	 * @var Options
	 */
	protected Options $options;

	/**
	 * Label provider.
	 *
	 * @since 2.0.0
	 * @var LabelProvider
	 */
	protected LabelProvider $labels;

	/**
	 * Create a theme renderer.
	 *
	 * @since 2.0.0
	 * @param Options       $options Plugin settings repository.
	 * @param LabelProvider $labels  Label provider.
	 */
	public function __construct( Options $options, LabelProvider $labels ) {
		$this->options = $options;
		$this->labels  = $labels;
	}

	// -------------------------------------------------------------------------
	// Public API
	// -------------------------------------------------------------------------

	/**
	 * Render one or more posts using this theme's template.
	 *
	 * @param \WP_Post[] $posts     Posts to render.
	 * @param int|null   $source_id Source post ID (for tracking data attributes).
	 * @return string HTML output.
	 */
	public function render( array $posts, ?int $source_id = null ): string {
		if ( empty( $posts ) || '' === $this->template_file ) {
			return '';
		}
		$template = $this->locate_template( $this->template_file );
		if ( ! $template ) {
			return '';
		}
		$html = '';
		foreach ( $posts as $post ) {
			$html .= $this->load_template( $template, $this->template_vars( $post, $source_id ) );
		}
		return $html;
	}

	// -------------------------------------------------------------------------
	// Abstract / overridable
	// -------------------------------------------------------------------------

	/**
	 * Get the display_type value used for tracking attributes.
	 *
	 * @return string
	 */
	abstract protected function display_type(): string;

	/**
	 * Get the label shown by this theme.
	 *
	 * @return string
	 */
	protected function get_label(): string {
		return $this->labels->random_inline();
	}

	/**
	 * Build variables passed into the template.
	 * Concrete themes override this to add theme-specific vars (css_vars, etc.).
	 *
	 * Link and tracking values are passed as individual scalars so each attribute
	 * can be escaped at the point of output in the template.
	 *
	 * @since  2.0.0
	 * @param  \WP_Post $post      The post to render.
	 * @param  int|null $source_id Source post ID for tracking data attributes.
	 * @return array<string,mixed>
	 */
	protected function template_vars( \WP_Post $post, ?int $source_id ): array {
		return [
			'post'             => $post,
			'post_id'          => $post->ID,
			'source_post_id'   => $source_id,
			'title'            => get_the_title( $post->ID ),
			'permalink'        => get_permalink( $post->ID ),
			'excerpt'          => wp_trim_words( wp_strip_all_tags( '' !== $post->post_excerpt ? $post->post_excerpt : $post->post_content ), 20 ),
			'thumbnail_id'     => get_post_thumbnail_id( $post->ID ),
			'label'            => $this->get_label(),
			// Individual scalars — each attribute is escaped independently at output time.
			'link_target'      => (string) $this->options->get( 'link_target', '_self' ),
			'link_rel'         => (string) $this->options->get( 'link_rel', '' ),
			'tracking_post_id' => (int) $post->ID,
			'display_type'     => $this->display_type(),
			'options'          => $this->options,
		];
	}

	// -------------------------------------------------------------------------
	// Template loading
	// -------------------------------------------------------------------------

	/**
	 * Locate template — allows theme/child-theme overrides.
	 * Checks: child-theme/relatedly/ → theme/relatedly/ → plugin/templates/
	 *
	 * @param string $relative Template path relative to templates/.
	 * @return string|null
	 */
	protected function locate_template( string $relative ): ?string {
		$locations = [
			get_stylesheet_directory() . '/relatedly/' . $relative,
			get_template_directory() . '/relatedly/' . $relative,
			RELATEDLY_PLUGIN_DIR . 'templates/' . $relative,
		];
		foreach ( $locations as $path ) {
			if ( file_exists( $path ) ) {
				return $path;
			}
		}
		return null;
	}

	/**
	 * Load a template file with variables available in scope, return output.
	 *
	 * Variables are passed via a single $data array rather than extract(),
	 * which is banned by wp.org review guidelines.
	 * Templates access values as $data['key'].
	 *
	 * @param string              $template Absolute template path.
	 * @param array<string,mixed> $vars     Template variables.
	 * @return string
	 */
	protected function load_template( string $template, array $vars ): string {
		$data = $vars; // $data is the variable name templates use.
		ob_start();
		include $template;
		return (string) ob_get_clean();
	}

	/**
	 * Build a CSS custom-properties inline string from option keys.
	 * Templates use var(--relatedly-*) so user colours are set once on the wrapper.
	 *
	 * @param array<string,string> $map [ '--css-var' => 'option_key' ].
	 * @return string
	 */
	protected function css_vars( array $map ): string {
		$parts = [];
		foreach ( $map as $var => $key ) {
			$val = (string) $this->options->get( $key, '' );
			if ( '' !== $val ) {
				$parts[] = esc_attr( $var ) . ':' . esc_attr( $val );
			}
		}
		return implode( ';', $parts );
	}
}
