<?php
/**
 * Simply Schedule Appointments Utils.
 *
 * @since   0.0.3
 * @package Simply_Schedule_Appointments
 */
use League\Period\Period;

/**
 * Simply Schedule Appointments Utils.
 *
 * @since 0.0.3
 */
class SSA_Utils {
	/**
	 * Parent plugin class.
	 *
	 * @since 0.0.3
	 *
	 * @var   Simply_Schedule_Appointments
	 */
	protected $plugin = null;

	protected $server_default_timezone_before_ssa;

	/**
	 * Constructor.
	 *
	 * @since  0.0.3
	 *
	 * @param  Simply_Schedule_Appointments $plugin Main plugin object.
	 */
	public function __construct( $plugin ) {
		$this->plugin = $plugin;
		$this->hooks();
	}

	/**
	 * Initiate our hooks.
	 *
	 * @since  0.0.3
	 */
	public function hooks() {

	}

	public function defensive_timezone_fix() {
		if ( 'UTC' === date_default_timezone_get() ) {
			return;
		}

		$this->server_default_timezone_before_ssa = date_default_timezone_get();
		
		// We know that setting the default_timezone on a server is bad practice
		// WordPress expects it to be UTC to function properly
		// Our plugin also expects it to be UTC to function properly
		// Unfortunately we have found that some plugins do change the default timezone
		// We only call this function as a defensive measure, so SSA can co-exist with plugins
		// that set the timezone. Looking at you...
		// 
		// * Ajax Event Calendar plugin [https://wordpress.org/support/plugin/ajax-event-calendar] 
		// *** already removed from the wordpress.org repository
		// 
		// * Series Engine plugin [https://seriesengine.com/]
		// ** Pro plugin not available on wordpress.org
		// 
		// Here's our approach to addressing this issue:
		// We ONLY set the timezone to UTC if it's something different
		// 
		// We feel that it should be forced to UTC to adhere to WordPress standards, 
		// but that will probably break users' sites running these problematic plugins
		// 
		// To try and play nicely with others and to protect the user, we will call our
		// defensive_timezone_fix() before SSA does anything where we rely on a UTC timezone
		// at the end of our functions, we will call defensive_timezone_reset() so we'll put it
		// back to whatever the server already had set.
		// 
		// We see this as the only way to co-exist with these problematic plugins and simplify
		// life for the user. If there is a better approach, please get in touch.
		// We'd love to remove this code :)
		date_default_timezone_set( 'UTC' );
	}
	public function defensive_timezone_reset() {
		if ( empty( $this->server_default_timezone_before_ssa ) || 'UTC' === $this->server_default_timezone_before_ssa ) {
			return;
		}

		// We know that setting the default_timezone on a server is bad practice
		// ^^^ See note above in defensive_timezone_fix() ^^^
		date_default_timezone_set( $this->server_default_timezone_before_ssa );
	}

	public static function site_unique_hash( $string ) {
		if (defined('SSA_AUTH_SALT')) {
			$salt = SSA_AUTH_SALT;
		} else if ( defined( 'AUTH_SALT' ) ) {
			$salt = AUTH_SALT;
		} else {
			$salt = 'M+mZDQYJlrHoRZ0OfE1ESGG5T5CgPMOgOub25eOmwJYdPLmiNLbKwXYGQfG0pkF5YCw45DVtwaWREx3Jr4hILB';
		}

		return hash_hmac('md5', $string, $salt);
	}

	/**
	 * 
	 *
	 * @param [type] $string
	 * @return void
	 */
	public static function deprecated_hash( $string ) {
		if ( defined( 'SSA_AUTH_SALT' ) ) {
			$salt = SSA_AUTH_SALT;
		} else {
			$salt = '6U2aRk6oGvAZAEXstbFNMppRF=D|H.NX!-gU:-aXGVH<)8kcF~FPor5{Z<SFr~wKz';
		}
		
		return hash_hmac('md5', $string, $salt);
	}

	public static function get_home_id() {
		return self::site_unique_hash( get_home_url() );
	}

	public static function is_assoc_array( array $arr ) {
		if ( array() === $arr ) return false;
		return array_keys( $arr ) !== range( 0, count( $arr ) - 1 );
	}

	public static function arrays_assoc_are_equal( $a, $b ) {
		if ( ! is_array( $a ) || ! is_array( $b ) ) {
			return false;
		}

		if ( count( $a ) !== count( $b ) ) {
			return false;
		}

		foreach ( $a as $key => $value ) {
			if ( ! array_key_exists( $key, $b ) ) {
					return false;
			}
			if ( is_array( $value ) ) {
				if ( ! is_array( $b[ $key ] ) ) {
					return false;
				}
				if ( ! self::arrays_assoc_are_equal( $value, $b[ $key ] ) ) {
					return false;
				}
			} else {
				if ( $value !== $b[ $key ] ) {
					return false;
				}
			}
		}
		return true;
	}

	public static function array_key( $array, $key ) {
		if ( isset( $array[ $key ] ) ) {
			return $array[ $key ];
		}

		return false;
	}

	public static function datetime( $time='now' ) {
		if ( $time instanceof DateTimeImmutable ) {
			return $time;
		}
		
		if ( 0 === strpos( $time, 'Invalid' ) ) {
			ssa_debug_log( 'SSA_Utils::datetime()  `Invalid Date` detected' );
			// $time = 'now';
			return null; // TODO: handle error state gracefully
		} else if ( empty( $time ) ) {
			$time = 'now';
		}
		
		$timezone = new DateTimeZone( 'UTC' );

		return new DateTimeImmutable( $time, $timezone );
	}

	public static function ceil_datetime( DateTimeImmutable $datetime, $mins = 5 ) {
		$seconds = $mins * 60;
		$time = ( ceil( $datetime->getTimestamp() / $seconds ) ) * $seconds;

		return $datetime->setTimestamp( $time );
	}

	public static function floor_datetime( DateTimeImmutable $datetime, $mins = 5 ) {
		$seconds = $mins * 60;
		$time = ( floor( $datetime->getTimestamp() / $seconds ) ) * $seconds;

		return $datetime->setTimestamp( $time );
	}

	/**
	 * Create DateTimeZone safely, handling PHP 8.3+ deprecated timezone names.
	 * 
	 * @since 5.8.8
	 * @param string $timezone_string Timezone identifier
	 * @param DateTimeZone|null $fallback Optional fallback timezone if creation fails
	 * @return DateTimeZone
	 */
	public static function safe_timezone( $timezone_string, $fallback = null ) {
		if ( $timezone_string instanceof DateTimeZone ) {
			return $timezone_string;
		}

		try {
			return new DateTimeZone( $timezone_string );
		} catch ( Exception $e ) {
			// Map deprecated timezone names to modern equivalents (PHP 8.3+)
			// Based on IANA timezone database backward compatibility file
			$deprecated_timezones = array(
				// US timezones
				'US/Alaska'        => 'America/Anchorage',
				'US/Aleutian'      => 'America/Adak',
				'US/Arizona'       => 'America/Phoenix',
				'US/Central'       => 'America/Chicago',
				'US/Eastern'       => 'America/New_York',
				'US/East-Indiana'  => 'America/Indiana/Indianapolis',
				'US/Hawaii'        => 'Pacific/Honolulu',
				'US/Indiana-Starke'=> 'America/Indiana/Knox',
				'US/Michigan'      => 'America/Detroit',
				'US/Mountain'      => 'America/Denver',
				'US/Pacific'       => 'America/Los_Angeles',
				'US/Samoa'         => 'Pacific/Pago_Pago',
				
				// Canada
				'Canada/Atlantic'     => 'America/Halifax',
				'Canada/Central'      => 'America/Winnipeg',
				'Canada/Eastern'      => 'America/Toronto',
				'Canada/Mountain'     => 'America/Edmonton',
				'Canada/Newfoundland' => 'America/St_Johns',
				'Canada/Pacific'      => 'America/Vancouver',
				'Canada/Saskatchewan' => 'America/Regina',
				'Canada/Yukon'        => 'America/Whitehorse',
				
				// Asia
				'Asia/Calcutta'       => 'Asia/Kolkata',
				'Asia/Katmandu'       => 'Asia/Kathmandu',
				'Asia/Saigon'         => 'Asia/Ho_Chi_Minh',
				'Asia/Chongqing'      => 'Asia/Shanghai',
				'Asia/Chungking'      => 'Asia/Shanghai',
				'Asia/Dacca'          => 'Asia/Dhaka',
				'Asia/Harbin'         => 'Asia/Shanghai',
				'Asia/Kashgar'        => 'Asia/Urumqi',
				'Asia/Macao'          => 'Asia/Macau',
				'Asia/Tel_Aviv'       => 'Asia/Jerusalem',
				'Asia/Thimbu'         => 'Asia/Thimphu',
				'Asia/Ujung_Pandang'  => 'Asia/Makassar',
				'Asia/Ulan_Bator'     => 'Asia/Ulaanbaatar',
				
				// Europe
				'Europe/Belfast'   => 'Europe/London',
				'Europe/Tiraspol'  => 'Europe/Chisinau',
				'Europe/Nicosia'   => 'Asia/Nicosia',
				
				// Africa
				'Africa/Asmera'    => 'Africa/Asmara',
				'Africa/Timbuktu'  => 'Africa/Bamako',
				
				// America
				'America/Argentina/ComodRivadavia' => 'America/Argentina/Catamarca',
				'America/Atka'                     => 'America/Adak',
				'America/Buenos_Aires'             => 'America/Argentina/Buenos_Aires',
				'America/Catamarca'                => 'America/Argentina/Catamarca',
				'America/Coral_Harbour'            => 'America/Atikokan',
				'America/Cordoba'                  => 'America/Argentina/Cordoba',
				'America/Ensenada'                 => 'America/Tijuana',
				'America/Fort_Wayne'               => 'America/Indiana/Indianapolis',
				'America/Indianapolis'             => 'America/Indiana/Indianapolis',
				'America/Jujuy'                    => 'America/Argentina/Jujuy',
				'America/Knox_IN'                  => 'America/Indiana/Knox',
				'America/Louisville'               => 'America/Kentucky/Louisville',
				'America/Mendoza'                  => 'America/Argentina/Mendoza',
				'America/Porto_Acre'               => 'America/Rio_Branco',
				'America/Rosario'                  => 'America/Argentina/Cordoba',
				'America/Virgin'                   => 'America/St_Thomas',
				
				// Australia
				'Australia/ACT'        => 'Australia/Sydney',
				'Australia/Canberra'   => 'Australia/Sydney',
				'Australia/LHI'        => 'Australia/Lord_Howe',
				'Australia/NSW'        => 'Australia/Sydney',
				'Australia/North'      => 'Australia/Darwin',
				'Australia/Queensland' => 'Australia/Brisbane',
				'Australia/South'      => 'Australia/Adelaide',
				'Australia/Tasmania'   => 'Australia/Hobart',
				'Australia/Victoria'   => 'Australia/Melbourne',
				'Australia/West'       => 'Australia/Perth',
				'Australia/Yancowinna' => 'Australia/Broken_Hill',
				
				// Brazil
				'Brazil/Acre'       => 'America/Rio_Branco',
				'Brazil/DeNoronha'  => 'America/Noronha',
				'Brazil/East'       => 'America/Sao_Paulo',
				'Brazil/West'       => 'America/Manaus',
				
				// Chile
				'Chile/Continental'  => 'America/Santiago',
				'Chile/EasterIsland' => 'Pacific/Easter',
				
				// Pacific
				'Pacific/Ponape' => 'Pacific/Pohnpei',
				'Pacific/Samoa'  => 'Pacific/Pago_Pago',
				'Pacific/Truk'   => 'Pacific/Chuuk',
				'Pacific/Yap'    => 'Pacific/Chuuk',
				
				// Country shortcuts
				'Egypt'     => 'Africa/Cairo',
				'Eire'      => 'Europe/Dublin',
				'GB'        => 'Europe/London',
				'GB-Eire'   => 'Europe/London',
				'Greenwich' => 'Etc/GMT',
				'Hongkong'  => 'Asia/Hong_Kong',
				'Iceland'   => 'Atlantic/Reykjavik',
				'Iran'      => 'Asia/Tehran',
				'Israel'    => 'Asia/Jerusalem',
				'Jamaica'   => 'America/Jamaica',
				'Japan'     => 'Asia/Tokyo',
				'Kwajalein' => 'Pacific/Kwajalein',
				'Libya'     => 'Africa/Tripoli',
				'NZ'        => 'Pacific/Auckland',
				'NZ-CHAT'   => 'Pacific/Chatham',
				'Navajo'    => 'America/Denver',
				'PRC'       => 'Asia/Shanghai',
				'Poland'    => 'Europe/Warsaw',
				'Portugal'  => 'Europe/Lisbon',
				'ROC'       => 'Asia/Taipei',
				'ROK'       => 'Asia/Seoul',
				'Singapore' => 'Asia/Singapore',
				'Turkey'    => 'Europe/Istanbul',
				'UCT'       => 'Etc/UTC',
				'Universal' => 'Etc/UTC',
				'W-SU'      => 'Europe/Moscow',
				'Zulu'      => 'Etc/UTC',
				
				// Mexico
				'Mexico/BajaNorte' => 'America/Tijuana',
				'Mexico/BajaSur'   => 'America/Mazatlan',
				'Mexico/General'   => 'America/Mexico_City',
			);

			if ( isset( $deprecated_timezones[ $timezone_string ] ) ) {
				try {
					return new DateTimeZone( $deprecated_timezones[ $timezone_string ] );
				} catch ( Exception $e2 ) {
					// Mapped timezone also failed - this shouldn't happen with valid mappings
					error_log( sprintf( 
						'SSA: Deprecated timezone "%s" mapped to "%s" but mapping also failed: %s', 
						$timezone_string, 
						$deprecated_timezones[ $timezone_string ],
						$e2->getMessage()
					) );
					// Now Fall through to fallback logic
				}
			}

			// Return fallback or UTC as last resort
			if ( $fallback instanceof DateTimeZone ) {
				return $fallback;
			}
			return new DateTimeZone( 'UTC' );
		}
	}

	public static function get_datetime_in_utc( $datetime, $datetimezone='UTC' ) {
		if ( ! ( $datetimezone instanceof DateTimeZone ) ) {
			$datetimezone = self::safe_timezone( $datetimezone );
		}

		if ( ! ( $datetime instanceof DateTimeImmutable ) ) {
			$datetime = new DateTimeImmutable( $datetime, $datetimezone );
		}

		$datetime = $datetime->setTimezone( new DateTimeZone( 'UTC' ) );
		return $datetime;
	}

	public static function get_period_in_utc( Period $period ) {
		return self::get_period_in_timezone( $period, new DateTimeZone( 'UTC' ) );
	}

	public static function get_period_in_timezone( Period $period, DateTimeZone $timezone ) {
		return new Period(
			$period->getStartDate()->setTimezone( $timezone ),
			$period->getEndDate()->setTimezone( $timezone )
		);
	}

	public function get_datetime_as_local_datetime( $datetime, $appointment_type_id=0, $staff_id = 0, $location_id = 0 ) {
		$local_timezone = $this->get_datetimezone( $appointment_type_id, $staff_id, $location_id );

		if ( empty( $local_timezone ) || ! ( $local_timezone instanceof DateTimeZone ) ) {
			throw new SSA_Exception( __( 'No $local_timezone defined' ), 500 );
		}

		if ( $datetime instanceof DateTime ) {
			$datetime = new DateTimeImmutable( $datetime );
		} elseif ( ! ( $datetime instanceof DateTimeImmutable ) ) {
			$utc_timezone = new DateTimeZone( 'UTC' );
			$datetime = new DateTimeImmutable( $datetime, $utc_timezone );
			$datetime = $datetime->setTimezone( $local_timezone );
		} else {
			$datetime = $datetime->setTimezone( $local_timezone );
		}


		return $datetime;
	}

	public function get_datetimezone( $appointment_type_id = 0, $staff_id = 0, $location_id = 0 ) {
		if ( !empty( $staff_id ) ) {
			throw new SSA_Exception( __( 'Staff members are not supported yet' ), 500 );
		} elseif ( !empty( $location_id ) ) {
			throw new SSA_Exception( __( 'Locations are not supported yet' ), 500 );
		} else {
			$local_timezone = $this->plugin->settings_global->get_datetimezone();
		}

		return $local_timezone;
	}

	/**
	 * Returns datetime format set by the customer on the global settings page.
	 *
	 * @since 4.9.1
	 *
	 * @return string
	 */
	public static function get_localized_date_format_from_settings() {
		$settings = ssa()->settings->get();
		$format   = $settings['global']['date_format'] . ' ' . $settings['global']['time_format'];

		return $format;
	}

	/**
	 * Returns date format set by the customer on the global settings page.
	 *
	 * @since 4.9.1
	 *
	 * @return string
	 */	
	public static function get_localized_date_only_format_from_settings() {
		$settings = ssa()->settings->get();
		$format   = $settings['global']['date_format'];

		return $format;
	}

	/**
	 * Returns time format set by the customer on the global settings page.
	 *
	 * @since 4.9.1
	 *
	 * @return string
	 */
	public static function get_localized_time_only_format_from_settings() {
		$settings = ssa()->settings->get();
		$format   = $settings['global']['time_format'];

		return $format;
	}

	public static function localize_default_date_strings( $php_date_format ) {
		if ( 'F j, Y' === $php_date_format ) {
			$php_date_format = __( 'F j, Y', 'default' );	
		} else if ( 'g:i a' === $php_date_format ) {
			$php_date_format = __( 'g:i a', 'default' );	
		} else if ( 'F j, Y g:i a' === $php_date_format ) {
			$php_date_format = __( 'F j, Y g:i a', 'default' );	
		}

		return $php_date_format;
	}
	
	public static function translate_formatted_date( $formatted_date ) {
		$translations = array();
		
		if( !empty( ssa()->translation->programmatic_locale ) && is_string( ssa()->translation->programmatic_locale ) ){
			$mo_file = WP_LANG_DIR . '/' . ssa()->translation->programmatic_locale . '.mo';
			if( ! file_exists( $mo_file ) ){
				// if the WP core language file doesn't exist, try the plugin language file
				$mo_file = WP_LANG_DIR . '/plugins/simply-schedule-appointments-' . ssa()->translation->programmatic_locale . '.mo';
			}
			if ( file_exists( $mo_file ) ) {
				// Make sure the MO class is available.
				if ( ! class_exists( 'MO' ) ) {
					require_once( ABSPATH . WPINC . '/l10n.php' );
				}

				// Instantiate a new MO object.
				$mo = new MO();
				// Load your specific .mo file.
				// (Replace '/path/to/your/file.mo' with the correct path.)
				if ( $mo->import_from_file( $mo_file ) ) {

					// Now translate the strings using the loaded .mo file.
					$translations = array(
						'January'   => $mo->translate( 'January' ),
						'February'  => $mo->translate( 'February' ),
						'March'     => $mo->translate( 'March' ),
						'April'     => $mo->translate( 'April' ),
						'May'       => $mo->translate( 'May' ),
						'June'      => $mo->translate( 'June' ),
						'July'      => $mo->translate( 'July' ),
						'August'    => $mo->translate( 'August' ),
						'September' => $mo->translate( 'September' ),
						'October'   => $mo->translate( 'October' ),
						'November'  => $mo->translate( 'November' ),
						'December'  => $mo->translate( 'December' ),

						'Monday'    => $mo->translate( 'Monday' ),
						'Tuesday'   => $mo->translate( 'Tuesday' ),
						'Wednesday' => $mo->translate( 'Wednesday' ),
						'Thursday'  => $mo->translate( 'Thursday' ),
						'Friday'    => $mo->translate( 'Friday' ),
						'Saturday'  => $mo->translate( 'Saturday' ),
						'Sunday'    => $mo->translate( 'Sunday' ),
					);
				}
			}
		}
		
		if(empty($translations)){
			$translations = array(
				'January' => __( 'January' ),
				'February' => __( 'February' ),
				'March' => __( 'March' ),
				'April' => __( 'April' ),
				'May' => __( 'May' ),
				'June' => __( 'June' ),
				'July' => __( 'July' ),
				'August' => __( 'August' ),
				'September' => __( 'September' ),
				'October' => __( 'October' ),
				'November' => __( 'November' ),
				'December' => __( 'December' ),
	
				'Monday' => __( 'Monday' ),
				'Tuesday' => __( 'Tuesday' ),
				'Wednesday' => __( 'Wednesday' ),
				'Thursday' => __( 'Thursday' ),
				'Friday' => __( 'Friday' ),
				'Saturday' => __( 'Saturday' ),
				'Sunday' => __( 'Sunday' ),
			);
		}
		
		return str_replace( array_keys( $translations ), array_values( $translations ), $formatted_date );
	   
		// return strtr( ( string ) $formatted_date, $translations );
	}

	public static function php_to_moment_format($php_date_format) {
		$php_date_format = self::localize_default_date_strings( $php_date_format );

	    $replacements = array(
	        '\\h' => '[h]',
	        '\\m\\i\\n' => '[min]',
	        '\\m' => '[m]',
	        '\\' => '',
	        '\\d' => '\\d', // Leave Spanish string 'de' (of) untouched
	        '\\e' => '\\e', // Leave Spanish string 'de' (of) untouched

	        'd' => 'DD',
	        'D' => 'ddd',
	        'j' => 'D',
	        'l' => 'dddd',
	        'N' => 'E',
	        'S' => 'o',
	        'w' => 'e',
	        'z' => 'DDD',
	        'W' => 'W',
	        'F' => 'MMMM',
	        'm' => 'MM',
	        'M' => 'MMM',
	        'n' => 'M',
	        't' => '', // no equivalent
	        'L' => '', // no equivalent
	        'o' => 'YYYY',
	        'Y' => 'YYYY',
	        'y' => 'YY',
	        'a' => 'a',
	        'A' => 'A',
	        'B' => '', // no equivalent
	        'g' => 'h',
	        'G' => 'H',
	        'h' => 'hh',
	        'H' => 'HH',
	        'i' => 'mm',
	        's' => 'ss',
	        'u' => 'SSS',
	        'e' => 'zz', // deprecated since version 1.6.0 of moment.js
	        'I' => '', // no equivalent
	        'O' => '', // no equivalent
	        'P' => '', // no equivalent
	        'T' => '', // no equivalent
	        'Z' => '', // no equivalent
	        'c' => '', // no equivalent
	        'r' => '', // no equivalent
	        'U' => 'X',
	    );

	    $moment_format = strtr($php_date_format, $replacements);
		
		if( isset( $_GET["ssa_locale"] )){
			$moment_format = apply_filters( 'ssa/moment_format', $moment_format, $_GET["ssa_locale"] );
		}

	    return $moment_format;
	}

	public static function moment_to_php_format($moment_date_format) {
	    $replacements = array(
	        'DD' => 'd', 
	        'ddd' => 'D', 
	        'D' => 'j', 
	        'dddd' => 'l', 
	        'E' => 'N', 
	        'o' => 'S', 
	        'e' => 'w', 
	        'DDD' => 'z', 
	        'W' => 'W', 
	        'MMMM' => 'F', 
	        'MM' => 'm', 
	        'MMM' => 'M', 
	        'M' => 'n', 
	        'YYYY' => 'o', 
	        'YYYY' => 'Y', 
	        'YY' => 'y', 
	        'a' => 'a', 
	        'A' => 'A', 
	        'h' => 'g', 
	        'H' => 'G', 
	        'hh' => 'h', 
	        'HH' => 'H', 
	        'mm' => 'i', 
	        'ss' => 's', 
	        'SSS' => 'u', 
	        'zz' => 'e',  // deprecated since version 1.6.0 of moment.js
	        'X' => 'U', 
	    );
	    
	    $php_format = strtr($moment_date_format, $replacements);

	    return $php_format;
	}

	public function get_formatted_date( $date, $appointment_type_id = 0 ){
		$local_date = $this->get_datetime_as_local_datetime( $date, $appointment_type_id );
		$format 		= self::get_localized_date_format_from_settings();
		$format			= self::localize_default_date_strings( $format ) . ' (T)';
		$value 			= $local_date->format( $format );
		$value 			= self::translate_formatted_date( $value );
		return $value;
	}

	/**
	 * Define constant if not already set.
	 *
	 * @param string      $name  Constant name.
	 * @param string|bool $value Constant value.
	 */
	public static function define( $name, $value ) {
		if ( ! defined( $name ) ) {
			define( $name, $value );
		}
	}

	public static function get_appointment_type( $appointment_type ) {
		if ( (int)$appointment_type == $appointment_type ) {
			$appointment_type = ssa()->appointment_type_model->get( $appointment_type );
		}
		if ( empty( $appointment_type['id'] ) ) {
			return false;
		}

		return $appointment_type;
	}

	public static function get_query_period( Period $query_period = null ) {
		if ( null === $query_period ) {
			$query_period = SSA_Constants::EPOCH_PERIOD();
		}

		return $query_period;
	}
}

function ssa_datetime( $time='now' ) {
	return SSA_Utils::datetime( $time );
}

function ssa_gmtstrtotime( $string ) {
	$time = strtotime($string . ' +0000');

	if ( -1 == $time ) {
		return strtotime($string);
	}

	return $time;
}

function ssa_defensive_timezone_fix() {
	ssa()->utils->defensive_timezone_fix();
}
function ssa_defensive_timezone_reset() {
	ssa()->utils->defensive_timezone_reset();
}

function ssa_debug_log( $var, $debug_level = 1, $label = '', $file = 'debug' ) {
	if ( defined( 'SSA_DEBUG_LOG' ) && empty( SSA_DEBUG_LOG ) ) {
		return;
	}
	
	// Store backtrace early for PHP 7.0+ compatibility (before using parameters)
	$backtrace = debug_backtrace();
	
	$ssa_debug_level = get_option( 'ssa_debug_level', 10 );
	if ( $debug_level < $ssa_debug_level ) {
		// We want to log really fatal errors (level 10) so the support team can see them when logging in after the fact
		return;
	}
	
	$path = ssa()->support_status->get_log_file_path( $file );
	$log_prefix = gmdate( '[Y-m-d H:i:s] ' );
	
	if ( empty( $path ) ) {
		return;
	}
	
	// Validate indices before access (PHP 8.0+ compatibility)

	if ( isset( $backtrace[1]['function'] ) ) {
		error_log( PHP_EOL . 'The following is logged from ' . $backtrace[1]['function'] . '()' . PHP_EOL, 3, $path );
	}
	if ( isset( $backtrace[0]['file'], $backtrace[0]['line'] ) ) {
		error_log( 'in ' . $backtrace[0]['file'] . ' line ' . $backtrace[0]['line'] . ':' . PHP_EOL, 3, $path );
	}

	if ( isset( $backtrace[2]['function'] ) ) {
		error_log( PHP_EOL . '... ' . $backtrace[2]['function'] . '()' . PHP_EOL, 3, $path );
	}
	if ( isset( $backtrace[1]['file'], $backtrace[1]['line'] ) ) {
		error_log( 'in ' . $backtrace[1]['file'] . ' line ' . $backtrace[1]['line'] . ':' . PHP_EOL, 3, $path );
	}

	if ( isset( $backtrace[3]['function'] ) ) {
		error_log( PHP_EOL . '...... ' . $backtrace[3]['function'] . '()' . PHP_EOL, 3, $path );
	}
	if ( isset( $backtrace[2]['file'], $backtrace[2]['line'] ) ) {
		error_log( 'in ' . $backtrace[2]['file'] . ' line ' . $backtrace[2]['line'] . ':' . PHP_EOL, 3, $path );
	}

	if ( ! empty( $label ) ) {
		error_log( $log_prefix.$label.PHP_EOL, 3, $path );
	}
	if ( is_string( $var ) ) {
		error_log( $log_prefix.$var.PHP_EOL, 3, $path );
	} else {
		// error_log( $log_prefix.print_r( $var, true ).PHP_EOL, 3, $path );
	}
}

function ssa_int_hash( $string ) {
	return abs( crc32( $string ) );
}

function ssa_get_staff_id( $user_id ) {
	return ssa()->staff_model->get_staff_id_for_user_id( $user_id );
}
function ssa_get_current_staff_id() {
	return ssa_get_staff_id( get_current_user_id() );
}

function ssa_cache_get( $key, $group = '', $force = false, &$found = null ) {
	return SSA_Cache::object_cache_get( $key, $group, $force, $found );
}
function ssa_cache_set( $key, $data, $group = '', $expire = 0 ) {
	return SSA_Cache::object_cache_set( $key, $data, $group, $expire );
}
function ssa_cache_delete( $key, $group = '' ) {
	return SSA_Cache::object_cache_delete( $key, $group );
}


/**
 * SSA custom class_exists function
 *
 * @since   6.0.3
 * 
 * @param string $class
 * @return bool
 */
function ssa_class_exists( $class = '' ) {
	if ( ! class_exists( $class ) ) {
		$var = 'Class "' . $class . '" not found!';
		$debug_level = 10;
		ssa_debug_log($var, $debug_level );
		return false;
	}
	return true;
}

/**
 * SSA custom function_exists function
 *
 * @since   6.0.3
 * 
 * @param string $function
 * @return bool
 */
function ssa_function_exists( $function = '' ) {
	if ( ! function_exists( $function ) ) {
		$var = 'Function "' . $function . '" not found!';
		$debug_level = 10;
		ssa_debug_log($var, $debug_level );
		return false;
	}
	return true;
}

/**
 * SSA custom as_has_scheduled_action that does all the checking needed
 * 
 * @since   6.0.3
 *
 * @param string $hook  Required
 * @return bool|null
 */
function ssa_has_scheduled_action( $hook = '', $args = array() ) {

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

	if ( ! ssa_class_exists( 'ActionScheduler' ) ) {
		return;
	}

	if ( ! ssa_function_exists( 'as_has_scheduled_action' ) ) {
		return;
	}

	try {
		return as_has_scheduled_action( $hook, $args );

	} catch( \Exception $e ) {
		$var = $e->getMessage();
		$debug_level = 10;
		$label = 'Action Scheduler';
		ssa_debug_log( $var, $debug_level, $label );
	}
}

/**
 * SSA custom as_schedule_recurring_action that does all the checking needed
 * 
 * @since   6.0.3
 *
 * @param integer $timestamp            Required
 * @param integer $interval_in_seconds	Required
 * @param string $hook                  Required
 * @param array $args                   Optional
 * @param string $group                 Optional
 * @return integer|null                 The action ID or void
 */
function ssa_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook='', $args = array(), $group = '' ) {

	if ( empty( $timestamp ) ||  empty( $interval_in_seconds ) || empty( $hook ) ) {
		return;
	}

	if ( ! ssa_class_exists( 'ActionScheduler' ) ) {
		return;
	}

	if ( ! ssa_function_exists( 'as_schedule_recurring_action' ) ) {
		return;
	}

	try {
		// return the ID of the scheduled action
		return as_schedule_recurring_action( $timestamp, $interval_in_seconds, $hook, $args, $group );

	} catch( \Exception $e ) {
		$var = $e->getMessage();
		$debug_level = 10;
		$label = 'Action Scheduler';
		ssa_debug_log( $var, $debug_level, $label );
	}
}

/**
 * SSA custom as_unschedule_action that does all the checking needed
 * 
 * @since   6.0.3
 *
 * @param string $hook      Required
 * @param array $args       Optional
 * @param string $group     Optional
 * @return integer|null.    The scheduled action ID if a scheduled action was found, or null if no matching action found.
 */
function ssa_unschedule_action( $hook='', $args = array(), $group = '' ) {

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

	if ( ! ssa_class_exists( 'ActionScheduler' ) ) {
		return;
	}

	if ( ! ssa_function_exists( 'as_unschedule_action' ) ) {
		return;
	}

	try {
		return as_unschedule_action( $hook, $args, $group );

	} catch( \Exception $e ) {
		$var = $e->getMessage();
		$debug_level = 10;
		$label = 'Action Scheduler';
		ssa_debug_log( $var, $debug_level, $label );
	}
}

/**
 * SSA custom as_unschedule_all_actions that does all the checking needed
 * 
 * @since   6.0.3
 *
 * @param string $hook      Required
 * @param array $args       Optional
 * @param string $group     Optional
 */
function ssa_unschedule_all_actions( $hook='', $args = array(), $group = '' ) {

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

	if ( ! ssa_class_exists( 'ActionScheduler' ) ) {
		return;
	}

	if ( ! ssa_function_exists( 'as_unschedule_all_actions' ) ) {
		return;
	}

	try {
		return as_unschedule_all_actions( $hook, $args, $group );

	} catch( \Exception $e ) {
		$var = $e->getMessage();
		$debug_level = 10;
		$label = 'Action Scheduler';
		ssa_debug_log( $var, $debug_level, $label );
	}
}

/**
 * SSA custom as_schedule_single_action that does all the checking needed
 * 
 * @since   6.0.3
 *
 * @param integer $timestamp	Required
 * @param string $hook          Required
 * @param array $args           Optional
 * @param string $group         Optional
 * @return integer|null.        The scheduled action ID if a scheduled action was found, or null if no matching action found.
 */
function ssa_schedule_single_action( $timestamp, $hook='', $args = array(), $group = '' ) {

	if ( empty( $timestamp ) || empty( $hook ) ) {
		return;
	}

	if ( ! ssa_class_exists( 'ActionScheduler' ) ) {
		return;
	}

	if ( ! ssa_function_exists( 'as_schedule_single_action' ) ) {
		return;
	}

	try {
		return as_schedule_single_action( $timestamp, $hook, $args, $group );

	} catch( \Exception $e ) {
		$var = $e->getMessage();
		$debug_level = 10;
		$label = 'Action Scheduler';
		ssa_debug_log( $var, $debug_level, $label );
	}
}

/**
 * SSA queue runner for wp_actionscheduler_actions table 
 *
 * @since   6.4.3
 * 
 * @return integer
 */
function ssa_run_action_scheduler_queue(){

	if ( ! class_exists( 'ActionScheduler' ) ) {
		return;
	}

	try {
		return ActionScheduler::runner()->run();

	} catch( \Exception $e ) {
		$var = $e->getMessage();
		$debug_level = 10;
		$label = 'Action Scheduler';
		ssa_debug_log( $var, $debug_level, $label );
	}

}

/**
 * Get all active plugins
 * Return array of active plugins names, eg. array( 'draw-attention', 'simply-schedule-appointments', 'wp-mail-logging' );
 *
 * @return array
 */
function ssa_get_all_active_plugins(){

	$active_plugins = get_option( 'active_plugins' );
	$output = array();
	
	foreach ($active_plugins as $active_plugin ) {
		if ( strpos( $active_plugin, '/' ) ) {
			$active_plugin = substr( $active_plugin, 0, strpos( $active_plugin, '/' ) );
		}
		$output[] = $active_plugin;
	}
	return $output;
}


function ssa_evaluate_datetime_merge_tag( $start_date, $modifier, $date_timezone ){
	$modifier_string = explode( ':', $modifier, 3);
	// by default format as day of the week
	$formatted_value = ssa_datetime( $start_date )->setTimezone(  $date_timezone )->format( 'l' );

	if( $modifier_string[1] === 'format' ){
		// if a format string is included, format accordingly
		$date_format = str_replace( 'y', 'Y', $modifier_string[2] );
		$date_format = str_replace( 'h', 'H', $date_format );
		$formatted_value = ssa_datetime( $start_date )->setTimezone(  $date_timezone )->format( $date_format );
	}
	
	$formatted_value = SSA_Utils::translate_formatted_date( $formatted_value );
	return $formatted_value;
}

function ssa_evaluate_merge_tag ( $appointment_id, $modifier ) {
	
	try {
		$appointment_obj = new SSA_Appointment_Object( $appointment_id );
		$start_date      = $appointment_obj->start_date;
	} catch ( Exception $e ) {
		// failed to get appointment from appointment_id
		return '';
	}
	// get settings
	$settings = ssa()->settings->get();

	if ( 'business_start_date' === explode( ':', $modifier )[0] ) {
		$date_timezone    = SSA_Utils::safe_timezone( $settings['global']['timezone_string'] );
		return ssa_evaluate_datetime_merge_tag( $start_date, $modifier, $date_timezone );
	}
	
	if ( 'customer_start_date' === explode( ':', $modifier )[0] && count( explode( ':', $modifier ) ) > 1 ) {
		return ssa_evaluate_datetime_merge_tag( $start_date, $modifier, $appointment_obj->get_customer_timezone() );
	}

	if ( 'instructions' === $modifier ) {
		return $appointment_obj->get_appointment_type()->instructions;
	}
	
	if ( 'public_edit_url' === $modifier ) {
		return $appointment_obj->get_public_edit_url();
	}

	if ( 'appointment_type_slug' === $modifier ) {
		$appointment_type = $appointment_obj->get_appointment_type();
		return $appointment_type->get_slug();
	}

	if ( 'appointment_type_title' === $modifier ) {
		$appointment_type = $appointment_obj->get_appointment_type();
		return $appointment_type->get_title();
	}

	if ( 'web_meeting_url' === $modifier ) {
		return $appointment_obj->__get( 'web_meeting_url' );
	}

	if ( 'add_to_calendar_link_ics' === $modifier ) {
		$token = ssa()->appointment_model->get_id_token( $appointment_id );
		return add_query_arg(
			array(
				'token' => $token,
			),
			$appointment_obj->get_ics_download_url( 'customer' )
		);
	}

	if ( 'add_to_calendar_link_gcal' === $modifier ) {
		return $appointment_obj->get_gcal_add_link( 'customer' );
	}

	if ( 'team_member_name' === $modifier ) {
		$members = $appointment_obj->get_staff_members();

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

		// loop through members and get a list of names.
		$names = array();
		foreach ( $members as $member ) {
			$names[] = $member->get_name();
		}

		if ( count( $names ) > 1 ) {
			$last  = array_pop( $names );
			$text  = implode( ', ', $names );
			$text .= ' and ' . $last;

			return $text;
		} else {
			return $names[0];
		}
	}

	if ( 'team_member_email' === $modifier ) {
		$members = $appointment_obj->get_staff_members();

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

		// loop through members and get a list of emails.
		$emails = array();
		foreach ( $members as $member ) {
			$emails[] = $member->get_email();
		}

		if ( count( $emails ) > 1 ) {
			$last  = array_pop( $emails );
			$text  = implode( ', ', $emails );
			$text .= ' and ' . $last;

			return $text;
		} else {
			return $emails[0];
		}
	}

	$ssa = ssa();

	if (
		'start_date_only' === $modifier ||
		'start_time_only' === $modifier
	) {
		$business_start_date = ssa_datetime( $start_date );

		if ( 'start_date_only' === $modifier ) {
			$format = SSA_Utils::get_localized_date_only_format_from_settings();
			$format = SSA_Utils::localize_default_date_strings( $format );
		} else {
			$format = SSA_Utils::get_localized_time_only_format_from_settings();
			$format = SSA_Utils::localize_default_date_strings( $format ) . ' (T)';
		}

		$formatted_value = $ssa->utils->get_datetime_as_local_datetime( $business_start_date, $appointment_id )->format( $format );
		$formatted_value = SSA_Utils::translate_formatted_date( $formatted_value );
		return $formatted_value;
	}

	if (
		'customer_start_date' === $modifier ||
		'customer_start_date_only' === $modifier ||
		'customer_start_time_only' === $modifier
	) {
		if ( 'customer_start_date' === $modifier ) {
			$format = SSA_Utils::get_localized_date_format_from_settings();
			$format = SSA_Utils::localize_default_date_strings( $format ) . ' (T)';
		} elseif ( 'customer_start_date_only' === $modifier ) {
			$format = SSA_Utils::get_localized_date_only_format_from_settings();
			$format = SSA_Utils::localize_default_date_strings( $format );
		} elseif ( 'customer_start_time_only' === $modifier ) {
			$format = SSA_Utils::get_localized_time_only_format_from_settings();
			$format = SSA_Utils::localize_default_date_strings( $format ) . ' (T)';
		}

		$formatted_value = ssa_datetime( $start_date )->setTimezone( $appointment_obj->get_customer_timezone() )->format( $format );
		$formatted_value = SSA_Utils::translate_formatted_date( $formatted_value );
		return $formatted_value;
	}

	$local_start_date = $ssa->utils->get_datetime_as_local_datetime( $start_date );
	$format           = SSA_Utils::get_localized_date_format_from_settings();
	$format           = SSA_Utils::localize_default_date_strings( $format ) . ' (T)';
	$formatted_value  = $local_start_date->format( $format );
	$formatted_value  = SSA_Utils::translate_formatted_date( $formatted_value );
	return $formatted_value;
}

function ssa_get_stack_trace() {
	ob_start();
	debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
	$trace = ob_get_contents();
	ob_end_clean();
	return $trace;
}

function ssa_should_render_booking_flow() {
	return ssa()->settings_installed->is_installed( 'booking_flows' ) ? 1 : 0;
}

/**
 * Unles the notification is sent to a customer, it's categorized as a staff notification
 * 
 * @param array $recipients
 * @return string
 */
function ssa_get_recipient_type_for_recipients_array ( array $recipients ) {
	$recipients_string = implode( ',', $recipients );
	if ( strpos( $recipients_string, 'customer_email' ) === false && strpos( $recipients_string, 'customer_phone' ) === false ){
		return 'staff';
	}
	return  'customer';
}

/**
 * Build current page URL without query parameters
 *
 * @return string
 */
function ssa_get_current_page_url() {
	global $wp;
	return home_url($wp->request);
}

/**
 * Sanitizes the accent color input.
 *
 * @param string $color The input color string.
 * @return string|null The sanitized color or null if the input is invalid.
 */
function ssa_sanitize_color_input($color) {
    $hex_pattern = '/^[a-fA-F0-9]+$/';

    $rgba_pattern = '/^rgba\(\d{1,3},\d{1,3},\d{1,3},(?:0|1|0?\.\d+)\)$/i';

    if (preg_match($hex_pattern, $color) && in_array(strlen($color), [6, 3])) {
        return strtolower($color);
    }

    if (preg_match($rgba_pattern, $color)) {
        return $color;
    }

    return null;
}

// sometimes the request hits the php code even though it's a file request
function ssa_is_json_request() {
	// wp_is_json_request checks for the headers - not helpful here
	// so we check if /wp-json/ is in the request URI
	return strpos( $_SERVER['REQUEST_URI'], '/wp-json/' ) !== false && strpos( $_SERVER['REQUEST_URI'], '/block-renderer/ssa') === false;
}

function ssa_is_file_request() {
    $request_uri = $_SERVER['REQUEST_URI'];
    $file_extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'ico', 'css', 'js', 'woff', 'woff2', 'ttf', 'eot', 'otf', 'svg', 'webp', 'map'];
    $path_info = pathinfo($request_uri);

    if ( isset( $path_info['extension'] ) && in_array( strtolower( $path_info['extension'] ), $file_extensions ) ) {
        return true;
    }
    return false;
}

function ssa_should_skip_async_logic() {
	$should_skip = true;
		
	if( ssa_doing_async() ) {
		$should_skip = false;
	}
	
	// wp cron
	if ( wp_doing_cron() ) {
		$should_skip = false;
	}
	
	return $should_skip;
}
