<?php
/**
 * Compare button helper + compare-page hydrator (free tier).
 *
 * The compare bag lives in sessionStorage on the client; the server only
 * provides the button that toggles items into it and a public compare page
 * shell that JS hydrates with vehicle cards + a side-by-side spec table.
 *
 * Per ADR-09, REST writes are Pro. The free tier limits the bag to 4
 * items (also enforced client-side in frontend.js).
 *
 * @package ThemeREX\AutoDealer
 * @version 1.0.0
 */

declare(strict_types=1);

namespace ThemeREX\AutoDealer\Frontend;

defined( 'ABSPATH' ) || exit;
/**
 * Stateless compare button renderer + page shell.
 */
final class CompareHandler {

	public const MAX_ITEMS        = 4;
	public const SHORTCODE        = 'trx_ad_compare';
	public const SHORTCODE_HEADER = 'trx_ad_compare_link';

	/**
	 * Wire the global compare-bar render to wp_footer on plugin-relevant
	 * pages. The bar starts hidden; frontend.js shows it once the
	 * localStorage compare bag has > 0 items. Also register the
	 * `[trx_ad_compare]` shortcode for the compare-page surface.
	 */
	public function register(): void {
		add_action( 'wp_footer', array( $this, 'renderCompareBar' ), 20 );
		add_shortcode( self::SHORTCODE, array( $this, 'renderShortcode' ) );
		add_shortcode( self::SHORTCODE_HEADER, array( $this, 'renderHeaderLinkShortcode' ) );
	}

	/**
	 * Shortcode handler — emits the compare page shell (heading, grid,
	 * specs table). JS hydrates the grid from the localStorage bag.
	 *
	 * @param array<string,mixed>|string $atts
	 */
	public function renderShortcode( $atts = array() ): string {
		unset( $atts );
		// Escaped at the callback's return boundary -- WP outputs shortcode
		// return values directly (wp.org 2026-08 review round 4).
		return wp_kses( self::renderPage(), \ThemeREX\AutoDealer\allowedHtml() );
	}

	/**
	 * Echoes the compare-bar partial on plugin pages (vehicle archive,
	 * single-vehicle, compare page) and any page that already loads
	 * frontend.css/js. Hidden by default.
	 */
	public function renderCompareBar(): void {
		if (
			! is_singular( 'trx_ad_listing' )
			&& ! is_post_type_archive( 'trx_ad_listing' )
			&& ! is_tax( array( 'trx_ad_make', 'trx_ad_model', 'trx_ad_body_style', 'trx_ad_fuel_type', 'trx_ad_condition', 'trx_ad_vehicle_type' ) )
		) {
			return;
		}
		// Pass into the partial via the standard TemplateLoader contract.
		TemplateLoader::getTemplatePart( 'compare-bar', array( 'compare_url' => self::comparePageUrl() ) );
	}

	/**
	 * Resolve the compare-page URL once. Prefers a published page slugged
	 * `compare`, falls back to home + query var. Filterable via
	 * `trx_ad_compare_page_url`.
	 *
	 * @return string
	 */
	public static function comparePageUrl(): string {
		// 1. Auto-created/known compare page tracked in option.
		$page_id = (int) get_option( 'trx_ad_compare_page_id', 0 );
		if ( $page_id > 0 ) {
			$post = get_post( $page_id );
			if ( $post instanceof \WP_Post && 'publish' === $post->post_status ) {
				return (string) apply_filters( 'trx_ad_compare_page_url', (string) get_permalink( $page_id ) );
			}
		}
		// 2. Fallback to slug-match for sites that hand-built one.
		$page = get_page_by_path( 'compare-vehicles' );
		if ( ! ( $page instanceof \WP_Post ) ) {
			$page = get_page_by_path( 'compare' );
		}
		$default_url = $page instanceof \WP_Post && 'publish' === $page->post_status
			? (string) get_permalink( $page )
			: home_url( '/?trx_ad_compare=1' );
		return (string) apply_filters( 'trx_ad_compare_page_url', $default_url );
	}

	/**
	 * Render the small header compare-link badge — used by the Gutenberg
	 * block, Elementor widget, and `[trx_ad_compare_link]` shortcode. The
	 * `<span class="trx-ad-compare-count">` is the same selector
	 * `Compare.updateCount()` already targets, so the count auto-syncs
	 * with localStorage on every toggle without extra JS wiring.
	 *
	 * @param array<string,mixed> $atts {
	 *     Render options.
	 *
	 *     @type bool   $show_label    Show the "Compare" text label.
	 *     @type bool   $show_icon     Show the SVG icon.
	 *     @type bool   $hide_if_empty Hide the badge when the queue is empty.
	 *     @type string $label         Custom label text.
	 *     @type string $class         Extra CSS classes.
	 * }
	 * @return string
	 */
	public static function renderHeaderLink( array $atts = array() ): string {
		$args = wp_parse_args(
			$atts,
			array(
				'show_label'    => true,
				'show_icon'     => true,
				'hide_if_empty' => false,
				'label'         => __( 'Compare', 'trx-auto-dealer' ),
				'class'         => '',
			)
		);

		$class = 'trx-ad-compare-link';
		if ( ! empty( $args['class'] ) ) {
			$class .= ' ' . sanitize_html_class( (string) $args['class'] );
		}

		ob_start();
		?>
		<a class="<?php echo esc_attr( $class ); ?>"
			href="<?php echo esc_url( self::comparePageUrl() ); ?>"
			data-trx-ad-compare-link
			<?php echo $args['hide_if_empty'] ? 'data-trx-ad-compare-link-hide-empty' : ''; ?>
			aria-label="<?php echo esc_attr( (string) $args['label'] ); ?>">
			<?php if ( $args['show_icon'] ) : ?>
				<svg class="trx-ad-compare-link__icon" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
					<line x1="18" y1="20" x2="18" y2="10"/>
					<line x1="12" y1="20" x2="12" y2="4"/>
					<line x1="6"  y1="20" x2="6"  y2="14"/>
				</svg>
			<?php endif; ?>
			<?php if ( $args['show_label'] ) : ?>
				<span class="trx-ad-compare-link__label"><?php echo esc_html( (string) $args['label'] ); ?></span>
			<?php endif; ?>
			<span class="trx-ad-compare-count" aria-live="polite">0</span>
		</a>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Shortcode handler for `[trx_ad_compare_link]`.
	 *
	 * @param array<string,mixed>|string $atts
	 * @return string
	 */
	public function renderHeaderLinkShortcode( $atts = array() ): string {
		$atts = is_array( $atts ) ? $atts : array();
		$norm = array(
			'show_label'    => isset( $atts['show_label'] ) ? filter_var( $atts['show_label'], FILTER_VALIDATE_BOOLEAN ) : true,
			'show_icon'     => isset( $atts['show_icon'] ) ? filter_var( $atts['show_icon'], FILTER_VALIDATE_BOOLEAN ) : true,
			'hide_if_empty' => isset( $atts['hide_if_empty'] ) ? filter_var( $atts['hide_if_empty'], FILTER_VALIDATE_BOOLEAN ) : false,
			'label'         => isset( $atts['label'] ) ? (string) $atts['label'] : __( 'Compare', 'trx-auto-dealer' ),
			'class'         => isset( $atts['class'] ) ? (string) $atts['class'] : '',
		);
		return wp_kses( self::renderHeaderLink( $norm ), \ThemeREX\AutoDealer\allowedHtml() );
	}

	/**
	 * Render a compare-toggle button. JS reads `data-vehicle-id` and
	 * flips `is-active` on click via event delegation.
	 *
	 * @param int $post_id Vehicle ID.
	 * @return string
	 */
	public static function renderButton( int $post_id ): string {
		$markup = sprintf(
			'<button type="button"
				class="trx-ad-btn trx-ad-btn--icon trx-ad-card__action--compare"
				data-vehicle-id="%1$d"
				aria-label="%2$s">
				<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
					<line x1="18" y1="20" x2="18" y2="10"/>
					<line x1="12" y1="20" x2="12" y2="4"/>
					<line x1="6"  y1="20" x2="6"  y2="14"/>
				</svg>
				<span>%3$s</span>
			</button>',
			(int) $post_id,
			esc_attr__( 'Add to compare', 'trx-auto-dealer' ),
			esc_html__( 'Compare', 'trx-auto-dealer' )
		);

		/**
		 * Filter the compare button markup.
		 *
		 * @param string $markup  Default HTML.
		 * @param int    $post_id Vehicle ID.
		 */
		return (string) apply_filters( 'trx_ad_compare_button', $markup, $post_id );
	}

	/**
	 * Canonical key list for comparable spec fields. The ComparisonBar
	 * widget iterates this to render its toggles in a stable order; the JS
	 * hydrator iterates a (possibly subset) version to render rows.
	 *
	 * @return array<string, true>
	 */
	public static function compareFieldConfigKeys(): array {
		return array(
			'price'            => true,
			'sale_price'       => true,
			'year'             => true,
			'mileage'          => true,
			'body_style'       => true,
			'fuel_type'        => true,
			'transmission'     => true,
			'drivetrain'       => true,
			'condition_type'   => true,
			'doors'            => true,
			'seats'            => true,
			'color_ext'        => true,
			'color_int'        => true,
			'engine'           => true,
			'horsepower'       => true,
			'torque'           => true,
			'weight'           => true,
			'mpg_combined'     => true,
			'mpg_city'         => true,
			'mpg_highway'      => true,
			'co2_emissions'    => true,
			'battery_capacity' => true,
			'electric_range'   => true,
			'charge_time'      => true,
			'accident_free'    => true,
			'vin'              => true,
			'stock_number'     => true,
		);
	}

	/**
	 * Default field set used when the caller doesn't provide one. Order
	 * matters — that's the order rows will appear in the comparison table.
	 *
	 * @return array<int, string>
	 */
	public static function defaultFields(): array {
		return array(
			'price',
			'year',
			'mileage',
			'body_style',
			'fuel_type',
			'transmission',
			'drivetrain',
			'condition_type',
			'doors',
			'color_ext',
			'accident_free',
		);
	}

	/**
	 * Render the public compare page shell. JS hydrates the side-by-side
	 * grid + spec table from localStorage by fetching /vehicles/{id} for
	 * each ID. The `data-fields` attribute tells JS which spec rows to
	 * render and in what order.
	 *
	 * @param array<int, string> $fields  Spec keys to compare (in order).
	 *                                    Empty = default field set.
	 * @param string             $heading Optional heading rendered inside the
	 *                                    section (used by the block/Elementor
	 *                                    embeds). The shortcode page passes
	 *                                    none — its page title is the H1, and
	 *                                    rendering our own duplicated it
	 *                                    (favorites-page follows the same
	 *                                    pattern).
	 * @return string
	 */
	public static function renderPage( array $fields = array(), string $heading = '' ): string {
		$fields = ! empty( $fields ) ? array_values( array_filter( array_map( 'sanitize_key', $fields ) ) ) : self::defaultFields();

		ob_start();
		?>
		<section class="trx-ad-compare-page" data-trx-ad-compare-page data-max-items="<?php echo (int) self::MAX_ITEMS; ?>" aria-label="<?php esc_attr_e( 'Compare Vehicles', 'trx-auto-dealer' ); ?>">
			<?php if ( '' !== $heading ) : ?>
				<h2 class="trx-ad-compare-page__heading">
					<?php echo esc_html( $heading ); ?>
				</h2>
			<?php endif; ?>
			<p class="trx-ad-compare-page__empty" data-trx-ad-compare-empty hidden>
				<?php esc_html_e( 'Add vehicles to compare from any listing.', 'trx-auto-dealer' ); ?>
			</p>
			<div class="trx-ad-compare-page__specs-scroll">
				<table class="trx-ad-compare-page__specs" data-trx-ad-compare-specs data-fields="<?php echo esc_attr( implode( ',', $fields ) ); ?>">
					<tbody></tbody>
				</table>
			</div>
		</section>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Convenience: count visible items in a list of vehicle IDs.
	 * Used by the count badge (also localised for JS).
	 *
	 * @param int[] $ids
	 * @return int
	 */
	public static function visibleCount( array $ids ): int {
		$ids = array_unique( array_filter( array_map( 'intval', $ids ) ) );
		return min( count( $ids ), self::MAX_ITEMS );
	}
}
