<?php
/**
 * Sunflower Event handling
 *
 * @package Sunflower 26
 */

/**
 * Get the event metadata.
 */
function sunflower_get_event_fields() {
	return array(
		'_sunflower_event_from'            => array( __( 'Startdate', 'sunflower' ), 'datetimepicker' ),
		'_sunflower_event_until'           => array( __( 'Enddate', 'sunflower' ), 'datetimepicker' ),
		'_sunflower_event_whole_day'       => array( __( 'Whole day', 'sunflower' ), null, 'checkbox' ),
		'_sunflower_event_location_name'   => array( __( 'Location name', 'sunflower' ) ),
		'_sunflower_event_location_street' => array( __( 'Street', 'sunflower' ) ),
		'_sunflower_event_location_city'   => array( __( 'City', 'sunflower' ) ),
		'_sunflower_event_webinar'         => array( __( 'Webinar', 'sunflower' ) ),
		'_sunflower_event_organizer'       => array( __( 'Organizer', 'sunflower' ) ),
		'_sunflower_event_organizer_url'   => array( __( 'Organizer URL', 'sunflower' ) ),
		'_sunflower_event_lat'             => array( 'Latitude', null, 'hidden' ),
		'_sunflower_event_lon'             => array( 'Longitude', null, 'hidden' ),
		'_sunflower_event_zoom'            => array( 'Zoom', null, 'hidden' ),
		'_sunflower_event_show_map'        => array( __( 'Show map on event', 'sunflower' ), null, 'checkbox', 'checked' ),
	);
}

/**
 * Create sunflower event post type.
 */
function sunflower_create_event_post_type() {

	$is_events_enabled = sunflower_get_setting( 'sunflower_events_enabled' );
	if ( ! $is_events_enabled ) {
		return;
	}

	// Get the events slug from settings or use default 'termine' for compatibility reasons.
	$events_slug = sunflower_get_setting( 'sunflower_events_slug' ) ? sunflower_get_setting( 'sunflower_events_slug' ) : 'termine';

	register_post_type(
		'sunflower_event',
		array(
			'labels'       => array(
				'name'          => __( 'Events', 'sunflower' ),
				'singular_name' => __( 'Event', 'sunflower' ),
			),
			'public'       => true,
			'menu_icon'    => 'dashicons-calendar',
			'has_archive'  => 'termine',
			'rewrite'      => array(
				'slug' => $events_slug,
			),
			'show_in_rest' => true,
			'supports'     => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' ),
		)
	);

	$sunflower_labels = array(
		'name'                       => _x( 'Tags', 'taxonomy general name', 'default' ),
		'singular_name'              => _x( 'Tag', 'taxonomy singular name', 'default' ),
		'search_items'               => __( 'Search Tags', 'default' ),
		'popular_items'              => __( 'Popular Tags', 'default' ),
		'all_items'                  => __( 'All Tags', 'default' ),
		'parent_item'                => null,
		'parent_item_colon'          => null,
		'edit_item'                  => __( 'Edit Tag', 'default' ),
		'update_item'                => __( 'Update Tag', 'default' ),
		'add_new_item'               => __( 'Add New Tag', 'default' ),
		'new_item_name'              => __( 'New Tag Name', 'default' ),
		'separate_items_with_commas' => __( 'Separate tags with commas', 'default' ),
		'add_or_remove_items'        => __( 'Add or remove tags', 'default' ),
		'choose_from_most_used'      => __( 'Choose from the most used tags', 'default' ),
		'menu_name'                  => __( 'Tags', 'default' ),
	);

	register_taxonomy(
		'sunflower_event_tag',
		'sunflower_event',
		array(
			'hierarchical'      => false,
			'labels'            => $sunflower_labels,
			'show_ui'           => true,
			'show_admin_column' => true,
			'show_in_rest'      => true,
		)
	);

	if ( get_option( 'sunflower_flush_rewrite_rules' ) ) {

		// Flush rewrite rules to make the new post type available immediately.
		flush_rewrite_rules();

		delete_option( 'sunflower_flush_rewrite_rules' );
	}
}

add_action( 'init', 'sunflower_create_event_post_type' );

/**
 * Add event meta box
 *
 * See https://developer.wordpress.org/reference/functions/add_meta_box for a full explanation of each property
 */
function sunflower_add_event_meta_boxes() {

	add_meta_box(
		'sunflower_event_meta_box', // div id containing rendered fields.
		__( 'Event', 'sunflower' ), // section heading displayed as text.
		'sunflower_event_meta_box', // callback function to render fields.
		'sunflower_event', // name of post type on which to render fields.
		'side', // location on the screen.
		'high' // placement priority.
	);
}

add_action( 'admin_init', 'sunflower_add_event_meta_boxes' );

/**
 * Save event meta data to database.
 */
function sunflower_save_event_meta_boxes() {
	global $post;

	if ( ! isset( $post->ID ) || empty( $_POST ) ) {
		return;
	}

	// Do not save, if nonce is invalid.
	if ( ! isset( $_POST['_wpnonce'] ) || ! wp_verify_nonce( $_POST['_wpnonce'], 'update-post_' . $post->ID ) ) {
		return;
	}

	// Ignore other post types.
	if ( 'sunflower_event' !== $post->post_type ) {
		return;
	}

	$sunflower_event_fields = sunflower_get_event_fields();

	if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
		return;
	}

	if ( get_post_status( $post->ID ) === 'auto-draft' ) {
		return;
	}

	$into_transients = array( '_sunflower_event_lat', '_sunflower_event_lon', '_sunflower_event_zoom' );

	$is_all_day = $_POST['_sunflower_event_whole_day'] ?? '';

	foreach ( $sunflower_event_fields as $id => $config ) {

		// In case of all day events the events end on midnight, next day. So we have to add one day on save.
		if ( '_sunflower_event_until' === $id && 'checked' === $is_all_day ) {
			$value = gmdate( 'Y-m-d', strtotime( sunflower_german_date2int_date( $_POST[ $id ] ) ) + 86400 );
		} elseif ( '_sunflower_event_from' === $id || '_sunflower_event_until' === $id ) {
			$value = sunflower_german_date2int_date( $_POST[ $id ] );
		} else {
			$value = $_POST[ $id ];
		}

		update_post_meta( $post->ID, $id, sanitize_text_field( $value ) );
		if ( ! in_array( $id, $into_transients, true ) ) {
			continue;
		}

		if ( ! $value ) {
			continue;
		}

		set_transient( $id, $value );
	}

	$sunflower_location = array();

	if ( $_POST['_sunflower_event_location_street'] ) {
		$sunflower_location[] = trim( $_POST['_sunflower_event_location_street'] );
	}

	if ( $_POST['_sunflower_event_location_city'] ) {
		$sunflower_location[] = trim( $_POST['_sunflower_event_location_city'] );
	}

	$sunflower_location_string = implode( ', ', $sunflower_location );

	if ( ! empty( $sunflower_location_string ) ) {
		$coordinates = sunflower_geocode( $sunflower_location_string );
		if ( $coordinates ) {
			[$lon, $lat] = $coordinates;
			update_post_meta( $post->ID, '_sunflower_event_lat', $lat );
			update_post_meta( $post->ID, '_sunflower_event_lon', $lon );
			$zoom = sunflower_get_constant( 'SUNFLOWER_EVENT_IMPORTED_ZOOM' ) ? sunflower_get_constant( 'SUNFLOWER_EVENT_IMPORTED_ZOOM' ) : 12;
			update_post_meta( $post->ID, '_sunflower_event_zoom', $zoom );
		}
	}
}

add_action( 'save_post', 'sunflower_save_event_meta_boxes', 10, 2 );

/**
 * Transform date from German format "DD.MM.YYYY HH:mm" to "YYYY-MM-DD HH:mm"
 *
 * @param string $german_date The date string in German format.
 */
function sunflower_german_date2int_date( $german_date ) {
	if ( ! $german_date ) {
		return '';
	}

	$date_array = preg_split( '/[^0-9]/', (string) $german_date );

	if ( count( $date_array ) === 5 ) {
		return sprintf( '%s-%s-%s %s:%s', $date_array[2], $date_array[1], $date_array[0], $date_array[3], $date_array[4] );
	} else {
		return sprintf( '%s-%s-%s', $date_array[2], $date_array[1], $date_array[0] );
	}
}

/**
 * Transform date format from "YYYY-MM-DD HH:mm" into German format "DD.MM.YYYY HH:mm"
 *
 * @param int $int_date The date in format international format.
 */
function sunflower_int_date2german_date( $int_date ) {
	if ( ! $int_date ) {
		return '';
	}

	$date_array = preg_split( '/[^0-9]/', (string) $int_date );

	if ( count( $date_array ) === 5 ) {
		return sprintf( '%s.%s.%s %s:%s', $date_array[2], $date_array[1], $date_array[0], $date_array[3], $date_array[4] );
	} else {
		return sprintf( '%s.%s.%s', $date_array[2], $date_array[1], $date_array[0] );
	}
}



/**
 * Render event meta box
 */
function sunflower_event_meta_box() {
	global $post;

	$sunflower_event_fields  = sunflower_get_event_fields();
	$sunflower_form_disabled = '';

	$custom = get_post_custom( $post->ID );
	$uid    = $custom['_sunflower_event_uid'][0] ?? false;

	if ( $uid ) {
		printf( '<div style="color:red">%s</div>', esc_html__( 'This event will be imported by remote ical-calendar. All changes here will be overwritten.', 'sunflower' ) );
		$sunflower_form_disabled = 'disabled';
		?>
		<script>
			jQuery( function() {
				window.setTimeout(() => {
					jQuery('.popover-slot').prepend('<div class="sunflower-admin-hint">Dies ist ein importierter Termin.<br>Änderungen hier werden in Kürze automatisch überschrieben.</div>');
				}, 1000);

			});
		</script>
		<?php
	} else {
		?>
		<script>
		jQuery( function() {
				if (jQuery('#_sunflower_event_show_map').is(':checked')) {
					jQuery('#sunflower-map-settings').show();
				}
		});
		</script>
		<?php
	}

	$is_all_day = $custom['_sunflower_event_whole_day'][0] ?? '';

	foreach ( $sunflower_event_fields as $id => $config ) {
		$value = $custom[ $id ][0] ?? false;

		// In case of all day events the events end on midnight, next day. For the input, we show last day of event.
		if ( '_sunflower_event_until' === $id && 'checked' === $is_all_day ) {
			$value = gmdate( 'Y.m.d', strtotime( $value ) - 86400 );
		}
		if ( $sunflower_form_disabled ) {
			$config[4] = 'disabled="disabled"';
		}
		sunflower_event_field( $id, $config, $value );
	}

	$lat  = $custom['_sunflower_event_lat'][0] ?? -1;
	$lon  = $custom['_sunflower_event_lon'][0] ?? -1;
	$zoom = $custom['_sunflower_event_zoom'][0] ?? -1;

	printf(
		'<hr />
		<div id="sunflower-map-settings" style="display:none;">
		<h3 class="">%1$s</h3>
        <div class="components-flex components-h-stack">
            <button id="sunflowerShowMap" class="components-button is-primary" onClick="sunflowerShowLeaflet( %3$s, %4$s, %5$s, true );">%2$s</button>
        </div>
        <div id="leaflet" style="height:270px"></div>
		</div>',
		esc_html__( 'Map', 'sunflower' ),
		esc_html__( 'Load map and fix location marker', 'sunflower' ),
		esc_attr( $lat ),
		esc_attr( $lon ),
		esc_attr( $zoom ),
	);
	wp_nonce_field( 'sunflower_location', '_wpnonce-locationfix' );
}

/**
 * Render Event metadata fields
 *
 * @param int    $id The form field id.
 * @param array  $config The field configuration.
 * @param string $value The field value.
 */
function sunflower_event_field( $id, $config, $value ) {
	$sunflower_label               = $config[0];
	$sunflower_class               = $config[1] ?? '';
	$sunflower_type                = $config[2] ?? false;
	$sunflower_initialized_checked = $config[3] ?? '';
	$sunflower_field_disabled      = $config[4] ?? '';

	if ( 'datetimepicker' === $sunflower_class ) {
		$value = sunflower_int_date2german_date( $value );
	}

	$sunflower_checked = '';
	if ( false === $value && 'checked' === $sunflower_initialized_checked ) {
		$sunflower_checked = 'checked';
	}

	match ( $sunflower_type ) {
		'checkbox' => printf(
			'<div class="sunflower-field checkbox"><span><input class="%4$s" type="checkbox" name="%1$s" id="%1$s" %3$s value="checked" ' . esc_attr( $sunflower_field_disabled ) . ' %5$s></span><label for="%1$s">%2$s</label></div>',
			esc_attr( $id ),
			esc_attr( $sunflower_label ),
			esc_attr( $value ),
			esc_attr( $sunflower_class ),
			esc_attr( $sunflower_checked )
		),
		'hidden' => printf(
			'<input type="hidden" name="%1$s" id="%1$s" value="%2$s">',
			esc_attr( $id ),
			esc_attr( $value )
		),
		default => printf(
			'<div class="sunflower-field text"><label for="%1$s">%2$s</label><br /><input class="%4$s" type="text" name="%1$s" placeholder="%2$s" autocomplete="off" value="%3$s" ' . esc_attr( $sunflower_field_disabled ) . '></div>',
			esc_attr( $id ),
			esc_attr( $sunflower_label ),
			esc_attr( $value ),
			esc_attr( $sunflower_class )
		),
	};
}

/**
 * Load the required JavaScript files.
 *
 * @param string $hook The hook name.
 */
function sunflower_load_event_admin_scripts( $hook ) {
	if ( 'sunflower_page_sunflower_events_options' === $hook || 'sunflower_event' === get_post_type() ) {

		wp_enqueue_script(
			'sunflower-datetimepicker',
			get_template_directory_uri() . '/assets/vndr/jquery-datetimepicker/build/jquery.datetimepicker.full.min.js',
			array( 'jquery' ),
			'1.0.0',
			true
		);

		wp_enqueue_script(
			'sunflower-datetimepicker-custom',
			get_template_directory_uri() . '/assets/js/custom-jquery-date-time-picker.js',
			array( 'sunflower-datetimepicker' ),
			'1.0.0',
			true
		);

		wp_enqueue_style(
			'sunflower-datetimepicker',
			get_template_directory_uri() . '/assets/vndr/jquery-datetimepicker/build/jquery.datetimepicker.min.css',
			array(),
			'1.0.0'
		);

		wp_enqueue_script(
			'sunflower-leaflet',
			get_template_directory_uri() . '/assets/vndr/leaflet/dist/leaflet.js',
			null,
			'3.2.1',
			true
		);

		wp_enqueue_style(
			'sunflower-leaflet',
			get_template_directory_uri() . '/assets/vndr/leaflet/dist/leaflet.css',
			array(),
			'1.0.0'
		);
	}
}

add_action( 'admin_enqueue_scripts', 'sunflower_load_event_admin_scripts' );

/**
 * Helper function to check if array has only numeric values
 *
 * @param array $value Array of elements which are either numeric or strings.
 */
function sunflower_is_numeric_array( array $value ) {
	foreach ( $value as $a => $b ) {
		if ( ! is_numeric( $b ) ) {
			return false;
		}
	}

	return true;
}

/**
 * Get the next events.
 *
 * @param int                 $number             The amount of events to fetch.
 * @param null|int[]|string[] $tag_ids            Array of sunflower_event_tag IDs.
 * @param string              $event_title_filter Optional string to filter events by title (case-insensitive substring match).
 *
 * @return WP_Query
 */
function sunflower_get_next_events( $number = -1, $tag_ids = null, $event_title_filter = '' ) {
	$sunflower_tax_query = null;

	if ( $tag_ids ) {
		if ( sunflower_is_numeric_array( $tag_ids ) ) {
			$sunflower_tax_query = array(
				array(
					'taxonomy' => 'sunflower_event_tag',
					'field'    => 'id',
					'terms'    => $tag_ids,
				),
			);
		} else {
			$sunflower_tax_query = array(
				array(
					'taxonomy' => 'sunflower_event_tag',
					'field'    => 'slug',
					'terms'    => $tag_ids,
				),
			);
		}
	}

	$query_args = array(
		'post_type'      => 'sunflower_event',
		'posts_per_page' => $number,
		'tax_query'      => $sunflower_tax_query,
		'meta_key'       => '_sunflower_event_from',
		'orderby'        => 'meta_value',
		'order'          => 'ASC',
		'meta_query'     => array(
			'relation' => 'OR',
			array(
				'key'     => '_sunflower_event_until',
				'value'   => gmdate( 'Y-m-d H:i', strToTime( 'now + 1 hours' ) ),
				'compare' => '>',
			),
			array(
				'key'     => '_sunflower_event_from',
				'value'   => gmdate( 'Y-m-d H:i', strToTime( 'now - 6 hours' ) ),
				'compare' => '>',
			),
		),
	);

	// 's' triggers WP_Query's built-in SQL LIKE search; search_columns restricts it to post_title only.
	if ( ! empty( $event_title_filter ) ) {
		$query_args['s']              = $event_title_filter;
		$query_args['search_columns'] = array( 'post_title' );
	}

	return new WP_Query( $query_args );
}

/**
 * Get past events.
 *
 * @param int $number The number of events to fetch.
 */
function sunflower_get_past_events( $number = -1 ) {
	return new WP_Query(
		array(
			'post_type'      => 'sunflower_event',
			'posts_per_page' => $number,
			'meta_key'       => '_sunflower_event_from',
			'orderby'        => 'meta_value',
			'meta_query'     => array(
				'relation' => 'OR',
				array(
					'key'     => '_sunflower_event_from',
					'value'   => gmdate( 'Y-m-d H:i', strToTime( 'now' ) ),
					'compare' => '<',
				),
			),
			'order'          => 'DESC',
		)
	);
}

/**
 * Prepare the event times for output.
 *
 * @param \WP_post $post The post object.
 */
function sunflower_prepare_event_time_data( $post ) {
	$_sunflower_event_from = get_post_meta( $post->ID, '_sunflower_event_from', true ) ?? false;
	$_sunflower_event_from = strToTime( (string) $_sunflower_event_from );

	$_sunflower_event_until = get_post_meta( $post->ID, '_sunflower_event_until', true ) ?? false;
	$_sunflower_event_until = strToTime( (string) $_sunflower_event_until );

	$_sunflower_event_whole_day = get_post_meta( $post->ID, '_sunflower_event_whole_day', true ) ?? false;

	$event_more_days = ( $_sunflower_event_until && gmdate( 'jFY', $_sunflower_event_from ) !== gmdate( 'jFY', $_sunflower_event_until ) );

	$weekday   = sprintf(
		'%s%s',
		date_i18n( 'l', $_sunflower_event_from ),
		( $event_more_days ) ? ' – ' . date_i18n( 'l', $_sunflower_event_until ) : ''
	);
	$untildate = '';
	$untiltime = '';
	$fromdate  = date_i18n( 'd.m.Y', $_sunflower_event_from );
	if ( $_sunflower_event_until ) {
		$untildate = ' – ' . date_i18n( 'd.m.Y', $_sunflower_event_until );

		if ( date_i18n( 'd.m.Y', $_sunflower_event_from ) === date_i18n( 'd.m.Y', $_sunflower_event_until ) ) {
			// On same day there is no until day.
			$untildate = '';
		} elseif ( gmdate( 'H:i', $_sunflower_event_until ) === '00:00' ) {
			// Days with time 00:00.
			$datetime = new DateTime();
			$datetime->setTimestamp( $_sunflower_event_from );
			$datetime->modify( '+1 day' );

			if ( gmdate( 'Y-m-d', $_sunflower_event_until ) === $datetime->format( 'Y-m-d' ) ) {
				// Its Tommorrow.
				$weekday   = date_i18n( 'l', $_sunflower_event_from );
				$untildate = '';
			} else {
				$weekday = sprintf(
					'%s%s',
					date_i18n( 'l', $_sunflower_event_from ),
					( $event_more_days ) ? ' – ' . date_i18n( 'l', $_sunflower_event_until - 1 ) : ''
				);
				// The - 1 leads to 1 seconds before midnight, that means the day before.
				$untildate = ' – ' . date_i18n( 'd.m.Y', $_sunflower_event_until - 1 );
			}
		} elseif ( date_i18n( 'm', $_sunflower_event_from ) === date_i18n( 'm', $_sunflower_event_until ) ) {
			// The same month.
			$fromdate = date_i18n( 'd.', $_sunflower_event_from );
		} elseif ( date_i18n( 'Y', $_sunflower_event_from ) === date_i18n( 'Y', $_sunflower_event_until ) ) {
			// The same year.
			$fromdate = date_i18n( 'd.m.', $_sunflower_event_from );
		}

		$untiltime = '–' . date_i18n( ' H:i', $_sunflower_event_until );
	}

	$days = sprintf(
		'%s%s',
		$fromdate,
		$untildate
	);

	$time = false;
	if ( gmdate( 'H:i', $_sunflower_event_from ) !== '00:00' && ! $_sunflower_event_whole_day ) {
		$time = sprintf(
			'%s %s',
			date_i18n( 'H:i', $_sunflower_event_from ),
			$untiltime
		);
	}

	return array( $weekday, $days, $time );
}

/**
 * Add the custom columns to the book post type
 *
 * @param array $columns Columns of the event post type.
 */
function sunflower_set_custom_edit_book_columns( $columns ) {
	unset( $columns['date'] );
	$columns['sunflower_event_date']          = __( 'Event date', 'sunflower' );
	$columns['sunflower_event_location_name'] = __( 'Event location', 'sunflower' );

	return $columns;
}

add_filter( 'manage_sunflower_event_posts_columns', 'sunflower_set_custom_edit_book_columns' );

/**
 * Add the data to the custom columns for the book post type
 *
 * @param string $column The event metadata field.
 * @param int    $post_id The post id.
 */
function sunflower_custom_event_column( $column, $post_id ) {
	switch ( $column ) {
		case 'sunflower_event_date':
			$date = get_post_meta( $post_id, '_sunflower_event_from', true ) ?? false;

			if ( $date ) {
				echo esc_attr( sunflower_int_date2german_date( $date ) );
			}

			break;

		case 'sunflower_event_location_name':
			$location = get_post_meta( $post_id, '_sunflower_event_location_name', true ) ?? false;

			echo esc_attr( $location );
	}
}

add_action( 'manage_sunflower_event_posts_custom_column', 'sunflower_custom_event_column', 10, 2 );

/**
 * Normalize calendar datetimes to the local-naive format used in event meta.
 *
 * FullCalendar sends ISO strings with timezone offsets, while this theme stores
 * event meta as plain local date strings. For the calendar query we intentionally
 * strip timezone information and compare local calendar values consistently.
 *
 * @param string $datetime Raw calendar datetime string.
 * @return string Normalized datetime in `Y-m-d H:i:s` format or an empty string.
 */
function sunflower_normalize_calendar_datetime( $datetime ) {
	if ( ! is_string( $datetime ) || '' === trim( $datetime ) ) {
		return '';
	}

	if (
		! preg_match(
			'/^(\d{4}-\d{2}-\d{2})(?:[T\s](\d{2}:\d{2})(?::(\d{2}))?)?/',
			$datetime,
			$matches
		)
	) {
		return '';
	}

	$date    = $matches[1];
	$time    = $matches[2] ?? '00:00';
	$seconds = $matches[3] ?? '00';

	return sprintf( '%s %s:%s', $date, $time, $seconds );
}

/**
 * Normalize the incoming tag filter to sanitized term slugs.
 *
 * @param string $tags Comma-separated tag slugs from the request.
 * @return array
 */
function sunflower_prepare_calendar_filter_tags( $tags ) {
	if ( ! is_string( $tags ) || '' === trim( $tags ) ) {
		return array();
	}

	return array_filter(
		array_map( 'sanitize_title', array_map( 'trim', explode( ',', $tags ) ) )
	);
}

/**
 * Build the WP_Query arguments for the calendar event endpoint.
 *
 * The range comparison intentionally follows FullCalendar's exclusive end date:
 * events must start before the requested end, and must either end after the
 * requested start or, when they do not have an end date, start on/after it.
 *
 * @param string $range_start Normalized range start.
 * @param string $range_end   Normalized range end.
 * @param array  $tags        Optional tag slugs to filter by.
 * @return array
 */
function sunflower_build_calendar_event_query_args( $range_start, $range_end, $tags = array() ) {
	$query_args = array(
		'post_type'      => 'sunflower_event',
		'posts_per_page' => -1,
		'meta_query'     => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
			'relation' => 'AND',
			array(
				'key'     => '_sunflower_event_from',
				'value'   => $range_end,
				'compare' => '<',
				'type'    => 'DATETIME',
			),
			array(
				'relation' => 'OR',
				array(
					'key'     => '_sunflower_event_until',
					'value'   => $range_start,
					'compare' => '>',
					'type'    => 'DATETIME',
				),
				array(
					'relation' => 'AND',
					array(
						'relation' => 'OR',
						array(
							'key'     => '_sunflower_event_until',
							'compare' => 'NOT EXISTS',
						),
						array(
							'key'     => '_sunflower_event_until',
							'value'   => '',
							'compare' => '=',
						),
					),
					array(
						'key'     => '_sunflower_event_from',
						'value'   => $range_start,
						'compare' => '>=',
						'type'    => 'DATETIME',
					),
				),
			),
		),
		'orderby'        => 'meta_value',
		'meta_key'       => '_sunflower_event_from', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
		'order'          => 'ASC',
	);

	if ( ! empty( $tags ) ) {
		$query_args['tax_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
			array(
				'taxonomy' => 'sunflower_event_tag',
				'field'    => 'slug',
				'terms'    => $tags,
			),
		);
	}

	return $query_args;
}

/**
 * Format event meta values for FullCalendar.
 *
 * @param string $datetime Event meta datetime.
 * @param bool   $is_all_day Whether the event is all day.
 * @return string|null
 */
function sunflower_format_calendar_event_datetime( $datetime, $is_all_day ) {
	$normalized_datetime = sunflower_normalize_calendar_datetime( $datetime );

	if ( ! $normalized_datetime ) {
		return null;
	}

	if ( $is_all_day ) {
		return substr( $normalized_datetime, 0, 10 );
	}

	return str_replace( ' ', 'T', $normalized_datetime );
}

/**
 * AJAX handler – return calendar events as JSON for a given date range.
 *
 * Accepts GET parameters: start, end (ISO 8601), nonce, tags (comma-separated slugs).
 * Responds with a FullCalendar-compatible event array.
 *
 * @return void Outputs JSON and exits.
 */
function sunflower_ajax_get_calendar_events() {
	$nonce = isset( $_GET['nonce'] ) ? sanitize_text_field( wp_unslash( $_GET['nonce'] ) ) : '';
	if ( ! wp_verify_nonce( $nonce, 'sunflower_calendar_events' ) ) {
		wp_send_json_error(
			array(
				'message' => 'Security check failed',
				'code'    => 'invalid_nonce',
			),
			403
		);
		return;
	}

	$start = sunflower_normalize_calendar_datetime(
		isset( $_GET['start'] ) ? sanitize_text_field( wp_unslash( $_GET['start'] ) ) : ''
	);
	$end   = sunflower_normalize_calendar_datetime(
		isset( $_GET['end'] ) ? sanitize_text_field( wp_unslash( $_GET['end'] ) ) : ''
	);
	$tags  = sunflower_prepare_calendar_filter_tags(
		isset( $_GET['tags'] ) ? sanitize_text_field( wp_unslash( $_GET['tags'] ) ) : ''
	);

	if ( empty( $start ) || empty( $end ) ) {
		wp_send_json_error(
			array(
				'message' => 'Invalid date range',
				'code'    => 'invalid_params',
			),
			400
		);
		return;
	}

	$events_query = new WP_Query(
		sunflower_build_calendar_event_query_args( $start, $end, $tags )
	);

	$calendar_events = array();

	while ( $events_query->have_posts() ) {
		$events_query->the_post();

		$event_start_date = get_post_meta( get_the_ID(), '_sunflower_event_from', true );
		$event_end_date   = get_post_meta( get_the_ID(), '_sunflower_event_until', true );
		$event_whole_day  = get_post_meta( get_the_ID(), '_sunflower_event_whole_day', true );

		$is_all_day = ( 'checked' === $event_whole_day );
		$fc_start   = sunflower_format_calendar_event_datetime( $event_start_date, $is_all_day );
		$fc_end     = sunflower_format_calendar_event_datetime( $event_end_date, $is_all_day );

		$event_tag_slugs = wp_get_post_terms( get_the_ID(), 'sunflower_event_tag', array( 'fields' => 'slugs' ) );
		$event_tag_slugs = is_array( $event_tag_slugs ) ? $event_tag_slugs : array();

		$calendar_event = array(
			// The calendar renders titles as plain text (textContent), so decode the
			// HTML entities WordPress stores in post_title (e.g. block-editor "&amp;")
			// back to plain characters. Inverse of WP's title encoding.
			'title'         => wp_specialchars_decode( get_post()->post_title, ENT_QUOTES ),
			'start'         => $fc_start,
			'url'           => get_permalink(),
			'allDay'        => $is_all_day,
			'extendedProps' => array(
				'tags' => $event_tag_slugs,
			),
		);

		if ( $fc_end && $fc_end !== $fc_start ) {
			$calendar_event['end'] = $fc_end;
		}

		$calendar_events[] = $calendar_event;
	}
	wp_reset_postdata();

	wp_send_json( $calendar_events );
}

add_action( 'wp_ajax_sunflower_get_calendar_events', 'sunflower_ajax_get_calendar_events' );
add_action( 'wp_ajax_nopriv_sunflower_get_calendar_events', 'sunflower_ajax_get_calendar_events' );

// Make sunflower_event_date column-header clickable in backend
// to allow ordering by it.
add_filter(
	'manage_edit-sunflower_event_sortable_columns',
	static function ( $columns ) {
		$columns['sunflower_event_date'] = 'sunflower_event_date';
		return $columns;
	}
);

add_action(
	'pre_get_posts',
	static function ( $query ) {
		if ( ! is_admin() ) {
			return;
		}

		$orderby = $query->get( 'orderby' );
		if ( 'sunflower_event_date' === $orderby ) {
			$query->set( 'meta_key', '_sunflower_event_from' );
			$query->set( 'orderby', 'meta_value' );
		}
	}
);
