<?php
/**
 * Frontend shortcodes and styling.
 *
 * @package Vachr Short-Term Rental Reservations
 * @license GPLv2 or later
 */
if (!defined('ABSPATH')) exit;

class VSTRB_Shortcodes {
	private static function safe_table_name($table_name) {
		$table_name = (string) $table_name;
		if ($table_name === '' || preg_match('/[^A-Za-z0-9_]/', $table_name)) {
			return '';
		}
		return $table_name;
	}

	private static function parse_bool_shortcode_att($value, $default = false) {
		if ($value === null || $value === '') {
			return (bool) $default;
		}
		if (is_bool($value)) {
			return $value;
		}
		$normalized = strtolower(trim((string) $value));
		return in_array($normalized, ['1', 'true', 'yes', 'on'], true);
	}

	private static function render_billing_fields($name_prefix = 'billing_', $class_prefix = '', $required = false) {
		$name_prefix = (string) $name_prefix;
		$class_prefix = (string) $class_prefix;
		$required_attr = $required ? ' required' : '';
		$required_mark = $required ? ' *' : '';
		ob_start();
		?>
		<div class="vstrb-row">
			<div class="vstrb-field">
				<label><?php esc_html_e('Billing Name', 'vachr-short-term-rental-reservations'); ?></label>
				<input type="text" class="<?php echo esc_attr($class_prefix); ?>billing-name" name="<?php echo esc_attr($name_prefix); ?>name" autocomplete="section-vstrb-billing organization">
			</div>
		</div>
		<div class="vstrb-row vstrb-row-half">
			<div class="vstrb-field">
				<label><?php esc_html_e('Company ID', 'vachr-short-term-rental-reservations'); ?></label>
				<input type="text" class="<?php echo esc_attr($class_prefix); ?>billing-company-id" name="<?php echo esc_attr($name_prefix); ?>ico" aria-label="ICO" inputmode="numeric" pattern="[0-9]*">
			</div>
			<div class="vstrb-field">
				<label><?php esc_html_e('VAT ID', 'vachr-short-term-rental-reservations'); ?></label>
				<input type="text" class="<?php echo esc_attr($class_prefix); ?>billing-vat-id" name="<?php echo esc_attr($name_prefix); ?>vat_id" autocomplete="section-vstrb-billing tax-id">
			</div>
		</div>
		<div class="vstrb-row">
			<div class="vstrb-field">
				<label><?php echo esc_html__('Billing Street', 'vachr-short-term-rental-reservations') . esc_html($required_mark); ?></label>
				<input type="text" class="<?php echo esc_attr($class_prefix); ?>billing-street" name="<?php echo esc_attr($name_prefix); ?>street" autocomplete="section-vstrb-billing street-address"<?php echo $required_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static required attribute marker. ?>>
			</div>
		</div>
		<div class="vstrb-row vstrb-row-half">
			<div class="vstrb-field">
				<label><?php echo esc_html__('Billing City', 'vachr-short-term-rental-reservations') . esc_html($required_mark); ?></label>
				<input type="text" class="<?php echo esc_attr($class_prefix); ?>billing-city" name="<?php echo esc_attr($name_prefix); ?>city" autocomplete="section-vstrb-billing address-level2"<?php echo $required_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static required attribute marker. ?>>
			</div>
			<div class="vstrb-field">
				<label><?php echo esc_html__('ZIP Code', 'vachr-short-term-rental-reservations') . esc_html($required_mark); ?></label>
				<input type="text" class="<?php echo esc_attr($class_prefix); ?>billing-zip" name="<?php echo esc_attr($name_prefix); ?>zip" autocomplete="section-vstrb-billing postal-code"<?php echo $required_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static required attribute marker. ?>>
			</div>
		</div>
		<div class="vstrb-row">
			<div class="vstrb-field" style="max-width:calc(50% - 8px)">
				<label><?php echo esc_html__('Billing Country', 'vachr-short-term-rental-reservations') . esc_html($required_mark); ?></label>
				<input type="text" class="<?php echo esc_attr($class_prefix); ?>billing-country" name="<?php echo esc_attr($name_prefix); ?>country" autocomplete="section-vstrb-billing country-name"<?php echo $required_attr; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Static required attribute marker. ?>>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	private static function normalize_locale_code($locale) {
		$locale = strtolower(trim((string) $locale));
		$locale = str_replace('-', '_', $locale);
		if (preg_match('/^([a-z]{2})/', $locale, $m)) {
			return $m[1];
		}
		return '';
	}

	private static function resolve_wp_locale($language) {
		$language = sanitize_text_field((string) $language);
		$language = str_replace('-', '_', $language);
		if ($language === '') {
			return '';
		}
		if (function_exists('PLL') && PLL()->model) {
			$lang = PLL()->model->get_language($language);
			if ($lang && !empty($lang->locale)) {
				return (string) $lang->locale;
			}
		}
		if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $language)) {
			return $language;
		}
		$map = [
			'cs' => 'cs_CZ',
			'de' => 'de_DE',
			'en' => 'en_US',
			'es' => 'es_ES',
			'fr' => 'fr_FR',
			'it' => 'it_IT',
			'nl' => 'nl_NL',
			'pl' => 'pl_PL',
			'pt' => 'pt_PT',
			'ru' => 'ru_RU',
			'sk' => 'sk_SK',
			'uk' => 'uk_UA',
			'zh' => 'zh_CN',
		];
		$key = strtolower(substr($language, 0, 2));
		return $map[$key] ?? $language;
	}

	private static function country_from_locale($locale) {
		$locale = str_replace('-', '_', sanitize_text_field((string) $locale));
		if (preg_match('/^[a-z]{2}_([A-Z]{2})$/', $locale, $matches) && VSTRB_Countries::is_valid($matches[1])) {
			return $matches[1];
		}
		return '';
	}

	private static function load_textdomain_for_locale($locale) {
		$locale = sanitize_text_field((string) $locale);
		if ($locale === '') {
			return;
		}
		$domain = 'vachr-short-term-rental-reservations';
		$mofile = VSTRB_PLUGIN_DIR . 'languages/' . $domain . '-' . $locale . '.mo';
		if (is_readable($mofile)) {
			unload_textdomain($domain, true);
			load_textdomain($domain, $mofile);
		}
	}

	private static function normalize_unit_lookup_name($name) {
		$name = wp_strip_all_tags((string) $name);
		$name = html_entity_decode($name, ENT_QUOTES | ENT_HTML5, 'UTF-8');
		$name = remove_accents($name);
		$name = strtolower($name);
		$name = preg_replace('/[^a-z0-9]+/', ' ', $name);
		return trim((string) preg_replace('/\s+/', ' ', (string) $name));
	}

	private static function get_text_query_param($param) {
		$param = preg_replace('/[^a-zA-Z0-9_\-]/', '', (string) $param);
		// Public lookup shortcodes accept read-only query parameters, so no nonce is required.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ($param === '' || !isset($_GET[$param])) {
			return '';
		}
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only public query parameter, sanitized after scalar normalization.
		$value = wp_unslash($_GET[$param]);
		if (is_array($value)) {
			$value = reset($value);
		}
		return sanitize_text_field((string) $value);
	}

	private static function find_unit_by_public_name($name, $match = 'exact') {
		$needle = self::normalize_unit_lookup_name($name);
		if ($needle === '') {
			return 0;
		}

		$units = get_posts([
			'post_type' => 'vstrb_unit',
			'post_status' => ['publish', 'private', 'draft', 'pending', 'future'],
			'posts_per_page' => -1,
			'fields' => 'ids',
			'orderby' => 'title',
			'order' => 'ASC',
			'no_found_rows' => true,
		]);

		$contains_matches = [];
		foreach ((array) $units as $unit_id) {
			$names = [get_the_title((int) $unit_id)];
			$aliases = (string) get_post_meta((int) $unit_id, '_vstrb_public_lookup_names', true);
			if ($aliases !== '') {
				$names = array_merge($names, preg_split('/\r\n|\r|\n/', $aliases));
			}

			foreach ($names as $public_name) {
				$public_name = self::normalize_unit_lookup_name($public_name);
				if ($public_name === '') {
					continue;
				}
				if ($public_name === $needle) {
					return (int) $unit_id;
				}
				if ($match === 'contains' && (strpos($public_name, $needle) !== false || strpos($needle, $public_name) !== false)) {
					$contains_matches[(int) $unit_id] = (int) $unit_id;
				}
			}
		}

		return count($contains_matches) === 1 ? (int) reset($contains_matches) : 0;
	}

	private static function normalize_unit_lookup_code($code) {
		$code = strtolower(trim((string) $code));
		return preg_replace('/[^a-z0-9_\-]/', '', $code);
	}

	private static function find_unit_by_public_code($code) {
		$code = self::normalize_unit_lookup_code($code);
		if ($code === '') {
			return 0;
		}

		$units = get_posts([
			'post_type' => 'vstrb_unit',
			'post_status' => ['publish', 'private', 'draft', 'pending', 'future'],
			'posts_per_page' => 2,
			'fields' => 'ids',
			'no_found_rows' => true,
			// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Public lookup code is stored as post meta.
			'meta_query' => [
				[
					'key' => '_vstrb_public_lookup_code',
					'value' => $code,
					'compare' => '=',
				],
			],
		]);

		return count((array) $units) === 1 ? (int) $units[0] : 0;
	}

	private static function build_unit_placeholder_values($unit_id, $locale = '', $context = []) {
		$unit_id = (int) $unit_id;
		$lang = self::normalize_locale_code($locale);
		$supplier_account = VSTRB_Payment_QR::supplier_account_details();
		$values = [
			'apartment_id' => (string) $unit_id,
			'apartment_name' => get_the_title($unit_id),
			'unit_id' => (string) $unit_id,
			'unit_name' => get_the_title($unit_id),
			'site_name' => get_bloginfo('name'),
			'checkin_time' => (string) get_post_meta($unit_id, '_vstrb_checkin', true),
			'checkout_time' => (string) get_post_meta($unit_id, '_vstrb_checkout', true),
			'capacity' => (string) get_post_meta($unit_id, '_vstrb_capacity', true),
			'iban' => (string) ($supplier_account['iban'] ?? ''),
			'bank_account' => (string) ($supplier_account['account_number'] ?? ''),
			'account_number' => (string) ($supplier_account['account_number'] ?? ''),
			'account_country' => (string) ($supplier_account['country'] ?? ''),
		];

		foreach ($context as $key => $value) {
			if (is_scalar($value)) {
				$values[strtolower((string) $key)] = (string) $value;
			}
		}
		$invoice_number = (string) ($values['invoice_number'] ?? '');
		$payment_reference = (string) ($values['payment_reference'] ?? $invoice_number);
		$variable_symbol = preg_replace('/\D+/', '', $payment_reference);
		$values['variable_symbol'] = $variable_symbol !== '' ? substr($variable_symbol, -10) : '';
		$values['payment_reference'] = $payment_reference;
		$values['amount'] = (string) ($values['payment_amount'] ?? $values['payment_amount_due'] ?? '');
		$values['currency'] = (string) ($values['payment_currency'] ?? '');

		$custom = get_post_meta($unit_id, '_vstrb_content_placeholders', true);
		if (is_array($custom)) {
			foreach ($custom as $key => $row) {
				$key = strtolower(trim((string) $key));
				if ($key === '') {
					continue;
				}
				if (is_array($row)) {
					$i18n = isset($row['i18n']) && is_array($row['i18n']) ? $row['i18n'] : [];
					if ($lang !== '' && isset($i18n[$lang]) && trim(wp_strip_all_tags((string) $i18n[$lang])) !== '') {
						$values[$key] = (string) $i18n[$lang];
					} else {
						$values[$key] = (string) ($row['value'] ?? '');
					}
				} else {
					$values[$key] = (string) $row;
				}
			}
		}

		return $values;
	}

	private static function apply_unit_placeholders($content, $unit_id, $locale = '', $context = []) {
		$content = (string) $content;
		if ($content === '' || strpos($content, '{{') === false) {
			return $content;
		}
		$values = self::build_unit_placeholder_values($unit_id, $locale, $context);
		return (string) preg_replace_callback('/\{\{\s*([a-zA-Z0-9_]+)(?:\|([^}]*))?\s*\}\}/', static function($m) use ($values) {
			$key = strtolower((string) ($m[1] ?? ''));
			if (array_key_exists($key, $values) && $values[$key] !== '') {
				return (string) $values[$key];
			}
			return isset($m[2]) ? (string) $m[2] : '';
		}, $content);
	}

	private static function resolve_unit_content($unit_id, $field, $locale = '', $context = []) {
		$unit_id = (int) $unit_id;
		if (!VSTRB_DB::unit_exists($unit_id)) {
			return '';
		}
		$lang = self::normalize_locale_code($locale);
		if ($lang === '') {
			$lang = self::normalize_locale_code(get_locale());
		}
		$field = in_array($field, ['description', 'amenities', 'instructions', 'bank_transfer_instructions'], true) ? $field : 'description';
		$meta_map = [
			'description' => '_vstrb_description_i18n',
			'amenities' => '_vstrb_amenities_i18n',
			'instructions' => '_vstrb_instructions_i18n',
			'bank_transfer_instructions' => '_vstrb_bank_transfer_instructions_i18n',
		];
		$template_map = [
			'description' => '_vstrb_description_template_id',
			'amenities' => '_vstrb_amenities_template_id',
			'instructions' => '_vstrb_instructions_template_id',
			'bank_transfer_instructions' => '_vstrb_bank_transfer_instructions_template_id',
		];

		$values = get_post_meta($unit_id, $meta_map[$field], true);
		if (is_array($values)) {
			if ($lang !== '' && !empty($values[$lang])) {
				return self::apply_unit_placeholders((string) $values[$lang], $unit_id, $lang, $context);
			}
			foreach ($values as $value) {
				if (trim(wp_strip_all_tags((string) $value)) !== '') {
					return self::apply_unit_placeholders((string) $value, $unit_id, $lang, $context);
				}
			}
		}

		$template_id = (string) get_post_meta($unit_id, $template_map[$field], true);
		$templates = get_option('vstrb_unit_content_templates', []);
		if ($template_id !== '' && is_array($templates) && isset($templates[$template_id]) && is_array($templates[$template_id])) {
			$template_values = $templates[$template_id][$field] ?? [];
			if (is_array($template_values)) {
				if ($lang !== '' && !empty($template_values[$lang])) {
					return self::apply_unit_placeholders((string) $template_values[$lang], $unit_id, $lang, $context);
				}
				foreach ($template_values as $value) {
					if (trim(wp_strip_all_tags((string) $value)) !== '') {
						return self::apply_unit_placeholders((string) $value, $unit_id, $lang, $context);
					}
				}
			}
		}

		if ($field === 'instructions') {
			return self::apply_unit_placeholders((string) get_post_meta($unit_id, '_vstrb_instructions', true), $unit_id, $lang, $context);
		}
		if ($field === 'description') {
			$post = get_post($unit_id);
			return $post ? self::apply_unit_placeholders((string) $post->post_content, $unit_id, $lang, $context) : '';
		}
		if ($field === 'bank_transfer_instructions') {
			return self::apply_unit_placeholders((string) get_option('vstrb_payment_bank_details', ''), $unit_id, $lang, $context);
		}
		return '';
	}

	public static function register() {
		add_filter('the_password_form', [__CLASS__, 'preserve_password_form_query']);
		add_action('login_form_postpass', [__CLASS__, 'prepare_postpass_redirect'], 0);

		add_shortcode('vstrb_booking_form', [__CLASS__, 'booking_form']);
		add_shortcode('vstrb_availability_calendar', [__CLASS__, 'availability_calendar']);
		add_shortcode('vstrb_guest_registration', [__CLASS__, 'guest_registration_form']);
		add_shortcode('vstrb_staff_bookings', [__CLASS__, 'staff_bookings']);
		add_shortcode('vstrb_staff_tasks', [__CLASS__, 'staff_tasks']);
		add_shortcode('vstrb_unit_description', [__CLASS__, 'unit_description']);
		add_shortcode('vstrb_unit_amenity_list', [__CLASS__, 'unit_amenity_list']);
		add_shortcode('vstrb_unit_arrival_instructions', [__CLASS__, 'unit_arrival_instructions']);
		add_shortcode('vstrb_unit_arrival_instructions_by_name', [__CLASS__, 'unit_arrival_instructions_by_name']);
		add_shortcode('vstrb_unit_arrival_instructions_by_code', [__CLASS__, 'unit_arrival_instructions_by_code']);
		add_shortcode('vstrb_unit_arrival_instructions_by_reservation', [__CLASS__, 'unit_arrival_instructions_by_reservation']);
		add_shortcode('vstrb_unit_bank_transfer_instructions', [__CLASS__, 'unit_bank_transfer_instructions']);
	}

	private static function current_request_url_with_query() {
		$request_uri = isset($_SERVER['REQUEST_URI']) ? sanitize_url(wp_unslash($_SERVER['REQUEST_URI'])) : '';
		if ($request_uri === '' || strpos($request_uri, '?') === false || preg_match('/[\r\n]/', $request_uri)) {
			return '';
		}
		return home_url($request_uri);
	}

	public static function preserve_password_form_query($form) {
		$redirect_url = self::current_request_url_with_query();
		if ($redirect_url === '') {
			return $form;
		}

		$hidden_referer = '<input type="hidden" name="_wp_http_referer" value="' . esc_attr($redirect_url) . '" />';
		$hidden_redirect = '<input type="hidden" name="redirect_to" value="' . esc_attr($redirect_url) . '" />';
		$form = (string) $form;

		$form = (string) preg_replace_callback('/\saction=([\'"])([^\'"]*action=postpass[^\'"]*)\1/i', static function($m) use ($redirect_url) {
			$quote = (string) $m[1];
			$action = html_entity_decode((string) $m[2], ENT_QUOTES | ENT_HTML5, 'UTF-8');
			$action = add_query_arg('redirect_to', $redirect_url, $action);
			return ' action=' . $quote . esc_url($action) . $quote;
		}, $form, 1);

		if (strpos($form, 'name="_wp_http_referer"') !== false || strpos($form, "name='_wp_http_referer'") !== false) {
			$form = (string) preg_replace_callback('/<input[^>]+name=[\'"]_wp_http_referer[\'"][^>]*>/i', static function() use ($hidden_referer) {
				return $hidden_referer;
			}, $form, 1);
		} else {
			$form = self::insert_hidden_before_form_end($form, $hidden_referer);
		}

		if (strpos($form, 'name="redirect_to"') !== false || strpos($form, "name='redirect_to'") !== false) {
			return (string) preg_replace_callback('/<input[^>]+name=[\'"]redirect_to[\'"][^>]*>/i', static function() use ($hidden_redirect) {
				return $hidden_redirect;
			}, $form, 1);
		}

		return self::insert_hidden_before_form_end($form, $hidden_redirect);
	}

	private static function insert_hidden_before_form_end($form, $hidden) {
		if (stripos((string) $form, '</form>') !== false) {
			return (string) preg_replace_callback('/<\/form>/i', static function() use ($hidden) {
				return $hidden . '</form>';
			}, (string) $form, 1);
		}
		return (string) $form . (string) $hidden;
	}

	public static function prepare_postpass_redirect() {
		// WordPress' post password form does not include a plugin nonce; redirect_to is validated below.
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$redirect_to = isset($_REQUEST['redirect_to']) ? sanitize_url(wp_unslash($_REQUEST['redirect_to'])) : '';
		if ($redirect_to === '' || preg_match('/[\r\n]/', $redirect_to)) {
			return;
		}

		$redirect_to = wp_validate_redirect(esc_url_raw($redirect_to), '');
		if ($redirect_to === '') {
			return;
		}

		$_POST['_wp_http_referer'] = $redirect_to;
		$_SERVER['HTTP_REFERER'] = $redirect_to;
	}

	private static function render_staff_line_list(array $lines) {
		if (empty($lines)) {
			return '';
		}
		$out = '<dl class="vstrb-staff-lines">';
		foreach ($lines as $label => $value) {
			$out .= '<div><dt>' . esc_html($label) . '</dt><dd>' . esc_html((string) $value) . '</dd></div>';
		}
		$out .= '</dl>';
		return $out;
	}

	private static function staff_bookings_styles() {
		return '<style>
			.vstrb-staff-view{display:grid;gap:18px}
			.vstrb-staff-list{display:grid;gap:10px}
			.vstrb-staff-card{border:1px solid #dcdcde;border-radius:6px;padding:12px;background:#fff}
			.vstrb-staff-card h4{margin:0 0 8px;font-size:16px}
			.vstrb-staff-lines{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:8px 14px;margin:0}
			.vstrb-staff-lines div{min-width:0}
			.vstrb-staff-lines dt{font-weight:600;font-size:12px;color:#555;margin:0}
			.vstrb-staff-lines dd{margin:2px 0 0}
		</style>';
	}

	public static function staff_bookings($atts) {
		global $wpdb;
		$atts = shortcode_atts([
			'email' => '',
			'unit' => 0,
			'days' => 30,
			'limit' => 50,
			'include_registrations' => '1',
			'status' => 'active',
		], $atts, 'vstrb_staff_bookings');

		$email = sanitize_email((string) $atts['email']);
		$rule = VSTRB_Staff_Access::get_rule($email);
		if (!$rule) {
			return '<p>' . esc_html__('No staff access rule found for this email.', 'vachr-short-term-rental-reservations') . '</p>';
		}

		$t = VSTRB_DB::tables();
		$bookings_table = self::safe_table_name($t['bookings'] ?? '');
		$registrations_table = self::safe_table_name($t['guest_registrations'] ?? '');
		if ($bookings_table === '' || $registrations_table === '') {
			return '<p>' . esc_html__('No bookings found.', 'vachr-short-term-rental-reservations') . '</p>';
		}
		$unit_id = max(0, (int) $atts['unit']);
		$days = max(1, min(365, (int) $atts['days']));
		$limit = max(1, min(200, (int) $atts['limit']));
		$today = current_time('Y-m-d');
		$date_to = gmdate('Y-m-d', strtotime($today . ' +' . $days . ' days'));

		$where = ['date_to >= %s', 'date_from <= %s'];
		$params = [$today, $date_to];
		if ($unit_id > 0) {
			$where[] = 'unit_id = %d';
			$params[] = $unit_id;
		}
		if ((string) $atts['status'] === 'active') {
			$where[] = "status NOT IN ('cancelled','blocked')";
		}
		$where_sql = implode(' AND ', $where);
		$bookings = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with validated table name.
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is validated and WHERE fragments are static placeholders.
				"SELECT * FROM {$bookings_table} WHERE {$where_sql} ORDER BY date_from ASC, id ASC LIMIT %d",
				...array_merge($params, [$limit])
			)
		);

		$include_registrations = self::parse_bool_shortcode_att($atts['include_registrations'], true);
		$registration_groups = [];
		if ($include_registrations) {
			$reg_where = ['date_to >= %s', 'date_from <= %s'];
			$reg_params = [$today, $date_to];
			if ($unit_id > 0) {
				$reg_where[] = 'unit_id = %d';
				$reg_params[] = $unit_id;
			}
			$reg_where_sql = implode(' AND ', $reg_where);
			$reg_rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with validated table name.
				$wpdb->prepare(
					// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is validated and WHERE fragments are static placeholders.
					"SELECT * FROM {$registrations_table} WHERE {$reg_where_sql} ORDER BY date_from ASC, unit_id ASC, id ASC LIMIT %d",
					...array_merge($reg_params, [$limit * 5])
				)
			);
			foreach ((array) $reg_rows as $row) {
				$key = !empty($row->booking_reference)
					? 'ref:' . (int) $row->unit_id . ':' . (string) $row->booking_reference
					: 'stay:' . (int) $row->unit_id . ':' . (string) $row->date_from . ':' . (string) $row->date_to . ':' . (string) $row->email;
				if (!isset($registration_groups[$key])) {
					$registration_groups[$key] = [];
				}
				$registration_groups[$key][] = $row;
			}
		}

		$out = self::staff_bookings_styles();
		$out .= '<div class="vstrb vstrb-staff-view">';
		$out .= '<section><h3>' . esc_html__('Bookings', 'vachr-short-term-rental-reservations') . '</h3>';
		if (empty($bookings)) {
			$out .= '<p>' . esc_html__('No bookings found.', 'vachr-short-term-rental-reservations') . '</p>';
		} else {
			$out .= '<div class="vstrb-staff-list">';
			foreach ($bookings as $booking) {
				$title = get_the_title((int) ($booking->unit_id ?? 0));
				$out .= '<article class="vstrb-staff-card"><h4>' . esc_html($title) . '</h4>';
				$out .= self::render_staff_line_list(VSTRB_Staff_Access::booking_lines($booking, $rule));
				$out .= '</article>';
			}
			$out .= '</div>';
		}
		$out .= '</section>';

		if ($include_registrations) {
			$out .= '<section><h3>' . esc_html__('Guest Registrations', 'vachr-short-term-rental-reservations') . '</h3>';
			if (empty($registration_groups)) {
				$out .= '<p>' . esc_html__('No registrations found.', 'vachr-short-term-rental-reservations') . '</p>';
			} else {
				$out .= '<div class="vstrb-staff-list">';
				foreach ($registration_groups as $group) {
					$rep = $group[0];
					$guest_names = [];
					foreach ($group as $guest) {
						$guest_names[] = trim((string) ($guest->first_name ?? '') . ' ' . (string) ($guest->last_name ?? ''));
					}
					$title = get_the_title((int) ($rep->unit_id ?? 0));
					$lines = VSTRB_Staff_Access::registration_group_lines(
						(int) ($rep->unit_id ?? 0),
						count($group),
						(string) ($rep->date_from ?? ''),
						(string) ($rep->date_to ?? ''),
						(string) ($rep->email ?? ''),
						(string) ($rep->source ?? ''),
						(string) ($rep->booking_reference ?? ''),
						$guest_names,
						$rule
					);
					$out .= '<article class="vstrb-staff-card"><h4>' . esc_html($title) . '</h4>';
					$out .= self::render_staff_line_list($lines);
					$out .= '</article>';
				}
				$out .= '</div>';
			}
			$out .= '</section>';
		}
		$out .= '</div>';
		return $out;
	}

	public static function staff_tasks($atts) {
		$atts = shortcode_atts(['email' => '', 'unit' => 0, 'days' => 90], $atts, 'vstrb_staff_tasks');
		return VSTRB_Staff_Tasks::render_calendar(
			sanitize_email((string) $atts['email']),
			max(1, min(365, (int) $atts['days'])),
			max(0, (int) $atts['unit'])
		);
	}

	public static function unit_description($atts) {
		$atts = shortcode_atts(['unit' => 0, 'language' => ''], $atts, 'vstrb_unit_description');
		$content = self::resolve_unit_content((int) $atts['unit'], 'description', sanitize_text_field($atts['language']));
		return $content !== '' ? '<div class="vstrb-unit-description">' . wp_kses_post(wpautop($content)) . '</div>' : '';
	}

	private static function resolve_unit_amenity_rows($unit_id, $locale = '') {
		global $wpdb;
		$unit_id = (int) $unit_id;
		if (!VSTRB_DB::unit_exists($unit_id)) {
			return [];
		}
		$t = VSTRB_DB::tables();
		$lang = self::normalize_locale_code($locale);
		if ($lang === '') {
			$lang = self::normalize_locale_code(get_locale());
		}
		$amenities_table = self::safe_table_name($t['amenities'] ?? '');
		$unit_amenities_table = self::safe_table_name($t['unit_amenities'] ?? '');
		$amenity_meta_table = self::safe_table_name($t['amenity_meta'] ?? '');
		if ($amenities_table === '' || $unit_amenities_table === '' || $amenity_meta_table === '') {
			return [];
		}
		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table names are validated by safe_table_name().
		$amenities = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with validated table names.
			$wpdb->prepare(
				"SELECT a.id, a.name, a.code, a.category, a.description
			 FROM {$amenities_table} a
			 INNER JOIN {$unit_amenities_table} ua ON ua.amenity_id=a.id
			 WHERE ua.unit_id=%d AND a.is_active=1
			 ORDER BY a.sort_order ASC, a.category ASC, a.name ASC",
				$unit_id
			),
			ARRAY_A
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		if (!$amenities) {
			return [];
		}

		$ids = array_map('intval', wp_list_pluck($amenities, 'id'));
		$placeholders = implode(',', array_fill(0, count($ids), '%d'));
		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is validated and placeholder list is generated from integer IDs.
		$meta_rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with validated table name.
			$wpdb->prepare(
				"SELECT amenity_id, meta_key, meta_value, locale
			 FROM {$amenity_meta_table}
			 WHERE amenity_id IN ({$placeholders})",
				...$ids
			),
			ARRAY_A
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$meta = [];
		foreach ((array) $meta_rows as $row) {
			$aid = (int) ($row['amenity_id'] ?? 0);
			$key = strtolower((string) ($row['meta_key'] ?? ''));
			$loc = self::normalize_locale_code((string) ($row['locale'] ?? ''));
			if ($aid <= 0 || $key === '' || $loc === '') {
				continue;
			}
			$meta[$aid][$key][$loc] = (string) ($row['meta_value'] ?? '');
		}

		foreach ($amenities as &$amenity) {
			$aid = (int) ($amenity['id'] ?? 0);
			if ($lang !== '' && !empty($meta[$aid]['label'][$lang])) {
				$amenity['name'] = $meta[$aid]['label'][$lang];
			} elseif (!empty($amenity['name'])) {
				// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText -- Amenity names are seeded/user-defined labels with bundled PO fallbacks.
				$amenity['name'] = __((string) $amenity['name'], 'vachr-short-term-rental-reservations');
			}
			if ($lang !== '' && !empty($meta[$aid]['description'][$lang])) {
				$amenity['description'] = $meta[$aid]['description'][$lang];
			}
		}
		unset($amenity);

		return $amenities;
	}

	public static function unit_amenity_list($atts) {
		$atts = shortcode_atts([
			'unit' => 0,
			'language' => '',
			'show_descriptions' => '0',
			'grouped' => '0',
		], $atts, 'vstrb_unit_amenity_list');
		$rows = self::resolve_unit_amenity_rows((int) $atts['unit'], sanitize_text_field($atts['language']));
		if (!$rows) {
			return '';
		}
		$show_descriptions = in_array(strtolower((string) $atts['show_descriptions']), ['1', 'yes', 'true'], true);
		$grouped = in_array(strtolower((string) $atts['grouped']), ['1', 'yes', 'true'], true);
		$out = '<div class="vstrb-unit-amenity-list">';
		if ($grouped) {
			$groups = [];
			foreach ($rows as $row) {
				$category = sanitize_key((string) ($row['category'] ?? 'general'));
				$groups[$category][] = $row;
			}
			foreach ($groups as $category => $items) {
				$out .= '<section class="vstrb-unit-amenity-group" data-category="' . esc_attr($category) . '"><ul>';
				foreach ($items as $item) {
					$out .= '<li><span>' . esc_html($item['name']) . '</span>';
					if ($show_descriptions && !empty($item['description'])) {
						$out .= '<small>' . esc_html($item['description']) . '</small>';
					}
					$out .= '</li>';
				}
				$out .= '</ul></section>';
			}
		} else {
			$out .= '<ul>';
			foreach ($rows as $item) {
				$out .= '<li><span>' . esc_html($item['name']) . '</span>';
				if ($show_descriptions && !empty($item['description'])) {
					$out .= '<small>' . esc_html($item['description']) . '</small>';
				}
				$out .= '</li>';
			}
			$out .= '</ul>';
		}
		$out .= '</div>';
		return $out;
	}

	public static function unit_arrival_instructions($atts) {
		$atts = shortcode_atts(['unit' => 0, 'language' => ''], $atts, 'vstrb_unit_arrival_instructions');
		$content = self::resolve_unit_content((int) $atts['unit'], 'instructions', sanitize_text_field($atts['language']));
		return $content !== '' ? '<div class="vstrb-unit-arrival-instructions">' . wp_kses_post(wpautop($content)) . '</div>' : '';
	}

	public static function unit_arrival_instructions_by_name($atts) {
		$atts = shortcode_atts([
			'param' => 'unit_name',
			'name' => '',
			'language' => '',
			'match' => 'exact',
			'show_title' => '0',
			'fallback' => '',
		], $atts, 'vstrb_unit_arrival_instructions_by_name');

		$name = (string) $atts['name'];
		if ($name === '') {
			$name = self::get_text_query_param((string) $atts['param']);
		}

		$match = strtolower(sanitize_key((string) $atts['match'])) === 'contains' ? 'contains' : 'exact';
		$unit_id = self::find_unit_by_public_name($name, $match);
		if ($unit_id <= 0) {
			$fallback = (string) $atts['fallback'];
			return $fallback !== '' ? '<div class="vstrb-unit-arrival-instructions vstrb-unit-arrival-instructions-missing">' . wp_kses_post(wpautop($fallback)) . '</div>' : '';
		}

		$content = self::resolve_unit_content($unit_id, 'instructions', sanitize_text_field($atts['language']));
		if ($content === '') {
			return '';
		}

		$out = '<div class="vstrb-unit-arrival-instructions vstrb-unit-arrival-instructions-by-name">';
		if (self::parse_bool_shortcode_att($atts['show_title'], false)) {
			$out .= '<h3>' . esc_html(get_the_title($unit_id)) . '</h3>';
		}
		$out .= wp_kses_post(wpautop($content)) . '</div>';
		return $out;
	}

	public static function unit_arrival_instructions_by_code($atts) {
		$atts = shortcode_atts([
			'param' => 'unit_code',
			'code' => '',
			'language' => '',
			'show_title' => '0',
			'fallback' => '',
		], $atts, 'vstrb_unit_arrival_instructions_by_code');

		$code = self::normalize_unit_lookup_code((string) $atts['code']);
		if ($code === '') {
			$code = self::normalize_unit_lookup_code(self::get_text_query_param((string) $atts['param']));
		}

		$unit_id = self::find_unit_by_public_code($code);
		if ($unit_id <= 0) {
			$fallback = (string) $atts['fallback'];
			return $fallback !== '' ? '<div class="vstrb-unit-arrival-instructions vstrb-unit-arrival-instructions-missing">' . wp_kses_post(wpautop($fallback)) . '</div>' : '';
		}

		$content = self::resolve_unit_content($unit_id, 'instructions', sanitize_text_field($atts['language']));
		if ($content === '') {
			return '';
		}

		$out = '<div class="vstrb-unit-arrival-instructions vstrb-unit-arrival-instructions-by-code">';
		if (self::parse_bool_shortcode_att($atts['show_title'], false)) {
			$out .= '<h3>' . esc_html(get_the_title($unit_id)) . '</h3>';
		}
		$out .= wp_kses_post(wpautop($content)) . '</div>';
		return $out;
	}

	private static function add_shortcode_date_candidate(&$dates, $year, $month, $day) {
		$year = (int) $year;
		$month = (int) $month;
		$day = (int) $day;
		if ($year > 0 && $year < 100) {
			$year += $year >= 70 ? 1900 : 2000;
		}
		if ($year < 1900 || $year > 2100 || !checkdate($month, $day, $year)) {
			return;
		}
		$dates[sprintf('%04d-%02d-%02d', $year, $month, $day)] = true;
	}

	private static function parse_shortcode_date_candidates($value) {
		$value = sanitize_text_field((string) $value);
		$value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
		$value = trim($value);
		if ($value === '') {
			return [];
		}
		$dates = [];

		if (preg_match('/^(\d{4})-(\d{2})-(\d{2})/', $value, $m)) {
			self::add_shortcode_date_candidate($dates, $m[1], $m[2], $m[3]);
			return array_keys($dates);
		}

		preg_match_all('/\d{1,4}/', $value, $matches);
		$parts = array_map('intval', (array) ($matches[0] ?? []));
		if (count($parts) >= 3) {
			$a = $parts[0];
			$b = $parts[1];
			$c = $parts[2];
			if ($a > 31 || strlen((string) ($matches[0][0] ?? '')) === 4) {
				self::add_shortcode_date_candidate($dates, $a, $b, $c);
				self::add_shortcode_date_candidate($dates, $a, $c, $b);
			} elseif ($c > 31 || strlen((string) ($matches[0][2] ?? '')) === 4) {
				self::add_shortcode_date_candidate($dates, $c, $b, $a);
				self::add_shortcode_date_candidate($dates, $c, $a, $b);
			} else {
				self::add_shortcode_date_candidate($dates, $c, $b, $a);
				self::add_shortcode_date_candidate($dates, $c, $a, $b);
			}
		}

		return array_keys($dates);
	}

	private static function normalize_guest_lookup_text($value) {
		$value = wp_strip_all_tags((string) $value);
		$value = html_entity_decode($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
		$value = remove_accents($value);
		$value = strtolower($value);
		$value = preg_replace('/[^a-z0-9]+/', ' ', $value);
		return trim((string) preg_replace('/\s+/', ' ', (string) $value));
	}

	private static function guest_name_matches($row_first, $row_last, $first_name, $last_name, $mode = 'any') {
		$first_name = self::normalize_guest_lookup_text($first_name);
		$last_name = self::normalize_guest_lookup_text($last_name);
		if ($first_name === '' && $last_name === '') {
			return true;
		}
		$row_first = self::normalize_guest_lookup_text($row_first);
		$row_last = self::normalize_guest_lookup_text($row_last);

		if ($mode === 'both') {
			return $first_name !== '' && $last_name !== '' && $row_first === $first_name && $row_last === $last_name;
		}
		if ($mode === 'first') {
			return $first_name !== '' && $row_first === $first_name;
		}
		if ($mode === 'last') {
			return $last_name !== '' && $row_last === $last_name;
		}
		return ($first_name !== '' && $row_first === $first_name) || ($last_name !== '' && $row_last === $last_name);
	}

	private static function booking_context_from_row($booking, $first_name = '', $last_name = '') {
		if (!$booking) {
			return [];
		}
		$first_name = $first_name !== '' ? $first_name : (string) ($booking->customer_name ?? '');
		$last_name = $last_name !== '' ? $last_name : (string) ($booking->customer_lastname ?? '');
		return [
			'booking_id' => (string) (int) ($booking->id ?? 0),
			'guest_name' => trim($first_name . ' ' . $last_name),
			'guest_first_name' => $first_name,
			'guest_last_name' => $last_name,
			'guest_email' => (string) ($booking->customer_email ?? ''),
			'date_from' => (string) ($booking->date_from ?? ''),
			'date_to' => (string) ($booking->date_to ?? ''),
		];
	}

	private static function get_unique_unit_match(array $matches) {
		$unit_matches = [];
		foreach ($matches as $match) {
			$match_unit_id = (int) ($match['unit_id'] ?? 0);
			if ($match_unit_id > 0) {
				$unit_matches[$match_unit_id] = $match;
			}
		}
		return count($unit_matches) === 1 ? reset($unit_matches) : null;
	}

	private static function filter_arrival_instruction_matches_by_name(array $matches, $first_name, $last_name, $mode) {
		$filtered = [];
		foreach ($matches as $key => $match) {
			if (self::guest_name_matches($match['first_name'] ?? '', $match['last_name'] ?? '', $first_name, $last_name, $mode)) {
				$filtered[$key] = $match;
			}
		}
		return $filtered;
	}

	private static function collect_arrival_instruction_matches_for_dates($date_from, $date_to, $unit_id = 0) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$bookings_table = self::safe_table_name($t['bookings'] ?? '');
		$registrations_table = self::safe_table_name($t['guest_registrations'] ?? '');
		if ($bookings_table === '' || $date_from === '' || $date_to === '') {
			return [];
		}

		$where = ["date_from = %s", "date_to = %s", "status <> 'cancelled'"];
		$params = [$date_from, $date_to];
		if ($unit_id > 0) {
			$where[] = 'unit_id = %d';
			$params[] = (int) $unit_id;
		}
		$where_sql = implode(' AND ', $where);
		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table name is validated and WHERE fragments are static placeholders.
		$rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with validated table name.
			$wpdb->prepare(
				"SELECT * FROM {$bookings_table} WHERE {$where_sql} ORDER BY id ASC LIMIT 50",
				...$params
			)
		);
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$matches = [];
		foreach ((array) $rows as $booking) {
			$matches['booking:' . (int) $booking->id] = [
				'unit_id' => (int) ($booking->unit_id ?? 0),
				'first_name' => (string) ($booking->customer_name ?? ''),
				'last_name' => (string) ($booking->customer_lastname ?? ''),
				'context' => self::booking_context_from_row($booking),
			];
		}

		if ($registrations_table !== '') {
			$reg_where = ['gr.date_from = %s', 'gr.date_to = %s'];
			$reg_params = [$date_from, $date_to];
			if ($unit_id > 0) {
				$reg_where[] = 'gr.unit_id = %d';
				$reg_params[] = (int) $unit_id;
			}
			$reg_where_sql = implode(' AND ', $reg_where);
			// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Table names are validated and WHERE fragments are static placeholders.
			$regs = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter -- Custom table query with validated table names.
				$wpdb->prepare(
					"SELECT gr.*, b.id AS linked_booking_id, b.unit_id AS linked_unit_id, b.customer_email, b.customer_name, b.customer_lastname
					 FROM {$registrations_table} gr
					 LEFT JOIN {$bookings_table} b ON b.id = gr.booking_id AND b.status <> 'cancelled'
					 WHERE {$reg_where_sql}
					 ORDER BY gr.id ASC
					 LIMIT 20",
					...$reg_params
				)
			);
			// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare, PluginCheck.Security.DirectDB.UnescapedDBParameter
			foreach ((array) $regs as $reg) {
				$matched_unit_id = (int) ($reg->linked_unit_id ?? 0);
				if ($matched_unit_id <= 0) {
					$matched_unit_id = (int) ($reg->unit_id ?? 0);
				}
				$key = ((int) ($reg->linked_booking_id ?? 0) > 0)
					? 'booking:' . (int) $reg->linked_booking_id
					: 'registration:' . (int) ($reg->id ?? 0);
				$matches[$key] = [
					'unit_id' => $matched_unit_id,
					'first_name' => (string) ($reg->first_name ?? ''),
					'last_name' => (string) ($reg->last_name ?? ''),
					'context' => [
						'booking_id' => (string) (int) ($reg->linked_booking_id ?? 0),
						'guest_name' => trim((string) ($reg->first_name ?? '') . ' ' . (string) ($reg->last_name ?? '')),
						'guest_first_name' => (string) ($reg->first_name ?? ''),
						'guest_last_name' => (string) ($reg->last_name ?? ''),
						'guest_email' => (string) ($reg->email ?? ($reg->customer_email ?? '')),
						'date_from' => (string) ($reg->date_from ?? ''),
						'date_to' => (string) ($reg->date_to ?? ''),
					],
				];
			}
		}

		return $matches;
	}

	private static function find_arrival_instruction_match(array $date_from_candidates, array $date_to_candidates, $first_name = '', $last_name = '', $unit_id = 0) {
		$matches = [];
		foreach ($date_from_candidates as $date_from) {
			foreach ($date_to_candidates as $date_to) {
				if ($date_from === '' || $date_to === '' || $date_to <= $date_from) {
					continue;
				}
				foreach (self::collect_arrival_instruction_matches_for_dates($date_from, $date_to, $unit_id) as $key => $match) {
					$matches[$date_from . ':' . $date_to . ':' . $key] = $match;
				}
			}
		}

		$match = self::get_unique_unit_match($matches);
		if ($match) {
			return $match;
		}

		$first_name = self::normalize_guest_lookup_text($first_name);
		$last_name = self::normalize_guest_lookup_text($last_name);
		$name_modes = [];
		if ($first_name !== '' && $last_name !== '') {
			$name_modes[] = 'both';
			$name_modes[] = 'last';
			$name_modes[] = 'first';
		} elseif ($last_name !== '') {
			$name_modes[] = 'last';
		} elseif ($first_name !== '') {
			$name_modes[] = 'first';
		}

		foreach ($name_modes as $mode) {
			$match = self::get_unique_unit_match(self::filter_arrival_instruction_matches_by_name($matches, $first_name, $last_name, $mode));
			if ($match) {
				return $match;
			}
		}

		return null;
	}

	private static function find_first_unit_with_arrival_instructions($locale = '') {
		$units = get_posts([
			'post_type' => 'vstrb_unit',
			'post_status' => ['publish', 'private', 'draft', 'pending', 'future'],
			'posts_per_page' => -1,
			'fields' => 'ids',
			'orderby' => 'title',
			'order' => 'ASC',
			'no_found_rows' => true,
		]);

		foreach ((array) $units as $unit_id) {
			$content = self::resolve_unit_content((int) $unit_id, 'instructions', $locale);
			if (trim(wp_strip_all_tags((string) $content)) !== '') {
				return (int) $unit_id;
			}
		}
		return 0;
	}

	private static function render_arrival_instructions_unavailable_message($message, $locale = '') {
		$message = trim((string) $message);
		if ($message === '') {
			$switched = false;
			$locale = sanitize_text_field((string) $locale);
			$locale_map = [
				'cs' => 'cs_CZ',
				'de' => 'de_DE',
				'es' => 'es_ES',
				'fr' => 'fr_FR',
				'it' => 'it_IT',
				'nl' => 'nl_NL',
				'pl' => 'pl_PL',
				'pt' => 'pt_PT',
				'ru' => 'ru_RU',
				'zh' => 'zh_CN',
			];
			$locale = $locale_map[strtolower($locale)] ?? $locale;
			if ($locale !== '' && function_exists('switch_to_locale')) {
				$switched = switch_to_locale($locale);
			}
			$message = __('Arrival instructions are not available yet. They will be shown closer to your arrival date.', 'vachr-short-term-rental-reservations');
			if ($switched) {
				restore_current_locale();
			}
		}
		return '<div class="vstrb-unit-arrival-instructions vstrb-unit-arrival-instructions-unavailable">' . wp_kses_post(wpautop($message)) . '</div>';
	}

	private static function can_show_arrival_instructions_for_date($date_from, $days_before) {
		$date_from = sanitize_text_field((string) $date_from);
		if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_from)) {
			return false;
		}
		$days_before = max(0, min(365, (int) $days_before));
		$available_from = gmdate('Y-m-d', strtotime($date_from . ' -' . $days_before . ' days'));
		return current_time('Y-m-d') >= $available_from;
	}

	public static function unit_arrival_instructions_by_reservation($atts) {
		$atts = shortcode_atts([
			'unit' => 0,
			'date_from_param' => 'date_from',
			'date_to_param' => 'date_to',
			'first_name_param' => 'first_name',
			'last_name_param' => 'last_name',
			'date_from' => '',
			'date_to' => '',
			'first_name' => '',
			'last_name' => '',
			'language' => '',
			'show_title' => '0',
			'fallback' => '',
			'available_days_before' => 1,
			'unavailable_text' => '',
		], $atts, 'vstrb_unit_arrival_instructions_by_reservation');

		$date_from_raw = (string) $atts['date_from'];
		if ($date_from_raw === '') {
			$date_from_raw = self::get_text_query_param((string) $atts['date_from_param']);
		}
		$date_to_raw = (string) $atts['date_to'];
		if ($date_to_raw === '') {
			$date_to_raw = self::get_text_query_param((string) $atts['date_to_param']);
		}
		$date_from_candidates = self::parse_shortcode_date_candidates($date_from_raw);
		$date_to_candidates = self::parse_shortcode_date_candidates($date_to_raw);
		$first_name = sanitize_text_field((string) $atts['first_name']);
		if ($first_name === '') {
			$first_name = self::get_text_query_param((string) $atts['first_name_param']);
		}
		$last_name = sanitize_text_field((string) $atts['last_name']);
		if ($last_name === '') {
			$last_name = self::get_text_query_param((string) $atts['last_name_param']);
		}
		$locale = sanitize_text_field((string) $atts['language']);
		$unit_id = 0;
		$context = [];

		if (!empty($date_from_candidates) && !empty($date_to_candidates)) {
			$match = self::find_arrival_instruction_match($date_from_candidates, $date_to_candidates, $first_name, $last_name, max(0, (int) $atts['unit']));
			if ($match) {
				$unit_id = (int) ($match['unit_id'] ?? 0);
				$context = is_array($match['context'] ?? null) ? $match['context'] : [];
			}
		}

		if ($unit_id <= 0) {
			$unit_id = self::find_first_unit_with_arrival_instructions($locale);
			$context = [
				'guest_name' => trim($first_name . ' ' . $last_name),
				'guest_first_name' => $first_name,
				'guest_last_name' => $last_name,
				'date_from' => (string) ($date_from_candidates[0] ?? $date_from_raw),
				'date_to' => (string) ($date_to_candidates[0] ?? $date_to_raw),
			];
		}

		$matched_date_from = (string) ($context['date_from'] ?? ($date_from_candidates[0] ?? ''));
		if (!self::can_show_arrival_instructions_for_date($matched_date_from, $atts['available_days_before'])) {
			return self::render_arrival_instructions_unavailable_message((string) $atts['unavailable_text'], $locale);
		}

		if ($unit_id <= 0) {
			$fallback = (string) $atts['fallback'];
			return $fallback !== '' ? '<div class="vstrb-unit-arrival-instructions vstrb-unit-arrival-instructions-missing">' . wp_kses_post(wpautop($fallback)) . '</div>' : '';
		}

		$content = self::resolve_unit_content($unit_id, 'instructions', $locale, $context);
		if ($content === '') {
			$fallback = (string) $atts['fallback'];
			return $fallback !== '' ? '<div class="vstrb-unit-arrival-instructions vstrb-unit-arrival-instructions-missing">' . wp_kses_post(wpautop($fallback)) . '</div>' : '';
		}

		$out = '<div class="vstrb-unit-arrival-instructions vstrb-reservation-arrival-instructions">';
		if (self::parse_bool_shortcode_att($atts['show_title'], false)) {
			$out .= '<h3>' . esc_html(get_the_title($unit_id)) . '</h3>';
		}
		$out .= wp_kses_post(wpautop($content)) . '</div>';
		return $out;
	}

	public static function unit_bank_transfer_instructions($atts) {
		$atts = shortcode_atts(['unit' => 0, 'language' => ''], $atts, 'vstrb_unit_bank_transfer_instructions');
		$content = self::resolve_unit_content((int) $atts['unit'], 'bank_transfer_instructions', sanitize_text_field($atts['language']));
		return $content !== '' ? '<div class="vstrb-unit-bank-transfer-instructions">' . wp_kses_post(wpautop($content)) . '</div>' : '';
	}

	private static function sanitize_css_font_family($value) {
		$value = (string) $value;
		$value = preg_replace('/[^a-zA-Z0-9,\- _"\']/', '', $value);
		return trim((string) $value);
	}

	private static function sanitize_css_text_align($value) {
		$allowed = ['left', 'right', 'center', 'justify', 'start', 'end'];
		$value = strtolower(trim((string) $value));
		return in_array($value, $allowed, true) ? $value : '';
	}

	private static function sanitize_inline_css($css) {
		$css = wp_strip_all_tags((string) $css, false);
		$css = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', $css);
		return trim((string) $css);
	}

	private static function sanitize_inline_script($script) {
		$script = preg_replace('#</?script[^>]*>#i', '', (string) $script);
		return trim((string) $script);
	}

	public static function get_allowed_markup() {
		return [
			'div' => [
				'id' => true,
				'class' => true,
				'style' => true,
				'data-unit' => true,
				'data-months' => true,
				'data-grid' => true,
				'data-currency' => true,
				'data-language' => true,
				'data-i18n' => true,
				'hidden' => true,
				'role' => true,
			],
			'span' => [
				'class' => true,
				'style' => true,
				'aria-hidden' => true,
			],
			'form' => [
				'id' => true,
				'class' => true,
				'method' => true,
				'action' => true,
				'novalidate' => true,
			],
			'input' => [
				'type' => true,
				'name' => true,
				'class' => true,
				'value' => true,
				'min' => true,
				'max' => true,
				'step' => true,
				'placeholder' => true,
				'required' => true,
				'autocomplete' => true,
				'inputmode' => true,
				'pattern' => true,
				'aria-label' => true,
				'tabindex' => true,
				'readonly' => true,
				'data-field' => true,
			],
			'button' => [
				'type' => true,
				'class' => true,
				'aria-expanded' => true,
			],
			'select' => [
				'name' => true,
				'class' => true,
				'required' => true,
				'autocomplete' => true,
				'tabindex' => true,
				'aria-hidden' => true,
				'data-field' => true,
			],
			'option' => [
				'value' => true,
				'selected' => true,
			],
			'label' => [
				'class' => true,
				'for' => true,
			],
			'h3' => [
				'class' => true,
				'style' => true,
			],
			'h4' => [
				'class' => true,
			],
			'p' => [
				'class' => true,
				'style' => true,
			],
			'strong' => [
				'class' => true,
			],
			'em' => [
				'class' => true,
			],
			'hr' => [
				'class' => true,
			],
			'br' => [],
			'code' => [
				'class' => true,
			],
			'a' => [
				'class' => true,
				'href' => true,
				'target' => true,
				'rel' => true,
			],
			'ul' => [
				'class' => true,
				'style' => true,
			],
			'li' => [
				'class' => true,
			],
		];
	}

	public static function enqueue_frontend() {
		if (!wp_style_is('vstrb-theme-integration', 'registered')) {
			wp_register_style('vstrb-theme-integration', VSTRB_PLUGIN_URL . 'assets/theme-integration.css', [], VSTRB_VERSION);
		}
		if (!wp_style_is('vstrb-tom-select', 'registered')) {
			wp_register_style('vstrb-tom-select', VSTRB_PLUGIN_URL . 'assets/vendor/tom-select/tom-select.default.min.css', [], '2.6.1');
		}
		if (!wp_style_is('vstrb-frontend', 'registered')) {
			wp_register_style('vstrb-frontend', VSTRB_PLUGIN_URL . 'assets/frontend.css', ['vstrb-theme-integration', 'vstrb-tom-select'], VSTRB_VERSION);
		}
		if (!wp_script_is('vstrb-tom-select', 'registered')) {
			wp_register_script('vstrb-tom-select', VSTRB_PLUGIN_URL . 'assets/vendor/tom-select/tom-select.complete.min.js', [], '2.6.1', true);
		}
		if (!wp_script_is('vstrb-qrcode', 'registered')) {
			wp_register_script('vstrb-qrcode', VSTRB_PLUGIN_URL . 'assets/vendor/qrcodejs/qrcode.min.js', [], '1.0.0', true);
		}
		if (!wp_script_is('vstrb-payment-qr', 'registered')) {
			wp_register_script('vstrb-payment-qr', VSTRB_PLUGIN_URL . 'assets/payment-qr.js', ['vstrb-qrcode'], VSTRB_VERSION, true);
		}
		if (!wp_script_is('vstrb-frontend', 'registered')) {
			wp_register_script('vstrb-frontend', VSTRB_PLUGIN_URL . 'assets/frontend.js', ['wp-api-fetch', 'vstrb-payment-qr', 'vstrb-tom-select'], VSTRB_VERSION, true);
		}

		wp_enqueue_style('vstrb-theme-integration');
		wp_enqueue_style('vstrb-tom-select');
		wp_enqueue_style('vstrb-frontend');
		wp_enqueue_script('vstrb-tom-select');
		wp_enqueue_script('vstrb-qrcode');
		wp_enqueue_script('vstrb-payment-qr');
		wp_enqueue_script('vstrb-frontend');
		wp_localize_script('vstrb-frontend', 'VSTRB', self::get_localization_data());

		$mode = get_option('vstrb_styling_mode', 'off');
		$visual = get_option('vstrb_visual_settings', []);
		$css = '';

		$is_en = function($key) use ($visual) {
			return (!isset($visual[$key . '_en']) || $visual[$key . '_en']);
		};

		// --- Global Styling (Metadata & Layout) ---
		// These settings apply regardless of the main styling mode
		
		// Metadata Styling
		if ($is_en('meta_style')) {
			$meta_style = !empty($visual['meta_style']) ? $visual['meta_style'] : 'list';
			
			// Container Layout
			$css .= ".vstrb-services .vstrb-meta-container { margin-left: 28px !important; margin-top: 4px !important; line-height: 1.4 !important; font-size: 0.85em !important; } ";

			if ($meta_style === 'inline_bold' || $meta_style === 'inline_normal') {
				$css .= ".vstrb-services .vstrb-meta-row { display: inline-block !important; margin-right: 0 !important; } ";
				
				// Bold Key?
				if ($meta_style === 'inline_bold') {
					$css .= ".vstrb-services .vstrb-meta-key { font-weight: bold !important; } ";
				}

				// Separator
				if ($is_en('meta_sep')) {
					$sep = !empty($visual['meta_sep']) ? $visual['meta_sep'] : '';
					if ($sep) {
						$sep_content = '"' . esc_js($sep) . '"'; 
						$css .= ".vstrb-services .vstrb-meta-row:not(:last-child)::after { content: {$sep_content}; margin: 0 6px; color: #999; } ";
					} else {
						$css .= ".vstrb-services .vstrb-meta-row:not(:last-child) { margin-right: 10px !important; } ";
					}
				} else {
					$css .= ".vstrb-services .vstrb-meta-row:not(:last-child) { margin-right: 10px !important; } ";
				}

			} else {
				// List (default)
				$css .= ".vstrb-services .vstrb-meta-row { display: block !important; margin-bottom: 2px !important; } ";
			}
		}

		// --- Main Styling Mode Logic ---
		if ($mode === 'css') {
			$url = get_option('vstrb_custom_css_url', '');
			if ($url) {
				wp_enqueue_style('vstrb-custom', $url, ['vstrb-frontend'], VSTRB_VERSION);
			}
		}
		
		// --- Visual Settings (Independent Toggles) ---
				// 1. Font Family
				if ($is_en('font_family') && !empty($visual['font_family'])) {
					$font_family = self::sanitize_css_font_family($visual['font_family']);
					if ($font_family !== '') {
						$css .= '.vstrb { font-family: ' . $font_family . ' !important; } ';
					}
				}

			// 2. Max Width
			if ($is_en('max_width') && !empty($visual['max_width'])) {
				$unit = !empty($visual['max_width_unit']) ? $visual['max_width_unit'] : 'px';
				if (!in_array($unit, ['px', '%'])) $unit = 'px';
				$css .= '.vstrb { max-width: ' . (int)$visual['max_width'] . $unit . ' !important; } ';
			}

			// 3. Font Size
			if ($is_en('font_size') && !empty($visual['font_size'])) {
				$css .= '.vstrb { font-size: ' . (int)$visual['font_size'] . 'px !important; } ';
			}

			// 4. Form Background
			if ($is_en('form_bg') && !empty($visual['form_bg'])) {
				$fbg = sanitize_hex_color($visual['form_bg']);
				$css .= ".vstrb-booking { background-color: {$fbg} !important; border-radius: 8px; padding: 15px; } "; 
			}

			// 5. Calendar Colors
			if ($is_en('cal_bg') && !empty($visual['cal_bg'])) {
				$bg = sanitize_hex_color($visual['cal_bg']);
				$css .= ".vstrb-calendar, .vstrb-cal-grid { background-color: {$bg} !important; } ";
			}
			if ($is_en('cal_text') && !empty($visual['cal_text'])) {
				$text = sanitize_hex_color($visual['cal_text']);
				$css .= ".vstrb-calendar { color: {$text} !important; } ";
			}
			if ($is_en('cal_active') && !empty($visual['cal_active'])) {
				$active = sanitize_hex_color($visual['cal_active']);
				$css .= ".vstrb-pill-sel { background-color: {$active} !important; } ";
				$css .= ".vstrb-selected-range { background-color: transparent !important; font-size: 1.2em !important; font-weight: bold; color: inherit; padding: 0; } ";
				
				$sel_text = ($is_en('cal_sel_text') && !empty($visual['cal_sel_text'])) ? sanitize_hex_color($visual['cal_sel_text']) : '#ffffff';
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.sel { background-color: {$active} !important; } ";
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.sel, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.sel .d { color: {$sel_text} !important; } ";
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.inrange { background-color: {$active} !important; opacity: 0.7; } ";
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.inrange, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.inrange .d { color: {$sel_text} !important; } ";
			}
			if ($is_en('cal_occupied') && !empty($visual['cal_occupied'])) {
				$occ = sanitize_hex_color($visual['cal_occupied']);
				$css .= ".vstrb-calendar { --vstrb-current-occupied: {$occ}; } ";
				$css .= ".vstrb-pill-occ { background-color: {$occ} !important; } ";
				
				$occ_text = ($is_en('cal_occ_text') && !empty($visual['cal_occ_text'])) ? sanitize_hex_color($visual['cal_occ_text']) : '#ffffff';
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.occ:not(.vstrb-checkin):not(.vstrb-checkout):not(.vstrb-checkout-checkin) { background-color: {$occ} !important; } ";
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.occ:not(.vstrb-checkin):not(.vstrb-checkout):not(.vstrb-checkout-checkin), 
						 .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.occ:not(.vstrb-checkin):not(.vstrb-checkout):not(.vstrb-checkout-checkin) .d { color: {$occ_text} !important; } ";
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.occ .tag { color: {$occ_text} !important; opacity: 0.9; } "; 
				
				// --- Transitions ---
				$trans = '#ffffff'; 

				// Check-out
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout { background: linear-gradient(135deg, {$occ} 50%, {$trans} 50%) !important; } ";
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout .d { color: inherit !important; } ";
				if (!empty($active)) {
					$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout.sel, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout.inrange { background: linear-gradient(135deg, {$occ} 50%, {$active} 50%) !important; } ";
					$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout.sel, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout.sel .d,
							 .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout.inrange, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkout.inrange .d { color: {$sel_text} !important; } ";
				}

				// Check-in
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin { background: linear-gradient(135deg, {$trans} 50%, {$occ} 50%) !important; } ";
				$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin .d { color: inherit !important; } ";
				if (!empty($active)) {
					$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin.sel, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin.inrange { background: linear-gradient(135deg, {$active} 50%, {$occ} 50%) !important; } ";
					$css .= ".vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin.sel, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin.sel .d,
							 .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin.inrange, .vstrb-calendar .vstrb-cal-grid .vstrb-cal-day.vstrb-checkin.inrange .d { color: {$sel_text} !important; } ";
				}
			}
				 // Text colors for transitions: The "tag" (Available/Occupied/[Transition]) might need color tweak?
				 // Usually text is black on white part, white on red part. Hard to do without mix-blend-mode or complex HTML.
				 // For now, let's leave text inherit, but ensure .occ parts have contrast if possible.
				 // Actually, if selected, text should be white (as it's blue/red).


			// 5.5 & 5.6 Calendar Text Outline & Shadow
			$shadows = [];
			
			// 1. Outline
			if ($is_en('cal_outline') && !empty($visual['cal_outline'])) {
				$out_color = sanitize_hex_color($visual['cal_outline']);
				$out_size = (int)($visual['cal_outline_size'] ?? 1);
				$shadows[] = "-{$out_size}px -{$out_size}px 0 {$out_color}";
				$shadows[] = "{$out_size}px -{$out_size}px 0 {$out_color}";
				$shadows[] = "-{$out_size}px {$out_size}px 0 {$out_color}";
				$shadows[] = "{$out_size}px {$out_size}px 0 {$out_color}";
			}

			// 2. Custom Shadow
			if ($is_en('bs_color') && !empty($visual['bs_color'])) {
				$bsc = sanitize_hex_color($visual['bs_color']);
				$bsx = (int)($visual['bs_x'] ?? 0);
				$bsy = (int)($visual['bs_y'] ?? 4);
				$bsb = (int)($visual['bs_blur'] ?? 10);
				// text-shadow technically doesn't support spread in standard CSS, but we'll include blur
				$shadows[] = "{$bsx}px {$bsy}px {$bsb}px {$bsc}";
			}

			if (!empty($shadows)) {
				$css .= ".vstrb-cal-day { text-shadow: " . implode(', ', $shadows) . " !important; } ";
			}

			// 5.7 Calendar Dates Styling
			$d_css = '';
			if ($is_en('cal_date_size') && !empty($visual['cal_date_size'])) $d_css .= 'font-size: ' . (int)$visual['cal_date_size'] . 'px !important; ';
			if ($is_en('cal_date_color') && !empty($visual['cal_date_color'])) $d_css .= 'color: ' . sanitize_hex_color($visual['cal_date_color']) . ' !important; ';
			if ($is_en('cal_date_weight')) $d_css .= 'font-weight: bold !important; ';
			if ($d_css) {
				$css .= ".vstrb-cal-day .d { {$d_css} } ";
				// Also apply color to the day container to ensure consistency if no .d is present
				if ($is_en('cal_date_color') && !empty($visual['cal_date_color'])) $css .= ".vstrb-cal-day { color: " . sanitize_hex_color($visual['cal_date_color']) . " !important; } ";
			}

			// 5.8 Calendar Labels Styling (Tags inside days and Legend pills)
			$l_css = '';
			if ($is_en('cal_lbl_size') && !empty($visual['cal_lbl_size'])) $l_css .= 'font-size: ' . (int)$visual['cal_lbl_size'] . 'px !important; ';
			if ($is_en('cal_lbl_color') && !empty($visual['cal_lbl_color'])) $l_css .= 'color: ' . sanitize_hex_color($visual['cal_lbl_color']) . ' !important; ';
			if ($is_en('cal_lbl_weight')) $l_css .= 'font-weight: bold !important; ';
			if ($l_css) {
				$css .= ".vstrb-cal-day .tag, .vstrb-cal-legend .vstrb-pill { {$l_css} } ";
			}


		// 6. Section Headings Styling
		// Calendar Heading
		if ($is_en('cal_h_text')) {
			$h_css = '';
			if ($is_en('cal_h_size') && !empty($visual['cal_h_size'])) $h_css .= 'font-size: ' . (int)$visual['cal_h_size'] . 'px !important; ';
			if ($is_en('cal_h_color') && !empty($visual['cal_h_color'])) $h_css .= 'color: ' . sanitize_hex_color($visual['cal_h_color']) . ' !important; ';
			if ($is_en('cal_h_weight')) $h_css .= 'font-weight: bold !important; ';
				if ($is_en('cal_h_align') && !empty($visual['cal_h_align'])) {
					$align = self::sanitize_css_text_align($visual['cal_h_align']);
					if ($align !== '') {
						$h_css .= 'text-align: ' . $align . ' !important; ';
					}
				}
			if ($h_css) $css .= ".vstrb-cal-h { {$h_css} margin-bottom: 15px; } ";
		}
		// Booking Form Heading
		if ($is_en('frm_h_text')) {
			$h_css = '';
			if ($is_en('frm_h_size') && !empty($visual['frm_h_size'])) $h_css .= 'font-size: ' . (int)$visual['frm_h_size'] . 'px !important; ';
			if ($is_en('frm_h_color') && !empty($visual['frm_h_color'])) $h_css .= 'color: ' . sanitize_hex_color($visual['frm_h_color']) . ' !important; ';
			if ($is_en('frm_h_weight')) $h_css .= 'font-weight: bold !important; ';
			if ($h_css) $css .= ".vstrb-frm-h { {$h_css} margin-bottom: 15px; } ";
		}


		// 7. Booking Button Styling
		if ($is_en('btn_bg') || $is_en('btn_color') || $is_en('btn_size') || $is_en('btn_radius') || $is_en('btn_font') || $is_en('btn_weight')) {
			$btn_css = '';
			if ($is_en('btn_bg') && !empty($visual['btn_bg'])) {
				$bg = sanitize_hex_color($visual['btn_bg']);
				$btn_css .= "background-color: {$bg} !important; border-color: {$bg} !important; ";
			}
			if ($is_en('btn_color') && !empty($visual['btn_color'])) {
				$col = sanitize_hex_color($visual['btn_color']);
				$btn_css .= "color: {$col} !important; ";
			}
			if ($is_en('btn_size') && !empty($visual['btn_size'])) {
				$btn_css .= "font-size: " . (int)$visual['btn_size'] . "px !important; ";
			}
			if ($is_en('btn_radius') && isset($visual['btn_radius'])) { // radius can be 0
				$btn_css .= "border-radius: " . (int)$visual['btn_radius'] . "px !important; ";
			}
				if ($is_en('btn_font') && !empty($visual['btn_font'])) {
					$btn_font = self::sanitize_css_font_family($visual['btn_font']);
					if ($btn_font !== '') {
						$btn_css .= "font-family: " . $btn_font . " !important; ";
					}
				}
			if ($is_en('btn_weight') && !empty($visual['btn_weight'])) {
				$btn_css .= "font-weight: bold !important; ";
			}
			
			if ($btn_css) {
				$css .= ".vstrb-btn-primary { {$btn_css} } ";
			}
		}

			// Calendar price badge styling
		if ($is_en('cal_price_bg') && !empty($visual['cal_price_bg'])) {
			$bg = sanitize_hex_color($visual['cal_price_bg']);
			$css .= ".vstrb-cal-day .price:not(.disc):not(.surch):not(.has-disc):not(.has-surch) { background: {$bg} !important; } ";
		}
		if ($is_en('cal_price_color') && !empty($visual['cal_price_color'])) {
			$col = sanitize_hex_color($visual['cal_price_color']);
			$css .= ".vstrb-cal-day .price:not(.disc):not(.surch) { color: {$col} !important; } ";
		}
		if ($is_en('cal_price_size') && !empty($visual['cal_price_size'])) {
			$sz = (int)$visual['cal_price_size'];
			$css .= ".vstrb-cal-day .price:not(.disc):not(.surch) { font-size: {$sz}px !important; } ";
		}
		if ($is_en('cal_disc_color') && !empty($visual['cal_disc_color'])) {
			$dc = sanitize_hex_color($visual['cal_disc_color']);
			$css .= ".vstrb-cal-day .price.disc { background: {$dc} !important; } ";
			$css .= ".vstrb-cal-day .price.has-disc { background: {$dc} !important; } ";
		}
		if ($is_en('cal_surch_color') && !empty($visual['cal_surch_color'])) {
			$sc = sanitize_hex_color($visual['cal_surch_color']);
			$css .= ".vstrb-cal-day .price.surch { background: {$sc} !important; } ";
			$css .= ".vstrb-cal-day .price.has-surch { background: {$sc} !important; } ";
		}
		if ($is_en('cal_price_lbl_color') && !empty($visual['cal_price_lbl_color'])) {
			$lc = sanitize_hex_color($visual['cal_price_lbl_color']);
			$css .= ".vstrb-price-mode-label { color: {$lc} !important; } ";
		}
		if ($is_en('cal_price_lbl_size') && !empty($visual['cal_price_lbl_size'])) {
			$ls = (int)$visual['cal_price_lbl_size'];
			$css .= ".vstrb-price-mode-label { font-size: {$ls}px !important; } ";
		}
		if ($is_en('cal_price_lbl_weight')) {
			$css .= ".vstrb-price-mode-label { font-weight: bold !important; } ";
		}
		if ($is_en('cal_price_lbl_italic')) {
			$css .= ".vstrb-price-mode-label { font-style: italic !important; } ";
		}
		// Apply collected inline CSS (from Global + Visual mode if active)
			if ($css) {
				wp_add_inline_style('vstrb-frontend', self::sanitize_inline_css($css));
			}
		}

	private static function render_calendar($unit_id, $is_booking = false, $months = 1, $grid = null, $currency = '', $language = '') {
		$grid = $grid !== null ? (int)$grid : (int)$months;
		$vis = get_option('vstrb_visual_settings', []);
		$language = sanitize_text_field((string) $language);
		$i18n_json = wp_json_encode(self::get_i18n_data());
		ob_start();

		if (!$is_booking && !empty($vis['cal_h_text_en']) && !empty($vis['cal_h_text'])) {
			echo '<span class="vstrb-cal-h">' . esc_html($vis['cal_h_text']) . '</span>';
		}
?>
		<div class="vstrb vstrb-calendar <?php echo esc_attr($is_booking ? 'vstrb-calendar-in-form' : ''); ?>" 
			 data-unit="<?php echo esc_attr($unit_id); ?>" 
			 data-months="<?php echo (int)$months; ?>"
			 data-grid="<?php echo (int)$grid; ?>"
			 data-currency="<?php echo esc_attr($currency); ?>"
			 data-language="<?php echo esc_attr($language); ?>"
			 data-i18n="<?php echo esc_attr($i18n_json); ?>">
		  <div class="vstrb-cal-head">
            <button class="vstrb-btn vstrb-prev" type="button">
				<span class="vstrb-nav-arrow" aria-hidden="true">&lsaquo;</span>
				<span class="vstrb-nav-label vstrb-prev-label"></span>
			</button>
			<div class="vstrb-cal-title"></div>
            <button class="vstrb-btn vstrb-next" type="button">
				<span class="vstrb-nav-label vstrb-next-label"></span>
				<span class="vstrb-nav-arrow" aria-hidden="true">&rsaquo;</span>
			</button>
		  </div>
		  <div class="vstrb-cal-grid-container">
			<div class="vstrb-cal-grid"></div>
		  </div>
		  <div class="vstrb-cal-legend">
			<span class="vstrb-pill vstrb-pill-free"><?php esc_html_e('Available', 'vachr-short-term-rental-reservations'); ?></span>
			<span class="vstrb-pill vstrb-pill-occ"><?php esc_html_e('Occupied', 'vachr-short-term-rental-reservations'); ?></span>
			<span class="vstrb-pill vstrb-pill-sel"><?php esc_html_e('Selection', 'vachr-short-term-rental-reservations'); ?></span>
		  </div>
		  <div class="vstrb-cal-selected">
			<span class="vstrb-selected-range"></span>
			<?php if ($is_booking): ?>
			<div class="vstrb-cal-date-inputs">
				<label>
					<span><?php esc_html_e('Check-in', 'vachr-short-term-rental-reservations'); ?></span>
					<input type="date" class="vstrb-date-from" />
				</label>
				<label>
					<span><?php esc_html_e('Check-out', 'vachr-short-term-rental-reservations'); ?></span>
					<input type="date" class="vstrb-date-to" />
				</label>
			</div>
			<?php endif; ?>
		  </div>
		</div>
		<?php
		return ob_get_clean();
	}

	public static function booking_form($atts) {
		$atts = shortcode_atts(
			[
				'unit'=>'0',
				'months'=>'1',
				'grid'=>'1',
				'currency'=>'EUR',
				'language'=>'',
				'enable_stripe' => '0',
				'enable_mollie' => '0',
				'enable_bank_transfer' => '0',
				'enable_cash' => '0',
				'show_gender' => '1',
				'show_country' => '0',
				'show_children' => '1',
				'country' => '',
			],
			$atts,
			'vstrb_booking_form'
		);
		$unit_id = (int)$atts['unit'];
		if (!$unit_id) return '<div class="vstrb-error">' . esc_html__('Unit missing.', 'vachr-short-term-rental-reservations') . '</div>';

		if (get_post_meta($unit_id, '_vstrb_is_paused', true)) {
			return '<div class="vstrb-error">' . esc_html__('This apartment is temporarily unavailable for booking.', 'vachr-short-term-rental-reservations') . '</div>';
		}

		self::enqueue_frontend();

		$currency = $atts['currency'];
		$language = sanitize_text_field($atts['language']);
		$language_locale = self::resolve_wp_locale($language);
		$language_switched = false;
		if ($language_locale !== '' && function_exists('switch_to_locale')) {
			$language_switched = switch_to_locale($language_locale);
			if ($language_switched) {
				self::load_textdomain_for_locale($language_locale);
			}
		}
		$enable_stripe = self::parse_bool_shortcode_att($atts['enable_stripe'], false);
		$enable_mollie = self::parse_bool_shortcode_att($atts['enable_mollie'], false);
		$enable_bank_transfer = self::parse_bool_shortcode_att($atts['enable_bank_transfer'], false);
		$enable_cash = self::parse_bool_shortcode_att($atts['enable_cash'], false);
		$show_gender = self::parse_bool_shortcode_att($atts['show_gender'], true);
		$show_country = self::parse_bool_shortcode_att($atts['show_country'], false);
		$show_children = self::parse_bool_shortcode_att($atts['show_children'], true);
		$capacity = max(0, (int) get_post_meta($unit_id, '_vstrb_capacity', true));
		$countries = $show_country ? VSTRB_Countries::all($language_locale) : [];
		$selected_country = strtoupper(sanitize_text_field($atts['country']));
		if (!VSTRB_Countries::is_valid($selected_country)) {
			$country_locale = $language_locale;
			if ($country_locale === '') {
				$country_locale = function_exists('determine_locale') ? determine_locale() : get_locale();
			}
			$selected_country = self::country_from_locale($country_locale);
		}
		$payment_methods = [];
		if ($enable_stripe) {
			$payment_methods['card_stripe'] = __('Payment card (Stripe)', 'vachr-short-term-rental-reservations');
		}
		if ($enable_mollie) {
			$payment_methods['card_mollie'] = __('Payment card (Mollie)', 'vachr-short-term-rental-reservations');
		}
		if ($enable_bank_transfer) {
			$payment_methods['bank_transfer'] = __('Bank transfer', 'vachr-short-term-rental-reservations');
		}
		if ($enable_cash) {
			$payment_methods['cash'] = __('Cash', 'vachr-short-term-rental-reservations');
		}
		$has_payment_methods = !empty($payment_methods);
		$default_payment_method = $has_payment_methods ? (string) array_key_first($payment_methods) : '';
		$i18n_json = wp_json_encode(self::get_i18n_data());

		ob_start();
		$vis = get_option('vstrb_visual_settings', []);
		if (!empty($vis['frm_h_text_en']) && !empty($vis['frm_h_text'])) {
			echo '<h3 class="vstrb-frm-h">' . esc_html($vis['frm_h_text']) . '</h3>';
		}
		?>
		<div class="vstrb vstrb-booking" data-unit="<?php echo esc_attr($unit_id); ?>" data-currency="<?php echo esc_attr($currency); ?>" data-language="<?php echo esc_attr($language); ?>" data-i18n="<?php echo esc_attr($i18n_json); ?>" data-has-payment-methods="<?php echo esc_attr($has_payment_methods ? '1' : '0'); ?>">
		  <div class="vstrb-selection-row">
			<?php if (!empty($vis['cal_h_text_en'])): ?>
			<label class="vstrb-label-block"><?php echo esc_html(!empty($vis['cal_h_text']) ? $vis['cal_h_text'] : esc_html__('Availability Calendar', 'vachr-short-term-rental-reservations')); ?></label>
			<?php endif; ?>
			<?php echo wp_kses(self::render_calendar($unit_id, true, $atts['months'], $atts['grid'], $currency, $language), self::get_allowed_markup()); ?>
		  </div>
		  
		  <?php if ($show_children): ?>
		  <div class="vstrb-row vstrb-row-half">
			<div class="vstrb-field">
			  <label><?php esc_html_e('Adults', 'vachr-short-term-rental-reservations'); ?></label>
			  <input type="number" class="vstrb-guests vstrb-adults" min="1" <?php if ($capacity > 0): ?>max="<?php echo esc_attr($capacity); ?>"<?php endif; ?> value="1" />
			</div>
			<div class="vstrb-field">
			  <label><?php esc_html_e('Children', 'vachr-short-term-rental-reservations'); ?></label>
			  <input type="number" class="vstrb-children" min="0" <?php if ($capacity > 0): ?>max="<?php echo esc_attr(max(0, $capacity - 1)); ?>"<?php endif; ?> value="0" />
			</div>
		  </div>
		  <?php else: ?>
		  <div class="vstrb-row">
			<label><?php esc_html_e('Guests', 'vachr-short-term-rental-reservations'); ?></label>
			<input type="number" class="vstrb-guests" min="1" <?php if ($capacity > 0): ?>max="<?php echo esc_attr($capacity); ?>"<?php endif; ?> value="1" />
		  </div>
		  <?php endif; ?>

		  <?php 
		  $vis = get_option('vstrb_visual_settings', []);
		  $svc_en = !isset($vis['svc_label_en']) || $vis['svc_label_en'];
		  if ($svc_en && !empty($vis['svc_label'])) {
			  echo '<div class="vstrb-row"><label class="vstrb-label-block">' . esc_html($vis['svc_label']) . '</label></div>';
		  }
		  ?>
		  <div class="vstrb-services vstrb-services-box"></div>

		  <div class="vstrb-row vstrb-quote">
			<span class="vstrb-total-label"><?php esc_html_e('Total price with selected services', 'vachr-short-term-rental-reservations'); ?>:</span>
			<span class="vstrb-total"></span>
			<span class="vstrb-currency"><?php echo esc_html($currency); ?></span>
		  </div>
		  <div class="vstrb-price-breakdown" aria-live="polite">
			<div class="vstrb-price-line">
				<span><?php esc_html_e('Nights', 'vachr-short-term-rental-reservations'); ?></span>
				<span class="vstrb-price-nights">-</span>
			</div>
			<div class="vstrb-price-line">
				<span><?php esc_html_e('Accommodation', 'vachr-short-term-rental-reservations'); ?></span>
				<span class="vstrb-price-base">-</span>
			</div>
			<div class="vstrb-price-line vstrb-price-services-row">
				<span><?php esc_html_e('Services', 'vachr-short-term-rental-reservations'); ?></span>
				<span class="vstrb-price-services">-</span>
			</div>
			<div class="vstrb-price-service-lines" hidden></div>
			<div class="vstrb-price-line vstrb-price-discount-row" hidden>
				<span class="vstrb-price-discount-label"><?php esc_html_e('Discount', 'vachr-short-term-rental-reservations'); ?></span>
				<span class="vstrb-price-discount">-</span>
			</div>
			<div class="vstrb-price-discount-lines" hidden></div>
			<div class="vstrb-price-line vstrb-price-surcharge-row" hidden>
				<span class="vstrb-price-surcharge-label"><?php esc_html_e('Surcharge', 'vachr-short-term-rental-reservations'); ?></span>
				<span class="vstrb-price-surcharge">-</span>
			</div>
			<div class="vstrb-price-surcharge-lines" hidden></div>
			<div class="vstrb-price-line vstrb-price-final-line">
				<span><?php esc_html_e('Final total', 'vachr-short-term-rental-reservations'); ?></span>
				<span class="vstrb-price-final">-</span>
			</div>
			<div class="vstrb-price-line vstrb-price-deposit-row" hidden>
				<span><?php esc_html_e('Refundable deposit', 'vachr-short-term-rental-reservations'); ?></span>
				<span class="vstrb-price-deposit">-</span>
			</div>
		  </div>

		  <div class="vstrb-row vstrb-row-half">
			<div class="vstrb-field">
			  <label><?php esc_html_e('First Name', 'vachr-short-term-rental-reservations'); ?> *</label>
			  <input type="text" class="vstrb-name" name="first_name" autocomplete="given-name" required />
			</div>
			<div class="vstrb-field">
			  <label><?php esc_html_e('Last Name', 'vachr-short-term-rental-reservations'); ?> *</label>
			  <input type="text" class="vstrb-lastname" name="last_name" autocomplete="family-name" required />
			</div>
		  </div>
		  <?php if ($show_gender): ?>
		  <div class="vstrb-row">
			<label><?php esc_html_e('Gender', 'vachr-short-term-rental-reservations'); ?></label>
			<select class="vstrb-gender">
				<option value="male"><?php esc_html_e('Male', 'vachr-short-term-rental-reservations'); ?></option>
				<option value="female"><?php esc_html_e('Female', 'vachr-short-term-rental-reservations'); ?></option>
				<option value="other"><?php esc_html_e('Other', 'vachr-short-term-rental-reservations'); ?></option>
			</select>
		  </div>
		  <?php endif; ?>
		  <?php if ($show_country): ?>
		  <div class="vstrb-row vstrb-country-field">
			<label><?php esc_html_e('Country', 'vachr-short-term-rental-reservations'); ?></label>
			<select class="vstrb-country" name="country" autocomplete="country">
				<option value=""><?php esc_html_e('Select country', 'vachr-short-term-rental-reservations'); ?></option>
				<?php foreach ($countries as $country_code => $country_name): ?>
				<option value="<?php echo esc_attr($country_code); ?>" <?php selected($selected_country, $country_code); ?>><?php echo esc_html($country_name . ' (' . $country_code . ')'); ?></option>
				<?php endforeach; ?>
			</select>
		  </div>
		  <?php endif; ?>
		  <div class="vstrb-row">
			<label><?php esc_html_e('Email', 'vachr-short-term-rental-reservations'); ?> *</label>
			<input type="email" class="vstrb-email" name="email" autocomplete="email" required />
		  </div>
		  <div class="vstrb-row">
			<label><?php esc_html_e('Phone', 'vachr-short-term-rental-reservations'); ?> *</label>
			<div class="vstrb-phone-wrapper" style="display: flex; gap: 5px;">
			  <select class="vstrb-phone-prefix" style="flex: 0 0 100px;">
				<option value="+420">+420 (CZ)</option>
				<option value="+421">+421 (SK)</option>
				<option value="+49">+49 (DE)</option>
				<option value="+43">+43 (AT)</option>
				<option value="+48">+48 (PL)</option>
				<option value="+44">+44 (UK)</option>
				<option value="+1">+1 (US/CA)</option>
				<option value="+33">+33 (FR)</option>
				<option value="+39">+39 (IT)</option>
				<option value="+34">+34 (ES)</option>
				<option value="+31">+31 (NL)</option>
				<option value="+32">+32 (BE)</option>
				<option value="+41">+41 (CH)</option>
			  </select>
			  <input type="text" class="vstrb-phone" name="phone" autocomplete="tel-national" required style="flex: 1;" />
			</div>
		  </div>

		  <div class="vstrb-custom-fields vstrb-row-v"></div>

		  <div class="vstrb-row">
			<label><?php esc_html_e('Note', 'vachr-short-term-rental-reservations'); ?></label>
			<textarea class="vstrb-note"></textarea>
		  </div>

		  <?php if ($has_payment_methods): ?>
		  <div class="vstrb-row vstrb-row-v">
			<label class="vstrb-label-block"><?php esc_html_e('Payment Method', 'vachr-short-term-rental-reservations'); ?></label>
			<div class="vstrb-payment-methods">
				<?php foreach ($payment_methods as $method_key => $method_label): ?>
					<label class="vstrb-payment-option">
						<input type="radio" class="vstrb-payment-method" name="vstrb_payment_method_<?php echo esc_attr($unit_id); ?>" value="<?php echo esc_attr($method_key); ?>" <?php checked($default_payment_method, $method_key); ?>>
						<span><?php echo esc_html($method_label); ?></span>
					</label>
				<?php endforeach; ?>
			</div>
		  </div>
		  <div class="vstrb-billing-box vstrb-booking-billing-box">
			<label class="vstrb-label-block">
				<input type="checkbox" class="vstrb-book-billing-enabled" value="1">
				<?php esc_html_e('Add Billing Details', 'vachr-short-term-rental-reservations'); ?>
			</label>
			<div class="vstrb-booking-billing-fields" style="display:none">
				<?php echo wp_kses(self::render_billing_fields('book_billing_', 'vstrb-book-', true), self::get_allowed_markup()); ?>
			</div>
		  </div>
		  <?php endif; ?>

		  <div class="vstrb-row vstrb-hp" style="display:none !important">
			<label>Leave this empty</label>
			<input type="text" class="vstrb-honeypot" tabindex="-1" autocomplete="off" />
		  </div>

		  <?php $captcha = self::generate_captcha(); ?>
		  <div class="vstrb-row vstrb-captcha-row" data-captcha-token="<?php echo esc_attr($captcha['token']); ?>">
			<label><?php echo esc_html($captcha['question']); ?> *</label>
			<input type="text" class="vstrb-captcha" placeholder="?" autocomplete="off" required />
		  </div>

		  <div class="vstrb-row">
			<?php 
			$btn_label = __('Send Booking', 'vachr-short-term-rental-reservations');
			if (!empty($vis['btn_text_en']) && !empty($vis['btn_text'])) {
				$btn_label = esc_html($vis['btn_text']);
			}
			?>
			<button class="vstrb-btn vstrb-btn-primary vstrb-submit"><?php echo esc_html($btn_label); ?></button>
			<span class="vstrb-msg"></span>
		  </div>
		  <div style="text-align: right; font-size: 12px; color: #999999FF; margin-top: 20px; opacity: 0.8;">
			<a href="https://vachr.cz/vacation-rental-website-booking-system/" target="_blank" rel="noopener"><?php esc_html_e('Vachr Short-Term Rental Reservations', 'vachr-short-term-rental-reservations'); ?></a>
		  </div>
		</div>
		<div class="vstrb-thanks vstrb-booking-thanks" style="display:none; text-align:center; padding: 40px 20px;">
		   <h3 style="color: #2c3e50;"><?php esc_html_e('Thank You!', 'vachr-short-term-rental-reservations'); ?></h3>
		   <p style="font-size: 1.1em; color: #7f8c8d;"><?php esc_html_e('We have received your booking request. We are looking forward to your visit and will contact you shortly.', 'vachr-short-term-rental-reservations'); ?></p>
           <div style="font-size: 3em; margin-top: 20px;">&#127968;&#10024;</div>
		</div>
		<?php
		$html = ob_get_clean();
		if ($language_switched) {
			restore_current_locale();
			self::load_textdomain_for_locale(function_exists('determine_locale') ? determine_locale() : get_locale());
		}
		return $html;
	}

	public static function availability_calendar($atts) {
		$atts = shortcode_atts(['unit'=>'0', 'months'=>'1', 'grid'=>NULL, 'currency'=>'EUR', 'language'=>''], $atts, 'vstrb_availability_calendar');
		$unit_id = (int)$atts['unit'];
		if (!$unit_id) return '<div class="vstrb-error">' . __('Unit missing.', 'vachr-short-term-rental-reservations') . '</div>';

		if (get_post_meta($unit_id, '_vstrb_is_paused', true)) {
			return '<div class="vstrb-error">' . __('Temporarily unavailable.', 'vachr-short-term-rental-reservations') . '</div>';
		}

		self::enqueue_frontend();

		$language = sanitize_text_field($atts['language']);
		$language_locale = self::resolve_wp_locale($language);
		$language_switched = false;
		if ($language_locale !== '' && function_exists('switch_to_locale')) {
			$language_switched = switch_to_locale($language_locale);
			if ($language_switched) {
				self::load_textdomain_for_locale($language_locale);
			}
		}

		$html  = self::render_calendar($unit_id, false, $atts['months'], $atts['grid'], $atts['currency'], $language);
		$html .= '<div style="text-align: right; font-size: 12px; color: #999999FF; margin-top: 8px; opacity: 0.8;">';
		$html .= '<a href="https://vachr.cz/vacation-rental-website-booking-system/" target="_blank" rel="noopener" style="color: inherit; text-decoration: none;">' . esc_html__('Vachr Short-Term Rental Reservations', 'vachr-short-term-rental-reservations') . '</a>';
		$html .= '</div>';
		if ($language_switched) {
			restore_current_locale();
			self::load_textdomain_for_locale(function_exists('determine_locale') ? determine_locale() : get_locale());
		}
		return $html;
	}

	private static function get_i18n_data() {
		return [
			'selectDates' => esc_html__('Select dates', 'vachr-short-term-rental-reservations'),
			'loading' => esc_html__('Loading...', 'vachr-short-term-rental-reservations'),
			'occupied' => esc_html__('Occupied', 'vachr-short-term-rental-reservations'),
			'closed' => esc_html__('Closed', 'vachr-short-term-rental-reservations'),
			...VSTRB_CPT::get_service_pricing_types(),
			'free' => esc_html__('Available', 'vachr-short-term-rental-reservations'),
			'freeSvc' => esc_html__('free (service)', 'vachr-short-term-rental-reservations'),
			'nights' => esc_html__('nights', 'vachr-short-term-rental-reservations'),
			'total' => esc_html__('Total', 'vachr-short-term-rental-reservations'),
			'success' => esc_html__('Booking sent successfully!', 'vachr-short-term-rental-reservations'),
			'error' => esc_html__('Error sending booking.', 'vachr-short-term-rental-reservations'),
			'noServices' => esc_html__('No optional services.', 'vachr-short-term-rental-reservations'),
			'errorLoadingServices' => esc_html__('Error loading services.', 'vachr-short-term-rental-reservations'),
			'missingContact' => esc_html__('Fill in all required fields (indicated by *).', 'vachr-short-term-rental-reservations'),
			'missingBilling' => esc_html__('Fill in billing details for selected payment method.', 'vachr-short-term-rental-reservations'),
			'invalidCountry' => esc_html__('Select a valid country.', 'vachr-short-term-rental-reservations'),
			'searchCountry' => esc_html__('Search country...', 'vachr-short-term-rental-reservations'),
			'paymentRedirecting' => esc_html__('Redirecting to payment gateway...', 'vachr-short-term-rental-reservations'),
			'paymentInstructionsTitle' => esc_html__('Payment instructions', 'vachr-short-term-rental-reservations'),
			'paymentInstructionsMissing' => esc_html__('Booking was created, but bank transfer instructions are not configured. Please contact us.', 'vachr-short-term-rental-reservations'),
			'paymentLinkFailed' => esc_html__('Booking was created, but the card payment link could not be created. Please contact us.', 'vachr-short-term-rental-reservations'),
			'paymentSuccessReturn' => esc_html__('Payment has been successfully completed. Thank you!', 'vachr-short-term-rental-reservations'),
			'paymentFailedReturn' => esc_html__('There was a problem with payment confirmation. Please try again or contact us.', 'vachr-short-term-rental-reservations'),
			'checkout' => esc_html__('Check-out', 'vachr-short-term-rental-reservations'),
			'transition' => esc_html__('Transition', 'vachr-short-term-rental-reservations'),
			/* translators: %1$d: minimum number of nights. */
			'minStay' => str_replace('%1$d', '%d', esc_html__('Minimum stay is %1$d nights.', 'vachr-short-term-rental-reservations')),
			/* translators: %1$d: maximum number of nights. */
			'maxStay' => str_replace('%1$d', '%d', esc_html__('Maximum stay is %1$d nights.', 'vachr-short-term-rental-reservations')),
			/* translators: %1$d: maximum number of guests. */
			'maxGuests' => str_replace('%1$d', '%d', esc_html__('Maximum number of guests is %1$d.', 'vachr-short-term-rental-reservations')),
			'captchaError' => esc_html__('Please answer the security question correctly.', 'vachr-short-term-rental-reservations'),
			'robotError' => esc_html__('Robots are not allowed!', 'vachr-short-term-rental-reservations'),
			'mon' => esc_html__('Mo', 'vachr-short-term-rental-reservations'),
			'tue' => esc_html__('Tu', 'vachr-short-term-rental-reservations'),
			'wed' => esc_html__('We', 'vachr-short-term-rental-reservations'),
			'thu' => esc_html__('Th', 'vachr-short-term-rental-reservations'),
			'fri' => esc_html__('Fr', 'vachr-short-term-rental-reservations'),
			'sat' => esc_html__('Sa', 'vachr-short-term-rental-reservations'),
			'sun' => esc_html__('Su', 'vachr-short-term-rental-reservations'),
			'discount' => esc_html__('Discount', 'vachr-short-term-rental-reservations'),
			'surcharge' => esc_html__('Surcharge', 'vachr-short-term-rental-reservations'),
			'priceModes' => [
				'per_unit_night'   => esc_html__('per unit / night', 'vachr-short-term-rental-reservations'),
				'per_person_night' => esc_html__('per person / night', 'vachr-short-term-rental-reservations'),
				'per_stay'         => esc_html__('per stay', 'vachr-short-term-rental-reservations'),
				'per_night'        => esc_html__('per night', 'vachr-short-term-rental-reservations'),
				'per_person'       => esc_html__('per person', 'vachr-short-term-rental-reservations'),
			],
			'priceFrom' => [
				/* translators: %s: formatted price. */
				'per_unit_night'   => esc_html__('from %s / night', 'vachr-short-term-rental-reservations'),
				/* translators: %s: formatted price. */
				'per_person_night' => esc_html__('from %s / night', 'vachr-short-term-rental-reservations'),
				/* translators: %s: formatted price. */
				'per_stay'         => esc_html__('from %s / stay', 'vachr-short-term-rental-reservations'),
				/* translators: %s: formatted price. */
				'per_night'        => esc_html__('from %s / night', 'vachr-short-term-rental-reservations'),
				/* translators: %s: formatted price. */
				'per_person'       => esc_html__('from %s / person', 'vachr-short-term-rental-reservations'),
			],
			'paymentCard' => esc_html__('Payment card (Stripe)', 'vachr-short-term-rental-reservations'),
			'paymentCardMollie' => esc_html__('Payment card (Mollie)', 'vachr-short-term-rental-reservations'),
			'paymentBank' => esc_html__('Bank transfer', 'vachr-short-term-rental-reservations'),
			'paymentCash' => esc_html__('Cash', 'vachr-short-term-rental-reservations'),
		];
	}

	public static function get_localization_data() {
		return [
			'restUrl' => esc_url_raw(rest_url('vstrb/v1')),
			'nonce'   => wp_create_nonce('wp_rest'),
			'locale'  => substr(get_locale(), 0, 2),
			'i18n' => self::get_i18n_data(),
		];
	}
	private static function render_guest_block($unit_id, $index, $prefill = []) {
		$uid = esc_attr($unit_id);
		$i   = (int) $index;
		$fn  = esc_attr($prefill['first_name'] ?? '');
		$ln  = esc_attr($prefill['last_name'] ?? '');
		ob_start();
		?>
		<div class="vstrb-guest-block">
			<div class="vstrb-guest-header">
				<strong class="vstrb-guest-num"></strong>
				<button type="button" class="vstrb-btn vstrb-remove-guest"><?php esc_html_e('Remove', 'vachr-short-term-rental-reservations'); ?></button>
			</div>
			<div class="vstrb-row vstrb-row-half">
				<div class="vstrb-field">
					<label><?php esc_html_e('First Name', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="text" data-field="first_name" value="<?php echo esc_attr($fn); ?>" required>
				</div>
				<div class="vstrb-field">
					<label><?php esc_html_e('Last Name', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="text" data-field="last_name" value="<?php echo esc_attr($ln); ?>" required>
				</div>
			</div>
			<div class="vstrb-row vstrb-row-half">
				<div class="vstrb-field">
					<label><?php esc_html_e('Date of Birth', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="date" data-field="date_of_birth" required>
				</div>
				<div class="vstrb-field">
					<label><?php esc_html_e('Nationality', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="text" data-field="nationality" required>
				</div>
			</div>
			<div class="vstrb-row vstrb-row-half">
				<div class="vstrb-field">
					<label><?php esc_html_e('Street', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="text" data-field="street" required>
				</div>
				<div class="vstrb-field">
					<label><?php esc_html_e('City', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="text" data-field="city" required>
				</div>
			</div>
			<div class="vstrb-row">
				<div class="vstrb-field" style="max-width:calc(50% - 8px)">
					<label><?php esc_html_e('Country', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="text" data-field="state" required>
				</div>
			</div>
			<div class="vstrb-row vstrb-row-half">
				<div class="vstrb-field">
					<label><?php esc_html_e('Document Type', 'vachr-short-term-rental-reservations'); ?> *</label>
					<select data-field="document_type" required>
                        <option value=""><?php esc_html_e('- select -', 'vachr-short-term-rental-reservations'); ?></option>
						<option value="passport"><?php esc_html_e('Passport', 'vachr-short-term-rental-reservations'); ?></option>
						<option value="id_card"><?php esc_html_e('ID Card', 'vachr-short-term-rental-reservations'); ?></option>
						<option value="driving_license"><?php esc_html_e('Driving License', 'vachr-short-term-rental-reservations'); ?></option>
						<option value="other"><?php esc_html_e('Other', 'vachr-short-term-rental-reservations'); ?></option>
					</select>
				</div>
				<div class="vstrb-field">
					<label><?php esc_html_e('Document Number', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="text" data-field="document_number" required>
				</div>
			</div>
		</div>
		<?php
		return ob_get_clean();
	}

	public static function guest_registration_form($atts) {
		$atts = shortcode_atts([
			'unit' => 0,
			'title' => '',
			'language' => '',
			'param' => 'accommodation',
			'name' => '',
			'match' => 'exact',
			'fallback' => '',
		], $atts, 'vstrb_guest_registration');
		$unit_id = (int) $atts['unit'];
		if ($unit_id <= 0) {
			$name = (string) $atts['name'];
			if ($name === '') {
				$name = self::get_text_query_param((string) $atts['param']);
			}
			$match = strtolower(sanitize_key((string) $atts['match'])) === 'contains' ? 'contains' : 'exact';
			$unit_id = self::find_unit_by_public_name($name, $match);
		}

		if (!$unit_id || !VSTRB_DB::unit_exists($unit_id)) {
			$fallback = (string) $atts['fallback'];
			return $fallback !== '' ? '<p>' . esc_html($fallback) . '</p>' : '<p>' . esc_html__('Invalid or missing apartment.', 'vachr-short-term-rental-reservations') . '</p>';
		}

		$locale = sanitize_text_field($atts['language']);
		if ($locale) {
			switch_to_locale($locale);
		}

		self::enqueue_frontend();
		$unit_title  = !empty($atts['title']) ? $atts['title'] : get_the_title($unit_id);
		$rest_url    = esc_url(rest_url('vstrb/v1/guest-register'));
		$nonce       = wp_create_nonce('wp_rest');

        // Read GET params (prefill) - nonce not required, these are read-only pre-fill values only.
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		$allowed_sources = ['booking_com', 'airbnb', 'vrbo', 'expedia', 'direct', 'other'];
		$p_date_from  = sanitize_text_field(wp_unslash($_GET['date_from'] ?? ''));
		$p_date_to    = sanitize_text_field(wp_unslash($_GET['date_to'] ?? ''));
		$p_first_name = sanitize_text_field(wp_unslash($_GET['first_name'] ?? ''));
		$p_last_name  = sanitize_text_field(wp_unslash($_GET['last_name'] ?? ''));
		$p_source_raw = sanitize_text_field(wp_unslash($_GET['source'] ?? ''));
		$p_source     = in_array($p_source_raw, $allowed_sources, true) ? $p_source_raw : '';
		$p_booking_ref = sanitize_text_field(wp_unslash($_GET['booking_ref'] ?? ''));
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		$guest_block = self::render_guest_block($unit_id, 1, [
			'first_name' => $p_first_name,
			'last_name'  => $p_last_name,
		]);

		$sources = [
			'booking_com' => 'Booking.com',
			'airbnb'      => 'Airbnb',
			'vrbo'        => 'VRBO / HomeAway',
			'expedia'     => 'Expedia',
			'direct'      => __('Direct booking', 'vachr-short-term-rental-reservations'),
			'other'       => __('Other', 'vachr-short-term-rental-reservations'),
		];

		ob_start();
		?>
		<div class="vstrb">
			<form id="vstrb-guest-reg-form-<?php echo esc_attr($unit_id); ?>" class="vstrb-guest-reg-form" novalidate>
				<input type="hidden" name="unit_id" value="<?php echo esc_attr($unit_id); ?>">

				<h3><?php echo esc_html($unit_title); ?></h3>

				<!-- Shared: term + email -->
				<div class="vstrb-row">
					<label><?php esc_html_e('Email', 'vachr-short-term-rental-reservations'); ?> *</label>
					<input type="email" name="email" required>
				</div>
				<div class="vstrb-row vstrb-row-half">
					<div class="vstrb-field">
						<label><?php esc_html_e('Check-in Date', 'vachr-short-term-rental-reservations'); ?> *</label>
						<input type="date" name="date_from" value="<?php echo esc_attr($p_date_from); ?>" required>
					</div>
					<div class="vstrb-field">
						<label><?php esc_html_e('Check-out Date', 'vachr-short-term-rental-reservations'); ?> *</label>
						<input type="date" name="date_to" value="<?php echo esc_attr($p_date_to); ?>" required>
					</div>
				</div>

                <!-- Booking source + booking reference -->
				<div class="vstrb-row vstrb-row-half">
					<div class="vstrb-field">
						<label><?php esc_html_e('Booking Source', 'vachr-short-term-rental-reservations'); ?></label>
						<select name="source">
                            <option value=""><?php esc_html_e('- select -', 'vachr-short-term-rental-reservations'); ?></option>
							<?php foreach ($sources as $val => $label): ?>
								<option value="<?php echo esc_attr($val); ?>" <?php selected($p_source, $val); ?>><?php echo esc_html($label); ?></option>
							<?php endforeach; ?>
						</select>
					</div>
					<div class="vstrb-field">
						<label><?php esc_html_e('Booking Reference', 'vachr-short-term-rental-reservations'); ?></label>
						<input type="text" name="booking_reference" value="<?php echo esc_attr($p_booking_ref); ?>">
					</div>
				</div>

				<div class="vstrb-row">
					<button type="button" class="vstrb-btn vstrb-add-billing">+ <?php esc_html_e('Add Billing Details', 'vachr-short-term-rental-reservations'); ?></button>
				</div>
				<div class="vstrb-billing-box" style="display:none">
					<?php echo wp_kses(self::render_billing_fields('billing_'), self::get_allowed_markup()); ?>
				</div>

				<hr class="vstrb-gr-divider">

                <!-- Guest list -->
				<div class="vstrb-guests-list">
					<?php echo wp_kses($guest_block, self::get_allowed_markup()); ?>
				</div>

				<div class="vstrb-row">
					<button type="button" class="vstrb-btn vstrb-add-guest">+ <?php esc_html_e('Add Guest', 'vachr-short-term-rental-reservations'); ?></button>
				</div>

				<hr class="vstrb-gr-divider">

				<!-- Honeypot -->
				<div style="display:none"><input type="text" name="hp" value="" tabindex="-1" autocomplete="off"></div>

				<?php $captcha = self::generate_captcha(); ?>
				<div class="vstrb-row vstrb-captcha-row" data-captcha-token="<?php echo esc_attr($captcha['token']); ?>">
					<label><?php echo esc_html($captcha['question']); ?> *</label>
					<input type="text" name="captcha" autocomplete="off" placeholder="?" required>
				</div>

				<div class="vstrb-notice vstrb-guest-reg-notice" style="display:none"></div>

				<div class="vstrb-row">
					<button type="submit" class="vstrb-btn vstrb-btn-primary"><?php esc_html_e('Submit Form', 'vachr-short-term-rental-reservations'); ?></button>
				</div>
			</form>
		<div id="vstrb-guest-reg-thanks-<?php echo esc_attr($unit_id); ?>" class="vstrb-thanks" style="display:none; text-align:center; padding: 40px 20px;">
			<h3 style="color: #2c3e50;"><?php esc_html_e('Thank You!', 'vachr-short-term-rental-reservations'); ?></h3>
			<p style="font-size: 1.1em; color: #7f8c8d;"><?php esc_html_e('Your registration has been submitted successfully. We look forward to your stay!', 'vachr-short-term-rental-reservations'); ?></p>
            <div style="font-size: 3em; margin-top: 20px;">&#10003;</div>
		</div>
		<div style="text-align: right; font-size: 12px; color: #999999FF; margin-top: 20px; opacity: 0.8;">
			<a href="https://vachr.cz/vacation-rental-website-booking-system/" target="_blank" rel="noopener" style="color: inherit; text-decoration: none;"><?php esc_html_e('Vachr Short-Term Rental Reservations', 'vachr-short-term-rental-reservations'); ?></a>
		</div>
		</div>
		<?php
		$output = ob_get_clean();
		ob_start();
		?>
		(function(){
			var form = document.getElementById('vstrb-guest-reg-form-<?php echo esc_js($unit_id); ?>');
			if (!form) return;
			var thanks = document.getElementById('vstrb-guest-reg-thanks-<?php echo esc_js($unit_id); ?>');

			var list    = form.querySelector('.vstrb-guests-list');
			var tplHtml = <?php echo wp_json_encode($guest_block); ?>;
			var labelGuest = <?php echo wp_json_encode(__('Guest', 'vachr-short-term-rental-reservations')); ?>;
			var addBillingBtn = form.querySelector('.vstrb-add-billing');
			var billingBox = form.querySelector('.vstrb-billing-box');
			var addBillingLabel = <?php echo wp_json_encode('+ ' . __('Add Billing Details', 'vachr-short-term-rental-reservations')); ?>;
			var removeBillingLabel = <?php echo wp_json_encode(__('Remove Billing Details', 'vachr-short-term-rental-reservations')); ?>;

			function updateNumbers() {
				var blocks = list.querySelectorAll('.vstrb-guest-block');
				blocks.forEach(function(block, i) {
					block.querySelector('.vstrb-guest-num').textContent = labelGuest + ' ' + (i + 1);
					var rm = block.querySelector('.vstrb-remove-guest');
					if (rm) rm.style.display = blocks.length > 1 ? '' : 'none';
				});
			}

			function bindRemove(block) {
				var rm = block.querySelector('.vstrb-remove-guest');
				if (rm) rm.addEventListener('click', function() {
					block.remove();
					updateNumbers();
				});
			}

			bindRemove(list.querySelector('.vstrb-guest-block'));
			updateNumbers();

			if (addBillingBtn && billingBox) {
				addBillingBtn.addEventListener('click', function() {
					var isOpen = billingBox.style.display !== 'none';
					billingBox.style.display = isOpen ? 'none' : 'block';
					addBillingBtn.textContent = isOpen ? addBillingLabel : removeBillingLabel;
				});
			}

			form.querySelector('.vstrb-add-guest').addEventListener('click', function() {
				var div = document.createElement('div');
				div.innerHTML = tplHtml;
				var block = div.firstElementChild;
				bindRemove(block);
				list.appendChild(block);
				updateNumbers();
				block.querySelector('input').focus();
			});

			function collectGuests() {
				var guests = [];
				list.querySelectorAll('.vstrb-guest-block').forEach(function(block) {
					guests.push({
						first_name:      block.querySelector('[data-field="first_name"]').value.trim(),
						last_name:       block.querySelector('[data-field="last_name"]').value.trim(),
						date_of_birth:   block.querySelector('[data-field="date_of_birth"]').value,
						nationality:     block.querySelector('[data-field="nationality"]').value.trim(),
						street:          block.querySelector('[data-field="street"]').value.trim(),
						city:            block.querySelector('[data-field="city"]').value.trim(),
						state:           block.querySelector('[data-field="state"]').value.trim(),
						document_type:   block.querySelector('[data-field="document_type"]').value,
						document_number: block.querySelector('[data-field="document_number"]').value.trim(),
					});
				});
				return guests;
			}

			form.addEventListener('submit', function(e) {
				e.preventDefault();
				var notice = form.querySelector('.vstrb-guest-reg-notice');
				var btn    = form.querySelector('button[type=submit]');
				btn.disabled = true;
				notice.style.display = 'none';

				var captchaRow = form.querySelector('.vstrb-captcha-row');
				var payload = {
					unit_id:           parseInt(form.querySelector('[name="unit_id"]').value, 10),
					date_from:         form.querySelector('[name="date_from"]').value,
					date_to:           form.querySelector('[name="date_to"]').value,
					email:             form.querySelector('[name="email"]').value.trim(),
					hp:                form.querySelector('[name="hp"]').value,
					captcha:           form.querySelector('[name="captcha"]').value.trim(),
					captcha_token:     captchaRow ? (captchaRow.dataset.captchaToken || '') : '',
					source:            form.querySelector('[name="source"]').value,
					booking_reference: form.querySelector('[name="booking_reference"]').value.trim(),
					billing: {
						enabled:      !!(billingBox && billingBox.style.display !== 'none'),
						name:         (form.querySelector('[name="billing_name"]') || {}).value ? form.querySelector('[name="billing_name"]').value.trim() : '',
						company_id:   (form.querySelector('[name="billing_ico"],[name="billing_company_id"]') || {}).value ? form.querySelector('[name="billing_ico"],[name="billing_company_id"]').value.trim() : '',
						vat_id:       (form.querySelector('[name="billing_vat_id"]') || {}).value ? form.querySelector('[name="billing_vat_id"]').value.trim() : '',
						street:       (form.querySelector('[name="billing_street"]') || {}).value ? form.querySelector('[name="billing_street"]').value.trim() : '',
						city:         (form.querySelector('[name="billing_city"]') || {}).value ? form.querySelector('[name="billing_city"]').value.trim() : '',
						zip:          (form.querySelector('[name="billing_zip"]') || {}).value ? form.querySelector('[name="billing_zip"]').value.trim() : '',
						country:      (form.querySelector('[name="billing_country"]') || {}).value ? form.querySelector('[name="billing_country"]').value.trim() : '',
					},
					guests:            collectGuests(),
				};

				fetch('<?php echo esc_js($rest_url); ?>', {
					method: 'POST',
					headers: {
						'Content-Type': 'application/json',
						'X-WP-Nonce': '<?php echo esc_js($nonce); ?>'
					},
					body: JSON.stringify(payload)
				}).then(function(r){
					return r.text().then(function(text){
						var data = null;
						try {
							data = JSON.parse(text);
						} catch (err) {
							throw new Error('invalid_json_response');
						}
						return data;
					});
				}).then(function(res){
					if (res.ok) {
						form.style.display = 'none';
						if (thanks) thanks.style.display = 'block';
					} else {
						var msgs = {
							captcha_invalid:    '<?php echo esc_js(__('Please answer the security question correctly.', 'vachr-short-term-rental-reservations')); ?>',
							missing_fields:     '<?php echo esc_js(__('Please fill in all required fields.', 'vachr-short-term-rental-reservations')); ?>',
							invalid_date_range: '<?php echo esc_js(__('Check-out must be after check-in.', 'vachr-short-term-rental-reservations')); ?>',
						};
						notice.className = 'vstrb-notice vstrb-notice-error vstrb-guest-reg-notice';
						notice.style.display = 'block';
						notice.textContent = msgs[res.error] || '<?php echo esc_js(__('An error occurred. Please try again.', 'vachr-short-term-rental-reservations')); ?>';
						btn.disabled = false;
					}
				}).catch(function(){
					notice.className = 'vstrb-notice vstrb-notice-error vstrb-guest-reg-notice';
					notice.style.display = 'block';
					notice.textContent = '<?php echo esc_js(__('An error occurred. Please try again.', 'vachr-short-term-rental-reservations')); ?>';
					btn.disabled = false;
				});
			});
		})();
		<?php
			wp_add_inline_script('vstrb-frontend', self::sanitize_inline_script(ob_get_clean()));
		if ($locale) {
			restore_current_locale();
		}
		return $output;
	}
	public static function generate_captcha() {
		$op = wp_rand(0, 1) ? '+' : '-';
		if ($op === '+') {
			$a = wp_rand(1, 9);
			$b = wp_rand(1, 9);
			$result = $a + $b;
		} else {
			$a = wp_rand(3, 9);
			$b = wp_rand(1, $a - 1);
			$result = $a - $b;
		}
		$expires = time() + 1800; // 30 min
		$payload = $result . '|' . $expires;
		$token   = $payload . '|' . hash_hmac('sha256', $payload, wp_salt('auth'));
		/* translators: 1: first number, 2: operator (+ or -), 3: second number */
		$question = sprintf(__('Security check: %1$d %2$s %3$d = ?', 'vachr-short-term-rental-reservations'), $a, $op, $b);
		return ['question' => $question, 'token' => $token];
	}

	public static function verify_captcha($token, $answer) {
		$parts = explode('|', (string) $token);
		if (count($parts) !== 3) return false;
		[$result, $expires, $sig] = $parts;
		$payload = $result . '|' . $expires;
		if (!hash_equals(hash_hmac('sha256', $payload, wp_salt('auth')), $sig)) return false;
		if (time() > (int) $expires) return false;
		return (int) trim((string) $answer) === (int) $result;
	}
}

add_action('init', ['VSTRB_Shortcodes', 'register']);
