<?php
/**
 * Timed Products admin UI.
 *
 * @package FoodOrd
 */

defined('ABSPATH') || exit;

/**
 * Class Foodord_Timed_Products_Admin
 */
class Foodord_Timed_Products_Admin
{

	/**
	 * Initialize hooks.
	 */
	public function init(): void
	{
		if (!class_exists('Foodord_Timed_Products')) {
			return;
		}

		add_action('admin_menu', array($this, 'register_submenu'), 18);
		add_action('add_meta_boxes', array($this, 'register_meta_boxes'));
		add_action('save_post_' . Foodord_Timed_Products::POST_TYPE, array($this, 'handle_save_post'), 10, 2);
		add_action('admin_enqueue_scripts', array($this, 'enqueue_assets'), 10, 0);
		add_action('admin_notices', array($this, 'render_admin_notices'));
		add_action('admin_init', array($this, 'handle_legacy_post_type_redirect'));
		add_filter('screen_options_show_screen', array($this, 'maybe_hide_screen_options'), 10, 1);

		add_filter('manage_edit-' . Foodord_Timed_Products::POST_TYPE . '_columns', array($this, 'register_columns'));
		add_action('manage_' . Foodord_Timed_Products::POST_TYPE . '_posts_custom_column', array($this, 'render_columns'), 10, 2);
		add_action('restrict_manage_posts', array($this, 'render_action_filter'));
		add_action('pre_get_posts', array($this, 'handle_action_filter'));
	}

	/**
	 * Hide screen options on Timed Products screens.
	 *
	 * @param bool $show Whether to show screen options.
	 */
	public function maybe_hide_screen_options($show): bool
	{
		if (!function_exists('get_current_screen')) {
			return (bool) $show;
		}

		$screen = get_current_screen();
		if ($screen && Foodord_Timed_Products::POST_TYPE === $screen->post_type) {
			return false;
		}

		return (bool) $show;
	}

	/**
	 * Determine whether the current screen is the add-new Timed Products rule screen.
	 *
	 * @param mixed $screen Current admin screen.
	 */
	private function is_add_new_rule_screen($screen): bool
	{
		if (!$screen instanceof WP_Screen) {
			return false;
		}

		return Foodord_Timed_Products::POST_TYPE === $screen->post_type
			&& 'post' === $screen->base
			&& 'add' === $screen->action;
	}

	/**
	 * Add submenu entry under FoodOrd menu.
	 */
	public function register_submenu(): void
	{
		if (!current_user_can('manage_options')) {
			return;
		}

		$parent_slug = function_exists('foodord_get_admin_parent_slug')
			? foodord_get_admin_parent_slug()
			: 'foodord-settings';

		add_submenu_page(
			$parent_slug,
			__('Timed Products', 'foodord'),
			__('Timed Products', 'foodord'),
			'manage_options',
			'edit.php?post_type=' . Foodord_Timed_Products::POST_TYPE
		);
	}

	/**
	 * Register meta boxes.
	 */
	public function register_meta_boxes(): void
	{
		global $typenow;
		if (Foodord_Timed_Products::POST_TYPE !== $typenow) {
			return;
		}

		add_meta_box(
			'foodord-timed-products-rules',
			__('Rules', 'foodord'),
			array($this, 'render_rules_metabox'),
			Foodord_Timed_Products::POST_TYPE,
			'normal',
			'high'
		);

		add_meta_box(
			'foodord-timed-products-conditions',
			__('Conditions', 'foodord'),
			array($this, 'render_conditions_metabox'),
			Foodord_Timed_Products::POST_TYPE,
			'normal',
			'high'
		);
	}

	/**
	 * Render rules meta box.
	 *
	 * @param WP_Post $post Current post.
	 */
	public function render_rules_metabox(WP_Post $post): void
	{
		$action = Foodord_Timed_Products::sanitize_action(get_post_meta($post->ID, '_foodord_timed_action', true));
		$order_mode = Foodord_Timed_Products::sanitize_order_mode(get_post_meta($post->ID, '_foodord_timed_order_mode', true));
		$schedule = Foodord_Timed_Products::sanitize_schedule(get_post_meta($post->ID, '_foodord_timed_schedule', true));
		if (empty($schedule)) {
			$schedule = array(
				array(
					'type' => 'days_of_week',
					'days' => array(),
					'time_from' => '',
					'time_to' => '',
					'date' => '',
					'date_from' => '',
					'date_to' => '',
				),
			);
		}

		wp_nonce_field('foodord_save_timed_product', '_foodord_timed_nonce');

		$timezone = wp_timezone();
		$current_ts = time();
		$current_text = wp_date('l, j F Y H:i', $current_ts, $timezone);
		$tz_name = $timezone instanceof DateTimeZone ? $timezone->getName() : get_option('timezone_string', 'UTC');
		?>
		<fieldset class="foodord-timed-field foodord-timed-segmented-control">
			<legend class="foodord-timed-label"><?php esc_html_e('Action', 'foodord'); ?></legend>
			<div class="foodord-timed-segmented-control__group">
				<input type="radio" id="foodord-timed-action-hide" class="foodord-timed-segmented-control__input" name="foodord_timed_action" value="hide" <?php checked('hide', $action); ?> />
				<label for="foodord-timed-action-hide" class="foodord-timed-segmented-control__button"><?php esc_html_e('Set Hidden', 'foodord'); ?></label>

				<input type="radio" id="foodord-timed-action-show" class="foodord-timed-segmented-control__input" name="foodord_timed_action" value="show" <?php checked('show', $action); ?> />
				<label for="foodord-timed-action-show" class="foodord-timed-segmented-control__button"><?php esc_html_e('Set Visible', 'foodord'); ?></label>
			</div>
		</fieldset>

		<fieldset class="foodord-timed-field foodord-timed-segmented-control">
			<legend class="foodord-timed-label"><?php esc_html_e('Order Mode', 'foodord'); ?></legend>
			<div class="foodord-timed-segmented-control__group">
				<input type="radio" id="foodord-timed-order-mode-both" class="foodord-timed-segmented-control__input" name="foodord_timed_order_mode" value="both" <?php checked('both', $order_mode); ?> />
				<label for="foodord-timed-order-mode-both" class="foodord-timed-segmented-control__button"><?php esc_html_e('Delivery + Pickup', 'foodord'); ?></label>

				<input type="radio" id="foodord-timed-order-mode-delivery" class="foodord-timed-segmented-control__input" name="foodord_timed_order_mode" value="delivery" <?php checked('delivery', $order_mode); ?> />
				<label for="foodord-timed-order-mode-delivery" class="foodord-timed-segmented-control__button"><?php esc_html_e('Delivery only', 'foodord'); ?></label>

				<input type="radio" id="foodord-timed-order-mode-pickup" class="foodord-timed-segmented-control__input" name="foodord_timed_order_mode" value="pickup" <?php checked('pickup', $order_mode); ?> />
				<label for="foodord-timed-order-mode-pickup" class="foodord-timed-segmented-control__button"><?php esc_html_e('Pickup only', 'foodord'); ?></label>
			</div>
		</fieldset>

		<div class="foodord-timed-current-time">
			<span class="dashicons dashicons-clock"></span>
			<div class="foodord-timed-current-time__content">
				<strong><?php esc_html_e('Current Date/Time', 'foodord'); ?>:</strong>
				<span><?php echo esc_html($current_text); ?></span>
				<span class="foodord-timed-timezone">(<?php echo esc_html($tz_name); ?>)</span>
				<a href="<?php echo esc_url(admin_url('options-general.php#timezone_string')); ?>" target="_blank"
					rel="noopener noreferrer">
					<?php esc_html_e('Timezone settings', 'foodord'); ?>
				</a>
			</div>
		</div>

		<div class="foodord-timed-schedule-wrapper" data-next-index="<?php echo esc_attr(count($schedule)); ?>">
			<?php foreach ($schedule as $index => $entry): ?>
				<?php echo wp_kses($this->get_schedule_item_markup((string) $index, $entry), $this->get_metabox_allowed_html()); ?>
			<?php endforeach; ?>
		</div>
		<p class="foodord-timed-actions">
			<button type="button" class="button button-secondary foodord-add-schedule">
				<span class="dashicons dashicons-plus"></span>
				<?php esc_html_e('Add Rule', 'foodord'); ?>
			</button>
		</p>
		<script type="text/template" id="foodord-timed-schedule-template">
											<?php
											echo wp_kses(
												$this->get_schedule_item_markup(
													'__INDEX__',
													array(
														'type' => 'days_of_week',
														'days' => array(),
														'time_from' => '',
														'time_to' => '',
														'date' => '',
														'date_from' => '',
														'date_to' => '',
													)
												),
												$this->get_metabox_allowed_html()
											);
											?>
										</script>
		<?php
	}

	/**
	 * Render conditions meta box.
	 *
	 * @param WP_Post $post Current post.
	 */
	public function render_conditions_metabox(WP_Post $post): void
	{
		$conditions = Foodord_Timed_Products::sanitize_conditions(get_post_meta($post->ID, '_foodord_timed_conditions', true));
		$rules = $this->flatten_conditions_rules($conditions);
		if (empty($rules)) {
			$rules = array(
				array(
					'subject' => 'product',
					'operator' => 'equals',
					'ids' => array(),
				),
			);
		}

		?>
		<div class="foodord-timed-condition-list" data-next-index="<?php echo esc_attr(count($rules)); ?>">
			<?php foreach ($rules as $rule_index => $rule): ?>
				<?php echo wp_kses($this->get_condition_rule_markup((string) $rule_index, $rule), $this->get_metabox_allowed_html()); ?>
			<?php endforeach; ?>
		</div>
		<p class="foodord-timed-actions">
			<button type="button" class="button button-secondary foodord-add-condition">
				<span class="dashicons dashicons-plus"></span>
				<?php esc_html_e('Add Condition', 'foodord'); ?>
			</button>
		</p>
		<script type="text/template" id="foodord-timed-condition-template">
											<?php
											echo wp_kses(
												$this->get_condition_rule_markup(
													'__INDEX__',
													array(
														'subject' => 'product',
														'operator' => 'equals',
														'ids' => array(),
													)
												),
												$this->get_metabox_allowed_html()
											);
											?>
										</script>
		<?php
	}

	/**
	 * Enqueue admin assets.
	 */
	public function enqueue_assets(): void
	{
		$screen = get_current_screen();
		if (!$screen || Foodord_Timed_Products::POST_TYPE !== $screen->post_type) {
			return;
		}

		if (!$this->is_add_new_rule_screen($screen)) {
			wp_enqueue_style(
				'foodord-admin-nav',
				plugins_url('admin/css/foodord-admin-nav.css', FOODORD_FILE),
				array(),
				FOODORD_VERSION
			);

			wp_enqueue_script(
				'foodord-admin-nav',
				plugins_url('admin/js/foodord-admin-nav.js', FOODORD_FILE),
				array(),
				FOODORD_VERSION,
				true
			);

			wp_enqueue_script(
				'foodord-admin-nav-reposition',
				plugins_url('admin/js/foodord-admin-nav-reposition.js', FOODORD_FILE),
				array(),
				FOODORD_VERSION,
				true
			);
		}

		wp_enqueue_style(
			'foodord-timed-products-admin',
			plugins_url('admin/css/foodord-timed-products.css', FOODORD_FILE),
			array(),
			FOODORD_VERSION
		);

		$wc_instance = function_exists('WC') ? WC() : null;
		if (!wp_style_is('select2', 'registered') && $wc_instance) {
			wp_register_style(
				'select2',
				$wc_instance->plugin_url() . '/assets/css/select2.css',
				array(),
				defined('WC_VERSION') ? WC_VERSION : FOODORD_VERSION
			);
		}

		if (wp_style_is('select2', 'registered')) {
			wp_enqueue_style('select2');
		}

		$deps = array('jquery');
		if (wp_script_is('selectWoo', 'registered')) {
			wp_enqueue_script('selectWoo');
		}
		if (class_exists('WooCommerce') && wp_script_is('wc-enhanced-select', 'registered')) {
			$deps[] = 'wc-enhanced-select';
			wp_enqueue_script('wc-enhanced-select');
		}

		wp_enqueue_script(
			'foodord-timed-products-admin',
			plugins_url('admin/js/foodord-timed-products.js', FOODORD_FILE),
			$deps,
			FOODORD_VERSION,
			true
		);

		wp_localize_script(
			'foodord-timed-products-admin',
			'foodordTimedProductsAdmin',
			array(
				'i18n' => array(
					'confirmDeleteSchedule' => __('Remove this rule?', 'foodord'),
					'confirmDeleteCondition' => __('Remove this condition?', 'foodord'),
				),
			)
		);
	}

	/**
	 * Handle post save.
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post    Post object.
	 */
	public function handle_save_post(int $post_id, $post): void
	{
		unset($post);

		if (!isset($_POST['_foodord_timed_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_foodord_timed_nonce'])), 'foodord_save_timed_product')) {
			return;
		}

		if (!current_user_can('manage_options')) {
			return;
		}

		if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
			return;
		}

		$action = $this->read_request_action();
		$order_mode = $this->read_request_order_mode();

		// Conditions must exist; sanitize them before continuing.
		$conditions = Foodord_Timed_Products::sanitize_conditions(
			$this->read_request_array('foodord_timed_conditions')
		);

		if (empty($conditions)) {
			wp_die(esc_html__('Please add at least one valid condition.', 'foodord'));
		}

		// Schedule entries are optional; sanitize after validation.
		$schedule = Foodord_Timed_Products::sanitize_schedule(
			$this->read_request_array('foodord_timed_schedule')
		);

		update_post_meta($post_id, '_foodord_timed_action', $action);
		update_post_meta($post_id, '_foodord_timed_order_mode', $order_mode);

		if (empty($schedule)) {
			delete_post_meta($post_id, '_foodord_timed_schedule');
		} else {
			update_post_meta($post_id, '_foodord_timed_schedule', $schedule);
		}

		update_post_meta($post_id, '_foodord_timed_conditions', $conditions);
		update_post_meta($post_id, '_foodord_timed_last_editor', get_current_user_id());
		update_post_meta($post_id, '_foodord_timed_last_updated', current_time('mysql', true));

		Foodord_Timed_Products::flush_cache();
	}

	/**
	 * Register admin list columns.
	 *
	 * @param array<string,string> $columns Columns.
	 */
	public function register_columns(array $columns): array
	{
		$columns['foodord_action'] = __('Action', 'foodord');
		$columns['foodord_order_mode'] = __('Order Mode', 'foodord');
		$columns['foodord_schedule'] = __('Schedule', 'foodord');
		$columns['foodord_conditions'] = __('Conditions', 'foodord');
		return $columns;
	}

	/**
	 * Render custom column values.
	 *
	 * @param string $column Column key.
	 * @param int    $post_id Post ID.
	 */
	public function render_columns(string $column, int $post_id): void
	{
		if ('foodord_action' === $column) {
			$action = Foodord_Timed_Products::sanitize_action(get_post_meta($post_id, '_foodord_timed_action', true));
			if ('show' === $action) {
				echo esc_html__('Set Visible', 'foodord');
			} else {
				echo esc_html__('Set Hidden', 'foodord');
			}
			return;
		}

		if ('foodord_schedule' === $column) {
			$schedule = Foodord_Timed_Products::sanitize_schedule(get_post_meta($post_id, '_foodord_timed_schedule', true));
			if (empty($schedule)) {
				echo esc_html__('Always', 'foodord');
				return;
			}
			$snippets = array();
			foreach ($schedule as $entry) {
				$summary = $this->summarize_schedule_entry($entry);
				if ('' !== $summary) {
					$snippets[] = $summary;
				}
			}
			if (empty($snippets)) {
				echo esc_html__('Always', 'foodord');
			} else {
				echo esc_html(implode('; ', $snippets));
			}
			return;
		}

		if ('foodord_order_mode' === $column) {
			echo esc_html($this->get_order_mode_label(
				Foodord_Timed_Products::sanitize_order_mode(get_post_meta($post_id, '_foodord_timed_order_mode', true))
			));
			return;
		}

		if ('foodord_conditions' === $column) {
			$conditions = Foodord_Timed_Products::sanitize_conditions(get_post_meta($post_id, '_foodord_timed_conditions', true));
			$rules = $this->flatten_conditions_rules($conditions);
			if (empty($rules)) {
				echo esc_html('—');
				return;
			}
			$parts = array();
			foreach ($rules as $rule) {
				$summary = $this->summarize_condition_rule($rule);
				if ('' !== $summary) {
					$parts[] = $summary;
				}
			}
			if (empty($parts)) {
				echo esc_html('—');
				return;
			}
			$glue = ' ' . __('OR', 'foodord') . ' ';
			echo esc_html(implode($glue, $parts));
		}
	}

	/**
	 * Render dropdown filter for actions.
	 */
	public function render_action_filter(): void
	{
		$screen = get_current_screen();
		if (!$screen || Foodord_Timed_Products::POST_TYPE !== $screen->post_type) {
			return;
		}

		$selected_raw = filter_input(INPUT_GET, 'foodord_timed_action', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
		$selected = is_string($selected_raw) ? sanitize_text_field($selected_raw) : '';
		?>
		<label for="foodord-filter-action"
			class="screen-reader-text"><?php esc_html_e('Filter by action', 'foodord'); ?></label>
		<select name="foodord_timed_action" id="foodord-filter-action">
			<option value=""><?php esc_html_e('All actions', 'foodord'); ?></option>
			<option value="hide" <?php selected('hide', $selected); ?>><?php esc_html_e('Set Hidden', 'foodord'); ?>
			</option>
			<option value="show" <?php selected('show', $selected); ?>><?php esc_html_e('Set Visible', 'foodord'); ?>
			</option>
		</select>
		<?php
	}

	/**
	 * Handle action filter query modifications.
	 *
	 * @param WP_Query $query Query instance.
	 */
	public function handle_action_filter($query): void
	{
		if (!is_admin() || !$query instanceof WP_Query || !$query->is_main_query()) {
			return;
		}

		$post_type = $query->get('post_type');
		if (Foodord_Timed_Products::POST_TYPE !== $post_type) {
			return;
		}

		$action_raw = filter_input(INPUT_GET, 'foodord_timed_action', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
		if (null === $action_raw || '' === $action_raw) {
			return;
		}

		$action = sanitize_text_field($action_raw);
		if (!in_array($action, array('hide', 'show'), true)) {
			return;
		}

		$meta_query = (array) $query->get('meta_query', array());
		$meta_query[] = array(
			'key' => '_foodord_timed_action',
			'value' => $action,
		);
		$query->set('meta_query', $meta_query);
	}

	/**
	 * Display admin notices registered through settings errors.
	 */
	public function render_admin_notices(): void
	{
		$screen = get_current_screen();
		if ($screen && Foodord_Timed_Products::POST_TYPE === $screen->post_type) {
			if (!$this->is_add_new_rule_screen($screen) && function_exists('foodord_admin_render_horizontal_menu')) {
				?>
				<div class="foodord-admin-nav-mount" data-foodord-admin-nav-mount="<?php echo esc_attr('timed-products'); ?>">
					<?php foodord_admin_render_horizontal_menu(); ?>
				</div>
				<?php
			}
			settings_errors('foodord_timed_products');
		}
	}

	/**
	 * Build markup for a schedule item.
	 *
	 * @param string $index Index key.
	 * @param array  $entry Entry data.
	 */
	private function get_schedule_item_markup(string $index, array $entry): string
	{
		$weekdays = $this->get_weekdays();
		$type = isset($entry['type']) ? sanitize_key($entry['type']) : 'days_of_week';
		$days = isset($entry['days']) && is_array($entry['days']) ? array_map('intval', $entry['days']) : array();
		$time_from = isset($entry['time_from']) ? $entry['time_from'] : ($entry['from'] ?? '');
		$time_to = isset($entry['time_to']) ? $entry['time_to'] : ($entry['to'] ?? '');
		$date = isset($entry['date']) ? $entry['date'] : '';
		$date_from = isset($entry['date_from']) ? $entry['date_from'] : '';
		$date_to = isset($entry['date_to']) ? $entry['date_to'] : '';

		if ('' === $type) {
			$type = 'days_of_week';
		}

		ob_start();
		?>
		<div class="foodord-timed-schedule-item" data-index="<?php echo esc_attr($index); ?>">
			<?php
			$logic_class = ('0' === (string) $index) ? 'foodord-schedule-logic is-hidden' : 'foodord-schedule-logic';
			?>
			<div class="<?php echo esc_attr($logic_class); ?>"><?php esc_html_e('AND', 'foodord'); ?></div>
			<div class="foodord-timed-schedule-header">
				<strong><span class="dashicons dashicons-calendar-alt"></span>
					<?php esc_html_e('Schedule Rule', 'foodord'); ?></strong>
				<button type="button" class="foodord-remove-schedule"
					aria-label="<?php esc_attr_e('Remove time range', 'foodord'); ?>">
					<span class="dashicons dashicons-no-alt"></span>
				</button>
			</div>
			<div class="foodord-timed-schedule-body">
				<div class="foodord-timed-field">
					<label class="foodord-timed-label"
						for="foodord-schedule-type-<?php echo esc_attr($index); ?>"><?php esc_html_e('Rule Type', 'foodord'); ?></label>
					<select id="foodord-schedule-type-<?php echo esc_attr($index); ?>" class="foodord-schedule-type"
						name="<?php echo esc_attr(sprintf('foodord_timed_schedule[%s][type]', $index)); ?>">
						<option value="on_date" <?php selected('on_date', $type); ?>>
							<?php esc_html_e('On date', 'foodord'); ?>
						</option>
						<option value="before_date" <?php selected('before_date', $type); ?>>
							<?php esc_html_e('Before date', 'foodord'); ?>
						</option>
						<option value="after_date" <?php selected('after_date', $type); ?>>
							<?php esc_html_e('After date', 'foodord'); ?>
						</option>
						<option value="date_range" <?php selected('date_range', $type); ?>>
							<?php esc_html_e('Date range', 'foodord'); ?>
						</option>
						<option value="days_of_week" <?php selected('days_of_week', $type); ?>>
							<?php esc_html_e('Days of the week', 'foodord'); ?>
						</option>
						<option value="time_range" <?php selected('time_range', $type); ?>>
							<?php esc_html_e('Time range', 'foodord'); ?>
						</option>
					</select>
				</div>
				<div class="foodord-timed-days foodord-schedule-field" data-schedule-field="days_of_week">
					<span class="foodord-timed-label"><?php esc_html_e('Day of the Week', 'foodord'); ?></span>
					<div class="foodord-timed-day-list">
						<?php
						foreach ($weekdays as $day_index => $day_label):
							$field_name = sprintf('foodord_timed_schedule[%s][days][]', $index);
							$field_id = sprintf('foodord_timed_schedule_%s_day_%s', $index, $day_index);
							?>
							<label for="<?php echo esc_attr($field_id); ?>" class="foodord-timed-day">
								<input type="checkbox" id="<?php echo esc_attr($field_id); ?>"
									name="<?php echo esc_attr($field_name); ?>"
									value="<?php echo esc_attr((string) $day_index); ?>" <?php checked(in_array($day_index, $days, true)); ?> />
								<span><?php echo esc_html($day_label); ?></span>
							</label>
						<?php endforeach; ?>
					</div>
				</div>
				<div class="foodord-timed-time-range foodord-schedule-field" data-schedule-field="time_range,days_of_week">
					<label class="foodord-timed-label-group">
						<span class="foodord-timed-label"><?php esc_html_e('Time Range', 'foodord'); ?></span>
						<span class="foodord-timed-time-inputs">
							<input type="time"
								name="<?php echo esc_attr(sprintf('foodord_timed_schedule[%s][time_from]', $index)); ?>"
								value="<?php echo esc_attr($time_from); ?>" />
							<span class="foodord-timed-time-separator">&mdash;</span>
							<input type="time"
								name="<?php echo esc_attr(sprintf('foodord_timed_schedule[%s][time_to]', $index)); ?>"
								value="<?php echo esc_attr($time_to); ?>" />
						</span>
					</label>
					<p class="description"><?php esc_html_e('Leave times empty to apply the entire day.', 'foodord'); ?></p>
				</div>
				<div class="foodord-schedule-field" data-schedule-field="on_date,before_date,after_date">
					<label class="foodord-timed-label"
						for="foodord-schedule-date-<?php echo esc_attr($index); ?>"><?php esc_html_e('Date', 'foodord'); ?></label>
					<input type="date" id="foodord-schedule-date-<?php echo esc_attr($index); ?>"
						name="<?php echo esc_attr(sprintf('foodord_timed_schedule[%s][date]', $index)); ?>"
						value="<?php echo esc_attr($date); ?>" />
				</div>
				<div class="foodord-schedule-field" data-schedule-field="date_range">
					<label class="foodord-timed-label"><?php esc_html_e('Date Range', 'foodord'); ?></label>
					<div class="foodord-timed-date-range-inputs">
						<input type="date"
							name="<?php echo esc_attr(sprintf('foodord_timed_schedule[%s][date_from]', $index)); ?>"
							value="<?php echo esc_attr($date_from); ?>" />
						<span class="foodord-timed-time-separator">&mdash;</span>
						<input type="date"
							name="<?php echo esc_attr(sprintf('foodord_timed_schedule[%s][date_to]', $index)); ?>"
							value="<?php echo esc_attr($date_to); ?>" />
					</div>
				</div>
			</div>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Build markup for a single condition rule.
	 *
	 * @param string $rule_index Rule index.
	 * @param array  $rule       Rule data.
	 */
	private function get_condition_rule_markup(string $rule_index, array $rule): string
	{
		$subject = isset($rule['subject']) && 'category' === $rule['subject'] ? 'category' : 'product';
		$operator = isset($rule['operator']) && 'not_equals' === $rule['operator'] ? 'not_equals' : 'equals';
		$ids = isset($rule['ids']) && is_array($rule['ids']) ? array_map('absint', $rule['ids']) : array();

		$product_options = ('product' === $subject) ? $this->get_product_options_markup($ids) : '';
		$category_options = ('category' === $subject) ? $this->get_category_options_markup($ids) : '';
		$allowed_options = $this->get_metabox_allowed_html();
		$is_product = ('product' === $subject);

		$is_first_row = ('0' === (string) $rule_index);
		$logic_class = $is_first_row ? 'foodord-condition-logic is-hidden' : 'foodord-condition-logic';
		$product_class = $is_product ? 'foodord-condition-product' : 'foodord-condition-product is-hidden';
		$category_class = $is_product ? 'foodord-condition-category is-hidden' : 'foodord-condition-category';

		ob_start();
		?>
		<div class="foodord-condition-row" data-rule-index="<?php echo esc_attr($rule_index); ?>">
			<div class="<?php echo esc_attr($logic_class); ?>" aria-hidden="true"><?php esc_html_e('OR', 'foodord'); ?>
			</div>
			<select name="<?php echo esc_attr(sprintf('foodord_timed_conditions[%s][subject]', $rule_index)); ?>"
				class="foodord-condition-subject">
				<option value="product" <?php selected('product', $subject); ?>><?php esc_html_e('Product', 'foodord'); ?>
				</option>
				<option value="category" <?php selected('category', $subject); ?>>
					<?php esc_html_e('Product Category', 'foodord'); ?>
				</option>
			</select>
			<select name="<?php echo esc_attr(sprintf('foodord_timed_conditions[%s][operator]', $rule_index)); ?>">
				<option value="equals" <?php selected('equals', $operator); ?>>
					<?php esc_html_e('is equal to', 'foodord'); ?>
				</option>
				<option value="not_equals" <?php selected('not_equals', $operator); ?>>
					<?php esc_html_e('is not equal to', 'foodord'); ?>
				</option>
			</select>
			<div class="foodord-condition-value">
				<div class="<?php echo esc_attr($product_class); ?>">
					<select class="wc-product-search" multiple="multiple"
						name="<?php echo esc_attr(sprintf('foodord_timed_conditions[%s][ids][]', $rule_index)); ?>"
						data-placeholder="<?php echo esc_attr__('Search products…', 'foodord'); ?>"
						data-action="woocommerce_json_search_products_and_variations" <?php disabled(!$is_product); ?>>
						<?php echo wp_kses($product_options, $allowed_options); ?>
					</select>
				</div>
				<div class="<?php echo esc_attr($category_class); ?>">
					<select class="foodord-condition-category-select wc-category-search" multiple="multiple"
						name="<?php echo esc_attr(sprintf('foodord_timed_conditions[%s][ids][]', $rule_index)); ?>"
						data-placeholder="<?php echo esc_attr__('Search categories…', 'foodord'); ?>" data-allow-clear="true"
						data-return_id="true" data-minimum_input_length="1" <?php disabled($is_product); ?>>
						<?php echo wp_kses($category_options, $allowed_options); ?>
					</select>
				</div>
			</div>
			<button type="button" class="foodord-remove-condition"
				aria-label="<?php esc_attr_e('Remove condition', 'foodord'); ?>">
				<span class="dashicons dashicons-trash"></span>
			</button>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	/**
	 * Get weekday labels.
	 */
	private function get_weekdays(): array
	{
		$labels = array();
		$timestamp = strtotime('Sunday');
		for ($i = 0; $i < 7; $i++) {
			$labels[$i] = wp_date('l', strtotime('+' . $i . ' day', $timestamp));
		}
		return $labels;
	}

	/**
	 * Summarize day labels.
	 *
	 * @param array $days Days list.
	 */
	private function format_days_label(array $days): string
	{
		$days = array_unique(array_map('intval', $days));
		sort($days);
		if (count($days) >= 7) {
			return __('Daily', 'foodord');
		}
		$labels = $this->get_weekdays();
		$output = array();
		foreach ($days as $day) {
			if (isset($labels[$day])) {
				$output[] = $labels[$day];
			}
		}
		return implode(', ', $output);
	}

	/**
	 * Summarize a schedule entry for the admin list.
	 *
	 * @param array $entry Schedule payload.
	 */
	private function summarize_schedule_entry(array $entry): string
	{
		$type = isset($entry['type']) ? $entry['type'] : 'days_of_week';
		switch ($type) {
			case 'days_of_week':
				$days = isset($entry['days']) ? $this->format_days_label($entry['days']) : '';
				if ('' === $days) {
					return '';
				}
				$time_from = isset($entry['time_from']) ? $entry['time_from'] : '';
				$time_to = isset($entry['time_to']) ? $entry['time_to'] : '';
				if ('' !== $time_from && '' !== $time_to) {
					return sprintf(
						/* translators: 1: comma-separated weekdays, 2: start time, 3: end time. */
						__('Days: %1$s (%2$s–%3$s)', 'foodord'),
						$days,
						$time_from,
						$time_to
					);
				}
				return sprintf(
					/* translators: %s: comma-separated weekdays. */
					__('Days: %s', 'foodord'),
					$days
				);

			case 'time_range':
				$from = isset($entry['time_from']) ? $entry['time_from'] : '';
				$to = isset($entry['time_to']) ? $entry['time_to'] : '';
				if ('' === $from || '' === $to) {
					return '';
				}
				return sprintf(
					/* translators: 1: start time, 2: end time. */
					__('Time: %1$s–%2$s', 'foodord'),
					$from,
					$to
				);

			case 'on_date':
				if (empty($entry['date'])) {
					return '';
				}
				return sprintf(
					/* translators: %s: specific date. */
					__('On %s', 'foodord'),
					$entry['date']
				);

			case 'before_date':
				if (empty($entry['date'])) {
					return '';
				}
				return sprintf(
					/* translators: %s: cutoff date. */
					__('Before %s', 'foodord'),
					$entry['date']
				);

			case 'after_date':
				if (empty($entry['date'])) {
					return '';
				}
				return sprintf(
					/* translators: %s: start date. */
					__('After %s', 'foodord'),
					$entry['date']
				);

			case 'date_range':
				$from = isset($entry['date_from']) ? $entry['date_from'] : '';
				$to = isset($entry['date_to']) ? $entry['date_to'] : '';
				if ('' === $from || '' === $to) {
					return '';
				}
				return sprintf(
					/* translators: 1: start date, 2: end date. */
					__('%1$s to %2$s', 'foodord'),
					$from,
					$to
				);
		}

		return '';
	}

	/**
	 * Flatten stored condition groups into a single rule list.
	 *
	 * @param array $groups Condition groups.
	 * @return array<int,array<string,mixed>>
	 */
	private function flatten_conditions_rules(array $groups): array
	{
		$rules = array();

		foreach ($groups as $group) {
			if (empty($group['rules']) || !is_array($group['rules'])) {
				continue;
			}

			foreach ($group['rules'] as $rule) {
				$rules[] = $rule;
			}
		}

		return $rules;
	}

	/**
	 * Summaries condition rule.
	 *
	 * @param array $rule Rule data.
	 */
	private function summarize_condition_rule(array $rule): string
	{
		$subject = isset($rule['subject']) && 'category' === $rule['subject'] ? 'category' : 'product';
		$operator = isset($rule['operator']) && 'not_equals' === $rule['operator'] ? 'not_equals' : 'equals';
		$ids = isset($rule['ids']) && is_array($rule['ids']) ? array_map('absint', $rule['ids']) : array();
		$ids = array_filter($ids);
		if (empty($ids)) {
			return '';
		}

		$names = array();
		if ('product' === $subject) {
			foreach ($ids as $id) {
				$names[] = $this->get_product_display_title($id);
			}
		} else {
			foreach ($ids as $id) {
				$term = get_term($id, 'product_cat');
				if ($term instanceof WP_Term) {
					$names[] = $term->name;
				}
			}
		}

		if (empty($names)) {
			return '';
		}

		$glue = 'not_equals' === $operator ? __('is not', 'foodord') : __('is', 'foodord');
		$subject_label = ('product' === $subject) ? __('Product', 'foodord') : __('Category', 'foodord');
		return sprintf(
			/* translators: 1: subject label, 2: operator text, 3: targets list. */
			__('%1$s %2$s %3$s', 'foodord'),
			$subject_label,
			$glue,
			implode(', ', $names)
		);
	}

	/**
	 * Generate <option> markup for product selects.
	 *
	 * @param array $ids Product IDs.
	 */
	private function get_product_options_markup(array $ids): string
	{
		if (empty($ids)) {
			return '';
		}
		$options = array();
		foreach ($ids as $id) {
			$options[] = sprintf('<option value="%1$s" selected>%2$s</option>', esc_attr((string) $id), esc_html($this->get_product_display_title($id)));
		}
		return implode('', $options);
	}

	/**
	 * Read and sanitize array input from the current request.
	 *
	 * @param string $field Field key.
	 * @return array
	 */
	private function read_request_array(string $field): array
	{
		$value = filter_input(INPUT_POST, $field, FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);

		return is_array($value) ? $this->sanitize_deep(wp_unslash($value)) : array();
	}

	/**
	 * Read and sanitize the submitted action.
	 */
	private function read_request_action(): string
	{
		$value = filter_input(INPUT_POST, 'foodord_timed_action', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
		$value = is_string($value) ? sanitize_text_field(wp_unslash($value)) : 'hide';

		return Foodord_Timed_Products::sanitize_action($value);
	}

	/**
	 * Read and sanitize the submitted order mode.
	 */
	private function read_request_order_mode(): string
	{
		$value = filter_input(INPUT_POST, 'foodord_timed_order_mode', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
		$value = is_string($value) ? sanitize_text_field(wp_unslash($value)) : 'both';

		return Foodord_Timed_Products::sanitize_order_mode($value);
	}

	/**
	 * Human readable order mode label.
	 *
	 * @param string $order_mode Order mode value.
	 */
	private function get_order_mode_label(string $order_mode): string
	{
		if ('delivery' === $order_mode) {
			return __('Delivery only', 'foodord');
		}

		if ('pickup' === $order_mode) {
			return __('Pickup only', 'foodord');
		}

		return __('Delivery + Pickup', 'foodord');
	}

	/**
	 * Allowed HTML map for metabox markup.
	 */
	private function get_metabox_allowed_html(): array
	{
		static $allowed = null;
		if (null !== $allowed) {
			return $allowed;
		}

		$common_data_attrs = array(
			'data-index' => true,
			'data-next-index' => true,
			'data-schedule-field' => true,
			'data-group-index' => true,
			'data-next-rule' => true,
			'data-next-group' => true,
			'data-rule-index' => true,
			'data-group' => true,
		);

		$allowed = array(
			'div' => array_merge(array('class' => true), $common_data_attrs),
			'span' => array(
				'class' => true,
				'hidden' => true,
				'aria-hidden' => true,
			),
			'button' => array(
				'class' => true,
				'type' => true,
				'aria-label' => true,
				'data-group' => true,
			),
			'strong' => array(
				'class' => true,
			),
			'label' => array(
				'class' => true,
				'for' => true,
			),
			'select' => array(
				'class' => true,
				'id' => true,
				'name' => true,
				'multiple' => true,
				'data-placeholder' => true,
				'data-action' => true,
				'data-allow-clear' => true,
				'data-return_id' => true,
				'data-minimum_input_length' => true,
				'disabled' => true,
			),
			'option' => array(
				'value' => true,
				'selected' => true,
			),
			'input' => array(
				'class' => true,
				'id' => true,
				'name' => true,
				'type' => true,
				'value' => true,
				'checked' => true,
				'disabled' => true,
			),
			'p' => array(
				'class' => true,
			),
		);

		return $allowed;
	}

	/**
	 * Sanitize nested input arrays recursively.
	 *
	 * @param mixed $value Raw value.
	 * @return mixed
	 */
	private function sanitize_deep($value)
	{
		if (is_array($value)) {
			foreach ($value as $key => $item) {
				$value[$key] = $this->sanitize_deep($item);
			}
			return $value;
		}

		if (is_scalar($value)) {
			return sanitize_text_field((string) $value);
		}

		return $value;
	}

	/**
	 * Provide a product title fallback for option lists.
	 *
	 * @param int $product_id Product ID.
	 */
	private function get_product_display_title(int $product_id): string
	{
		$title = get_the_title($product_id);
		if ('' !== $title) {
			return $title;
		}

		/* translators: %d: product ID. */
		return sprintf(__('Product #%d', 'foodord'), $product_id);
	}

	/**
	 * Generate category option markup.
	 *
	 * @param array $ids Category IDs.
	 */
	private function get_category_options_markup(array $ids): string
	{
		$selected_ids = array_values(array_filter(array_map('intval', $ids)));
		if (empty($selected_ids)) {
			return '';
		}

		$terms = get_terms(
			array(
				'taxonomy' => 'product_cat',
				'include' => $selected_ids,
				'hide_empty' => false,
			)
		);

		if (is_wp_error($terms)) {
			return '';
		}

		$term_map = array();
		foreach ($terms as $term) {
			if ($term instanceof WP_Term) {
				$term_map[(int) $term->term_id] = $term;
			}
		}

		$options = array();
		foreach ($selected_ids as $term_id) {
			if (!isset($term_map[$term_id])) {
				$term = get_term($term_id, 'product_cat');
				if (!$term instanceof WP_Term) {
					continue;
				}
			} else {
				$term = $term_map[$term_id];
			}

			$options[] = sprintf(
				'<option value="%1$s" selected>%2$s</option>',
				esc_attr((string) $term->term_id),
				esc_html($term->name)
			);
		}

		return implode('', $options);
	}

	/**
	 * Redirect legacy requests that reference the old, invalid post type slug.
	 */
	public function handle_legacy_post_type_redirect(): void
	{
		if (!is_admin() || wp_doing_ajax()) {
			return;
		}

		$legacy_slug = 'foodord_timed_product';
		$post_type = filter_input(INPUT_GET, 'post_type', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
		if ($legacy_slug !== $post_type) {
			return;
		}

		$target = add_query_arg('post_type', Foodord_Timed_Products::POST_TYPE);
		wp_safe_redirect($target);
		exit;
	}
}
