<?php
/**
 * Similar-vehicles finder.
 *
 * Returns up to N listing IDs that share salient attributes with a given
 * vehicle. Heuristic, in priority order:
 *
 *   1. Same body style + same condition (broad match), excluding self.
 *   2. Same make, any body style/condition.
 *   3. Same body style alone (last resort to fill the strip).
 *
 * Each layer top-up only runs if the previous layer underfilled. Results
 * are deduplicated and capped at $limit.
 *
 * Filterable via `trx_ad_similar_vehicles` for theme overrides.
 *
 * @package ThemeREX\AutoDealer
 * @version 1.0.0
 */

declare(strict_types=1);

namespace ThemeREX\AutoDealer\Frontend;

use ThemeREX\AutoDealer\PostTypes\VehiclePostType;

/**
 * Stateless similar-vehicles query helper.
 */
final class SimilarVehicles {

	/**
	 * @param int $post_id Reference vehicle.
	 * @param int $limit   Max IDs to return (1–12).
	 * @return int[]       Listing IDs in display order.
	 */
	public static function find( int $post_id, int $limit = 4 ): array {
		if ( $post_id <= 0 ) {
			return array();
		}

		$limit = max( 1, min( 12, $limit ) );

		global $wpdb;
		$specs = $wpdb->prefix . 'trx_ad_vehicle_specs';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$row = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT body_style, condition_type, make
				 FROM {$specs}
				 WHERE post_id = %d
				 LIMIT 1",
				$post_id
			),
			ARRAY_A
		);

		if ( ! is_array( $row ) ) {
			return array();
		}

		$body_style = (string) ( $row['body_style'] ?? '' );
		$condition  = (string) ( $row['condition_type'] ?? '' );
		$make       = (string) ( $row['make'] ?? '' );

		$collected = array();

		// Tier 1: same body style + same condition.
		if ( '' !== $body_style && '' !== $condition ) {
			$collected = self::querySpecs(
				array(
					'body_style'     => $body_style,
					'condition_type' => $condition,
				),
				$post_id,
				array(),
				$limit
			);
		}

		// Tier 2: same make.
		if ( count( $collected ) < $limit && '' !== $make ) {
			$more      = self::querySpecs(
				array( 'make' => $make ),
				$post_id,
				$collected,
				$limit - count( $collected )
			);
			$collected = array_merge( $collected, $more );
		}

		// Tier 3: same body style only.
		if ( count( $collected ) < $limit && '' !== $body_style ) {
			$more      = self::querySpecs(
				array( 'body_style' => $body_style ),
				$post_id,
				$collected,
				$limit - count( $collected )
			);
			$collected = array_merge( $collected, $more );
		}

		$result = array_slice( $collected, 0, $limit );

		/**
		 * Filter the resolved similar-vehicle IDs for a given listing.
		 *
		 * @param int[] $result  Vehicle post IDs.
		 * @param int   $post_id Reference vehicle.
		 * @param int   $limit   Requested max.
		 */
		$result = apply_filters( 'trx_ad_similar_vehicles', $result, $post_id, $limit );

		return array_values( array_filter( array_map( 'intval', (array) $result ) ) );
	}

	/**
	 * Query the specs table for matching listings, excluding seen IDs.
	 *
	 * @param array<string,string> $where     [column => value] equality matches.
	 * @param int                  $excludeId Reference listing to exclude.
	 * @param int[]                $exclude   Already-collected IDs.
	 * @param int                  $limit     Slot budget.
	 * @return int[]
	 */
	private static function querySpecs( array $where, int $excludeId, array $exclude, int $limit ): array {
		if ( $limit <= 0 ) {
			return array();
		}

		global $wpdb;
		$specs = $wpdb->prefix . 'trx_ad_vehicle_specs';

		$conditions = array(
			"p.post_type = '" . VehiclePostType::POST_TYPE . "'",
			"p.post_status = 'publish'",
			'p.ID <> %d',
		);
		$values     = array( $excludeId );

		foreach ( $where as $col => $val ) {
			$conditions[] = "s.{$col} = %s";
			$values[]     = $val;
		}

		$exclude = array_values( array_unique( array_filter( array_map( 'intval', $exclude ) ) ) );
		if ( ! empty( $exclude ) ) {
			$placeholders = implode( ',', array_fill( 0, count( $exclude ), '%d' ) );
			$conditions[] = "p.ID NOT IN ({$placeholders})";
			$values       = array_merge( $values, $exclude );
		}

		$where_sql = implode( ' AND ', $conditions );
		$values[]  = $limit;

		$sql = "SELECT p.ID
			FROM {$wpdb->posts} AS p
			INNER JOIN {$specs} AS s ON p.ID = s.post_id
			WHERE {$where_sql}
			ORDER BY p.post_date DESC
			LIMIT %d";

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$rows = $wpdb->get_col( $wpdb->prepare( $sql, ...$values ) );

		return is_array( $rows ) ? array_map( 'intval', $rows ) : array();
	}
}
