<?php
/**
 * WooCommerce Product Manager
 *
 * Handles product ticket configuration, variations, and metadata for WooCommerce products.
 *
 * @package    Event_Tickets_With_Ticket_Scanner
 * @subpackage WooCommerce
 * @since      2.9.0
 */

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

// Load seating base class for constants
require_once dirname(__DIR__) . '/seating/class-seating-base.php';

/**
 * Product Manager Class
 *
 * Manages ticket configuration for WooCommerce products including:
 * - Product meta boxes and fields
 * - Variation settings
 * - Product columns in admin
 * - Ticket list associations
 *
 * @since 2.9.0
 */
if (!class_exists('sasoEventtickets_WC_Product')) {
	class sasoEventtickets_WC_Product extends sasoEventtickets_WC_Base {

		/**
		 * Constructor
		 *
		 * @param sasoEventtickets $main Main plugin instance
		 */
		public function __construct($main) {
			parent::__construct($main);
		}

		/**
		 * Check if product ID is a ticket product
		 *
		 * @param int $product_id Product ID
		 * @return bool
		 */
		public function isTicketByProductId(int $product_id): bool {
			if ($product_id < 1) {
				return false;
			}
			return get_post_meta($product_id, self::META_PRODUCT_IS_TICKET, true) === "yes";
		}

		/**
		 * Get available ticket lists for dropdown
		 *
		 * @return array Ticket lists array
		 */
		public function wc_get_lists(): array {
			$lists = $this->MAIN->getAdmin()->getLists();
			$dropdown_list = ['' => esc_attr__('Deactivate auto-generating ticket', 'event-tickets-with-ticket-scanner')];

			foreach ($lists as $key => $list) {
				$dropdown_list[$list['id']] = $list['name'];
			}

			return $dropdown_list;
		}

		/**
		 * Add Event Tickets tab to product data tabs
		 *
		 * @param array $tabs Existing tabs
		 * @return array Modified tabs
		 */
		public function woocommerce_product_data_tabs(array $tabs): array {
			$tabs['saso_eventtickets_code_woo'] = [
				'label' => _x('Event Tickets', 'label', 'event-tickets-with-ticket-scanner'),
				'title' => _x('Event Tickets', 'title', 'event-tickets-with-ticket-scanner'),
				'target' => 'saso_eventtickets_wc_product_data',
				'class' => ['hide_if_grouped']
			];
			return $tabs;
		}

		/**
		 * Display product data panel content
		 *
		 * Renders the "Event Tickets" tab panel in WooCommerce product edit page.
		 * Includes all ticket configuration fields, date/time settings, and custom options.
		 *
		 * @return void
		 */
		public function woocommerce_product_data_panels(): void {
			$product = wc_get_product(get_the_ID());
			//$is_variable = $product->get_type() == "variable" ? true : false;
			$is_variation = $product->get_type() == "variation" ? true : false;
			$prem_JS_file = "";
			if ($this->MAIN->isPremium() && method_exists($this->MAIN->getPremiumFunctions(), 'getJSBackendFile')) {
				$prem_JS_file = $this->MAIN->getPremiumFunctions()->getJSBackendFile();
			}

			wp_enqueue_style("wp-jquery-ui-dialog");

			wp_register_script(
				'SasoEventticketsValidator_WC_backend',
				trailingslashit( plugin_dir_url( dirname(dirname(__FILE__)) ) ) . 'wc_backend.js?_v='.$this->MAIN->getPluginVersion(),
				array( 'jquery', 'jquery-blockui', 'wp-i18n'),
				(current_user_can("administrator") ? time() : $this->MAIN->getPluginVersion()),
				true );
			wp_localize_script(
				'SasoEventticketsValidator_WC_backend',
				'Ajax_sasoEventtickets_wc', // name der js variable
				[
					'ajaxurl' => admin_url( 'admin-ajax.php' ),
					'_plugin_home_url' =>plugins_url( "", dirname(dirname(__FILE__)) ),
					'prefix'=>$this->MAIN->getPrefix(),
					'nonce' => wp_create_nonce( $this->MAIN->_js_nonce ),
					'action' => $this->MAIN->getPrefix().'_executeWCBackend',
					'product_id'=>isset($_GET['post']) ? intval($_GET['post']) : 0,
					'order_id'=>0,
					'scope'=>'product',
					'_doNotInit'=>true,
					'_max'=>$this->MAIN->getBase()->getMaxValues(),
					'_isPremium'=>$this->MAIN->isPremium(),
					'_isUserLoggedin'=>is_user_logged_in(),
					'_backendJS'=>trailingslashit( plugin_dir_url( dirname(dirname(__FILE__)) ) ) . 'backend.js?_v='.$this->MAIN->getPluginVersion(),
					'_premJS'=>$prem_JS_file,
					'_divAreaId'=>'saso_eventtickets_list_format_area',
					'formatterInputFieldDataId'=>'saso_eventtickets_list_formatter_values',
					'ticket_base_url'=>$this->MAIN->getCore()->getTicketURLBase()
				] // werte in der js variable
				);
			wp_enqueue_script('SasoEventticketsValidator_WC_backend');
			wp_set_script_translations('SasoEventticketsValidator_WC_backend', 'event-tickets-with-ticket-scanner', dirname(dirname(__DIR__)).'/languages');

			$js_url = "jquery.qrcode.min.js?_v=".$this->MAIN->getPluginVersion();
			wp_enqueue_script(
				'ajax_script2',
				plugins_url( "3rd/".$js_url, dirname(dirname(__FILE__)) ),
				array('jquery', 'jquery-ui-dialog')
			);

			wp_enqueue_style($this->MAIN->getPrefix()."_backendcss", plugins_url( "", dirname(dirname(__FILE__)) ).'/css/styles_backend.css');

			echo '<div id="saso_eventtickets_wc_product_data" class="panel woocommerce_options_panel hidden">';

			if (!$this->MAIN->isPremium()) {
				$mv = $this->MAIN->getMV();
				echo '<p style="color:red;">'.sprintf(/* translators: %d: amount of maximum ticket that can be created */__('With the free basic plugin, you can only <b>create up to %d tickets!</b><br>Make sure your are not selling more tickets :)', 'event-tickets-with-ticket-scanner'), intval($mv['codes_total'])).'<br>'.sprintf(/* translators: 1: start of a-tag 2: end of a-tag */__('Here you can purchase the %1$spremium plugin%2$s for unlimited tickets.', 'event-tickets-with-ticket-scanner'), '<a target="_blank" href="https://vollstart.com/event-tickets-with-ticket-scanner/">', '</a>').'</p>';
			}

			$is_ticket_activated = get_post_meta( get_the_ID(), self::META_PRODUCT_IS_TICKET, true );
			echo '<div class="options_group">';
			woocommerce_wp_checkbox([
				'id'          => self::META_PRODUCT_IS_TICKET,
				'value'       => $is_ticket_activated,
				'label'       => __('Is a ticket sales', 'event-tickets-with-ticket-scanner'),
				'description' => __('Activate this, to generate a ticket number', 'event-tickets-with-ticket-scanner')
			]);
			echo "<p><b>Important:</b> You need to choose a list below, to activate the ticket sale for this product.</p>";
			$ticket_lists = $this->wc_get_lists();
			if (count($ticket_lists) == 1) { // only deactivation option is available
				echo "<p><b>".esc_html__('You have no lists created!', 'event-tickets-with-ticket-scanner')."</b><br>".esc_html__('You need to create a list first within the event tickets admin area, to choose a list from.', 'event-tickets-with-ticket-scanner')."</b></p>";
			}
			$ticket_list_id_choosen = get_post_meta( get_the_ID(), 'saso_eventtickets_list', true );
			if (empty($ticket_list_id_choosen) && $is_ticket_activated != "yes" && count($ticket_lists) > 1) {
				$ticket_list_id_choosen = "1";
			}
			woocommerce_wp_select( array(
				'id'          => 'saso_eventtickets_list',
				'value'       => $ticket_list_id_choosen,
				'label'       => __('List', 'event-tickets-with-ticket-scanner'),
				'description' => __('Choose a list to activate auto-generating ticket numbers/codes for each sold item', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    => true,
				'options'     => $ticket_lists
			) );
			echo '</div>';

			// Congress assignment (only when the congress module is present and at least one congress exists)
			if (file_exists(plugin_dir_path(dirname(__FILE__)) . 'congress/class-congress-repository.php')) {
				$congresses = $this->MAIN->getCongressRepository()->getAll();
				if (!empty($congresses)) {
					$congress_options = ['0' => __('— None —', 'event-tickets-with-ticket-scanner')];
					foreach ($congresses as $c) {
						$label = $c['title'];
						$date  = !empty($c['event_start_at']) ? $c['event_start_at'] : ($c['access_expires_at'] ?? '');
						if (!empty($date)) {
							$label .= ' (' . date_i18n(get_option('date_format'), strtotime($date)) . ')';
						}
						$congress_options[(string)(int)$c['id']] = $label;
					}
					echo '<div class="options_group">';
					woocommerce_wp_select([
						'id'          => 'saso_et_congress_id',
						'value'       => (string)(int)get_post_meta(get_the_ID(), '_saso_et_congress_id', true),
						'label'       => __('Assign congress', 'event-tickets-with-ticket-scanner'),
						'description' => __('Ticket holders of this product will get access to the assigned congress page.', 'event-tickets-with-ticket-scanner'),
						'desc_tip'    => true,
						'options'     => $congress_options,
					]);
					echo '</div>';
				}
			}

			echo '<div class="options_group">';
			woocommerce_wp_checkbox([
				'id'          => 'saso_eventtickets_require_cvv_at_scanner',
				'value'       => get_post_meta(get_the_ID(), 'saso_eventtickets_require_cvv_at_scanner', true),
				'label'       => __('Require CVV at the ticket scanner', 'event-tickets-with-ticket-scanner'),
				'description' => __('If active, a CVV is auto-generated for every new ticket on this product, and the ticket scanner asks the customer to enter the CVV before redeem. The CVV is delivered via the order email and customer profile, never embedded in the QR code. Best for private/exclusive events where a lost ticket card alone must not grant entry.', 'event-tickets-with-ticket-scanner'),
			]);
			echo '</div>';

			// Seating Plan Section
			echo '<div class="options_group">';
			$planManager = $this->MAIN->getSeating()->getPlanManager();
			$seatingPlans = $planManager->getDropdownOptions(true);

			if (count($seatingPlans) > 1) {
				// Get draft-only plan IDs for warning
				$allPlans = $planManager->getAll(true);
				$draftOnlyPlanIds = [];
				foreach ($allPlans as $plan) {
					if (empty($plan['published_at'])) {
						$draftOnlyPlanIds[] = (int) $plan['id'];
					}
				}

				$seating = $this->MAIN->getSeating();
				$metaSeatingplan = $seating->getMetaProductSeatingplan();
				$metaSeatingRequired = $seating->getMetaProductSeatingRequired();

				$currentPlanId = (int) get_post_meta(get_the_ID(), $metaSeatingplan, true);

				woocommerce_wp_select([
					'id'          => $metaSeatingplan,
					'value'       => $currentPlanId,
					'label'       => __('Seating Plan', 'event-tickets-with-ticket-scanner'),
					'description' => __('Assign a seating plan to this ticket product.', 'event-tickets-with-ticket-scanner'),
					'desc_tip'    => true,
					'options'     => $seatingPlans,
					'custom_attributes' => [
						'data-draft-only-ids' => esc_attr(json_encode($draftOnlyPlanIds))
					]
				]);

				// Warning for draft-only plans
				$showWarning = in_array($currentPlanId, $draftOnlyPlanIds, true);
				echo '<p class="form-field saso-seating-draft-warning" style="' . ($showWarning ? '' : 'display:none;') . '">';
				echo '<span class="description" style="color: #d63638; font-weight: bold;">';
				echo '⚠️ ' . esc_html__('This seating plan has not been published yet. Customers will not see a seat selection until you publish the plan.', 'event-tickets-with-ticket-scanner');
				echo ' <a href="' . esc_url(admin_url('admin.php?page=sasoEventTickets&tab=seating')) . '">' . esc_html__('Go to Seating Plans', 'event-tickets-with-ticket-scanner') . '</a>';
				echo '</span></p>';

				// JavaScript for toggling warning
				?>
				<script>
				jQuery(function($) {
					var $select = $('#<?php echo esc_js($metaSeatingplan); ?>');
					var $warning = $('.saso-seating-draft-warning');
					var draftOnlyIds = <?php echo json_encode($draftOnlyPlanIds); ?>;

					$select.on('change', function() {
						var selectedId = parseInt($(this).val()) || 0;
						if (draftOnlyIds.indexOf(selectedId) !== -1) {
							$warning.show();
						} else {
							$warning.hide();
						}
					});
				});
				</script>
				<?php

				woocommerce_wp_checkbox([
					'id'          => $metaSeatingRequired,
					'value'       => get_post_meta(get_the_ID(), $metaSeatingRequired, true),
					'label'       => __('Seat Selection Required', 'event-tickets-with-ticket-scanner'),
					'description' => __('If enabled: Customer must select a seat before adding to cart (e.g., theater with numbered seats). If disabled: Seat selection is optional (e.g., festival with VIP seats + standing area).', 'event-tickets-with-ticket-scanner')
				]);
			} else {
				echo '<p class="form-field">';
				echo '<label>' . esc_html__('Seating Plan', 'event-tickets-with-ticket-scanner') . '</label>';
				echo '<span class="description">' . esc_html__('No seating plans available.', 'event-tickets-with-ticket-scanner') . ' ';
				echo '<a href="' . esc_url(admin_url('admin.php?page=sasoEventTickets&tab=seating')) . '">';
				echo esc_html__('Create one first', 'event-tickets-with-ticket-scanner') . '</a></span>';
				echo '</p>';
			}
			echo '</div>';

			echo '<div class="options_group">';
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_event_location',
				'value'       		=> get_post_meta( get_the_ID(), 'saso_eventtickets_event_location', true ),
				'label'       		=> wp_kses_post($this->MAIN->getOptions()->getOptionValue("wcTicketTransLocation")),
				'type'				=> 'text',
				'description' 		=> __('This will be also in the cal entry file.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_ticket_start_date',
				'value'       		=> get_post_meta( get_the_ID(), 'saso_eventtickets_ticket_start_date', true ),
				'label'       		=> __('Start date event', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'date',
				'custom_attributes'	=> ['data-type'=>'date'],
				'description' 		=> __('Set this to have this printed on the ticket and prevent too early redeemed tickets. Tickets can be redeemed from that day on.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_ticket_start_time',
				'value'       		=> get_post_meta( get_the_ID(), 'saso_eventtickets_ticket_start_time', true ),
				'label'       		=> __('Start time', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'time',
				'description' 		=> __('Set this to have this printed on the ticket.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_ticket_end_date',
				'value'       		=> get_post_meta( get_the_ID(), 'saso_eventtickets_ticket_end_date', true ),
				'label'       		=> __('End date event', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'date',
				'custom_attributes'	=> ['data-type'=>'date'],
				'description' 		=> __('Set this to have this printed on the ticket and prevent later the ticket to be still valid. Tickets cannot be redeemed after that day.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_ticket_end_time',
				'value'       		=> get_post_meta( get_the_ID(), 'saso_eventtickets_ticket_end_time', true ),
				'label'       		=> __('End time', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'time',
				'description' 		=> __('Set this to have this printed on the ticket.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			if (true || $is_variation) {
				woocommerce_wp_checkbox([
					'id'          => 'saso_eventtickets_is_date_for_all_variants',
					'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_is_date_for_all_variants', true ),
					'label'       => __('Date is for all variants', 'event-tickets-with-ticket-scanner'),
					'description' => __('Activate this, to have the entered date printed on all product variants. No effect on simple products.', 'event-tickets-with-ticket-scanner')
				]);
			}
			echo '</div>';

			echo '<div class="options_group">';
			// checkbox to activate the date choosnowser
				// info, that only the time will be taken from the date settings. If no time is set then it will be treated like 0:00 - 23:59.
			woocommerce_wp_checkbox([
				'id'          => 'saso_eventtickets_is_daychooser',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_is_daychooser', true ),
				'label'       => __('Customer can choose the day', 'event-tickets-with-ticket-scanner'),
				'description' => __('Activate this, to allow your customer to choose a date. If this option is active it will use the start and end date as limits, if provided. And use only the start and end time setting. If the start and end time is not set, then the entrance is allowed from 00:00 till 23:59.', 'event-tickets-with-ticket-scanner')
			]);
			woocommerce_wp_checkbox([
				'id'          => 'saso_eventtickets_only_one_day_for_all_tickets',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_only_one_day_for_all_tickets', true ),
				'label'       => __('Only one date picker', 'event-tickets-with-ticket-scanner'),
				'description' => __('If this option is active, then only one date is allowed per purchase of this product. If your customer buys 2 or more of this ticket, than only one date picker will be shown for all the tickets. The default is to display for each product in the cart one separated date picker.', 'event-tickets-with-ticket-scanner')
			]);
			// checkboxes to exclude days of week
			woocommerce_wp_select( array(
				'id'          => 'saso_eventtickets_daychooser_exclude_wdays',
				'name' 		  => 'saso_eventtickets_daychooser_exclude_wdays[]',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_daychooser_exclude_wdays', true ),
				'label'       => __('Choose which days to exclude', 'event-tickets-with-ticket-scanner'),
				'description' => __('To select more than one, hold down the CTRL key. The selected days cannot be choosen by your customer in date chooser.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    => true,
				'class' => 'cb-admin-multiselect',
				'options'     => [
					"1"=>__('Monday', 'event-tickets-with-ticket-scanner'),
					"2"=>__('Tuesday', 'event-tickets-with-ticket-scanner'),
					"3"=>__('Wednesday', 'event-tickets-with-ticket-scanner'),
					"4"=>__('Thursday', 'event-tickets-with-ticket-scanner'),
					"5"=>__('Friday', 'event-tickets-with-ticket-scanner'),
					"6"=>__('Saturday', 'event-tickets-with-ticket-scanner'),
					"0"=>__('Sunday', 'event-tickets-with-ticket-scanner')
				],
				'custom_attributes' => array('multiple' => 'multiple')
			) );
			// input field for offset first day to choose from in days
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_daychooser_offset_start',
				'value'       		=> intval(get_post_meta( get_the_ID(), 'saso_eventtickets_daychooser_offset_start', true )),
				'label'       		=> __('Offset days for start date', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'number',
				'custom_attributes'	=> ['step'=>'1', 'min'=>'0'],
				'description' 		=> __('This will set how many days to skip before you allow your customer to choose a date. 0 means starting from the same day, 1 means from tomorrow on and so on. If you set a start date, the the start date will be considered as a minimum starting date.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			// input field for offset last day to choose from in days
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_daychooser_offset_end',
				'value'       		=> intval(get_post_meta( get_the_ID(), 'saso_eventtickets_daychooser_offset_end', true )),
				'label'       		=> __('Offset days for end date', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'number',
				'custom_attributes'	=> ['step'=>'1', 'min'=>'0'],
				'description' 		=> __('This will set how many days in the future do you allow your customer to choose a date. 0 unlimited into the future, 1 means until tomorrow on and so on. If a end date is set, then this option is ignored and the end date is used.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_daychooser_cutoff_time',
				'value'       		=> get_post_meta( get_the_ID(), 'saso_eventtickets_daychooser_cutoff_time', true ),
				'label'       		=> __('Same-day cutoff time', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'time',
				'description' 		=> __('If the current time is past this value, today will no longer be selectable. Only applies when "Offset days for start date" is 0. Leave empty to disable.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true,
				'placeholder'		=> '19:00',
			]);
			woocommerce_wp_text_input([
				'id'          => 'saso_eventtickets_request_daychooser_per_ticket_label',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_daychooser_per_ticket_label', true ),
				'label'       => __('Label for the date picker', 'event-tickets-with-ticket-scanner'),
				'description' => __('This is how your customer understand what value should be choosen.', 'event-tickets-with-ticket-scanner'),
				'placeholder' => 'Please choose a day #{count}:',
				'desc_tip'    => true
			]);

			echo '</div>';

			echo '<div class="options_group">';
			$_max_redeem_amount = get_post_meta( get_the_ID(), 'saso_eventtickets_ticket_max_redeem_amount', true );
			if (empty($_max_redeem_amount) || $_max_redeem_amount == "0") {
				$max_redeem_amount = 1;
			} else {
				$max_redeem_amount = intval($_max_redeem_amount);
				if ($max_redeem_amount < 1) $max_redeem_amount = 1;
			}
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_ticket_max_redeem_amount',
				'value'       		=> $max_redeem_amount,
				'label'       		=> __('Max. redeem operations', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'number',
				'custom_attributes'	=> ['step'=>'1', 'min'=>'0'],
				'description' 		=> __('How often do you allow to redeem the ticket? If you set it to 0, you can redeem the ticket unlimited.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_ticket_max_redeem_per_day',
				'value'       		=> intval(get_post_meta(get_the_ID(), 'saso_eventtickets_ticket_max_redeem_per_day', true)),
				'label'       		=> __('Max. redeems per day', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'number',
				'custom_attributes'	=> ['step' => '1', 'min' => '0'],
				'description' 		=> __('How often can the ticket be redeemed per day? 0 = unlimited (only total max applies).', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			woocommerce_wp_textarea_input([
				'id'          => 'saso_eventtickets_ticket_is_ticket_info',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_ticket_is_ticket_info', true ),
				'label'       => __('Print this on the ticket', 'event-tickets-with-ticket-scanner'),
				'description' => __('This optional information will be displayed on the ticket detail page.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    => true
			]);

			/*
			woocommerce_wp_checkbox( array(
				'id'          => 'saso_eventtickets_ticket_is_RTL',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_ticket_is_RTL', true ),
				'label'       => __('Text is RTL', 'event-tickets-with-ticket-scanner'),
				'description' => __('Activate this, to use language from right to left.', 'event-tickets-with-ticket-scanner')
			));
			*/
			echo '</div>';

			echo '<div class="options_group">';
			$saso_eventtickets_ticket_amount_per_item = intval(get_post_meta( get_the_ID(), 'saso_eventtickets_ticket_amount_per_item', true ));
			if ($saso_eventtickets_ticket_amount_per_item < 1) $saso_eventtickets_ticket_amount_per_item = 1;
			woocommerce_wp_text_input([
				'id'				=> 'saso_eventtickets_ticket_amount_per_item',
				'value'       		=> $saso_eventtickets_ticket_amount_per_item,
				'label'       		=> __('Amount of ticket numbers per item sale', 'event-tickets-with-ticket-scanner'),
				'type'				=> 'number',
				'custom_attributes'	=> ['step'=>'1', 'min'=>'1'],
				'description' 		=> __('How many ticket number to assign if one product is sold?', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    		=> true
			]);
			echo '</div>';

			echo '<div class="options_group">';
			woocommerce_wp_checkbox([
				'id'          => 'saso_eventtickets_request_name_per_ticket',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_name_per_ticket', true ),
				'label'       => __('Request a value for each ticket', 'event-tickets-with-ticket-scanner'),
				'description' => __('Activate this, so that your customer can add a value for each ticket. This could be the name or any other value, defined by you. This value will be printed on the ticket. The value is limited to max 140 letters.', 'event-tickets-with-ticket-scanner')
			]);
			woocommerce_wp_text_input([
				'id'          => 'saso_eventtickets_request_name_per_ticket_label',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_name_per_ticket_label', true ),
				'label'       => __('Label for the value', 'event-tickets-with-ticket-scanner'),
				'description' => __('This is how your customer understand what value should be entered.', 'event-tickets-with-ticket-scanner'),
				'placeholder' => 'Name for the ticket #{count}:',
				'desc_tip'    => true
			]);
			woocommerce_wp_checkbox([
				'id'          => 'saso_eventtickets_request_name_per_ticket_mandatory',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_name_per_ticket_mandatory', true ),
				'label'       => __('The value for each ticket is mandatory', 'event-tickets-with-ticket-scanner'),
				'description' => __('Activate this, so that your customer has to enter a value.', 'event-tickets-with-ticket-scanner')
			]);

			echo "<hr>";

			woocommerce_wp_checkbox([
				'id'          => 'saso_eventtickets_request_value_per_ticket',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_value_per_ticket', true ),
				'label'       => __('Request a value for each ticket from dropdown', 'event-tickets-with-ticket-scanner'),
				'description' => __('Activate this, so that your customer can choose a value for each ticket.', 'event-tickets-with-ticket-scanner')
			]);
			woocommerce_wp_textarea_input([
				'id'          => 'saso_eventtickets_request_value_per_ticket_label',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_value_per_ticket_label', true ),
				'label'       => __('Label for the value', 'event-tickets-with-ticket-scanner'),
				'description' => __('This is how your customer understand what value should be choosen.', 'event-tickets-with-ticket-scanner'),
				'placeholder' => 'Please choose a value #{count}:',
				'desc_tip'    => true
			]);
			woocommerce_wp_textarea_input([
				'id'          => 'saso_eventtickets_request_value_per_ticket_values',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_value_per_ticket_values', true ),
				'label'       => __('Values for the dropdown', 'event-tickets-with-ticket-scanner'),
				'description' => __('Enter per line a key value pair like key|value1. If only key is given per line, then the key will be also the value.', 'event-tickets-with-ticket-scanner'),
				'placeholder' => "|Please choose\nkey1|value1\nkey2|value2\nvalue3",
				'desc_tip'    => true
			]);
			woocommerce_wp_text_input([
				'id'          => 'saso_eventtickets_request_value_per_ticket_def',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_value_per_ticket_def', true ),
				'label'       => __('Enter default key for the dropdown (optional)', 'event-tickets-with-ticket-scanner'),
				'description' => __('If not empty, the system will add the value with this key as the default chosen value.', 'event-tickets-with-ticket-scanner'),
				'placeholder' => 'key1',
				'desc_tip'    => true
			]);
			woocommerce_wp_checkbox([
				'id'          => 'saso_eventtickets_request_value_per_ticket_mandatory',
				'value'       => get_post_meta( get_the_ID(), 'saso_eventtickets_request_value_per_ticket_mandatory', true ),
				'label'       => __('The value for each ticket is mandatory', 'event-tickets-with-ticket-scanner'),
				'description' => __('Activate this, so that your customer has to choose a value.', 'event-tickets-with-ticket-scanner')
			]);
			echo '</div>';

			echo '<div class="options_group">';
			woocommerce_wp_checkbox( array(
				'id'            => 'saso_eventtickets_list_formatter',
				'label'			=> __('Use format settings', 'event-tickets-with-ticket-scanner'),
				'description'   => __('If active, then the format below will be used to generate ticket numbers during a purchase of this product.', 'event-tickets-with-ticket-scanner'),
				'value'         => get_post_meta( get_the_ID(), 'saso_eventtickets_list_formatter', true )
			) );
			echo '<input data-id="saso_eventtickets_list_formatter_values" name="saso_eventtickets_list_formatter_values" type="hidden" value="'.esc_js(get_post_meta( get_the_ID(), 'saso_eventtickets_list_formatter_values', true )).'">';
			echo '<div style="padding-top:10px;padding-left:10%;padding-right:20px;"><b>'.esc_html__('The ticket number format settings.', 'event-tickets-with-ticket-scanner').'</b><br><i>'.esc_html__('This will override an existing and active global "serial code formatter pattern for new sales" and also any format settings from the group.', 'event-tickets-with-ticket-scanner').'</i><div id="saso_eventtickets_list_format_area"></div></div>';
			echo '</div>';

			if ($this->MAIN->isPremium() && method_exists($this->MAIN->getPremiumFunctions(), 'saso_eventtickets_wc_product_panels')) {
				$this->MAIN->getPremiumFunctions()->saso_eventtickets_wc_product_panels(get_the_ID());
			}

			echo '</div>';
		}

		/**
		 * Save product meta data
		 *
		 * Processes and saves all ticket configuration fields from POST data to product meta.
		 * Handles checkboxes, text inputs, number fields, arrays, and HTML content.
		 *
		 * @param int $id Product ID
		 * @param WP_Post $post Post object
		 * @return void
		 */
		public function woocommerce_process_product_meta($id, $post): void {
		$id = intval($id);
			$R = SASO_EVENTTICKETS::getRequest();

			$key = 'saso_eventtickets_list';
			if( isset($R[$key]) && !empty( $R[$key] ) ) {
				update_post_meta( $id, $key, sanitize_text_field($R[$key]) );
			} else {
				delete_post_meta( $id, $key );
			}

			// damit nicht alte Eintragungen gelöscht werden - so kann der kunde upgrade machen und alles ist noch da
			if (version_compare( WC_VERSION, SASO_EVENTTICKETS_PLUGIN_MIN_WC_VER, '>=' )) {
				$key = 'saso_eventtickets_list_sale_restriction';
				if( isset($R[$key]) && ($R[$key] == '0' || !empty( $R[$key] )) ) {
					update_post_meta( $id, $key, sanitize_text_field($R[$key]) );
				} else {
					delete_post_meta( $id, $key );
				}
			}

			$seating = $this->MAIN->getSeating();
			$keys_checkbox = [
				self::META_PRODUCT_IS_TICKET,
				'saso_eventtickets_is_date_for_all_variants',
				'saso_eventtickets_is_daychooser',
				'saso_eventtickets_only_one_day_for_all_tickets',
				'saso_eventtickets_request_name_per_ticket',
				'saso_eventtickets_request_name_per_ticket_mandatory',
				'saso_eventtickets_request_value_per_ticket',
				'saso_eventtickets_request_value_per_ticket_mandatory',
				'saso_eventtickets_ticket_is_RTL',
				'saso_eventtickets_list_formatter',
				$seating->getMetaProductSeatingRequired(),
				'saso_eventtickets_require_cvv_at_scanner',  // NEW
			];
			foreach($keys_checkbox as $key) {
				// Variation rows render <select name="<key>[<i>]"> for some of these
				// keys, so on a variable product save $_POST[<key>] arrives as an
				// array even when the parent's own checkbox is unchecked. Treat
				// any array value as "parent did not submit a checkbox value" so
				// the parent meta is not silently flipped to "yes".
				if( isset( $R[$key] ) && !is_array($R[$key]) ) {
					update_post_meta( $id, $key, 'yes' );
				} else {
					delete_post_meta( $id, $key );
				}
			}

			$keys_inputfields = [
				'saso_eventtickets_event_location',
				'saso_eventtickets_ticket_start_date',
				'saso_eventtickets_ticket_start_time',
				'saso_eventtickets_ticket_end_date',
				'saso_eventtickets_ticket_end_time',
				'saso_eventtickets_request_name_per_ticket_label',
				'saso_eventtickets_request_value_per_ticket_label',
				'saso_eventtickets_request_value_per_ticket_def',
				'saso_eventtickets_list_formatter_values',
				'saso_eventtickets_request_daychooser_per_ticket_label',
				'saso_eventtickets_daychooser_cutoff_time'
			];

			foreach($keys_inputfields as $key) {
				// Same variation-collision guard as above — variation rows submit
				// arrays under these names, sanitize_text_field() on an array
				// would corrupt the parent value (or warn on PHP 8+).
				if( isset($R[$key]) && !is_array($R[$key]) && !empty( $R[$key] ) ) {
					update_post_meta( $id, $key, sanitize_text_field($R[$key]) );
				} else {
					delete_post_meta( $id, $key );
				}
			}

			$key = 'saso_eventtickets_daychooser_exclude_wdays';
			if (isset($R[$key])) {
				$array_to_save = [];
				foreach($R[$key] as $v) {
					$v = sanitize_text_field($v);
					$array_to_save[] = $v;
				}
				update_post_meta( $id, $key, $array_to_save );
			} else {
				delete_post_meta( $id, $key );
			}

			$keys_number = [
				'saso_eventtickets_ticket_max_redeem_amount',
				'saso_eventtickets_ticket_max_redeem_per_day',
				'saso_eventtickets_ticket_amount_per_item',
				'saso_eventtickets_daychooser_offset_start',
				'saso_eventtickets_daychooser_offset_end'
			];
			foreach($keys_number as $key) {
				// Variation collision guard: saso_eventtickets_ticket_amount_per_item
				// is also rendered per variation as "<key>[<i>]". intval() on an
				// array returns 1, which would silently set the parent to 1.
				if( isset($R[$key]) && !is_array($R[$key]) && (!empty($R[$key]) || $R[$key] == "0") ) {
					$value = intval($R[$key]);
					if ($value < 0) $value = 1;
					update_post_meta( $id, $key, $value );
				} else {
					delete_post_meta( $id, $key );
				}
			}

			$key = 'saso_eventtickets_ticket_is_ticket_info';
			if( isset($R[$key]) && !empty( $R[$key] ) ) {
				update_post_meta( $id, $key, wp_kses_post($R[$key]) );
			} else {
				delete_post_meta( $id, $key );
			}
			$key = 'saso_eventtickets_request_value_per_ticket_values';
			if( isset($R[$key]) && !empty( $R[$key] ) ) {
				$v = [];
				foreach(explode("\n", $R[$key]) as $entry) {
					$t = explode("|", $entry);
					if (count($t) > 0) {
						$t[0] = sanitize_key(trim($t[0]));
						if (count($t) > 1) {
							$t[1] = sanitize_key(trim($t[1]));
						}
						$v[] = join("|", $t);
					}
				}
				update_post_meta( $id, $key, join("\n", $v));
			} else {
				delete_post_meta( $id, $key );
			}

			// Seating Plan ID
			$seating_key = $seating->getMetaProductSeatingplan();
			$seating_plan_id = isset($_POST[$seating_key]) ? intval($_POST[$seating_key]) : 0;
			if ($seating_plan_id > 0) {
				update_post_meta($id, $seating_key, $seating_plan_id);
			} else {
				delete_post_meta($id, $seating_key);
			}

			if ($this->MAIN->isPremium() && method_exists($this->MAIN->getPremiumFunctions(), 'saso_eventtickets_wc_save_fields')) {
				$this->MAIN->getPremiumFunctions()->saso_eventtickets_wc_save_fields($id, $post);
			}

			// Save congress assignment
			if (file_exists(plugin_dir_path(dirname(__FILE__)) . 'congress/class-congress-repository.php')) {
				$congress_id     = (int)($_POST['saso_et_congress_id'] ?? 0);
				$old_congress_id = (int)get_post_meta($id, '_saso_et_congress_id', true);
				$repo            = $this->MAIN->getCongressRepository();

				// Remove product from old congress when switching
				if ($old_congress_id > 0 && $old_congress_id !== $congress_id) {
					$old_ids = array_values(array_diff(array_map('intval', $repo->getProductIds($old_congress_id)), [$id]));
					$repo->setProducts($old_congress_id, $old_ids);
				}

				if ($congress_id > 0) {
					update_post_meta($id, '_saso_et_congress_id', $congress_id);
					$current_ids = array_map('intval', $repo->getProductIds($congress_id));
					if (!in_array($id, $current_ids, true)) {
						$current_ids[] = $id;
						$repo->setProducts($congress_id, $current_ids);
					}
				} else {
					delete_post_meta($id, '_saso_et_congress_id');
				}
			}
		}

		/**
		 * Add fields to product variations
		 *
		 * @param int $loop Loop index
		 * @param array $variation_data Variation data
		 * @param WP_Post $variation Variation post object
		 * @return void
		 */
		public function woocommerce_product_after_variable_attributes($loop, array $variation_data, $variation): void {
		$loop = intval($loop);
			// Render ticket fields only when the parent variable product is a ticket.
			$parent_id = isset($variation->post_parent) ? (int) $variation->post_parent : 0;
			if ($parent_id < 1 || !$this->isTicketByProductId($parent_id)) {
				return;
			}
			echo '<div class="form-row form-row-full form-field">';
			woocommerce_wp_checkbox(
				array(
					'id'          => '_saso_eventtickets_is_not_ticket[' . $loop . ']',
					'label'       => __('This variation is NOT a ticket product', 'event-tickets-with-ticket-scanner'),
					'desc_tip'    => 'true',
					'description' => __('This allows you to exclude a variation to be a ticket', 'event-tickets-with-ticket-scanner'),
					'value'       => get_post_meta($variation->ID, self::META_VARIATION_NOT_TICKET, true)
				)
			);
			echo '<div style="border-left: 5px solid #b225cb;padding-left:30px;margin-left:16px;">';
			woocommerce_wp_text_input([
				'id'                => 'saso_eventtickets_ticket_start_date[' . $loop . ']',
				'value'             => get_post_meta($variation->ID, 'saso_eventtickets_ticket_start_date', true),
				'label'             => __('Start date event', 'event-tickets-with-ticket-scanner'),
				'type'              => 'date',
				'custom_attributes' => ['data-type' => 'date'],
				'description'       => __('Set this to have this printed on the ticket and prevent too early redeemed tickets. Tickets can be redeemed from that day on.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'          => true
			]);
			woocommerce_wp_text_input([
				'id'          => 'saso_eventtickets_ticket_start_time[' . $loop . ']',
				'value'       => get_post_meta($variation->ID, 'saso_eventtickets_ticket_start_time', true),
				'label'       => __('Start time', 'event-tickets-with-ticket-scanner'),
				'type'        => 'time',
				'description' => __('Set this to have this printed on the ticket.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    => true
			]);
			woocommerce_wp_text_input([
				'id'                => 'saso_eventtickets_ticket_end_date[' . $loop . ']',
				'value'             => get_post_meta($variation->ID, 'saso_eventtickets_ticket_end_date', true),
				'label'             => __('End date event', 'event-tickets-with-ticket-scanner'),
				'type'              => 'date',
				'custom_attributes' => ['data-type' => 'date'],
				'description'       => __('Set this to have this printed on the ticket and prevent later the ticket to be still valid. Tickets cannot be redeemed after that day.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'          => true
			]);
			woocommerce_wp_text_input([
				'id'          => 'saso_eventtickets_ticket_end_time[' . $loop . ']',
				'value'       => get_post_meta($variation->ID, 'saso_eventtickets_ticket_end_time', true),
				'label'       => __('End time', 'event-tickets-with-ticket-scanner'),
				'type'        => 'time',
				'description' => __('Set this to have this printed on the ticket.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'    => true
			]);
			echo '</div>';
			echo '</div>';

			echo '<div class="options_group">';
			$saso_eventtickets_ticket_amount_per_item = intval(get_post_meta($variation->ID, 'saso_eventtickets_ticket_amount_per_item', true));
			if ($saso_eventtickets_ticket_amount_per_item < 1) $saso_eventtickets_ticket_amount_per_item = 1;
			woocommerce_wp_text_input([
				'id'                => 'saso_eventtickets_ticket_amount_per_item[' . $loop . ']',
				'value'             => $saso_eventtickets_ticket_amount_per_item,
				'label'             => __('Amount of ticket numbers per item sale', 'event-tickets-with-ticket-scanner'),
				'type'              => 'number',
				'custom_attributes' => ['step' => '1', 'min' => '1'],
				'description'       => __('How many ticket number to assign if one product is sold?', 'event-tickets-with-ticket-scanner'),
				'desc_tip'          => true
			]);

			// Seating Plan Override for Variation
			$seating = $this->MAIN->getSeating();
			$metaVariationSeatingplan = $seating->getMetaVariationSeatingplan();
			$seatingPlans = $seating->getPlanManager()->getDropdownOptions(true);
			$seatingPlans = ['' => __('-- Use Parent Product Setting --', 'event-tickets-with-ticket-scanner')] + $seatingPlans;
			$variation_seating_id = get_post_meta($variation->ID, $metaVariationSeatingplan, true);

			woocommerce_wp_select([
				'id'            => $metaVariationSeatingplan . '[' . $loop . ']',
				'name'          => $metaVariationSeatingplan . '[' . $loop . ']',
				'value'         => $variation_seating_id ?: '',
				'label'         => __('Seating Plan Override', 'event-tickets-with-ticket-scanner'),
				'options'       => $seatingPlans,
				'description'   => __('Override the parent product seating plan for this variation.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'      => true,
				'wrapper_class' => 'form-row form-row-full'
			]);
			echo '</div>';

			// Name per ticket override for variation
			echo '<hr style="margin:12px 0">';
			echo '<div class="options_group">';
			echo '<p style="padding:0 12px;font-weight:600;font-size:12px;color:#666;text-transform:uppercase;letter-spacing:0.5px">' . esc_html__('Name per ticket (text input)', 'event-tickets-with-ticket-scanner') . '</p>';
			$inherit_label = __('-- Inherit from parent product --', 'event-tickets-with-ticket-scanner');
			woocommerce_wp_select([
				'id'            => 'saso_eventtickets_request_name_per_ticket[' . $loop . ']',
				'name'          => 'saso_eventtickets_request_name_per_ticket[' . $loop . ']',
				'value'         => get_post_meta($variation->ID, 'saso_eventtickets_request_name_per_ticket', true) ?: '',
				'label'         => __('Request name per ticket', 'event-tickets-with-ticket-scanner'),
				'options'       => ['' => $inherit_label, 'yes' => __('Yes', 'event-tickets-with-ticket-scanner'), 'no' => __('No', 'event-tickets-with-ticket-scanner')],
				'desc_tip'      => true,
				'description'   => __('Override: Ask for a name/text input per ticket.', 'event-tickets-with-ticket-scanner'),
				'wrapper_class' => 'form-row form-row-full'
			]);
			woocommerce_wp_text_input([
				'id'            => 'saso_eventtickets_request_name_per_ticket_label[' . $loop . ']',
				'value'         => get_post_meta($variation->ID, 'saso_eventtickets_request_name_per_ticket_label', true),
				'label'         => __('Name field label', 'event-tickets-with-ticket-scanner'),
				'description'   => __('Override label. Leave empty to inherit from parent.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'      => true,
				'wrapper_class' => 'form-row form-row-full'
			]);
			woocommerce_wp_select([
				'id'            => 'saso_eventtickets_request_name_per_ticket_mandatory[' . $loop . ']',
				'name'          => 'saso_eventtickets_request_name_per_ticket_mandatory[' . $loop . ']',
				'value'         => get_post_meta($variation->ID, 'saso_eventtickets_request_name_per_ticket_mandatory', true) ?: '',
				'label'         => __('Name field mandatory', 'event-tickets-with-ticket-scanner'),
				'options'       => ['' => $inherit_label, 'yes' => __('Yes', 'event-tickets-with-ticket-scanner'), 'no' => __('No', 'event-tickets-with-ticket-scanner')],
				'wrapper_class' => 'form-row form-row-full'
			]);
			echo '</div>';

			// Value per ticket (dropdown) override for variation
			echo '<hr style="margin:12px 0">';
			echo '<div class="options_group">';
			echo '<p style="padding:0 12px;font-weight:600;font-size:12px;color:#666;text-transform:uppercase;letter-spacing:0.5px">' . esc_html__('Value per ticket (dropdown)', 'event-tickets-with-ticket-scanner') . '</p>';
			woocommerce_wp_select([
				'id'            => 'saso_eventtickets_request_value_per_ticket[' . $loop . ']',
				'name'          => 'saso_eventtickets_request_value_per_ticket[' . $loop . ']',
				'value'         => get_post_meta($variation->ID, 'saso_eventtickets_request_value_per_ticket', true) ?: '',
				'label'         => __('Request dropdown per ticket', 'event-tickets-with-ticket-scanner'),
				'options'       => ['' => $inherit_label, 'yes' => __('Yes', 'event-tickets-with-ticket-scanner'), 'no' => __('No', 'event-tickets-with-ticket-scanner')],
				'desc_tip'      => true,
				'description'   => __('Override: Ask for a dropdown selection per ticket.', 'event-tickets-with-ticket-scanner'),
				'wrapper_class' => 'form-row form-row-full'
			]);
			woocommerce_wp_text_input([
				'id'            => 'saso_eventtickets_request_value_per_ticket_label[' . $loop . ']',
				'value'         => get_post_meta($variation->ID, 'saso_eventtickets_request_value_per_ticket_label', true),
				'label'         => __('Dropdown label', 'event-tickets-with-ticket-scanner'),
				'description'   => __('Override label. Leave empty to inherit from parent.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'      => true,
				'wrapper_class' => 'form-row form-row-full'
			]);
			woocommerce_wp_textarea_input([
				'id'            => 'saso_eventtickets_request_value_per_ticket_values[' . $loop . ']',
				'value'         => get_post_meta($variation->ID, 'saso_eventtickets_request_value_per_ticket_values', true),
				'label'         => __('Dropdown values', 'event-tickets-with-ticket-scanner'),
				'description'   => __('One value per line. Leave empty to inherit from parent.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'      => true,
				'wrapper_class' => 'form-row form-row-full'
			]);
			woocommerce_wp_text_input([
				'id'            => 'saso_eventtickets_request_value_per_ticket_def[' . $loop . ']',
				'value'         => get_post_meta($variation->ID, 'saso_eventtickets_request_value_per_ticket_def', true),
				'label'         => __('Dropdown default value', 'event-tickets-with-ticket-scanner'),
				'description'   => __('Override default. Leave empty to inherit from parent.', 'event-tickets-with-ticket-scanner'),
				'desc_tip'      => true,
				'wrapper_class' => 'form-row form-row-full'
			]);
			woocommerce_wp_select([
				'id'            => 'saso_eventtickets_request_value_per_ticket_mandatory[' . $loop . ']',
				'name'          => 'saso_eventtickets_request_value_per_ticket_mandatory[' . $loop . ']',
				'value'         => get_post_meta($variation->ID, 'saso_eventtickets_request_value_per_ticket_mandatory', true) ?: '',
				'label'         => __('Dropdown mandatory', 'event-tickets-with-ticket-scanner'),
				'options'       => ['' => $inherit_label, 'yes' => __('Yes', 'event-tickets-with-ticket-scanner'), 'no' => __('No', 'event-tickets-with-ticket-scanner')],
				'wrapper_class' => 'form-row form-row-full'
			]);
			echo '</div>';
			echo '<hr style="margin:12px 0">';

			if ($this->MAIN->isPremium() && method_exists($this->MAIN->getPremiumFunctions(), 'woocommerce_product_after_variable_attributes')) {
				$this->MAIN->getPremiumFunctions()->woocommerce_product_after_variable_attributes($loop, $variation_data, $variation);
			}
			echo "<hr>";
		}

		/**
		 * Save product variation meta
		 *
		 * @param int $variation_id Variation ID
		 * @param int $i Loop index
		 * @return void
		 */
		public function woocommerce_save_product_variation($variation_id, $i): void {
		$variation_id = intval($variation_id);
		$i = intval($i);
			$R = SASO_EVENTTICKETS::getRequest();

			// Checkbox - is NOT a ticket
			$key = self::META_VARIATION_NOT_TICKET;
			if (isset($R[$key]) && isset($R[$key][$i])) {
				update_post_meta($variation_id, $key, 'yes');
			} else {
				delete_post_meta($variation_id, $key);
			}

			// Text input fields - dates and times
			$keys = [
				'saso_eventtickets_ticket_start_date',
				'saso_eventtickets_ticket_start_time',
				'saso_eventtickets_ticket_end_date',
				'saso_eventtickets_ticket_end_time'
			];
			foreach ($keys as $key) {
				if (isset($R[$key]) && isset($R[$key][$i])) {
					update_post_meta($variation_id, $key, sanitize_text_field($R[$key][$i]));
				} else {
					delete_post_meta($variation_id, $key);
				}
			}

			// Number field - ticket amount per item
			$key = 'saso_eventtickets_ticket_amount_per_item';
			if (isset($R[$key]) && isset($R[$key][$i])) {
				$value = intval($R[$key][$i]);
				if ($value < 1) $value = 1;
				update_post_meta($variation_id, $key, $value);
			} else {
				delete_post_meta($variation_id, $key);
			}

			// Seating Plan Override for Variation
			$seating_key = $this->MAIN->getSeating()->getMetaVariationSeatingplan();
			if (isset($_POST[$seating_key]) && isset($_POST[$seating_key][$i])) {
				$value = intval($_POST[$seating_key][$i]);
				if ($value > 0) {
					update_post_meta($variation_id, $seating_key, $value);
				} else {
					delete_post_meta($variation_id, $seating_key);
				}
			} else {
				delete_post_meta($variation_id, $seating_key);
			}

			// Name/value per ticket overrides (select: yes/no/empty=inherit)
			$select_keys = [
				'saso_eventtickets_request_name_per_ticket',
				'saso_eventtickets_request_name_per_ticket_mandatory',
				'saso_eventtickets_request_value_per_ticket',
				'saso_eventtickets_request_value_per_ticket_mandatory'
			];
			foreach ($select_keys as $key) {
				if (isset($_POST[$key]) && isset($_POST[$key][$i]) && !empty($_POST[$key][$i])) {
					update_post_meta($variation_id, $key, sanitize_text_field($_POST[$key][$i]));
				} else {
					delete_post_meta($variation_id, $key);
				}
			}
			// Text/textarea overrides (empty = inherit from parent)
			$text_keys = [
				'saso_eventtickets_request_name_per_ticket_label',
				'saso_eventtickets_request_value_per_ticket_label',
				'saso_eventtickets_request_value_per_ticket_values',
				'saso_eventtickets_request_value_per_ticket_def'
			];
			foreach ($text_keys as $key) {
				if (isset($_POST[$key]) && isset($_POST[$key][$i]) && trim($_POST[$key][$i]) !== '') {
					update_post_meta($variation_id, $key, sanitize_textarea_field($_POST[$key][$i]));
				} else {
					delete_post_meta($variation_id, $key);
				}
			}

			// Premium extension point
			if ($this->MAIN->isPremium() && method_exists($this->MAIN->getPremiumFunctions(), 'woocommerce_save_product_variation')) {
				$this->MAIN->getPremiumFunctions()->woocommerce_save_product_variation($variation_id, $i);
			}
		}

		/**
		 * Add custom columns to product list
		 *
		 * @param array $columns Existing columns
		 * @return array Modified columns
		 */
		public function manage_edit_product_columns(array $columns): array {
			$new_columns = (is_array($columns)) ? $columns : [];
			$new_columns['SASO_EVENTTICKETS_LIST_COLUMN'] = _x('Ticket List', 'label', 'event-tickets-with-ticket-scanner');
			return $new_columns;
		}

		/**
		 * Display custom column content
		 *
		 * @param string $column Column name
		 * @return void
		 */
		public function manage_product_posts_custom_column(string $column): void {
			global $post;

			if ($column == 'SASO_EVENTTICKETS_LIST_COLUMN') {
				$code_list_ids = get_post_meta($post->ID, 'saso_eventtickets_list', true);

				$lists = $this->MAIN->getAdmin()->getLists();
				$dropdown_list = array('' => '-');
				foreach ($lists as $key => $list) {
					$dropdown_list[$list['id']] = $list['name'];
				}

				if (isset($code_list_ids) && !empty($code_list_ids)) {
					echo !empty($dropdown_list[$code_list_ids]) ? esc_html($dropdown_list[$code_list_ids]) : '-';
				} else {
					echo "-";
				}
			}
		}

		/**
		 * Make custom columns sortable
		 *
		 * @param array $columns Existing sortable columns
		 * @return array Modified sortable columns
		 */
		public function manage_edit_product_sortable_columns(array $columns): array {
			$custom = [
				'SASO_EVENTTICKETS_LIST_COLUMN' => 'saso_eventtickets_list'
			];
			return wp_parse_args($custom, $columns);
		}

		/**
		 * Add meta box to product edit page sidebar
		 *
		 * Displays action buttons for downloading event flyers, ICS files, and ticket information.
		 *
		 * @return void
		 */
		public function wc_product_display_side_box(): void {
			?>
			<p>Download Event Flyer</p>
			<button disabled data-id="<?php echo esc_attr($this->MAIN->getPrefix()."btn_download_flyer"); ?>" class="button button-primary">Download Event Flyer</button>
			<p>Download ICS File (cal file)</p>
			<button disabled data-id="<?php echo esc_attr($this->MAIN->getPrefix()."btn_download_ics"); ?>" class="button button-primary">Download ICS File</button>
			<p>Display all Tickets Infos</p>
			<button disabled data-id="<?php echo esc_attr($this->MAIN->getPrefix()."btn_download_ticket_infos"); ?>" class="button button-primary">Print Ticket Infos</button>
			<?php
			// Calendar button — show for all ticket products, but only enable for daychooser
			$product_id = isset($_GET['post']) ? intval($_GET['post']) : 0;
			$is_daychooser = $product_id > 0 && get_post_meta($product_id, self::META_PRODUCT_IS_DAYCHOOSER, true) === 'yes';
			?>
			<p><?php esc_html_e('Sold Tickets Calendar', 'event-tickets-with-ticket-scanner'); ?></p>
			<?php if ($is_daychooser) : ?>
				<button disabled data-id="<?php echo esc_attr($this->MAIN->getPrefix()."btn_product_calendar"); ?>" class="button button-primary"><?php esc_html_e('View Calendar', 'event-tickets-with-ticket-scanner'); ?></button>
				<button disabled data-id="<?php echo esc_attr($this->MAIN->getPrefix()."btn_product_calendar_print"); ?>" class="button"><?php esc_html_e('Print List', 'event-tickets-with-ticket-scanner'); ?></button>
			<?php else : ?>
				<button disabled class="button" style="opacity:0.5;cursor:default;"><?php esc_html_e('View Calendar', 'event-tickets-with-ticket-scanner'); ?></button>
				<button disabled class="button" style="opacity:0.5;cursor:default;"><?php esc_html_e('Print List', 'event-tickets-with-ticket-scanner'); ?></button>
				<p><small style="color:#999;"><?php esc_html_e('Calendar is available for date picker products. For all products use the Attendance tab.', 'event-tickets-with-ticket-scanner'); ?></small></p>
			<?php endif;
			do_action( $this->MAIN->_do_action_prefix.'wc_product_display_side_box', [] );
		}

		/**
		 * Download ticket information for a product
		 *
		 * Retrieves all ticket codes associated with a product and returns
		 * product information along with ticket details.
		 *
		 * @param array $data Request data containing product_id
		 * @return array Ticket information and product data
		 */
		public function downloadTicketInfosOfProduct(array $data): array {
			$product_id = intval($data['product_id']);
			$daten = [];
			$product = [];
			if ($product_id > 0) {
				$daten = $this->MAIN->getAdmin()->getCodesByProductId($product_id);
				$productObj = wc_get_product($product_id);
				if ($productObj != null) {
					$product['name'] = $productObj->get_name();
				}
			}
			return ['ticket_infos' => $daten, 'product' => $product];
		}

		/**
		 * Download all tickets for an order as one PDF
		 *
		 * Generates and outputs a combined PDF containing all tickets for an order.
		 * Note: Despite the name suggesting product scope, this method operates on orders.
		 *
		 * @param array $data Request data containing order_id
		 * @param string $filemode File output mode (I=inline, D=download, F=file)
		 * @return void Exits after output
		 */
		public function downloadAllTicketsAsOnePDF(array $data, string $filemode = "I"): void {
			$order_id = intval($data['order_id']);
			if ($order_id > 0) {
				$order = wc_get_order($order_id);
				$ticketHandler = $this->MAIN->getTicketHandler();
				$ticketHandler->outputPDFTicketsForOrder($order);
				exit;
			} else {
				echo "ORDER ID IS WRONG";
				exit;
			}
		}

		/**
		 * Download event flyer PDF for a product
		 *
		 * Generates a PDF flyer with event details including:
		 * - Banner image
		 * - Event title and date
		 * - Location
		 * - QR code linking to product page
		 * - Price (optional)
		 * - Blog information (optional)
		 * - Logo (optional)
		 *
		 * @param array $data Request data containing product_id
		 * @throws Exception If product_id is missing
		 * @return void
		 */
		public function downloadFlyer(array $data): void {
			if (!isset($data['product_id'])) {
				throw new Exception("#6001 " . esc_html__("Product Id for the event flyer is missing", 'event-tickets-with-ticket-scanner'));
			}
			$product_id = intval($data['product_id']);

			$pdf = $this->MAIN->getNewPDFObject();

			// Load product
			$product = wc_get_product($product_id);
			$titel = $product->get_name();
			$short_desc = $product->get_short_description();
			$location = trim(get_post_meta($product_id, 'saso_eventtickets_event_location', true));

			$dateAsString = $this->MAIN->getTicketHandler()->displayTicketDateAsString($product_id);
			$event_date = "";
			if (!empty($dateAsString)) {
				$event_date = '<br><p style="text-align:center;">';
				$event_date .= $dateAsString;
				$event_date .= '</p>';
			}

			$event_url = get_permalink($product->get_id());

			$pdf->setFilemode('I');

			// Add banner image if configured
			$this->addFlyerBanner($pdf);

			// Add title
			$pdf->addPart('<h1 style="text-align:center;">' . esc_html($titel) . '</h1>');

			// Add event date
			if (!empty($event_date)) {
				$pdf->addPart($event_date);
			}

			// Add location
			if (!empty($location)) {
				$pdf->addPart('<p>' . wp_kses_post($this->MAIN->getOptions()->getOptionValue("wcTicketTransLocation")) . " <b>" . wp_kses_post($location) . "</b></p>");
			}

			// Add QR code placeholder and URL
			$pdf->addPart('{QRCODE_INLINE}');
			$pdf->addPart('<br><p style="font-size:9pt;text-align:center;">' . esc_url($event_url) . '</p>');

			// Add short description
			$pdf->addPart('<br><p style="text-align:center;">' . wp_kses_post($short_desc) . '</p>');

			// Add price if enabled
			if (!$this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketFlyerDontDisplayPrice')) {
				$pdf->addPart('<br><br><p style="text-align:center;font-size:18pt;">' . wc_price($product->get_price(), ['decimals' => 2]) . '</p>');
			}

			// Add blog name if enabled
			$wcTicketFlyerDontDisplayBlogName = $this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketFlyerDontDisplayBlogName');
			if (!$wcTicketFlyerDontDisplayBlogName) {
				$pdf->addPart('<br><br><div style="text-align:center;font-size:10pt;"><b>' . get_bloginfo("name") . '</b></div>');
			}

			// Add blog description if enabled
			if (!$this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketFlyerDontDisplayBlogDesc')) {
				if ($wcTicketFlyerDontDisplayBlogName) {
					$pdf->addPart('<br>');
				}
				$pdf->addPart('<div style="text-align:center;font-size:10pt;">' . get_bloginfo("description") . '</div>');
			}

			// Add blog URL if enabled
			if (!$this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketFlyerDontDisplayBlogURL')) {
				$pdf->addPart('<br><div style="text-align:center;font-size:10pt;">' . site_url() . '</div>');
			}

			// Add logo if configured
			$this->addFlyerLogo($pdf);

			// Add plugin credit
			$pdf->addPart('<br><p style="text-align:center;font-size:9pt;">powered by Event Tickets With Ticket Scanner Plugin for Wordpress</p>');

			// Configure QR code
			$pdf->setQRParams(['style' => ['position' => 'C'], 'align' => 'N']);
			$qrTicketPDFPadding = intval($this->MAIN->getOptions()->getOptionValue('qrTicketPDFPadding'));
			$pdf->setQRCodeContent(["text" => $event_url, "style" => ["vpadding" => $qrTicketPDFPadding, "hpadding" => $qrTicketPDFPadding]]);

			// Add background image if configured
			$this->addFlyerBackground($pdf);

			$pdf->render();
			exit;
		}

		/**
		 * Add banner image to flyer PDF
		 *
		 * @param object $pdf PDF object
		 * @return void
		 */
		private function addFlyerBanner($pdf): void {
			$wcTicketFlyerBanner = $this->MAIN->getOptions()->getOptionValue('wcTicketFlyerBanner');
			if (empty($wcTicketFlyerBanner) || intval($wcTicketFlyerBanner) <= 0) {
				return;
			}

			$mediaData = SASO_EVENTTICKETS::getMediaData($wcTicketFlyerBanner);
			$has_banner = false;

			if ($this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketCompatibilityUseURL')) {
				if (!empty($mediaData['url'])) {
					$pdf->addPart('<div style="text-align:center;"><img src="' . $mediaData['url'] . '"></div>');
					$has_banner = true;
				}
			} else {
				if (!empty($mediaData['for_pdf'])) {
					$pdf->addPart('<div style="text-align:center;"><img src="' . $mediaData['for_pdf'] . '"></div>');
					$has_banner = true;
				}
			}

			if ($has_banner) {
				if (isset($mediaData['meta']) && isset($mediaData['meta']['height']) && floatval($mediaData['meta']['height']) > 0) {
					$dpiY = 96;
					if (function_exists("getimagesize")) {
						$imageInfo = getimagesize($mediaData['location']);
						$dpiY = isset($imageInfo['dpi_y']) ? $imageInfo['dpi_y'] : $dpiY;
					}
					$units = $pdf->convertPixelIntoMm($mediaData['meta']['height'] + 10, $dpiY);
					$pdf->setQRParams(['pos' => ['y' => $units]]);
				}
			}
		}

		/**
		 * Add logo image to flyer PDF
		 *
		 * @param object $pdf PDF object
		 * @return void
		 */
		private function addFlyerLogo($pdf): void {
			$wcTicketFlyerLogo = $this->MAIN->getOptions()->getOptionValue('wcTicketFlyerLogo');
			if (empty($wcTicketFlyerLogo) || intval($wcTicketFlyerLogo) <= 0) {
				return;
			}

			$option_wcTicketFlyerLogo = $this->MAIN->getOptions()->getOption('wcTicketFlyerLogo');
			$mediaData = SASO_EVENTTICKETS::getMediaData($wcTicketFlyerLogo);
			$width = "200";

			if (isset($option_wcTicketFlyerLogo['additional']) && isset($option_wcTicketFlyerLogo['additional']['max']) && isset($option_wcTicketFlyerLogo['additional']['max']['width'])) {
				$width = $option_wcTicketFlyerLogo['additional']['max']['width'];
			}

			if ($this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketCompatibilityUseURL')) {
				if (!empty($mediaData['url'])) {
					$pdf->addPart('<br><br><p style="text-align:center;"><img width="' . $width . '" src="' . $mediaData['url'] . '"></p>');
				}
			} else {
				if (!empty($mediaData['for_pdf'])) {
					$pdf->addPart('<br><br><p style="text-align:center;"><img width="' . $width . '" src="' . $mediaData['for_pdf'] . '"></p>');
				}
			}
		}

		/**
		 * Add background image and color to flyer PDF
		 *
		 * @param object $pdf PDF object
		 * @return void
		 */
		private function addFlyerBackground($pdf): void {
			// Background color (as fallback when no image or to fill gaps)
			$wcTicketPDFBackgroundColor = $this->MAIN->getOptions()->getOptionValue('wcTicketPDFBackgroundColor');
			if (!empty($wcTicketPDFBackgroundColor)) {
				$pdf->setBackgroundColor($wcTicketPDFBackgroundColor);
			}

			$wcTicketFlyerBG = $this->MAIN->getOptions()->getOptionValue('wcTicketFlyerBG');
			if (empty($wcTicketFlyerBG) || intval($wcTicketFlyerBG) <= 0) {
				return;
			}

			$mediaData = SASO_EVENTTICKETS::getMediaData($wcTicketFlyerBG);
			if ($this->MAIN->getOptions()->isOptionCheckboxActive('wcTicketCompatibilityUseURL')) {
				if (!empty($mediaData['url'])) {
					$pdf->setBackgroundImage($mediaData['url']);
				}
			} else {
				if (!empty($mediaData['for_pdf'])) {
					$pdf->setBackgroundImage($mediaData['for_pdf']);
				}
			}
		}

		// ── Product Calendar (#191) ─────────────────────────────────

		/**
		 * Get aggregated ticket counts per date for a daychooser product.
		 *
		 * @param array $data {product_id: int}
		 * @return array{dates: array<string,int>, product_id: int, is_daychooser: bool}
		 * @throws Exception If product_id is invalid (#7001)
		 */
		/**
		 * Get all daychooser products and their sold ticket counts grouped by date.
		 * Used by the Attendance → Sold Tickets tab.
		 *
		 * @param array $data {date_from: string, date_to: string, exclude_products: string (comma-separated IDs)}
		 * @return array {products: [...], dates: {date => [{product_id, product_name, count}]}}
		 */
		public function getAllDaychooserCalendarData(array $data): array {
			$dateFrom = isset($data['date_from']) ? sanitize_text_field($data['date_from']) : wp_date('Y-m-d');
			$dateTo = isset($data['date_to']) ? sanitize_text_field($data['date_to']) : wp_date('Y-m-d');
			$excludeIds = [];
			if (!empty($data['exclude_products'])) {
				$excludeIds = array_map('intval', explode(',', sanitize_text_field($data['exclude_products'])));
			}

			// Get all daychooser products
			$args = [
				'post_type' => 'product',
				'posts_per_page' => -1,
				'post_status' => 'publish',
				'meta_query' => [
					['key' => self::META_PRODUCT_IS_DAYCHOOSER, 'value' => 'yes'],
					['key' => self::META_PRODUCT_IS_TICKET, 'value' => 'yes'],
				],
				'fields' => 'ids',
			];
			$product_ids = get_posts($args);

			$products = [];
			foreach ($product_ids as $pid) {
				$products[] = [
					'id' => $pid,
					'name' => get_the_title($pid),
					'excluded' => in_array($pid, $excludeIds),
				];
			}

			// Filter out excluded
			$activeIds = array_diff($product_ids, $excludeIds);
			if (empty($activeIds)) {
				return ['products' => $products, 'dates' => []];
			}

			$db = $this->MAIN->getDB();
			$codes_table = $db->getTabelle('codes');
			$idList = implode(',', array_map('intval', $activeIds));

			$rows = $db->_db_datenholen(
				"SELECT JSON_UNQUOTE(JSON_EXTRACT(meta, '$.wc_ticket.day_per_ticket')) AS ticket_date,
				        JSON_UNQUOTE(JSON_EXTRACT(meta, '$.woocommerce.product_id')) AS product_id,
				        COUNT(*) AS ticket_count
				 FROM {$codes_table}
				 WHERE aktiv = 1
				   AND JSON_EXTRACT(meta, '$.wc_ticket.is_daychooser') = 1
				   AND JSON_EXTRACT(meta, '$.woocommerce.product_id') IN ({$idList})
				   AND JSON_UNQUOTE(JSON_EXTRACT(meta, '$.wc_ticket.day_per_ticket')) BETWEEN '" . esc_sql($dateFrom) . "' AND '" . esc_sql($dateTo) . "'
				 GROUP BY ticket_date, product_id
				 ORDER BY ticket_date"
			);

			$dates = [];
			foreach ($rows as $row) {
				if (!empty($row['ticket_date']) && $row['ticket_date'] !== 'null') {
					$d = $row['ticket_date'];
					if (!isset($dates[$d])) $dates[$d] = [];
					$pid = (int)$row['product_id'];
					$dates[$d][] = [
						'product_id' => $pid,
						'product_name' => get_the_title($pid),
						'count' => (int)$row['ticket_count'],
					];
				}
			}

			return [
				'products' => $products,
				'dates' => $dates,
				'date_from' => $dateFrom,
				'date_to' => $dateTo,
			];
		}

		public function getProductCalendarData(array $data): array {
			$product_id = isset($data['product_id']) ? (int)$data['product_id'] : 0;
			if ($product_id <= 0) {
				throw new Exception('#7001 ' . esc_html__('invalid product_id', 'event-tickets-with-ticket-scanner'));
			}

			$is_daychooser = get_post_meta($product_id, self::META_PRODUCT_IS_DAYCHOOSER, true) === 'yes';
			if (!$is_daychooser) {
				return ['dates' => [], 'product_id' => $product_id, 'is_daychooser' => false];
			}

			$db          = $this->MAIN->getDB();
			$codes_table = $db->getTabelle('codes');

			$rows = $db->_db_datenholen_prepared(
				"SELECT JSON_UNQUOTE(JSON_EXTRACT(meta, '$.wc_ticket.day_per_ticket')) AS ticket_date,
				        COUNT(*) AS ticket_count
				 FROM {$codes_table}
				 WHERE aktiv = 1
				   AND JSON_EXTRACT(meta, '$.wc_ticket.is_daychooser') = 1
				   AND JSON_EXTRACT(meta, '$.woocommerce.product_id') = %d
				 GROUP BY ticket_date
				 ORDER BY ticket_date",
				[$product_id]
			);

			$dates = [];
			foreach ($rows as $row) {
				if (!empty($row['ticket_date']) && $row['ticket_date'] !== 'null') {
					$dates[$row['ticket_date']] = (int)$row['ticket_count'];
				}
			}

			return [
				'dates'         => $dates,
				'product_id'    => $product_id,
				'is_daychooser' => true,
			];
		}

		/**
		 * Get individual ticket codes for a product + date combination (calendar drill-down).
		 *
		 * @param array $data {product_id: int, date: string (Y-m-d)}
		 * @return array List of ticket detail arrays
		 * @throws Exception If product_id (#7002) or date (#7003) is invalid
		 */
		public function getProductCalendarDetails(array $data): array {
			$product_id = isset($data['product_id']) ? (int)$data['product_id'] : 0;
			$date       = isset($data['date']) ? sanitize_text_field($data['date']) : '';

			if ($product_id <= 0) {
				throw new Exception('#7002 ' . esc_html__('invalid product_id', 'event-tickets-with-ticket-scanner'));
			}
			if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date)) {
				throw new Exception('#7003 ' . esc_html__('invalid date format — expected Y-m-d', 'event-tickets-with-ticket-scanner'));
			}

			$db          = $this->MAIN->getDB();
			$codes_table = $db->getTabelle('codes');

			$rows = $db->_db_datenholen_prepared(
				"SELECT id, code, code_display, order_id, redeemed, meta
				 FROM {$codes_table}
				 WHERE aktiv = 1
				   AND JSON_EXTRACT(meta, '$.wc_ticket.is_daychooser') = 1
				   AND JSON_EXTRACT(meta, '$.woocommerce.product_id') = %d
				   AND JSON_UNQUOTE(JSON_EXTRACT(meta, '$.wc_ticket.day_per_ticket')) = %s
				 ORDER BY id ASC",
				[$product_id, $date]
			);

			$results = [];
			foreach ($rows as $row) {
				$meta      = @json_decode($row['meta'], true);
				$wc_ticket = is_array($meta) ? ($meta['wc_ticket'] ?? []) : [];

				$results[] = [
					'code'             => $row['code'],
					'code_display'     => $row['code_display'] ?: $row['code'],
					'order_id'         => (int)$row['order_id'],
					'redeemed'         => (int)$row['redeemed'],
					'public_ticket_id' => $wc_ticket['_public_ticket_id'] ?? '',
				];
			}

			return $results;
		}
	}
}
