<?php

namespace SugarCalendarBookings\Support\Helpers;

use SugarCalendarBookings\Admin\Area;
use SugarCalendarBookings\Appointments\AppointmentModel;
use SugarCalendarBookings\Locations\AbstractLocation;
use SugarCalendarBookings\Migration\Sources\Amelia\AmeliaAdapter;
use SugarCalendarBookings\Payments\PaymentModel;

/**
 * Admin interface helpers.
 *
 * @since 1.0.0
 */
class UI {

	/**
	 * Render the dashboard header.
	 *
	 * @since 1.0.0
	 *
	 * @return void
	 */
	public static function header() {

		?>
		<div id="sugar-calendar-bookings-header" class="sugar-calendar-bookings-header">
			<img class="sugar-calendar-bookings-header-logo"
				src="<?php echo esc_url( SCBOOKINGS_PLUGIN_URL . 'assets/images/logo.svg' ); ?>"
				alt="<?php esc_attr_e( 'Sugar Calendar Logo', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>" />
			<a href="<?php echo esc_url( Helpers::get_utm_url( 'https://sugarcalendar.com/docs/bookings/', [ 'content' => 'Help' ] ) ); ?>" target="_blank" id="sugar-calendar-bookings-header-help">
				<?php esc_html_e( 'Help', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
			</a>
		</div>
		<?php
	}

	/**
	 * Render a tab navigation menu.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $tabs     List of tabs.
	 * @param string $selected Selected tab id.
	 *
	 * @return void
	 */
	public static function tabs( $tabs, $selected ) {

		if ( empty( $tabs ) ) {
			return;
		}

		// Sort the tabs by priority.
		uasort( $tabs, fn( $a, $b ) => $a['priority'] <= $b['priority'] ? - 1 : 1 );

		if ( empty( $selected ) ) {
			$selected = array_key_first( $tabs );
		}

		/**
		 * Filter the navigation items before they are used to generate HTML.
		 *
		 * @since 1.0.0
		 *
		 * @param array  $tabs     List of tabs.
		 * @param string $selected Selected tab id.
		 */
		$tabs = (array) apply_filters( 'sugar_calendar_bookings_admin_nav_items', $tabs, $selected ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName

		/**
		 * Fires before the admin navigation.
		 *
		 * @since 1.0.0
		 *
		 * @param array  $tabs     List of tabs.
		 * @param string $selected Selected tab id.
		 */
		do_action( 'sugar_calendar_bookings_admin_nav_before_wrapper', $tabs, $selected ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
		?>

		<ul class="sugar-calendar-bookings-admin-tabs">
			<?php

			/**
			 * Fires before the admin navigation inside the wrapper.
			 *
			 * @since 1.0.0
			 *
			 * @param array  $tabs     List of tabs.
			 * @param string $selected Selected tab id.
			 */
			do_action( 'sugar_calendar_bookings_admin_nav_before_items', $tabs, $selected ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
			?>

			<?php foreach ( $tabs as $nav_id => $nav ) : ?>

				<li>
					<a href="<?php echo esc_url( $nav['url'] ); ?>"
						class="<?php echo esc_attr( ( $selected === $nav_id ) ? 'active' : '' ); ?>"><?php echo esc_html( $nav['name'] ); ?></a>
				</li>

			<?php endforeach; ?>

		</ul>

		<?php
		/**
		 * Fires after the admin navigation.
		 *
		 * @since 1.0.0
		 *
		 * @param array  $tabs     List of tabs.
		 * @param string $selected Selected tab id.
		 */
		do_action( 'sugar_calendar_bookings_admin_nav_after_wrapper', $tabs, $selected ); // phpcs:ignore WPForms.PHP.ValidateHooks.InvalidHookName
	}

	/**
	 * Get a name attribute.
	 *
	 * @since 1.0.0
	 *
	 * @param string|array $name Name, or list of names.
	 */
	private static function get_name_attribute( $name ) {

		$name = is_array( $name ) ? $name : [ $name ];
		$name = array_map( fn( $name ) => "[{$name}]", $name );
		$name = implode( '', $name );
		$name = esc_attr( $name );

		return "sugar-calendar-bookings$name";
	}

	/**
	 * Generate data attributes string for HTML elements.
	 *
	 * @since 1.0.0
	 *
	 * @param array $data_attributes Array of data attributes.
	 *
	 * @return string
	 */
	private static function get_data_attributes( $data_attributes ) {

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

		$output = '';

		foreach ( $data_attributes as $key => $value ) {
			$output .= sprintf(
				' data-%s="%s"',
				esc_attr( $key ),
				esc_attr( $value )
			);
		}

		return $output;
	}

	/**
	 * Output a select setting control.
	 *
	 * Multi-select data attributes.
	 * $args['data']['placeholder']        - Placeholder text.
	 * $args['data']['search-placeholder'] - Search placeholder text.
	 * $args['data']['max-items']          - Maximum number of items to select.
	 *
	 * @since 1.0.0
	 * @since 1.3.0 Added support for ChoiceJS multi-select.
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output the control wrapper.
	 */
	public static function select_input( $args, $bare = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh,Generic.Metrics.CyclomaticComplexity.MaxExceeded

		$args = wp_parse_args(
			$args,
			[
				'type'        => 'select',
				'id'          => '',
				'class'       => '',
				'name'        => '',
				'options'     => [],
				'value'       => '',
				'description' => '',
				'choicejs'    => false,
				'data'        => [],
				'disabled'    => false,
				'multiple'    => false,
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$choicejs = (bool) $args['choicejs'];
		$multiple = $choicejs && (bool) $args['multiple'];
		$class    = trim( (string) $args['class'] );
		$class    = $choicejs ? $class . ' choicesjs-select' : $class;
		$class    = $multiple ? $class . ' choicesjs-select-multiple choicesjs-select-multiple-is-loading' : $class;
		$name     = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$options  = $args['options'];
		$value    = is_array( $args['value'] ) ? $args['value'] : [ $args['value'] ];

		ob_start();
		?>

		<?php if ( $choicejs ) : ?>

			<span class="sugar-calendar-bookings-dropdown-control">

		<?php endif; ?>

		<select
			name="<?php echo esc_attr( $name ); ?>"
			id="<?php echo esc_attr( $id ); ?>"
			class="<?php echo esc_attr( $class ); ?>"
			<?php echo wp_kses_post( self::get_data_attributes( $args['data'] ) ); ?>
			<?php disabled( (bool) $args['disabled'] ); ?>
			<?php echo $multiple ? 'multiple' : ''; ?>
		>
			<?php foreach ( $options as $option_value => $option_label ) : ?>
				<?php
				$option_enabled = true;

				if ( is_array( $option_label ) ) {
					[ $option_label, $option_enabled ] = $option_label;
				}
				?>
				<option
					value="<?php echo esc_attr( $option_value ); ?>"
					<?php disabled( ! (bool) $option_enabled ); ?>
					<?php echo in_array( $option_value, $value, true ) ? 'selected' : ''; ?>
				>
					<?php echo esc_html( $option_label ); ?>
				</option>
			<?php endforeach; ?>
		</select>

		<?php if ( $choicejs ) : ?>

			</span>

		<?php endif; ?>

		<?php
		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a radio input control.
	 *
	 * Renders a group of radio buttons sharing one name. Options are a
	 * value => label map; the currently-selected value matches $args['value'].
	 *
	 * $args['id']          - Input id. Prefixed with "sugar-calendar-bookings-setting-" unless bare.
	 * $args['name']        - Input name (dot-notation supported; converted to bracket form).
	 * $args['options']     - Map of value => label.
	 * $args['value']       - Currently-selected value.
	 * $args['description'] - Description text shown beneath the group.
	 * $args['disabled']    - Whether all radios are disabled.
	 *
	 * @since 1.5.0
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to skip the field wrapper.
	 */
	public static function radio_input( $args, $bare = false ) {

		$args = wp_parse_args(
			$args,
			[
				'type'        => 'radio',
				'id'          => '',
				'name'        => '',
				'options'     => [],
				'value'       => '',
				'description' => '',
				'disabled'    => false,
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name    = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$options = $args['options'];
		$value   = (string) $args['value'];

		ob_start();
		?>

		<?php foreach ( $options as $option_value => $option_label ) : ?>
			<?php $radio_id = $id . '-' . sanitize_key( (string) $option_value ); ?>
			<span class="sugar-calendar-bookings-settings-field-radio-wrapper">
				<input
					type="radio"
					name="<?php echo esc_attr( $name ); ?>"
					id="<?php echo esc_attr( $radio_id ); ?>"
					value="<?php echo esc_attr( $option_value ); ?>"
					<?php disabled( (bool) $args['disabled'] ); ?>
					<?php checked( (string) $option_value, $value ); ?>
				>
				<label for="<?php echo esc_attr( $radio_id ); ?>"><?php echo esc_html( $option_label ); ?></label>
			</span>
		<?php endforeach; ?>

		<?php
		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a select control wrapper.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Description arguments.
	 */
	private static function field_description( $args ) {

		$description = ! empty( $args['description'] ) ? $args['description'] : '';
		$warning     = ! empty( $args['description_warning'] ) ? $args['description_warning'] : '';

		if ( empty( $description ) && empty( $warning ) ) {
			return;
		}

		$parts = [];

		if ( $description ) {
			$parts[] = wp_kses_post( $description );
		}

		if ( $warning ) {
			$parts[] = '<span class="desc-warning">' . wp_kses_post( $warning ) . '</span>';
		}

		?>

		<?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $parts are sanitized via wp_kses_post() above. ?>
		<p class="desc"><?php echo implode( '<br>', $parts ); ?></p>

		<?php
	}

	/**
	 * Output a select control wrapper.
	 *
	 * @since 1.0.0
	 *
	 * @param array  $args    Wrapper arguments.
	 * @param string $content Wrapper contents.
	 */
	public static function field_wrapper( $args, $content = '' ) {

		$args = wp_parse_args(
			$args,
			[
				'type'  => '',
				'id'    => '',
				'class' => [],
			]
		);

		// HTML class.
		$classes = [
			'sugar-calendar-bookings-setting-row',
			'sugar-calendar-bookings-clear',
		];

		if ( ! empty( $args['class'] ) ) {
			$class   = is_array( $args['class'] ) ? $args['class'] : [ $args['class'] ];
			$classes = array_merge( $classes, $class );
		}

		$type = sanitize_key( $args['type'] );

		if ( ! empty( $type ) ) {
			$classes[] = "sugar-calendar-bookings-setting-row-{$type}";
		}

		$classes = self::sanitize_class( $classes );

		// HTML id.
		$row_id = '';
		$id     = sanitize_key( $args['id'] );

		if ( ! empty( $id ) ) {
			$row_id = "sugar-calendar-bookings-setting-row-{$id}";
			$id     = "sugar-calendar-bookings-setting-{$id}";
		}
		?>

		<div id="<?php echo esc_attr( $row_id ); ?>" class="<?php echo esc_attr( $classes ); ?>">

			<?php if ( ! empty( $args['label'] ) ) : ?>

				<span class="sugar-calendar-bookings-setting-label">
					<label for="<?php echo esc_attr( $id ); ?>"><?php echo esc_html( $args['label'] ); ?></label>
				</span>

			<?php endif; ?>

			<span class="sugar-calendar-bookings-setting-field"><?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>

		</div>

		<?php
	}

	/**
	 * Sanitize one or more HTML classes.
	 *
	 * @since 1.0.0
	 *
	 * @param string|array $class_name HTML classes.
	 *
	 * @return string
	 */
	public static function sanitize_class( $class_name ) {

		$class_name = is_array( $class_name ) ? $class_name : [ $class_name ];
		$class_name = array_map( 'sanitize_html_class', $class_name );
		$class_name = array_filter( $class_name );
		$class_name = array_unique( $class_name );
		$class_name = implode( ' ', $class_name );

		return $class_name;
	}

	/**
	 * Output a number setting control.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output the control wrapper.
	 */
	public static function number_input( $args, $bare = false ) {

		$args = wp_parse_args(
			$args,
			[
				'type'         => 'number',
				'id'           => '',
				'name'         => '',
				'value'        => '',
				'description'  => '',
				'input_mode'   => 'numeric',
				'step'         => 1,
				'min'          => 0,
				'max'          => '',
				'inline_label' => '',
				'placeholder'  => '',
				'data'         => [],
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name        = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$value       = $args['value'];
		$input_mode  = $args['input_mode'];
		$step        = $args['step'];
		$min         = $args['min'];
		$max         = $args['max'];
		$placeholder = $args['placeholder'];

		ob_start();
		?>

		<span class="sugar-calendar-bookings-setting-input">
			<input
				type="number"
				name="<?php echo esc_attr( $name ); ?>"
				value="<?php echo esc_attr( $value ); ?>"
				id="<?php echo esc_attr( $id ); ?>"
				inputMode="<?php echo esc_attr( $input_mode ); ?>"
				step="<?php echo esc_attr( $step ); ?>"
				min="<?php echo esc_attr( $min ); ?>"
				max="<?php echo esc_attr( $max ); ?>"
				placeholder="<?php echo esc_attr( $placeholder ); ?>"
				<?php echo wp_kses_post( self::get_data_attributes( $args['data'] ) ); ?>
			/>

			<?php if ( ! empty( $args['inline_label'] ) ) : ?>
				<label for="<?php echo esc_attr( $id ); ?>"><?php echo esc_html( $args['inline_label'] ); ?></label>
			<?php endif; ?>
		</span>

		<?php
		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a date/time format setting control.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Control arguments.
	 */
	public static function date_time_format_control( $args ) {

		$args = wp_parse_args(
			$args,
			[
				'type'        => 'date_time_format',
				'id'          => '',
				'name'        => '',
				'formats'     => [],
				'value'       => '',
				'description' => '',
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name    = self::get_name_attribute( $args['name'] );
		$value   = $args['value'];
		$formats = $args['formats'];
		$i       = 0;

		ob_start();
		?>

		<?php foreach ( $formats as $format ) : ?>

			<span class="sugar-calendar-bookings-settings-field-radio-wrapper">
				<input
					type="radio"
					name="<?php echo esc_attr( $name ); ?>"
					id="<?php echo esc_attr( $id ); ?>_<?php echo esc_attr( $i ); ?>"
					value="<?php echo esc_attr( $format ); ?>"
					<?php checked( $format, $value ); ?>
				>
				<label for="<?php echo esc_attr( $id ); ?>_<?php echo esc_attr( $i ); ?>">
					<span data-format-i18n><?php echo esc_html( wp_date( $format, null, Date::get_default_timezone() ) ); ?></span>
					<code><?php echo esc_html( $format ); ?></code>
				</label>
			</span>

			<?php ++$i; ?>

		<?php endforeach; ?>

		<span class="sugar-calendar-bookings-settings-field-radio-wrapper">
			<input
				type="radio"
				name="<?php echo esc_attr( $name ); ?>"
				id="<?php echo esc_attr( $id ); ?>_custom"
				value="custom" <?php checked( ! in_array( $value, $formats, true ) ); ?>
				data-custom-option
			/>
			<label for="<?php echo esc_attr( $id ); ?>_custom"><?php esc_html_e( 'Custom', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></label>
			<input
				type="text"
				name="<?php echo esc_attr( $name ); ?>"
				id="<?php echo esc_attr( $id ); ?>_custom_format"
				class="sugar-calendar-bookings-custom-date-time-format"
				value="<?php echo esc_attr( $value ); ?>"
				data-custom-field
			/>
		</span>

		<p class="desc">
			<strong><?php esc_html_e( 'Looks Like:', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></strong>
			<span data-format-example><?php echo esc_html( wp_date( $value, null, Date::get_default_timezone() ) ); ?></span>
			<span class="spinner" data-spinner></span>
		</p>

		<?php
		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a toggle setting control.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output the control wrapper.
	 *
	 * @return void
	 */
	public static function toggle_control( $args, $bare = false ) {

		$args = wp_parse_args(
			$args,
			[
				'type'                => 'toggle',
				'id'                  => '',
				'name'                => '',
				'value'               => '1',
				'disabled'            => false,
				'description'         => '',
				'description_warning' => '',
				'toggle_labels'       => [
					esc_html__( 'On', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					esc_html__( 'Off', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				],
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name     = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$value    = (bool) $args['value'];
		$disabled = (bool) $args['disabled'];

		[ $toggle_label_on, $toggle_label_off ] = $args['toggle_labels'];

		ob_start();
		?>

		<span class="sugar-calendar-bookings-toggle-control">
			<input
				type="checkbox"
				id="<?php echo esc_attr( $id ); ?>"
				name="<?php echo esc_attr( $name ); ?>"
				value="1"
				<?php disabled( $disabled ); ?>
				<?php checked( $value ); ?>
			>
			<label class="sugar-calendar-bookings-toggle-control-icon" for="<?php echo esc_attr( $id ); ?>"></label>
			<label class="sugar-calendar-bookings-toggle-control-status sugar-calendar-bookings-toggle-control-status-on" for="<?php echo esc_attr( $id ); ?>"><?php echo esc_html( $toggle_label_on ); ?></label>
			<label class="sugar-calendar-bookings-toggle-control-status sugar-calendar-bookings-toggle-control-status-off" for="<?php echo esc_attr( $id ); ?>"><?php echo esc_html( $toggle_label_off ); ?></label>
		</span>

		<?php
		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a toggle group control.
	 *
	 * @since 1.0.0
	 *
	 * @param array    $args     Control arguments.
	 * @param callable $callback Callback function.
	 *
	 * @return void
	 */
	public static function toggle_group( $args, $callback = null ) {

		$args = wp_parse_args(
			$args,
			[
				'id'                => '',
				'group_description' => '',
				'value'             => '1',
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) ) {
			$id = "sugar-calendar-bookings-setting-group-{$id}";
		}

		$value = (bool) $args['value'];
		?>

		<div class="sugar-calendar-bookings-setting-group" id="<?php echo esc_attr( $id ); ?>">

			<?php static::toggle_control( $args ); ?>

			<div class="sugar-calendar-bookings-setting-group-contents" <?php echo $value ? '' : 'style="display: none;"'; ?>>

				<?php
				if ( is_callable( $callback ) ) {
					$callback();
				}
				?>

			</div>

		</div>

		<?php
	}

	/**
	 * Output a heading setting control.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Control arguments.
	 *
	 * @return void
	 */
	public static function heading( $args ) {

		$args = wp_parse_args(
			$args,
			[
				'type'  => 'heading',
				'id'    => '',
				'title' => '',
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		ob_start();
		?>

		<h4 id="<?php echo esc_attr( $id ); ?>"><?php echo wp_kses_post( $args['title'] ); ?></h4>

		<?php
		self::field_description( $args );
		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a textarea setting control.
	 *
	 * @since 1.0.0
	 * @since 1.0.0 Added the `preserved` argument.
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output the control wrapper.
	 *
	 * @return void
	 */
	public static function textarea( $args, $bare = false ) {

		$args = wp_parse_args(
			$args,
			[
				'id'          => '',
				'name'        => '',
				'value'       => '',
				'placeholder' => '',
				'rows'        => '5',
				'cols'        => '40',
				'description' => '',
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name        = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$value       = $args['value'];
		$placeholder = $args['placeholder'];
		$rows        = $args['rows'];
		$cols        = $args['cols'];

		ob_start();
		?>

		<textarea
			name="<?php echo esc_attr( $name ); ?>"
			id="<?php echo esc_attr( $id ); ?>"
			cols="<?php echo esc_attr( $cols ); ?>"
			rows="<?php echo esc_attr( $rows ); ?>"
			placeholder="<?php echo esc_attr( $placeholder ); ?>"
		><?php echo esc_textarea( $value ); ?></textarea>

		<?php

		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a WYSIWYG editor setting control.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output the control wrapper.
	 *
	 * @return void
	 */
	public static function wysiwyg_editor( $args, $bare = false ) {

		$args = wp_parse_args(
			$args,
			[
				'id'            => '',
				'name'          => '',
				'value'         => '',
				'description'   => '',
				'rows'          => 10,
				'media_buttons' => false,
				'teeny'         => false,
				'quicktags'     => true,
				'smart_tags'    => [],
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name  = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$value = $args['value'];

		$editor_settings = [
			'textarea_name' => $name,
			'textarea_rows' => $args['rows'],
			'media_buttons' => $args['media_buttons'],
			'teeny'         => $args['teeny'],
			'quicktags'     => $args['quicktags'],
		];

		// Add smart tags support.
		if ( ! empty( $args['smart_tags'] ) && is_array( $args['smart_tags'] ) ) {
			$editor_settings['tinymce'] = [
				'toolbar1' => 'formatselect,bold,italic,bullist,numlist,blockquote,alignleft,aligncenter,alignright,link,smarttags',
				'toolbar2' => false,
				'plugins'  => 'lists,link,smarttags',
			];

			// Enqueue smart tags script and styles (only once).
			if ( ! wp_script_is( 'sugar-calendar-bookings-wysiwyg-smart-tags', 'enqueued' ) ) {
				wp_enqueue_script(
					'sugar-calendar-bookings-wysiwyg-smart-tags',
					SCBOOKINGS_PLUGIN_URL . 'assets/dist/admin/wysiwyg-smart-tags.js',
					[ 'wp-tinymce' ],
					SCBOOKINGS_PLUGIN_VERSION,
					true
				);

				wp_set_script_translations(
					'sugar-calendar-bookings-wysiwyg-smart-tags',
					'sugar-calendar-bookings-scheduling-appointments-lite',
					SCBOOKINGS_PLUGIN_PATH . 'assets/languages'
				);

				wp_enqueue_style(
					'sugar-calendar-bookings-wysiwyg-smart-tags',
					SCBOOKINGS_PLUGIN_URL . 'assets/dist/admin/wysiwyg-smart-tags.css',
					[],
					SCBOOKINGS_PLUGIN_VERSION
				);

				// Initialize global smart tags array and plugin URL.
				wp_add_inline_script(
					'sugar-calendar-bookings-wysiwyg-smart-tags',
					'window.sugarCalendarBookingsSmartTags = window.sugarCalendarBookingsSmartTags || {};
					window.sugarCalendarBookingsPluginUrl = "' . esc_js( SCBOOKINGS_PLUGIN_URL ) . '";',
					'before'
				);
			}

			// Add smart tags for this specific editor.
			wp_add_inline_script(
				'sugar-calendar-bookings-wysiwyg-smart-tags',
				'window.sugarCalendarBookingsSmartTags["' . esc_js( $id ) . '"] = ' . wp_json_encode( $args['smart_tags'] ) . ';'
			);
		}

		ob_start();

		wp_editor( $value, $id, $editor_settings );

		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a password setting control.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Control arguments.
	 *
	 * @return void
	 */
	public static function password_input( $args ) {

		$args['type'] = 'password';

		self::text_input( $args );
	}

	/**
	 * Output a text setting control.
	 *
	 * @since 1.0.0
	 * @since 1.0.0 Added the `preserved` argument.
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output the control wrapper.
	 *
	 * @return void
	 */
	public static function text_input( $args, $bare = false ) {

		$args = wp_parse_args(
			$args,
			[
				'type'        => 'text',
				'id'          => '',
				'name'        => '',
				'value'       => '',
				'placeholder' => '',
				'description' => '',
				'required'    => false,
				'disabled'    => false,
			]
		);

		$type = sanitize_key( $args['type'] );
		$id   = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name        = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$value       = $args['value'];
		$placeholder = $args['placeholder'];

		ob_start();
		?>

		<input
			type="<?php echo esc_attr( $type ); ?>"
			name="<?php echo esc_attr( $name ); ?>"
			value="<?php echo esc_attr( $value ); ?>"
			id="<?php echo esc_attr( $id ); ?>"
			placeholder="<?php echo esc_attr( $placeholder ); ?>"
			<?php echo( $args['required'] ? esc_attr( 'required' ) : '' ); ?>
			<?php echo( $args['disabled'] ? esc_attr( 'disabled' ) : '' ); ?>
		/>

		<?php

		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a text setting control with button.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output the control wrapper.
	 *
	 * @return void
	 */
	public static function text_input_with_button( $args, $bare = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh

		$args = wp_parse_args(
			$args,
			[
				'type'        => 'text',
				'id'          => '',
				'name'        => '',
				'value'       => '',
				'placeholder' => '',
				'description' => '',
				'required'    => false,
				'preserved'   => false,
				'button'      => null,
			]
		);

		$type = sanitize_key( $args['type'] );
		$id   = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name        = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$value       = $args['value'];
		$placeholder = $args['placeholder'];

		// If the value is preserved, get from $_REQUEST.
		if ( $args['preserved'] && self::is_preserved( $name ) ) {
			$value = urldecode( self::get_preserved_value( $name ) );
		}

		ob_start();
		?>

		<div class="sugar-calendar-bookings-setting-input-group">
			<input
				type="<?php echo esc_attr( $type ); ?>"
				name="<?php echo esc_attr( $name ); ?>"
				value="<?php echo esc_attr( $value ); ?>"
				id="<?php echo esc_attr( $id ); ?>"
				placeholder="<?php echo esc_attr( $placeholder ); ?>"
				<?php echo( $args['required'] ? esc_attr( 'required' ) : '' ); ?>
			/>

			<?php if ( ! empty( $args['button'] ) && is_array( $args['button'] ) ) : ?>
				<button
					type="button"
					id="<?php echo esc_attr( $args['button']['id'] ); ?>"
					class="<?php echo esc_attr( $args['button']['class'] ); ?>"
				>
					<?php echo esc_html( $args['button']['text'] ); ?>
				</button>
			<?php endif; ?>
		</div>

		<?php

		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a text input with a smart tags dropdown button.
	 *
	 * The smart_tags array should be a list of categories, each with a label and tags:
	 * [
	 *     [ 'label' => 'Category', 'tags' => [ [ 'value' => 'tag_key', 'label' => 'Tag Label' ] ] ],
	 * ]
	 *
	 * @since 1.4.0
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output without wrapper.
	 *
	 * @return void
	 */
	public static function text_input_with_smart_tags( $args, $bare = false ) {

		$args = wp_parse_args(
			$args,
			[
				'id'          => '',
				'name'        => '',
				'value'       => '',
				'placeholder' => '',
				'description' => '',
				'smart_tags'  => [],
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name  = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$value = $args['value'];

		ob_start();
		?>

		<div class="sugar-calendar-bookings-setting-input-group">
			<input
				type="text"
				name="<?php echo esc_attr( $name ); ?>"
				value="<?php echo esc_attr( $value ); ?>"
				id="<?php echo esc_attr( $id ); ?>"
				placeholder="<?php echo esc_attr( $args['placeholder'] ); ?>"
			/>
			<button
				type="button"
				class="scb-smart-tags-btn"
				data-smart-tags-target="<?php echo esc_attr( $id ); ?>"
				data-smart-tags="<?php echo esc_attr( wp_json_encode( $args['smart_tags'] ) ); ?>"
			>
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><path d="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176C230.8 38.7 214.5 32 197.5 32H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
			</button>
		</div>

		<?php

		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a textarea setting control with smart tags support.
	 *
	 * @since 1.4.0
	 *
	 * @param array $args Control arguments.
	 * @param bool  $bare Whether to output the control wrapper.
	 *
	 * @return void
	 */
	public static function textarea_with_smart_tags( $args, $bare = false ) {

		$args = wp_parse_args(
			$args,
			[
				'id'          => '',
				'name'        => '',
				'value'       => '',
				'placeholder' => '',
				'rows'        => '5',
				'description' => '',
				'smart_tags'  => [],
			]
		);

		$id = sanitize_key( $args['id'] );

		if ( ! empty( $id ) && ! $bare ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$name  = ! $bare ? self::get_name_attribute( $args['name'] ) : esc_attr( $args['name'] );
		$value = $args['value'];

		ob_start();
		?>

		<div class="sugar-calendar-bookings-setting-input-group">
			<textarea
				name="<?php echo esc_attr( $name ); ?>"
				id="<?php echo esc_attr( $id ); ?>"
				rows="<?php echo esc_attr( $args['rows'] ); ?>"
				placeholder="<?php echo esc_attr( $args['placeholder'] ); ?>"
			><?php echo esc_textarea( $value ); ?></textarea>
			<button
				type="button"
				class="scb-smart-tags-btn"
				data-smart-tags-target="<?php echo esc_attr( $id ); ?>"
				data-smart-tags="<?php echo esc_attr( wp_json_encode( $args['smart_tags'] ) ); ?>"
			>
				<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="currentColor"><path d="M0 80V229.5c0 17 6.7 33.3 18.7 45.3l176 176c25 25 65.5 25 90.5 0L418.7 317.3c25-25 25-65.5 0-90.5l-176-176C230.8 38.7 214.5 32 197.5 32H48C21.5 32 0 53.5 0 80zm112 32a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"/></svg>
			</button>
		</div>

		<?php

		self::field_description( $args );

		if ( $bare ) {
			echo ob_get_clean(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped

			return;
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Output a button setting control.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Control arguments.
	 *
	 * @return void
	 */
	public static function button( $args ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.CyclomaticComplexity.MaxExceeded

		$args = wp_parse_args(
			$args,
			[
				'id'     => '',
				'name'   => 'sugar-calendar-bookings-submit',
				'class'  => '',
				'type'   => 'primary',
				'size'   => 'md',
				'text'   => '',
				'link'   => '',
				'submit' => true,
				'target' => '_self',
				'data'   => [],
			]
		);

		$id      = $args['id'];
		$name    = $args['name'];
		$classes = [ 'sugar-calendar-bookings-btn' ];

		if ( ! empty( $args['class'] ) ) {
			$class   = is_array( $args['class'] ) ? $args['class'] : [ $args['class'] ];
			$classes = array_merge( $classes, $class );
		}

		// Type.
		$types     = [ 'primary', 'secondary', 'tertiary', 'danger' ];
		$type      = in_array( $args['type'], $types, true ) ? $args['type'] : 'primary';
		$classes[] = "sugar-calendar-bookings-btn-{$type}";

		// Size.
		$sizes     = [ 'sm', 'md', 'lg', 'xl' ];
		$size      = in_array( $args['size'], $sizes, true ) ? $args['size'] : 'md';
		$classes[] = "sugar-calendar-bookings-btn-{$size}";
		$class     = self::sanitize_class( $classes );

		// Submit.
		$submit = (bool) $args['submit'] ? 'submit' : 'button';

		$text   = $args['text'];
		$link   = $args['link'];
		$target = $args['target'];
		?>

		<?php if ( empty( $link ) ) : ?>

			<button
				type="<?php echo esc_attr( $submit ); ?>"
				name="<?php echo esc_attr( $name ); ?>"
				id="<?php echo esc_attr( $id ); ?>"
				class="<?php echo esc_attr( $class ); ?>"
				<?php if ( ! empty( $args['data'] ) ) : ?>
					<?php foreach ( $args['data'] as $key => $value ) : ?>
						data-<?php echo esc_attr( $key ); ?>="<?php echo esc_attr( $value ); ?>"
					<?php endforeach; ?>
				<?php endif; ?>
			>
				<?php echo esc_html( $text ); ?>
			</button>

		<?php else : ?>

			<a
				href="<?php echo esc_url( $link ); ?>"
				id="<?php echo esc_attr( $id ); ?>"
				target="<?php echo esc_attr( $target ); ?>"
				class="<?php echo esc_attr( $class ); ?>"
				<?php if ( ! empty( $args['data'] ) ) : ?>
					<?php foreach ( $args['data'] as $key => $value ) : ?>
						data-<?php echo esc_attr( $key ); ?>="<?php echo esc_attr( $value ); ?>"
					<?php endforeach; ?>
				<?php endif; ?>
			>
				<?php echo esc_html( $text ); ?>
			</a>

		<?php endif; ?>
		<?php
		self::field_description( $args );
	}

	/**
	 * Output a form wrapper.
	 *
	 * @since 1.0.0
	 * @since 1.4.0 Added the `$show_submit` parameter.
	 *
	 * @param string $content     Wrapper contents.
	 * @param bool   $show_submit Whether to render the default "Save Settings" submit button.
	 */
	public static function form_wrapper( $content = '', $show_submit = true ) {

		?>

		<form class="sugar-calendar-bookings-admin-content__settings-form" method="post" action="">

			<?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>

			<?php if ( $show_submit ) : ?>
				<p class="submit">
					<?php
					self::button(
						[
							'text' => esc_html__( 'Save Settings', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
						]
					);
					?>
				</p>
			<?php endif; ?>

			<?php wp_nonce_field( Area::SLUG ); ?>

		</form>

		<?php
	}

	/**
	 * Output calendar view modes menu.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Menu arguments.
	 *
	 * @return void
	 */
	public static function calendar_modes( $args ) {

		$args = wp_parse_args(
			$args,
			[
				'base_url' => admin_url(),
				'modes'    => [
					'agenda' => esc_html__( 'Agenda', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'month'  => esc_html__( 'Month', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'week'   => esc_html__( 'Week', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
					'day'    => esc_html__( 'Day', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				],
				'mode'     => '',
			]
		);

		if ( empty( $args['modes'] ) ) {
			return;
		}

		if ( empty( $args['modes'][ $args['mode'] ] ) ) {
			$args['mode'] = array_key_first( $args['modes'] );
		}

		$base_url     = $args['base_url'];
		$modes        = $args['modes'];
		$current_mode = $args['mode'];
		?>

		<div class="sugar-calendar-bookings-view-modes">

			<?php foreach ( $modes as $mode => $title ) : ?>

				<?php
				$classes = [
					'sugar-calendar-bookings-view-mode',
					'sugar-calendar-bookings-view-mode--' . $mode,
				];

				if ( $current_mode === $mode ) {
					$classes[] = 'sugar-calendar-bookings-view-mode--current';
				}

				$classes = implode( ' ', $classes );
				?>

				<a href="<?php echo esc_url( add_query_arg( 'mode', $mode, $base_url ) ); ?>"
					class="<?php echo esc_attr( $classes ); ?>"
					title="<?php echo esc_attr( $title ); ?>">
					<?php echo esc_html( $title ); ?>
				</a>

			<?php endforeach; ?>

		</div>

		<?php
	}

	/**
	 * Output calendar view options menu.
	 *
	 * @since 1.0.0
	 *
	 * @param array $args Menu arguments.
	 *
	 * @return void
	 */
	public static function calendar_view_options( $args ) {

		$args = wp_parse_args(
			$args,
			[
				'mode'           => '',
				'columns'        => [],
				'screen_options' => [],
			]
		);

		$mode           = $args['mode'];
		$columns        = $args['columns'];
		$screen_options = $args['screen_options'];
		$hidden_columns = ! empty( $screen_options['hidden_columns'][ $mode ] ) ? $screen_options['hidden_columns'][ $mode ] : [];
		?>
		<div class="sugar-calendar-bookings-screen-options">
			<input type="checkbox" id="sugar-calendar-bookings-screen-options-toggle">
			<label for="sugar-calendar-bookings-screen-options-toggle"></label>

			<div class="sugar-calendar-bookings-screen-options-menu">
				<form action="" method="post" id="sugar-calendar-bookings-screen-options__columns">
					<input type="hidden" name="mode" value="<?php echo esc_attr( $mode ); ?>">
					<fieldset>
						<legend><?php esc_html_e( 'Columns', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></legend>

						<?php if ( empty( $columns ) ) : ?>
							<p class="sugar-calendar-bookings-screen-options-menu-empty">
								<?php esc_html_e( 'No column settings available.', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
							</p>
						<?php else : ?>
							<?php foreach ( $columns as $column_display_name ) : ?>
								<label>
									<input
										type="checkbox"
										name="columns[]"
										value="<?php echo esc_attr( strtolower( $column_display_name ) ); ?>"
										<?php checked( ! in_array( strtolower( $column_display_name ), $hidden_columns, true ) ); ?>>

									<?php echo esc_html( $column_display_name ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
								</label>
							<?php endforeach; ?>
						<?php endif; ?>
					</fieldset>
				</form>
			</div>
		</div>
		<?php
	}

	/**
	 * Output a confirmation modal.
	 *
	 * @since 1.0.0
	 */
	public static function confirmation_modal() {

		?>
		<!-- Confirmation Modal -->
		<div id="sugar-calendar-bookings-confirmation-modal" class="sugar-calendar-bookings-confirmation-modal" style="display: none;">
			<div class="sugar-calendar-bookings-modal-container">
				<div class="sugar-calendar-bookings-modal-alert">
					<div class="sugar-calendar-bookings-modal-icon" aria-hidden="true"></div>
					<div class="sugar-calendar-bookings-modal-content">
						<div class="sugar-calendar-bookings-modal-header">
							<h3 class="sugar-calendar-bookings-modal-title">
								<!-- Title will be set dynamically -->
							</h3>
							<button type="button" class="sugar-calendar-bookings-modal-close" data-close aria-label="<?php esc_attr_e( 'Close', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>">
								<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
									<path d="M11.8333 1.34163L10.6583 0.166626L5.99996 4.82496L1.34163 0.166626L0.166626 1.34163L4.82496 5.99996L0.166626 10.6583L1.34163 11.8333L5.99996 7.17496L10.6583 11.8333L11.8333 10.6583L7.17496 5.99996L11.8333 1.34163Z" fill="currentColor"/>
								</svg>
							</button>
						</div>
						<p class="sugar-calendar-bookings-modal-message">
							<!-- Message will be set dynamically -->
						</p>
					</div>
					<div class="sugar-calendar-bookings-modal-buttons">
						<button type="button" class="sugar-calendar-bookings-modal-button sugar-calendar-bookings-modal-button--confirm" data-confirm>
							<!-- Button text will be set dynamically -->
						</button>
						<button type="button" class="sugar-calendar-bookings-modal-button sugar-calendar-bookings-modal-button--cancel" data-cancel>
							<!-- Button text will be set dynamically -->
						</button>
					</div>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Output a generic migration modal shell.
	 *
	 * This modal is a template that JavaScript populates dynamically from the
	 * migration config when the user clicks a migration button.
	 *
	 * @since 1.3.0
	 *
	 * @return void
	 */
	public static function migration_modal_shell() {

		?>
		<!-- Generic Migration Modal (populated by JS from config) -->
		<div id="sugar-calendar-bookings-migration-modal" class="sugar-calendar-bookings-migration-modal" style="display: none;">
			<div class="sugar-calendar-bookings-migration-modal-overlay">
				<div class="sugar-calendar-bookings-migration-modal-body">
					<!-- Container with light gray background -->
					<div class="sugar-calendar-bookings-migration-modal-container">

						<!-- ===== HEADER SECTION ===== -->
						<div class="sugar-calendar-bookings-migration-modal-header">
							<!-- Icons Row -->
							<div class="sugar-calendar-bookings-migration-modal-header-icons">
								<img src="" alt="" class="sugar-calendar-bookings-migration-modal-icon" data-source-icon/>
								<img src="<?php echo esc_url( SCBOOKINGS_PLUGIN_URL . 'assets/images/migration/arrow.svg' ); ?>" alt="" class="sugar-calendar-bookings-migration-modal-arrow"/>
								<div class="sugar-calendar-bookings-migration-modal-icon">
									<?php self::svg_icon( 'icon-sugar-calendar' ); ?>
								</div>
								<img src="<?php echo esc_url( SCBOOKINGS_PLUGIN_URL . 'assets/images/migration/decoration.svg' ); ?>" alt="" class="sugar-calendar-bookings-migration-modal-header-decoration"/>
							</div>
							<!-- Title and Description wrapper -->
							<div class="sugar-calendar-bookings-migration-modal-header-text">
								<h2 class="sugar-calendar-bookings-migration-modal-title" data-modal-title></h2>
								<p class="sugar-calendar-bookings-migration-modal-description" data-modal-description style="display: none;"></p>
							</div>
						</div>

						<!-- ===== MAIN CONTENT SECTION (populated by JS) ===== -->
						<div class="sugar-calendar-bookings-migration-modal-main" data-modal-main>
							<div class="sugar-calendar-bookings-migration-modal-options" data-view="options">
								<!-- Options built by JS from config -->
							</div>
						</div>

						<!-- Info Message (populated by JS) -->
						<div class="sugar-calendar-bookings-migration-modal-info-message sugar-calendar-bookings-migration-modal-info-message--pro" data-info-message style="display: none;">
							<span class="sugar-calendar-bookings-migration-modal-info-icon">
								<?php self::svg_icon( 'info-circle', true ); ?>
							</span>
							<span class="sugar-calendar-bookings-migration-modal-info-text"></span>
						</div>

						<!-- ===== FOOTER SECTION (buttons) ===== -->
						<div class="sugar-calendar-bookings-migration-modal-footer">
							<button type="button" class="sugar-calendar-bookings-migration-modal-button sugar-calendar-bookings-migration-modal-button--primary" data-confirm>
								<?php self::svg_icon( 'bolt', true ); ?>
								<span data-confirm-text></span>
							</button>
							<button type="button" class="sugar-calendar-bookings-migration-modal-button sugar-calendar-bookings-migration-modal-button--tertiary" data-cancel>
								<span data-cancel-text></span>
							</button>
						</div>
					</div><!-- /.sugar-calendar-bookings-migration-modal-container -->

				</div>
			</div>
		</div>
		<?php

		// Render shared migration templates.
		self::migration_templates();
	}

	/**
	 * Output shared migration templates.
	 *
	 * These templates are used by all migration sources for progress display and summary.
	 *
	 * @since 1.3.0
	 *
	 * @return void
	 */
	public static function migration_templates() {

		?>
		<!-- ===== MIGRATION TEMPLATES ===== -->

		<!-- Status Item Template (used during migration progress) -->
		<template id="tmpl-migration-status-item">
			<div class="sugar-calendar-bookings-migration-status-item" data-entity="" data-status="pending">
				<span class="sugar-calendar-bookings-migration-status-icon sugar-calendar-bookings-migration-status-icon--loader"></span>
				<span class="sugar-calendar-bookings-migration-status-text">
					<span data-label></span>
					<?php esc_html_e( 'migrated', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
					<span class="sugar-calendar-bookings-migration-status-count">
						<span data-count="migrated">0</span>/<span data-count="total"></span>
					</span>
				</span>
			</div>
		</template>

		<!-- Progress View Template (main content during migration) -->
		<template id="tmpl-migration-progress">
			<div class="sugar-calendar-bookings-migration-progress" data-view="progress">
				<h3 class="sugar-calendar-bookings-migration-progress-title">
					<?php esc_html_e( 'Migrating data…', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
				</h3>
				<hr class="sugar-calendar-bookings-migration-divider"/>
				<div class="sugar-calendar-bookings-migration-status-grid"></div>
			</div>
		</template>

		<!-- Migration Summary Template -->
		<template id="tmpl-migration-summary">
			<div class="sugar-calendar-bookings-migration-summary" data-view="summary">
				<!-- Summary section -->
				<div class="sugar-calendar-bookings-migration-summary-section">
					<div class="sugar-calendar-bookings-migration-summary-header">
						<h3 class="sugar-calendar-bookings-migration-summary-title">
							<?php esc_html_e( 'Summary', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
						</h3>
						<button
							type="button"
							class="sugar-calendar-bookings-btn-text sugar-calendar-bookings-btn-sm sugar-calendar-bookings-migration-download-report-btn"
							data-download-report
							style="display: none;"
						>
							<?php self::svg_icon( 'download' ); ?>
							<?php esc_html_e( 'Download Migration Report', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
						</button>
					</div>

					<!-- Stats boxes -->
					<div class="sugar-calendar-bookings-migration-summary-stats">
						<!-- Migrated -->
						<div class="sugar-calendar-bookings-migration-summary-stat sugar-calendar-bookings-migration-summary-stat--migrated">
							<span class="sugar-calendar-bookings-migration-summary-stat-icon sugar-calendar-bookings-migration-summary-stat-icon--migrated"></span>
							<div class="sugar-calendar-bookings-migration-summary-stat-content">
								<span class="sugar-calendar-bookings-migration-summary-stat-value" data-stat="migrated">0</span>
								<span class="sugar-calendar-bookings-migration-summary-stat-label">
									<?php esc_html_e( 'Migrated', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
								</span>
							</div>
						</div>
						<!-- Skipped -->
						<div class="sugar-calendar-bookings-migration-summary-stat sugar-calendar-bookings-migration-summary-stat--skipped">
							<span class="sugar-calendar-bookings-migration-summary-stat-icon sugar-calendar-bookings-migration-summary-stat-icon--skipped"></span>
							<div class="sugar-calendar-bookings-migration-summary-stat-content">
								<span class="sugar-calendar-bookings-migration-summary-stat-value" data-stat="skipped">0</span>
								<span class="sugar-calendar-bookings-migration-summary-stat-label">
									<?php esc_html_e( 'Skipped', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
								</span>
							</div>
						</div>
						<!-- Failed -->
						<div class="sugar-calendar-bookings-migration-summary-stat sugar-calendar-bookings-migration-summary-stat--failed">
							<span class="sugar-calendar-bookings-migration-summary-stat-icon sugar-calendar-bookings-migration-summary-stat-icon--failed"></span>
							<div class="sugar-calendar-bookings-migration-summary-stat-content">
								<span class="sugar-calendar-bookings-migration-summary-stat-value" data-stat="failed">0</span>
								<span class="sugar-calendar-bookings-migration-summary-stat-label">
									<?php esc_html_e( 'Failed', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
								</span>
							</div>
						</div>
					</div>
				</div>

				<?php
				// Pro to Lite banner: only show when Amelia Pro → SCB Lite.
				if ( AmeliaAdapter::is_amelia_pro() && ! sugar_calendar_bookings()->is_pro() ) :
					$upgrade_url = Helpers::get_utm_url(
						'https://sugarcalendar.com/pricing/bookings/',
						[
							'medium'  => 'amelia-migration-complete',
							'content' => 'pro-skipped-notice',
						]
					);
					?>
					<div class="sugar-calendar-bookings-migration-modal-info-message sugar-calendar-bookings-migration-modal-info-message--summary">
						<span class="sugar-calendar-bookings-migration-modal-info-icon">
							<?php self::svg_icon( 'info-circle', true ); ?>
						</span>
						<span class="sugar-calendar-bookings-migration-modal-info-text">
							<?php
							printf(
							/* translators: %1$s - opening link tag; %2$s - closing link tag. */
								esc_html__( 'As you\'re switching from Amelia Pro to Sugar Calendar Bookings Lite, some items are skipped for now. You can remigrate them after %1$sUpgrading to Pro%2$s', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
								'<a href="' . esc_url( $upgrade_url ) . '" target="_blank" rel="noopener noreferrer">',
								'</a>'
							);
							?>
						</span>
					</div>
				<?php endif; ?>

				<!-- Details accordion -->
				<div class="sugar-calendar-bookings-migration-summary-accordion">
					<button class="sugar-calendar-bookings-migration-summary-accordion-toggle" type="button" aria-expanded="false">
						<span><?php esc_html_e( 'Details', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></span>
						<span class="sugar-calendar-bookings-migration-summary-accordion-icon"></span>
					</button>
					<div class="sugar-calendar-bookings-migration-summary-accordion-content">
						<div class="sugar-calendar-bookings-migration-summary-list" data-entities-container></div>
					</div>
				</div>
			</div>
		</template>

		<!-- Migration Summary Entity Item Template -->
		<template id="tmpl-migration-summary-item">
			<div class="sugar-calendar-bookings-migration-summary-item">
				<span class="sugar-calendar-bookings-migration-summary-item-icon" data-icon></span>
				<span class="sugar-calendar-bookings-migration-summary-item-label" data-label></span>
				<span class="sugar-calendar-bookings-migration-summary-item-count">
					<span class="sugar-calendar-bookings-migration-summary-item-count-first" data-migrated></span>
					<span class="sugar-calendar-bookings-migration-summary-item-count-second" data-total></span>
				</span>
			</div>
		</template>

		<!-- Migration Error Template -->
		<template id="tmpl-migration-error">
			<div class="sugar-calendar-bookings-migration-error">
				<div class="sugar-calendar-bookings-migration-error-header">
					<span class="sugar-calendar-bookings-migration-error-icon">
						<?php self::svg_icon( 'times-circle' ); ?>
					</span>
					<h3 class="sugar-calendar-bookings-migration-error-title">
						<?php esc_html_e( 'Error', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
						<span data-error-code></span>:
						<?php esc_html_e( 'Migration Failed', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
					</h3>
					<p class="sugar-calendar-bookings-migration-error-description">
						<?php esc_html_e( 'It appears there was an issue during the migration process. Please retry migrating, and if the problem persists, contact our', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
						<a href="#" data-support-link target="_blank" rel="noopener noreferrer">
							<?php esc_html_e( 'support', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></a>.
					</p>
				</div>

				<div class="sugar-calendar-bookings-migration-error-body">
					<div class="sugar-calendar-bookings-migration-error-details-header">
						<span class="sugar-calendar-bookings-migration-error-details-title">
							<?php esc_html_e( 'Error Details', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
						</span>
						<button type="button" class="sugar-calendar-bookings-migration-error-copy" data-copy-error title="<?php esc_attr_e( 'Copy error', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>">
							<?php self::svg_icon( 'copy' ); ?>
						</button>
					</div>
					<div class="sugar-calendar-bookings-migration-error-details-container">
						<pre class="sugar-calendar-bookings-migration-error-details" data-error-details></pre>
					</div>
				</div>
			</div>
		</template>

		<!-- Migration Footer Buttons Template (for resetting modal state) -->
		<template id="tmpl-migration-footer-buttons">
			<button type="button" class="sugar-calendar-bookings-migration-modal-button sugar-calendar-bookings-migration-modal-button--primary" data-confirm>
				<?php self::svg_icon( 'bolt', true ); ?>
				<span data-confirm-text></span>
			</button>
			<button type="button" class="sugar-calendar-bookings-migration-modal-button sugar-calendar-bookings-migration-modal-button--tertiary" data-cancel>
				<span data-cancel-text></span>
			</button>
		</template>

		<!-- Migration Support Info Template -->
		<template id="tmpl-migration-support-info">
			<div class="sugar-calendar-bookings-migration-support">
				<p class="sugar-calendar-bookings-migration-support-message">
					<?php
					$support_url = Helpers::get_support_url(
						[
							'wpf96206_20' => 'Sugar Calendar Bookings',
							'wpf96206_5'  => 'Bug report or problem',
							'wpf96206_6'  => site_url(),
						]
					);

					echo wp_kses(
						sprintf(
						/* translators: %1$s - opening link tag; %2$s - closing link tag. */
							__( 'If needed, %1$scontact support%2$s for assistance', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
							'<a href="' . esc_url( $support_url ) . '" target="_blank" rel="noopener noreferrer">',
							'</a>'
						),
						[
							'a' => [
								'href'   => [],
								'target' => [],
								'rel'    => [],
							],
						]
					);
					?>
				</p>
				<div class="sugar-calendar-bookings-migration-support-tech">
					<span class="sugar-calendar-bookings-migration-support-tech-item" data-source-version>
						<span data-source-name></span>:
						<span data-version></span>
					</span>
					<span class="sugar-calendar-bookings-migration-support-tech-item" data-scb-version>
						<?php esc_html_e( 'SCB:', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
						<span data-version></span>
						(<span data-edition><?php echo sugar_calendar_bookings()->is_pro() ? esc_html__( 'Pro', 'sugar-calendar-bookings-scheduling-appointments-lite' ) : esc_html__( 'Lite', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?></span>)
					</span>
				</div>
			</div>
		</template>
		<?php
	}

	/**
	 * Output an image modal.
	 *
	 * Source and alt text are set dynamically.
	 *
	 * @since 1.2.0
	 *
	 * @return void
	 */
	public static function image_modal() {

		?>
		<div id="sugar-calendar-bookings-image-modal" role="dialog" tabindex="-1" style="display: none;">
			<div class="sugar-calendar-bookings-image-modal-wrap">
				<div class="sugar-calendar-bookings-image-modal-container">
					<div class="sugar-calendar-bookings-image-modal-content">
						<img src="" alt="" class="sugar-calendar-bookings-image-modal-image"/>
					</div>
				</div>
			</div>
		</div>
		<?php
	}

	/**
	 * Output an image modal trigger.
	 *
	 * @since 1.2.0
	 *
	 * @param array $args Arguments array. url, url_thumbnail, title.
	 *
	 * @return void
	 */
	public static function image_modal_trigger( $args ) {

		echo wp_kses(
			wp_sprintf(
				'<a
					href="%1$s"
					class="sugar-calendar-bookings-image-modal-trigger"
					data-image-alt="%2$s"
				>
					<img
						src="%3$s"
						alt="%2$s"
					>
				</a>',
				esc_url( $args['url'] ),
				esc_attr( $args['title'] ),
				esc_url( $args['url_thumbnail'] )
			),
			[
				'a'   => [
					'href'           => [],
					'class'          => [],
					'data-image-alt' => [],
				],
				'img' => [
					'src' => [],
					'alt' => [],
				],
			]
		);
	}

	/**
	 * Get inline SVG icon content.
	 *
	 * @since 1.0.0
	 *
	 * @param string $icon_name      Icon name (without .svg extension).
	 * @param bool   $original_color Whether to preserve original SVG colors. Default false (use currentColor).
	 *
	 * @return void
	 */
	public static function svg_icon( $icon_name, $original_color = false ) {

		$icon_path = SCBOOKINGS_PLUGIN_PATH . 'assets/images/icons/' . $icon_name . '.svg';

		// Check if file exists.
		if ( ! file_exists( $icon_path ) ) {
			return;
		}

		// Read SVG content.
		$svg_content = file_get_contents( $icon_path );

		if ( $svg_content === false ) {
			return;
		}

		// Replace colors with currentColor unless original colors are requested.
		if ( ! $original_color ) {

			// Replace any existing fill attributes with currentColor.
			$svg_content = preg_replace( '/fill="[^"]*"/', 'fill="currentColor"', $svg_content );

			// If no fill attribute exists, add it to the SVG tag.
			if ( strpos( $svg_content, 'fill=' ) === false ) {
				$svg_content = str_replace( '<svg', '<svg fill="currentColor"', $svg_content );
			}
		}

		echo $svg_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
	}

	/**
	 * Output a reusable notice banner (icon + title + text).
	 *
	 * @since 1.3.0
	 *
	 * @param array $args Notice banner arguments.
	 *                    - string       $title      Notice title. Output is escaped with esc_html(). Default empty.
	 *                    - string       $text       Notice text; may contain allowed HTML (e.g. links). Output is escaped with wp_kses_post(). Default empty.
	 *                    - string       $icon_name  Icon name. Default 'info-circle'.
	 *                    - string|array $class      Optional. Extra CSS class(es). Pass a string or array; will be imploded and sanitized.
	 *                    - bool         $bordered   Whether to add a border to the notice banner. Default false.
	 *
	 * @return void
	 */
	public static function notice_banner( $args ) {

		$args = wp_parse_args(
			$args,
			[
				'title'     => '',
				'text'      => '',
				'icon_name' => 'info-circle',
				'class'     => [],
				'bordered'  => false,
			]
		);

		$classes = [ 'sugar-calendar-bookings-notice-banner' ];

		if ( ! empty( $args['class'] ) ) {
			$extra = is_array( $args['class'] ) ? $args['class'] : [ $args['class'] ];

			$classes = array_merge( $classes, $extra );
		}

		if ( $args['bordered'] ) {
			$classes[] = 'sugar-calendar-bookings-notice-banner--bordered';
		}

		$class_attr = self::sanitize_class( $classes );

		?>
		<div class="<?php echo esc_attr( $class_attr ); ?>">
			<span class="sugar-calendar-bookings-notice-banner-icon">
				<?php self::svg_icon( $args['icon_name'], true ); ?>
			</span>
			<span class="sugar-calendar-bookings-notice-banner-text">
				<?php if ( ! empty( $args['title'] ) ) : ?>
					<b><?php echo esc_html( $args['title'] ); ?></b><br>
				<?php endif; ?>
				<?php echo wp_kses_post( $args['text'] ); ?>
			</span>
		</div>
		<?php
	}

	/**
	 * Output payment status badge.
	 *
	 * @since 1.0.0
	 *
	 * @param PaymentModel $payment Payment model instance.
	 * @param bool         $output  Whether to output immediately.
	 *
	 * @return void|string
	 */
	public static function payment_status_badge( $payment, $output = false ) {

		$classes = [
			'completed' => 'green',
			'pending'   => 'yellow',
			'failed'    => 'red',
			'refunded'  => 'neutral',
		];

		$class = ! empty( $classes[ $payment->status ] ) ? $classes[ $payment->status ] : 'neutral';

		if ( ! $output ) {
			ob_start();
		}
		?>

		<span class="sugar-calendar-bookings-badge sugar-calendar-bookings-badge--<?php echo sanitize_html_class( $class ); ?>">
		<?php echo esc_html( $payment->get_status_label() ); ?>
		</span>

		<?php

		if ( ! $output ) {
			return ob_get_clean();
		}
	}

	/**
	 * Output Pro badge.
	 *
	 * @since 1.2.0
	 *
	 * @param bool $output Whether to output immediately.
	 *
	 * @return void|string
	 */
	public static function pro_badge( $output = false ) {

		if ( ! $output ) {
			ob_start();
		}
		?>

		<span class="sugar-calendar-bookings-badge--pro">
			<?php esc_html_e( 'PRO', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
		</span>

		<?php

		if ( ! $output ) {
			return ob_get_clean();
		}
	}

	/**
	 * Render a Pro-education settings row: label + PRO badge + description column.
	 *
	 * Use whenever a settings row should advertise a Pro-only field instead of
	 * rendering the field itself. The description supports a single %1$s
	 * placeholder for the upgrade URL.
	 *
	 * @since 1.5.0
	 *
	 * @param array $args {
	 *     Row configuration.
	 *
	 *     @type string $label       Label text rendered next to the PRO badge.
	 *                               `<br>` is allowed to force a line break.
	 *     @type string $description Description with a single %1$s placeholder
	 *                               for the upgrade URL.
	 *     @type string $utm_content UTM content parameter.
	 *     @type string $utm_medium  UTM medium parameter. Default 'settings'.
	 * }
	 *
	 * @return void
	 */
	public static function pro_education_row( array $args ): void {

		$args = wp_parse_args(
			$args,
			[
				'label'       => '',
				'description' => '',
				'utm_content' => '',
				'utm_medium'  => 'settings',
			]
		);

		$upgrade_url = Helpers::get_utm_url(
			'https://sugarcalendar.com/pricing/bookings/',
			[
				'medium'  => $args['utm_medium'],
				'content' => $args['utm_content'],
			]
		);
		?>
		<hr class="sugar-calendar-bookings-setting-row-divider" />
		<div class="sugar-calendar-bookings-setting-row sugar-calendar-bookings-setting-row--pro-education">
			<span class="sugar-calendar-bookings-setting-label">
				<label>
					<?php echo wp_kses( $args['label'], [ 'br' => [] ] ); ?>
					<?php self::pro_badge( true ); ?>
				</label>
			</span>
			<div class="sugar-calendar-bookings-setting-field">
				<p class="desc">
					<?php
					printf(
						wp_kses( /* translators: %1$s - upgrade to Pro link. */
							$args['description'],
							[
								'a' => [
									'href'   => [],
									'target' => [],
									'rel'    => [],
								],
							]
						),
						esc_url( $upgrade_url )
					);
					?>
				</p>
			</div>
		</div>
		<?php
	}

	/**
	 * Render the "Powered by sugarcalendarbookings" branding badge.
	 *
	 * @since 1.5.0
	 *
	 * @param bool $output Whether to echo the markup (true) or return it (false).
	 *
	 * @return string
	 */
	public static function powered_by( $output = false ) {

		if ( ! $output ) {
			ob_start();
		}
		?>

		<div class="scbookings-powered-by">
			<span class="scbookings-powered-by__text">
				<?php esc_html_e( 'Powered by', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
			</span>
			<span class="scbookings-powered-by__logo">
				<?php self::svg_icon( 'sugarcalendarbookings-logo', true ); ?>
			</span>
		</div>

		<?php

		if ( ! $output ) {
			return ob_get_clean();
		}
	}

	/**
	 * Sanitize inline styles.
	 *
	 * @since 1.0.0
	 *
	 * @param array $styles Associative array of styles.
	 *
	 * @return string
	 */
	public static function sanitize_inline_styles( $styles ) {

		$styles = array_reduce(
			array_keys( $styles ),
			function ( $carry, $rule ) use ( $styles ) {

				$value = esc_attr( $styles[ $rule ] );

				array_unshift( $carry, "$rule: $value" );

				return $carry;
			},
			[]
		);

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

	/**
	 * Output a search input.
	 *
	 * @since 1.2.1
	 *
	 * @param array $args Arguments array.
	 *
	 * @return void
	 */
	public static function search_input( $args ) {

		$args = wp_parse_args(
			$args,
			[
				'id'           => '',
				'name'         => 'search',
				'value'        => '',
				'placeholder'  => null,
				'button_label' => esc_html__( 'Search', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			]
		);

		?>
		<label
			class="screen-reader-text"
			for="<?php echo esc_attr( $args['id'] ); ?>"
		>
			<?php echo esc_html( $args['placeholder'] ); ?>:
		</label>
		<input
			type="search"
			id="<?php echo esc_attr( $args['id'] ); ?>"
			name="<?php echo esc_attr( $args['name'] ); ?>"
			value="<?php echo esc_attr( $args['value'] ); ?>"
			placeholder="<?php echo esc_attr( $args['placeholder'] ); ?>"
		>
		<input
			type="submit"
			id="<?php echo esc_attr( $args['id'] ); ?>-submit"
			class="button"
			value="<?php echo esc_attr( $args['button_label'] ); ?>"
		>
		<?php
	}

	/**
	 * Output a search result notice.
	 *
	 * @since 1.2.1
	 * @since 1.4.0 Refactor to accept filter entries with label, label_plural, and value.
	 *
	 * @param array $args Arguments array.
	 *
	 * @return void
	 */
	public static function search_result_notice( $args ) {

		$args = wp_parse_args(
			$args,
			[
				'result_count'     => 0,
				'search_term'      => '',
				'close_url'        => '',
				'item_singular'    => esc_html__( 'item', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				'item_plural'      => esc_html__( 'items', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				'css_class_prefix' => 'sugar-calendar-bookings-calendar',
				'filters'          => [],
			]
		);

		if ( ( empty( $args['search_term'] ) && empty( $args['filters'] ) ) || $args['result_count'] < 0 ) {
			return;
		}

		$result_count = (int) $args['result_count'];
		$search_term  = $args['search_term'];
		$close_url    = $args['close_url'];
		$css_prefix   = sanitize_html_class( $args['css_class_prefix'] );
		$filter_text  = FilterLabels::build_text( $args['filters'] );

		$count_text = sprintf(
			/* translators: %1$s - formatted number; %2$s - item label (singular or plural). */
			_n( '%1$s %2$s', '%1$s %2$s', $result_count, 'sugar-calendar-bookings-scheduling-appointments-lite' ),
			number_format_i18n( $result_count ),
			$result_count === 1 ? $args['item_singular'] : $args['item_plural']
		);

		if ( ! empty( $search_term ) ) {
			$notice_html = sprintf(
				/* translators: %1$s - result count text; %2$s - search term; %3$s - filter text. */
				__( 'Found <strong>%1$s</strong> containing <i>"%2$s"</i>%3$s', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				$count_text,
				esc_html( $search_term ),
				$filter_text
			);
		} else {
			$notice_html = sprintf(
				/* translators: %1$s - result count text; %2$s - filter text. */
				__( 'Found <strong>%1$s</strong>%2$s', 'sugar-calendar-bookings-scheduling-appointments-lite' ),
				$count_text,
				$filter_text
			);
		}

		?>
		<div class="<?php echo esc_attr( $css_prefix ); ?>-search-results-notice sugar-calendar-bookings-search-reset-filter-notice">
			<span>
				<?php
				echo wp_kses(
					$notice_html,
					[
						'strong' => [],
						'i'      => [],
					]
				);
				?>
			</span>
			<?php if ( ! empty( $close_url ) ) : ?>
				<a href="<?php echo esc_url( $close_url ); ?>"
					class="<?php echo esc_attr( $css_prefix ); ?>-search-results-notice__close sugar-calendar-bookings-search-reset-filter-notice__close"></a>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * Conditionally render a title attribute if text exceeds maximum length.
	 *
	 * @since 1.2.1
	 *
	 * @param string $text The text to check.
	 * @param int    $max  Maximum character length before title is rendered.
	 */
	public static function maybe_render_title_attribute( $text, $max = 30 ) {

		if ( strlen( $text ) <= $max ) {
			return;
		}

		printf( 'title="%s"', esc_attr( $text ) );
	}

	/**
	 * Output a timezone select control with Choices.js support.
	 *
	 * Generates a searchable dropdown with timezone options sorted alphabetically
	 * by city name.
	 *
	 * @since 1.3.1
	 *
	 * @param array $args Control arguments.
	 */
	public static function timezone_select( $args = [] ) {

		$args = wp_parse_args(
			$args,
			[
				'type'              => 'timezone_select',
				'id'                => '',
				'name'              => '',
				'value'             => '',
				'label'             => '',
				'description'       => '',
				'after_description' => '',
			]
		);

		$id   = sanitize_key( $args['id'] );
		$name = self::get_name_attribute( $args['name'] );

		if ( ! empty( $id ) ) {
			$id = "sugar-calendar-bookings-setting-{$id}";
		}

		$value  = $args['value'];
		$cities = self::get_timezone_cities();

		ob_start();
		?>

		<span class="sugar-calendar-bookings-dropdown-control sugar-calendar-bookings-timezone-select">
			<select
				name="<?php echo esc_attr( $name ); ?>"
				id="<?php echo esc_attr( $id ); ?>"
				class="choicesjs-select"
			>
				<?php foreach ( $cities as $timezone ) : ?>
					<option
						value="<?php echo esc_attr( $timezone['value'] ); ?>"
						<?php selected( $value, $timezone['value'] ); ?>
					>
						<?php echo esc_html( $timezone['label'] ); ?>
					</option>
				<?php endforeach; ?>
			</select>
		</span>

		<?php
		self::field_description( $args );

		if ( ! empty( $args['after_description'] ) ) {
			echo wp_kses_post( $args['after_description'] );
		}

		self::field_wrapper( $args, ob_get_clean() );
	}

	/**
	 * Build timezone city list for the timezone select control.
	 *
	 * Returns IANA timezones as a flat list sorted alphabetically by city name.
	 *
	 * @since 1.3.1
	 *
	 * @return array
	 */
	public static function get_timezone_cities() {

		static $cache = null;

		if ( $cache !== null ) {
			return $cache;
		}

		$cities = [];

		foreach ( timezone_identifiers_list() as $tz_identifier ) {

			// Skip identifiers without a continent separator.
			if ( strpos( $tz_identifier, '/' ) === false ) {
				continue;
			}

			// Extract city name from the last segment.
			$parts = explode( '/', $tz_identifier );
			$city  = str_replace( '_', ' ', end( $parts ) );

			$cities[] = [
				'value' => $tz_identifier,
				'label' => $city,
			];
		}

		// Sort alphabetically by city name.
		usort( $cities, fn( $a, $b ) => strcmp( $a['label'], $b['label'] ) );

		$cache = $cities;

		return $cache;
	}

	/**
	 * Render an appointment location for display.
	 *
	 * For online locations, delegates URL resolution to the provider
	 * via get_appointment_url(). For in-person locations, renders plain text.
	 *
	 * @since 1.4.0
	 *
	 * @param AbstractLocation $location    Location model.
	 * @param AppointmentModel $appointment Appointment model.
	 *
	 * @return void
	 */
	public static function render_appointment_location_for_display( $location, $appointment ) {

		$display_name = $location->get_display_name();

		if ( $location->get_type() !== 'online' ) {
			echo esc_html( $display_name );

			return;
		}

		$url = $location->get_provider()->get_appointment_url( $appointment->online_location ?? [] );

		if ( ! empty( $url ) ) {
			printf(
				'<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a>',
				esc_url( $url ),
				esc_html( $display_name )
			);

			return;
		}

		echo esc_html( $display_name );
	}

	/**
	 * Get avatar data for an entity (employee, service, etc).
	 *
	 * @since 1.3.0
	 *
	 * @param string $name     The entity name.
	 * @param int    $image_id The featured image attachment ID.
	 *
	 * @return array Avatar data with 'initials' and 'image' keys.
	 */
	public static function get_entity_avatar_data( $name, $image_id ) {

		$retval = [
			'initials' => self::get_name_initials( $name ),
			'image'    => wp_get_attachment_image_src( $image_id, 'thumbnail' ),
		];

		return $retval;
	}

	/**
	 * Get name initials from a full name.
	 *
	 * @since 1.3.0
	 *
	 * @param string $name The full name.
	 *
	 * @return string Uppercase initials or empty string.
	 */
	public static function get_name_initials( $name ) {

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

		// Split name into parts and filter out empty strings.
		$name_parts = array_filter( explode( ' ', trim( $name ) ) );

		// If no valid name parts, return empty.
		if ( empty( $name_parts ) ) {
			return '';
		}

		// Single name: return first character.
		if ( count( $name_parts ) === 1 ) {
			return strtoupper( mb_substr( $name_parts[0], 0, 1 ) );
		}

		// Multiple names: return first character of first name + first character of second name.
		$first_initial  = strtoupper( mb_substr( $name_parts[0], 0, 1 ) );
		$second_initial = strtoupper( mb_substr( $name_parts[1], 0, 1 ) );

		return $first_initial . $second_initial;
	}

	/**
	 * Generate a consistent avatar color class based on name hash.
	 *
	 * Uses a deterministic hash function to assign one of 8 predefined colors
	 * to a name. The same name will always produce the same color.
	 *
	 * @since 1.3.0
	 *
	 * @param string $name The entity name to hash.
	 *
	 * @return string CSS class name for the avatar color.
	 */
	public static function generate_avatar_color_class( $name ) {

		// Color names matching SCSS $color-accent-*-50 variables.
		$color_names = [
			'orange',
			'red',
			'pink',
			'purple',
			'blue',
			'teal',
			'green',
			'yellow',
		];

		// Hash function (same algorithm as JavaScript implementation).
		$hash   = 0;
		$length = strlen( $name );

		for ( $i = 0; $i < $length; $i++ ) {
			$hash = ord( $name[ $i ] ) + ( ( $hash << 5 ) - $hash );
			$hash = $hash & 0xFFFFFFFF; // Keep within 32-bit bounds to prevent float conversion.
		}

		// Map hash to color index.
		$index = abs( $hash ) % count( $color_names );

		return 'avatar-color-' . $color_names[ $index ];
	}

	/**
	 * Render entity avatar (image or initials fallback).
	 *
	 * @since 1.3.0
	 *
	 * @param string $name     The entity name.
	 * @param int    $image_id The featured image attachment ID.
	 */
	public static function render_entity_avatar( $name, $image_id ) {

		$avatar      = self::get_entity_avatar_data( $name, $image_id );
		$color_class = '';

		// Generate color class based on name hash for initials.
		if ( empty( $avatar['image'] ) && ! empty( $avatar['initials'] ) ) {
			$color_class = ' ' . self::generate_avatar_color_class( $name );
		}

		$html = wp_sprintf(
			'<div class="sugar-calendar-bookings-entity-avatar%1$s">',
			esc_attr( $color_class )
		);

		if ( ! empty( $avatar['image'] ) && isset( $avatar['image'][0] ) ) {

			$html .= wp_sprintf(
				'<img src="%1$s" alt="%2$s" width="%3$s" height="%4$s" class="%5$s" />',
				esc_url( $avatar['image'][0] ),
				esc_attr( $name ),
				esc_attr( $avatar['image'][1] ),
				esc_attr( $avatar['image'][2] ),
				esc_attr( 'sugar-calendar-bookings-entity-avatar__image' )
			);
		} elseif ( ! empty( $avatar['initials'] ) ) {
			$html .= wp_sprintf(
				'<span class="sugar-calendar-bookings-entity-avatar__initials">%1$s</span>',
				esc_html( $avatar['initials'] )
			);
		}

		$html .= '</div>';

		echo wp_kses(
			$html,
			[
				'div'  => [
					'class' => [],
				],
				'img'  => [
					'src'    => [],
					'alt'    => [],
					'width'  => [],
					'height' => [],
					'class'  => [],
				],
				'span' => [
					'class' => [],
				],
			]
		);
	}

	/**
	 * Render an image upload field using the WordPress media library.
	 *
	 * @since 1.4.0
	 *
	 * @param array $args Field arguments.
	 */
	public static function image_upload( $args = [] ) {

		$defaults = [
			'id'          => '',
			'name'        => '',
			'value'       => '',
			'image_name'  => '',
			'label'       => '',
			'description' => '',
		];

		$args = wp_parse_args( $args, $defaults );

		$attachment_id  = absint( $args['value'] );
		$attachment_url = $attachment_id ? wp_get_attachment_image_url( $attachment_id, 'thumbnail' ) : '';
		$has_image      = ! empty( $attachment_url );
		$name_attr      = self::get_name_attribute( $args['name'] );
		$id             = 'sugar-calendar-bookings-setting-' . $args['id'];
		$hint_text      = $has_image && ! empty( $args['image_name'] ) ? $args['image_name'] : esc_html__( '.jpeg, .png & .webp', 'sugar-calendar-bookings-scheduling-appointments-lite' );

		ob_start();
		?>
		<div class="sugar-calendar-bookings-image-upload" id="<?php echo esc_attr( $id ); ?>">
			<div class="sugar-calendar-bookings-image-upload__preview<?php echo $has_image ? '' : ' sugar-calendar-bookings-image-upload__preview--hidden'; ?>">
				<img src="<?php echo esc_url( $attachment_url ); ?>" alt="" />
				<a href="#" class="sugar-calendar-bookings-image-upload__remove">
					<?php esc_html_e( 'Change', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
				</a>
			</div>
			<button type="button" class="sugar-calendar-bookings-btn sugar-calendar-bookings-btn--tertiary sugar-calendar-bookings-btn--sm sugar-calendar-bookings-image-upload__button<?php echo $has_image ? ' sugar-calendar-bookings-image-upload__button--hidden' : ''; ?>">
				<?php esc_html_e( 'Upload Image', 'sugar-calendar-bookings-scheduling-appointments-lite' ); ?>
			</button>
			<p class="sugar-calendar-bookings-image-upload__hint">
				<?php echo esc_html( $hint_text ); ?>
			</p>
			<input type="hidden" name="<?php echo esc_attr( $name_attr ); ?>" value="<?php echo esc_attr( $attachment_id ); ?>" class="sugar-calendar-bookings-image-upload__input" />
		</div>
		<?php
		$content = ob_get_clean();

		self::field_wrapper( $args, $content );
	}

	/**
	 * Render an international phone input field.
	 *
	 * The field is enhanced with intl-tel-input via JavaScript.
	 * Stores the full international number in a hidden input.
	 *
	 * @since 1.4.0
	 *
	 * @param array $args Field arguments.
	 */
	public static function phone_input( $args = [] ) {

		$defaults = [
			'id'          => '',
			'name'        => '',
			'value'       => '',
			'label'       => '',
			'description' => '',
		];

		$args = wp_parse_args( $args, $defaults );

		$name_attr = self::get_name_attribute( $args['name'] );
		$id        = 'sugar-calendar-bookings-setting-' . $args['id'];

		ob_start();
		?>
		<div class="sugar-calendar-bookings-phone-input" id="<?php echo esc_attr( $id ); ?>">
			<input
				type="tel"
				class="sugar-calendar-bookings-phone-input__tel"
			/>
			<input type="hidden" name="<?php echo esc_attr( $name_attr ); ?>" value="<?php echo esc_attr( $args['value'] ); ?>" class="sugar-calendar-bookings-phone-input__hidden" />
		</div>
		<?php
		$content = ob_get_clean();

		self::field_wrapper( $args, $content );
	}
}
