<?php
/*
Plugin Name: Automotive Inventory Importer – Sync Car Dealer Feeds
Plugin URI: https://www.ibexoft.com/product/automotive-feed-import/
Description: Automatically update your car inventory on your website. No manual entry needed. Stop wasting hours uploading cars one by one.
Version: 2.3
Author: Muhammad Jawaid Shamshad - Ibexoft
Author URI: https://ibexoft.com
License: GNU Public License
Requires PHP: 8.0
*/

if (!defined('ABSPATH')) {
	exit; // Exit if accessed directly
}

/**
 * A class to import vehicle data periodically and display in vehicles edit screen.
 * @author Muhammad Jawaid Shamshad
 *
 */
class AutomotiveFeedImport
{
	private $xml_file;
	private $log_file;
	private $plugin_slug = 'automotive-feed-import';
	private $survey_url = 'https://forms.gle/qEneb8ZeBxnFXuV78'; // Configurable survey URL
	private $rendering_vehicle_card = false;
	
	/**
	 * Constructor - PHP 8 compatible
	 */
	public function __construct()
	{
		// Get settings or use defaults
		$this->xml_file = $this->get_option('xml_file_path');
		
		// Set up log file
		$upload_dir = wp_upload_dir();
		$log_dir = $upload_dir['basedir'] . '/automotive-feed-import';
		if (!file_exists($log_dir)) {
			wp_mkdir_p($log_dir);
		}
		$this->log_file = $log_dir . '/import-log.txt';
	}
	
	/**
	 * Helper to get plugin options with default fallback
	 */
	private function get_option($key, $default = '') {
		return get_option($this->plugin_slug . '_' . $key, $default);
	}
	
	/**
	 * Helper to update plugin options
	 */
	private function update_option($key, $value) {
		return update_option($this->plugin_slug . '_' . $key, $value);
	}
	
	/**
	 * Log errors and messages
	 */
	private function log($message, $is_error = false) {
		$timestamp = current_time('mysql');
		$level = $is_error ? 'ERROR' : 'INFO';
		$log_entry = '[' . $timestamp . '] [' . $level . '] ' . $message . PHP_EOL;
		
		// Write to log file
		file_put_contents($this->log_file, $log_entry, FILE_APPEND);
		
		// Show admin notice for errors
		if ($is_error) {
			add_action('admin_notices', function() use ($message) {
				echo '<div class="notice notice-error is-dismissible"><p><strong>Automotive Inventory Importer Error:</strong> ' . esc_html($message) . '</p></div>';
			});
		}
	}
	
	/**
	 * Register custom post type 'vehicles'
	 */
	public function register_vehicles_post_type() {
		$labels = array(
			'name'               => 'Vehicles',
			'singular_name'      => 'Vehicle',
			'menu_name'          => 'Vehicles',
			'add_new'            => 'Add New',
			'add_new_item'       => 'Add New Vehicle',
			'edit_item'          => 'Edit Vehicle',
			'new_item'           => 'New Vehicle',
			'view_item'          => 'View Vehicle',
			'search_items'       => 'Search Vehicles',
			'not_found'          => 'No vehicles found',
			'not_found_in_trash' => 'No vehicles found in Trash',
		);

		$args = array(
			'labels'             => $labels,
			'public'             => true,
			'publicly_queryable' => true,
			'show_ui'            => true,
			'show_in_menu'       => true,
			'query_var'          => true,
			'rewrite'            => array('slug' => 'vehicles'),
			'capability_type'    => 'post',
			'has_archive'        => true,
			'hierarchical'       => false,
			'menu_position'      => 5,
			'menu_icon'          => 'dashicons-car',
			'supports'           => array('title', 'editor', 'thumbnail', 'custom-fields'),
		);

		register_post_type('vehicles', $args);
	}
	
	/**
	 * Migrate existing 'listing' posts to 'vehicles' post type
	 */
	public function migrate_listings_to_vehicles() {
		// Check if migration already done
		if ($this->get_option('migration_done')) {
			return;
		}
		
		global $wpdb;
		
		// Update post type from 'listing' to 'vehicles'
		$updated = $wpdb->update(
			$wpdb->posts,
			array('post_type' => 'vehicles'),
			array('post_type' => 'listing'),
			array('%s'),
			array('%s')
		);
		
		if ($updated !== false) {
			$this->update_option('migration_done', true);
			$this->log("Migrated {$updated} listings to vehicles post type");
		}
	}
	
	/**
	 * Initialize plugin
	 */
	public function init()
	{
		// Register post type
		$this->register_vehicles_post_type();
		
		// Migrate existing listings
		$this->migrate_listings_to_vehicles();
		
		// schedule an event
		$this->schedule();
		
		// Flush rewrite rules
		flush_rewrite_rules();
		
		// Set activation notice flag
		set_transient('afi_activation_notice', true, 60 * 60 * 24); // 24 hours
	}
	
	/**
	 * Display activation notice after plugin is activated
	 */
	public function display_activation_notice() {
		// Only show if activation flag is set
		if (!get_transient('afi_activation_notice')) {
			return;
		}

		// Only show to users who can manage options
		if (!current_user_can('manage_options')) {
			return;
		}

		// If this user already dismissed the notice, do not show again
		if (get_user_meta(get_current_user_id(), 'afi_activation_notice_dismissed', true)) {
			return;
		}

		?>
		<div class="notice notice-info is-dismissible" data-dismiss-type="activation">
			<p style="font-size: 16px;">
				<strong>Automotive Inventory Importer is ready!</strong>
				Ready to see your cars on your site?
				<a href="<?php echo esc_url( admin_url('options-general.php?page=' . $this->plugin_slug) ); ?>"
				   class="button button-primary" style="margin-left: 10px;">
					Start Your First Import
				</a>
			</p>
		</div>
		<?php
	}
	
	/**
	 * Display transient admin notices (used after redirects)
	 */
	public function display_transient_notices() {
		$notice = get_transient('afi_admin_notice');
		if ($notice) {
			$type = isset($notice['type']) ? $notice['type'] : 'info';
			$message = isset($notice['message']) ? $notice['message'] : '';
			
			if ($message) {
				echo '<div class="notice notice-' . esc_attr($type) . ' is-dismissible"><p>' . wp_kses_post($message) . '</p></div>';
			}
			
			// Delete transient so it only shows once
			delete_transient('afi_admin_notice');
		}
	}
	
	/**
	 * Uninitialize plugin
	 */
	public function uninit()
	{
		// clear all events
		$this->unschedule();
	}
	
	/**
	 * Clear schedule the plugin
	 */
	public function unschedule()
	{
		wp_clear_scheduled_hook('update_xml_event');
	}
	
	/**
	 * Reschedule the cron event with the current frequency setting
	 */
	public function reschedule() {
		$this->unschedule();
		$this->schedule();
	}

	/**
	 * Schedule the plugin
	 */
	public function schedule()
	{
		// Get frequency from settings (default 10 minutes)
		$frequency = $this->get_option('import_frequency', 'tenminute');
		
		// check if event is not defined, then schedule one
		if( !wp_next_scheduled( 'update_xml_event' )){
			wp_schedule_event( time(), $frequency, 'update_xml_event' );
		}
	}
	
	/**
	 * Defines custom intervals including 10 minute default
	 * @return Interval array  
	 */
	public function define_interval($schedules)
	{
		$schedules['tenminute'] = array(
		      'interval'=> 60*10,
		      'display'=>  __('Once Every 10 Minutes')
		  );
		$schedules['fiveminute'] = array(
		      'interval'=> 60*5,
		      'display'=>  __('Once Every 5 Minutes')
		  );
		$schedules['fifteenminute'] = array(
		      'interval'=> 60*15,
		      'display'=>  __('Once Every 15 Minutes')
		  );
		$schedules['thirtyminute'] = array(
		      'interval'=> 60*30,
		      'display'=>  __('Once Every 30 Minutes')
		  );
		  
		return $schedules;
	}

	/**
	 * Load the data from xml file and return as an array
	 * @return Data array on success, false on failure
	 */
	public function load_xml()
	{
		// Handle remote URL (http/https)
		if (preg_match('#^https?://#i', $this->xml_file)) {
			$response = wp_remote_get($this->xml_file, array('timeout' => 30));
			
			if (is_wp_error($response)) {
				$this->log("Failed to fetch remote XML: " . $response->get_error_message(), true);
				return false;
			}
			
			$code = wp_remote_retrieve_response_code($response);
			if ($code < 200 || $code >= 300) {
				$this->log("Remote XML returned error code: {$code}", true);
				return false;
			}
			
			$body = wp_remote_retrieve_body($response);
			if (empty($body)) {
				$this->log("Remote XML file is empty: {$this->xml_file}", true);
				return false;
			}
			
			libxml_use_internal_errors(true);
			$xml = simplexml_load_string($body);
			
			if (!$xml) {
				$error_msg = "Failed loading XML from {$this->xml_file}";
				$this->log($error_msg, true);
				
				foreach(libxml_get_errors() as $error) {
					$this->log("XML Error: " . $error->message, true);
				}
				libxml_clear_errors();
				
				return false;
			}
		} else {
			// Handle local file path
			if (!file_exists($this->xml_file)) {
				$this->log("XML file not found: {$this->xml_file}", true);
				return false;
			}
			
			// load the xml file
			$xml = simplexml_load_file($this->xml_file);

			if (!$xml) 
			{
			    $error_msg = "Failed loading XML from {$this->xml_file}";
			    $this->log($error_msg, true);
			    
			    foreach(libxml_get_errors() as $error)
			    {
			        $this->log("XML Error: " . $error->message, true);
			    }
			        
			    return false;
		    }
		}
	    
		$units = array();
		
		// loop through the xml and generate array to return
		foreach($xml->children() as $child)
		{
			$unit = array();
			
			foreach($child as $grand_child)
			{
				$unit[$grand_child->getName()] = strip_tags($grand_child->asXML());
			}
			
			array_push($units, $unit);
		}
		
		$this->log("Successfully loaded " . count($units) . " units from XML");
		return $units;
	}
	
	/**
	 * Detect whether the configured feed is a CSV file
	 */
	private function is_csv_feed() {
		return (bool) preg_match('/\.csv(\?.*)?$/i', $this->xml_file);
	}

	/**
	 * Load data from a CSV feed (remote URL or local path) and return as an array.
	 * The first row must be a header row whose values become the field names.
	 * @return array|false
	 */
	public function load_csv() {
		$raw = '';

		if (preg_match('#^https?://#i', $this->xml_file)) {
			$response = wp_remote_get($this->xml_file, array('timeout' => 30));

			if (is_wp_error($response)) {
				$this->log("Failed to fetch remote CSV: " . $response->get_error_message(), true);
				return false;
			}

			$code = wp_remote_retrieve_response_code($response);
			if ($code < 200 || $code >= 300) {
				$this->log("Remote CSV returned error code: {$code}", true);
				return false;
			}

			$raw = wp_remote_retrieve_body($response);
			if (empty($raw)) {
				$this->log("Remote CSV file is empty: {$this->xml_file}", true);
				return false;
			}
		} else {
			if (!file_exists($this->xml_file)) {
				$this->log("CSV file not found: {$this->xml_file}", true);
				return false;
			}

			$raw = file_get_contents($this->xml_file);
			if ($raw === false) {
				$this->log("Could not read CSV file: {$this->xml_file}", true);
				return false;
			}
		}

		// Normalise line endings
		$raw = str_replace("\r\n", "\n", str_replace("\r", "\n", $raw));

		// Strip UTF-8 BOM if present
		if (str_starts_with($raw, "\xEF\xBB\xBF")) {
			$raw = substr($raw, 3);
		}

		$lines = explode("\n", trim($raw));
		if (count($lines) < 2) {
			$this->log("CSV file has no data rows: {$this->xml_file}", true);
			return false;
		}

		$headers = str_getcsv(array_shift($lines));
		$headers = array_map('trim', $headers);

		$units = array();
		foreach ($lines as $line) {
			if (trim($line) === '') {
				continue;
			}

			$values = str_getcsv($line);
			// Pad or truncate to match header count
			$values = array_slice(array_pad($values, count($headers), ''), 0, count($headers));

			$unit = array();
			foreach ($headers as $index => $header) {
				if ($header !== '') {
					$unit[$header] = trim($values[$index]);
				}
			}

			$units[] = $unit;
		}

		$this->log("Successfully loaded " . count($units) . " units from CSV");
		return $units;
	}

	/**
	 * Parse post format template with tokens
	 */
	private function parse_template($template, $unit) {
		// Replace tokens like {manufacturer}, {brand}, etc.
		$replacements = array();
		foreach ($unit as $key => $value) {
			$replacements['{' . $key . '}'] = $value;
		}
		return str_replace(array_keys($replacements), array_values($replacements), $template);
	}
	
	/**
	 * Inserts data into database
	 * @param Associative array containing data to be inserted in database
	 * @return boolean: Post Id on success, false on failure
	 */
	public function add_listing($unit)
	{
		// check for valid unit
		if(!isset($unit))
		{
			$this->log("Invalid unit data provided", true);
			return false;
		}
		
		// Get custom formats from settings or use defaults
		$title_template = $this->get_option('post_title_format', '{manufacturer} {brand}');
		$content_template = $this->get_option('post_content_format', '{designation} {manufacturer} {brand} {model} {model_year}');
		
		// Parse templates
		$title = $this->parse_template($title_template, $unit);
		$content = $this->parse_template($content_template, $unit);
		
		// define post object
		$new_post = array(
				    'post_title' 	=> $title,
				    'post_content' 	=> $content,
				    'post_status' 	=> 'publish',
					'post_type' 	=> 'vehicles',
				);
				
		// insert the post into the database
		$post_id = wp_insert_post( $new_post, true );
		
		// check if post added successfully
		if( is_wp_error($post_id) )
		{
			// error, cannot insert post
			$error_msg = $post_id->get_error_message();
			$this->log("Failed to create vehicle: " . $error_msg, true);
			return false;
		}

		$this->log("Created new vehicle (ID: {$post_id}): " . $title);
		return $post_id;
	}

	/**
	 * Add/Update data in database
	 * @param Post Id to update
	 * @param Unit data to be updated
	 */
	public function update_inventory($post_id, $unit)
	{
		foreach ($unit as $key => $value) {
			update_post_meta($post_id, $key, $value);
			$this->update_theme_compatibility_field($post_id, $key, $value);
		}
	}
	
	/**
	 * Update theme compatibility fields
	 * @param int $post_id Post ID
	 * @param string $key Field key
	 * @param mixed $value Field value
	 */
	private function update_theme_compatibility_field($post_id, $key, $value) {
		switch ($key) {
			case 'manufacturer':
				update_post_meta($post_id, 'manufacturer_level2_value', $value);
				break;
			
			case 'model_year':
				update_post_meta($post_id, 'year_value', $value);
				break;
			
			case 'special_web_price':
				update_post_meta($post_id, 'price_value', $value);
				break;
			
			case 'mileage':
				update_post_meta($post_id, 'mileage_value', $value);
				break;
			
			case 'exterior_color':
				update_post_meta($post_id, 'color_value', $value);
				break;
		}
	}

	/**
	 * Import images from feed into Media Library and attach to vehicle
	 */
	private function process_images($post_id, $unit)
	{
		$image_urls = array();

		// Combined list fields (comma / pipe / whitespace separated)
		foreach (array('image_urls', 'images', 'photos') as $field) {
			if (!empty($unit[$field]) && is_string($unit[$field])) {
				$parts = preg_split('/[,\|\s]+/', $unit[$field]);
				foreach ($parts as $url) {
					$url = trim($url);
					if ($url !== '' && preg_match('#^https?://#i', $url)) {
						$image_urls[] = esc_url_raw($url);
					}
				}
			}
		}

		// Individual image/photo fields: image1, image_2, photo3, etc.
		foreach ($unit as $key => $value) {
			if (!is_string($value) || $value === '') {
				continue;
			}
			if (preg_match('/^(image|photo)[_\-]?\d*$/i', (string) $key) && preg_match('#^https?://#i', $value)) {
				$image_urls[] = esc_url_raw($value);
			}
		}

		$image_urls = array_values(array_unique($image_urls));

		if (empty($image_urls)) {
			// No images in feed: ensure placeholder is set if needed
			if (!has_post_thumbnail($post_id)) {
				$placeholder_id = $this->ensure_placeholder_attachment();
				if ($placeholder_id) {
					set_post_thumbnail($post_id, $placeholder_id);
				}
			}
			return;
		}

		// Avoid re-importing the same URLs
		$already_imported = get_post_meta($post_id, '_afi_imported_image_urls', true);
		if (!is_array($already_imported)) {
			$already_imported = array();
		}

		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';

		$new_imported = $already_imported;
		$index        = 0;

		foreach ($image_urls as $url) {
			if (in_array($url, $already_imported, true)) {
				$index++;
				continue;
			}

			// Use download_url + media_handle_sideload instead of media_sideload_image
			// so that URLs without a file extension (e.g. placehold.co, CDN tokens) are accepted.
			$tmp = download_url( $url );
			if ( is_wp_error( $tmp ) ) {
				$this->log("Failed to download image {$url} for post {$post_id}: " . $tmp->get_error_message(), true);
				$index++;
				continue;
			}

			// Build a filename with the correct extension.
			// When the URL path has no image extension, detect the actual MIME type
			// of the downloaded file so WordPress's filetype check doesn't reject it.
			$url_path = parse_url( $url, PHP_URL_PATH );
			$filename = $url_path ? basename( $url_path ) : '';
			if ( ! preg_match( '/\.(jpe?g|png|gif|webp)$/i', $filename ) ) {
				$mime_to_ext = array(
					'image/jpeg' => 'jpg',
					'image/png'  => 'png',
					'image/gif'  => 'gif',
					'image/webp' => 'webp',
				);
				$mime = wp_get_image_mime( $tmp );
				$ext  = isset( $mime_to_ext[ $mime ] ) ? $mime_to_ext[ $mime ] : 'jpg';
				$filename = ( $filename !== '' ? $filename : 'image' ) . '.' . $ext;
			}

			$file_array = array(
				'name'     => sanitize_file_name( $filename ),
				'tmp_name' => $tmp,
			);

			$attachment_id = media_handle_sideload( $file_array, $post_id );

			if ( is_wp_error( $attachment_id ) ) {
				@unlink( $tmp );
				$this->log("Failed to import image {$url} for post {$post_id}: " . $attachment_id->get_error_message(), true);
				$index++;
				continue;
			}

			$new_imported[] = $url;
			// First imported image becomes featured image if none set yet
			if ($index === 0 && !has_post_thumbnail($post_id)) {
				set_post_thumbnail($post_id, $attachment_id);
			}

			$index++;
		}

		update_post_meta($post_id, '_afi_imported_image_urls', array_values(array_unique($new_imported)));

		// If, for some reason, no thumbnail exists after processing, set placeholder
		if (!has_post_thumbnail($post_id)) {
			$placeholder_id = $this->ensure_placeholder_attachment();
			if ($placeholder_id) {
				set_post_thumbnail($post_id, $placeholder_id);
			}
		}
	}

	/**
	 * Ensure a reusable placeholder image exists in Media Library and return its attachment ID
	 */
	/**
	 * Title-case a display value that may arrive from a feed in ALL CAPS or all lower.
	 * Skips values that look like stock numbers, VINs, or codes (short all-caps tokens
	 * with digits are left alone).
	 */
	private function format_display_value( $value ) {
		$trimmed = trim( $value );
		if ( $trimmed === '' ) return $trimmed;

		// Leave it alone if it looks like a code: all-uppercase + contains digits (e.g. "VIN123", "ABC-4D")
		if ( preg_match( '/\d/', $trimmed ) ) {
			return $trimmed;
		}

		// mbstring is available on virtually all WP hosts; fall back to ucwords otherwise
		if ( function_exists( 'mb_convert_case' ) ) {
			return mb_convert_case( mb_strtolower( $trimmed ), MB_CASE_TITLE, 'UTF-8' );
		}
		return ucwords( strtolower( $trimmed ) );
	}

	private function ensure_placeholder_attachment()
	{
		// Try cached attachment ID first
		$cached_id = $this->get_option('placeholder_attachment_id');
		if ($cached_id) {
			$cached_id = (int) $cached_id;
			if ($cached_id > 0 && get_post($cached_id)) {
				return $cached_id;
			}
		}

		$placeholder_path = plugin_dir_path(__FILE__) . 'assets/car_placeholder.png';
		if (!file_exists($placeholder_path)) {
			$this->log('Placeholder image not found at ' . $placeholder_path, true);
			return 0;
		}

		require_once ABSPATH . 'wp-admin/includes/file.php';
		require_once ABSPATH . 'wp-admin/includes/media.php';
		require_once ABSPATH . 'wp-admin/includes/image.php';

		$upload_dir = wp_upload_dir();
		if (!empty($upload_dir['error'])) {
			$this->log('Failed to access upload directory for placeholder image: ' . $upload_dir['error'], true);
			return 0;
		}

		$filename   = 'car_placeholder.png';
		$dest_path  = trailingslashit($upload_dir['path']) . $filename;
		$dest_url   = trailingslashit($upload_dir['url']) . $filename;

		// Copy file into current uploads folder if it does not exist yet
		if (!file_exists($dest_path)) {
			if (!@copy($placeholder_path, $dest_path)) {
				$this->log('Failed to copy placeholder image into uploads directory.', true);
				return 0;
			}
		}

		$filetype = wp_check_filetype($filename, null);
		$attachment = array(
			'guid'           => $dest_url,
			'post_mime_type' => $filetype['type'],
			'post_title'     => 'Vehicle Placeholder Image',
			'post_content'   => '',
			'post_status'    => 'inherit',
		);

		$attach_id = wp_insert_attachment($attachment, $dest_path);
		if (is_wp_error($attach_id)) {
			$this->log('Failed to create placeholder attachment: ' . $attach_id->get_error_message(), true);
			return 0;
		}

		$attach_data = wp_generate_attachment_metadata($attach_id, $dest_path);
		wp_update_attachment_metadata($attach_id, $attach_data);

		$this->update_option('placeholder_attachment_id', $attach_id);

		return (int) $attach_id;
	}
	
	/**
	 * Fetches the data from xml then add/update database
	 */
	public function update_data()
	{
		global $wpdb;

		$this->log("Starting feed import process");

		// get the data from the feed (CSV or XML)
		$units = $this->is_csv_feed() ? $this->load_csv() : $this->load_xml();

		if ($units === false) {
			return; // Error already logged
		}

		$created = 0;
		$updated = 0;
		$feed_stock_numbers = array();

		foreach($units as $unit)
		{
			$unit = $this->apply_field_mappings($unit);

			if (!isset($unit['stock_number'])) {
				$this->log("Unit missing stock_number field. Check the Field Mapping tab to map your feed's unique ID field to 'stock_number'.", true);
				continue;
			}

			$feed_stock_numbers[] = $unit['stock_number'];

			// check if listing already exist, if not create new listing
			// Exclude trashed posts so they are not updated and don't block re-creation
			$post_id = $wpdb->get_var( $wpdb->prepare(
				"SELECT pm.post_id FROM $wpdb->postmeta pm
				 INNER JOIN $wpdb->posts p ON p.ID = pm.post_id
				 WHERE pm.meta_key = 'stock_number' AND pm.meta_value = %s
				 AND p.post_status != 'trash'",
				$unit['stock_number']
			));

			if( $post_id == NULL )
			{
				// listing does not exist, therefore, create a new listing
				$post_id = $this->add_listing($unit);
				if ($post_id) {
					$created++;
				}
			} else {
				$updated++;
			}

			// now add/update the plugin data against the listing
			if ($post_id) {
				$this->update_inventory($post_id, $unit);
				$this->process_images($post_id, $unit);
			}
		}

		// Handle vehicles that are no longer present in the feed
		$removed_action = $this->get_option('removed_vehicle_action', 'nothing');
		if ($removed_action !== 'nothing' && !empty($feed_stock_numbers)) {
			$removed = $this->handle_removed_vehicles($feed_stock_numbers, $removed_action);
			$this->log("Import complete: {$created} created, {$updated} updated, {$removed} removed from feed");
		} else {
			$this->log("Import complete: {$created} created, {$updated} updated");
		}

		// Remove activation banner after first successful import
		delete_transient('afi_activation_notice');

		// Track successful imports for review notice
		$import_count = get_option('afi_total_imports', 0);
		update_option('afi_total_imports', $import_count + 1);

		return array('created' => $created, 'updated' => $updated, 'success' => true);
	}

	/**
	 * Handle vehicles that are no longer present in the feed.
	 * @param array  $feed_stock_numbers Stock numbers seen in the current feed
	 * @param string $action             One of: draft, trash, sold
	 * @return int Number of vehicles acted upon
	 */
	public function handle_removed_vehicles(array $feed_stock_numbers, string $action): int
	{
		global $wpdb;

		// Find all active vehicle post IDs and their stock numbers
		$rows = $wpdb->get_results(
			"SELECT pm.post_id, pm.meta_value AS stock_number
			 FROM $wpdb->postmeta pm
			 INNER JOIN $wpdb->posts p ON p.ID = pm.post_id
			 WHERE pm.meta_key = 'stock_number'
			 AND p.post_type = 'vehicles'
			 AND p.post_status NOT IN ('trash', 'auto-draft')"
		);

		if (empty($rows)) {
			return 0;
		}

		$count = 0;
		foreach ($rows as $row) {
			if (in_array($row->stock_number, $feed_stock_numbers, true)) {
				continue; // Still in feed, skip
			}

			switch ($action) {
				case 'draft':
					wp_update_post(array('ID' => $row->post_id, 'post_status' => 'draft'));
					$this->log("Vehicle #{$row->post_id} (stock #{$row->stock_number}) removed from feed — marked as draft.");
					break;
				case 'trash':
					wp_trash_post($row->post_id);
					$this->log("Vehicle #{$row->post_id} (stock #{$row->stock_number}) removed from feed — moved to trash.");
					break;
				case 'sold':
					update_post_meta($row->post_id, 'status', 'sold');
					$this->log("Vehicle #{$row->post_id} (stock #{$row->stock_number}) removed from feed — status set to sold.");
					break;
			}
			$count++;
		}

		return $count;
	}

	/**
	 * Fetch the data from database for the current listing
	 * @return Associative array containing data from database 
	 */
	public function get_inventory()
	{
		$unit = array();
		
		// fetch the inventory from database for current listing
		$custom_fields = get_post_custom();

		// generate array to return
	  	$unit['stock_number'] 		= array('Stock Number', 		$custom_fields['stock_number'][0] ?? ''		);
	  	$unit['body_type'] 			= array('Body Type', 			$custom_fields['body_type_value'][0] ?? ''	);
	  	$unit['mileage'] 			= array('Mileage', 				$custom_fields['mileage'][0] ?? ''			);
	  	$unit['designation'] 		= array('Designation', 			$custom_fields['designation'][0] ?? ''		); 
	  	$unit['special_web_price'] 	= array('Special Web Price', 	$custom_fields['special_web_price'][0] ?? ''	); 
	  	$unit['type']  				= array('Type', 				$custom_fields['type'][0] ?? ''				);
	  	$unit['manufacturer'] 		= array('Manufacturer', 		$custom_fields['manufacturer'][0] ?? ''		);
	  	$unit['brand'] 				= array('Brand', 				$custom_fields['brand'][0] ?? ''				);
	  	$unit['model'] 				= array('Model', 				$custom_fields['model'][0] ?? ''				);
	  	$unit['length'] 			= array('Length', 				$custom_fields['length'][0] ?? ''				);
	  	$unit['color'] 				= array('Color', 				$custom_fields['exterior_color'][0] ?? ''		);
	  	$unit['price'] 				= array('Price', 				$custom_fields['price_value'][0] ?? ''		);
	  	$unit['status'] 			= array('Status', 				$custom_fields['status'][0] ?? ''				);
	  		    
	    return $unit;
	}

	/**
	 * Display data on listing post
	 * @param Array of array containing text and value to be displayed
	 */
	public function display_inventory($unit)
	{
		echo '<table class="form-table">';
  		
		// loop through each field and display
		foreach ($unit as $key => $val) 
		{
	  		echo '<tr><th scope="row">';
		  	echo '<label for="afi_field_' . esc_attr($key) . '">';
		       echo esc_html($val[0]);
		  	echo '</label>';
		  	echo '</th><td>';  	
		  	echo '<input type="text" class="regular-text" id="afi_field_' . esc_attr($key) . '" name="afi_field[' . esc_attr($key) . ']" value="' . esc_attr($val[1]) . '" />';
		  	echo '</td></tr>';
		}

	  	echo '</table>';
	}
	
	/**
	 * Save vehicle post meta data
	 */
	public function save_vehicle_meta($post_id) {
		// Check if this is an autosave
		if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
			return;
		}
		
		// Verify nonce
		if (!isset($_POST['afi_noncename']) || !wp_verify_nonce($_POST['afi_noncename'], plugin_basename(__FILE__))) {
			return;
		}
		
		// Check user permissions
		if (!current_user_can('edit_post', $post_id)) {
			return;
		}
		
		// Save the fields
		if (isset($_POST['afi_field']) && is_array($_POST['afi_field'])) {
			foreach ($_POST['afi_field'] as $key => $value) {
				$sanitized_value = sanitize_text_field($value);
				update_post_meta($post_id, $key, $sanitized_value);
				
				// Update theme compatibility fields
				switch ($key) {
					case 'manufacturer':
						update_post_meta($post_id, 'manufacturer_level2_value', $sanitized_value);
					break;
					case 'model_year':
						update_post_meta($post_id, 'year_value', $sanitized_value);
					break;
					case 'special_web_price':
						update_post_meta($post_id, 'price_value', $sanitized_value);
					break;
					case 'mileage':
						update_post_meta($post_id, 'mileage_value', $sanitized_value);
					break;
					case 'exterior_color':
						update_post_meta($post_id, 'color_value', $sanitized_value);
					break;
				}
			}
		}
	}

	/**
	 * Adds a box to the main column on the Vehicle edit screen
	 */
	public function add_custom_box()
	{
		add_meta_box( 
	        'afi_vehicle_info',
	        __( 'Vehicle Information (Imported from Feed)', 'automotive-feed-import' ),
	        array($this, 'inner_custom_box'),
	        'vehicles',
	        'normal',
	        'high'
	    );
	}

	/**
	 * Fetches the data from database and prints on the vehicle edit screen
	 */
	public function inner_custom_box() 
	{
		// Use nonce for verification
	  	wp_nonce_field( plugin_basename( __FILE__ ), 'afi_noncename' );
	
	  	// get the information from database
	  	$unit = $this->get_inventory();
	  	
  		// display the unit
  		$this->display_inventory($unit);
	}
	
	/**
	 * Register settings page
	 */
	public function add_settings_page() {
		add_options_page(
			'Automotive Inventory Importer Settings',
			'Automotive Inventory Importer',
			'manage_options',
			$this->plugin_slug,
			array($this, 'render_settings_page')
		);
	}
	
	/**
	 * Register settings
	 */
	public function register_settings() {
		// Register settings
		register_setting($this->plugin_slug . '_settings', $this->plugin_slug . '_xml_file_path');
		register_setting($this->plugin_slug . '_settings', $this->plugin_slug . '_import_frequency');
		register_setting($this->plugin_slug . '_settings', $this->plugin_slug . '_post_title_format');
		register_setting($this->plugin_slug . '_settings', $this->plugin_slug . '_post_content_format');
		register_setting($this->plugin_slug . '_settings', $this->plugin_slug . '_currency_symbol');
		register_setting($this->plugin_slug . '_settings', $this->plugin_slug . '_removed_vehicle_action');

		// General Settings Section
		add_settings_section(
			'afi_general_section',
			'Feed & Sync Settings',
			array($this, 'render_general_section'),
			$this->plugin_slug
		);
		
		// XML File Path
		add_settings_field(
			'xml_file_path',
			'Your Inventory Link',
			array($this, 'render_xml_path_field'),
			$this->plugin_slug,
			'afi_general_section'
		);
		
		// Import Frequency
		add_settings_field(
			'import_frequency',
			'How Often to Sync',
			array($this, 'render_frequency_field'),
			$this->plugin_slug,
			'afi_general_section'
		);
		
		// Removed Vehicle Action
		add_settings_field(
			'removed_vehicle_action',
			'When Vehicle is Removed from Feed',
			array($this, 'render_removed_vehicle_action_field'),
			$this->plugin_slug,
			'afi_general_section'
		);

		// Post Format Section
		add_settings_section(
			'afi_format_section',
			'How Vehicle Pages Look',
			array($this, 'render_format_section'),
			$this->plugin_slug
		);
		
		// Post Title Format
		add_settings_field(
			'post_title_format',
			'Vehicle Page Title Template',
			array($this, 'render_title_format_field'),
			$this->plugin_slug,
			'afi_format_section'
		);
		
		// Post Content Format
		add_settings_field(
			'post_content_format',
			'Vehicle Description Template',
			array($this, 'render_content_format_field'),
			$this->plugin_slug,
			'afi_format_section'
		);
		
		// Currency Symbol
		add_settings_field(
			'currency_symbol',
			'Currency Symbol',
			array($this, 'render_currency_symbol_field'),
			$this->plugin_slug,
			'afi_format_section'
		);
	}
	
	/**
	 * Extract available field names from the feed (XML or CSV)
	 * @return array|false Array of field names on success, false on failure
	 */
	public function extract_xml_fields() {
		if (empty($this->xml_file)) {
			return false;
		}

		// Load feed using the appropriate parser
		$units = $this->is_csv_feed() ? $this->load_csv() : $this->load_xml();
		
		if ($units === false || empty($units)) {
			return false;
		}
		
		// Get field names from first unit
		$first_unit = reset($units);
		$fields = array_keys($first_unit);
		
		return $fields;
	}
	
	/**
	 * Get available target field options for mapping
	 * @return array Target fields with labels
	 */
	public function get_available_target_fields() {
		return array(
			// Core vehicle fields
			'stock_number' => 'Stock Number',
			'vin' => 'VIN',
			'manufacturer' => 'Manufacturer',
			'brand' => 'Brand',
			'model' => 'Model',
			'model_year' => 'Year',
			'designation' => 'Condition/Designation',
			'type' => 'Type',
			'body_type' => 'Body Type',
			'style' => 'Style',
			
			// Pricing fields
			'special_web_price' => 'Special Web Price',
			'base_list' => 'Base List Price',
			'factory_list' => 'Factory List Price',
			'total_list' => 'Total List Price',
			'take_price' => 'Take Price',
			'show_web_price' => 'Show Web Price',
			
			// Physical attributes
			'mileage' => 'Mileage',
			'exterior_color' => 'Exterior Color',
			'interior_color' => 'Interior Color',
			'length' => 'Length',
			'width' => 'Width',
			'height' => 'Height',
			'weight' => 'Weight',
			
			// Identification
			'chassis_no' => 'Chassis Number',
			'serial_no' => 'Serial Number',
			
			// Status and location
			'status' => 'Status',
			'status_code' => 'Status Code',
			'lot_location' => 'Lot Location',
			'lot_location_code' => 'Lot Location Code',
			'gl_location_code' => 'GL Location Code',
			
			// Dates
			'received_date' => 'Received Date',
			'sold_date' => 'Sold Date',
			
			// Other
			'web_dealer_id' => 'Web Dealer ID',
			'description' => 'Description',
			'features' => 'Features',
			'options' => 'Options',
			'notes' => 'Notes',
			
			// Compatibility fields
			'manufacturer_level2_value' => 'Manufacturer (Theme Compat)',
			'year_value' => 'Year (Theme Compat)',
			'price_value' => 'Price (Theme Compat)',
			'mileage_value' => 'Mileage (Theme Compat)',
			'color_value' => 'Color (Theme Compat)',
			'body_type_value' => 'Body Type (Theme Compat)',
		);
	}
	
	/**
	 * Get saved field mappings or return default mappings
	 * @return array Field mappings
	 */
	public function get_field_mappings() {
		$mappings = $this->get_option('field_mappings', array());
		
		// If no mappings exist, return default 1:1 mapping
		if (empty($mappings)) {
			$fields = $this->extract_xml_fields();
			if ($fields !== false) {
				foreach ($fields as $field) {
					$mappings[$field] = array(
						'enabled' => true,
						'target' => $field
					);
				}
			}
		}
		
		return $mappings;
	}
	
	/**
	 * Save field mappings
	 * @param array $mappings Field mappings to save
	 */
	public function save_field_mappings($mappings) {
		$this->update_option('field_mappings', $mappings);
	}

	/**
	 * Apply field mappings to a raw unit, renaming source keys to target keys.
	 * Disabled fields are dropped. If no mappings are configured, the unit is returned as-is.
	 * @param array $unit Raw unit data from feed
	 * @return array Unit data with mapped field names
	 */
	public function apply_field_mappings($unit) {
		$mappings = $this->get_field_mappings();

		if (empty($mappings)) {
			return $unit;
		}

		$mapped = array();
		foreach ($unit as $source_field => $value) {
			if (!isset($mappings[$source_field]) || !$mappings[$source_field]['enabled']) {
				continue;
			}
			$target_field = $mappings[$source_field]['target'];
			$mapped[$target_field] = $value;
		}
		return $mapped;
	}
	
	/**
	 * Check if feed URL has changed
	 * @param string $new_url New feed URL
	 * @return bool True if changed, false otherwise
	 */
	public function has_feed_url_changed($new_url) {
		$old_url = $this->get_option('last_feed_url', '');
		return $old_url !== $new_url && !empty($new_url);
	}
	
	/**
	 * Check if field mapping is needed
	 * @return bool True if mapping needed, false otherwise
	 */
	public function needs_field_mapping() {
		$mappings = $this->get_option('field_mappings', array());
		$feed_url = $this->get_option('xml_file_path', '');
		$last_feed_url = $this->get_option('last_feed_url', '');
		
		// Need mapping if no mappings exist or feed URL changed
		return empty($mappings) || ($feed_url !== $last_feed_url && !empty($feed_url));
	}
	
	/**
	 * Render general section description
	 */
	public function render_general_section() {
		echo '<p>Tell us where your inventory feed lives and how often to sync it.</p>';
	}
	
	/**
	 * Render format section description
	 */
	public function render_format_section() {
		echo '<p>Control how vehicle titles and descriptions are built using tokens like {manufacturer}, {brand}, {model}, {model_year}, etc.</p>';
	}
	
	/**
	 * Render XML path field
	 */
	public function render_xml_path_field() {
		$value = $this->get_option('xml_file_path');
		echo '<input type="text" id="afi_xml_file_path" name="' . $this->plugin_slug . '_xml_file_path" value="' . esc_attr($value) . '" class="regular-text" placeholder="https://example.com/feed.xml" />';
		echo ' &nbsp;<span class="afi-input-divider" style="color:#999;font-size:12px;">— or —</span>&nbsp; ';
		echo '<button type="button" class="button" id="afi_browse_file" title="Use this to browse and select a feed file already uploaded to this server. Use a URL instead if your feed is hosted externally (e.g. a third-party provider or CDN).">Browse Server&hellip;</button>';
		echo '<p class="description"><strong>URL:</strong> Paste a remote feed URL (e.g. from your DMS or inventory provider). &nbsp;<strong>Browse Server:</strong> Pick a local file already uploaded to this WordPress site. CSV files must include a header row.</p>';
		$validation = get_transient('afi_feed_validation_' . get_current_user_id());
		if ($validation) {
			delete_transient('afi_feed_validation_' . get_current_user_id());
			$icon = $validation['type'] === 'success' ? '✓' : '✗';
			$color = $validation['type'] === 'success' ? '#00a32a' : '#d63638';
			echo '<p style="color:' . $color . ';margin-top:6px;"><strong>' . $icon . ' ' . esc_html($validation['message']) . '</strong></p>';
		}
		?>
		<script type="text/javascript">
		jQuery(document).ready(function($) {
			$('#afi_browse_file').on('click', function(e) {
				e.preventDefault();
				var currentPath = $('#afi_xml_file_path').val() || '<?php echo esc_js(ABSPATH); ?>';
				
				// Create modal
				var modal = $('<div id="afi-file-browser-modal" style="display:none;"><div id="afi-file-browser-content"></div></div>');
				$('body').append(modal);
				
				// Open modal with WordPress media library style
				$.post(ajaxurl, {
					action: 'afi_browse_files',
					path: currentPath,
					nonce: '<?php echo wp_create_nonce('afi_browse_files'); ?>'
				}, function(response) {
					if (response.success) {
						$('#afi-file-browser-content').html(response.data.html);
						
						// Show modal using WordPress thickbox style
						tb_show('Browse Server Files', '#TB_inline?inlineId=afi-file-browser-modal&width=600&height=400');
					}
				});
			});
			
			// Handle file/folder clicks
			$(document).on('click', '.afi-file-item', function(e) {
				e.preventDefault();
				var path = $(this).data('path');
				$('#afi_xml_file_path').val(path);
				tb_remove();
			});
			
			$(document).on('click', '.afi-folder-item', function(e) {
				e.preventDefault();
				var path = $(this).data('path');
				
				$.post(ajaxurl, {
					action: 'afi_browse_files',
					path: path,
					nonce: '<?php echo wp_create_nonce('afi_browse_files'); ?>'
				}, function(response) {
					if (response.success) {
						$('#afi-file-browser-content').html(response.data.html);
					}
				});
			});
		});
		</script>
		<style>
		#afi-file-browser-modal { padding: 20px; }
		.afi-file-list { list-style: none; padding: 0; margin: 10px 0; max-height: 400px; overflow-y: auto; border: 1px solid #ddd; background: #fff; }
		.afi-file-list li { padding: 8px 12px; border-bottom: 1px solid #f0f0f0; cursor: pointer; }
		.afi-file-list li:hover { background: #f0f0f0; }
		.afi-folder-item { color: #0073aa; font-weight: 600; }
		.afi-file-item { color: #333; }
		.afi-current-path { padding: 10px; background: #f5f5f5; margin-bottom: 10px; font-family: monospace; }
		</style>
		<?php
	}
	
	/**
	 * Render frequency field
	 */
	public function render_frequency_field() {
		$value = $this->get_option('import_frequency', 'tenminute');
		$frequencies = array(
			'fiveminute' => 'Every 5 Minutes',
			'tenminute' => 'Every 10 Minutes',
			'fifteenminute' => 'Every 15 Minutes',
			'thirtyminute' => 'Every 30 Minutes',
			'hourly' => 'Hourly',
			'twicedaily' => 'Twice Daily',
			'daily' => 'Daily'
		);
		
		echo '<select name="' . $this->plugin_slug . '_import_frequency">';
		foreach ($frequencies as $key => $label) {
			$selected = ($value === $key) ? 'selected' : '';
			echo '<option value="' . esc_attr($key) . '" ' . $selected . '>' . esc_html($label) . '</option>';
		}
		echo '</select>';
		echo '<p class="description">Choose how often we should check your feed for new or updated vehicles.</p>';
	}
	
	/**
	 * Render removed vehicle action field
	 */
	public function render_removed_vehicle_action_field() {
		$value = $this->get_option('removed_vehicle_action', 'nothing');
		$options = array(
			'nothing' => 'Do Nothing',
			'draft'   => 'Mark as Draft',
			'trash'   => 'Move to Trash',
			'sold'    => 'Change Status to Sold',
		);
		echo '<select name="' . $this->plugin_slug . '_removed_vehicle_action">';
		foreach ($options as $key => $label) {
			$selected = ($value === $key) ? 'selected' : '';
			echo '<option value="' . esc_attr($key) . '" ' . $selected . '>' . esc_html($label) . '</option>';
		}
		echo '</select>';
		echo '<p class="description">What to do with vehicle listings that are no longer present in the feed. "Change Status to Sold" sets the <code>status</code> meta field to <code>sold</code> and leaves the post published.</p>';
	}

	/**
	 * Render title format field
	 */
	public function render_title_format_field() {
		$value = $this->get_option('post_title_format', '{manufacturer} {brand}');
		echo '<input type="text" name="' . $this->plugin_slug . '_post_title_format" value="' . esc_attr($value) . '" class="regular-text" />';
		echo '<p class="description">Set the pattern for your vehicle page titles. Example: {manufacturer} {brand} {model_year}. Use any field from your feed inside {curly_braces}.</p>';
	}
	
	/**
	 * Render content format field
	 */
	public function render_content_format_field() {
		$value = $this->get_option('post_content_format', '{designation} {manufacturer} {brand} {model} {model_year}');
		echo '<textarea name="' . $this->plugin_slug . '_post_content_format" class="large-text" rows="3">' . esc_textarea($value) . '</textarea>';
		echo '<p class="description">Write the default description for each vehicle using tokens such as {designation}, {manufacturer}, {brand}, {model}, {model_year}, etc.</p>';
	}
	
	/**
	 * Render currency symbol field
	 */
	public function render_currency_symbol_field() {
		$value = $this->get_option('currency_symbol', '$');
		echo '<input type="text" name="' . $this->plugin_slug . '_currency_symbol" value="' . esc_attr($value) . '" class="small-text" maxlength="5" />';
		echo '<p class="description">Symbol shown before prices (e.g. $, &amp;pound;, &amp;euro;). Defaults to $.</p>';
	}

	/**
	 * Render settings page
	 */
	public function render_settings_page() {
		// Get active tab from URL or default to 'general'
		$active_tab = isset($_GET['tab']) ? sanitize_text_field($_GET['tab']) : 'general';
		?>
		<div class="wrap">
			<h1>Automotive Inventory Importer Settings</h1>
			
			<!-- Tab Navigation -->
			<h2 class="nav-tab-wrapper">
				<a href="?page=<?php echo $this->plugin_slug; ?>&tab=general" class="nav-tab <?php echo $active_tab === 'general' ? 'nav-tab-active' : ''; ?>">Feed & Sync</a>
				<a href="?page=<?php echo $this->plugin_slug; ?>&tab=mapping" class="nav-tab <?php echo $active_tab === 'mapping' ? 'nav-tab-active' : ''; ?>">Field Mapping</a>
				<a href="?page=<?php echo $this->plugin_slug; ?>&tab=format" class="nav-tab <?php echo $active_tab === 'format' ? 'nav-tab-active' : ''; ?>">Page Templates</a>
				<a href="?page=<?php echo $this->plugin_slug; ?>&tab=log" class="nav-tab <?php echo $active_tab === 'log' ? 'nav-tab-active' : ''; ?>">Import History</a>
			</h2>
			
			<div class="afi-tab-content">
				<div class="afi-settings-layout">
					<div class="afi-settings-main">
						<?php if ($active_tab === 'general'): ?>
							<!-- General Settings Tab -->
							<form method="post" action="options.php">
								<?php
								settings_fields($this->plugin_slug . '_settings');
								// Hidden fields to preserve format settings when saving general settings
								$title_format = $this->get_option('post_title_format', '{manufacturer} {brand}');
								$content_format = $this->get_option('post_content_format', '{designation} {manufacturer} {brand} {model} {model_year}');
								echo '<input type="hidden" name="' . $this->plugin_slug . '_post_title_format" value="' . esc_attr($title_format) . '" />';
								echo '<input type="hidden" name="' . $this->plugin_slug . '_post_content_format" value="' . esc_attr($content_format) . '" />';
						$currency_symbol_hidden = $this->get_option('currency_symbol', '$');
						echo '<input type="hidden" name="' . $this->plugin_slug . '_currency_symbol" value="' . esc_attr($currency_symbol_hidden) . '" />';
								?>
								<table class="form-table">
									<?php
									// Manually render general settings fields
									do_settings_fields($this->plugin_slug, 'afi_general_section');
									?>
								</table>
								<?php submit_button(); ?>
								<p>
									<button type="button" class="button button-primary" onclick="if(confirm('Run a fresh import now?')) { window.location.href='<?php echo admin_url('options-general.php?page=' . $this->plugin_slug . '&tab=general&action=run_import&_wpnonce=' . wp_create_nonce('run_import')); ?>'; }">Sync Inventory Now</button>
									<a href="<?php echo admin_url('options-general.php?page=' . $this->plugin_slug . '&action=download_sample&_wpnonce=' . wp_create_nonce('download_sample')); ?>" class="button" style="margin-left: 8px;">Download Sample Feed</a>
									<a href="<?php echo admin_url('options-general.php?page=' . $this->plugin_slug . '&tab=mapping'); ?>" class="button" style="margin-left: 8px;">Configure Field Mapping</a>
								</p>
							</form>
							
						<?php elseif ($active_tab === 'format'): ?>
							<!-- Post Format Tab -->
							<form method="post" action="options.php">
								<?php
								settings_fields($this->plugin_slug . '_settings');
								// Hidden fields to preserve general settings when saving format settings
								$xml_file_path = $this->get_option('xml_file_path');
								$import_frequency = $this->get_option('import_frequency', 'tenminute');
								echo '<input type="hidden" name="' . $this->plugin_slug . '_xml_file_path" value="' . esc_attr($xml_file_path) . '" />';
								echo '<input type="hidden" name="' . $this->plugin_slug . '_import_frequency" value="' . esc_attr($import_frequency) . '" />';
								$removed_vehicle_action = $this->get_option('removed_vehicle_action', 'nothing');
								echo '<input type="hidden" name="' . $this->plugin_slug . '_removed_vehicle_action" value="' . esc_attr($removed_vehicle_action) . '" />';
								?>
								<h3>How Vehicle Pages Look</h3>
								<p>Decide how titles and descriptions should be built from your feed fields, using tokens like {manufacturer}, {brand}, {model}, {model_year}, etc.</p>
								<table class="form-table">
									<?php
									// Manually render format settings fields
									do_settings_fields($this->plugin_slug, 'afi_format_section');
									?>
								</table>
								<?php submit_button(); ?>
							</form>
							
						<?php elseif ($active_tab === 'mapping'): ?>
							<!-- Field Mapping Tab -->
							<h2>Field Mapping</h2>
							<p>Map fields from your feed to vehicle post meta fields. Enable or disable fields and select the target meta field from the dropdown for each feed field. To import vehicles that use a different field name for the unique ID (e.g. <code>StockNo</code>), map that field to <strong>Stock Number (stock_number)</strong>.</p>
							
							<?php
							// Show notification if redirected after feed URL change
							if (isset($_GET['feed_changed']) && $_GET['feed_changed'] === '1') {
								echo '<div class="notice notice-info is-dismissible"><p><strong>Feed URL updated!</strong> Please review and configure your field mappings below, then save them to start importing vehicles.</p></div>';
							}
							
							// Check if feed URL is set
							$feed_url = $this->get_option('xml_file_path', '');
							if (empty($feed_url)) {
								echo '<div class="notice notice-warning"><p><strong>No feed URL configured.</strong> Please configure your inventory link on the <a href="?page=' . $this->plugin_slug . '&tab=general">Feed & Sync</a> tab first.</p></div>';
							} else {
								// Extract fields from XML
								$xml_fields = $this->extract_xml_fields();
								$current_mappings = $this->get_field_mappings();

								// Warn if no enabled field maps to stock_number
								$has_stock_number_target = false;
								foreach ($current_mappings as $cfg) {
									if (!empty($cfg['enabled']) && $cfg['target'] === 'stock_number') {
										$has_stock_number_target = true;
										break;
									}
								}
								if (!$has_stock_number_target) {
									echo '<div class="notice notice-error"><p><strong>⚠ No field is mapped to Stock Number (stock_number).</strong> Every vehicle requires a unique stock number. Set the Target for your unique ID field (e.g. <code>StockNo</code>) to <strong>Stock Number (stock_number)</strong> and save.</p></div>';
								}

								if ($xml_fields === false) {
									echo '<div class="notice notice-error"><p><strong>Could not load XML feed.</strong> Please verify your feed URL is correct and accessible.</p></div>';
								} else {
									?>
									<form method="post" action="<?php echo admin_url('admin-post.php'); ?>">
										<input type="hidden" name="action" value="afi_save_field_mappings" />
										<?php wp_nonce_field('afi_save_field_mappings', 'afi_mapping_nonce'); ?>
										
										<div style="background: #f0f0f1; padding: 15px; margin: 20px 0; border-left: 4px solid #0073aa;">
											<p style="margin: 0;"><strong>💡 Tip:</strong> Found <strong><?php echo count($xml_fields); ?> fields</strong> in your feed. Enable the ones you want to import and select a target meta field from the dropdown for each.</p>
										</div>
										
										<table class="wp-list-table widefat fixed striped" style="margin-top: 20px;">
											<thead>
												<tr>
													<th style="width: 60px;">Enable</th>
													<th style="width: 40%;">Feed Field (Source)</th>
													<th style="width: 40%;">Target Meta Field</th>
													<th style="width: 200px;">Sample Value</th>
												</tr>
											</thead>
											<tbody>
												<?php
												// Load first unit to show sample values
												$units = $this->is_csv_feed() ? $this->load_csv() : $this->load_xml();
												$sample_unit = !empty($units) ? reset($units) : array();
												
												// Get available target fields
												$available_targets = $this->get_available_target_fields();
												
												foreach ($xml_fields as $field):
													$is_enabled = isset($current_mappings[$field]['enabled']) ? $current_mappings[$field]['enabled'] : true;
													$target_field = isset($current_mappings[$field]['target']) ? $current_mappings[$field]['target'] : $field;
													$sample_value = isset($sample_unit[$field]) ? $sample_unit[$field] : '';
													
													// Truncate long sample values
													if (strlen($sample_value) > 50) {
														$sample_value = substr($sample_value, 0, 50) . '...';
													}
												?>
												<tr>
													<td>
														<input type="checkbox" name="afi_mapping[<?php echo esc_attr($field); ?>][enabled]" value="1" <?php checked($is_enabled, true); ?> />
													</td>
													<td>
														<strong><?php echo esc_html($field); ?></strong>
													</td>
													<td>
														<select name="afi_mapping[<?php echo esc_attr($field); ?>][target]" class="regular-text" style="width: 100%;">
															<?php foreach ($available_targets as $target_key => $target_label): ?>
																<option value="<?php echo esc_attr($target_key); ?>" <?php selected($target_field, $target_key); ?>>
																	<?php echo esc_html($target_label); ?> (<?php echo esc_html($target_key); ?>)
																</option>
															<?php endforeach; ?>
															<!-- Add current field if not in list -->
															<?php if (!isset($available_targets[$field])): ?>
																<option value="<?php echo esc_attr($field); ?>" <?php selected($target_field, $field); ?>>
																	<?php echo esc_html($field); ?> (custom)
																</option>
															<?php endif; ?>
															<!-- Add selected field if different and not in list -->
															<?php if ($target_field !== $field && !isset($available_targets[$target_field])): ?>
																<option value="<?php echo esc_attr($target_field); ?>" selected>
																	<?php echo esc_html($target_field); ?> (custom)
																</option>
															<?php endif; ?>
														</select>
													</td>
													<td>
														<code style="font-size: 11px; color: #666;"><?php echo esc_html($sample_value); ?></code>
													</td>
												</tr>
												<?php endforeach; ?>
											</tbody>
										</table>
										
										<?php submit_button('Save Field Mappings'); ?>
										
										<p style="margin-top: 20px;">
											<button type="button" class="button" onclick="if(confirm('Reset all mappings to default (1:1 mapping)?')) { document.getElementById('afi-reset-mappings').value='1'; this.form.submit(); }">Reset to Defaults</button>
											<input type="hidden" id="afi-reset-mappings" name="afi_reset_mappings" value="0" />
										</p>
									</form>
									<?php
								}
							}
							?>
							
						<?php elseif ($active_tab === 'log'): ?>
							<!-- Import Log Tab -->
							<h2>Import History</h2>
							<?php $this->display_log(); ?>
							<p style="margin-top: 15px;">
								<button type="button" class="button" onclick="if(confirm('Clear all saved import history?')) { window.location.href='<?php echo admin_url('options-general.php?page=' . $this->plugin_slug . '&tab=log&action=clear_log&_wpnonce=' . wp_create_nonce('clear_log')); ?>'; }">Clear History</button>
							</p>
						<?php endif; ?>
					</div>
					<div class="afi-settings-sidebar">
						<div style="background: #fff; border: 1px solid #ccd0d4; padding: 15px; margin-top: 0; border-left: 4px solid #0073aa;">
							<h3 style="margin-top: 0;">🚀 Quick Start Guide</h3>
							<ol>
								<li><strong>Enter your Inventory Link:</strong> Paste the URL or server path to your XML vehicle feed on the <strong>Feed &amp; Sync</strong> tab.</li>
								<li><strong>Configure Field Mapping:</strong> After saving your feed URL, map which fields to import on the <strong>Field Mapping</strong> tab.</li>
								<li><strong>Choose how often to sync:</strong> Pick a schedule that matches how often your provider updates the feed, then click <strong>Save Changes</strong>.</li>
								<li><strong>Sync &amp; review:</strong> Click <strong>Sync Inventory Now</strong> to run your first import, then review your vehicles under the <strong>Vehicles</strong> menu.</li>
							</ol>
							<p><em>Not sure about your link? Check your inventory provider's documentation or portal for your "XML Feed" or "Inventory Export" URL.</em></p>
						</div>
					</div>
				</div>
			</div>
			
			<style>
				.afi-tab-content {
					background: #fff;
					padding: 20px;
					border: 1px solid #ccd0d4;
					border-top: none;
					margin-top: 0;
				}
				.nav-tab-wrapper {
					margin-bottom: 0 !important;
				}
				.afi-settings-layout {
					display: flex;
					gap: 20px;
					align-items: flex-start;
					flex-wrap: wrap;
				}
				.afi-settings-main {
					flex: 1 1 0;
					min-width: 0;
				}
				.afi-settings-sidebar {
					flex: 0 0 280px;
					max-width: 100%;
				}
				@media (max-width: 900px) {
					.afi-settings-layout {
						flex-direction: column;
					}
					.afi-settings-sidebar {
						flex: 1 1 auto;
					}
				}
			</style>
		</div>
		<?php
	}
	
	/**
	 * Display import log with summary stats and color-coded errors
	 */
	private function display_log() {
		if ( ! file_exists( $this->log_file ) ) {
			echo '<p>No log file found.</p>';
			return;
		}

		$log_content = file_get_contents( $this->log_file );
		if ( empty( trim( $log_content ) ) ) {
			echo '<p>Log is empty.</p>';
			return;
		}

		$lines = explode( "\n", rtrim( $log_content ) );

		// --- Compute summary stats ---
		$total_runs       = 0;
		$last_run_status  = 'Unknown';
		$last_vehicles    = null;
		$last_start_index = null;
		$last_complete_index = null;

		foreach ( $lines as $i => $line ) {
			if ( strpos( $line, 'Starting feed import process' ) !== false ) {
				$last_start_index = $i;
			}
			if ( strpos( $line, 'Import complete:' ) !== false ) {
				$total_runs++;
				$last_complete_index = $i;
				// Parse vehicle counts: "X created, Y updated" or "X created, Y updated, Z removed"
				if ( preg_match( '/(\d+) created,\s*(\d+) updated/', $line, $m ) ) {
					$last_vehicles = (int) $m[1] + (int) $m[2];
				}
			}
		}

		// Determine last run status
		if ( $last_start_index !== null ) {
			if ( $last_complete_index !== null && $last_complete_index >= $last_start_index ) {
				// Check for errors between last start and last complete
				$has_error = false;
				for ( $i = $last_start_index; $i <= $last_complete_index; $i++ ) {
					if ( strpos( $lines[ $i ], '] [ERROR]' ) !== false ) {
						$has_error = true;
						break;
					}
				}
				$last_run_status = $has_error ? 'Completed with errors' : 'Success';
			} else {
				$last_run_status = 'Failed';
			}
		} elseif ( $total_runs > 0 ) {
			$last_run_status = 'Success';
		}

		// Status color
		$status_colors = array(
			'Success'                => '#00a32a',
			'Completed with errors'  => '#d63638',
			'Failed'                 => '#d63638',
			'Unknown'                => '#757575',
		);
		$status_color = isset( $status_colors[ $last_run_status ] ) ? $status_colors[ $last_run_status ] : '#757575';

		// --- Render summary cards ---
		?>
		<div style="display:flex; gap:12px; flex-wrap:wrap; margin-bottom:16px;">
			<div style="background:#fff; border:1px solid #ccd0d4; border-left:4px solid #0073aa; padding:12px 18px; min-width:120px;">
				<div style="font-size:22px; font-weight:700; color:#0073aa; line-height:1;"><?php echo esc_html( $total_runs ); ?></div>
				<div style="font-size:12px; color:#555; margin-top:4px;">Total Runs</div>
			</div>
			<div style="background:#fff; border:1px solid #ccd0d4; border-left:4px solid <?php echo esc_attr( $status_color ); ?>; padding:12px 18px; min-width:160px;">
				<div style="font-size:16px; font-weight:700; color:<?php echo esc_attr( $status_color ); ?>; line-height:1.2;"><?php echo esc_html( $last_run_status ); ?></div>
				<div style="font-size:12px; color:#555; margin-top:4px;">Last Run Status</div>
			</div>
			<div style="background:#fff; border:1px solid #ccd0d4; border-left:4px solid #0073aa; padding:12px 18px; min-width:120px;">
				<div style="font-size:22px; font-weight:700; color:#0073aa; line-height:1;"><?php echo $last_vehicles !== null ? esc_html( $last_vehicles ) : '&mdash;'; ?></div>
				<div style="font-size:12px; color:#555; margin-top:4px;">Vehicles (Last Run)</div>
			</div>
		</div>
		<?php

		// --- Render log lines with errors in red ---
		echo '<pre style="margin:0; white-space:pre-wrap; font-size:12px; line-height:1.6;">';
		foreach ( $lines as $line ) {
			if ( strpos( $line, '] [ERROR]' ) !== false ) {
				echo '<span style="color:#d63638; font-weight:600;">' . esc_html( $line ) . '</span>' . "\n";
			} else {
				echo esc_html( $line ) . "\n";
			}
		}
		echo '</pre>';
	}
	
	/**
	 * Test XML feed connection and show an admin notice
	 */
	private function test_connection() {
		$path = $this->get_option('xml_file_path');

		if (empty($path)) {
			return array( 'type' => 'error', 'message' => 'No feed URL or path is saved yet.' );
		}

		$is_csv = (bool) preg_match('/\.csv(\?.*)?$/i', $path);

		// Remote URL (http/https)
		if (preg_match('#^https?://#i', $path)) {
			$response = wp_remote_get($path, array('timeout' => 15));

			if (is_wp_error($response)) {
				return array( 'type' => 'error', 'message' => 'We could not reach your feed URL: ' . $response->get_error_message() . '.' );
			}

			$code = wp_remote_retrieve_response_code($response);
			if ($code < 200 || $code >= 300) {
				return array( 'type' => 'error', 'message' => 'Your feed URL responded with an error code: ' . intval($code) . '.' );
			}

			$body = wp_remote_retrieve_body($response);
			if ($body === '') {
				return array( 'type' => 'error', 'message' => 'Your feed URL responded, but the file was empty.' );
			}

			if (!$is_csv) {
				libxml_use_internal_errors(true);
				$xml = simplexml_load_string($body);
				if ($xml === false) {
					$errors  = libxml_get_errors();
					libxml_clear_errors();
					$first   = reset($errors);
					$err_msg = $first ? trim($first->message) : 'Unknown XML error.';
					return array( 'type' => 'error', 'message' => 'We reached your feed URL, but the XML is not valid: ' . $err_msg . '.' );
				}
			}
		} else {
			// Local file path
			if (!file_exists($path)) {
				return array( 'type' => 'error', 'message' => 'We could not find a feed file at this path: ' . $path . '.' );
			}

			if (!is_readable($path)) {
				return array( 'type' => 'error', 'message' => 'The feed file exists but cannot be read. Please check permissions on: ' . $path . '.' );
			}

			if (!$is_csv) {
				libxml_use_internal_errors(true);
				$xml = simplexml_load_file($path);
				if ($xml === false) {
					$errors  = libxml_get_errors();
					libxml_clear_errors();
					$first   = reset($errors);
					$err_msg = $first ? trim($first->message) : 'Unknown XML error.';
					return array( 'type' => 'error', 'message' => 'We found your feed file, but the XML is not valid: ' . $err_msg . '.' );
				}
			}
		}

		$type = $is_csv ? 'CSV' : 'XML';
		return array( 'type' => 'success', 'message' => 'Connected successfully and read the ' . $type . ' feed.' );
	}
	
	/**
	 * Handle admin actions
	 */
	public function handle_admin_actions() {
		if (!isset($_GET['page']) || $_GET['page'] !== $this->plugin_slug) {
			return;
		}
		
		if (!isset($_GET['action'])) {
			// Check if we should redirect to field mapping after settings save
			if (isset($_GET['settings-updated']) && $_GET['settings-updated'] === 'true') {
				// Auto-validate the feed URL on every save
				$validation = $this->test_connection();
				if ($validation !== null) {
					set_transient('afi_feed_validation_' . get_current_user_id(), $validation, 60);
				}

				$current_feed_url = $this->get_option('xml_file_path', '');
				$last_feed_url = $this->get_option('last_feed_url', '');

				// If feed URL changed, not empty, and validation succeeded, redirect to mapping tab
				if (!empty($current_feed_url) && $current_feed_url !== $last_feed_url && isset($validation['type']) && $validation['type'] === 'success') {
					$this->update_option('last_feed_url', $current_feed_url);
					wp_redirect(admin_url('options-general.php?page=' . $this->plugin_slug . '&tab=mapping&feed_changed=1'));
					exit;
				}
			}
			return;
		}

		$action = sanitize_text_field($_GET['action']);
		$tab    = isset($_GET['tab']) ? '&tab=' . sanitize_text_field($_GET['tab']) : '';
		
		// Clear log
		if ($action === 'clear_log' && check_admin_referer('clear_log')) {
			if (file_exists($this->log_file)) {
				file_put_contents($this->log_file, '');
				set_transient('afi_admin_notice', array(
					'type' => 'success',
					'message' => 'Log cleared successfully.'
				), 30);
			}
			wp_redirect(admin_url('options-general.php?page=' . $this->plugin_slug . $tab));
			exit;
		}
		
		// Run import
		if ($action === 'run_import' && check_admin_referer('run_import')) {
			$result = $this->update_data();
			if ($result && $result['success']) {
				set_transient('afi_admin_notice', array(
					'type' => 'success',
					'message' => '<strong>Import Successful!</strong> ' . $result['created'] . ' vehicles created, ' . $result['updated'] . ' updated.'
				), 30);
			} else {
				set_transient('afi_admin_notice', array(
					'type' => 'error',
					'message' => '<strong>Import Failed!</strong> Check the log below for details.'
				), 30);
			}
			wp_redirect(admin_url('options-general.php?page=' . $this->plugin_slug . $tab));
			exit;
		}
		
		// Download sample XML
		if ($action === 'download_sample' && check_admin_referer('download_sample')) {
			$sample_file = plugin_dir_path(__FILE__) . 'Web_Inventory_999.xml';
			if (file_exists($sample_file)) {
				header('Content-Type: application/xml');
				header('Content-Disposition: attachment; filename="sample-vehicle-feed.xml"');
				header('Content-Length: ' . filesize($sample_file));
				readfile($sample_file);
				exit;
			}
		}
	}
	
	/**
	 * Handle field mapping save
	 */
	public function handle_field_mapping_save() {
		// Verify nonce
		if (!isset($_POST['afi_mapping_nonce']) || !wp_verify_nonce($_POST['afi_mapping_nonce'], 'afi_save_field_mappings')) {
			wp_die('Security check failed');
		}
		
		// Check user permissions
		if (!current_user_can('manage_options')) {
			wp_die('Unauthorized access');
		}
		
		// Check if reset was requested
		if (isset($_POST['afi_reset_mappings']) && $_POST['afi_reset_mappings'] === '1') {
			// Delete existing mappings to trigger default generation
			delete_option($this->plugin_slug . '_field_mappings');
			
			set_transient('afi_admin_notice', array(
				'type' => 'success',
				'message' => 'Field mappings reset to defaults successfully.'
			), 30);
			
			wp_redirect(admin_url('options-general.php?page=' . $this->plugin_slug . '&tab=mapping'));
			exit;
		}
		
		// Process mappings
		$mappings = array();
		if (isset($_POST['afi_mapping']) && is_array($_POST['afi_mapping'])) {
			foreach ($_POST['afi_mapping'] as $source_field => $config) {
				$source_field = sanitize_text_field($source_field);
				$target_field = isset($config['target']) ? sanitize_text_field($config['target']) : $source_field;
				$enabled = isset($config['enabled']) && $config['enabled'] === '1';
				
				$mappings[$source_field] = array(
					'enabled' => $enabled,
					'target' => $target_field
				);
			}
		}
		
		// Save mappings
		$this->save_field_mappings($mappings);
		
		set_transient('afi_admin_notice', array(
			'type' => 'success',
			'message' => 'Field mappings saved successfully. Import will now use these mappings.'
		), 30);
		
		wp_redirect(admin_url('options-general.php?page=' . $this->plugin_slug . '&tab=mapping'));
		exit;
	}
	
	/**
	 * Render feedback banner (dashboard only, after first import)
	 */
	public function render_feedback_banner() {
		// Only show if an import has actually been completed successfully
		$import_count = get_option('afi_total_imports', 0);
		if ($import_count < 1) {
			return;
		}
		
		// Only show on dashboard
		$screen = get_current_screen();
		if (!$screen || $screen->id !== 'dashboard') {
			return;
		}
		
		// Check if already dismissed
		$dismissed = get_user_meta(get_current_user_id(), 'afi_feedback_dismissed', true);
		if ($dismissed) {
			return;
		}
		?>
		<div class="notice notice-success is-dismissible" data-dismiss-type="feedback" style="border-left-color: #ffb900;">
			<p style="font-size: 15px;">🚗 <strong>Is your inventory looking good?</strong> 
			If <em>Automotive Inventory Importer</em> saved you time today, would you mind leaving a quick review? It helps us keep the plugin free!
			<a href="https://wordpress.org/support/plugin/automotive-feed-import/reviews/#new-post" target="_blank" class="button button-secondary" style="margin-left: 10px;">Leave a Review ★★★★★</a>
			</p>
		</div>
		<?php
	}
	
	/**
	 * Render survey banner (dashboard only, after first import)
	 */
	public function render_survey_banner() {
		// Only show if an import has actually been completed successfully
		$import_count = get_option('afi_total_imports', 0);
		if ($import_count < 1) {
			return;
		}
		
		// Only show on dashboard
		$screen = get_current_screen();
		if (!$screen || $screen->id !== 'dashboard') {
			return;
		}
		
		// Check if already dismissed
		$dismissed = get_user_meta(get_current_user_id(), 'afi_survey_dismissed', true);
		if ($dismissed) {
			return;
		}
		?>
		<div class="notice notice-info is-dismissible" data-dismiss-type="survey" style="border-left-color: #00a0d2;">
			<p style="font-size: 15px;">📋 <strong>Making your vehicle imports easier?</strong> 
			Help shape the future of <em>Automotive Inventory Importer</em>! Share your experience in our quick 2-minute survey. Your insights help us build features you actually need.
			<a href="<?php echo esc_url($this->survey_url); ?>" target="_blank" class="button button-primary" style="margin-left: 10px;">Take Survey</a>
			</p>
		</div>
		<?php
	}
	
	/**
	 * Handle AJAX banner dismissal
	 */
	public function dismiss_banner() {
		check_ajax_referer('afi_dismiss_banner', 'nonce');
		
		$type = sanitize_text_field($_POST['type']);
		$user_id = get_current_user_id();
		
		if ($type === 'feedback') {
			update_user_meta($user_id, 'afi_feedback_dismissed', true);
		} elseif ($type === 'survey') {
			update_user_meta($user_id, 'afi_survey_dismissed', true);
		} elseif ($type === 'activation') {
			update_user_meta($user_id, 'afi_activation_notice_dismissed', true);
			delete_transient('afi_activation_notice');
		}
		
		wp_send_json_success();
	}
	
	/**
	 * Handle AJAX file browser
	 */
	public function ajax_browse_files() {
		check_ajax_referer('afi_browse_files', 'nonce');
		
		if (!current_user_can('manage_options')) {
			wp_send_json_error('Insufficient permissions');
		}
		
		$path = isset($_POST['path']) ? sanitize_text_field($_POST['path']) : ABSPATH;
		
		// Security: ensure path is within ABSPATH
		$real_path = realpath($path);
		$real_abspath = realpath(ABSPATH);
		
		if ($real_path === false || strpos($real_path, $real_abspath) !== 0) {
			$path = ABSPATH;
			$real_path = $real_abspath;
		}
		
		$html = '<div class="afi-current-path">Current: ' . esc_html($real_path) . '</div>';
		
		if (!is_readable($real_path)) {
			wp_send_json_error('Directory not readable');
		}
		
		$items = scandir($real_path);
		$html .= '<ul class="afi-file-list">';
		
		// Add parent directory link if not at root
		if ($real_path !== $real_abspath) {
			$parent = dirname($real_path);
			$html .= '<li><a href="#" class="afi-folder-item" data-path="' . esc_attr($parent) . '">📁 ..</a></li>';
		}
		
		// List directories first, then files
		$dirs = [];
		$files = [];
		
		foreach ($items as $item) {
			if ($item === '.' || $item === '..') continue;
			
			$item_path = $real_path . DIRECTORY_SEPARATOR . $item;
			
			if (is_dir($item_path)) {
				$dirs[] = array('name' => $item, 'path' => $item_path);
			} elseif (pathinfo($item, PATHINFO_EXTENSION) === 'xml') {
				$files[] = array('name' => $item, 'path' => $item_path);
			}
		}
		
		// Sort and display
		sort($dirs);
		sort($files);
		
		foreach ($dirs as $dir) {
			$html .= '<li><a href="#" class="afi-folder-item" data-path="' . esc_attr($dir['path']) . '">📁 ' . esc_html($dir['name']) . '</a></li>';
		}
		
		foreach ($files as $file) {
			$html .= '<li><a href="#" class="afi-file-item" data-path="' . esc_attr($file['path']) . '">📄 ' . esc_html($file['name']) . '</a></li>';
		}
		
		if (empty($dirs) && empty($files)) {
			$html .= '<li style="color: #666; font-style: italic;">No XML files or folders found</li>';
		}
		
		$html .= '</ul>';
		
		wp_send_json_success(array('html' => $html));
	}
	
	/**
	 * Display vehicle data on frontend
	 */
	public function display_vehicle_frontend($content) {
		if (is_singular('vehicles') && is_main_query()) {
			global $post;

			$custom_fields = get_post_custom($post->ID);

			// --- Image gallery ---
			$placeholder_id = (int) $this->get_option( 'placeholder_attachment_id' );
			$attachments    = get_children( array(
				'post_parent'    => $post->ID,
				'post_type'      => 'attachment',
				'post_mime_type' => 'image',
				'orderby'        => 'menu_order',
				'order'          => 'ASC',
				'numberposts'    => -1,
			) );

			if ( $placeholder_id && isset( $attachments[ $placeholder_id ] ) ) {
				unset( $attachments[ $placeholder_id ] );
			}

			$gallery_html = '';
			if ( ! empty( $attachments ) ) {
				$att_ids = array_keys( $attachments );
				$gallery_html .= '<div class="afi-gallery" role="region" aria-label="Vehicle photos">';
				foreach ( $att_ids as $i => $att_id ) {
					$thumb = wp_get_attachment_image_url( $att_id, 'medium' );
					$full  = wp_get_attachment_image_url( $att_id, 'full' );
					$alt   = get_post_meta( $att_id, '_wp_attachment_image_alt', true );
					if ( ! $alt ) $alt = get_the_title( $post->ID );
					$gallery_html .= '<a class="afi-gallery-thumb" href="' . esc_url( $full ) . '"'
						. ' data-index="' . $i . '"'
						. ' data-alt="' . esc_attr( $alt ) . '"'
						. ' aria-label="View photo ' . ( $i + 1 ) . '">'
						. '<img src="' . esc_url( $thumb ) . '" alt="' . esc_attr( $alt ) . '" loading="lazy">'
						. '</a>';
				}
				$gallery_html .= '</div>';

				// Lightbox overlay (rendered once, hidden until triggered)
				$gallery_html .= '<div class="afi-lightbox" id="afi-lightbox" role="dialog" aria-modal="true" aria-label="Photo viewer" hidden>';
				$gallery_html .= '<button class="afi-lb-close" aria-label="Close">&times;</button>';
				$gallery_html .= '<button class="afi-lb-prev" aria-label="Previous">&#8249;</button>';
				$gallery_html .= '<button class="afi-lb-next" aria-label="Next">&#8250;</button>';
				$gallery_html .= '<div class="afi-lb-img-wrap"><img class="afi-lb-img" src="" alt=""></div>';
				$gallery_html .= '<div class="afi-lb-counter"></div>';
				$gallery_html .= '</div>';

				// Build full-size URL list for JS
				$full_urls = array();
				$full_alts = array();
				foreach ( $att_ids as $att_id ) {
					$full_urls[] = wp_get_attachment_image_url( $att_id, 'full' );
					$a = get_post_meta( $att_id, '_wp_attachment_image_alt', true );
					$full_alts[] = $a ?: get_the_title( $post->ID );
				}
				$gallery_html .= '<script>
(function(){
	var images = ' . wp_json_encode( array_values( $full_urls ) ) . ';
	var alts   = ' . wp_json_encode( array_values( $full_alts ) ) . ';
	var idx    = 0;
	var lb     = document.getElementById("afi-lightbox");
	var lbImg  = lb.querySelector(".afi-lb-img");
	var lbCtr  = lb.querySelector(".afi-lb-counter");

	function show(i) {
		idx = (i + images.length) % images.length;
		lbImg.src = images[idx];
		lbImg.alt = alts[idx];
		lbCtr.textContent = (idx + 1) + " / " + images.length;
		lb.removeAttribute("hidden");
		document.body.style.overflow = "hidden";
	}
	function close() {
		lb.setAttribute("hidden", "");
		document.body.style.overflow = "";
	}

	document.querySelectorAll(".afi-gallery-thumb").forEach(function(el) {
		el.addEventListener("click", function(e) {
			e.preventDefault();
			show(parseInt(el.dataset.index, 10));
		});
	});
	lb.querySelector(".afi-lb-close").addEventListener("click", close);
	lb.querySelector(".afi-lb-prev").addEventListener("click", function(){ show(idx - 1); });
	lb.querySelector(".afi-lb-next").addEventListener("click", function(){ show(idx + 1); });
	lb.addEventListener("click", function(e){ if (e.target === lb) close(); });
	document.addEventListener("keydown", function(e){
		if (lb.hasAttribute("hidden")) return;
		if (e.key === "Escape")     close();
		if (e.key === "ArrowLeft")  show(idx - 1);
		if (e.key === "ArrowRight") show(idx + 1);
	});
	var touchStartX = 0;
	lb.addEventListener("touchstart", function(e){ touchStartX = e.changedTouches[0].clientX; }, { passive: true });
	lb.addEventListener("touchend", function(e){
		var diff = touchStartX - e.changedTouches[0].clientX;
		if (Math.abs(diff) > 50) { diff > 0 ? show(idx + 1) : show(idx - 1); }
	});
})();
</script>';
			}

			// --- Specs grid ---
			$fields = array(
				'special_web_price' => 'Price',
				'stock_number'      => 'Stock Number',
				'manufacturer'      => 'Make',
				'brand'             => 'Brand',
				'model'             => 'Model',
				'model_year'        => 'Year',
				'designation'       => 'Condition',
				'type'              => 'Type',
				'mileage'           => 'Mileage',
				'exterior_color'    => 'Color',
				'length'            => 'Length',
				'status'            => 'Status',
			);

			// Inline SVG icons per field (stroke-based, 20×20)
			$icons = array(
				'special_web_price' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="10" y1="2" x2="10" y2="18"/><path d="M14 6H8a2 2 0 0 0 0 4h4a2 2 0 0 1 0 4H6"/></svg>',
				'stock_number'      => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="7" y1="3" x2="5" y2="17"/><line x1="15" y1="3" x2="13" y2="17"/><line x1="3" y1="7" x2="17" y2="7"/><line x1="3" y1="13" x2="17" y2="13"/></svg>',
				'manufacturer'      => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="7" width="16" height="11"/><path d="M2 7l8-5 8 5"/><line x1="8" y1="18" x2="8" y2="13"/><line x1="12" y1="18" x2="12" y2="13"/></svg>',
				'brand'             => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 2l2.4 4.9 5.4.8-3.9 3.8.9 5.4L10 14.4l-4.8 2.5.9-5.4L2.2 7.7l5.4-.8z"/></svg>',
				'model'             => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 11l1.5-4h11L17 11"/><rect x="1" y="11" width="18" height="5" rx="1"/><circle cx="5.5" cy="17" r="1.5"/><circle cx="14.5" cy="17" r="1.5"/></svg>',
				'model_year'        => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="2" y="4" width="16" height="14" rx="1"/><line x1="2" y1="8" x2="18" y2="8"/><line x1="6" y1="2" x2="6" y2="6"/><line x1="14" y1="2" x2="14" y2="6"/></svg>',
				'designation'       => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 2l7 3v5c0 4.4-3.1 7.4-7 8-3.9-.6-7-3.6-7-8V5z"/><polyline points="7,10 9,12 13,8"/></svg>',
				'type'              => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3h7.6L18 10.4 10.4 18 3 10.6V3z"/><circle cx="7" cy="7" r="1.2" fill="currentColor" stroke="none"/></svg>',
				'mileage'           => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3.5 14.5a7.5 7.5 0 0 1 13 0"/><line x1="10" y1="7" x2="13" y2="11"/><circle cx="10" cy="14" r="1" fill="currentColor" stroke="none"/></svg>',
				'exterior_color'    => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="10" cy="10" r="7"/><circle cx="7.5" cy="8.5" r="1" fill="currentColor" stroke="none"/><circle cx="12.5" cy="8.5" r="1" fill="currentColor" stroke="none"/><circle cx="10" cy="13" r="1" fill="currentColor" stroke="none"/></svg>',
				'length'            => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="2" y1="10" x2="18" y2="10"/><line x1="2" y1="7" x2="2" y2="13"/><line x1="18" y1="7" x2="18" y2="13"/><line x1="7" y1="8" x2="7" y2="10"/><line x1="13" y1="8" x2="13" y2="10"/></svg>',
				'status'            => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><circle cx="10" cy="10" r="5" fill="currentColor"/></svg>',
			);
			$default_icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2"><circle cx="10" cy="10" r="7"/></svg>';

			// --- Natural-language description (replaces raw template content) ---
			$nl_year      = $this->format_display_value( $custom_fields['model_year'][0]     ?? '' );
			$nl_make      = $this->format_display_value( $custom_fields['manufacturer'][0]   ?? '' );
			$nl_brand     = $this->format_display_value( $custom_fields['brand'][0]          ?? '' );
			$nl_model     = $this->format_display_value( $custom_fields['model'][0]          ?? '' );
			$nl_condition = $this->format_display_value( $custom_fields['designation'][0]    ?? '' );
			$nl_type      = $this->format_display_value( $custom_fields['type'][0]           ?? '' );
			$nl_color     = $this->format_display_value( $custom_fields['exterior_color'][0] ?? '' );
			$nl_mileage   = $custom_fields['mileage'][0]           ?? '';
			$nl_price_raw = $custom_fields['special_web_price'][0] ?? '';

			$name_parts = array_filter( [ $nl_condition, $nl_year, $nl_make, $nl_brand, $nl_model ] );
			$desc = '';
			if ( $name_parts ) {
				$desc = 'This ' . implode( ' ', $name_parts );
				$desc .= $nl_type ? ' is a ' . $nl_type . '.' : '.';

				$detail_parts = [];
				if ( $nl_color )   $detail_parts[] = $nl_color . ' exterior';
				if ( $nl_mileage ) $detail_parts[] = number_format( (float) $nl_mileage ) . ' miles';
				if ( $nl_price_raw ) {
					$currency     = $this->get_option( 'currency_symbol', '$' );
					$detail_parts[] = 'priced at ' . $currency . number_format( (float) $nl_price_raw );
				}
				if ( $detail_parts ) {
					$desc .= ' ' . ucfirst( implode( ', ', $detail_parts ) ) . '.';
				}
			}
			$description_html = $desc ? '<p class="vehicle-intro">' . esc_html( $desc ) . '</p>' : '';

			$vehicle_data = '<div class="vehicle-details">';
			$vehicle_data .= '<h3>Vehicle Specifications</h3>';
			$vehicle_data .= '<div class="vehicle-specs-grid">';

			foreach ($fields as $key => $label) {
				$value = $custom_fields[$key][0] ?? '';
				if ( empty( $value ) ) continue;

				if ($key === 'special_web_price') {
					$currency      = $this->get_option('currency_symbol', '$');
					$display_value = $currency . number_format((float)$value);
				} elseif ($key === 'mileage') {
					$display_value = number_format((float)$value) . ' mi';
				} else {
					$display_value = $this->format_display_value( $value );
				}

				$css_key       = esc_attr( str_replace( '_', '-', $key ) );
				$icon          = $icons[$key] ?? $default_icon;
				$vehicle_data .= '<div class="spec-card spec-' . $css_key . '">';
				$vehicle_data .= '<span class="spec-icon" aria-hidden="true">' . $icon . '</span>';
				$vehicle_data .= '<span class="spec-label">' . esc_html($label) . '</span>';
				$vehicle_data .= '<span class="spec-value">' . esc_html($display_value) . '</span>';
				$vehicle_data .= '</div>';
			}

			$vehicle_data .= '</div>'; // Close vehicle-specs-grid
			$vehicle_data .= '</div>'; // Close vehicle-details
			
			$content = $gallery_html . $description_html . $vehicle_data;
		}
		
		return $content;
	}
	
	/**
	 * Display vehicle info in archive/listing pages
	 */
	/**
	 * Get distinct makes, years, and conditions from published vehicles
	 */
	private function get_filter_options() {
		global $wpdb;

		$base = "FROM {$wpdb->postmeta} pm
		         INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
		         WHERE p.post_type = 'vehicles' AND p.post_status = 'publish'";

		$makes = $wpdb->get_col(
			"SELECT DISTINCT pm.meta_value {$base} AND pm.meta_key = 'manufacturer' AND pm.meta_value != '' ORDER BY pm.meta_value" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		);
		$years = $wpdb->get_col(
			"SELECT DISTINCT pm.meta_value {$base} AND pm.meta_key = 'model_year' AND pm.meta_value != '' ORDER BY pm.meta_value DESC" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		);
		$conditions = $wpdb->get_col(
			"SELECT DISTINCT pm.meta_value {$base} AND pm.meta_key = 'designation' AND pm.meta_value != '' ORDER BY pm.meta_value" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		);

		return compact('makes', 'years', 'conditions');
	}

	/**
	 * Render the search and filter form HTML
	 */
	private function render_vehicle_filter_form() {
		$options           = $this->get_filter_options();
		$current_search    = sanitize_text_field( $_GET['afi_search'] ?? '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$current_make      = sanitize_text_field( $_GET['afi_make'] ?? '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$current_year      = sanitize_text_field( $_GET['afi_year'] ?? '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$current_condition = sanitize_text_field( $_GET['afi_condition'] ?? '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$current_min       = absint( $_GET['afi_price_min'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$current_max       = absint( $_GET['afi_price_max'] ?? 0 ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$archive_url       = get_post_type_archive_link( 'vehicles' );

		$html  = '<div class="vehicle-filter-form">';
		$html .= '<form method="GET" action="' . esc_url( $archive_url ) . '">';
		$html .= '<div class="filter-row">';

		$html .= '<div class="filter-group filter-search">';
		$html .= '<input type="text" name="afi_search" placeholder="Search by make, model&hellip;" value="' . esc_attr( $current_search ) . '">';
		$html .= '</div>';

		if ( ! empty( $options['makes'] ) ) {
			$html .= '<div class="filter-group">';
			$html .= '<select name="afi_make"><option value="">All Makes</option>';
			foreach ( $options['makes'] as $make ) {
				$html .= '<option value="' . esc_attr( $make ) . '"' . selected( $current_make, $make, false ) . '>' . esc_html( $make ) . '</option>';
			}
			$html .= '</select></div>';
		}

		if ( ! empty( $options['years'] ) ) {
			$html .= '<div class="filter-group">';
			$html .= '<select name="afi_year"><option value="">All Years</option>';
			foreach ( $options['years'] as $year ) {
				$html .= '<option value="' . esc_attr( $year ) . '"' . selected( $current_year, $year, false ) . '>' . esc_html( $year ) . '</option>';
			}
			$html .= '</select></div>';
		}

		if ( ! empty( $options['conditions'] ) ) {
			$html .= '<div class="filter-group">';
			$html .= '<select name="afi_condition"><option value="">All Conditions</option>';
			foreach ( $options['conditions'] as $cond ) {
				$html .= '<option value="' . esc_attr( $cond ) . '"' . selected( $current_condition, $cond, false ) . '>' . esc_html( $cond ) . '</option>';
			}
			$html .= '</select></div>';
		}

		$html .= '<div class="filter-group filter-price">';
		$html .= '<input type="number" name="afi_price_min" placeholder="Min $" value="' . esc_attr( $current_min ?: '' ) . '" min="0">';
		$html .= '<span class="price-separator">&ndash;</span>';
		$html .= '<input type="number" name="afi_price_max" placeholder="Max $" value="' . esc_attr( $current_max ?: '' ) . '" min="0">';
		$html .= '</div>';

		$html .= '<div class="filter-group filter-actions">';
		$html .= '<button type="submit" class="filter-submit-btn">Filter</button>';
		$html .= '<a href="' . esc_url( $archive_url ) . '" class="filter-reset-btn">Reset</a>';
		$html .= '</div>';

		$html .= '</div></form></div>';

		return $html;
	}

	/**
	 * Echo the filter form at the start of the vehicle archive loop
	 */
	public function output_vehicle_filter_form( $query ) {
		if ( $query->is_main_query() && ! is_admin() &&
		     ( $query->is_post_type_archive( 'vehicles' ) || $query->is_tax() ) ) {
			echo $this->render_vehicle_filter_form(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			echo '<div class="vehicle-archive-grid">';
		}
	}

	/**
	 * Close the vehicle archive grid wrapper opened in output_vehicle_filter_form
	 */
	public function close_vehicle_grid( $query ) {
		if ( $query->is_main_query() && ! is_admin() &&
		     ( $query->is_post_type_archive( 'vehicles' ) || $query->is_tax() ) ) {
			echo '</div><!-- .vehicle-archive-grid -->';
		}
	}

	/**
	 * Modify the main vehicle archive query based on filter GET params
	 */
	public function filter_vehicle_query( $query ) {
		if ( is_admin() || ! $query->is_main_query() ) {
			return;
		}
		if ( ! $query->is_post_type_archive( 'vehicles' ) && ! $query->is_tax() ) {
			return;
		}

		$meta_query = [];

		if ( ! empty( $_GET['afi_make'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$meta_query[] = [ 'key' => 'manufacturer', 'value' => sanitize_text_field( $_GET['afi_make'] ), 'compare' => '=' ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		}
		if ( ! empty( $_GET['afi_year'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$meta_query[] = [ 'key' => 'model_year', 'value' => sanitize_text_field( $_GET['afi_year'] ), 'compare' => '=' ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		}
		if ( ! empty( $_GET['afi_condition'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$meta_query[] = [ 'key' => 'designation', 'value' => sanitize_text_field( $_GET['afi_condition'] ), 'compare' => '=' ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		}
		if ( ! empty( $_GET['afi_price_min'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$meta_query[] = [ 'key' => 'special_web_price', 'value' => (float) $_GET['afi_price_min'], 'compare' => '>=', 'type' => 'DECIMAL(10,2)' ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		}
		if ( ! empty( $_GET['afi_price_max'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$meta_query[] = [ 'key' => 'special_web_price', 'value' => (float) $_GET['afi_price_max'], 'compare' => '<=', 'type' => 'DECIMAL(10,2)' ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		}

		if ( ! empty( $meta_query ) ) {
			$meta_query['relation'] = 'AND';
			$query->set( 'meta_query', $meta_query );
		}
	}

	/**
	 * Add text search against post title and content for the vehicle archive
	 */
	public function filter_vehicle_search( $where, $query ) {
		if ( is_admin() || ! $query->is_main_query() ) {
			return $where;
		}
		if ( ! $query->is_post_type_archive( 'vehicles' ) && ! $query->is_tax() ) {
			return $where;
		}
		if ( ! empty( $_GET['afi_search'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			global $wpdb;
			$term   = '%' . $wpdb->esc_like( sanitize_text_field( $_GET['afi_search'] ) ) . '%'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			$where .= $wpdb->prepare( " AND ({$wpdb->posts}.post_title LIKE %s OR {$wpdb->posts}.post_content LIKE %s)", $term, $term ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		}
		return $where;
	}

	public function display_vehicle_archive($content) {
		if ( ( is_post_type_archive('vehicles') || is_tax() ) && in_the_loop() && is_main_query() ) {
			global $post, $wp_query;
			$custom_fields = get_post_custom($post->ID);

			// Key info
			$manufacturer = $custom_fields['manufacturer'][0] ?? '';
			$model = $custom_fields['model'][0] ?? '';
			$year = $custom_fields['model_year'][0] ?? '';
			$price = $custom_fields['special_web_price'][0] ?? '';
			$mileage = $custom_fields['mileage'][0] ?? '';
			$color = $custom_fields['exterior_color'][0] ?? '';
			$designation = $custom_fields['designation'][0] ?? '';

			// Show a real image when available; skip the placeholder entirely
			$thumbnail_id   = get_post_thumbnail_id( $post->ID );
			$placeholder_id = (int) $this->get_option( 'placeholder_attachment_id' );
			$has_real_image = $thumbnail_id && ( ! $placeholder_id || (int) $thumbnail_id !== $placeholder_id );

			$vehicle_card = '<div class="vehicle-archive-card' . ( $has_real_image ? ' has-image' : ' no-image' ) . '">';

			if ( $has_real_image ) {
				$vehicle_card .= '<a href="' . get_permalink() . '" class="vehicle-card-image-link" tabindex="-1" aria-hidden="true">';
				$this->rendering_vehicle_card = true;
				$vehicle_card .= get_the_post_thumbnail( $post->ID, 'medium', array( 'class' => 'vehicle-card-image', 'loading' => 'lazy' ) );
				$this->rendering_vehicle_card = false;
				$vehicle_card .= '</a>';
			}

			$vehicle_card .= '<div class="vehicle-info">';

			if ($price) {
				$currency = $this->get_option('currency_symbol', '$');
				$vehicle_card .= '<div class="vehicle-price">' . esc_html($currency) . esc_html(number_format((float)$price)) . '</div>';
			}

			$vehicle_card .= '<div class="vehicle-quick-specs">';
			if ($year) $vehicle_card .= '<span class="spec-item"><strong>Year:</strong> ' . esc_html($year) . '</span>';
			if ($mileage) $vehicle_card .= '<span class="spec-item"><strong>Mileage:</strong> ' . esc_html(number_format((float)$mileage)) . ' mi</span>';
			if ($color) $vehicle_card .= '<span class="spec-item"><strong>Color:</strong> ' . esc_html( $this->format_display_value( $color ) ) . '</span>';
			if ($designation) $vehicle_card .= '<span class="spec-item"><strong>Condition:</strong> ' . esc_html( $this->format_display_value( $designation ) ) . '</span>';
			$vehicle_card .= '</div>';

			$vehicle_card .= '<a href="' . get_permalink() . '" class="view-details-btn">View Details →</a>';
			$vehicle_card .= '</div>'; // Close vehicle-info
			$vehicle_card .= '</div>'; // Close vehicle-archive-card

			// For the first post, prepend the filter form (JS will reposition it outside the article).
			if ( $wp_query->current_post === 0 ) {
				$vehicle_card = '<div id="afi-filter-standalone">' . $this->render_vehicle_filter_form() . '</div>' . $vehicle_card;
			}

			return $vehicle_card;
		}
		return $content;
	}
	
	/**
	 * Enqueue frontend styles for vehicle specifications and archive
	 */
	public function enqueue_frontend_styles() {
		if (is_singular('vehicles') || is_post_type_archive('vehicles') || is_tax()) {
			echo '<style id="afi-frontend-css">' . '
				/* Vehicle Spec Cards */
				.vehicle-intro {
					font-size: 16px;
					line-height: 1.65;
					color: #444;
					margin: 0 0 28px;
				}
				.vehicle-details h3 {
					margin-top: 30px;
					margin-bottom: 14px;
					font-size: 20px;
					font-weight: 700;
					color: #1a1a1a;
					letter-spacing: -0.01em;
				}
				.vehicle-specs-grid {
					display: grid;
					grid-template-columns: repeat(2, 1fr);
					gap: 12px;
					margin: 0 0 32px;
				}
				@media (max-width: 500px) {
					.vehicle-specs-grid { grid-template-columns: 1fr; }
				}
				.spec-card {
					display: flex;
					flex-direction: column;
					gap: 3px;
					padding: 14px 16px;
					border: 1px solid #e5e7eb;
					border-radius: 8px;
					background: #fff;
					transition: box-shadow 0.15s ease;
				}
				.spec-card:nth-child(odd) {
					background: #fafafa;
				}
				.spec-card:hover {
					box-shadow: 0 2px 8px rgba(0,0,0,0.08);
				}
				.spec-card.spec-special-web-price {
					grid-column: 1 / -1;
					background: #f0f7ff;
					border-color: #0073aa;
					border-left-width: 4px;
					flex-direction: row;
					align-items: center;
					gap: 12px;
					padding: 16px 20px;
				}
				.spec-card.spec-special-web-price .spec-icon {
					color: #0073aa;
					flex-shrink: 0;
				}
				.spec-card.spec-special-web-price .spec-label {
					flex: 1;
					font-size: 13px;
				}
				.spec-card.spec-special-web-price .spec-value {
					font-size: 28px;
					font-weight: 800;
					color: #0073aa;
				}
				.spec-icon {
					color: #0073aa;
					width: 20px;
					height: 20px;
					margin-bottom: 4px;
				}
				.spec-icon svg {
					width: 20px;
					height: 20px;
					display: block;
				}
				.spec-label {
					font-size: 10px;
					text-transform: uppercase;
					letter-spacing: 0.07em;
					color: #888;
					font-weight: 600;
				}
				.spec-value {
					font-size: 15px;
					font-weight: 600;
					color: #1a1a1a;
					word-break: break-word;
				}
				
				/* Vehicle Image Gallery */
				.afi-gallery {
					display: grid;
					grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
					gap: 8px;
					margin: 0 0 24px;
				}
				.afi-gallery-thumb {
					display: block;
					overflow: hidden;
					border-radius: 4px;
					aspect-ratio: 4/3;
					background: #f0f0f0;
				}
				.afi-gallery-thumb img {
					width: 100%;
					height: 100%;
					object-fit: cover;
					object-position: center;
					transition: transform 0.2s ease, opacity 0.2s ease;
					cursor: pointer;
				}
				.afi-gallery-thumb:hover img {
					transform: scale(1.05);
					opacity: 0.9;
				}

				/* Lightbox */
				.afi-lightbox {
					position: fixed;
					inset: 0;
					background: rgba(0,0,0,0.92);
					z-index: 99999;
					display: flex;
					align-items: center;
					justify-content: center;
				}
				.afi-lightbox[hidden] { display: none; }
				.afi-lb-img-wrap {
					max-width: 90vw;
					max-height: 85vh;
					display: flex;
					align-items: center;
					justify-content: center;
				}
				.afi-lb-img {
					max-width: 90vw;
					max-height: 85vh;
					object-fit: contain;
					border-radius: 2px;
					box-shadow: 0 4px 32px rgba(0,0,0,0.6);
					display: block;
				}
				.afi-lb-close,
				.afi-lb-prev,
				.afi-lb-next {
					position: fixed;
					background: rgba(255,255,255,0.15);
					border: none;
					color: #fff;
					cursor: pointer;
					border-radius: 4px;
					line-height: 1;
					transition: background 0.2s;
				}
				.afi-lb-close:hover,
				.afi-lb-prev:hover,
				.afi-lb-next:hover {
					background: rgba(255,255,255,0.3);
				}
				.afi-lb-close {
					top: 16px;
					right: 16px;
					font-size: 32px;
					padding: 4px 12px;
				}
				.afi-lb-prev,
				.afi-lb-next {
					top: 50%;
					transform: translateY(-50%);
					font-size: 48px;
					padding: 8px 16px;
				}
				.afi-lb-prev { left: 16px; }
				.afi-lb-next { right: 16px; }
				.afi-lb-counter {
					position: fixed;
					bottom: 16px;
					left: 50%;
					transform: translateX(-50%);
					color: rgba(255,255,255,0.7);
					font-size: 14px;
					font-family: sans-serif;
				}

				/* Vehicle Archive Grid Wrapper */
				.vehicle-archive-grid {
					display: grid !important;
					grid-template-columns: repeat(3, 1fr) !important;
					gap: 24px;
					margin-bottom: 30px;
				}
				@media (max-width: 900px) {
					.vehicle-archive-grid {
						grid-template-columns: repeat(2, 1fr) !important;
					}
				}
				@media (max-width: 560px) {
					.vehicle-archive-grid {
						grid-template-columns: 1fr !important;
					}
				}
				/* Strip theme article/li wrappers that are direct grid children */
				.vehicle-archive-grid > article,
				.vehicle-archive-grid > li,
				.vehicle-archive-grid > .hentry {
					margin: 0 !important;
					padding: 0 !important;
					border: none !important;
					box-shadow: none !important;
					background: none !important;
					float: none !important;
					width: auto !important;
				}
				/* Hide theme entry-meta (author/category byline) on vehicle pages */
				.post-type-archive-vehicles .entry-meta,
				.post-type-archive-vehicles .byline,
				.post-type-archive-vehicles .posted-on,
				.post-type-archive-vehicles .cat-links,
				.single-vehicles .entry-meta,
				.single-vehicles .byline,
				.single-vehicles .posted-on,
				.single-vehicles .cat-links,
				/* Block theme (Twenty Twenty-Two+) post meta blocks */
				.single-vehicles .wp-block-post-author,
				.single-vehicles .wp-block-post-author__name,
				.single-vehicles .wp-block-post-author__bio,
				.single-vehicles .wp-block-post-terms,
				.single-vehicles .wp-block-post-date,
				.post-type-archive-vehicles .wp-block-post-author,
				.post-type-archive-vehicles .wp-block-post-terms,
				.post-type-archive-vehicles .wp-block-post-date {
					display: none !important;
				}

				/* Vehicle Archive/Listing Grid Styling */
				.vehicle-archive-card {
					background: #ffffff;
					border: 1px solid #e0e0e0;
					border-radius: 8px;
					overflow: hidden;
					box-shadow: 0 2px 8px rgba(0,0,0,0.1);
					transition: transform 0.3s ease, box-shadow 0.3s ease;
				}
				.vehicle-archive-card:hover {
					transform: translateY(-5px);
					box-shadow: 0 4px 16px rgba(0,0,0,0.15);
				}
				.vehicle-card-image-link {
					display: block;
					overflow: hidden;
				}
				.vehicle-card-image {
					display: block;
					width: 100%;
					height: 200px;
					object-fit: cover;
					object-position: center;
					transition: transform 0.3s ease;
				}
				.vehicle-archive-card:hover .vehicle-card-image {
					transform: scale(1.03);
				}
				.vehicle-archive-card .vehicle-info {
					padding: 20px;
				}
				.vehicle-archive-card .vehicle-price {
					font-size: 28px;
					font-weight: bold;
					color: #0073aa;
					margin-bottom: 15px;
				}
				.vehicle-archive-card .vehicle-quick-specs {
					display: flex;
					flex-wrap: wrap;
					gap: 15px;
					margin-bottom: 20px;
					padding-bottom: 15px;
					border-bottom: 1px solid #e0e0e0;
				}
				.vehicle-archive-card .spec-item {
					font-size: 14px;
					color: #555;
				}
				.vehicle-archive-card .spec-item strong {
					color: #333;
				}
				.vehicle-archive-card .view-details-btn {
					display: inline-block;
					background: #0073aa;
					color: #ffffff;
					padding: 10px 20px;
					border-radius: 4px;
					text-decoration: none;
					transition: background 0.3s ease;
				}
				.vehicle-archive-card .view-details-btn:hover {
					background: #005a87;
					color: #ffffff;
				}
				
				/* Vehicle Search & Filter Form */
				.vehicle-filter-form {
					grid-column: 1 / -1;
					background: #f8f9fa;
					border: 1px solid #e0e0e0;
					border-radius: 8px;
					padding: 20px;
					margin-bottom: 10px;
					box-sizing: border-box;
					width: 100%;
				}
				.vehicle-filter-form .filter-row {
					display: flex;
					flex-wrap: wrap;
					gap: 10px;
					align-items: flex-end;
				}
				.vehicle-filter-form .filter-group {
					flex: 1;
					min-width: 140px;
				}
				.vehicle-filter-form .filter-search {
					flex: 2;
					min-width: 200px;
				}
				.vehicle-filter-form input[type="text"],
				.vehicle-filter-form input[type="number"],
				.vehicle-filter-form select {
					width: 100%;
					padding: 8px 12px;
					border: 1px solid #ccc;
					border-radius: 4px;
					font-size: 14px;
					height: 38px;
					box-sizing: border-box;
				}
				.vehicle-filter-form .filter-price {
					display: flex;
					align-items: center;
					gap: 6px;
					min-width: 220px;
				}
				.vehicle-filter-form .filter-price input {
					width: 50%;
				}
				.vehicle-filter-form .price-separator {
					color: #666;
					flex-shrink: 0;
				}
				.vehicle-filter-form .filter-actions {
					display: flex;
					gap: 8px;
					flex: 0;
					min-width: auto;
					white-space: nowrap;
				}
				.vehicle-filter-form .filter-submit-btn {
					background: #0073aa;
					color: #ffffff;
					border: none;
					padding: 0 20px;
					border-radius: 4px;
					cursor: pointer;
					font-size: 14px;
					height: 38px;
					transition: background 0.3s ease;
				}
				.vehicle-filter-form .filter-submit-btn:hover {
					background: #005a87;
				}
				.vehicle-filter-form .filter-reset-btn {
					display: inline-flex;
					align-items: center;
					background: #e0e0e0;
					color: #333;
					padding: 0 16px;
					border-radius: 4px;
					text-decoration: none;
					font-size: 14px;
					height: 38px;
					transition: background 0.3s ease;
				}
				.vehicle-filter-form .filter-reset-btn:hover {
					background: #ccc;
					color: #333;
				}

			/* Grid layout for archive pages */
				@media (min-width: 768px) {
					.post-type-archive-vehicles .site-main,
					.tax-vehicles .site-main {
						display: grid;
						grid-template-columns: repeat(2, 1fr);
						gap: 30px;
					}
				}
				@media (min-width: 1024px) {
					.post-type-archive-vehicles .site-main,
					.tax-vehicles .site-main {
						grid-template-columns: repeat(3, 1fr);
					}
				}

				/* Hide post date on vehicle pages */
				.single-vehicles .entry-date,
				.single-vehicles .posted-on,
				.single-vehicles .post-date,
				.post-type-archive-vehicles .entry-date,
				.post-type-archive-vehicles .posted-on,
				.post-type-archive-vehicles .post-date,
				.tax-vehicles .entry-date,
				.tax-vehicles .posted-on,
				.tax-vehicles .post-date {
					display: none !important;
				}

				/* ── Mobile: filter form stacks vertically ── */
				@media (max-width: 560px) {
					.vehicle-filter-form {
						padding: 14px;
					}
					.vehicle-filter-form .filter-row {
						flex-direction: column;
					}
					.vehicle-filter-form .filter-group,
					.vehicle-filter-form .filter-search,
					.vehicle-filter-form .filter-price,
					.vehicle-filter-form .filter-actions {
						flex: none;
						min-width: 0;
						width: 100%;
					}
					.vehicle-filter-form .filter-actions {
						flex-direction: column;
					}
					.vehicle-filter-form .filter-submit-btn,
					.vehicle-filter-form .filter-reset-btn {
						width: 100%;
						justify-content: center;
						text-align: center;
						box-sizing: border-box;
					}
					.vehicle-archive-card .view-details-btn {
						display: block;
						text-align: center;
					}
					.vehicle-archive-grid {
						gap: 16px;
					}
					.afi-lb-prev,
					.afi-lb-next {
						font-size: 32px;
						padding: 6px 10px;
					}
					.afi-lb-close {
						font-size: 24px;
						padding: 4px 10px;
					}
				}

				/* ── Touch: disable sticky hover effects ── */
				@media (hover: none) {
					.vehicle-archive-card:hover {
						transform: none;
						box-shadow: 0 2px 8px rgba(0,0,0,0.1);
					}
					.vehicle-archive-card:hover .vehicle-card-image {
						transform: none;
					}
					.afi-gallery-thumb:hover img {
						transform: none;
						opacity: 1;
					}
				}
			' . '</style>';
		}
	}
	
	/**
	 * Add Settings link to plugin action links
	 */
	public function add_plugin_action_links($links) {
		$settings_link = '<a href="' . admin_url('options-general.php?page=' . $this->plugin_slug) . '">Settings</a>';
		array_unshift($links, $settings_link);
		return $links;
	}
	
	/**
	 * Enqueue admin scripts
	 */
	public function enqueue_admin_scripts($hook) {
		if ($hook !== 'settings_page_' . $this->plugin_slug) {
			return;
		}
		
		wp_enqueue_script('jquery');
		add_thickbox();
		wp_add_inline_script('jquery', "
			jQuery(document).ready(function($) {
				$('.notice[data-dismiss-type]').on('click', '.notice-dismiss', function() {
					var type = $(this).closest('.notice').data('dismiss-type');
					$.post(ajaxurl, {
						action: 'afi_dismiss_banner',
						type: type,
						nonce: '" . wp_create_nonce('afi_dismiss_banner') . "'
					});
				});
			});
		");
	}
	
	/**
	 * Suppress post date/time output on vehicle pages.
	 * Hooked to get_the_date and get_the_time so themes that call
	 * these functions directly return an empty string.
	 */
	public function hide_vehicle_date( $date ) {
		if ( is_singular( 'vehicles' ) || is_post_type_archive( 'vehicles' ) || is_tax() ) {
			return '';
		}
		return $date;
	}

	/**
	 * Remove the "Archives: Vehicles" heading on the vehicle listing page.
	 */
	public function hide_vehicle_archive_title( $title ) {
		if ( is_post_type_archive( 'vehicles' ) || is_tax() ) {
			return '';
		}
		return $title;
	}

	/**
	 * Suppress the "written by" author on vehicle pages.
	 */
	public function hide_vehicle_author( $author ) {
		if ( is_singular( 'vehicles' ) || is_post_type_archive( 'vehicles' ) || is_tax() ) {
			return '';
		}
		return $author;
	}

	/**
	 * Output a <style> block in <head> to hide the theme's byline on vehicle pages.
	 * Using wp_head guarantees output regardless of which stylesheet handles are enqueued.
	 */
	public function hide_vehicle_byline_style() {
		if ( ! ( is_singular( 'vehicles' ) || is_post_type_archive( 'vehicles' ) || is_tax() ) ) {
			return;
		}
		echo '<style>
			/* AFI: hide theme author/category byline on vehicle pages */
			.entry-meta,
			.entry-footer,
			.byline,
			.posted-by,
			.author-byline,
			.post-author,
			.post-meta,
			.author-info,
			.cat-links,
			.posted-on,
			.entry-date,
			/* Block theme (Twenty Twenty-Two+) post meta blocks */
			.wp-block-post-author,
			.wp-block-post-author__name,
			.wp-block-post-author__bio,
			.wp-block-post-terms,
			.wp-block-post-date {
				display: none !important;
			}
		</style>' . "\n";
	}

	/**
	 * Output JS in the footer that:
	 *  1. Moves #afi-filter-standalone before its parent article (outside the listing).
	 *  2. Wraps all vehicle-card articles in .vehicle-archive-grid for multi-column layout.
	 */
	public function vehicle_archive_layout_js() {
		if ( ! ( is_post_type_archive( 'vehicles' ) || is_tax() ) ) {
			return;
		}
		?>
		<script>
		(function () {
			var filter = document.getElementById('afi-filter-standalone');
			if (!filter) return;

			// Find the article/post wrapper that contains the filter form.
			var firstArticle = filter.closest('article, .hentry, [id^="post-"]');
			if (!firstArticle) return;
			var listParent = firstArticle.parentNode;
			if (!listParent) return;

			// Move filter form before the first article.
			listParent.insertBefore(filter, firstArticle);

			// Collect all sibling elements that contain a vehicle card.
			var articles = Array.from(listParent.children).filter(function (el) {
				return el.querySelector('.vehicle-archive-card');
			});
			if (articles.length === 0) return;

			// Create the grid wrapper and insert it where the first article is.
			var grid = document.createElement('div');
			grid.className = 'vehicle-archive-grid';
			listParent.insertBefore(grid, articles[0]);

			// Move all vehicle articles into the grid.
			articles.forEach(function (el) { grid.appendChild(el); });
		})();
		</script>
		<?php
	}

	/**
	 * Suppress the featured image that themes output above the_content on archive pages.
	 * The plugin already outputs the image inside the card via the_content filter.
	 */
	public function hide_vehicle_thumbnail( $html ) {
		// Allow our own card renderer to fetch the thumbnail unfiltered.
		if ( $this->rendering_vehicle_card ) {
			return $html;
		}
		if ( is_post_type_archive( 'vehicles' ) || is_tax() ) {
			return '';
		}
		return $html;
	}

} // end of class

/////////////////////////////////////////////////////////////////////

$afi = new AutomotiveFeedImport();

/* The activation hook is executed when the plugin is activated. */
register_activation_hook(__FILE__, array($afi, 'init'));

/* The deactivation hook is executed when the plugin is deactivated */
register_deactivation_hook(__FILE__, array($afi, 'uninit'));

// Actions
add_action('init', array($afi, 'register_vehicles_post_type'));
add_action('admin_init', array($afi, 'add_custom_box'), 1);
add_action('admin_init', array($afi, 'register_settings'));
add_action('admin_init', array($afi, 'handle_admin_actions'));
add_action('admin_menu', array($afi, 'add_settings_page'));
add_action('admin_notices', array($afi, 'display_activation_notice'));
add_action('admin_notices', array($afi, 'display_transient_notices'));
add_action('admin_notices', array($afi, 'render_feedback_banner'));
add_action('admin_notices', array($afi, 'render_survey_banner'));
add_action('update_xml_event', array($afi, 'update_data'));
add_action('save_post_vehicles', array($afi, 'save_vehicle_meta'));
add_action('wp_ajax_afi_dismiss_banner', array($afi, 'dismiss_banner'));
add_action('wp_ajax_afi_browse_files', array($afi, 'ajax_browse_files'));
add_action('admin_enqueue_scripts', array($afi, 'enqueue_admin_scripts'));
add_action('admin_post_afi_save_field_mappings', array($afi, 'handle_field_mapping_save'));
add_action('wp_head', array($afi, 'enqueue_frontend_styles'));
add_action('pre_get_posts', array($afi, 'filter_vehicle_query'));

// Filters
add_filter('cron_schedules', array($afi, 'define_interval'));
add_action('update_option_automotive-feed-import_import_frequency', array($afi, 'reschedule'));
add_filter('the_content', array($afi, 'display_vehicle_frontend'));
add_filter('the_content', array($afi, 'display_vehicle_archive'));
add_filter('plugin_action_links_' . plugin_basename(__FILE__), array($afi, 'add_plugin_action_links'));
add_filter('posts_where', array($afi, 'filter_vehicle_search'), 10, 2);
add_filter('get_the_date', array($afi, 'hide_vehicle_date'));
add_filter('get_the_time', array($afi, 'hide_vehicle_date'));
add_filter('the_author', array($afi, 'hide_vehicle_author'));
add_filter('get_the_archive_title', array($afi, 'hide_vehicle_archive_title'));
add_filter('post_thumbnail_html', array($afi, 'hide_vehicle_thumbnail'));
add_action('wp_head', array($afi, 'hide_vehicle_byline_style'));
add_action('wp_footer', array($afi, 'vehicle_archive_layout_js'));

?>