<?php
/**
 * Simply Schedule Appointments Templates.
 *
 * @since   2.0.3
 * @package Simply_Schedule_Appointments
 */


/**
 * Simply Schedule Appointments Templates.
 *
 * @since 2.0.3
 */
class SSA_Templates {
	/**
	 * Parent plugin class.
	 *
	 * @since 2.0.3
	 *
	 * @var   Simply_Schedule_Appointments
	 */
	protected $plugin = null;

	/**
	 * Whether batch-mode vars caching is active.
	 * Off by default — must be explicitly enabled by callers (e.g. ICS exporter).
	 * Zero effect on any code path that never calls enable_batch_mode().
	 *
	 * @var bool
	 */
	protected $batch_mode_enabled = false;

	/**
	 * Vars cache used only while batch_mode_enabled is true.
	 * Keyed by appointment ID; cleared when batch mode is disabled.
	 *
	 * @var array
	 */
	protected $batch_vars_cache = array();

	/**
	 * Constructor.
	 *
	 * @since  2.0.3
	 *
	 * @param  Simply_Schedule_Appointments $plugin Main plugin object.
	 */
	public function __construct( $plugin ) {
		$this->plugin = $plugin;
		
		SSA_Utils::define( 'SSA_TEMPLATE_DEBUG_MODE', false );

		$this->hooks();
	}

	/**
	 * Initiate our hooks.
	 *
	 * @since  2.0.3
	 */
	public function hooks() {
		add_filter( 'ssa/templates/get_template_vars', array( $this, 'add_global_template_vars' ), 5, 2 );
		add_filter( 'ssa/templates/get_template_vars', array( $this, 'add_appointment_template_vars' ), 5, 2 );
		add_filter( 'ssa/templates/get_template_vars', array( $this, 'add_appointment_calendar_link_vars' ), 5, 2 );
		add_filter( 'ssa/templates/get_template_vars', array( $this, 'add_example_appointment_type_template_vars' ), 5, 2 );
		add_filter( 'ssa/templates/get_template_vars', array( $this, 'ssa_get_tec_vars' ), 10, 2 );
	
	}

	public function get_template_vars( $template, $vars = array() ) {
		// Batch-mode caching is opt-in and disabled by default.
		// Only active when a caller (e.g. ICS exporter) explicitly calls enable_batch_mode().
		// All regular code paths are completely unaffected.
		if ( $this->batch_mode_enabled && isset( $vars['appointment_id'] ) ) {
			$cache_key = (int) $vars['appointment_id'];
			if ( isset( $this->batch_vars_cache[ $cache_key ] ) ) {
				return $this->batch_vars_cache[ $cache_key ];
			}
		}

		$vars = apply_filters( 'ssa/templates/get_template_vars', $vars, $template );

		if ( $this->batch_mode_enabled && isset( $cache_key ) ) {
			$this->batch_vars_cache[ $cache_key ] = $vars;
		}

		return $vars;
	}

	/**
	 * Enable per-appointment vars caching for batch rendering.
	 *
	 * Call before processing a collection of appointments (e.g. ICS feed generation)
	 * to prevent the template-var filter chain from re-running for each Twig property
	 * (title, description, location) on the same appointment.
	 * Always pair with a disable_batch_mode() call afterwards.
	 */
	public function enable_batch_mode() {
		$this->batch_mode_enabled = true;
		$this->batch_vars_cache   = array();
	}

	/**
	 * Disable batch-mode caching and clear the vars cache.
	 */
	public function disable_batch_mode() {
		$this->batch_mode_enabled = false;
		$this->batch_vars_cache   = array();
	}

	public function add_global_template_vars( $vars, $template ) {
		if ( empty( $vars['Global'] ) ) {
			$vars['Global'] = array();
		}

		$global_settings = $this->plugin->settings->get()['global'];
		$vars['Global'] = array_merge( $vars['Global'], array(
			'site_url' => site_url(),
			'home_url' => home_url(),
			'company_name' => get_bloginfo('name')
		), $global_settings );

		return $vars;
	}

	/**
	 * Add appointment template vars.
	 *
	 * @since  5.5.3
	 *
	 * @param  array  $vars Template vars.
	 * @param  string $template Template name.
	 *
	 * @return array
	 */
	public function add_appointment_calendar_link_vars( $vars, $template ) {
		if ( empty( $vars['appointment_id'] ) ) {
			return $vars;
		}

		if ( empty( $vars['Appointment'] ) ) {
			$vars['Appointment'] = array();
		}
		remove_filter( 'ssa/templates/get_template_vars', array( ssa()->templates, 'add_appointment_calendar_link_vars' ), 5, 2 );
		$appointment_obj = new SSA_Appointment_Object( (int) $vars['appointment_id'] );
		// Add to calendar link.
		$vars['add_to_calendar_link']         = array();
		$vars['add_to_calendar_link']['ics']  = $appointment_obj->get_ics_download_url( 'customer' );
		$vars['add_to_calendar_link']['gcal'] = $appointment_obj->get_gcal_add_link( 'customer' );

		// Get public token for ics download.
		$public_token = ssa()->appointment_model->get_id_token( $vars['appointment_id'] );

		$vars['add_to_calendar_link']['ics'] = add_query_arg(
			array(
				'token' => $public_token,
			),
			$vars['add_to_calendar_link']['ics']
		);

		add_filter( 'ssa/templates/get_template_vars', array( ssa()->templates, 'add_appointment_calendar_link_vars' ), 5, 2 );

		return $vars;
	}

	public function add_example_appointment_type_template_vars( $vars, $template ) {
		if ( empty( $vars['example_appointment_type_id'] ) ) {
			return $vars;
		}

		if ( empty( $vars['Appointment'] ) ) {
			$vars['Appointment'] = array();
		}

		$settings = $this->plugin->settings->get();
		$appointment_type_object = new SSA_Appointment_Type_Object( (int)$vars['example_appointment_type_id'] );
		
		$vars['Appointment']['AppointmentType'] = $appointment_type_object->data;
		if ( isset( $vars['Appointment']['AppointmentType']['availability'] ) ) {
			unset( $vars['Appointment']['AppointmentType']['availability'] );
		}
		if ( isset( $vars['Appointment']['AppointmentType']['notifications'] ) ) {
			unset( $vars['Appointment']['AppointmentType']['notifications'] );
		}
		if ( isset( $vars['Appointment']['AppointmentType']['google_calendars_availability'] ) ) {
			unset( $vars['Appointment']['AppointmentType']['google_calendars_availability'] );
		}
		if ( !empty( $vars['Appointment']['AppointmentType']['custom_customer_information'] ) ) {
			$vars['Appointment']['AppointmentType']['customer_information'] = $vars['Appointment']['AppointmentType']['custom_customer_information'];
		}
		if ( isset( $vars['Appointment']['AppointmentType']['custom_customer_information'] ) ) {
			unset( $vars['Appointment']['AppointmentType']['custom_customer_information'] );
		}
		if ( isset( $vars['Author'] ) ) {
			$vars['Customer'] = $vars['Author'];
			unset( $vars['Author'] );
		}

		$vars['Appointment']['customer_information'] = array();
		$vars['Appointment']['customer_information_strings'] = array();
		foreach ($vars['Appointment']['AppointmentType']['customer_information'] as $key => $value) {
			$vars['Appointment']['customer_information'][$value['field']] = __( '[customer info will go here...]', 'simply-schedule-appointments' );
			$vars['Appointment']['customer_information_strings'][$value['field']] = __( '[customer info will go here...]', 'simply-schedule-appointments' );
		}
		$vars['Appointment']['start_date'] = gmdate( 'Y-m-d H:i:s' );
		$vars['Appointment']['end_date'] = gmdate( 'Y-m-d H:i:s' );
		$vars['Appointment']['status'] = 'booked';
		$vars['Appointment']['customer_id'] = 0;
		$vars['Appointment']['customer_timezone'] = 'UTC';
		$vars['Appointment']['appointment_type_id'] = $vars['example_appointment_type_id'];

		$vars['admin_email'] = $settings['global']['admin_email'];
		$vars['customer_email'] = $vars['Appointment']['customer_information']['Email'];
		$vars['customer_name'] = $vars['Appointment']['customer_information']['Name'];
		$vars['attendees_list'] = __( '[List of attendees will go here...]', 'simply-schedule-appointments' );
		$vars['instructions'] = $vars['Appointment']['AppointmentType']['instructions'];
		$vars['location'] = __( '[Event location info will go here...]', 'simply-schedule-appointments' );

		// Refund policy
		$vars['refund_policy'] = __('[Refund Policy will go here...]', 'simply-schedule-appointments');

		// Add to calendar link.
		$vars['add_to_calendar_link'] = array(
			'ics'  => __( '[Link to save .ics file will go here...]', 'simply-schedule-appointments' ),
			'gcal' => __( '[Link to add to calendar will go here...]', 'simply-schedule-appointments' ),
		);

		return $vars;
	}

	public function add_appointment_template_vars( $vars, $template ) {
		if ( empty( $vars['appointment_id'] ) ) {
			return $vars;
		}

		if ( empty( $vars['Appointment'] ) ) {
			$vars['Appointment'] = array();
		}

		$settings = $this->plugin->settings->get();
		$appointment_obj = new SSA_Appointment_Object( (int)$vars['appointment_id'] );

		$eol = "\r\n";

		$vars['Appointment'] = array_merge( $vars['Appointment'], $appointment_obj->get_data( 1 ) );
		$vars['Appointment']['customer_information_strings'] = array();
		foreach ( $vars['Appointment']['customer_information'] as $key => $value ) {
			if (is_array($value)) {
				$string_value = implode(', ', $value);
				$vars['Appointment']['customer_information_strings'][$key] = htmlspecialchars($string_value);
				$vars['Appointment']['customer_information'][$key] = array_map('htmlspecialchars', $value);
			} else {
				$value = htmlspecialchars($value);
				$vars['Appointment']['customer_information'][$key] = $value;
				$vars['Appointment']['customer_information_strings'][$key] = $value;
			}
		}

		if ( isset( $vars['Appointment']['AppointmentType']['availability'] ) ) {
			unset( $vars['Appointment']['AppointmentType']['availability'] );
		}
		if ( isset( $vars['Appointment']['AppointmentType']['notifications'] ) ) {
			unset( $vars['Appointment']['AppointmentType']['notifications'] );
		}
		if ( isset( $vars['Appointment']['AppointmentType']['google_calendars_availability'] ) ) {
			unset( $vars['Appointment']['AppointmentType']['google_calendars_availability'] );
		}
		if ( !empty( $vars['Appointment']['AppointmentType']['custom_customer_information'] ) ) {
			$vars['Appointment']['AppointmentType']['customer_information'] = $vars['Appointment']['AppointmentType']['custom_customer_information'];
		}
		if ( isset( $vars['Appointment']['AppointmentType']['custom_customer_information'] ) ) {
			unset( $vars['Appointment']['AppointmentType']['custom_customer_information'] );
		}

		if ( isset( $vars['Author'] ) ) {
			$vars['Customer'] = $vars['Author'];
			unset( $vars['Author'] );
		}

		$vars['admin_email'] = $settings['global']['admin_email'];
		$vars['customer_email'] = isset ( $vars['Appointment']['customer_information']['Email'] ) ? $vars['Appointment']['customer_information']['Email'] : '';
		$vars['customer_name'] = isset ( $vars['Appointment']['customer_information']['Name'] ) ? $vars['Appointment']['customer_information']['Name'] : '';

		
		// =================================================================================================
		// important: get meta data early on
		// =================================================================================================
		$meta = $this->plugin->appointment_model->get_metas( (int)$vars['appointment_id'], array( 'booking_url', 'booking_post_id', 'booking_title', 'cancelation_note', 'canceled_by_user_id', 'rescheduling_note', 'rescheduled_by_user_id', 'rescheduled_from_start_dates' ) );

		
		/* BEGIN customer_start_date */
		if ( ! empty( $vars['Appointment']['customer_locale'] ) ) {
			$this->plugin->translation->set_programmatic_locale( esc_attr( $vars['Appointment']['customer_locale'] ) );
		}
		$format = SSA_Utils::localize_default_date_strings( 'F j, Y g:i a' ) . ' (T)';

		if ( empty( $vars['Appointment']['customer_timezone'] ) ) {
			$customer_timezone = $appointment_obj->get_date_timezone();
			$vars['Appointment']['customer_timezone'] = $customer_timezone->getName();
		} else if ( false !== strpos( $vars['Appointment']['customer_timezone'], 'Etc/' ) ) {
			$customer_timezone = $appointment_obj->get_date_timezone();
			$vars['Appointment']['customer_timezone'] = $customer_timezone->getName();
		} else {
			// Use safe timezone helper to handle PHP 8.3+ deprecated timezone names
			$customer_timezone = SSA_Utils::safe_timezone( 
				$vars['Appointment']['customer_timezone'],
				$appointment_obj->get_date_timezone() // Fallback to appointment's default timezone
			);
			$vars['Appointment']['customer_timezone'] = $customer_timezone->getName();
		}

		$vars['Appointment']['customer_start_date'] = ssa_datetime( $vars['Appointment']['start_date'] )->setTimezone( $customer_timezone )->format( $format );
		$vars['Appointment']['customer_start_date'] = SSA_Utils::translate_formatted_date( $vars['Appointment']['customer_start_date'] );
		/* END customer_start_date */
		
		/* Start customer_prev_start_dates - rescheduling history */
		if( ! empty( $meta['rescheduled_from_start_dates'] ) ) {
			$customer_prev_start_dates = array_map( function( $date ) use ( $format, $vars, $customer_timezone ) {
				$value = ssa_datetime( $date )->setTimezone( $customer_timezone )->format( $format );
				$value = SSA_Utils::translate_formatted_date( $value );
				return $value;
			}, array_reverse( $meta['rescheduled_from_start_dates'] ) );
			// in business timezone
			$vars['Appointment']['customer_prev_start_dates'] = implode(",\n", $customer_prev_start_dates );
			$vars['Appointment']['customer_prev_start_date'] = array_shift( $customer_prev_start_dates );
		} else {
			$vars['Appointment']['customer_prev_start_dates'] = null;
			$vars['Appointment']['customer_prev_start_date'] = null;
		}
		/* End customer_prev_start_dates - rescheduling history */

		// =================================================================================================
		// important: reset the locale to the default locale before proceeding to the business timezone
		// =================================================================================================
		$this->plugin->translation->set_programmatic_locale( null );
		
		/* BEGIN business_start_date */
		$format = SSA_Utils::localize_default_date_strings( 'F j, Y g:i a' ) . ' (T)';
		$vars['Appointment']['business_start_date'] = ssa_datetime( $vars['Appointment']['start_date'] );

		$vars['Appointment']['business_start_date'] = $this->plugin->utils->get_datetime_as_local_datetime( $vars['Appointment']['business_start_date'], $vars['Appointment']['appointment_type_id'] )->format( $format );
		$vars['Appointment']['business_start_date'] = SSA_Utils::translate_formatted_date( $vars['Appointment']['business_start_date'] );
		/* END business_start_date */
		
		/* Start business_prev_start_dates - rescheduling history */
		if( ! empty( $meta['rescheduled_from_start_dates'] ) ) {
			$business_prev_start_dates = array_map( function( $date ) use ( $format, $vars ) {
				$value = ssa_datetime( $date );
				$value = $this->plugin->utils->get_datetime_as_local_datetime( $value, $vars['Appointment']['appointment_type_id'] )->format( $format );
				$value = SSA_Utils::translate_formatted_date( $value );
				return $value;
			}, array_reverse( $meta['rescheduled_from_start_dates'] ) );
			// in business timezone
			$vars['Appointment']['business_prev_start_dates'] = implode(",\n", $business_prev_start_dates );
			$vars['Appointment']['business_prev_start_date'] = array_shift( $business_prev_start_dates );
		} else {
			$vars['Appointment']['business_prev_start_dates'] = null;
			$vars['Appointment']['business_prev_start_date'] = null;
		}
		/* End business_prev_start_dates - rescheduling history */

		if ( empty( $vars['Appointment']['customer_timezone'] ) || false !== strpos( $vars['Appointment']['customer_timezone'], 'Etc/' ) ) {
			$vars['Appointment']['customer_timezone'] = $vars['Appointment']['date_timezone'];
		}

		// If Appointment date_timezone is a DateTimeZone object, get the name.
		if ( is_object( $vars['Appointment']['date_timezone'] ) ) {
			$vars['Appointment']['date_timezone'] = $vars['Appointment']['date_timezone']->getName();
		}

		// Include booking page variables
		$meta = $this->plugin->appointment_model->get_metas( (int)$vars['appointment_id'], array( 'booking_url', 'booking_post_id', 'booking_title', 'cancelation_note', 'canceled_by_user_id', 'rescheduling_note', 'rescheduled_by_user_id', 'mepr_membership_id' ) );

		$vars['booking_url'] = isset( $meta['booking_url'] ) ? $meta['booking_url'] : null;
		$vars['booking_post_id'] = isset( $meta['booking_post_id'] ) ? $meta['booking_post_id'] : null;
		$vars['booking_title'] = isset( $meta['booking_title'] ) ? $meta['booking_title'] : null;
		$vars['cancelation_note'] = isset( $meta['cancelation_note'] ) ? $meta['cancelation_note'] : null;
		$vars['canceled_by_name'] = isset( $meta['canceled_by_user_id'] ) ? $this->get_user_name_from_user_id( $meta['canceled_by_user_id'] ) :  null;
		$vars['rescheduling_note'] = isset( $meta['rescheduling_note'] ) ? $meta['rescheduling_note'] : null;
		$vars['rescheduled_by_name'] = isset( $meta['rescheduled_by_user_id'] ) ? $this->get_user_name_from_user_id( $meta['rescheduled_by_user_id'] ) :  null;
		$vars['location'] = isset($vars['Appointment']['web_meeting_url'] ) ? $vars['Appointment']['web_meeting_url'] : '';
		$vars['mepr_membership_title'] = isset( $meta['mepr_membership_id'] ) ? $this->get_mepr_membership_title( $meta['mepr_membership_id'] ) :  null;

		// List of attendees
		$vars['attendees_list'] = '';
		$appointments = $appointment_obj->query_group_appointments();
		if( $appointments ) {
			$appointments_count = count( $appointments );
			foreach ($appointments as $index => $appointment) {
				$separator = $index < $appointments_count - 1 ? ', ' : '';
				if ( ! $appointment->is_booked() ) {
					continue;
				}
	
				$vars['attendees_list'] .= $appointment->get_customer_name() . $separator . $eol;
			}		
		}

		if ( empty( $vars['instructions'] ) ) {
			$vars['instructions'] = '';
		}
		$vars['instructions'] .= $vars['Appointment']['AppointmentType']['instructions'] ?? '';

		// Refund policy
		$vars['refund_policy'] = isset($vars['Appointment']['AppointmentType']['payments']) && isset($vars['Appointment']['AppointmentType']['payments']['refund_policy']) ? $vars['Appointment']['AppointmentType']['payments']['refund_policy'] : '';

		// adding empty values for {{team_member_name}}, {{team_member_email}}, {{team_member_phone}} in case they're used without the business edition
		$team_member_keys = array( 'name', 'email', 'phone' );
		foreach ($team_member_keys as $key) {
			$vars['team_member_'.$key] = '';
		}

		return $vars;
	}
	
	
	public function render_template_string( $template_string, $vars ) {
		
		$loader = new Twig\Loader\ArrayLoader(
			array(
				'template'   => $template_string,
				'autoescape' => false,
			)
		);

		$twig = new Twig\Environment( $loader );
		
		// enable sandbox mode
		$twig->addExtension( $this->getCustomTwigSandboxExtension() );
		
		$twig->addExtension( new SSA_Twig_Extension() );
		$twig->getExtension( \Twig\Extension\CoreExtension::class )->setTimezone( 'UTC' );
		try {
			$rendered_template = $twig->render( 'template', $vars );
		} catch ( Exception $e ) {
			return $this->generate_twig_error_summary($e, $template_string);
		}
		
		// need to unescape the html so PHP can process it in wp_kses_post
		// was escaped in the frontend
		$rendered_template = html_entity_decode($rendered_template);
		
		return wp_kses_post($rendered_template);
	}

	protected function get_template_line_at_error( $line = null, $template_string = null ) {
		if( empty( $line ) || empty( $template_string ) ) {
			return;
		}
		$line = (int) $line - 1; // Convert to 0-indexed
		$lines = explode("\n", $template_string);
		return isset($lines[ $line ]) ? $lines[ $line ] : null;
	}

	protected function generate_twig_error_summary(Exception $e, $template_string) {
		$msg = '<p>Error on Line: ' . $e->getTemplateLine() . "</p>";
		$msg .= "<p>" . $e->getRawMessage() . "</p>";
		$affected_line = $this->get_template_line_at_error( $e->getTemplateLine(), $template_string );
		if( $affected_line ) {
			$msg .= "<p>Check this part of your template: <p><pre>" . $affected_line . "</pre>";
		}
		return $msg;
	}
	
	
	/**
	 * Initialize sandbox extension with custom policy to prevent template-injections
	 *
	 * @return \Twig\Extension\SandboxExtension
	 */
	public function getCustomTwigSandboxExtension()
	{
		$tags = array('if', 'else', 'elseif', 'endif', 'include', 'import', 'block', 'set', 'for');
		$filters = array( 'internationalize', 'trim', 'join', 'number_format', 'date', 'escape', 'trans', 'split', 'length', 'slice', 'lower', 'raw', 'filter', 'date', 'upper', 'link', 'nl2br', 'replace');
		$methods = array();
		$properties = array();
		$functions = array('include', 'path', 'absolute_url', 'asset', 'is_granted', 'attribute');

		$policy = new \Twig\Sandbox\SecurityPolicy($tags, $filters, $methods, $properties, $functions);
		return new \Twig\Extension\SandboxExtension( $policy, true );
	}



	/**
	 * Get template part.
	 *
	 * SSA_TEMPLATE_DEBUG_MODE will prevent overrides in themes from taking priority.
	 *
	 * @access public
	 * @param mixed  $slug Template slug.
	 * @param string $name Template name (default: '').
	 * @param boolean $echo  Should echo $output to screen. (default: false).
	 * 
	 * @return string
	 */
	function get_template_part( $slug, $name = '', $echo = false ) {
		ob_start();
		$template = '';

		// Look in yourtheme/slug-name.php and yourtheme/ssa/slug-name.php.
		if ( $name && ! SSA_TEMPLATE_DEBUG_MODE ) {
			$template = locate_template( array( "{$slug}-{$name}.php", $this->plugin->template_subdirectory() . "{$slug}-{$name}.php" ) );
		}

		// Get default slug-name.php.
		if ( ! $template && $name && file_exists( $this->plugin->dir() . "templates/{$slug}-{$name}.php" ) ) {
			$template = $this->plugin->dir() . "templates/{$slug}-{$name}.php";
		}

		// If template file doesn't exist, look in yourtheme/slug.php and yourtheme/ssa/slug.php.
		if ( ! $template && ! SSA_TEMPLATE_DEBUG_MODE ) {
			$template = locate_template( array( "{$slug}.php", $this->plugin->template_subdirectory() . "{$slug}.php" ) );
		}

		// Allow 3rd party plugins to filter template file from their plugin.
		$template = apply_filters( 'ssa/templates/get_template_part', $template, $slug, $name );

		if ( $template ) {
			load_template( $template, false );
		}

		$output = ob_get_clean();
		if ( !empty( $echo ) ) {
			echo $output;
		}

		return $output;
	}

	/**
	 * Get other templates passing attributes and including the file.
	 *
	 * @access public
	 * @param string $template_name Template name.
	 * @param array  $args          Arguments. (default: array).
	 * @param string $template_path Template path. (default: '').
	 * @param string $default_path  Default path. (default: '').
	 * @param boolean $echo  Should echo $output to screen. (default: false).
	 * 
	 * @return string
	 */
	function get_template( $template_name, $args = array(), $template_path = '', $default_path = '', $echo = false ) {
		ob_start();

		if ( ! empty( $args ) && is_array( $args ) ) {
			extract( $args ); // @codingStandardsIgnoreLine
		}

		$located = $this->locate_template( $template_name, $template_path, $default_path );

		if ( ! file_exists( $located ) ) {
			return;
		}

		// Allow 3rd party plugin filter template file from their plugin.
		$located = apply_filters( 'ssa/templates/get_template', $located, $template_name, $args, $template_path, $default_path );

		do_action( 'ssa_before_template_part', $template_name, $template_path, $located, $args );

		include $located;

		do_action( 'ssa_after_template_part', $template_name, $template_path, $located, $args );

		$output = ob_get_clean();
		if ( !empty( $echo ) ) {
			echo $output;
		}

		return $output;
	}

	public function get_template_rendered( $template, $template_vars = array() ) {
		$template_string = $this->get_template( $template );
		$template_vars = $this->get_template_vars( $template, $template_vars );
		if ( empty( $template_string ) ) {
			return false;
		}

		$rendered_template_string = $this->render_template_string( $template_string, $template_vars );

		return $rendered_template_string;
	}


	/**
	 * Like get_template, but returns the HTML instead of outputting.
	 *
	 * @see get_template_html
	 * @since 2.5.0
	 * @param string $template_name Template name.
	 * @param array  $args          Arguments. (default: array).
	 * @param string $template_path Template path. (default: '').
	 * @param string $default_path  Default path. (default: '').
	 *
	 * @return string
	 */
	function get_template_html( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
		ob_start();
		$this->get_template( $template_name, $args, $template_path, $default_path );
		return ob_get_clean();
	}
	/**
	 * Locate a template and return the path for inclusion.
	 *
	 * This is the load order:
	 *
	 * yourtheme/$template_path/$template_name
	 * yourtheme/$template_name
	 * $default_path/$template_name
	 *
	 * @access public
	 * @param string $template_name Template name.
	 * @param string $template_path Template path. (default: '').
	 * @param string $default_path  Default path. (default: '').
	 * @return string
	 */
	function locate_template( $template_name, $template_path = '', $default_path = '' ) {
		if ( ! $template_path ) {
			$template_path = $this->plugin->template_subdirectory();
		}

		if ( ! $default_path ) {
			$default_path = $this->plugin->dir() . 'templates/';
		}

		// Look within passed path within the theme - this is priority.
		$template = locate_template(
			array(
				trailingslashit( $template_path ) . $template_name,
				$template_name,
			)
		);

		// Get default template/.
		if ( ! $template || SSA_TEMPLATE_DEBUG_MODE ) {
			$template = $default_path . $template_name;
		}

		// Return what we found.
		return apply_filters( 'ssa/templates/locate_template', $template, $template_name, $template_path );
	}

	/**
	 * Locate a template and return the path for inclusion.
	 *
	 * This is the load order:
	 *
	 * yourtheme/$template_path/$template_name
	 * yourtheme/$template_name
	 * $default_path/$template_name
	 *
	 * @access public
	 * @param string $template_name Template name.
	 * @param string $template_path Template path. (default: '').
	 * @param string $default_path  Default path. (default: '').
	 * @return string
	 */
	function locate_template_url( $template_name, $template_path = '', $default_path = '' ) {
		$template = $this->locate_template( $template_name, $template_path, $default_path );

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

		if ( $themes_pos = strpos( $template, 'themes/' ) ) {
			return content_url( substr( $template, $themes_pos ) );
		}

		if ( $plugins_pos = strpos( $template, 'plugins/' ) ) {
			return content_url( substr( $template, $plugins_pos ) );
		}

		return false;
	}


	public function cleanup_variables_in_string( $string ) {
		$string =  $this->strip_mustache_content($string);		

		$string = str_replace(
			array( '{{', '{{  ', '}}', '  }}', '{%', '{%  ', '%}', '  %}', "\n }}", "\n}}" ),
			array( '{{ ', '{{ ', ' }}', ' }}', '{% ', '{% ', ' %}', ' %}', ' }}', '}}' ),
			$string
		);	

		return $string;
	}

	/**
	 * Cleans HTML tags within mustache and template tags in the provided text.
	 * 
	 * This function processes the input text to remove any HTML entities or elements
	 * inside mustache tags ({{ }}) and template tags ({% %}). It ensures that the content
	 * within these tags is stripped of any HTML, leaving only the plain text or function calls.
	 *
	 * @param string $text The input text containing mustache and template tags.
	 * @return string The cleaned text with HTML tags removed from mustache and template tags.
	 */
	public function strip_mustache_content($text) {
		$mustachePattern = '/{{\s*([^}]+)\s*}}/';
				$mustacheCallback = function ($matches) {
				return '{{' . strip_tags(trim($matches[1])) . '}}';
		};
		$text = preg_replace_callback($mustachePattern, $mustacheCallback, $text);
		
		$templatePattern = '/{%\s*([^}]+)\s*%}/';
		$templateCallback = function ($matches) {
				return '{%' . strip_tags(trim($matches[1])) . '%}';
		};
		$text = preg_replace_callback($templatePattern, $templateCallback, $text);
		
		return $text;
	}

	/**
	 * Having the user_id check if it's a staff member and return its display name
	 * Otherwise use get_userdata wp function to get the user name from the users table
	 *
	 * @param string $user_id
	 * @return string
	 */
	public function get_user_name_from_user_id( $user_id = null ){
		if( empty( $user_id ) ) {
			return __( 'A logged out user', 'simply-schedule-appointments' );
		}

		// Check if who canceled is a staff member
		if( class_exists( 'SSA_Staff_Model' ) ) {
			$staff_id = $this->plugin->staff_model->get_staff_id_for_user_id( $user_id );

			if( ! empty( $staff_id ) ) {
				$staff = new SSA_Staff_Object( $staff_id );
				return $staff->display_name;
			}
		}

		// Try get the display name from the wp users table or the user_login(username)
		$userdata = get_userdata( $user_id );

		if( ! empty( $userdata ) &&  ! empty( $userdata->display_name ) ) {
			return $userdata->display_name;
		} 
		
		if( ! empty( $userdata ) && ! empty( $userdata->user_login )  ) {
			return $userdata->user_login;
		} 

		return __( 'A logged out user', 'simply-schedule-appointments' ); // Just in case but we shouldn't really reach this far
		
	}

	/**
	 * Memberpress Integration
	 *
	 * @param int $id
	 * @return string|null
	 */
	public function get_mepr_membership_title( $id ) {
		if ( empty( $id ) || ! class_exists( 'SSA_Mepr_Membership' ) || ! class_exists( 'MeprProduct' ) ) {
			return;
		}
		$membership = new SSA_Mepr_Membership( $id );
		if ( empty( $membership->exists() ) ) {
			return;
		}
		return $membership->get_title();
	}


function ssa_get_tec_vars( $vars, $template ) {

	if ( empty( $vars['booking_post_id'] ) ) {
		return $vars;
	}

	if ( ! function_exists( 'tribe_get_venue_object' ) ) {
		return $vars;

	}

	if ( ! function_exists( 'tribe_get_organizer_object' ) ) {
		return $vars;
	
	}
	
	$event_id = $vars['booking_post_id'];

	// Add Venue Information

	$venue_object = tribe_get_venue_object($event_id);
	$venue_id = get_post_meta($event_id, '_EventVenueID', true);

	if (!empty($venue_id)) {
		$venue_post = get_post($venue_id);
        $venue_information = array(
            'name'    => !empty( $venue_post->post_title ) ? $venue_post->post_title : null,
            'address' => !empty( $venue_object->address ) ? $venue_object->address : null,
            'city'    => !empty( $venue_object->city ) ? $venue_object->city : null,
            'state'   => !empty( $venue_object->state ) ? $venue_object->state : null,
            'province'   => !empty( $venue_object->province ) ? $venue_object->province : null,
            'zip'     => !empty( $venue_object->zip ) ? $venue_object->zip : null,
        );

		$vars['TEC']['venue'] = $venue_information;

	}
	
	// Add Organizer Information

	$organizer_object = tribe_get_organizer_object($event_id);
	$organizer_id = get_post_meta($event_id, '_EventOrganizerID', true);

	if (!empty($organizer_id)) {

		$organizer_post = get_post($organizer_id);
        $organizer_information = array(
            'name'    => !empty( $organizer_post->post_title ) ? $organizer_post->post_title : null,
            'phone'   => !empty( $organizer_object->phone ) ? $organizer_object->phone : null,
            'email'   => !empty( $organizer_object->email ) ? $organizer_object->email : null,
            'website' => !empty( $organizer_object->website ) ? $organizer_object->website : null,
        );
		
		$vars['TEC']['organizer'] = $organizer_information;
		
		}

	// Add Categories for the event

	$event_categories = get_the_terms( $event_id, 'tribe_events_cat' ); 

    if ( ! empty( $event_categories ) && ! is_wp_error( $event_categories ) ) {
        $tec_categories= [];
        foreach ( $event_categories as $index => $category ) {
            $tec_categories[$index] = $category->name;
        }
        $vars['TEC']['categories'] = $tec_categories;
    }

	return $vars;

}
}
