<?php
/**
 * Structured staff notification types, confirmation and escalation.
 *
 * @package Vachr Short-Term Rental Reservations
 * @license GPLv2 or later
 */

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

class VSTRB_Staff_Tasks {
	const OPTION = 'vstrb_staff_notification_config';
	const CRON = 'vstrb_staff_task_notifications_cron';

	public static function register() {
		add_action(self::CRON, [__CLASS__, 'process_due_tasks']);
		add_action('admin_post_vstrb_confirm_staff_task', [__CLASS__, 'handle_confirmation']);
		add_action('admin_post_nopriv_vstrb_confirm_staff_task', [__CLASS__, 'handle_confirmation']);
	}

	public static function event_labels() {
		return [
			'departure' => __('Departure / cleaning', 'vachr-short-term-rental-reservations'),
			'arrival' => __('Arrival / handover', 'vachr-short-term-rental-reservations'),
			'new_booking' => __('New booking', 'vachr-short-term-rental-reservations'),
			'registration' => __('Guest registration', 'vachr-short-term-rental-reservations'),
			'ical_cancelled' => __('iCal cancellation', 'vachr-short-term-rental-reservations'),
		];
	}

	private static function default_type_label($event) {
		$labels = self::event_labels();
		return isset($labels[$event]) ? $labels[$event] : __('Other', 'vachr-short-term-rental-reservations');
	}

	private static function unique_id($value, $fallback = 'task') {
		$id = sanitize_key((string) $value);
		return $id !== '' ? substr($id, 0, 48) : $fallback;
	}

	public static function from_legacy_rules() {
		$raw = (string) get_option('vstrb_staff_notification_rules', '');
		$rules = VSTRB_Staff_Access::parse_rules_text($raw);
		if (!$rules) {
			$legacy = preg_split('/[\s,;]+/', (string) get_option('vstrb_staff_notification_emails', ''));
			foreach ((array) $legacy as $email) {
				$email = sanitize_email($email);
				if (is_email($email)) {
					$rules[] = ['email' => strtolower($email), 'fields' => VSTRB_Staff_Access::default_fields(), 'events' => VSTRB_Staff_Access::default_events(), 'locale' => '', 'days_before' => 1];
				}
			}
		}

		$types = [];
		foreach ($rules as $rule) {
			foreach ((array) ($rule['events'] ?? []) as $event) {
				if (!isset(self::event_labels()[$event])) continue;
				if (!isset($types[$event])) {
					$types[$event] = [
						'id' => $event,
						'label' => self::default_type_label($event),
						'event' => $event,
						'color' => $event === 'departure' ? '#2e7d32' : ($event === 'arrival' ? '#1565c0' : '#7b1fa2'),
						'require_confirmation' => 0,
						'recipients' => [],
					];
				}
				$types[$event]['recipients'][] = [
					'name' => '',
					'email' => (string) $rule['email'],
					'locale' => (string) ($rule['locale'] ?? ''),
					'days_before' => (int) ($rule['days_before'] ?? 1),
					'escalate_after_hours' => 24,
					'fields' => (array) ($rule['fields'] ?? VSTRB_Staff_Access::default_fields()),
				];
			}
		}
		return ['version' => 1, 'types' => array_values($types)];
	}

	public static function get_config() {
		$config = get_option(self::OPTION, null);
		if (!is_array($config)) {
			return self::from_legacy_rules();
		}
		return self::sanitize_config($config);
	}

	public static function sanitize_config($raw) {
		$events = array_keys(self::event_labels());
		$allowed_fields = VSTRB_Staff_Access::allowed_fields();
		$out = ['version' => 1, 'types' => []];
		$used = [];
		foreach ((array) ($raw['types'] ?? $raw) as $type_index => $type) {
			if (!is_array($type) || !empty($type['_delete'])) continue;
			$label = sanitize_text_field((string) ($type['label'] ?? ''));
			if ($label === '') continue;
			$id = self::unique_id($type['id'] ?? $label, 'task-' . ((int) $type_index + 1));
			$base_id = $id;
			$suffix = 2;
			while (isset($used[$id])) $id = $base_id . '-' . $suffix++;
			$used[$id] = true;
			$event = sanitize_key((string) ($type['event'] ?? 'departure'));
			if (!in_array($event, $events, true)) $event = 'departure';
			$color = sanitize_hex_color((string) ($type['color'] ?? '')) ?: '#2271b1';
			$recipients = [];
			foreach ((array) ($type['recipients'] ?? []) as $recipient) {
				if (!is_array($recipient) || !empty($recipient['_delete'])) continue;
				$email = strtolower(sanitize_email((string) ($recipient['email'] ?? '')));
				if (!is_email($email)) continue;
				$fields = array_values(array_intersect($allowed_fields, array_map('sanitize_key', (array) ($recipient['fields'] ?? []))));
				$locale = str_replace('-', '_', sanitize_text_field((string) ($recipient['locale'] ?? '')));
				if ($locale !== '' && !preg_match('/^[A-Za-z]{2}(?:_[A-Za-z]{2})?$/', $locale)) $locale = '';
				$recipients[] = [
					'name' => sanitize_text_field((string) ($recipient['name'] ?? '')),
					'email' => $email,
					'locale' => $locale,
					'days_before' => max(0, min(365, (int) ($recipient['days_before'] ?? 1))),
					'escalate_after_hours' => max(1, min(8760, (int) ($recipient['escalate_after_hours'] ?? 24))),
					'fields' => $fields ?: VSTRB_Staff_Access::default_fields(),
				];
			}
			if (!$recipients) continue;
			$out['types'][] = [
				'id' => $id,
				'label' => $label,
				'event' => $event,
				'color' => $color,
				'require_confirmation' => !empty($type['require_confirmation']) && in_array($event, ['arrival', 'departure'], true) ? 1 : 0,
				'recipients' => $recipients,
			];
		}
		return $out;
	}

	public static function save_config($raw) {
		$config = self::sanitize_config($raw);
		update_option(self::OPTION, $config, false);
		update_option('vstrb_staff_notification_rules', self::legacy_text($config));
		return $config;
	}

	public static function legacy_text($config = null) {
		$config = is_array($config) ? $config : self::get_config();
		$lines = [];
		foreach ((array) ($config['types'] ?? []) as $type) {
			foreach ((array) ($type['recipients'] ?? []) as $recipient) {
				$key = $recipient['email'] . '|' . implode(',', $recipient['fields']) . '|' . $type['event'] . '|' . $recipient['locale'] . '|' . $recipient['days_before'];
				$lines[$key] = $key;
			}
		}
		return implode("\n", $lines);
	}

	public static function flattened_rules() {
		$out = [];
		foreach ((array) (self::get_config()['types'] ?? []) as $type) {
			foreach ((array) $type['recipients'] as $position => $recipient) {
				$out[] = array_merge($recipient, [
					'events' => [$type['event']],
					'task_type' => $type['id'],
					'task_label' => $type['label'],
					'task_color' => $type['color'],
					'escalation_position' => (int) $position,
					'require_confirmation' => !empty($type['require_confirmation']),
				]);
			}
		}
		return $out;
	}

	public static function confirmation_required_for_rule($rule) {
		return !empty($rule['require_confirmation']);
	}

	private static function meta_key($type_id) {
		return 'staff_task_state_' . self::unique_id($type_id);
	}

	private static function get_state($booking_id, $type_id) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$cache_key = 'staff_task_state_' . (int) $booking_id . '_' . md5((string) $type_id);
		$cached = wp_cache_get($cache_key, 'vstrb_staff_tasks');
		if (is_array($cached)) return $cached;
		$booking_meta_table = esc_sql($t['booking_meta']);
		// Direct access is required because staff task state is stored in a plugin-owned custom table.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped above; values use placeholders.
		$value = $wpdb->get_var($wpdb->prepare('SELECT meta_value FROM ' . $booking_meta_table . ' WHERE booking_id=%d AND meta_key=%s ORDER BY id DESC LIMIT 1', (int) $booking_id, self::meta_key($type_id)));
		$state = json_decode((string) $value, true);
		$state = is_array($state) ? $state : [];
		wp_cache_set($cache_key, $state, 'vstrb_staff_tasks', 5 * MINUTE_IN_SECONDS);
		return $state;
	}

	private static function save_state($booking_id, $type_id, array $state) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$key = self::meta_key($type_id);
		$value = wp_json_encode($state);
		$booking_meta_table = esc_sql($t['booking_meta']);
		// Direct access is required because staff task state is stored in a plugin-owned custom table.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped above; values use placeholders.
		$id = (int) $wpdb->get_var($wpdb->prepare('SELECT id FROM ' . $booking_meta_table . ' WHERE booking_id=%d AND meta_key=%s ORDER BY id DESC LIMIT 1', (int) $booking_id, $key));
		if ($id > 0) {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.SlowDBQuery.slow_db_query_meta_value
			$saved = false !== $wpdb->update($t['booking_meta'], ['meta_value' => $value], ['id' => $id], ['%s'], ['%d']);
		} else {
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.SlowDBQuery.slow_db_query_meta_key, WordPress.DB.SlowDBQuery.slow_db_query_meta_value
			$saved = false !== $wpdb->insert($t['booking_meta'], ['booking_id' => (int) $booking_id, 'meta_key' => $key, 'meta_value' => $value], ['%d', '%s', '%s']);
		}
		if ($saved) wp_cache_delete('staff_task_state_' . (int) $booking_id . '_' . md5((string) $type_id), 'vstrb_staff_tasks');
		return $saved;
	}

	private static function type_by_id($id) {
		foreach ((array) (self::get_config()['types'] ?? []) as $type) if ($type['id'] === $id) return $type;
		return null;
	}

	private static function booking($booking_id) {
		global $wpdb;
		$t = VSTRB_DB::tables();
		$cache_key = 'booking_' . (int) $booking_id;
		$cached = wp_cache_get($cache_key, 'vstrb_staff_tasks');
		if (is_object($cached)) return $cached;
		$bookings_table = esc_sql($t['bookings']);
		// Direct access is required because reservations are stored in a plugin-owned custom table.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped above; the ID uses a placeholder.
		$booking = $wpdb->get_row($wpdb->prepare('SELECT * FROM ' . $bookings_table . ' WHERE id=%d', (int) $booking_id));
		if ($booking) wp_cache_set($cache_key, $booking, 'vstrb_staff_tasks', 5 * MINUTE_IN_SECONDS);
		return $booking;
	}

	private static function token($booking_id, $type_id, $email) {
		return hash_hmac('sha256', (int) $booking_id . '|' . self::unique_id($type_id) . '|' . strtolower($email), wp_salt('auth'));
	}

	private static function confirmation_url($booking_id, $type_id, $email) {
		return add_query_arg([
			'action' => 'vstrb_confirm_staff_task', 'booking_id' => (int) $booking_id,
			'task' => self::unique_id($type_id), 'email' => strtolower($email),
			'token' => self::token($booking_id, $type_id, $email),
		], admin_url('admin-post.php'));
	}

	private static function send_assignment($booking, $type, $recipient, $is_escalation = false) {
		$switched = false;
		if (!empty($recipient['locale']) && function_exists('switch_to_locale')) {
			$switched = switch_to_locale($recipient['locale']);
		}
		$unit = get_the_title((int) $booking->unit_id);
		/* translators: 1: task type, 2: rental unit name, 3: task date. */
		$subject = sprintf(__('%1$s: %2$s on %3$s', 'vachr-short-term-rental-reservations'), $type['label'], $unit, self::event_date($booking, $type['event']));
		if ($is_escalation) $subject = __('Escalation: ', 'vachr-short-term-rental-reservations') . $subject;
		/* translators: 1: task type, 2: rental unit name. */
		$message = sprintf(__('Please take responsibility for "%1$s" at %2$s.', 'vachr-short-term-rental-reservations'), $type['label'], $unit) . "\n\n";
		$rule = array_merge($recipient, ['events' => [$type['event']]]);
		foreach (VSTRB_Staff_Access::booking_lines($booking, $rule) as $label => $value) $message .= $label . ': ' . $value . "\n";
		$message .= "\n" . __('Confirm / take over:', 'vachr-short-term-rental-reservations') . "\n" . self::confirmation_url((int) $booking->id, $type['id'], $recipient['email']);
		$sent = wp_mail($recipient['email'], $subject, $message);
		if ($switched && function_exists('restore_previous_locale')) restore_previous_locale();
		return $sent;
	}

	private static function event_date($booking, $event) {
		return $event === 'departure' ? (string) $booking->date_to : (string) $booking->date_from;
	}

	private static function task_due($booking, $type, $recipient, $now) {
		if (!in_array($type['event'], ['arrival', 'departure'], true)) return false;
		$event_date = self::event_date($booking, $type['event']);
		$due = strtotime($event_date . ' -' . (int) $recipient['days_before'] . ' days 00:00:00');
		return $due <= $now && $event_date >= current_time('Y-m-d');
	}

	public static function process_due_tasks() {
		global $wpdb;
		$types = array_filter((array) (self::get_config()['types'] ?? []), static function($type) { return !empty($type['require_confirmation']) && in_array($type['event'], ['arrival', 'departure'], true); });
		if (!$types) return;
		$t = VSTRB_DB::tables();
		$today = current_time('Y-m-d');
		$until = gmdate('Y-m-d', strtotime($today . ' +365 days'));
		$bookings_table = esc_sql($t['bookings']);
		$ical_source = $wpdb->esc_like('ical_') . '%';
		$cache_key = 'due_' . md5($today . '|' . $until);
		$bookings = wp_cache_get($cache_key, 'vstrb_staff_tasks');
		if (!is_array($bookings)) {
			// Direct access is required because reservations are stored in a plugin-owned custom table.
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped above; values use placeholders.
			$bookings = $wpdb->get_results($wpdb->prepare("SELECT * FROM " . $bookings_table . " WHERE status <> 'cancelled' AND (status <> 'blocked' OR source LIKE %s) AND (date_from BETWEEN %s AND %s OR date_to BETWEEN %s AND %s) ORDER BY date_to ASC LIMIT 500", $ical_source, $today, $until, $today, $until));
			wp_cache_set($cache_key, (array) $bookings, 'vstrb_staff_tasks', 5 * MINUTE_IN_SECONDS);
		}
		$now = current_time('timestamp');
		foreach ((array) $bookings as $booking) {
			foreach ($types as $type) {
				$recipients = (array) $type['recipients'];
				if (!$recipients) continue;
				$state = self::get_state((int) $booking->id, $type['id']);
				if (($state['status'] ?? '') === 'confirmed') continue;
				$index = max(0, (int) ($state['current_index'] ?? 0));
				if (!isset($recipients[$index])) continue;
				if (empty($state['sent_at'])) {
					if (!self::task_due($booking, $type, $recipients[0], $now)) continue;
					if (self::send_assignment($booking, $type, $recipients[0])) {
						$state = ['status' => 'waiting', 'current_index' => 0, 'sent_at' => current_time('mysql'), 'notified' => [$recipients[0]['email']]];
						self::save_state((int) $booking->id, $type['id'], $state);
					}
					continue;
				}
				$wait = max(1, (int) $recipients[$index]['escalate_after_hours']) * HOUR_IN_SECONDS;
				if ($now < strtotime($state['sent_at']) + $wait || !isset($recipients[$index + 1])) continue;
				$next = $index + 1;
				if (self::send_assignment($booking, $type, $recipients[$next], true)) {
					$state['current_index'] = $next;
					$state['sent_at'] = current_time('mysql');
					$state['notified'][] = $recipients[$next]['email'];
					self::save_state((int) $booking->id, $type['id'], $state);
				}
			}
		}
	}

	public static function handle_confirmation() {
		// The HMAC token below authenticates this public email action and is not tied to a logged-in WordPress session.
		// phpcs:disable WordPress.Security.NonceVerification.Recommended
		$booking_id = isset($_GET['booking_id']) ? absint($_GET['booking_id']) : 0;
		$type_id = isset($_GET['task']) ? sanitize_key(wp_unslash($_GET['task'])) : '';
		$email = isset($_GET['email']) ? strtolower(sanitize_email(wp_unslash($_GET['email']))) : '';
		$token = isset($_GET['token']) ? sanitize_text_field(wp_unslash($_GET['token'])) : '';
		// phpcs:enable WordPress.Security.NonceVerification.Recommended
		if (!$booking_id || !$type_id || !is_email($email) || !hash_equals(self::token($booking_id, $type_id, $email), $token)) wp_die(esc_html__('Invalid or expired confirmation link.', 'vachr-short-term-rental-reservations'), '', ['response' => 403]);
		$type = self::type_by_id($type_id);
		$booking = self::booking($booking_id);
		if (!$type || !$booking) wp_die(esc_html__('The task no longer exists.', 'vachr-short-term-rental-reservations'), '', ['response' => 404]);
		$allowed = false;
		foreach ($type['recipients'] as $recipient) if ($recipient['email'] === $email) $allowed = true;
		if (!$allowed) wp_die(esc_html__('You are not assigned to this task.', 'vachr-short-term-rental-reservations'), '', ['response' => 403]);
		$state = self::get_state($booking_id, $type_id);
		$already_confirmed = ($state['status'] ?? '') === 'confirmed';
		if (!$already_confirmed) {
			$state['status'] = 'confirmed'; $state['confirmed_by'] = $email; $state['confirmed_at'] = current_time('mysql');
			self::save_state($booking_id, $type_id, $state);
			foreach (array_unique((array) ($state['notified'] ?? [])) as $previous) {
				if ($previous !== $email && is_email($previous)) {
					/* translators: %s: task type. */
					$subject = sprintf(__('Task taken over: %s', 'vachr-short-term-rental-reservations'), $type['label']);
					/* translators: 1: rental unit name, 2: task date, 3: email address of the staff member who accepted the task. */
					$message = sprintf(__('The task for %1$s on %2$s was taken over by %3$s.', 'vachr-short-term-rental-reservations'), get_the_title((int) $booking->unit_id), self::event_date($booking, $type['event']), $email);
					wp_mail($previous, $subject, $message);
				}
			}
		}
		if ($already_confirmed) {
			/* translators: %s: email address of the staff member who already accepted the task. */
			$message = sprintf(__('This task has already been taken over by %s.', 'vachr-short-term-rental-reservations'), (string) ($state['confirmed_by'] ?? __('another staff member', 'vachr-short-term-rental-reservations')));
		} else {
			/* translators: %s: task type. */
			$message = sprintf(__('You have taken responsibility for "%s".', 'vachr-short-term-rental-reservations'), $type['label']);
		}
		wp_die('<h1>' . esc_html__('Task confirmed', 'vachr-short-term-rental-reservations') . '</h1><p>' . esc_html($message) . '</p>', esc_html__('Task confirmed', 'vachr-short-term-rental-reservations'), ['response' => 200]);
	}

	public static function task_rows($email, $days = 60, $unit_id = 0) {
		global $wpdb;
		$email = strtolower(sanitize_email($email));
		$t = VSTRB_DB::tables();
		$today = current_time('Y-m-d');
		$until = gmdate('Y-m-d', strtotime($today . ' +' . max(1, min(365, (int) $days)) . ' days'));
		$bookings_table = esc_sql($t['bookings']);
		$cache_key = 'task_rows_' . md5($today . '|' . $until . '|' . (int) $unit_id);
		$bookings = wp_cache_get($cache_key, 'vstrb_staff_tasks');
		if (!is_array($bookings)) {
			if ($unit_id > 0) {
				// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped above; values use placeholders.
				$query = $wpdb->prepare("SELECT * FROM " . $bookings_table . " WHERE status <> 'cancelled' AND date_to >= %s AND date_from <= %s AND unit_id=%d ORDER BY date_from ASC LIMIT 500", $today, $until, (int) $unit_id);
			} else {
				// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Table name is escaped above; values use placeholders.
				$query = $wpdb->prepare("SELECT * FROM " . $bookings_table . " WHERE status <> 'cancelled' AND date_to >= %s AND date_from <= %s ORDER BY date_from ASC LIMIT 500", $today, $until);
			}
			// Direct access is required because reservations are stored in a plugin-owned custom table.
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared -- Query was prepared in the branch above.
			$bookings = $wpdb->get_results($query);
			wp_cache_set($cache_key, (array) $bookings, 'vstrb_staff_tasks', 5 * MINUTE_IN_SECONDS);
		}
		$rows = [];
		foreach ((array) $bookings as $booking) foreach ((array) (self::get_config()['types'] ?? []) as $type) {
			$assigned = null;
			foreach ($type['recipients'] as $recipient) if ($email === '' || $recipient['email'] === $email) { $assigned = $recipient; break; }
			if (!$assigned) continue;
			$state = self::get_state((int) $booking->id, $type['id']);
			$current = $type['recipients'][max(0, (int) ($state['current_index'] ?? 0))] ?? $assigned;
			$rows[] = ['booking' => $booking, 'type' => $type, 'state' => $state, 'assigned' => $current, 'date' => self::event_date($booking, $type['event'])];
		}
		return $rows;
	}

	private static function render_recipient_fields($type_index, $recipient_index, $recipient) {
		$name = 'vstrb_notification_types[' . (int) $type_index . '][recipients][' . (int) $recipient_index . ']';
		$labels = VSTRB_Staff_Access::field_labels();
		?>
		<div class="vstrb-notification-recipient" data-vstrb-recipient>
			<span class="vstrb-drag-handle" title="<?php esc_attr_e('Escalation order', 'vachr-short-term-rental-reservations'); ?>"><button type="button" class="button-link" data-vstrb-move-up aria-label="<?php esc_attr_e('Move up', 'vachr-short-term-rental-reservations'); ?>">&#8593;</button><button type="button" class="button-link" data-vstrb-move-down aria-label="<?php esc_attr_e('Move down', 'vachr-short-term-rental-reservations'); ?>">&#8595;</button></span>
			<label><?php esc_html_e('Name / role', 'vachr-short-term-rental-reservations'); ?><input type="text" name="<?php echo esc_attr($name); ?>[name]" value="<?php echo esc_attr($recipient['name'] ?? ''); ?>" placeholder="<?php esc_attr_e('Cleaner', 'vachr-short-term-rental-reservations'); ?>"></label>
			<label><?php esc_html_e('Email', 'vachr-short-term-rental-reservations'); ?><input type="email" name="<?php echo esc_attr($name); ?>[email]" value="<?php echo esc_attr($recipient['email'] ?? ''); ?>"></label>
			<label><?php esc_html_e('Language', 'vachr-short-term-rental-reservations'); ?><input type="text" name="<?php echo esc_attr($name); ?>[locale]" value="<?php echo esc_attr($recipient['locale'] ?? ''); ?>" placeholder="cs_CZ"></label>
			<label><?php esc_html_e('First email (days before)', 'vachr-short-term-rental-reservations'); ?><input type="number" min="0" max="365" name="<?php echo esc_attr($name); ?>[days_before]" value="<?php echo esc_attr((string) ($recipient['days_before'] ?? 1)); ?>"></label>
			<label><?php esc_html_e('Escalate after (hours)', 'vachr-short-term-rental-reservations'); ?><input type="number" min="1" max="8760" name="<?php echo esc_attr($name); ?>[escalate_after_hours]" value="<?php echo esc_attr((string) ($recipient['escalate_after_hours'] ?? 24)); ?>"></label>
			<label><?php esc_html_e('Visible data', 'vachr-short-term-rental-reservations'); ?>
				<select multiple name="<?php echo esc_attr($name); ?>[fields][]">
					<?php foreach ($labels as $field => $label) : ?><option value="<?php echo esc_attr($field); ?>" <?php selected(in_array($field, (array) ($recipient['fields'] ?? []), true)); ?>><?php echo esc_html($label); ?></option><?php endforeach; ?>
				</select>
			</label>
			<button type="button" class="button-link-delete" data-vstrb-remove-recipient><?php esc_html_e('Remove', 'vachr-short-term-rental-reservations'); ?></button>
		</div>
		<?php
	}

	public static function render_admin_form() {
		$config = self::get_config();
		$types = (array) ($config['types'] ?? []);
		if (!$types) {
			$types[] = [
				'id' => 'cleaning', 'label' => __('Cleaning', 'vachr-short-term-rental-reservations'),
				'event' => 'departure', 'color' => '#2e7d32', 'require_confirmation' => 1,
				'recipients' => [['name' => '', 'email' => '', 'locale' => '', 'days_before' => 1, 'escalate_after_hours' => 24, 'fields' => VSTRB_Staff_Access::default_fields()]],
			];
		}
		?>
		<div id="vstrb-notification-builder" data-next-type="<?php echo esc_attr((string) count($types)); ?>">
			<p class="description"><?php esc_html_e('The old pipe-separated rules have been converted into the form below. Types may represent cleaning, manager duty, repairs, or any other work.', 'vachr-short-term-rental-reservations'); ?></p>
			<div data-vstrb-types>
			<?php foreach ($types as $type_index => $type) : ?>
				<fieldset class="vstrb-notification-type" data-vstrb-type data-type-index="<?php echo esc_attr((string) $type_index); ?>" style="--vstrb-task-color:<?php echo esc_attr($type['color']); ?>">
					<div class="vstrb-notification-type__head">
						<input type="hidden" name="vstrb_notification_types[<?php echo (int) $type_index; ?>][id]" value="<?php echo esc_attr($type['id']); ?>">
						<label><?php esc_html_e('Type name', 'vachr-short-term-rental-reservations'); ?><input type="text" name="vstrb_notification_types[<?php echo (int) $type_index; ?>][label]" value="<?php echo esc_attr($type['label']); ?>"></label>
						<label><?php esc_html_e('Trigger', 'vachr-short-term-rental-reservations'); ?><select name="vstrb_notification_types[<?php echo (int) $type_index; ?>][event]">
							<?php foreach (self::event_labels() as $event => $label) : ?><option value="<?php echo esc_attr($event); ?>" <?php selected($type['event'], $event); ?>><?php echo esc_html($label); ?></option><?php endforeach; ?>
						</select></label>
						<label><?php esc_html_e('Color', 'vachr-short-term-rental-reservations'); ?><input type="color" name="vstrb_notification_types[<?php echo (int) $type_index; ?>][color]" value="<?php echo esc_attr($type['color']); ?>"></label>
						<label class="vstrb-confirm-label"><input type="checkbox" name="vstrb_notification_types[<?php echo (int) $type_index; ?>][require_confirmation]" value="1" <?php checked(!empty($type['require_confirmation'])); ?>> <?php esc_html_e('Require confirmation and escalate', 'vachr-short-term-rental-reservations'); ?></label>
						<button type="button" class="button-link-delete" data-vstrb-remove-type><?php esc_html_e('Remove type', 'vachr-short-term-rental-reservations'); ?></button>
					</div>
					<div class="vstrb-notification-recipients" data-vstrb-recipients data-next-recipient="<?php echo esc_attr((string) count($type['recipients'])); ?>">
						<?php foreach ($type['recipients'] as $recipient_index => $recipient) self::render_recipient_fields($type_index, $recipient_index, $recipient); ?>
					</div>
					<p><button type="button" class="button" data-vstrb-add-recipient><?php esc_html_e('Add next escalation recipient', 'vachr-short-term-rental-reservations'); ?></button></p>
				</fieldset>
			<?php endforeach; ?>
			</div>
			<p><button type="button" class="button button-secondary" data-vstrb-add-type><?php esc_html_e('Add notification type', 'vachr-short-term-rental-reservations'); ?></button></p>
		</div>
		<p class="description"><?php esc_html_e('Recipients are contacted from top to bottom. Each row controls how long that person has to confirm before the next email is sent.', 'vachr-short-term-rental-reservations'); ?></p>
		<p class="description"><?php esc_html_e('Staff task calendar shortcode:', 'vachr-short-term-rental-reservations'); ?> <code>[vstrb_staff_tasks email="cleaner@example.com" days="90"]</code></p>
		<?php
	}

	private static function month_calendar($year, $month, array $rows, $email) {
		$first = sprintf('%04d-%02d-01', $year, $month);
		$days = (int) gmdate('t', strtotime($first));
		$offset = (int) gmdate('N', strtotime($first)) - 1;
		$out = '<section class="vstrb-task-month"><h3>' . esc_html(date_i18n('F Y', strtotime($first))) . '</h3><div class="vstrb-task-grid">';
		foreach ([__('Mon', 'vachr-short-term-rental-reservations'), __('Tue', 'vachr-short-term-rental-reservations'), __('Wed', 'vachr-short-term-rental-reservations'), __('Thu', 'vachr-short-term-rental-reservations'), __('Fri', 'vachr-short-term-rental-reservations'), __('Sat', 'vachr-short-term-rental-reservations'), __('Sun', 'vachr-short-term-rental-reservations')] as $day_name) $out .= '<span class="vstrb-task-weekday">' . esc_html($day_name) . '</span>';
		for ($blank = 0; $blank < $offset; $blank++) $out .= '<span class="vstrb-task-day is-empty"></span>';
		for ($day = 1; $day <= $days; $day++) {
			$date = sprintf('%04d-%02d-%02d', $year, $month, $day);
			$items = array_values(array_filter($rows, static function($row) use ($date) { return $row['date'] === $date; }));
			$out .= '<button type="button" class="vstrb-task-day' . ($items ? ' has-tasks' : '') . '" data-vstrb-task-date="' . esc_attr($date) . '"' . (!$items ? ' disabled' : '') . '><b>' . (int) $day . '</b>';
			foreach ($items as $item) $out .= '<i style="background:' . esc_attr($item['type']['color']) . '" title="' . esc_attr($item['type']['label']) . '"></i>';
			$out .= '</button>';
		}
		$out .= '</div>';
		foreach ($rows as $row) {
			if (substr($row['date'], 0, 7) !== substr($first, 0, 7)) continue;
			$status = ($row['state']['status'] ?? '') === 'confirmed' ? __('Confirmed', 'vachr-short-term-rental-reservations') : __('Waiting', 'vachr-short-term-rental-reservations');
			$who = ($row['state']['status'] ?? '') === 'confirmed' ? ($row['state']['confirmed_by'] ?? '') : ($row['assigned']['name'] ?: $row['assigned']['email']);
			$out .= '<article class="vstrb-task-detail" data-vstrb-task-detail="' . esc_attr($row['date']) . '" hidden><span class="vstrb-task-dot" style="background:' . esc_attr($row['type']['color']) . '"></span><strong>' . esc_html($row['type']['label']) . '</strong> — ' . esc_html(get_the_title((int) $row['booking']->unit_id)) . '<br><small>' . esc_html($status . ': ' . $who) . '</small>';
			if (!empty($row['type']['require_confirmation']) && ($row['state']['status'] ?? '') !== 'confirmed' && $email !== '') $out .= '<br><a class="vstrb-task-confirm" href="' . esc_url(self::confirmation_url((int) $row['booking']->id, $row['type']['id'], $email)) . '">' . esc_html__('Confirm / take over', 'vachr-short-term-rental-reservations') . '</a>';
			$out .= '</article>';
		}
		return $out . '</section>';
	}

	public static function render_calendar($email, $days = 90, $unit_id = 0) {
		$email = strtolower(sanitize_email($email));
		if ($email === '' || !VSTRB_Staff_Access::get_rule($email)) return '<p>' . esc_html__('No staff access rule found for this email.', 'vachr-short-term-rental-reservations') . '</p>';
		$rows = self::task_rows($email, $days, $unit_id);
		$start = new DateTimeImmutable(current_time('Y-m-01'));
		$end = $start->modify('+' . max(1, min(365, (int) $days)) . ' days');
		$out = '<style>.vstrb-task-calendar{display:grid;gap:24px}.vstrb-task-grid{display:grid;grid-template-columns:repeat(7,minmax(34px,1fr));gap:5px}.vstrb-task-weekday{text-align:center;font-size:12px;font-weight:700}.vstrb-task-day{min-height:54px;border:1px solid #dcdcde;border-radius:7px;background:#fff;padding:5px;color:inherit}.vstrb-task-day.has-tasks{cursor:pointer}.vstrb-task-day i{display:inline-block;width:8px;height:8px;border-radius:50%;margin:3px 2px}.vstrb-task-detail{margin-top:8px;padding:10px;border:1px solid #dcdcde;border-radius:7px}.vstrb-task-dot{display:inline-block;width:10px;height:10px;border-radius:50%}.vstrb-task-confirm{display:inline-block;margin-top:7px;padding:6px 10px;border-radius:4px;background:#2271b1;color:#fff;text-decoration:none}</style><div class="vstrb-task-calendar">';
		for ($month = $start; $month <= $end; $month = $month->modify('+1 month')) $out .= self::month_calendar((int) $month->format('Y'), (int) $month->format('m'), $rows, $email);
		$out .= '</div><script>(function(){document.querySelectorAll("[data-vstrb-task-date]").forEach(function(b){b.addEventListener("click",function(){var root=b.closest(".vstrb-task-month");root.querySelectorAll("[data-vstrb-task-detail]").forEach(function(d){d.hidden=d.dataset.vstrbTaskDetail!==b.dataset.vstrbTaskDate;});});});})();</script>';
		return $out;
	}
}

VSTRB_Staff_Tasks::register();
