<?php
/**
 * Calendar element.
 *
 * @package BricksExtras
 */

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

/**
 * Calendar element class.
 */
class X_Calendar extends \Bricks\Element {

	/**
	 * Element category.
	 *
	 * @var string
	 */
	public $category = 'extras';

	/**
	 * Element name.
	 *
	 * @var string
	 */
	public $name = 'xcalendar';

	/**
	 * Element icon.
	 *
	 * @var string
	 */
	public $icon = 'ti-calendar';

	/**
	 * CSS selector.
	 *
	 * @var string
	 */
	public $css_selector = '';

	/**
	 * Required scripts.
	 *
	 * @var array
	 */
	public $scripts = array( 'extrasCalendarInit' );

	/**
	 * Calendar events array.
	 *
	 * @var array
	 */
	public $calendar_events = array();

	/**
	 * Get element label.
	 */
	public function get_label() {
		return esc_html__( 'Calendar', 'bricksextras' );
	}

	/**
	 * Get element keywords.
	 */
	public function get_keywords() {
		return array( 'calendar', 'events', 'schedule' );
	}

	/**
	 * Set control groups.
	 */
	public function set_control_groups() {

		$this->control_groups['fields'] = array(
			'title' => esc_html__( 'Field mapping', 'bricks' ),
			'tab'   => 'content',
		);

		$this->control_groups['recurring'] = array(
			'title'    => esc_html__( 'Recurring rules', 'bricks' ),
			'tab'      => 'content',
			'required' => array(
				array( 'hasLoop', '!=', '' ),
				array( 'enableRecurring', '!=', '' ),
			),
		);

		$this->control_groups['config'] = array(
			'title' => esc_html__( 'Views', 'bricksextras' ),
			'tab'   => 'content',
		);

		$this->control_groups['toolbar'] = array(
			'title' => esc_html__( 'Toolbar', 'bricks' ),
			'tab'   => 'content',
		);

		$this->control_groups['calendarStyle'] = array(
			'title' => esc_html__( 'Styles', 'bricks' ),
			'tab'   => 'content',
		);

		$this->control_groups['popup'] = array(
			'title' => esc_html__( 'Actions', 'bricks' ),
			'tab'   => 'content',
		);

		$this->control_groups['labels'] = array(
			'title' => esc_html__( 'Labels', 'bricks' ),
			'tab'   => 'content',
		);

		$this->control_groups['formats'] = array(
			'title' => esc_html__( 'Date & Time Formats', 'bricksextras' ),
			'tab'   => 'content',
		);
	}

	/**
	 * Set controls.
	 */
	public function set_controls() {

		$calendar_selector = '.x-calendar';

		// Query controls.
		$this->controls['hasLoop'] = array(
			'tab'   => 'content',
			'label' => esc_html__( 'Query loop', 'bricks' ),
			'type'  => 'checkbox',
		);

		$this->controls['query'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Query', 'bricks' ),
			'type'     => 'query',
			'popup'    => true,
			'inline'   => true,
			'required' => array(
				array( 'hasLoop', '!=', '' ),
			),
		);

		$this->controls['queryLoopInfo'] = array(
			'tab'      => 'content',
			'group'    => 'fields',
			'type'     => 'info',
			'content'  => esc_html__( 'Enable "Query loop" above to display calendar events from your posts/custom post types.', 'bricksextras' ),
			'required' => array(
				array( 'hasLoop', '=', '' ),
			),
		);

		// Field mapping.

		$this->controls['requiredSep'] = array(
			'tab'      => 'content',
			'group'    => 'fields',
			'label'    => esc_html__( 'Required', 'bricks' ),
			'type'     => 'separator',
			'required' => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['titleField'] = array(
			'tab'         => 'content',
			'group'       => 'fields',
			'label'       => esc_html__( 'Title', 'bricks' ),
			'type'        => 'text',
			'inline'      => true,
			'placeholder' => '{post_title}',
			'required'    => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['startField'] = array(
			'tab'         => 'content',
			'group'       => 'fields',
			'label'       => esc_html__( 'Start date', 'bricks' ),
			'type'        => 'text',
			'inline'      => true,
			'placeholder' => '{post_date}',
			'required'    => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['optionalSep'] = array(
			'tab'      => 'content',
			'group'    => 'fields',
			'label'    => esc_html__( 'Optional', 'bricks' ),
			'type'     => 'separator',
			'required' => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['endField'] = array(
			'tab'         => 'content',
			'group'       => 'fields',
			'label'       => esc_html__( 'End date', 'bricks' ),
			'type'        => 'text',
			'inline'      => true,
			'placeholder' => '',
			'required'    => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['colorField'] = array(
			'tab'      => 'content',
			'group'    => 'fields',
			'label'    => esc_html__( 'Color', 'bricks' ),
			'type'     => 'text',
			'inline'   => true,
			'required' => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['allDayField'] = array(
			'tab'      => 'content',
			'group'    => 'fields',
			'label'    => esc_html__( 'All Day?', 'bricksextras' ),
			'info'     => esc_html__( 'True', 'bricks' ) . '/' . esc_html__( 'False', 'bricks' ),
			'type'     => 'text',
			'inline'   => true,
			'required' => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['displayField'] = array(
			'tab'      => 'content',
			'group'    => 'fields',
			'label'    => esc_html__( 'Display', 'bricks' ),
			'info'     => 'auto, block, list-item, none',
			'type'     => 'text',
			'inline'   => true,
			'required' => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['locationField'] = array(
			'tab'      => 'content',
			'group'    => 'fields',
			'label'    => esc_html__( 'Location', 'bricks' ),
			'type'     => 'text',
			'inline'   => true,
			'required' => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['descriptionField'] = array(
			'tab'      => 'content',
			'group'    => 'fields',
			'label'    => esc_html__( 'Description', 'bricks' ),
			'type'     => 'text',
			'inline'   => true,
			'required' => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['enableRecurring'] = array(
			'tab'         => 'content',
			'group'       => 'fields',
			'label'       => esc_html__( 'Recurring', 'bricksextras' ),
			'type'        => 'checkbox',
			'description' => esc_html__( 'Allow items to repeat', 'bricksextras' ),
			'required'    => array( 'hasLoop', '!=', '' ),
		);

		$this->controls['rruleRules'] = array(
			'tab'           => 'content',
			'group'         => 'recurring',
			'label'         => esc_html__( 'Recurring Rules', 'bricksextras' ),
			'type'          => 'repeater',
			'titleProperty' => 'ruleName',
			'placeholder'   => esc_html__( 'Recurrence rule', 'bricksextras' ),
			'fields'        => array(
				'ruleName'  => array(
					'label'       => esc_html__( 'Name', 'bricks' ),
					'type'        => 'select',
					'searchable'  => true,
					'options'     => array(
						'FREQ'          => esc_html__( 'Frequency', 'bricksextras' ),
						'INTERVAL'      => esc_html__( 'Interval', 'bricksextras' ),
						'COUNT'         => esc_html__( 'Count', 'bricksextras' ),
						'UNTIL'         => esc_html__( 'Until Date', 'bricksextras' ),
						'BYDAY'         => esc_html__( 'By Day of Week', 'bricksextras' ),
						'BYDAY_MONTHLY' => esc_html__( 'By Weekday of Month', 'bricksextras' ),
						'BYMONTH'       => esc_html__( 'By Month', 'bricksextras' ),
						'BYMONTHDAY'    => esc_html__( 'By Day of Month', 'bricksextras' ),
						'BYYEARDAY'     => esc_html__( 'By Day of Year', 'bricksextras' ),
						'BYWEEKNO'      => esc_html__( 'By Week Number', 'bricksextras' ),
						'BYHOUR'        => esc_html__( 'By Hour', 'bricksextras' ),
						'BYMINUTE'      => esc_html__( 'By Minute', 'bricksextras' ),
						'BYSECOND'      => esc_html__( 'By Second', 'bricksextras' ),
						'BYSETPOS'      => esc_html__( 'By Position', 'bricksextras' ),
						'WKST'          => esc_html__( 'Week Start', 'bricksextras' ),
					),
					'placeholder' => esc_html__( 'Select rule', 'bricksextras' ),
				),
				'ruleValue' => array(
					'label'       => esc_html__( 'Value', 'bricks' ),
					'type'        => 'text',
					'description' => '(' . esc_html__( 'Dynamic data', 'bricks' ) . ')',
				),
			),
		);

		$this->controls['exclusionDatesField'] = array(
			'tab'    => 'content',
			'group'  => 'recurring',
			'label'  => esc_html__( 'Exclusion dates', 'bricksextras' ),
			'type'   => 'text',
			'inline' => true,
		);

		$this->controls['eventOverridesField'] = array(
			'tab'    => 'content',
			'group'  => 'recurring',
			'label'  => esc_html__( 'Event exceptions', 'bricksextras' ),
			'type'   => 'text',
			'inline' => true,
		);

		$this->controls['calendarGap'] = array(
			'tab'   => 'content',
			'label' => esc_html__( 'Gap', 'bricks' ) . ' (' . esc_html__( 'Toolbar', 'bricks' ) . ')',
			'group' => 'toolbar',
			'type'  => 'number',
			'units' => true,
			'css'   => array(
				array(
					'property' => 'gap',
					'selector' => '.x-calendar',
				),
			),
		);

		$this->controls['headerToolbarSep'] = array(
			'tab'   => 'content',
			'group' => 'toolbar',
			'label' => esc_html__( 'Toolbar', 'bricks' ) . ' (' . esc_html__( 'Header', 'bricks' ) . ')',
			'type'  => 'separator',
		);

		$this->controls['headerToolbar'] = array(
			'tab'     => 'content',
			'group'   => 'toolbar',
			'label'   => esc_html__( 'Show', 'bricks' ),
			'type'    => 'checkbox',
			'default' => true,
		);

		$buttons_array = array(
			'prev'           => esc_html__( 'Prev', 'bricksextras' ),
			'next'           => esc_html__( 'Next', 'bricksextras' ),
			'prevYear'       => esc_html__( 'Prev Year', 'bricksextras' ),
			'nextYear'       => esc_html__( 'Next Year', 'bricksextras' ),
			'today'          => esc_html__( 'Today', 'bricksextras' ),
			'title'          => esc_html__( 'Title', 'bricksextras' ),
			'spacer'         => esc_html__( 'Spacer', 'bricksextras' ),
			'dayGridYear'    => esc_html__( 'Year (DayGrid)', 'bricksextras' ),
			'dayGridMonth'   => esc_html__( 'Month (DayGrid)', 'bricksextras' ),
			'dayGridWeek'    => esc_html__( 'Week (DayGrid)', 'bricksextras' ),
			'dayGridDay'     => esc_html__( 'Day (DayGrid)', 'bricksextras' ),
			'timeGridWeek'   => esc_html__( 'Week (TimeGrid)', 'bricksextras' ),
			'timeGridDay'    => esc_html__( 'Day (TimeGrid)', 'bricksextras' ),
			'listDay'        => esc_html__( 'Day (List)', 'bricksextras' ),
			'listWeek'       => esc_html__( 'Week (List)', 'bricksextras' ),
			'listMonth'      => esc_html__( 'Month (List)', 'bricksextras' ),
			'listYear'       => esc_html__( 'Year (List)', 'bricksextras' ),
			'multiMonthYear' => esc_html__( 'Multi-Month', 'bricksextras' ),
		);

		$this->controls['headerToolbarLeft'] = array(
			'tab'         => 'content',
			'group'       => 'toolbar',
			'label'       => esc_html__( 'Header A', 'bricksextras' ),
			'type'        => 'select',
			'searchable'  => true,
			'options'     => $buttons_array,
			'multiple'    => true,
			'placeholder' => esc_html__( 'Select buttons', 'bricksextras' ),
			'default'     => array( 'prev', 'next' ),
			'required'    => array( 'headerToolbar', '!=', '' ),
		);

		$this->controls['headerToolbarCenter'] = array(
			'tab'         => 'content',
			'group'       => 'toolbar',
			'label'       => esc_html__( 'Header B', 'bricksextras' ),
			'type'        => 'select',
			'searchable'  => true,
			'options'     => $buttons_array,
			'multiple'    => true,
			'placeholder' => esc_html__( 'Select buttons', 'bricksextras' ),
			'default'     => array( 'title' ),
			'required'    => array( 'headerToolbar', '!=', '' ),
		);

		$this->controls['headerToolbarRight'] = array(
			'tab'         => 'content',
			'group'       => 'toolbar',
			'label'       => esc_html__( 'Header C', 'bricksextras' ),
			'type'        => 'select',
			'searchable'  => true,
			'options'     => $buttons_array,
			'multiple'    => true,
			'placeholder' => esc_html__( 'Select buttons', 'bricksextras' ),
			'default'     => array( 'dayGridMonth', 'timeGridWeek', 'listWeek' ),
			'required'    => array( 'headerToolbar', '!=', '' ),
		);

		$header_toolbar = '.fc-header-toolbar';

		$this->controls['toolbarDirection'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Direction', 'bricks' ),
			'group'    => 'toolbar',
			'tooltip'  => array(
				'content'  => 'flex-direction',
				'position' => 'top-left',
			),
			'type'     => 'direction',
			'css'      => array(
				array(
					'property' => 'flex-direction',
					'selector' => $header_toolbar,
				),
			),
			'inline'   => true,
			'required' => array( 'headerToolbar', '!=', '' ),
		);

		$this->controls['toolbarJustifyContent'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Align main axis', 'bricks' ),
			'group'    => 'toolbar',
			'tooltip'  => array(
				'content'  => 'justify-content',
				'position' => 'top-left',
			),
			'type'     => 'justify-content',
			'css'      => array(
				array(
					'property' => 'justify-content',
					'selector' => $header_toolbar,
				),
			),
			'required' => array( 'headerToolbar', '!=', '' ),
		);

		$this->controls['toolbarAlignItems'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Align cross axis', 'bricks' ),
			'group'    => 'toolbar',
			'tooltip'  => array(
				'content'  => 'align-items',
				'position' => 'top-left',
			),
			'type'     => 'align-items',
			'css'      => array(
				array(
					'property' => 'align-items',
					'selector' => $header_toolbar,
				),
			),
			'required' => array( 'headerToolbar', '!=', '' ),
		);

		$this->controls['toolbarGap'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Gap', 'bricks' ),
			'group'    => 'toolbar',
			'type'     => 'number',
			'units'    => true,
			'css'      => array(
				array(
					'property' => 'gap',
					'selector' => $header_toolbar,
				),
			),
			'required' => array( 'headerToolbar', '!=', '' ),
		);

		$this->controls['toolbarButtonsGap'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Gap', 'bricks' ) . ' (' . esc_html__( 'Buttons', 'bricks' ) . ')',
			'group'    => 'toolbar',
			'type'     => 'number',
			'units'    => true,
			'css'      => array(
				array(
					'property' => 'gap',
					'selector' => $header_toolbar . ' div:has(>[type="button"])',
				),
			),
			'required' => array( 'headerToolbar', '!=', '' ),
		);

		$this->controls['footerToolbarSep'] = array(
			'tab'   => 'content',
			'group' => 'toolbar',
			'label' => esc_html__( 'Toolbar', 'bricks' ) . ' (' . esc_html__( 'Footer', 'bricks' ) . ')',
			'type'  => 'separator',
		);

		$this->controls['footerToolbar'] = array(
			'tab'   => 'content',
			'group' => 'toolbar',
			'label' => esc_html__( 'Show', 'bricks' ),
			'type'  => 'checkbox',
		);

		$this->controls['footerToolbarLeft'] = array(
			'tab'         => 'content',
			'group'       => 'toolbar',
			'label'       => esc_html__( 'Footer A', 'bricksextras' ),
			'type'        => 'select',
			'searchable'  => true,
			'options'     => $buttons_array,
			'multiple'    => true,
			'placeholder' => esc_html__( 'Select buttons', 'bricksextras' ),
			'required'    => array( 'footerToolbar', '!=', '' ),
		);

		$this->controls['footerToolbarCenter'] = array(
			'tab'         => 'content',
			'group'       => 'toolbar',
			'label'       => esc_html__( 'Footer B', 'bricksextras' ),
			'type'        => 'select',
			'searchable'  => true,
			'options'     => $buttons_array,
			'multiple'    => true,
			'placeholder' => esc_html__( 'Select buttons', 'bricksextras' ),
			'required'    => array( 'footerToolbar', '!=', '' ),
		);

		$this->controls['footerToolbarRight'] = array(
			'tab'         => 'content',
			'group'       => 'toolbar',
			'label'       => esc_html__( 'Footer C', 'bricksextras' ),
			'type'        => 'select',
			'searchable'  => true,
			'options'     => $buttons_array,
			'multiple'    => true,
			'placeholder' => esc_html__( 'Select buttons', 'bricksextras' ),
			'required'    => array( 'footerToolbar', '!=', '' ),
		);

		$footer_toolbar = '.fc-footer-toolbar';

		$this->controls['footerToolbarDirection'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Direction', 'bricks' ),
			'group'    => 'toolbar',
			'tooltip'  => array(
				'content'  => 'flex-direction',
				'position' => 'top-left',
			),
			'type'     => 'direction',
			'css'      => array(
				array(
					'property' => 'flex-direction',
					'selector' => $footer_toolbar,
				),
			),
			'inline'   => true,
			'required' => array( 'footerToolbar', '!=', '' ),
		);

		$this->controls['footerToolbarJustifyContent'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Align main axis', 'bricks' ),
			'group'    => 'toolbar',
			'tooltip'  => array(
				'content'  => 'justify-content',
				'position' => 'top-left',
			),
			'type'     => 'justify-content',
			'css'      => array(
				array(
					'property' => 'justify-content',
					'selector' => $footer_toolbar,
				),
			),
			'required' => array( 'footerToolbar', '!=', '' ),
		);

		$this->controls['footerToolbarAlignItems'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Align cross axis', 'bricks' ),
			'group'    => 'toolbar',
			'tooltip'  => array(
				'content'  => 'align-items',
				'position' => 'top-left',
			),
			'type'     => 'align-items',
			'css'      => array(
				array(
					'property' => 'align-items',
					'selector' => $footer_toolbar,
				),
			),
			'required' => array( 'footerToolbar', '!=', '' ),
		);

		$this->controls['footerToolbarGap'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Gap', 'bricks' ),
			'group'    => 'toolbar',
			'type'     => 'number',
			'units'    => true,
			'css'      => array(
				array(
					'property' => 'gap',
					'selector' => $footer_toolbar,
				),
			),
			'required' => array( 'footerToolbar', '!=', '' ),
		);

		$this->controls['footerToolbarButtonsGap'] = array(
			'tab'      => 'content',
			'label'    => esc_html__( 'Gap', 'bricks' ) . ' (' . esc_html__( 'Buttons', 'bricks' ) . ')',
			'group'    => 'toolbar',
			'type'     => 'number',
			'units'    => true,
			'css'      => array(
				array(
					'property' => 'gap',
					'selector' => $footer_toolbar . ' div:has(>[type="button"])',
				),
			),
			'required' => array( 'footerToolbar', '!=', '' ),
		);

		/* config. */

		// Calendar settings.
		$this->controls['initialView'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Initial View', 'bricksextras' ),
			'type'        => 'select',
			'inline'      => true,
			'options'     => array(
				'dayGridYear'    => esc_html__( 'Year (DayGrid)', 'bricksextras' ),
				'dayGridMonth'   => esc_html__( 'Month (DayGrid)', 'bricksextras' ),
				'dayGridWeek'    => esc_html__( 'Week (DayGrid)', 'bricksextras' ),
				'dayGridDay'     => esc_html__( 'Day (DayGrid)', 'bricksextras' ),
				'timeGridWeek'   => esc_html__( 'Week (TimeGrid)', 'bricksextras' ),
				'timeGridDay'    => esc_html__( 'Day (TimeGrid)', 'bricksextras' ),
				'listDay'        => esc_html__( 'Day (List)', 'bricksextras' ),
				'listWeek'       => esc_html__( 'Week (List)', 'bricksextras' ),
				'listMonth'      => esc_html__( 'Month (List)', 'bricksextras' ),
				'listYear'       => esc_html__( 'Year (List)', 'bricksextras' ),
				'multiMonthYear' => esc_html__( 'Multi-Month', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Month (DayGrid)', 'bricksextras' ),
		);

		$this->controls['calendarHeight'] = array(
			'tab'            => 'content',
			'group'          => 'config',
			'label'          => esc_html__( 'Height', 'bricks' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => 'auto',
			'hasDynamicData' => false,
		);

		$this->controls['aspectRatio'] = array(
			'tab'    => 'content',
			'group'  => 'config',
			'label'  => esc_html__( 'Aspect ratio', 'bricks' ),
			'type'   => 'number',
			'inline' => true,
		);

		$this->controls['progressiveEventRendering'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Progressive event rendering', 'bricksextras' ),
			'type'  => 'checkbox',
		);

		$this->controls['dayMaxEvents'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Max events to show per day', 'bricksextras' ),
			'info'        => esc_html__( 'Leave at 0 for auto', 'bricksextras' ),
			'type'        => 'number',
			'inline'      => true,
			'placeholder' => '0',
		);

		$this->controls['firstDay'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'First day of week', 'bricksextras' ),
			'type'        => 'select',
			'inline'      => true,
			'options'     => array(
				'sunday'    => esc_html__( 'Sunday', 'bricks' ),
				'monday'    => esc_html__( 'Monday', 'bricks' ),
				'tuesday'   => esc_html__( 'Tuesday', 'bricks' ),
				'wednesday' => esc_html__( 'Wednesday', 'bricks' ),
				'thursday'  => esc_html__( 'Thursday', 'bricks' ),
				'friday'    => esc_html__( 'Friday', 'bricks' ),
				'saturday'  => esc_html__( 'Saturday', 'bricks' ),
			),
			'placeholder' => esc_html__( 'Sunday', 'bricks' ),
		);

		$this->controls['dayMinWidth'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Min day width', 'bricksextras' ),
			'type'        => 'number',
			'inline'      => true,
			'min'         => 1,
			'placeholder' => '150',
			'small'       => true,
			'units'       => 'px',
			'info'        => esc_html__( 'Minimum day width before horizontal scrollbars appear', 'bricksextras' ),
		);

		$this->controls['dayNarrowWidth'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Narrow day width', 'bricksextras' ),
			'type'        => 'number',
			'inline'      => true,
			'min'         => 1,
			'placeholder' => '80',
			'small'       => true,
			'units'       => 'px',
			'info'        => esc_html__( 'Pixel width threshold to switch to narrow date text', 'bricksextras' ),
		);

		$this->controls['weekNumbers'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Show week numbers', 'bricksextras' ),
			'type'  => 'checkbox',
		);

		$this->controls['navLinks'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Clickable dates', 'bricksextras' ),
			'type'  => 'checkbox',
		);

		$this->controls['weekends'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Show weekends', 'bricksextras' ),
			'type'        => 'select',
			'inline'      => true,
			'options'     => array(
				'true'  => esc_html__( 'True', 'bricks' ),
				'false' => esc_html__( 'False', 'bricks' ),
			),
			'placeholder' => esc_html__( 'True', 'bricks' ),
		);

		$this->controls['hiddenDays'] = array(
			'tab'      => 'content',
			'group'    => 'config',
			'label'    => esc_html__( 'Hidden days', 'bricksextras' ),
			'type'     => 'select',
			'inline'   => true,
			'options'  => array(
				'sunday'    => esc_html__( 'Sunday', 'bricks' ),
				'monday'    => esc_html__( 'Monday', 'bricks' ),
				'tuesday'   => esc_html__( 'Tuesday', 'bricks' ),
				'wednesday' => esc_html__( 'Wednesday', 'bricks' ),
				'thursday'  => esc_html__( 'Thursday', 'bricks' ),
				'friday'    => esc_html__( 'Friday', 'bricks' ),
				'saturday'  => esc_html__( 'Saturday', 'bricks' ),
			),
			'multiple' => true,
		);

		$this->controls['eventsSep'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Event display', 'bricksextras' ),
			'type'  => 'separator',
		);

		$this->controls['displayEventTime'] = array(
			'tab'     => 'content',
			'group'   => 'config',
			'label'   => esc_html__( 'Show event times', 'bricksextras' ),
			'type'    => 'checkbox',
			'default' => true,
		);

		$this->controls['displayEventEnd'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Show event end times', 'bricksextras' ),
			'type'  => 'checkbox',
			'info'  => esc_html__( 'Display end time for events', 'bricksextras' ),
		);

		$this->controls['gridSep'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Grid views', 'bricksextras' ),
			'type'  => 'separator',
		);

		$this->controls['dayHeaders'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Show day headers', 'bricksextras' ),
			'type'        => 'select',
			'inline'      => true,
			'options'     => array(
				'true'  => esc_html__( 'True', 'bricks' ),
				'false' => esc_html__( 'False', 'bricks' ),
			),
			'placeholder' => esc_html__( 'True', 'bricks' ),
			'info'        => esc_html__( 'Show/hide day headers in month and timegrid views', 'bricksextras' ),
		);

		$this->controls['eventDisplay'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Event default display style', 'bricksextras' ),
			'type'        => 'select',
			'inline'      => true,
			'options'     => array(
				'auto'      => 'auto',
				'block'     => 'block',
				'list-item' => 'List Item',
			),
			'placeholder' => esc_html__( 'List Item', 'bricksextras' ),
		);

		$this->controls['timelineSep'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Timegrid views', 'bricksextras' ),
			'type'  => 'separator',
		);

		$this->controls['nowIndicator'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Show now indicator', 'bricksextras' ),
			'type'  => 'checkbox',
			'info'  => esc_html__( 'Shows current time line in timegrid views', 'bricksextras' ),
		);

		$this->controls['fcNowIndicatorColor'] = array(
			'tab'      => 'content',
			'group'    => 'config',
			'label'    => esc_html__( 'Now Indicator Color', 'bricksextras' ),
			'type'     => 'color',
			'css'      => array(
				array(
					'property' => '--fc-classic-now',
					'selector' => $calendar_selector,
				),
			),
			'required' => array( 'nowIndicator', '=', true ),
		);

		$this->controls['allDaySlot'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Show all-day slot', 'bricksextras' ),
			'type'        => 'select',
			'inline'      => true,
			'options'     => array(
				'true'  => esc_html__( 'True', 'bricks' ),
				'false' => esc_html__( 'False', 'bricks' ),
			),
			'placeholder' => esc_html__( 'True', 'bricks' ),
			'info'        => esc_html__( 'Display the all-day row in TimeGrid views', 'bricksextras' ),
		);

		$this->controls['slotMinTime'] = array(
			'tab'            => 'content',
			'group'          => 'config',
			'label'          => esc_html__( 'Timegrid start time', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => '00:00:00',
			'info'           => esc_html__( 'First time slot in timegrid views (e.g., 06:00:00)', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['slotMaxTime'] = array(
			'tab'            => 'content',
			'group'          => 'config',
			'label'          => esc_html__( 'Timegrid end time', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => '24:00:00',
			'info'           => esc_html__( 'Last time slot in timegrid views (e.g., 22:00:00)', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['slotDuration'] = array(
			'tab'            => 'content',
			'group'          => 'config',
			'label'          => esc_html__( 'Time slot duration', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => '00:30:00',
			'info'           => esc_html__( 'Duration of each time slot (e.g., 00:15:00 for 15 minutes)', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['slotHeaderInterval'] = array(
			'tab'            => 'content',
			'group'          => 'config',
			'label'          => esc_html__( 'Slot header interval', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => '01:00:00',
			'info'           => esc_html__( 'Frequency that time slots are labeled (e.g., 02:00:00 for every 2 hours)', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['scrollTime'] = array(
			'tab'            => 'content',
			'group'          => 'config',
			'label'          => esc_html__( 'Initial scroll time', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => '06:00:00',
			'info'           => esc_html__( 'Time to scroll to when timeline views load', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['multiMonthSep'] = array(
			'tab'   => 'content',
			'group' => 'config',
			'label' => esc_html__( 'Multi-Month views', 'bricksextras' ),
			'type'  => 'separator',
		);

		$this->controls['multiMonthDuration'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'No. of months', 'bricksextras' ),
			'type'        => 'number',
			'inline'      => true,
			'min'         => 1,
			'max'         => 12,
			'placeholder' => '4',
			'small'       => true,
			'info'        => esc_html__( 'Number of months to display in Multi-Month view (1-12)', 'bricksextras' ),
		);

		$this->controls['multiMonthMaxColumns'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Max columns', 'bricksextras' ),
			'type'        => 'number',
			'inline'      => true,
			'min'         => 1,
			'max'         => 12,
			'placeholder' => '2',
			'small'       => true,
			'info'        => esc_html__( 'Maximum number of columns in Multi-Month view (1-12)', 'bricksextras' ),
		);

		$this->controls['singleMonthMinWidth'] = array(
			'tab'         => 'content',
			'group'       => 'config',
			'label'       => esc_html__( 'Min month width', 'bricksextras' ),
			'type'        => 'number',
			'inline'      => true,
			'min'         => 1,
			'placeholder' => '350',
			'small'       => true,
			'units'       => 'px',
			'info'        => esc_html__( 'Minimum width (px) of each mini-month in Multi-Month view before wrapping to a new row', 'bricksextras' ),
		);

		/* event action. */

		$this->controls['eventClickAction'] = array(
			'tab'         => 'content',
			'group'       => 'popup',
			'label'       => esc_html__( 'Click', 'bricks' ),
			'type'        => 'select',
			'options'     => array(
				'popup' => esc_html__( 'Show popup', 'bricks' ),
				'url'   => esc_html__( 'Link', 'bricks' ),
				'none'  => esc_html__( 'None', 'bricks' ),
			),
			'placeholder' => esc_html__( 'Link', 'bricks' ),
			'inline'      => true,
		);

		$this->controls['eventPopup'] = array(
			'tab'         => 'content',
			'group'       => 'popup',
			'label'       => esc_html__( 'Template', 'bricks' ),
			'type'        => 'select',
			'options'     => $this->get_popup_templates(),
			'placeholder' => esc_html__( 'Select template', 'bricks' ),
			'required'    => array( 'eventClickAction', '=', 'popup' ),
		);

		$this->controls['eventUrlField'] = array(
			'tab'         => 'content',
			'group'       => 'popup',
			'label'       => esc_html__( 'URL', 'bricksextras' ),
			'type'        => 'text',
			'inline'      => true,
			'placeholder' => '{post_url}',
			'required'    => array( 'eventClickAction', '!=', array( 'popup', 'none' ) ),
		);

		$this->controls['eventUrlTarget'] = array(
			'tab'      => 'content',
			'group'    => 'popup',
			'label'    => esc_html__( 'Open in new tab', 'bricks' ),
			'type'     => 'checkbox',
			'default'  => false,
			'required' => array( 'eventClickAction', '!=', array( 'popup', 'none' ) ),
		);

		// Text Labels.
		$this->controls['manualLocale'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Locale', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'e.g., en, de, fr, es', 'bricksextras' ),
			'description'    => esc_html__( 'Translate month names, day names, and date formatting.', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['autoLocale'] = array(
			'tab'         => 'content',
			'group'       => 'labels',
			'label'       => esc_html__( 'Auto-detect locale', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'enable'  => esc_html__( 'Enable', 'bricksextras' ),
				'disable' => esc_html__( 'Disable', 'bricksextras' ),
			),
			'inline'      => true,
			'placeholder' => esc_html__( 'Disable', 'bricksextras' ),
			'description' => esc_html__( 'Automatically detect locale from site language. Overrides above locale setting.', 'bricksextras' ),
		);

		$this->controls['buttonLabelsSep'] = array(
			'tab'   => 'content',
			'group' => 'labels',
			'label' => esc_html__( 'Labels', 'bricks' ) . ' (' . esc_html__( 'Buttons', 'bricks' ) . ')',
			'type'  => 'separator',
		);

		$this->controls['labelToday'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Today Button', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Today', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelPrev'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Previous Button', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Previous', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelNext'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Next Button', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Next', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelPrevYear'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Previous Year Button', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Previous Year', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelNextYear'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Next Year Button', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Next year', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelAllDaySep'] = array(
			'tab'   => 'content',
			'group' => 'labels',
			'type'  => 'separator',
		);

		$this->controls['labelAllDay'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'All-Day Text', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'All-day', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelNoEvents'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'No Events Text', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'No events to display', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['viewSpecificLabelsSep'] = array(
			'tab'   => 'content',
			'group' => 'labels',
			'label' => esc_html__( 'Labels', 'bricks' ) . ' (' . esc_html__( 'Views', 'bricksextras' ) . ')',
			'type'  => 'separator',
		);

		$this->controls['labelDayGridYear'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Year (DayGrid)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Year', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelDayGridMonth'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Month (DayGrid)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Month', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelDayGridWeek'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Week (DayGrid)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Week', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelDayGridDay'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Day (DayGrid)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Day', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelTimeGridWeek'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Week (TimeGrid)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Week', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelTimeGridDay'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Day (TimeGrid)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Day', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelListDay'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Day (List)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'List', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelListWeek'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Week (List)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'List', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelListMonth'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Month (List)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'List', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelListYear'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Year (List)', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'List', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		$this->controls['labelMultiMonthYear'] = array(
			'tab'            => 'content',
			'group'          => 'labels',
			'label'          => esc_html__( 'Multi-Month', 'bricksextras' ),
			'type'           => 'text',
			'inline'         => true,
			'placeholder'    => esc_html__( 'Multi-Month', 'bricksextras' ),
			'hasDynamicData' => false,
		);

		// Date & Time Formats.
		$this->controls['titleMonth'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Title: Month', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Numeric', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
				'short'   => esc_html__( 'Short', 'bricksextras' ),
				'long'    => esc_html__( 'Long', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Long', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['titleYear'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Title: Year', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Full', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Full', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['weekNumberFormat'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Week numbers', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Numeric', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Numeric', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['listDayWeekday'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'List: Weekday', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'narrow' => esc_html__( 'Narrow', 'bricksextras' ),
				'short'  => esc_html__( 'Short', 'bricksextras' ),
				'long'   => esc_html__( 'Long', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Long', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['listDayMonth'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'List: Month', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Numeric', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
				'short'   => esc_html__( 'Short', 'bricksextras' ),
				'long'    => esc_html__( 'Long', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Long', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['listDayDay'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'List: Day', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Numeric', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Numeric', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['eventTimeHour'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Event time: Hour', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Numeric', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
			),
			'placeholder' => esc_html__( '2-digit', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['eventTimeMinute'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Event time: Minute', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Numeric', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
			),
			'placeholder' => esc_html__( '2-digit', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['eventTimeMeridiem'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Event time: AM/PM', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'short'  => esc_html__( 'Short', 'bricksextras' ),
				'narrow' => esc_html__( 'Narrow', 'bricksextras' ),
				'false'  => esc_html__( '24-hour', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Short', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['slotHeaderHour'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Time grid: Hour', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Numeric', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Numeric', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['slotHeaderMinute'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Time grid: Minute', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'numeric' => esc_html__( 'Numeric', 'bricksextras' ),
				'2-digit' => esc_html__( '2-digit', 'bricksextras' ),
			),
			'placeholder' => esc_html__( '2-digit', 'bricksextras' ),
			'inline'      => true,
		);

		$this->controls['slotHeaderMeridiem'] = array(
			'tab'         => 'content',
			'group'       => 'formats',
			'label'       => esc_html__( 'Time grid: AM/PM', 'bricksextras' ),
			'type'        => 'select',
			'options'     => array(
				'short'  => esc_html__( 'Short', 'bricksextras' ),
				'narrow' => esc_html__( 'Narrow', 'bricksextras' ),
				'false'  => esc_html__( '24-hour', 'bricksextras' ),
			),
			'placeholder' => esc_html__( 'Short', 'bricksextras' ),
			'inline'      => true,
		);

		// Calendar Styling Controls - v7 Variables.

		// Demo events preview (builder only).

		$this->controls['previewSeparator'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Preview', 'bricksextras' ),
			'type'  => 'separator',
		);

		$this->controls['previewDemoEvents'] = array(
			'tab'    => 'content',
			'group'  => 'calendarStyle',
			'label'  => esc_html__( 'Preview demo events (for styling in builder)', 'bricksextras' ),
			'type'   => 'checkbox',
			'inline' => true,
		);

		/* General Colors */
		$this->controls['generalColorsSeparator'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'General Colors', 'bricksextras' ),
			'type'  => 'separator',
		);

		$this->controls['fcBackground'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Background', 'bricks' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-background',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcForeground'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Foreground (Text)', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-foreground',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcBorder'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Border', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-border',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcFaintBg'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Faint Background', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-faint',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcMutedBg'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Muted Background', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-muted',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcStrongBg'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Strong Background', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-strong',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcFaintForeground'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Faint Foreground', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-faint-foreground',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcMutedForeground'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Muted Foreground', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-muted-foreground',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcStrongBorder'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Strong Border', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-strong-border',
					'selector' => $calendar_selector,
				),
			),
		);

		/* Buttons */
		$this->controls['buttonsSeparator'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Buttons', 'bricks' ),
			'type'  => 'separator',
		);

		$this->controls['fcButtonBg'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Background', 'bricks' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-button',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcButtonBorder'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Border', 'bricks' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-button-border',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcButtonForeground'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Text Color', 'bricks' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-button-foreground',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcButtonOutline'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Outline', 'bricks' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-button-outline',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcButtonStrongBg'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Active/Hover Background', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-button-strong',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcButtonStrongBorder'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Active/Hover Border', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-button-strong-border',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcButtonStrongForeground'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Active/Hover Text Color', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-button-strong-foreground',
					'selector' => $calendar_selector,
				),
			),
		);

		/* Events */
		$this->controls['eventsSeparator'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Default event colors', 'bricksextras' ),
			'type'  => 'separator',
		);

		$this->controls['fcEvent'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Background', 'bricks' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-event',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcEventContrast'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Color', 'bricks' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-event-contrast',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcHighlight'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Color', 'bricks' ) . ' (' . esc_html__( 'Highlight', 'bricks' ) . ')',
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-highlight',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcEventPadding'] = array(
			'tab'         => 'content',
			'group'       => 'calendarStyle',
			'label'       => esc_html__( 'Padding', 'bricks' ),
			'type'        => 'number',
			'units'       => true,
			'placeholder' => '3px',
			'css'         => array(
				array(
					'property' => '--fc-classic-event-padding',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['specialSeparator'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Background event', 'bricksextras' ),
			'type'  => 'separator',
		);

		$this->controls['fcBgEventColor'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Color', 'bricks' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-background-event',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcBgEventOpacity'] = array(
			'tab'         => 'content',
			'group'       => 'calendarStyle',
			'label'       => esc_html__( 'Opacity', 'bricks' ),
			'type'        => 'text',
			'inline'      => true,
			'placeholder' => '10%',
			'css'         => array(
				array(
					'property' => '--fc-classic-background-event-opacity',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcBgEventForegroundOpacity'] = array(
			'tab'         => 'content',
			'group'       => 'calendarStyle',
			'label'       => esc_html__( 'Opacity', 'bricks' ). ' (' . esc_html__( 'Text', 'bricksextras' ) . ')',
			'type'        => 'text',
			'inline'      => true,
			'placeholder' => '80%',
			'css'         => array(
				array(
					'property' => '--fc-classic-background-event-foreground-opacity',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcToday'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Background', 'bricks' ) . ' (' . esc_html__( 'Today', 'bricksextras' ) . ')',
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-today',
					'selector' => $calendar_selector,
				),
			),
		);

		$this->controls['fcNow'] = array(
			'tab'   => 'content',
			'group' => 'calendarStyle',
			'label' => esc_html__( 'Now Indicator', 'bricksextras' ),
			'type'  => 'color',
			'css'   => array(
				array(
					'property' => '--fc-classic-now',
					'selector' => $calendar_selector,
				),
			),
		);
	}

	/**
	 * Get available popup templates.
	 *
	 * @return array Array of popup template options.
	 */
	private function get_popup_templates() {
		$templates = array();

		if ( ! bricks_is_builder() ) {
			return $templates;
		}

		$popup_templates = get_posts(
			array(
				'post_type'      => 'bricks_template',
				'posts_per_page' => -1,
				'meta_query'     => array(
					array(
						'key'   => '_bricks_template_type',
						'value' => 'popup',
					),
				),
			)
		);

		foreach ( $popup_templates as $template ) {
			$templates[ $template->ID ] = esc_html( $template->post_title );
		}

		return $templates;
	}

	/**
	 * Enqueue scripts.
	 */
	public function enqueue_scripts() {

		if ( bricks_is_builder_main() ) {
			return;
		}

		// RRULE library (required by FullCalendar RRULE plugin).
		wp_enqueue_script( 'bricksextras-rrule', BRICKSEXTRAS_URL . 'components/assets/js/' . \BricksExtras\Helpers::maybe_minify_scripts( 'rrule' ) . '.js', array( 'bricks-scripts' ), '2.8.1', true );

		// FullCalendar v7 core bundle (includes interaction, daygrid, timegrid, list, multimonth).
		wp_enqueue_script( 'bricksextras-fullcalendar', BRICKSEXTRAS_URL . 'components/assets/js/' . \BricksExtras\Helpers::maybe_minify_scripts( 'fullcalendar' ) . '.js', array( 'bricks-scripts' ), '7.0.0', true );

		// Capture FullCalendar reference immediately after it loads, before other plugins can overwrite it.
		wp_add_inline_script( 'bricksextras-fullcalendar', 'window.BricksExtrasFC = window.FullCalendar;', 'after' );

		// FullCalendar v7 RRULE plugin.
		wp_enqueue_script( 'bricksextras-fullcalendar-rrule', BRICKSEXTRAS_URL . 'components/assets/js/' . \BricksExtras\Helpers::maybe_minify_scripts( 'fullcalendar-rrule' ) . '.js', array( 'bricksextras-rrule', 'bricksextras-fullcalendar' ), '7.0.0', true );

		// FullCalendar v7 Classic theme JS.
		wp_enqueue_script( 'bricksextras-fullcalendar-theme', BRICKSEXTRAS_URL . 'components/assets/js/' . \BricksExtras\Helpers::maybe_minify_scripts( 'fullcalendar-theme' ) . '.js', array( 'bricksextras-fullcalendar' ), '7.0.0', true );

		// Calendar element script.
		wp_enqueue_script( 'x-calendar', BRICKSEXTRAS_URL . 'components/assets/js/' . \BricksExtras\Helpers::maybe_minify_scripts( 'calendar' ) . '.js', array( 'bricksextras-fullcalendar', 'bricksextras-fullcalendar-rrule' ), \BricksExtras\Plugin::VERSION, true );

		// Calendar element custom styles (if any).
		if ( ! \BricksExtras\Helpers::element_css_added( $this->name ) ) {
			wp_enqueue_style( 'x-calendar', BRICKSEXTRAS_URL . 'components/assets/css/calendar.css', array(), \BricksExtras\Plugin::VERSION );
		}
	}

	/**
	 * Convert date string to ISO 8601 format for FullCalendar.
	 *
	 * @param string $date_string Date string in various formats.
	 * @return string ISO 8601 formatted date or empty string.
	 */
	private function convert_to_iso_date( $date_string ) {
		if ( empty( $date_string ) ) {
			return '';
		}

		// If already in ISO format (YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss), normalize it.
		if ( preg_match( '/^\d{4}-\d{2}-\d{2}/', $date_string ) ) {
			// Replace space with T for proper ISO 8601 format.
			$date_string = str_replace( ' ', 'T', $date_string );
			// If it's date-only (no time part), return as is.
			if ( ! preg_match( '/T/', $date_string ) ) {
				return $date_string;
			}
			// Return with T separator.
			return $date_string;
		}

		$date = false;

		// Try common ACF date/time formats.
		// Prioritize ACF return formats (Y-m-d) before display formats (d/m/Y).
		$formats = array(
			'Y-m-d H:i:s',
			'Y-m-d',
			'd/m/Y H:i:s',
			'd/m/Y H:i',
			'd/m/Y g:i a',
			'd/m/Y',
			'm/d/Y H:i:s',
			'm/d/Y H:i',
			'm/d/Y g:i a',
			'm/d/Y',
		);

		foreach ( $formats as $format ) {
			$date = \DateTime::createFromFormat( $format, $date_string );
			if ( false !== $date ) {
				break;
			}
		}

		// If all formats failed, try generic parsing as last resort.
		if ( false === $date ) {
			try {
				$date = new \DateTime( $date_string );
			} catch ( \Exception $e ) {
				return '';
			}
		}

		// Check if time is midnight (00:00:00).
		if ( $date->format( 'H:i:s' ) === '00:00:00' ) {
			// Return date only (YYYY-MM-DD).
			return $date->format( 'Y-m-d' );
		} else {
			// Return date with time (YYYY-MM-DDTHH:mm:ss).
			return $date->format( 'Y-m-d\TH:i:s' );
		}
	}

	/**
	 * Map day name to integer for FullCalendar.
	 *
	 * @param string $day Day name (sunday, monday, etc.).
	 * @return int|null Day number (0-6) or null.
	 */
	private function map_first_day( $day ) {
		$map = array(
			'sunday'    => 0,
			'monday'    => 1,
			'tuesday'   => 2,
			'wednesday' => 3,
			'thursday'  => 4,
			'friday'    => 5,
			'saturday'  => 6,
		);

		return isset( $map[ $day ] ) ? $map[ $day ] : null;
	}

	/**
	 * Validate and sanitise a single RRULE property value.
	 *
	 * Returns the sanitised value (string) or null if the rule name is unknown
	 * or the value is entirely invalid and should be dropped.
	 *
	 * @param string $rule_name  RRULE property name, e.g. 'FREQ'.
	 * @param string $rule_value Raw value string.
	 * @return string|null Sanitised value, or null to drop the rule.
	 */
	private function validate_rrule_value( $rule_name, $rule_value ) {
		$valid_days = array( 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU' );

		switch ( $rule_name ) {

			case 'FREQ':
				$valid = array( 'SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY' );
				$upper = strtoupper( trim( $rule_value ) );
				return in_array( $upper, $valid, true ) ? $upper : null;

			case 'INTERVAL':
				$val = intval( $rule_value );
				return $val >= 1 ? (string) $val : '1';

			case 'COUNT':
				$val = intval( $rule_value );
				return $val >= 1 ? (string) $val : null;

			case 'UNTIL':
				$upper = strtoupper( trim( $rule_value ) );
				// Already in compact local format (no Z) — use as-is.
				if ( preg_match( '/^\d{8}T\d{6}$/', $upper ) ) {
					return $upper;
				}
				// Already in UTC format — strip Z to make local, matching DTSTART.
				if ( preg_match( '/^\d{8}T\d{6}Z$/', $upper ) ) {
					return rtrim( $upper, 'Z' );
				}
				try {
					$dt = new \DateTime( $rule_value );
					// Output as local time (no Z) to match DTSTART local time format.
					return $dt->format( 'Ymd\THis' );
				} catch ( \Exception $e ) {
					return null;
				}

			case 'BYDAY':
				// Each token: optional sign + optional 1-5 ordinal + day abbreviation, e.g. MO, 1MO, -1FR.
				$tokens       = array_map( 'trim', explode( ',', $rule_value ) );
				$valid_tokens = array();
				foreach ( $tokens as $token ) {
					$token = strtoupper( $token );
					if ( preg_match( '/^([+-]?[1-5]?)?(MO|TU|WE|TH|FR|SA|SU)$/', $token ) ) {
						$valid_tokens[] = $token;
					}
				}
				return ! empty( $valid_tokens ) ? implode( ',', $valid_tokens ) : null;

			case 'BYMONTH':
				return $this->validate_rrule_int_list( $rule_value, 1, 12, false );

			case 'BYMONTHDAY':
				return $this->validate_rrule_int_list( $rule_value, -31, 31, true );

			case 'BYYEARDAY':
				return $this->validate_rrule_int_list( $rule_value, -366, 366, true );

			case 'BYWEEKNO':
				return $this->validate_rrule_int_list( $rule_value, -53, 53, true );

			case 'BYSETPOS':
				return $this->validate_rrule_int_list( $rule_value, -366, 366, true );

			case 'BYHOUR':
				return $this->validate_rrule_int_list( $rule_value, 0, 23, false );

			case 'BYMINUTE':
				return $this->validate_rrule_int_list( $rule_value, 0, 59, false );

			case 'BYSECOND':
				return $this->validate_rrule_int_list( $rule_value, 0, 59, false );

			case 'WKST':
				$upper = strtoupper( trim( $rule_value ) );
				return in_array( $upper, $valid_days, true ) ? $upper : null;

			default:
				// Unknown rule name — drop it.
				return null;
		}
	}

	/**
	 * Validate a comma-separated list of integers within a min/max range.
	 *
	 * @param string $value      Raw comma-separated value string.
	 * @param int    $min        Minimum allowed integer value (inclusive).
	 * @param int    $max        Maximum allowed integer value (inclusive).
	 * @param bool   $exclude_zero Whether 0 is disallowed.
	 * @return string|null Sanitised comma-separated string, or null if no valid values remain.
	 */
	private function validate_rrule_int_list( $value, $min, $max, $exclude_zero ) {
		$items = array_map( 'trim', explode( ',', $value ) );
		$valid = array();

		foreach ( $items as $item ) {
			if ( ! is_numeric( $item ) ) {
				continue;
			}
			$int = intval( $item );
			if ( $int < $min || $int > $max ) {
				continue;
			}
			if ( $exclude_zero && 0 === $int ) {
				continue;
			}
			$valid[] = $int;
		}

		return ! empty( $valid ) ? implode( ',', $valid ) : null;
	}

	/**
	 * Build RRULE string from repeater rules.
	 *
	 * @param array $rules Array of rule objects with ruleName and ruleValue.
	 * @return string RRULE string or empty.
	 */
	private function build_rrule( $rules ) {
		if ( empty( $rules ) || ! is_array( $rules ) ) {
			return '';
		}

		$rrule_parts = array();

		foreach ( $rules as $rule ) {
			$rule_name  = isset( $rule['ruleName'] ) ? strtoupper( trim( bricks_render_dynamic_data( $rule['ruleName'] ) ) ) : '';
			$rule_value = isset( $rule['ruleValue'] ) ? bricks_render_dynamic_data( $rule['ruleValue'] ) : '';

			if ( empty( $rule_name ) || empty( $rule_value ) ) {
				continue;
			}

			// Map BYDAY_MONTHLY to BYDAY — it's a user-facing alias for Nth-weekday-of-month patterns.
			if ( 'BYDAY_MONTHLY' === $rule_name ) {
				$rule_name = 'BYDAY';
			}

			// Strip ACF select label suffix (e.g. "WEEKLY : Weekly" → "WEEKLY").
			if ( strpos( $rule_value, ':' ) !== false ) {
				$rule_value = trim( explode( ':', $rule_value )[0] );
			}

			$sanitised = $this->validate_rrule_value( $rule_name, $rule_value );

			if ( null === $sanitised ) {
				// Invalid rule name or value — skip entirely.
				continue;
			}

			if ( ! isset( $rrule_parts[ $rule_name ] ) ) {
				$rrule_parts[ $rule_name ] = $sanitised;
			}
		}

		// FREQ is required — bail without it.
		if ( empty( $rrule_parts['FREQ'] ) ) {
			return '';
		}

		// Rebuild as FREQ-first string for clarity.
		$freq = $rrule_parts['FREQ'];
		unset( $rrule_parts['FREQ'] );
		$ordered_parts = array( 'FREQ=' . $freq );
		foreach ( $rrule_parts as $key => $val ) {
			$ordered_parts[] = $key . '=' . $val;
		}

		$rrule_string = implode( ';', $ordered_parts );

		// Apply structural violation checks (mutual exclusion, rule interactions).
		$rrule_string = $this->validate_and_fix_rrule( $rrule_string );

		return $rrule_string;
	}

	/**
	 * Validate and fix RRULE string to prevent specification violations.
	 *
	 * @param string $rrule_string The RRULE string to validate.
	 * @return string Fixed RRULE string.
	 */
	private function validate_and_fix_rrule( $rrule_string ) {
		if ( empty( $rrule_string ) ) {
			return '';
		}

		// Parse RRULE into components.
		$parts = explode( ';', $rrule_string );
		$rules = array();

		foreach ( $parts as $part ) {
			if ( strpos( $part, '=' ) !== false ) {
				list( $key, $value )   = explode( '=', $part, 2 );
				$rules[ trim( $key ) ] = trim( $value );
			}
		}

		// COUNT and UNTIL are mutually exclusive — prioritize UNTIL.
		if ( isset( $rules['COUNT'] ) && isset( $rules['UNTIL'] ) ) {
			unset( $rules['COUNT'] );
		}

		// BYSETPOS requires at least one BY* rule to filter.
		if ( isset( $rules['BYSETPOS'] ) ) {
			$has_required = isset( $rules['BYDAY'] ) || isset( $rules['BYMONTH'] ) ||
							isset( $rules['BYWEEKNO'] ) || isset( $rules['BYYEARDAY'] ) ||
							isset( $rules['BYMONTHDAY'] );

			if ( false === $has_required ) {
				unset( $rules['BYSETPOS'] );
			}
		}

		// BYWEEKNO is only valid with FREQ=YEARLY.
		if ( isset( $rules['BYWEEKNO'] ) && ( ! isset( $rules['FREQ'] ) || 'YEARLY' !== $rules['FREQ'] ) ) {
			unset( $rules['BYWEEKNO'] );
		}

		// BYYEARDAY conflicts with BYMONTH or BYWEEKNO.
		if ( isset( $rules['BYYEARDAY'] ) && ( isset( $rules['BYMONTH'] ) || isset( $rules['BYWEEKNO'] ) ) ) {
			unset( $rules['BYYEARDAY'] );
		}

		// BYMONTHDAY conflicts with BYYEARDAY.
		if ( isset( $rules['BYMONTHDAY'] ) && isset( $rules['BYYEARDAY'] ) ) {
			unset( $rules['BYYEARDAY'] );
		}

		// Rebuild RRULE string.
		$fixed_parts = array();
		foreach ( $rules as $key => $value ) {
			$fixed_parts[] = $key . '=' . $value;
		}

		return implode( ';', $fixed_parts );
	}

	/**
	 * Process single event from query loop.
	 *
	 * @param array $settings Element settings.
	 * @return string Empty string (we collect events in class property).
	 */
	public function render_event( $settings ) {
		$event = array();

		// Post ID (for popup functionality).
		$event['id'] = get_the_ID();

		// Title.
		$title_field    = isset( $settings['titleField'] ) ? $settings['titleField'] : '{post_title}';
		$event['title'] = sanitize_text_field( bricks_render_dynamic_data( $title_field ) );

		// Start.
		$start_field = isset( $settings['startField'] ) ? $settings['startField'] : '{post_date:Y-m-d}';
		if ( $start_field ) {
			$start_value    = sanitize_text_field( bricks_render_dynamic_data( $start_field ) );
			$event['start'] = $this->convert_to_iso_date( $start_value );
		}

		// End.
		$end_field = isset( $settings['endField'] ) ? $settings['endField'] : '';
		if ( $end_field ) {
			$end_value     = sanitize_text_field( bricks_render_dynamic_data( $end_field ) );
			$converted_end = $this->convert_to_iso_date( $end_value );
			if ( $converted_end ) {
				$event['end'] = $converted_end;
			}
		}

		// All-Day Event - set allDay property for FullCalendar.
		$allday_field = isset( $settings['allDayField'] ) ? $settings['allDayField'] : '';
		if ( $allday_field ) {
			$is_allday = bricks_render_dynamic_data( $allday_field );
			// Check for truthy values: 1, '1', 'true', 'yes', etc.
			if ( in_array( strtolower( trim( $is_allday ) ), array( '1', 'true', 'yes', 'on' ), true ) || 1 === $is_allday ) {
				$event['allDay'] = true;

				// For all-day events, strip time component from start and end dates.
				if ( isset( $event['start'] ) ) {
					$event['start'] = substr( $event['start'], 0, 10 );
				}
				if ( isset( $event['end'] ) ) {
					$event['end'] = substr( $event['end'], 0, 10 );
					// For all-day events, FullCalendar expects exclusive end date (next day).
					$end_date = new \DateTime( $event['end'] );
					$end_date->add( new \DateInterval( 'P1D' ) );
					$event['end'] = $end_date->format( 'Y-m-d' );
				}
			} else {
				$event['allDay'] = false;
			}
		}

		// Location field.
		$location_field = isset( $settings['locationField'] ) ? $settings['locationField'] : '';
		if ( $location_field ) {
			$location = bricks_render_dynamic_data( $location_field );
			if ( ! empty( $location ) ) {
				$event['extendedProps']['location'] = sanitize_text_field( $location );
			}
		}

		// Description field.
		$description_field = isset( $settings['descriptionField'] ) ? $settings['descriptionField'] : '';
		if ( $description_field ) {
			$description = bricks_render_dynamic_data( $description_field );
			if ( ! empty( $description ) ) {
				$event['extendedProps']['description'] = wp_kses_post( $description );
			}
		}

		// If no end date provided and we have a start date, make it a one-day event.
		if ( ! isset( $event['end'] ) && isset( $event['start'] ) && ! empty( $event['start'] ) ) {
			try {
				$start_date = new \DateTime( $event['start'] );
				// For all-day events, end date should be the next day.
				if ( isset( $event['allDay'] ) && $event['allDay'] ) {
					$start_date->add( new \DateInterval( 'P1D' ) );
					$event['end'] = $start_date->format( 'Y-m-d' );
				} else {
					// For timed events, add 1 hour duration.
					$start_date->add( new \DateInterval( 'PT1H' ) );
					$event['end'] = $start_date->format( 'Y-m-d H:i:s' );
				}
			} catch ( \Exception $e ) {
				// Silently fail - event will use start date only.
			}
		}

		// Color.
		$color_field = isset( $settings['colorField'] ) ? $settings['colorField'] : '';
		if ( $color_field ) {
			$color_value    = bricks_render_dynamic_data( $color_field );
			$event['color'] = wp_strip_all_tags( $color_value );
		}

		// Display type.
		$default_display = isset( $settings['eventDisplay'] ) && ! empty( $settings['eventDisplay'] ) ? $settings['eventDisplay'] : 'auto';
		$display_field   = isset( $settings['displayField'] ) ? $settings['displayField'] : '';
		if ( $display_field ) {
			$display_value    = bricks_render_dynamic_data( $display_field );
			$display_value    = strtolower( trim( wp_strip_all_tags( $display_value ) ) );
			$allowed_displays = array( 'auto', 'block', 'list-item', 'background', 'inverse-background', 'none' );
			if ( ! empty( $display_value ) && in_array( $display_value, $allowed_displays, true ) ) {
				$event['display'] = $display_value;
			} else {
				$event['display'] = $default_display;
			}
		} else {
			$event['display'] = $default_display;
		}

		// URL (for link click action).
		$event_click_action = isset( $settings['eventClickAction'] ) ? $settings['eventClickAction'] : 'url';
		if ( 'url' === $event_click_action ) {
			$url_field = isset( $settings['eventUrlField'] ) ? $settings['eventUrlField'] : '{post_url}';
			if ( $url_field ) {
				$event['url'] = esc_url( bricks_render_dynamic_data( $url_field ) );
			}
		}

		// RRULE.
		// Only process recurring rules if enableRecurring is checked.
		$enable_recurring = isset( $settings['enableRecurring'] ) && $settings['enableRecurring'];
		$rrule_rules      = isset( $settings['rruleRules'] ) ? $settings['rruleRules'] : array();
		if ( $enable_recurring && ! empty( $rrule_rules ) ) {
			try {
				$rrule = $this->build_rrule( $rrule_rules );
				// Validate RRULE has required FREQ property.
				if ( $rrule && strpos( $rrule, 'FREQ=' ) !== false ) {
					// If FREQ=WEEKLY and no BYDAY is specified, add BYDAY based on start date.
					if ( strpos( $rrule, 'FREQ=WEEKLY' ) !== false && strpos( $rrule, 'BYDAY' ) === false && isset( $event['start'] ) ) {
						$start_date  = new \DateTime( $event['start'] );
						$day_of_week = strtoupper( substr( $start_date->format( 'D' ), 0, 2 ) );
						$rrule      .= ';BYDAY=' . $day_of_week;
					}

					// Process exclusion dates (EXDATE) - will be kept as separate property for object format.
					$exclusion_field = isset( $settings['exclusionDatesField'] ) ? $settings['exclusionDatesField'] : '';
					if ( $exclusion_field ) {
						$exclusion_dates = bricks_render_dynamic_data( $exclusion_field );
						if ( $exclusion_dates ) {
							$exdate_array             = array();
							$recurring_exclusions     = array();
							// Support both comma-separated and newline-separated dates.
								$dates = preg_split( '/[\r\n,]+/', $exclusion_dates );
								$dates = array_map( 'trim', $dates );
								$dates = array_map( 'sanitize_text_field', $dates );

							foreach ( $dates as $date ) {
								if ( empty( $date ) ) {
									continue;
								}

								// Check if this is a raw RRULE pattern (e.g., FREQ=WEEKLY;BYDAY=MO).
								if ( preg_match( '/^(FREQ=|BYDAY=|BYMONTH=|BYMONTHDAY=|BYSETPOS=|BYYEARDAY=|BYWEEKNO=|INTERVAL=|COUNT=|UNTIL=|WKST=)/i', $date ) ) {
									$recurring_exclusions[] = sanitize_text_field( $date );
									continue;
								}

								// Check if this is a simple recurring pattern (MM-DD format).
								if ( preg_match( '/^(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/', $date ) ) {

									$recurring_exclusions[] = $date;
									continue;
								}

								// Convert to ISO date format (YYYY-MM-DD).
								$converted_date = $this->convert_to_iso_date( $date );
								if ( $converted_date ) {
									// Keep ISO format with hyphens, remove time portion.
									$exdate_value   = substr( $converted_date, 0, 10 );
									$exdate_array[] = $exdate_value;
								}
							}

							if ( ! empty( $exdate_array ) ) {
								// Store as separate property - FullCalendar will handle it.
								$event['exdate'] = $exdate_array;
							}

							if ( ! empty( $recurring_exclusions ) ) {
								// Process recurring exclusions (MM-DD patterns or raw RRULE strings).
								$exrule_array = array();
								foreach ( $recurring_exclusions as $pattern ) {
									// Check if it's a raw RRULE string.
									if ( preg_match( '/^(FREQ=|BYDAY=|BYMONTH=)/i', $pattern ) ) {
										// Store raw RRULE string directly.
										$exrule_array[] = $pattern;
									} else {
										// Pattern is MM-DD (e.g., 12-25) - convert to exrule object.
										list( $month, $day ) = explode( '-', $pattern );
										$exrule_array[] = array(
											'freq'       => 'yearly',
											'bymonth'    => (int) $month,
											'bymonthday' => (int) $day,
										);
									}
								}
								if ( ! empty( $exrule_array ) ) {
									$event['exrule'] = $exrule_array;
								}
							}
						}
					}

					// Process event overrides.
					$overrides_field = isset( $settings['eventOverridesField'] ) ? $settings['eventOverridesField'] : '';
					if ( $overrides_field ) {
						$overrides_raw = bricks_render_dynamic_data( $overrides_field );
						if ( $overrides_raw ) {
							$overrides_array = array();

							// Support both line-separated and comma-separated formats.
							$delimiter = strpos( $overrides_raw, "\n" ) !== false ? "\n" : ',';
							$lines     = explode( $delimiter, trim( $overrides_raw ) );

							foreach ( $lines as $line ) {
								$line = trim( $line );
								if ( empty( $line ) ) {
									continue;
								}

								// Parse format: DATE START_TIME [DURATION]
								// Example: 2026-04-18 18:00 02:00:00 or 2026-04-18 18:00 2.00:00:00.
								$parts = preg_split( '/\s+/', $line, 3 );

								if ( count( $parts ) >= 2 ) {
									$override_date       = $parts[0];
									$override_start_time = $parts[1];
									$override_duration   = isset( $parts[2] ) ? $parts[2] : null;

									// Convert date to ISO format if needed.
									$converted_date = $this->convert_to_iso_date( $override_date );
									if ( $converted_date ) {
										$override_date = substr( $converted_date, 0, 10 );

										$overrides_array[] = array(
											'date'      => $override_date,
											'startTime' => sanitize_text_field( $override_start_time ),
											'duration'  => $override_duration ? sanitize_text_field( $override_duration ) : null,
										);
									}
								}
							}

							if ( ! empty( $overrides_array ) ) {
								// Add override dates to exclusion list so recurring event doesn't show on those dates.
								if ( ! isset( $event['exdate'] ) ) {
									$event['exdate'] = array();
								}
								foreach ( $overrides_array as $override ) {
									$event['exdate'][] = $override['date'];
								}
								
								// Store overrides in event for JavaScript processing.
								$event['eventOverrides'] = $overrides_array;
							}
						}
					}

					// For recurring events, convert end to duration (FullCalendar RRULE best practice).
					// Only do this if start and end are different (skip zero-duration events).
					if ( isset( $event['start'] ) && isset( $event['end'] ) && $event['start'] !== $event['end'] ) {
						try {
							$start_dt = new \DateTime( $event['start'] );
							$end_dt   = new \DateTime( $event['end'] );
							$interval = $start_dt->diff( $end_dt );

							// Build duration string (e.g., "02:00:00" for 2 hours, "1.00:00:00" for 1 day).
							if ( $interval->days > 0 ) {
								$event['duration'] = sprintf( '%d.%02d:%02d:%02d', $interval->days, $interval->h, $interval->i, $interval->s );
							} else {
								$event['duration'] = sprintf( '%02d:%02d:%02d', $interval->h, $interval->i, $interval->s );
							}

							// Remove end date for recurring events (use duration instead).
							unset( $event['end'] );
						} catch ( \Exception $e ) {
							// If date parsing fails, keep the event as is.
						}
					}

					// For recurring events with RRULE, prepend DTSTART to the RRULE string.
					if ( isset( $event['start'] ) && ! empty( $event['start'] ) ) {
						// Convert start time to compact format for DTSTART.
						// For all-day events, use date-only format (YYYYMMDD).
						// For timed events, use full datetime format (YYYYMMDDTHHMMSS).
						$start_dt      = new \DateTime( $event['start'] );
						$dtstart_value = ! empty( $event['allDay'] ) ? $start_dt->format( 'Ymd' ) : $start_dt->format( 'Ymd\THis' );
						$rrule         = 'DTSTART:' . $dtstart_value . "\n" . 'RRULE:' . $rrule;
					}

					// Append EXDATE to RRULE string if exclusion dates exist.
					if ( isset( $event['exdate'] ) && ! empty( $event['exdate'] ) ) {
						foreach ( $event['exdate'] as $exdate ) {
							// Convert YYYY-MM-DD to YYYYMMDD format for RRULE.
							$exdate_compact = str_replace( '-', '', $exdate );
							// Use VALUE=DATE for all-day events, VALUE=DATE-TIME for timed events.
							if ( ! empty( $event['allDay'] ) ) {
								$rrule .= "\n" . 'EXDATE;VALUE=DATE:' . $exdate_compact;
							} else {
								// For timed events, append time from start date.
								$start_dt  = new \DateTime( $event['start'] );
								$time_part = $start_dt->format( '\THis' );
								$rrule    .= "\n" . 'EXDATE;VALUE=DATE-TIME:' . $exdate_compact . $time_part;
							}
						}
						// Remove the exdate property as it's now in the RRULE string.
						unset( $event['exdate'] );
					}

					// Append EXRULE to RRULE string if recurring exclusions exist.
					if ( isset( $event['exrule'] ) && ! empty( $event['exrule'] ) ) {
						foreach ( $event['exrule'] as $exrule ) {
							if ( is_string( $exrule ) ) {
								// Raw RRULE string - use as-is.
								$exrule_string = 'EXRULE:' . strtoupper( $exrule );
							} else {
								// Exrule object - convert to EXRULE string.
								$exrule_parts = array();
								foreach ( $exrule as $key => $value ) {
									$exrule_parts[] = strtoupper( $key ) . '=' . strtoupper( $value );
								}
								$exrule_string = 'EXRULE:' . implode( ';', $exrule_parts );
							}
							$rrule .= "\n" . $exrule_string;
						}
						// Remove the exrule property as it's now in the RRULE string.
						unset( $event['exrule'] );
					}

					$event['rrule'] = $rrule;

					// For same-day events, remove end to let FullCalendar handle as all-day.
					if ( isset( $event['end'] ) && $event['start'] === $event['end'] ) {
						unset( $event['end'] );
					}
				}
			} catch ( \Exception $e ) {
				// Silently skip RRULE — invalid data will not crash the calendar.
			}
		}

		$this->calendar_events[] = $event;

		// Create separate events for overrides (if any).
		if ( isset( $event['eventOverrides'] ) && ! empty( $event['eventOverrides'] ) ) {
			foreach ( $event['eventOverrides'] as $override ) {
				$override_event = array(
					'id'    => $event['id'] . '-override-' . $override['date'],
					'title' => $event['title'],
					'start' => $override['date'] . 'T' . $override['startTime'],
					'allDay' => false,
				);

				// Copy relevant properties from original event.
				if ( isset( $event['display'] ) ) {
					$override_event['display'] = $event['display'];
				}
				if ( isset( $event['color'] ) ) {
					$override_event['color'] = $event['color'];
				}
				if ( isset( $event['url'] ) ) {
					$override_event['url'] = $event['url'];
				}
				if ( isset( $event['extendedProps'] ) ) {
					$override_event['extendedProps'] = $event['extendedProps'];
				}

				// Apply duration if provided, otherwise use original event duration.
				$duration_to_use = null;
				if ( $override['duration'] ) {
					$duration_to_use = $override['duration'];
				} elseif ( isset( $event['duration'] ) ) {
					$duration_to_use = $event['duration'];
				}

				// Calculate end time from start + duration.
				if ( $duration_to_use ) {
					try {
						$start_dt = new \DateTime( $override_event['start'] );
						
						// Parse duration (format: HH:MM:SS or D.HH:MM:SS).
						$duration_parts = explode( '.', $duration_to_use );
						if ( count( $duration_parts ) === 2 ) {
							// Multi-day format: D.HH:MM:SS.
							$days      = (int) $duration_parts[0];
							$time_part = $duration_parts[1];
						} else {
							// Single day format: HH:MM:SS.
							$days      = 0;
							$time_part = $duration_to_use;
						}
						
						// Parse time part.
						$time_components = explode( ':', $time_part );
						$hours           = isset( $time_components[0] ) ? (int) $time_components[0] : 0;
						$minutes         = isset( $time_components[1] ) ? (int) $time_components[1] : 0;
						$seconds         = isset( $time_components[2] ) ? (int) $time_components[2] : 0;
						
						// Add duration to start time.
						if ( $days > 0 ) {
							$start_dt->modify( "+{$days} days" );
						}
						if ( $hours > 0 ) {
							$start_dt->modify( "+{$hours} hours" );
						}
						if ( $minutes > 0 ) {
							$start_dt->modify( "+{$minutes} minutes" );
						}
						if ( $seconds > 0 ) {
							$start_dt->modify( "+{$seconds} seconds" );
						}
						
						$override_event['end'] = $start_dt->format( 'Y-m-d\TH:i:s' );
					} catch ( \Exception $e ) {
						// If calculation fails, fall back to duration.
						$override_event['duration'] = $duration_to_use;
					}
				}

				$this->calendar_events[] = $override_event;
			}
		}

		return '';
	}

	/**
	 * Render element output.
	 */
	public function render() {
		$settings = $this->settings;

		$this->calendar_events = array();

		// Query Loop.
		if ( isset( $settings['hasLoop'] ) ) {
			$query = new \Bricks\Query(
				array(
					'id'       => $this->id,
					'settings' => $settings,
				)
			);

			if ( $query->count > 0 ) {
				$query->render( array( $this, 'render_event' ), compact( 'settings' ) );
			}

			$query->destroy();
			unset( $query );
		}

		$events = $this->calendar_events;

		$events = apply_filters( 'bricksextras/calendar/events', $events, $this->id );

		// Calendar config.
		$config = array(
			'initialView'               => isset( $settings['initialView'] ) ? $settings['initialView'] : 'dayGridMonth',
			'multiMonthDuration'        => isset( $settings['multiMonthDuration'] ) && ! empty( $settings['multiMonthDuration'] ) ? (int) $settings['multiMonthDuration'] : 4,
			'multiMonthMaxColumns'      => isset( $settings['multiMonthMaxColumns'] ) && ! empty( $settings['multiMonthMaxColumns'] ) ? (int) $settings['multiMonthMaxColumns'] : 2,
			'singleMonthMinWidth'       => isset( $settings['singleMonthMinWidth'] ) && ! empty( $settings['singleMonthMinWidth'] ) ? (int) $settings['singleMonthMinWidth'] : 350,
			'height'                    => isset( $settings['calendarHeight'] ) && ! empty( $settings['calendarHeight'] ) ? $settings['calendarHeight'] : 'auto',
			'progressiveEventRendering' => isset( $settings['progressiveEventRendering'] ),
			'firstDay'                  => $this->map_first_day( isset( $settings['firstDay'] ) ? $settings['firstDay'] : '' ),
			'weekNumbers'               => isset( $settings['weekNumbers'] ) && $settings['weekNumbers'] ? true : false,
			'navLinks'                  => isset( $settings['navLinks'] ),
			'nowIndicator'              => isset( $settings['nowIndicator'] ) && $settings['nowIndicator'] ? true : false,
			'weekends'                  => isset( $settings['weekends'] ) && 'false' === $settings['weekends'] ? false : true,
			'displayEventTime'          => isset( $settings['displayEventTime'] ) && $settings['displayEventTime'] ? true : false,
			'displayEventEnd'           => isset( $settings['displayEventEnd'] ) && $settings['displayEventEnd'] ? true : false,
			'eventClickAction'          => isset( $settings['eventClickAction'] ) ? $settings['eventClickAction'] : 'url',
			'popupTemplate'             => isset( $settings['eventPopup'] ) ? (int) $settings['eventPopup'] : 0,
			'eventUrlTarget'            => isset( $settings['eventUrlTarget'] ) && $settings['eventUrlTarget'] ? '_blank' : '_self',
			'autoLocale'                => isset( $settings['autoLocale'] ) && 'enable' === $settings['autoLocale'] ? true : false,
			'manualLocale'              => isset( $settings['manualLocale'] ) && ! empty( $settings['manualLocale'] ) ? esc_attr( bricks_render_dynamic_data( $settings['manualLocale'] ) ) : '',
			'allDayText'                => isset( $settings['labelAllDay'] ) && ! empty( $settings['labelAllDay'] ) ? esc_html( $settings['labelAllDay'] ) : 'all-day',
			'allDaySlot'                => isset( $settings['allDaySlot'] ) && 'false' === $settings['allDaySlot'] ? false : true,
			'noEventsText'              => isset( $settings['labelNoEvents'] ) && ! empty( $settings['labelNoEvents'] ) ? esc_html( $settings['labelNoEvents'] ) : 'No events to display',
		);

		$button_text = array(
			'multiMonthYear' => 'Multi-Month',
		);

		if ( isset( $settings['labelMultiMonthYear'] ) && ! empty( $settings['labelMultiMonthYear'] ) ) {
			$button_text['multiMonthYear'] = esc_html( bricks_render_dynamic_data( $settings['labelMultiMonthYear'] ) );
		}

		{
		if ( isset( $settings['labelToday'] ) && ! empty( $settings['labelToday'] ) ) {
			$button_text['today'] = esc_html( bricks_render_dynamic_data( $settings['labelToday'] ) );
		}
		if ( isset( $settings['labelPrev'] ) && ! empty( $settings['labelPrev'] ) ) {
			$button_text['prev'] = esc_html( bricks_render_dynamic_data( $settings['labelPrev'] ) );
		}
		if ( isset( $settings['labelNext'] ) && ! empty( $settings['labelNext'] ) ) {
			$button_text['next'] = esc_html( bricks_render_dynamic_data( $settings['labelNext'] ) );
		}
		if ( isset( $settings['labelPrevYear'] ) && ! empty( $settings['labelPrevYear'] ) ) {
			$button_text['prevYear'] = esc_html( bricks_render_dynamic_data( $settings['labelPrevYear'] ) );
		}
		if ( isset( $settings['labelNextYear'] ) && ! empty( $settings['labelNextYear'] ) ) {
			$button_text['nextYear'] = esc_html( bricks_render_dynamic_data( $settings['labelNextYear'] ) );
		}
		if ( isset( $settings['labelDayGridYear'] ) && ! empty( $settings['labelDayGridYear'] ) ) {
			$button_text['dayGridYear'] = esc_html( bricks_render_dynamic_data( $settings['labelDayGridYear'] ) );
		}
		if ( isset( $settings['labelDayGridMonth'] ) && ! empty( $settings['labelDayGridMonth'] ) ) {
			$button_text['dayGridMonth'] = esc_html( bricks_render_dynamic_data( $settings['labelDayGridMonth'] ) );
		}
		if ( isset( $settings['labelDayGridWeek'] ) && ! empty( $settings['labelDayGridWeek'] ) ) {
			$button_text['dayGridWeek'] = esc_html( bricks_render_dynamic_data( $settings['labelDayGridWeek'] ) );
		}
		if ( isset( $settings['labelDayGridDay'] ) && ! empty( $settings['labelDayGridDay'] ) ) {
			$button_text['dayGridDay'] = esc_html( bricks_render_dynamic_data( $settings['labelDayGridDay'] ) );
		}
		if ( isset( $settings['labelTimeGridWeek'] ) && ! empty( $settings['labelTimeGridWeek'] ) ) {
			$button_text['timeGridWeek'] = esc_html( bricks_render_dynamic_data( $settings['labelTimeGridWeek'] ) );
		}
		if ( isset( $settings['labelTimeGridDay'] ) && ! empty( $settings['labelTimeGridDay'] ) ) {
			$button_text['timeGridDay'] = esc_html( bricks_render_dynamic_data( $settings['labelTimeGridDay'] ) );
		}
		if ( isset( $settings['labelListDay'] ) && ! empty( $settings['labelListDay'] ) ) {
			$button_text['listDay'] = esc_html( bricks_render_dynamic_data( $settings['labelListDay'] ) );
		}
		if ( isset( $settings['labelListWeek'] ) && ! empty( $settings['labelListWeek'] ) ) {
			$button_text['listWeek'] = esc_html( bricks_render_dynamic_data( $settings['labelListWeek'] ) );
		}
		if ( isset( $settings['labelListMonth'] ) && ! empty( $settings['labelListMonth'] ) ) {
			$button_text['listMonth'] = esc_html( bricks_render_dynamic_data( $settings['labelListMonth'] ) );
		}
		if ( isset( $settings['labelListYear'] ) && ! empty( $settings['labelListYear'] ) ) {
			$button_text['listYear'] = esc_html( bricks_render_dynamic_data( $settings['labelListYear'] ) );
		}

			$config['buttonText'] = $button_text;
		}

		// Add optional config values only if they're set (avoid passing null to FullCalendar).
		if ( isset( $settings['aspectRatio'] ) && ! empty( $settings['aspectRatio'] ) ) {
			$config['aspectRatio'] = floatval( $settings['aspectRatio'] );
		}
		if ( isset( $settings['dayMaxEvents'] ) && '' !== $settings['dayMaxEvents'] ) {
			$config['dayMaxEvents'] = (int) $settings['dayMaxEvents'];
		}
		if ( isset( $settings['slotMinTime'] ) && ! empty( $settings['slotMinTime'] ) ) {
			$config['slotMinTime'] = sanitize_text_field( $settings['slotMinTime'] );
		}
		if ( isset( $settings['slotMaxTime'] ) && ! empty( $settings['slotMaxTime'] ) ) {
			$config['slotMaxTime'] = sanitize_text_field( $settings['slotMaxTime'] );
		}
		if ( isset( $settings['slotDuration'] ) && ! empty( $settings['slotDuration'] ) ) {
			$config['slotDuration'] = sanitize_text_field( $settings['slotDuration'] );
		}
		if ( isset( $settings['slotHeaderInterval'] ) && ! empty( $settings['slotHeaderInterval'] ) ) {
			$config['slotHeaderInterval'] = sanitize_text_field( $settings['slotHeaderInterval'] );
		}
		if ( isset( $settings['scrollTime'] ) && ! empty( $settings['scrollTime'] ) ) {
			$config['scrollTime'] = sanitize_text_field( $settings['scrollTime'] );
		}
		if ( isset( $settings['dayHeaders'] ) && ! empty( $settings['dayHeaders'] ) ) {
			$config['dayHeaders'] = 'true' === $settings['dayHeaders'] ? true : false;
		}
		if ( isset( $settings['dayMinWidth'] ) && ! empty( $settings['dayMinWidth'] ) ) {
			$config['dayMinWidth'] = (int) $settings['dayMinWidth'];
		}
		if ( isset( $settings['dayNarrowWidth'] ) && ! empty( $settings['dayNarrowWidth'] ) ) {
			$config['dayNarrowWidth'] = (int) $settings['dayNarrowWidth'];
		}
		if ( isset( $settings['hiddenDays'] ) && ! empty( $settings['hiddenDays'] ) ) {
			$config['hiddenDays'] = array_map( array( $this, 'map_first_day' ), $settings['hiddenDays'] );
		}
		if ( isset( $settings['eventDisplay'] ) && ! empty( $settings['eventDisplay'] ) ) {
			$config['eventDisplay'] = $settings['eventDisplay'];
		}

		// Pass preview demo events flag.
		if ( isset( $settings['previewDemoEvents'] ) ) {
			$config['previewDemoEvents'] = true;
		}

		// Date & Time Format settings.
		if ( isset( $settings['titleMonth'] ) && ! empty( $settings['titleMonth'] ) ) {
			$config['titleMonth'] = $settings['titleMonth'];
		}
		if ( isset( $settings['titleYear'] ) && ! empty( $settings['titleYear'] ) ) {
			$config['titleYear'] = $settings['titleYear'];
		}
		if ( isset( $settings['weekNumberFormat'] ) && ! empty( $settings['weekNumberFormat'] ) ) {
			$config['weekNumberFormat'] = $settings['weekNumberFormat'];
		}
		if ( isset( $settings['listDayWeekday'] ) && ! empty( $settings['listDayWeekday'] ) ) {
			$config['listDayWeekday'] = $settings['listDayWeekday'];
		}
		if ( isset( $settings['listDayMonth'] ) && ! empty( $settings['listDayMonth'] ) ) {
			$config['listDayMonth'] = $settings['listDayMonth'];
		}
		if ( isset( $settings['listDayDay'] ) && ! empty( $settings['listDayDay'] ) ) {
			$config['listDayDay'] = $settings['listDayDay'];
		}
		if ( isset( $settings['eventTimeHour'] ) && ! empty( $settings['eventTimeHour'] ) ) {
			$config['eventTimeHour'] = $settings['eventTimeHour'];
		}
		if ( isset( $settings['eventTimeMinute'] ) && ! empty( $settings['eventTimeMinute'] ) ) {
			$config['eventTimeMinute'] = $settings['eventTimeMinute'];
		}
		if ( isset( $settings['eventTimeMeridiem'] ) && ! empty( $settings['eventTimeMeridiem'] ) ) {
			$config['eventTimeMeridiem'] = $settings['eventTimeMeridiem'];
		}
		if ( isset( $settings['slotHeaderHour'] ) && ! empty( $settings['slotHeaderHour'] ) ) {
			$config['slotHeaderHour'] = $settings['slotHeaderHour'];
		}
		if ( isset( $settings['slotHeaderMinute'] ) && ! empty( $settings['slotHeaderMinute'] ) ) {
			$config['slotHeaderMinute'] = $settings['slotHeaderMinute'];
		}
		if ( isset( $settings['slotHeaderMeridiem'] ) && ! empty( $settings['slotHeaderMeridiem'] ) ) {
			$config['slotHeaderMeridiem'] = $settings['slotHeaderMeridiem'];
		}

		// Build headerToolbar if enabled.
		if ( isset( $settings['headerToolbar'] ) && $settings['headerToolbar'] ) {
			$header_left   = isset( $settings['headerToolbarLeft'] ) && is_array( $settings['headerToolbarLeft'] ) ? implode( ',', $settings['headerToolbarLeft'] ) : '';
			$header_center = isset( $settings['headerToolbarCenter'] ) && is_array( $settings['headerToolbarCenter'] ) ? implode( ',', $settings['headerToolbarCenter'] ) : '';
			$header_right  = isset( $settings['headerToolbarRight'] ) && is_array( $settings['headerToolbarRight'] ) ? implode( ',', $settings['headerToolbarRight'] ) : '';

			// Replace 'spacer' with space character for FullCalendar.
			$header_left   = str_replace( ',spacer,', ' ', $header_left );
			$header_center = str_replace( ',spacer,', ' ', $header_center );
			$header_right  = str_replace( ',spacer,', ' ', $header_right );

			$config['headerToolbar'] = array(
				'left'   => $header_left,
				'center' => $header_center,
				'right'  => $header_right,
			);
		} else {
			$config['headerToolbar'] = false;
		}

		// Build footerToolbar if enabled.
		if ( isset( $settings['footerToolbar'] ) && $settings['footerToolbar'] ) {
			$footer_left   = isset( $settings['footerToolbarLeft'] ) && is_array( $settings['footerToolbarLeft'] ) ? implode( ',', $settings['footerToolbarLeft'] ) : '';
			$footer_center = isset( $settings['footerToolbarCenter'] ) && is_array( $settings['footerToolbarCenter'] ) ? implode( ',', $settings['footerToolbarCenter'] ) : '';
			$footer_right  = isset( $settings['footerToolbarRight'] ) && is_array( $settings['footerToolbarRight'] ) ? implode( ',', $settings['footerToolbarRight'] ) : '';

			// Replace 'spacer' with space character for FullCalendar.
			$footer_left   = str_replace( ',spacer,', ' ', $footer_left );
			$footer_center = str_replace( ',spacer,', ' ', $footer_center );
			$footer_right  = str_replace( ',spacer,', ' ', $footer_right );

			$config['footerToolbar'] = array(
				'left'   => $footer_left,
				'center' => $footer_center,
				'right'  => $footer_right,
			);
		}

		// Generate and set a unique identifier for this instance.
		$identifier = \BricksExtras\Helpers::set_identifier_attribute( $this );

		$this->set_attribute( '_root', 'class', 'x-calendar-wrapper' );
		$this->set_attribute( '_root', 'data-query-id', esc_attr( $this->id ) );
		$this->set_attribute( '_root', 'data-x-calendar-events', wp_json_encode( $events ) );
		$this->set_attribute( '_root', 'data-x-calendar-config', wp_json_encode( $config ) );

		if ( BricksExtras\Helpers::maybe_preview() ) {
			if ( isset( $settings['previewDemoEvents'] ) ) {
				$this->set_attribute( '_root', 'data-x-calendar-demo', 'true' );
			} else {
				$this->set_attribute( '_root', 'data-x-calendar-demo', 'false' );
			}
		}

		$output  = "<div {$this->render_attributes( '_root' )}>";
		$output .= "<div class='x-calendar'></div>";
		$output .= '</div>';

		echo $output;
	}
}
