<?php
/**
 * Email notification templates and logic.
 *
 * @package Vachr Short-Term Rental Reservations
 * @license GPLv2 or later
 */

if (!defined('ABSPATH')) exit;

class VSTRB_Emails {
	// Email notifications read plugin custom tables and keep short-lived object cache.
	// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
	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 safe_email_recipient($email) {
		$email = sanitize_email((string) $email);
		return is_email($email) ? $email : '';
	}

	private static function safe_text($value) {
		if (is_array($value) || is_object($value)) {
			return '';
		}
		return sanitize_text_field((string) $value);
	}

	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($locale) {
		$locale = sanitize_text_field((string) $locale);
		$locale = str_replace('-', '_', $locale);
		if ($locale === '') {
			return '';
		}
		if (function_exists('PLL') && PLL()->model) {
			$lang = PLL()->model->get_language($locale);
			if ($lang && !empty($lang->locale)) {
				return (string) $lang->locale;
			}
		}
		if (preg_match('/^[a-z]{2}_[A-Z]{2}$/', $locale)) {
			return $locale;
		}
		$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($locale, 0, 2));
		return $map[$key] ?? $locale;
	}

	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 switch_to_booking_locale($booking) {
		$booking_id = (int) ($booking->id ?? 0);
		$locale = $booking_id > 0 ? self::get_booking_meta_value($booking_id, 'customer_locale') : '';
		$locale = self::resolve_wp_locale($locale);
		if ($locale !== '' && function_exists('switch_to_locale') && switch_to_locale($locale)) {
			self::load_textdomain_for_locale($locale);
			return true;
		}
		return false;
	}

	private static function restore_booking_locale($switched) {
		if ($switched && function_exists('restore_previous_locale')) {
			restore_previous_locale();
			self::load_textdomain_for_locale(get_locale());
		}
	}

	private static function plain_text_value($value) {
		$value = html_entity_decode((string) $value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
		$value = str_replace("\xc2\xa0", ' ', $value);
		return $value;
	}

	private static function build_unit_placeholder_values($unit_id, $locale = '', $booking = null) {
		$unit_id = (int) $unit_id;
		$lang = self::normalize_locale_code($locale);
		$supplier_account = VSTRB_Payment_QR::supplier_account_details();
		$guest_name = '';
		if ($booking) {
			$guest_name = trim((string) ($booking->customer_name ?? '') . ' ' . (string) ($booking->customer_lastname ?? ''));
		}
		$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),
			'booking_id' => $booking ? (string) (int) ($booking->id ?? 0) : '',
			'guest_name' => $guest_name,
			'guest_first_name' => $booking ? (string) ($booking->customer_name ?? '') : '',
			'guest_last_name' => $booking ? (string) ($booking->customer_lastname ?? '') : '',
			'guest_email' => $booking ? (string) ($booking->customer_email ?? '') : '',
			'date_from' => $booking ? (string) ($booking->date_from ?? '') : '',
			'date_to' => $booking ? (string) ($booking->date_to ?? '') : '',
			'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'] ?? ''),
		];
		if ($booking && !empty($booking->id)) {
			$payment_amount = self::safe_text(self::get_booking_meta_value((int) $booking->id, 'payment_amount_due'));
			$payment_currency = self::safe_text(self::get_booking_meta_value((int) $booking->id, 'payment_currency'));
			$values['invoice_number'] = self::safe_text(self::get_booking_meta_value((int) $booking->id, 'invoice_number'));
			$values['payment_reference'] = self::safe_text(self::get_booking_meta_value((int) $booking->id, 'payment_reference'));
			$values['payment_amount'] = $payment_amount;
			$values['payment_amount_due'] = $payment_amount;
			$values['payment_currency'] = $payment_currency !== '' ? $payment_currency : (string) ($booking->currency ?? '');
			$values['total'] = (string) ($booking->total_price ?? '');
			$values['total_price'] = (string) ($booking->total_price ?? '');
		}
		$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 = '', $booking = null) {
		$content = (string) $content;
		if ($content === '' || strpos($content, '{{') === false) {
			return $content;
		}
		$values = self::build_unit_placeholder_values($unit_id, $locale, $booking);
		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 html_to_plain($html) {
		$html = (string) $html;
		$html = preg_replace('/<\s*br\s*\/?>/i', "\n", $html);
		$html = preg_replace('/<\/\s*(p|div|li|h[1-6])\s*>/i', "\n", $html);
		$text = wp_strip_all_tags($html);
		$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
		$text = preg_replace("/[ \t]+\n/", "\n", $text);
		$text = preg_replace("/\n{3,}/", "\n\n", $text);
		return trim((string) $text);
	}

	private static function get_booking_meta_value($booking_id, $meta_key) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$booking_id = (int) $booking_id;
		$meta_key = self::safe_text($meta_key);
		if ($booking_id <= 0 || $meta_key === '') {
			return '';
		}
		return (string) $wpdb->get_var($wpdb->prepare(
			"SELECT meta_value FROM {$t['booking_meta']} WHERE booking_id=%d AND meta_key=%s ORDER BY id DESC LIMIT 1",
			$booking_id,
			$meta_key
		));
	}

	private static function add_booking_meta_value($booking_id, $meta_key, $meta_value) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$booking_id = (int) $booking_id;
		$meta_key = self::safe_text($meta_key);
		if ($booking_id <= 0 || $meta_key === '') {
			return false;
		}
		// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value
		return false !== $wpdb->insert($t['booking_meta'], [
			'booking_id' => $booking_id,
			'meta_key' => $meta_key,
			'meta_value' => (string) $meta_value,
		], ['%d', '%s', '%s']);
		// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_meta_value
	}

	private static function upsert_booking_meta_value($booking_id, $meta_key, $meta_value) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$booking_id = (int) $booking_id;
		$meta_key = self::safe_text($meta_key);
		if ($booking_id <= 0 || $meta_key === '') {
			return false;
		}
		$existing_id = (int) $wpdb->get_var($wpdb->prepare(
			"SELECT id FROM {$t['booking_meta']} WHERE booking_id=%d AND meta_key=%s ORDER BY id DESC LIMIT 1",
			$booking_id,
			$meta_key
		));
		if ($existing_id > 0) {
			// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- Plugin custom meta table update by primary key.
			$updated = $wpdb->update($t['booking_meta'], ['meta_value' => (string) $meta_value], ['id' => $existing_id], ['%s'], ['%d']);
			// phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_value
			return false !== $updated;
		}
		return self::add_booking_meta_value($booking_id, $meta_key, $meta_value);
	}

	private static function resolve_unit_content($unit_id, $field, $locale = '', $booking = null) {
		$unit_id = (int) $unit_id;
		$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 : 'instructions';
		$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::html_to_plain(self::apply_unit_placeholders($values[$lang], $unit_id, $lang, $booking));
			}
			foreach ($values as $value) {
				if (trim(wp_strip_all_tags((string) $value)) !== '') {
					return self::html_to_plain(self::apply_unit_placeholders($value, $unit_id, $lang, $booking));
				}
			}
		}

		$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::html_to_plain(self::apply_unit_placeholders($template_values[$lang], $unit_id, $lang, $booking));
				}
				foreach ($template_values as $value) {
					if (trim(wp_strip_all_tags((string) $value)) !== '') {
						return self::html_to_plain(self::apply_unit_placeholders($value, $unit_id, $lang, $booking));
					}
				}
			}
		}

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

	private static function get_booking_cached($booking_id) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$booking_id = (int) $booking_id;
		if ($booking_id <= 0) {
			return null;
		}

		$cache_key = 'vstrb_booking_' . $booking_id;
		$booking = wp_cache_get($cache_key, 'vstrb_emails');
		if (false !== $booking) {
			return $booking;
		}

		$bookings_table = self::safe_table_name($t['bookings'] ?? '');
		if ($bookings_table === '') {
			return null;
		}

		$booking = $wpdb->get_row(
			$wpdb->prepare(
				"SELECT * FROM {$bookings_table} WHERE id = %d",
				$booking_id
			)
		);
		wp_cache_set($cache_key, $booking, 'vstrb_emails', 300);
		return $booking;
	}

	private static function get_booking_details_cached($booking_id) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$booking_id = (int) $booking_id;
		if ($booking_id <= 0) {
			return ['services' => [], 'meta' => []];
		}

		$cache_key = 'vstrb_booking_details_' . $booking_id;
		$details = wp_cache_get($cache_key, 'vstrb_emails');
		if (false !== $details && is_array($details)) {
			return $details;
		}

		$booking_services_table = self::safe_table_name($t['booking_services'] ?? '');
		$services_table = self::safe_table_name($t['services'] ?? '');
		$booking_meta_table = self::safe_table_name($t['booking_meta'] ?? '');
		if ($booking_services_table === '' || $services_table === '' || $booking_meta_table === '') {
			return ['services' => [], 'meta' => []];
		}

		$services = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT bs.service_id, bs.qty, bs.price, s.name AS service_name
				 FROM {$booking_services_table} bs
				 LEFT JOIN {$services_table} s ON s.id = bs.service_id
				 WHERE bs.booking_id = %d
				 ORDER BY bs.service_id ASC",
				$booking_id
			),
			ARRAY_A
		);

		$meta = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT meta_key, meta_value
				 FROM {$booking_meta_table}
				 WHERE booking_id = %d
				 ORDER BY id ASC",
				$booking_id
			),
			ARRAY_A
		);

		$details = [
			'services' => is_array($services) ? $services : [],
			'meta' => is_array($meta) ? $meta : [],
		];

		wp_cache_set($cache_key, $details, 'vstrb_emails', 300);
		return $details;
	}

	private static function booking_meta_map(array $meta_rows) {
		$out = [];
		foreach ($meta_rows as $meta) {
			$key = isset($meta['meta_key']) ? self::safe_text($meta['meta_key']) : '';
			if ($key === '') {
				continue;
			}
			$out[$key] = isset($meta['meta_value']) ? (string) $meta['meta_value'] : '';
		}
		return $out;
	}

	private static function is_internal_booking_meta_key($key) {
		$key = self::safe_text($key);
		$internal = [
			'arrival_instructions_sent_at',
			'billing_enabled',
			'billing_name',
			'billing_company_id',
			'billing_vat_id',
			'billing_street',
			'billing_city',
			'billing_zip',
			'billing_country',
			'customer_locale',
			'invoice_currency',
			'invoice_issued_at',
			'invoice_number',
			'invoice_paid_at',
			'invoice_status',
			'invoice_total',
			'payment_amount',
			'payment_amount_due',
			'payment_created_at',
			'payment_currency',
			'payment_gateway',
			'payment_gateway_error',
			'payment_gateway_intent_id',
			'payment_gateway_session_id',
			'payment_gateway_url',
			'payment_method',
			'payment_paid_at',
			'payment_provider',
			'payment_reference',
			'payment_status',
			'price_breakdown',
			'price_offer_include_payment_details',
			'price_offer_note',
			'price_offer_recipient',
			'price_offer_sent_at',
			'price_offer_token',
			'price_offer_updated_at',
			'refundable_deposit',
			'total_with_deposit',
		];
		return in_array($key, $internal, true);
	}

	private static function format_qty($value) {
		$value = (float) $value;
		if (abs($value - round($value)) < 0.00001) {
			return self::plain_text_value(number_format_i18n($value, 0));
		}
		return self::plain_text_value(number_format_i18n($value, 2));
	}

	private static function format_money($amount, $currency) {
		return self::plain_text_value(number_format_i18n((float) $amount, 2)) . ' ' . self::safe_text($currency);
	}

	private static function build_price_breakdown_block($booking, array $meta_map) {
		$raw = (string) ($meta_map['price_breakdown'] ?? '');
		$data = $raw !== '' ? json_decode($raw, true) : null;
		if (!is_array($data)) {
			return '';
		}

		$currency = self::safe_text($data['currency'] ?? ($booking->currency ?? ''));
		$nights = (int) ($data['nights'] ?? 0);
		$base = (float) ($data['base'] ?? 0);
		$services_total = (float) ($data['services_total'] ?? 0);
		$discount = (float) ($data['discount_total'] ?? 0);
		$surcharge = (float) ($data['surcharge_total'] ?? 0);
		$deposit = (float) ($data['refundable_deposit'] ?? 0);
		$total = (float) ($data['total'] ?? ($booking->total_price ?? 0));
		$services = isset($data['services']) && is_array($data['services']) ? $data['services'] : [];
		$discount_lines = isset($data['discount_lines']) && is_array($data['discount_lines']) ? $data['discount_lines'] : [];
		$surcharge_lines = isset($data['surcharge_lines']) && is_array($data['surcharge_lines']) ? $data['surcharge_lines'] : [];

		$block = '- ' . __('Nights', 'vachr-short-term-rental-reservations') . ': ' . $nights . "\n";
		$block .= '- ' . __('Accommodation', 'vachr-short-term-rental-reservations') . ': ' . self::format_money($base, $currency) . "\n";

		if ($services_total > 0 || !empty($services)) {
			$block .= '- ' . __('Services', 'vachr-short-term-rental-reservations') . ': ' . self::format_money($services_total, $currency) . "\n";
			foreach ($services as $service) {
				if (!is_array($service)) {
					continue;
				}
				$name = self::safe_text($service['name'] ?? '');
				$qty = self::format_qty($service['qty'] ?? 0);
				$unit = self::format_money($service['unit_price'] ?? 0, $currency);
				$line_total = self::format_money($service['total'] ?? 0, $currency);
				$block .= '  - ' . $name . ' (' . $qty . ' x ' . $unit . '): ' . $line_total . "\n";
			}
		}

		if ($discount > 0) {
			$block .= '- ' . __('Discount', 'vachr-short-term-rental-reservations') . ': -' . self::format_money($discount, $currency) . "\n";
			foreach ($discount_lines as $line) {
				if (!is_array($line)) {
					continue;
				}
				$label = self::safe_text($line['label'] ?? __('Discount', 'vachr-short-term-rental-reservations'));
				$block .= '  - ' . $label . ': -' . self::format_money($line['amount'] ?? 0, $currency) . "\n";
			}
		}

		if ($surcharge > 0) {
			$block .= '- ' . __('Surcharge', 'vachr-short-term-rental-reservations') . ': +' . self::format_money($surcharge, $currency) . "\n";
			foreach ($surcharge_lines as $line) {
				if (!is_array($line)) {
					continue;
				}
				$label = self::safe_text($line['label'] ?? __('Surcharge', 'vachr-short-term-rental-reservations'));
				$block .= '  - ' . $label . ': +' . self::format_money($line['amount'] ?? 0, $currency) . "\n";
			}
		}

		$block .= '- ' . __('Final total', 'vachr-short-term-rental-reservations') . ': ' . self::format_money($total, $currency) . "\n";
		if ($deposit > 0) {
			$block .= '- ' . __('Refundable deposit', 'vachr-short-term-rental-reservations') . ': +' . self::format_money($deposit, $currency) . "\n";
		}

		return rtrim($block);
	}

	private static function build_payment_instructions_block($booking, array $meta_map) {
		$payment_method = self::safe_text($meta_map['payment_method'] ?? '');
		$payment_status = self::safe_text($meta_map['payment_status'] ?? '');
		if ($payment_method === 'card') {
			if ($payment_status === 'paid') {
				return '';
			}
			$payment_url = esc_url_raw((string) ($meta_map['payment_gateway_url'] ?? ''));
			if ($payment_url === '') {
				return '';
			}
			$amount = self::safe_text($meta_map['payment_amount_due'] ?? '');
			$currency = self::safe_text($meta_map['payment_currency'] ?? ($booking->currency ?? ''));
			$block = __('Payment Instructions', 'vachr-short-term-rental-reservations') . ":\n";
			if ($amount !== '') {
				$block .= __('Amount to pay:', 'vachr-short-term-rental-reservations') . ' ' . $amount . ($currency !== '' ? ' ' . $currency : '') . "\n";
			}
			$block .= __('If the card payment was not completed, use this link:', 'vachr-short-term-rental-reservations') . "\n";
			$block .= __('Pay by card:', 'vachr-short-term-rental-reservations') . ' ' . $payment_url . "\n";
			return rtrim($block);
		}

		if ($payment_method === 'bank_transfer') {
			$locale = (string) ($meta_map['customer_locale'] ?? '');
			$bank_instructions = self::resolve_unit_content((int) $booking->unit_id, 'bank_transfer_instructions', $locale, $booking);
			if ($bank_instructions !== '') {
				return __('Bank Transfer Instructions', 'vachr-short-term-rental-reservations') . ":\n" . $bank_instructions;
			}
		}

		return '';
	}

	private static function build_summary_block($booking) {
		if (!$booking || empty($booking->id)) {
			return '';
		}

		$details = self::get_booking_details_cached((int) $booking->id);
		$services = $details['services'] ?? [];
		$meta_rows = $details['meta'] ?? [];
		$meta_map = self::booking_meta_map($meta_rows);
		$note = trim(self::safe_text($booking->note ?? ''));
		$price_breakdown = self::build_price_breakdown_block($booking, $meta_map);

		if ($price_breakdown === '' && empty($services) && empty($meta_rows) && $note === '') {
			return '';
		}

		$summary = __("Summary:", 'vachr-short-term-rental-reservations') . "\n";
		$has_summary_content = false;

		if ($price_breakdown !== '') {
			$summary .= $price_breakdown . "\n";
			$has_summary_content = true;
		} elseif (!empty($services)) {
			$summary .= __("Selected Services:", 'vachr-short-term-rental-reservations') . "\n";
			foreach ($services as $service) {
				$service_name = !empty($service['service_name']) ? self::safe_text($service['service_name']) : sprintf('#%d', (int) ($service['service_id'] ?? 0));
				$qty = self::format_qty($service['qty'] ?? 0);
				$price = self::format_money($service['price'] ?? 0, $booking->currency ?? '');
				$line_total = self::format_money((float) ($service['qty'] ?? 0) * (float) ($service['price'] ?? 0), $booking->currency ?? '');
				$summary .= sprintf('- %s: %s x %s = %s', $service_name, $qty, $price, $line_total) . "\n";
			}
			$has_summary_content = true;
		}

		if (!empty($meta_rows)) {
			$custom_lines = [];
			foreach ($meta_rows as $meta) {
				$key = isset($meta['meta_key']) ? self::safe_text($meta['meta_key']) : '';
				$val = isset($meta['meta_value']) ? self::safe_text($meta['meta_value']) : '';
				if (($key === '' && $val === '') || self::is_internal_booking_meta_key($key)) {
					continue;
				}
				$custom_lines[] = sprintf('- %s: %s', $key, $val);
			}
			if (!empty($custom_lines)) {
				$summary .= __("Custom Contact Fields:", 'vachr-short-term-rental-reservations') . "\n";
				$summary .= implode("\n", $custom_lines) . "\n";
				$has_summary_content = true;
			}
		}

		if ($note !== '') {
			$summary .= __("Note:", 'vachr-short-term-rental-reservations') . " " . $note . "\n";
			$has_summary_content = true;
		}

		return $has_summary_content ? rtrim($summary) : '';
	}

	private static function get_price_offer_token($booking_id) {
		$booking_id = (int) $booking_id;
		if ($booking_id <= 0) {
			return '';
		}
		$token = self::get_booking_meta_value($booking_id, 'price_offer_token');
		if (preg_match('/^[a-z0-9]{32,64}$/', $token)) {
			return $token;
		}
		$token = strtolower(preg_replace('/[^a-z0-9]/i', '', wp_generate_password(40, false, false)));
		self::upsert_booking_meta_value($booking_id, 'price_offer_token', $token);
		return $token;
	}

	public static function price_offer_url($booking_id) {
		$booking_id = (int) $booking_id;
		if ($booking_id <= 0) {
			return '';
		}
		$token = self::get_price_offer_token($booking_id);
		if ($token === '') {
			return '';
		}
		return add_query_arg(
			[
				'vstrb_price_offer' => $booking_id,
				'token' => $token,
			],
			home_url('/')
		);
	}

	public static function maybe_render_price_offer_page() {
		// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Public read-only price offer URL is protected by a one-time token.
		$booking_id = isset($_GET['vstrb_price_offer']) ? (int) sanitize_text_field(wp_unslash($_GET['vstrb_price_offer'])) : 0;
		$token = isset($_GET['token']) ? sanitize_text_field(wp_unslash($_GET['token'])) : '';
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
		if ($booking_id <= 0 || $token === '') {
			return;
		}
		$stored_token = self::get_booking_meta_value($booking_id, 'price_offer_token');
		if ($stored_token === '' || !hash_equals((string) $stored_token, (string) $token)) {
			status_header(404);
			wp_die(esc_html__('Price offer not found.', 'vachr-short-term-rental-reservations'));
		}
		$booking = self::get_booking_cached($booking_id);
		if (!$booking) {
			status_header(404);
			wp_die(esc_html__('Price offer not found.', 'vachr-short-term-rental-reservations'));
		}

		$unit_title = get_the_title((int) $booking->unit_id);
		$summary_block = self::build_summary_block($booking);
		$note = self::get_booking_meta_value($booking_id, 'price_offer_note');
		$details = self::get_booking_details_cached($booking_id);
		$meta_map = self::booking_meta_map($details['meta'] ?? []);
		$locale = (string) ($meta_map['customer_locale'] ?? '');
		$display_total = (float) ($booking->total_price ?? 0);
		$display_currency = (string) ($booking->currency ?? '');
		$include_payment_details = ((string) ($meta_map['price_offer_include_payment_details'] ?? '') === '1');
		$payment_status = self::safe_text($meta_map['payment_status'] ?? '');
		$payment_url = ($include_payment_details && $payment_status !== 'paid') ? esc_url_raw((string) ($meta_map['payment_gateway_url'] ?? '')) : '';
		$bank_instructions = $include_payment_details ? self::resolve_unit_content((int) $booking->unit_id, 'bank_transfer_instructions', $locale, $booking) : '';
		$invoice_number = self::safe_text($meta_map['invoice_number'] ?? '');
		$payment_amount_due = (float) self::safe_text($meta_map['payment_amount_due'] ?? '');
		if ($payment_amount_due <= 0) {
			$payment_amount_due = $display_total;
		}
		$payment_qr_payload = $include_payment_details
			? VSTRB_Payment_QR::payload(
				$payment_amount_due,
				$display_currency,
				$invoice_number
			)
			: '';
		/* translators: %s: rental unit title. */
		$title = sprintf(__('Price Offer: %s', 'vachr-short-term-rental-reservations'), $unit_title);
		status_header(200);
		nocache_headers();
		?>
		<!doctype html>
		<html <?php language_attributes(); ?>>
		<head>
			<meta charset="<?php bloginfo('charset'); ?>">
			<meta name="viewport" content="width=device-width, initial-scale=1">
			<title><?php echo esc_html($title); ?></title>
			<?php wp_head(); ?>
			<style>
				body.vstrb-price-offer-page{background:#f6f7f7;color:#1d2327;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;margin:0}
				.vstrb-price-offer-wrap{max-width:760px;margin:32px auto;padding:24px;background:#fff;border:1px solid #dcdcde}
				.vstrb-price-offer-wrap h1{margin:0 0 16px;font-size:28px;line-height:1.25}
				.vstrb-price-offer-meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin:18px 0}
				.vstrb-price-offer-meta div,.vstrb-price-offer-summary,.vstrb-price-offer-note{background:#f6f7f7;padding:12px;border:1px solid #dcdcde}
				.vstrb-price-offer-total{font-size:24px;font-weight:700;margin:20px 0}
				.vstrb-price-offer-links{display:flex;flex-wrap:wrap;gap:10px;margin:18px 0}
				.vstrb-price-offer-link{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;background:#2271b1;color:#fff;text-decoration:none;border-radius:3px;font-weight:600}
				.vstrb-price-offer-link:hover,.vstrb-price-offer-link:focus{background:#135e96;color:#fff}
				.vstrb-price-offer-link.secondary{background:#f6f7f7;color:#1d2327;border:1px solid #c3c4c7}
				.vstrb-price-offer-link.secondary:hover,.vstrb-price-offer-link.secondary:focus{background:#fff;color:#1d2327}
				.vstrb-price-offer-summary pre{white-space:pre-wrap;font-family:inherit;margin:0;line-height:1.55}
				.vstrb-price-offer-bank{background:#f6f7f7;padding:12px;border:1px solid #dcdcde;margin-top:16px}
				.vstrb-price-offer-bank pre{white-space:pre-wrap;font-family:inherit;margin:8px 0 0;line-height:1.55}
				.vstrb-payment-qr-code img,.vstrb-payment-qr-code canvas{display:block;max-width:180px;height:auto}
			</style>
		</head>
		<body <?php body_class('vstrb-price-offer-page'); ?>>
			<main class="vstrb-price-offer-wrap">
				<h1><?php echo esc_html($title); ?></h1>
				<div class="vstrb-price-offer-meta">
					<div><strong><?php esc_html_e('Unit:', 'vachr-short-term-rental-reservations'); ?></strong><br><?php echo esc_html($unit_title); ?></div>
					<div><strong><?php esc_html_e('Dates:', 'vachr-short-term-rental-reservations'); ?></strong><br><?php echo esc_html(self::safe_text($booking->date_from ?? '') . ' - ' . self::safe_text($booking->date_to ?? '')); ?></div>
					<div><strong><?php esc_html_e('Guests:', 'vachr-short-term-rental-reservations'); ?></strong><br><?php echo (int) ($booking->guests ?? 1); ?></div>
				</div>
				<div class="vstrb-price-offer-total"><?php echo esc_html__('Total Price:', 'vachr-short-term-rental-reservations') . ' ' . esc_html(self::format_money($display_total, $display_currency)); ?></div>
				<div class="vstrb-price-offer-links">
					<?php if ($payment_url !== '') : ?>
						<a class="vstrb-price-offer-link" href="<?php echo esc_url($payment_url); ?>"><?php esc_html_e('Pay by card', 'vachr-short-term-rental-reservations'); ?></a>
					<?php endif; ?>
					<?php if (trim($bank_instructions) !== '' || $payment_qr_payload !== '') : ?>
						<a class="vstrb-price-offer-link secondary" href="#vstrb-bank-transfer"><?php esc_html_e('Bank transfer', 'vachr-short-term-rental-reservations'); ?></a>
					<?php endif; ?>
				</div>
				<?php if ($summary_block !== '') : ?>
					<section class="vstrb-price-offer-summary"><pre><?php echo esc_html($summary_block); ?></pre></section>
				<?php endif; ?>
				<?php if (trim($bank_instructions) !== '' || $payment_qr_payload !== '') : ?>
					<section id="vstrb-bank-transfer" class="vstrb-price-offer-bank">
						<strong><?php esc_html_e('Bank Transfer Instructions', 'vachr-short-term-rental-reservations'); ?></strong>
						<?php if (trim($bank_instructions) !== '') : ?>
							<pre><?php echo esc_html($bank_instructions); ?></pre>
						<?php endif; ?>
						<?php VSTRB_Payment_QR::render($payment_qr_payload); ?>
					</section>
				<?php endif; ?>
				<?php if (trim($note) !== '') : ?>
					<section class="vstrb-price-offer-note"><strong><?php esc_html_e('Note:', 'vachr-short-term-rental-reservations'); ?></strong><br><?php echo nl2br(esc_html($note)); ?></section>
				<?php endif; ?>
			</main>
			<?php wp_footer(); ?>
			<?php if ($payment_qr_payload !== '') VSTRB_Payment_QR::render_assets(); ?>
		</body>
		</html>
		<?php
		exit;
	}

	private static function notify_staff_new_booking($booking) {
		if (!$booking || empty($booking->id)) {
			return;
		}
		$rules = VSTRB_Staff_Access::get_rules();
		if (empty($rules)) {
			return;
		}

		foreach ($rules as $rule) {
			if (!VSTRB_Staff_Access::wants_event($rule, 'new_booking')) {
				continue;
			}
			if (class_exists('VSTRB_Staff_Tasks') && VSTRB_Staff_Tasks::confirmation_required_for_rule($rule)) continue;
			$switched_locale = self::maybe_switch_staff_locale($rule);
			$unit_title = get_the_title((int) $booking->unit_id);
			/* translators: %s: apartment title */
			$subject = sprintf(__('New Booking: %s', 'vachr-short-term-rental-reservations'), $unit_title);
			/* translators: %s: apartment title */
			$message = sprintf(__('New booking received for %s', 'vachr-short-term-rental-reservations'), $unit_title) . "\n\n";
			foreach (VSTRB_Staff_Access::booking_lines($booking, $rule) as $label => $value) {
				$message .= $label . ': ' . $value . "\n";
			}
			wp_mail($rule['email'], $subject, $message);
			self::maybe_restore_staff_locale($switched_locale);
		}
	}

	private static function maybe_switch_staff_locale($rule) {
		$locale = self::resolve_wp_locale(VSTRB_Staff_Access::locale($rule));
		if ($locale !== '' && function_exists('switch_to_locale') && switch_to_locale($locale)) {
			self::load_textdomain_for_locale($locale);
			return true;
		}
		return false;
	}

	private static function maybe_restore_staff_locale($switched) {
		if ($switched && function_exists('restore_previous_locale')) {
			restore_previous_locale();
			self::load_textdomain_for_locale(get_locale());
		}
	}

	private static function staff_event_sent_meta_key($event, $email) {
		$event = sanitize_key((string) $event);
		$email = strtolower(sanitize_email((string) $email));
		if ($event === '' || $email === '') {
			return '';
		}
		return 'staff_' . $event . '_notification_sent_' . md5($email);
	}

	private static function has_staff_event_been_sent($booking_id, $event, $email) {
		$meta_key = self::staff_event_sent_meta_key($event, $email);
		if ($meta_key === '') {
			return false;
		}
		if (self::get_booking_meta_value((int) $booking_id, $meta_key) !== '') {
			return true;
		}
		$legacy_key = $event === 'arrival' ? 'staff_arrival_notification_sent_at' : 'staff_departure_notification_sent_at';
		return self::get_booking_meta_value((int) $booking_id, $legacy_key) !== '';
	}

	private static function mark_staff_event_sent($booking_id, $event, $email) {
		$meta_key = self::staff_event_sent_meta_key($event, $email);
		if ($meta_key === '') {
			return false;
		}
		return self::add_booking_meta_value((int) $booking_id, $meta_key, current_time('mysql'));
	}

	private static function staff_event_is_due($booking, $event, $rule, $today) {
		$date_field = $event === 'arrival' ? 'date_from' : 'date_to';
		$event_date = (string) ($booking->{$date_field} ?? '');
		if ($event_date === '' || $event_date < $today) {
			return false;
		}
		$days_before = VSTRB_Staff_Access::days_before($rule);
		$send_date = gmdate('Y-m-d', strtotime($event_date . ' -' . $days_before . ' days'));
		return $send_date <= $today;
	}

	private static function staff_max_days_before($rules, $event) {
		$max = 1;
		foreach ((array) $rules as $rule) {
			if (VSTRB_Staff_Access::wants_event($rule, $event)) {
				$max = max($max, VSTRB_Staff_Access::days_before($rule));
			}
		}
		return max(0, min(365, $max));
	}

	private static function staff_booking_status_where() {
		return "(b.status <> 'cancelled' AND (b.status <> 'blocked' OR b.source LIKE 'ical_%'))";
	}

	private static function staff_test_event_for_rule($rule) {
		foreach (['departure', 'arrival', 'new_booking', 'ical_cancelled'] as $event) {
			if (VSTRB_Staff_Access::wants_event($rule, $event)) {
				return $event;
			}
		}
		return '';
	}

	private static function find_staff_test_booking($event, $rule) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$today = current_time('Y-m-d');
		$status_where = self::staff_booking_status_where();

		if ($event === 'arrival' || $event === 'departure') {
			$date_field = $event === 'arrival' ? 'date_from' : 'date_to';
			$until = gmdate('Y-m-d', strtotime($today . ' +' . VSTRB_Staff_Access::days_before($rule) . ' days'));
			return $wpdb->get_row($wpdb->prepare(
				"SELECT b.*
				 FROM {$t['bookings']} b
				 WHERE {$status_where}
				   AND b.{$date_field} BETWEEN %s AND %s
				 ORDER BY b.{$date_field} ASC, b.id ASC
				 LIMIT 1",
				$today,
				$until
			));
		}

		return $wpdb->get_row(
			"SELECT b.*
			 FROM {$t['bookings']} b
			 WHERE {$status_where}
			 ORDER BY b.date_from ASC, b.id ASC
			 LIMIT 1"
		);
	}

	private static function build_staff_booking_notification($booking, $rule, $event, $is_test = false) {
		$unit_title = get_the_title((int) $booking->unit_id);
		if ($event === 'departure') {
			/* translators: %s: apartment title */
			$subject = sprintf(__('Departure Reminder: %s', 'vachr-short-term-rental-reservations'), $unit_title);
			/* translators: 1: checkout date, 2: apartment title */
			$message = sprintf(__('Guests are checking out on %1$s for %2$s.', 'vachr-short-term-rental-reservations'), self::safe_text($booking->date_to ?? ''), $unit_title) . "\n\n";
		} elseif ($event === 'arrival') {
			/* translators: %s: apartment title */
			$subject = sprintf(__('Arrival Reminder: %s', 'vachr-short-term-rental-reservations'), $unit_title);
			/* translators: 1: check-in date, 2: apartment title */
			$message = sprintf(__('Guests are arriving on %1$s for %2$s.', 'vachr-short-term-rental-reservations'), self::safe_text($booking->date_from ?? ''), $unit_title) . "\n\n";
		} elseif ($event === 'ical_cancelled') {
			/* translators: %s: apartment title */
			$subject = sprintf(__('iCal Reservation Cancelled: %s', 'vachr-short-term-rental-reservations'), $unit_title);
			/* translators: %s: apartment title */
			$message = sprintf(__('An imported iCal reservation was cancelled for %s.', 'vachr-short-term-rental-reservations'), $unit_title) . "\n\n";
		} else {
			/* translators: %s: apartment title */
			$subject = sprintf(__('New Booking: %s', 'vachr-short-term-rental-reservations'), $unit_title);
			/* translators: %s: apartment title */
			$message = sprintf(__('New booking received for %s', 'vachr-short-term-rental-reservations'), $unit_title) . "\n\n";
		}

		if ($is_test) {
			$subject = '[TEST] ' . $subject;
		}

		foreach (VSTRB_Staff_Access::booking_lines($booking, $rule) as $label => $value) {
			$message .= $label . ': ' . $value . "\n";
		}

		return [$subject, $message];
	}

	public static function send_staff_notification_test($rule_text, $booking_id = 0) {
		$rules = VSTRB_Staff_Access::parse_rules_text((string) $rule_text);
		if (empty($rules)) {
			return [
				'ok' => false,
				'message' => __('No valid staff access rule found. Use email|fields|events|language|days_before.', 'vachr-short-term-rental-reservations'),
			];
		}

		$rule = $rules[0];
		$event = self::staff_test_event_for_rule($rule);
		if ($event === '') {
			return [
				'ok' => false,
				'message' => __('The rule does not contain a supported booking notification event.', 'vachr-short-term-rental-reservations'),
			];
		}

		$booking = $booking_id > 0 ? self::get_booking_cached((int) $booking_id) : self::find_staff_test_booking($event, $rule);
		if (!$booking || empty($booking->id)) {
			return [
				'ok' => false,
				'message' => __('No matching booking was found for this test. Enter a booking ID or choose a rule whose reminder date matches an upcoming booking.', 'vachr-short-term-rental-reservations'),
			];
		}

		if ((string) ($booking->status ?? '') === 'cancelled') {
			return [
				'ok' => false,
				'message' => __('The selected booking is cancelled, so no staff notification was sent.', 'vachr-short-term-rental-reservations'),
			];
		}

		$switched_locale = self::maybe_switch_staff_locale($rule);
		[$subject, $message] = self::build_staff_booking_notification($booking, $rule, $event, true);
		$sent = wp_mail($rule['email'], $subject, $message);
		self::maybe_restore_staff_locale($switched_locale);

		if (!$sent) {
			return [
				'ok' => false,
				'message' => sprintf(
					/* translators: 1: recipient email, 2: booking ID */
					__('Test notification could not be sent to %1$s for booking #%2$d. Check WordPress mail delivery/SMTP.', 'vachr-short-term-rental-reservations'),
					$rule['email'],
					(int) $booking->id
				),
			];
		}

		return [
			'ok' => true,
			'message' => sprintf(
				/* translators: 1: event name, 2: recipient email, 3: booking ID */
				__('Test %1$s notification sent to %2$s for booking #%3$d.', 'vachr-short-term-rental-reservations'),
				$event,
				$rule['email'],
				(int) $booking->id
			),
		];
	}

	private static function notify_staff_departure($booking, $today = '') {
		if (!$booking || empty($booking->id)) {
			return false;
		}
		$rules = VSTRB_Staff_Access::get_rules();
		if (empty($rules)) {
			return false;
		}

		$today = $today !== '' ? $today : current_time('Y-m-d');
		$sent_any = false;
		foreach ($rules as $rule) {
			if (!VSTRB_Staff_Access::wants_event($rule, 'departure')) {
				continue;
			}
			if (class_exists('VSTRB_Staff_Tasks') && VSTRB_Staff_Tasks::confirmation_required_for_rule($rule)) continue;
			if (!self::staff_event_is_due($booking, 'departure', $rule, $today) || self::has_staff_event_been_sent((int) $booking->id, 'departure', $rule['email'])) {
				continue;
			}
			$switched_locale = self::maybe_switch_staff_locale($rule);
			[$subject, $message] = self::build_staff_booking_notification($booking, $rule, 'departure');
			$sent = wp_mail($rule['email'], $subject, $message);
			if ($sent) {
				self::mark_staff_event_sent((int) $booking->id, 'departure', $rule['email']);
			}
			$sent_any = $sent || $sent_any;
			self::maybe_restore_staff_locale($switched_locale);
		}
		return $sent_any;
	}

	private static function notify_staff_arrival($booking, $today = '') {
		if (!$booking || empty($booking->id)) {
			return false;
		}
		$rules = VSTRB_Staff_Access::get_rules();
		if (empty($rules)) {
			return false;
		}

		$today = $today !== '' ? $today : current_time('Y-m-d');
		$sent_any = false;
		foreach ($rules as $rule) {
			if (!VSTRB_Staff_Access::wants_event($rule, 'arrival')) {
				continue;
			}
			if (class_exists('VSTRB_Staff_Tasks') && VSTRB_Staff_Tasks::confirmation_required_for_rule($rule)) continue;
			if (!self::staff_event_is_due($booking, 'arrival', $rule, $today) || self::has_staff_event_been_sent((int) $booking->id, 'arrival', $rule['email'])) {
				continue;
			}
			$switched_locale = self::maybe_switch_staff_locale($rule);
			[$subject, $message] = self::build_staff_booking_notification($booking, $rule, 'arrival');
			$sent = wp_mail($rule['email'], $subject, $message);
			if ($sent) {
				self::mark_staff_event_sent((int) $booking->id, 'arrival', $rule['email']);
			}
			$sent_any = $sent || $sent_any;
			self::maybe_restore_staff_locale($switched_locale);
		}
		return $sent_any;
	}

	private static function notify_staff_ical_cancelled($booking) {
		if (!$booking || empty($booking->id)) {
			return false;
		}
		$rules = VSTRB_Staff_Access::get_rules();
		if (empty($rules)) {
			return false;
		}

		$sent_any = false;
		foreach ($rules as $rule) {
			if (!VSTRB_Staff_Access::wants_event($rule, 'ical_cancelled')) {
				continue;
			}
			$switched_locale = self::maybe_switch_staff_locale($rule);
			$unit_title = get_the_title((int) $booking->unit_id);
			/* translators: %s: apartment title */
			$subject = sprintf(__('iCal Reservation Cancelled: %s', 'vachr-short-term-rental-reservations'), $unit_title);
			/* translators: %s: apartment title */
			$message = sprintf(__('An imported iCal reservation was cancelled for %s.', 'vachr-short-term-rental-reservations'), $unit_title) . "\n\n";
			foreach (VSTRB_Staff_Access::booking_lines($booking, $rule) as $label => $value) {
				$message .= $label . ': ' . $value . "\n";
			}
			$sent = wp_mail($rule['email'], $subject, $message);
			$sent_any = $sent || $sent_any;
			self::maybe_restore_staff_locale($switched_locale);
		}
		return $sent_any;
	}

	public static function notify_ical_booking_cancelled($booking) {
		if (!$booking || empty($booking->id)) {
			return false;
		}

		self::notify_staff_ical_cancelled($booking);

		$admin_email = self::safe_email_recipient(get_option('admin_email'));
		if ($admin_email === '') {
			return false;
		}
		$unit_title = get_the_title((int) $booking->unit_id);
		/* translators: %s: apartment title */
		$subject = sprintf(__('iCal Reservation Cancelled: %s', 'vachr-short-term-rental-reservations'), $unit_title);
		/* translators: %s: apartment title */
		$message = sprintf(__('An imported iCal reservation was cancelled for %s.', 'vachr-short-term-rental-reservations'), $unit_title) . "\n\n";
		$message .= __('Details:', 'vachr-short-term-rental-reservations') . "\n";
		$message .= __('Unit:', 'vachr-short-term-rental-reservations') . ' ' . $unit_title . "\n";
		$message .= __('Dates:', 'vachr-short-term-rental-reservations') . ' ' . self::safe_text($booking->date_from ?? '') . ' to ' . self::safe_text($booking->date_to ?? '') . "\n";
		$message .= __('Booking Source:', 'vachr-short-term-rental-reservations') . ' ' . self::safe_text($booking->source ?? '') . "\n";
		if (!empty($booking->external_booking_id)) {
			$message .= __('Booking Reference:', 'vachr-short-term-rental-reservations') . ' ' . self::safe_text($booking->external_booking_id) . "\n";
		}
		if (!empty($booking->ical_summary)) {
			$message .= __('iCal Summary:', 'vachr-short-term-rental-reservations') . ' ' . self::safe_text($booking->ical_summary) . "\n";
		}
		if (!empty($booking->ical_uid)) {
			$message .= 'UID: ' . self::safe_text($booking->ical_uid) . "\n";
		}
		$message .= "\n" . __('Manage bookings:', 'vachr-short-term-rental-reservations') . ' ' . admin_url('admin.php?page=vstrb');

		return (bool) wp_mail($admin_email, $subject, $message);
	}

	private static function notify_staff_guest_registration_group($unit_id, array $regs, $date_from, $date_to, $email) {
		$rules = VSTRB_Staff_Access::get_rules();
		if (empty($rules)) {
			return;
		}

		$guest_count = count($regs);
		$source = !empty($regs) ? (string) ($regs[0]->source ?? '') : '';
		$booking_reference = !empty($regs) ? (string) ($regs[0]->booking_reference ?? '') : '';
		$guest_names = [];
		foreach ($regs as $reg) {
			$guest_names[] = trim((string) ($reg->first_name ?? '') . ' ' . (string) ($reg->last_name ?? ''));
		}
		foreach ($rules as $rule) {
			if (!VSTRB_Staff_Access::wants_event($rule, 'registration')) {
				continue;
			}
			$switched_locale = self::maybe_switch_staff_locale($rule);
			$unit_title = get_the_title((int) $unit_id);
			/* translators: %s: apartment title */
			$subject = sprintf(__('New Guest Registration: %s', 'vachr-short-term-rental-reservations'), $unit_title);
			/* translators: 1: count, 2: apartment title */
			$message = sprintf(_n('New guest registration received for %2$s (%1$d guest)', 'New guest registration received for %2$s (%1$d guests)', (int) $guest_count, 'vachr-short-term-rental-reservations'), (int) $guest_count, $unit_title) . "\n\n";
			foreach (VSTRB_Staff_Access::registration_group_lines($unit_id, $guest_count, $date_from, $date_to, $email, $source, $booking_reference, $guest_names, $rule) as $label => $value) {
				$message .= $label . ': ' . $value . "\n";
			}
			wp_mail($rule['email'], $subject, $message);
			self::maybe_restore_staff_locale($switched_locale);
		}
	}

	public static function notify_admin_new_booking($booking_id) {
		$booking = self::get_booking_cached($booking_id);

		if (!$booking) return;
		self::notify_staff_new_booking($booking);

		$admin_email = self::safe_email_recipient(get_option('admin_email'));
		if ($admin_email === '') return;
		$unit_title = get_the_title($booking->unit_id);
		
		/* translators: %s: apartment title */
		$subject = sprintf(__('New Booking: %s', 'vachr-short-term-rental-reservations'), $unit_title);
		
		/* translators: %s: apartment title */
		$message = sprintf(__("New booking received for %s", 'vachr-short-term-rental-reservations'), $unit_title) . "\n\n";
		$message .= __("ID:", 'vachr-short-term-rental-reservations') . " " . $booking->id . "\n";
		$message .= __("Customer:", 'vachr-short-term-rental-reservations') . " " . $booking->customer_name . " " . $booking->customer_lastname . "\n";
		$message .= __("Email:", 'vachr-short-term-rental-reservations') . " " . $booking->customer_email . "\n";
		$message .= __("Phone:", 'vachr-short-term-rental-reservations') . " " . $booking->customer_phone . "\n";
		$message .= __("Dates:", 'vachr-short-term-rental-reservations') . " " . $booking->date_from . " to " . $booking->date_to . "\n";
		$message .= __("Total:", 'vachr-short-term-rental-reservations') . " " . $booking->total_price . " " . $booking->currency . "\n";
		$summary_block = self::build_summary_block($booking);
		if ($summary_block !== '') {
			$message .= $summary_block . "\n\n";
		}
		$message .= __("Manage bookings:", 'vachr-short-term-rental-reservations') . " " . admin_url('admin.php?page=vstrb');

		wp_mail($admin_email, $subject, $message);
	}

	public static function notify_customer_confirmed($booking_id) {
		$booking = self::get_booking_cached($booking_id);

		if (!$booking || empty($booking->customer_email)) return;
		$customer_email = self::safe_email_recipient($booking->customer_email);
		if ($customer_email === '') return;

		$switched_locale = self::switch_to_booking_locale($booking);
		$unit_title = get_the_title($booking->unit_id);
		/* translators: %s: apartment title */
		$subject = sprintf(__('Booking Confirmed: %s', 'vachr-short-term-rental-reservations'), $unit_title);

		/* translators: %s: customer name */
		$message = sprintf(__("Hello %s,", 'vachr-short-term-rental-reservations'), $booking->customer_name) . "\n\n";
		$message .= __("Your booking has been confirmed.", 'vachr-short-term-rental-reservations') . "\n\n";
		$message .= __("Details:", 'vachr-short-term-rental-reservations') . "\n";
		$message .= __("Unit:", 'vachr-short-term-rental-reservations') . " " . $unit_title . "\n";
		/* translators: 1: arrival date, 2: departure date */
		$message .= __("Dates:", 'vachr-short-term-rental-reservations') . " " . sprintf(__('%1$s to %2$s', 'vachr-short-term-rental-reservations'), $booking->date_from, $booking->date_to) . "\n";
		$message .= __("Total Price:", 'vachr-short-term-rental-reservations') . " " . self::format_money($booking->total_price, $booking->currency) . "\n";
		$summary_block = self::build_summary_block($booking);
		if ($summary_block !== '') {
			$message .= "\n" . $summary_block . "\n";
		}
		$details = self::get_booking_details_cached((int) $booking->id);
		$meta_map = self::booking_meta_map($details['meta'] ?? []);
		$payment_block = self::build_payment_instructions_block($booking, $meta_map);
		if ($payment_block !== '') {
			$message .= "\n" . $payment_block . "\n";
		}
		$message .= "\n";
		$message .= __("Thank you!", 'vachr-short-term-rental-reservations');

		wp_mail($customer_email, $subject, $message);
		self::restore_booking_locale($switched_locale);
	}

	public static function notify_customer_booking_received($booking_id) {
		$booking = self::get_booking_cached($booking_id);

		if (!$booking || empty($booking->customer_email)) return;
		$customer_email = self::safe_email_recipient($booking->customer_email);
		if ($customer_email === '') return;

		$switched_locale = self::switch_to_booking_locale($booking);
		$unit_title = get_the_title($booking->unit_id);
		/* translators: %s: apartment title */
		$subject = sprintf(__('Booking Received: %s', 'vachr-short-term-rental-reservations'), $unit_title);

		/* translators: %s: customer name */
		$message = sprintf(__("Hello %s,", 'vachr-short-term-rental-reservations'), $booking->customer_name) . "\n\n";
		$message .= __('We received your booking request. We look forward to your visit and will contact you soon.', 'vachr-short-term-rental-reservations') . "\n\n";
		$message .= __('Details:', 'vachr-short-term-rental-reservations') . "\n";
		$message .= __('Unit:', 'vachr-short-term-rental-reservations') . " " . $unit_title . "\n";
		/* translators: 1: arrival date, 2: departure date */
		$message .= __('Dates:', 'vachr-short-term-rental-reservations') . " " . sprintf(__('%1$s to %2$s', 'vachr-short-term-rental-reservations'), $booking->date_from, $booking->date_to) . "\n";
		$message .= __('Total Price:', 'vachr-short-term-rental-reservations') . " " . self::format_money($booking->total_price, $booking->currency) . "\n";
		$summary_block = self::build_summary_block($booking);
		if ($summary_block !== '') {
			$message .= "\n" . $summary_block . "\n";
		}
		$details = self::get_booking_details_cached((int) $booking->id);
		$meta_map = self::booking_meta_map($details['meta'] ?? []);
		$payment_block = self::build_payment_instructions_block($booking, $meta_map);
		if ($payment_block !== '') {
			$message .= "\n" . $payment_block . "\n";
		}
		$message .= "\n";
		$message .= __('Thank you!', 'vachr-short-term-rental-reservations');

		wp_mail($customer_email, $subject, $message);
		self::restore_booking_locale($switched_locale);
	}

	public static function notify_customer_price_offer($booking_id, $recipient = '') {
		$booking = self::get_booking_cached($booking_id);

		if (!$booking) {
			return false;
		}
		$customer_email = self::safe_email_recipient($recipient !== '' ? $recipient : ($booking->customer_email ?? ''));
		if ($customer_email === '') {
			return false;
		}

		$switched_locale = self::switch_to_booking_locale($booking);
		$unit_title = get_the_title((int) $booking->unit_id);
		/* translators: %s: apartment title */
		$subject = sprintf(__('Price Offer: %s', 'vachr-short-term-rental-reservations'), $unit_title);
		$guest_name = trim((string) ($booking->customer_name ?? ''));
		$message = ($guest_name !== '')
			/* translators: %s: customer first name */
			? sprintf(__('Hello %s,', 'vachr-short-term-rental-reservations'), $guest_name) . "\n\n"
			: __('Hello,', 'vachr-short-term-rental-reservations') . "\n\n";
		$message .= __('we are sending you the price offer for your stay.', 'vachr-short-term-rental-reservations') . "\n\n";
		$message .= __('Details:', 'vachr-short-term-rental-reservations') . "\n";
		$message .= __('Unit:', 'vachr-short-term-rental-reservations') . ' ' . $unit_title . "\n";
		/* translators: 1: arrival date, 2: departure date */
		$message .= __('Dates:', 'vachr-short-term-rental-reservations') . ' ' . sprintf(__('%1$s to %2$s', 'vachr-short-term-rental-reservations'), self::safe_text($booking->date_from ?? ''), self::safe_text($booking->date_to ?? '')) . "\n";
		$message .= __('Guests:', 'vachr-short-term-rental-reservations') . ' ' . (int) ($booking->guests ?? 1) . "\n";
		$message .= __('Total Price:', 'vachr-short-term-rental-reservations') . ' ' . self::format_money($booking->total_price ?? 0, $booking->currency ?? '') . "\n";
		$offer_url = self::price_offer_url((int) $booking->id);
		if ($offer_url !== '') {
			$message .= __('Price offer link:', 'vachr-short-term-rental-reservations') . ' ' . $offer_url . "\n";
		}
		$summary_block = self::build_summary_block($booking);
		if ($summary_block !== '') {
			$message .= "\n" . $summary_block . "\n";
		}
		$note = self::get_booking_meta_value((int) $booking->id, 'price_offer_note');
		if (trim($note) !== '') {
			$message .= "\n" . __('Note:', 'vachr-short-term-rental-reservations') . "\n" . self::safe_text($note) . "\n";
		}
		$message .= "\n" . __('Thank you!', 'vachr-short-term-rental-reservations');

		$sent = wp_mail($customer_email, $subject, $message);
		if ($sent) {
			self::add_booking_meta_value((int) $booking->id, 'price_offer_sent_at', current_time('mysql'));
			self::upsert_booking_meta_value((int) $booking->id, 'price_offer_recipient', $customer_email);
		}
		self::restore_booking_locale($switched_locale);
		return (bool) $sent;
	}

	public static function notify_customer_arrival_instructions($booking_id) {
		$booking = self::get_booking_cached($booking_id);
		if (!$booking || empty($booking->customer_email) || (string) ($booking->status ?? '') !== 'confirmed') {
			return false;
		}
		$recipient = self::safe_email_recipient($booking->customer_email);
		if ($recipient === '') {
			return false;
		}
		$unit_id = (int) ($booking->unit_id ?? 0);
		if ((int) get_post_meta($unit_id, '_vstrb_arrival_instructions_enabled', true) !== 1) {
			return false;
		}
		$locale = self::get_booking_meta_value((int) $booking->id, 'customer_locale');
		$instructions = self::resolve_unit_content($unit_id, 'instructions', $locale, $booking);
		if ($instructions === '') {
			return false;
		}

		$switched_locale = self::switch_to_booking_locale($booking);
		$unit_title = get_the_title($unit_id);
		$description = self::resolve_unit_content($unit_id, 'description', $locale, $booking);
		$amenities = self::resolve_unit_content($unit_id, 'amenities', $locale, $booking);
		// translators: %s is the apartment/unit title.
		$subject = sprintf(__('Arrival Instructions: %s', 'vachr-short-term-rental-reservations'), $unit_title);
		// translators: %s is the customer's name.
		$message = sprintf(__("Hello %s,", 'vachr-short-term-rental-reservations'), self::safe_text($booking->customer_name ?? '')) . "\n\n";
		$message .= __('Here are the arrival instructions for your stay.', 'vachr-short-term-rental-reservations') . "\n\n";
		$message .= __('Unit:', 'vachr-short-term-rental-reservations') . ' ' . $unit_title . "\n";
		/* translators: 1: arrival date, 2: departure date */
		$message .= __('Dates:', 'vachr-short-term-rental-reservations') . ' ' . sprintf(__('%1$s to %2$s', 'vachr-short-term-rental-reservations'), self::safe_text($booking->date_from ?? ''), self::safe_text($booking->date_to ?? '')) . "\n\n";
		if ($description !== '') {
			$message .= __('Apartment Description', 'vachr-short-term-rental-reservations') . ":\n" . $description . "\n\n";
		}
		if ($amenities !== '') {
			$message .= __('Amenities', 'vachr-short-term-rental-reservations') . ":\n" . $amenities . "\n\n";
		}
		$message .= __('Arrival Instructions', 'vachr-short-term-rental-reservations') . ":\n" . $instructions . "\n\n";
		$message .= __('Thank you!', 'vachr-short-term-rental-reservations');

		$sent = wp_mail($recipient, $subject, $message);
		if ($sent) {
			self::add_booking_meta_value((int) $booking->id, 'arrival_instructions_sent_at', current_time('mysql'));
		}
		self::restore_booking_locale($switched_locale);
		return (bool) $sent;
	}

	public static function send_due_arrival_instructions() {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$today = current_time('Y-m-d');
		$rows = $wpdb->get_results($wpdb->prepare(
			"SELECT b.*
			 FROM {$t['bookings']} b
			 LEFT JOIN {$t['booking_meta']} sent ON sent.booking_id = b.id AND sent.meta_key = 'arrival_instructions_sent_at'
			 WHERE b.status = 'confirmed'
			   AND b.customer_email IS NOT NULL
			   AND b.customer_email <> ''
			   AND b.date_from >= %s
			   AND sent.id IS NULL
			 ORDER BY b.date_from ASC
			 LIMIT 200",
			$today
		));
		foreach ((array) $rows as $booking) {
			$unit_id = (int) ($booking->unit_id ?? 0);
			if ((int) get_post_meta($unit_id, '_vstrb_arrival_instructions_enabled', true) !== 1) {
				continue;
			}
			$days_before = (int) get_post_meta($unit_id, '_vstrb_arrival_instructions_days_before', true);
			$days_before = max(0, min(365, $days_before));
			$send_date = gmdate('Y-m-d', strtotime((string) $booking->date_from . ' -' . $days_before . ' days'));
			if ($send_date <= $today) {
				self::notify_customer_arrival_instructions((int) $booking->id);
			}
		}
	}

	public static function send_due_staff_departure_notifications() {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$rules = VSTRB_Staff_Access::get_rules();
		if (empty($rules)) {
			return;
		}
		$today = current_time('Y-m-d');
		$departure_until = gmdate('Y-m-d', strtotime($today . ' +' . self::staff_max_days_before($rules, 'departure') . ' days'));
		$status_where = self::staff_booking_status_where();
		$rows = $wpdb->get_results($wpdb->prepare(
			"SELECT b.*
			 FROM {$t['bookings']} b
			 WHERE {$status_where}
			   AND b.date_to BETWEEN %s AND %s
			 ORDER BY b.date_to ASC, b.id ASC
			 LIMIT 200",
			$today,
			$departure_until
		));
		foreach ((array) $rows as $booking) {
			self::notify_staff_departure($booking, $today);
		}

		$arrival_until = gmdate('Y-m-d', strtotime($today . ' +' . self::staff_max_days_before($rules, 'arrival') . ' days'));
		$rows = $wpdb->get_results($wpdb->prepare(
			"SELECT b.*
			 FROM {$t['bookings']} b
			 WHERE {$status_where}
			   AND b.date_from BETWEEN %s AND %s
			 ORDER BY b.date_from ASC, b.id ASC
			 LIMIT 200",
			$today,
			$arrival_until
		));
		foreach ((array) $rows as $booking) {
			self::notify_staff_arrival($booking, $today);
		}
	}
	private static function get_registrations_by_ids(array $ids) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$gr_table = self::safe_table_name($t['guest_registrations'] ?? '');
		if ($gr_table === '' || empty($ids)) return [];

		$placeholders = implode(',', array_fill(0, count($ids), '%d'));
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare
		return $wpdb->get_results($wpdb->prepare("SELECT * FROM {$gr_table} WHERE id IN ($placeholders) ORDER BY id ASC", $ids));
	}

	public static function notify_admin_guest_registration_group($unit_id, array $ids, $date_from, $date_to, $email) {
		$regs       = self::get_registrations_by_ids($ids);
		self::notify_staff_guest_registration_group($unit_id, $regs, $date_from, $date_to, $email);
		$admin_mail = self::safe_email_recipient(get_option('admin_email'));
		if ($admin_mail === '') return;
		$unit_title = get_the_title($unit_id);
		$count      = count($regs);

		/* translators: %s: apartment title */
		$subject = sprintf(__('New Guest Registration: %s', 'vachr-short-term-rental-reservations'), $unit_title);

		/* translators: 1: count, 2: apartment title */
		$message = sprintf(_n('New guest registration received for %2$s (%1$d guest)', 'New guest registration received for %2$s (%1$d guests)', $count, 'vachr-short-term-rental-reservations'), $count, $unit_title) . "\n\n";
		/* translators: 1: arrival date, 2: departure date */
		$message .= __('Stay:', 'vachr-short-term-rental-reservations') . ' ' . sprintf(__('%1$s to %2$s', 'vachr-short-term-rental-reservations'), $date_from, $date_to) . "\n";
		$message .= __('Email:', 'vachr-short-term-rental-reservations') . ' ' . $email . "\n";
		if (!empty($regs) && !empty($regs[0]->source)) {
			$message .= __('Booking Source:', 'vachr-short-term-rental-reservations') . ' ' . $regs[0]->source . "\n";
		}
		if (!empty($regs) && !empty($regs[0]->booking_reference)) {
			$message .= __('Booking Reference:', 'vachr-short-term-rental-reservations') . ' ' . $regs[0]->booking_reference . "\n";
		}
		$message .= "\n";

		foreach ($regs as $i => $reg) {
			/* translators: %d: guest number */
			$message .= sprintf(__('Guest %d', 'vachr-short-term-rental-reservations'), $i + 1) . ': ' . $reg->first_name . ' ' . $reg->last_name . "\n";
			$message .= '  ' . __('Date of Birth:', 'vachr-short-term-rental-reservations') . ' ' . $reg->date_of_birth . "\n";
			$message .= '  ' . __('Nationality:', 'vachr-short-term-rental-reservations') . ' ' . $reg->nationality . "\n";
			$message .= '  ' . __('Street:', 'vachr-short-term-rental-reservations') . ' ' . $reg->street . "\n";
			$message .= '  ' . __('City:', 'vachr-short-term-rental-reservations') . ' ' . $reg->city . "\n";
			if (!empty($reg->state)) {
				$message .= '  ' . __('Country:', 'vachr-short-term-rental-reservations') . ' ' . $reg->state . "\n";
			}
			$message .= '  ' . __('Document Type:', 'vachr-short-term-rental-reservations') . ' ' . $reg->document_type . "\n";
			$message .= '  ' . __('Document Number:', 'vachr-short-term-rental-reservations') . ' ' . $reg->document_number . "\n\n";
		}

		$message .= __('View registrations:', 'vachr-short-term-rental-reservations') . ' ' . admin_url('admin.php?page=vstrb-guests');
		wp_mail($admin_mail, $subject, $message);
	}

	public static function notify_guest_registration_group_received($unit_id, array $ids, $date_from, $date_to, $email) {
		$email = self::safe_email_recipient($email);
		if (!$email) return;
		$regs       = self::get_registrations_by_ids($ids);
		$unit_title = get_the_title($unit_id);
		$count      = count($regs);

		/* translators: %s: apartment title */
		$subject = sprintf(__('Registration Received: %s', 'vachr-short-term-rental-reservations'), $unit_title);

		$message = __('Hello,', 'vachr-short-term-rental-reservations') . "\n\n";
		$message .= __('Your registration details have been received. Thank you!', 'vachr-short-term-rental-reservations') . "\n\n";
		$message .= __('Unit:', 'vachr-short-term-rental-reservations') . ' ' . $unit_title . "\n";
		/* translators: 1: arrival date, 2: departure date */
		$message .= __('Stay:', 'vachr-short-term-rental-reservations') . ' ' . sprintf(__('%1$s to %2$s', 'vachr-short-term-rental-reservations'), $date_from, $date_to) . "\n\n";

		foreach ($regs as $i => $reg) {
			/* translators: %d: guest number */
			$message .= sprintf(__('Guest %d', 'vachr-short-term-rental-reservations'), $i + 1) . ': ' . $reg->first_name . ' ' . $reg->last_name . "\n";
		}

		$message .= "\n" . __('Thank you!', 'vachr-short-term-rental-reservations');
		wp_mail($email, $subject, $message);
	}

	public static function notify_admin_guest_registration($reg_id) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$reg_id = (int) $reg_id;
		if ($reg_id <= 0) return;

		$gr_table = self::safe_table_name($t['guest_registrations'] ?? '');
		if ($gr_table === '') return;

		$reg = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare("SELECT * FROM {$gr_table} WHERE id = %d", $reg_id)
		);
		if (!$reg) return;

		$admin_email = self::safe_email_recipient(get_option('admin_email'));
		if ($admin_email === '') return;
		$unit_title = get_the_title($reg->unit_id);

		/* translators: %s: apartment title */
		$subject = sprintf(__('New Guest Registration: %s', 'vachr-short-term-rental-reservations'), $unit_title);

		/* translators: %s: apartment title */
		$message = sprintf(__("New guest registration received for %s", 'vachr-short-term-rental-reservations'), $unit_title) . "\n\n";
		$message .= __("ID:", 'vachr-short-term-rental-reservations') . " " . $reg->id . "\n";
		$message .= __("Name:", 'vachr-short-term-rental-reservations') . " " . $reg->first_name . " " . $reg->last_name . "\n";
		$message .= __("Date of Birth:", 'vachr-short-term-rental-reservations') . " " . $reg->date_of_birth . "\n";
		$message .= __("Address:", 'vachr-short-term-rental-reservations') . " " . $reg->address . "\n";
		$message .= __("Document Type:", 'vachr-short-term-rental-reservations') . " " . $reg->document_type . "\n";
		$message .= __("Document Number:", 'vachr-short-term-rental-reservations') . " " . $reg->document_number . "\n";
		$message .= __("Nationality:", 'vachr-short-term-rental-reservations') . " " . $reg->nationality . "\n";
		/* translators: 1: arrival date, 2: departure date */
		$message .= __("Stay:", 'vachr-short-term-rental-reservations') . " " . sprintf(__('%1$s to %2$s', 'vachr-short-term-rental-reservations'), $reg->date_from, $reg->date_to) . "\n";
		$message .= __("Email:", 'vachr-short-term-rental-reservations') . " " . $reg->email . "\n\n";
		$message .= __("View registrations:", 'vachr-short-term-rental-reservations') . " " . admin_url('admin.php?page=vstrb-guests');

		wp_mail($admin_email, $subject, $message);
	}

	public static function notify_guest_registration_received($reg_id) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$reg_id = (int) $reg_id;
		if ($reg_id <= 0) return;

		$gr_table = self::safe_table_name($t['guest_registrations'] ?? '');
		if ($gr_table === '') return;

		$reg = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare("SELECT * FROM {$gr_table} WHERE id = %d", $reg_id)
		);
		if (!$reg || empty($reg->email)) return;
		$recipient = self::safe_email_recipient($reg->email);
		if ($recipient === '') return;

		$unit_title = get_the_title($reg->unit_id);
		/* translators: %s: apartment title */
		$subject = sprintf(__('Registration Received: %s', 'vachr-short-term-rental-reservations'), $unit_title);

		/* translators: %s: guest first name */
		$message = sprintf(__("Hello %s,", 'vachr-short-term-rental-reservations'), $reg->first_name) . "\n\n";
		$message .= __("Your registration details have been received. Thank you!", 'vachr-short-term-rental-reservations') . "\n\n";
		$message .= __("Details:", 'vachr-short-term-rental-reservations') . "\n";
		$message .= __("Unit:", 'vachr-short-term-rental-reservations') . " " . $unit_title . "\n";
		/* translators: 1: arrival date, 2: departure date */
		$message .= __("Stay:", 'vachr-short-term-rental-reservations') . " " . sprintf(__('%1$s to %2$s', 'vachr-short-term-rental-reservations'), $reg->date_from, $reg->date_to) . "\n";
		$message .= "\n" . __("Thank you!", 'vachr-short-term-rental-reservations');

		wp_mail($recipient, $subject, $message);
	}
	// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
}

add_action('vstrb_arrival_instructions_cron', ['VSTRB_Emails', 'send_due_arrival_instructions']);
add_action('vstrb_staff_departure_notifications_cron', ['VSTRB_Emails', 'send_due_staff_departure_notifications']);
add_action('template_redirect', ['VSTRB_Emails', 'maybe_render_price_offer_page']);
