<?php
/**
 * Meeting Hub Functions
 *
 * @package MHUB
 * @version 1.0.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

if ( ! function_exists( 'mhub_count_upcoming_meetings' ) ) {
	/**
	 * Count upcoming meetings
	 *
	 * @return int
	 */
	function mhub_count_upcoming_meetings() {
		$args = array(
			'post_type'      => 'mhub_meeting',
			'post_status'    => 'publish',
			'posts_per_page' => -1,
			'fields'         => 'ids',
		);

		$query = new \WP_Query( $args );
		$ids   = $query->posts;
		$count = 0;
		$now   = time();

		foreach ( $ids as $id ) {
			$settings = get_post_meta( $id, 'mhub__meeting_settings', true );
			$settings = maybe_unserialize( $settings );
			if ( isset( $settings['startDateTime'] ) ) {
				$start_time = strtotime( $settings['startDateTime'] );
				if ( $start_time > $now ) {
					$count++;
				}
			}
		}

		return $count;
	}
}

if ( ! function_exists( 'mhub_delete_meeting' ) ) {
	/**
	 * Delete Meeting
	 *
	 * @param int $id Delete Id.
	 *
	 * @return int|boolean
	 *
	 * @since 1.0.0
	 */
	function mhub_delete_meeting( $id ) {
		delete_post_meta( $id, 'mhub__meeting_settings', null );
		$deleted = wp_delete_post( $id );
		return $deleted;
	}
}


if ( ! function_exists( 'mhub_meetings' ) ) {
	/**
	 * Fetch jitsi all meetings
	 *
	 * @return array
	 *
	 * @since 1.0.0
	 */
	function mhub_meetings() {

		$user_id = get_current_user_id();

		$args = array(
			'post_type'      => 'mhub_meeting',
			'post_status'    => 'publish',
			'posts_per_page' => -1,
		);

		// If the user does not have the manage_options capability, limit the query to their own meetings.
		if ( ! current_user_can( 'manage_options' ) ) {
			$args['author'] = $user_id;
		}

		$wp_query = new \WP_Query( $args );
		$results  = $wp_query->posts;

		$meetings = array();

		foreach ( $results as $result ) {
			$meeting         = array();
			$settings        = get_post_meta( $result->ID, 'mhub__meeting_settings', true );
			$settings        = maybe_unserialize( $settings );
			$settings['url'] = get_permalink( $result->ID );

			$meeting['id']       = $result->ID;
			$meeting['title']    = $result->post_title;
			$meeting['settings'] = $settings;
			$meeting['date']     = $result->post_date;
			$meetings[]          = $meeting;

		}

		wp_reset_postdata();

		return $meetings;
	}

}


if ( ! function_exists( 'mhub_meeting' ) ) {

	/**
	 * Fetch a single meeting from the DB
	 *
	 * @param int $id Meeting Id.
	 *
	 * @return object
	 *
	 * @since 1.0.0
	 */
	function mhub_meeting( $id ) {
		$meeting = array();

		$post                = get_post( $id );
		$settings            = get_post_meta( $id, 'mhub__meeting_settings', true );
		$meeting_description = get_post_meta( $id, 'meeting_description', true );

		$meeting['id']                  = $post->ID;
		$meeting['title']               = $post->post_title;
		$meeting['settings']            = maybe_unserialize( $settings );
		$meeting['meeting_description'] = $meeting_description;
		$meeting['date']                = $post->post_date;

		return $meeting;
	}
}

if ( ! function_exists( 'mhub_random_domain' ) ) {

	/**
	 * GetRandomDomain
	 *
	 * This function returns a randomly selected domain from a predefined list.
	 *
	 * @return string The randomly selected domain.
	 */
	function mhub_random_domain() {
		static $shuffled_domains = null;

		if ( null === $shuffled_domains ) {
			$domains = array(
				'webconf.viviers-fibre.net',
				'jitsi.member.fsf.org',
				'meet.evolix.org',
				'video.devloprog.org',
			);

			shuffle( $domains );
			$shuffled_domains = $domains;
		}

		$random_index = wp_rand( 0, count( $shuffled_domains ) - 1 );
		return $shuffled_domains[ $random_index ];
	}
}

if ( ! function_exists( 'mhub_generate_random_room' ) ) {

	/**
	 * GenerateRandomString
	 *
	 * This function generates a random string of a specified length using alphanumeric characters.
	 *
	 * @param int $length The length of the random string to generate.
	 * @return string The randomly generated string.
	 */
	function mhub_generate_random_room( $length ) {
		$characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
		$result     = '';

		for ( $i = 0; $i < $length; $i++ ) {
			$random_index = wp_rand( 0, strlen( $characters ) - 1 );
			$result      .= $characters[ $random_index ];
		}

		return $result;
	}
}

if ( ! function_exists( 'mhub_prepare_zoom_meeting_data' ) ) {
	/**
	 * Prepare data for creating a Zoom meeting.
	 *
	 * This function prepares data required for creating a Zoom meeting based on the provided parameters.
	 *
	 * @param string $title The title of the meeting.
	 * @param array  $meeting_settings The settings for the meeting, including start date, agenda, timezone, etc.
	 * @param int    $duration The duration of the meeting in minutes.
	 *
	 * @return array The prepared meeting data including topic, agenda, start time, timezone, password, duration, and settings.
	 */
	function mhub_prepare_zoom_meeting_data( $title, $meeting_settings, $duration ) {
		$start_time           = $meeting_settings['startDateTime'];
		$alternative_host_ids = '';

		// Create a DateTime object from the given string.
		$datetime = new DateTime( $start_time );

		// Set the time zone to UTC.
		$datetime->setTimezone( new DateTimeZone( 'UTC' ) );

		// Format the date and time to ISO 8601 format in UTC.
		$utc_time = $datetime->format( 'Y-m-d\TH:i:s\Z' );

		if ( ! empty( $meeting_settings['alternative_host'] ) ) {
			$alternative_host_arr = array();

			foreach ( $meeting_settings['alternative_host'] as $meeting_host ) {
				$alternative_host_arr[] = $meeting_host->value;
			}

			if ( count( $alternative_host_arr ) > 1 ) {
				$alternative_host_ids = implode( ',', $alternative_host_arr );
			} else {
				$alternative_host_ids = $alternative_host_arr[0];
			}
		}

		$meeting_arr = array(
			'topic'      => $title,
			'agenda'     => ! empty( $meeting_settings['agenda'] ) ? $meeting_settings['agenda'] : '',
			'start_time' => $utc_time,
			'timezone'   => $meeting_settings['meeting_timezone'],
			'password'   => ! empty( $meeting_settings['password'] ) ? $meeting_settings['password'] : false,
			'duration'   => $duration,
			'settings'   => array(
				'meeting_authentication' => ! empty( $meeting_settings['meeting_authentication'] ) ? true : false,
				'join_before_host'       => ! empty( $meeting_settings['join_before_host'] ) ? true : false,
				'host_video'             => ! empty( $meeting_settings['option_host_video'] ) ? true : false,
				'participant_video'      => ! empty( $meeting_settings['option_participants_video'] ) ? true : false,
				'mute_upon_entry'        => ! empty( $meeting_settings['option_mute_participants'] ) ? true : false,
				'auto_recording'         => ! empty( $meeting_settings['auto_recording'] ) ? $meeting_settings['auto_recording'] : 'none',
				'alternative_hosts'      => isset( $alternative_host_ids ) ? $alternative_host_ids : '',
				'waiting_room'           => isset( $meeting_settings['disable_waiting_room'] ) && ( 'yes' === $meeting_settings['disable_waiting_room'] ) ? false : true,
			),
		);

		return $meeting_arr;
	}
}

if ( ! function_exists( 'mhub_prepare_webinar' ) ) {
	/**
	 * Prepare data for creating a Zoom webinar.
	 *
	 * This function prepares data required for creating a Zoom webinar based on the provided parameters.
	 *
	 * @param string $title The title of the webinar.
	 * @param array  $meeting_settings The settings for the webinar, including start date, agenda, timezone, etc.
	 * @param int    $duration The duration of the webinar in minutes.
	 *
	 * @return array The prepared webinar data including topic, agenda, start time, timezone, password, duration, and settings.
	 */
	function mhub_prepare_webinar( $title, $meeting_settings, $duration ) {
		$start_time           = $meeting_settings['startDateTime'];
		$alternative_host_ids = '';

		// Create a DateTime object from the given string.
		$datetime = new DateTime( $start_time );

		// Set the time zone to UTC.
		$datetime->setTimezone( new DateTimeZone( 'UTC' ) );

		// Format the date and time to ISO 8601 format in UTC.
		$utc_time = $datetime->format( 'Y-m-d\TH:i:s\Z' );

		if ( ! empty( $meeting_settings['alternative_host'] ) ) {
			$alternative_host_arr = array();

			foreach ( $meeting_settings['alternative_host'] as $meeting_host ) {
				$alternative_host_arr[] = $meeting_host->value;
			}

			if ( count( $alternative_host_arr ) > 1 ) {
				$alternative_host_ids = implode( ',', $alternative_host_arr );
			} else {
				$alternative_host_ids = $alternative_host_arr[0];
			}
		}

		$webinar_arrr = array(
			'topic'      => $title,
			'agenda'     => ! empty( $meeting_settings['agenda'] ) ? $meeting_settings['agenda'] : '',
			'start_time' => $utc_time,
			'timezone'   => $meeting_settings['meeting_timezone'],
			'password'   => ! empty( $meeting_settings['password'] ) ? $meeting_settings['password'] : false,
			'duration'   => $duration,
			'settings'   => array(
				'host_video'             => ! empty( $meeting_settings['option_host_video'] ) ? true : false,
				'panelists_video'        => ! empty( $meeting_settings['panelists_video'] ) ? true : false,
				'practice_session'       => ! empty( $meeting_settings['practice_session'] ) ? true : false,
				'hd_video'               => ! empty( $meeting_settings['hd_video'] ) ? true : false,
				'allow_multiple_devices' => ! empty( $meeting_settings['allow_multiple_devices'] ) ? true : false,
				'alternative_hosts'      => isset( $alternative_host_ids ) ? $alternative_host_ids : '',
				'auto_recording'         => ! empty( $meeting_settings['auto_recording'] ) ? $meeting_settings['auto_recording'] : 'none',
			),
		);

		return $webinar_arrr;
	}
}


if ( ! function_exists( 'mhub_is_server_auth_prepare' ) ) {
	/**
	 * Check if the server authentication for Zoom is prepared by verifying the presence
	 * of OAuth account ID, OAuth client ID, and OAuth client secret in the Zoom settings.
	 *
	 * @return bool Whether the server authentication is prepared or not.
	 */
	function mhub_is_server_auth_prepare() {
		$status = false;

		// Get Zoom settings from options.
		$zoom_settings = get_option( 'mhub_zoom_settings' );

		if ( is_array( $zoom_settings ) && ! empty( $zoom_settings ) ) {
			// Extract API key and API secret from settings.
			$account_id    = isset( $zoom_settings['oauth_account_id'] ) ? $zoom_settings['oauth_account_id'] : '';
			$client_id     = isset( $zoom_settings['oauth_client_id'] ) ? $zoom_settings['oauth_client_id'] : '';
			$client_secret = isset( $zoom_settings['oauth_client_secret'] ) ? $zoom_settings['oauth_client_secret'] : '';

			// Check if API key is not empty and API secret is empty.
			if ( ! empty( $account_id ) && ! empty( $client_id ) && ! empty( $client_secret ) ) {
				update_option( 'mhub_is_server_auth_prepare', 'yes' );
				$status = true;
			} else {
				update_option( 'mhub_is_server_auth_prepare', 'no' );
			}
		}

		return $status;
	}

}

if ( ! function_exists( 'mhub_is_zoom_oauth_connected' ) ) {
	/**
	 * Check if Zoom OAuth is fully connected (has token).
	 *
	 * @return bool
	 */
	function mhub_is_zoom_oauth_connected() {
		$oauth_data = get_option( 'mhub_zoom_global_oauth_data' );
		return ! empty( $oauth_data ) && mhub_is_server_auth_prepare();
	}
}

if ( ! function_exists( 'mhub_is_sdk_prepare' ) ) {
	/**
	 * Check if the Zoom SDK is prepared by verifying the presence of SDK client ID and secret.
	 *
	 * @return bool Whether the Zoom SDK is prepared or not.
	 */
	function mhub_is_sdk_prepare() {
		$status = false;

		// Get Zoom settings from options.
		$zoom_settings = get_option( 'mhub_zoom_settings', true );

		if ( is_array( $zoom_settings ) && ! empty( $zoom_settings ) ) {
			// Extract API key and API secret from settings.
			$api_key    = isset( $zoom_settings['sdk_client_id'] ) ? $zoom_settings['sdk_client_id'] : '';
			$api_secret = isset( $zoom_settings['sdk_client_secret'] ) ? $zoom_settings['sdk_client_secret'] : '';

			// Check if API key and API secret are not empty.
			if ( ! empty( $api_key ) && ! empty( $api_secret ) ) {
				$status = true;
				update_option( 'mhub_is_sdk_prepare', 'yes' );
			} else {
				$status = false;
				update_option( 'mhub_is_sdk_prepare', 'no' );
			}
		}
		return $status;
	}
}

if ( ! function_exists( 'mhub_webex_auth_prepare' ) ) {
	/**
	 * Prepare Webex authentication.
	 *
	 * This function retrieves Webex settings and checks if the client ID and client secret are present.
	 *
	 * @return bool True if both are present, false otherwise.
	 */
	function mhub_webex_auth_prepare() {
		$status         = false;
		$webex_settings = get_option( 'mhub_webex_settings', array() );

		if ( is_array( $webex_settings ) && ! empty( $webex_settings ) ) {
			$client_id     = isset( $webex_settings['client_id'] ) ? $webex_settings['client_id'] : '';
			$client_secret = isset( $webex_settings['client_secret'] ) ? $webex_settings['client_secret'] : '';

			if ( ! empty( $client_id ) && ! empty( $client_secret ) ) {
				update_option( 'mhub_webex_auth_prepare', 'yes' );
				$status = true;
			} else {
				update_option( 'mhub_webex_auth_prepare', 'no' );
			}
		}

		return $status;
	}
}

if ( ! function_exists( 'mhub_is_webex_connected' ) ) {
	/**
	 * Check if Webex is fully connected.
	 *
	 * @return bool
	 */
	function mhub_is_webex_connected() {
		$current_user_id    = get_current_user_id();
		$webex_access_token = get_user_meta( $current_user_id, 'mhub_webex_access_token', true );
		return ! empty( $webex_access_token ) && mhub_webex_auth_prepare();
	}
}

if ( ! function_exists( 'mhub_msteams_auth_prepare' ) ) {
	/**
	 * Prepare Microsoft Teams authentication.
	 *
	 * This function retrieves Microsoft Teams settings and checks if the client ID and client secret are present.
	 *
	 * @return bool True if both are present, false otherwise.
	 */
	function mhub_msteams_auth_prepare() {
		$status           = false;
		$msteams_settings = get_option( 'mhub_msteams_settings', array() );

		if ( is_array( $msteams_settings ) && ! empty( $msteams_settings ) ) {
			$client_id     = isset( $msteams_settings['client_id'] ) ? $msteams_settings['client_id'] : '';
			$client_secret = isset( $msteams_settings['client_secret'] ) ? $msteams_settings['client_secret'] : '';

			if ( ! empty( $client_id ) && ! empty( $client_secret ) ) {
				update_option( 'mhub_msteams_auth_prepare', 'yes' );
				$status = true;
			} else {
				update_option( 'mhub_msteams_auth_prepare', 'no' );
			}
		}

		return $status;
	}
}

if ( ! function_exists( 'mhub_is_msteams_connected' ) ) {
	/**
	 * Check if MS Teams is fully connected.
	 *
	 * @return bool
	 */
	function mhub_is_msteams_connected() {
		$current_user_id    = get_current_user_id();
		$user_token         = get_user_meta( $current_user_id, 'mhub_msteams_access_token', true );
		$global_token       = get_option( 'mhub_msteams_global_oauth_data' );
		$msteams_oauth_data = ! empty( $user_token['access_token'] ) ? $user_token : $global_token;

		return ! empty( $msteams_oauth_data ) && mhub_msteams_auth_prepare();
	}
}

if ( ! function_exists( 'mhub_is_google_meet_prepare' ) ) {
	/**
	 * Check if Google Meet credentials (json) are loaded.
	 *
	 * @return bool
	 */
	function mhub_is_google_meet_prepare() {
		$file_path = trailingslashit( wp_upload_dir()['basedir'] ) . 'mhub-json/credential.json';
		return file_exists( $file_path );
	}
}

if ( ! function_exists( 'mhub_is_google_meet_connected' ) ) {
	/**
	 * Check if Google Meet is connected.
	 *
	 * @return bool
	 */
	function mhub_is_google_meet_connected() {
		return 'connected' === get_option( 'mhub_google_meet_account' ) && mhub_is_google_meet_prepare();
	}
}

if ( ! function_exists( 'mhub_timezone_offset' ) ) {
	/**
	 * Retrieve the timezone offset in hours and minutes.
	 *
	 * This function fetches the timezone offset from WordPress options. If the offset is empty,
	 * it defaults to 'UTC'. The offset is returned in the format specified by WordPress options.
	 *
	 * @return string The timezone offset in hours and minutes.
	 */
	function mhub_timezone_offset() {
		$fetched_offset = get_option( 'timezone_string' );
		$offset         = empty( $fetched_offset ) ? 'UTC' : $fetched_offset;
		return $offset;
	}

}

if ( ! function_exists( 'mhub_convert_to_minutes' ) ) {
	/**
	 * Convert hours and minutes to minutes.
	 *
	 * This function takes the given hour and minute values and converts them into minutes.
	 * It multiplies the hour value by 60 and adds the minute value to get the total minutes.
	 *
	 * @param int $hour The hour value to be converted.
	 * @param int $minute The minute value to be converted.
	 * @return int The total minutes calculated from the provided hour and minute values.
	 */
	function mhub_convert_to_minutes( $hour, $minute ) {
		$hour   = $hour * 60;
		$result = $hour + $minute;
		return $result;
	}
}

if ( ! function_exists( 'mhub_pro_plugin_exists' ) ) {
	/**
	 * Checks if the MeetingHub Pro plugin is installed.
	 *
	 * @return bool True if MeetingHub Pro plugin exists, false otherwise.
	 */
	function mhub_pro_plugin_exists() {
		return file_exists( WP_PLUGIN_DIR . '/meetinghub-pro/meetinghub-pro.php' );
	}
}


if ( ! function_exists( 'mhub_insert_meeting' ) ) {
	/**
	 * Inserts a new meeting post into the database.
	 *
	 * @param string $title              The title of the meeting.
	 * @param array  $meeting_settings   The settings for the meeting.
	 * @param string $meeting_description The description of the meeting.
	 *
	 * @return int|WP_Error The ID of the inserted meeting post on success, or a WP_Error object on failure.
	 */
	function mhub_insert_meeting( $title, $meeting_settings, $meeting_description ) {
		$un_meeting_settings       = maybe_unserialize( $meeting_settings );
		$meeting_description_array = json_decode( $meeting_description, true );
		$meeting_description       = isset( $meeting_description_array['content'] ) ? wp_kses_post( $meeting_description_array['content'] ) : '';

		$new_meeting = array(
			'post_title'   => $title,
			'post_type'    => 'mhub_meeting',
			'post_status'  => 'publish',
			'post_content' => '',
			'meta_input'   => array(
				'mhub__meeting_settings' => $meeting_settings,
				'meeting_description'    => $meeting_description,
			),

		);

		$meeting_id = wp_insert_post( $new_meeting );

		$thumbnail_id = $un_meeting_settings['image_id'];

		// Set post thumbnail.
		if ( ! empty( $thumbnail_id ) ) {
			set_post_thumbnail( $meeting_id, $thumbnail_id );
		}

		if ( is_wp_error( $meeting_id ) ) {
			$meeting_id->add_data( array( 'status' => 400 ) );
			return $meeting_id;
		}

		return $meeting_id;
	}

}

if ( ! function_exists( 'mhub_update_meeting' ) ) {
	/**
	 * Updates an existing meeting post in the database.
	 *
	 * @param string $title              The new title of the meeting.
	 * @param array  $meeting_settings   The new settings for the meeting.
	 * @param int    $meeting_id         The ID of the meeting post to update.
	 * @param string $meeting_description The new description of the meeting.
	 *
	 * @return int|WP_Error The number of affected rows on success, or a WP_Error object on failure.
	 */
	function mhub_update_meeting( $title, $meeting_settings, $meeting_id, $meeting_description ) {
		$un_meeting_settings       = maybe_unserialize( $meeting_settings );
		$meeting_description_array = json_decode( $meeting_description, true );
		$meeting_description       = isset( $meeting_description_array['content'] ) ? wp_kses_post( $meeting_description_array['content'] ) : '';

		$arg = array(
			'ID'           => $meeting_id,
			'post_title'   => $title,
			'post_type'    => 'mhub_meeting',
			'post_status'  => 'publish',
			'post_content' => '',
			'meta_input'   => array(
				'mhub__meeting_settings' => $meeting_settings,
				'meeting_description'    => $meeting_description,
			),
		);

		$updated = wp_update_post( $arg );

		$thumbnail_id = isset( $un_meeting_settings['image_id'] ) ? $un_meeting_settings['image_id'] : '';

		// Check if the image ID is empty and remove the thumbnail if it is.
		if ( empty( $thumbnail_id ) ) {
			delete_post_thumbnail( $meeting_id );
		} else {
			// Set the post thumbnail if the image ID is not empty.
			set_post_thumbnail( $meeting_id, $thumbnail_id );
		}

		if ( ! $updated ) {
			return new WP_Error(
				'rest_not_updated',
				__( 'Sorry, the address could not be updated.', 'meetinghub' ),
				array( 'status' => 400 )
			);
		}
	}
}

if ( ! function_exists( 'mhub_prepare_webex_meeting_data' ) ) {
	/**
	 * Prepares meeting data for Webex API (Free Version).
	 *
	 * @param string $title The title of the meeting.
	 * @param array  $meeting_settings Meeting settings including start time, agenda, password, and more.
	 * @param int    $duration The duration of the meeting in minutes.
	 *
	 * @return array Formatted meeting data for Webex API.
	 */
	function mhub_prepare_webex_meeting_data( $title, $meeting_settings, $duration ) {
		$gmt_array      = mhub_get_gmt_offset( $meeting_settings['meeting_timezone'] );
		$gmt_offset_val = $gmt_array['gmt_offset_val'];

		$start_timestamp  = strtotime( $meeting_settings['startDateTime'] ) + ( $gmt_offset_val );
		$start_time       = gmdate( 'Y-m-d H:i:s', $start_timestamp );
		$duration_seconds = $duration * 60;

		// Calculate the end time by adding the duration to the start timestamp.
		$end_timestamp = $start_timestamp + $duration_seconds;
		$end_time      = gmdate( 'Y-m-d H:i:s', $end_timestamp );

		$meeting_arr = array(
			'title'                    => $title,
			'agenda'                   => isset( $meeting_settings['agenda'] ) ? $meeting_settings['agenda'] : null,
			'password'                 => isset( $meeting_settings['password'] ) ? $meeting_settings['password'] : null,
			'start'                    => $start_time,
			'end'                      => $end_time,
			'timezone'                 => isset( $meeting_settings['meeting_timezone'] ) ? $meeting_settings['meeting_timezone'] : null,
			'enabledAutoRecordMeeting' => isset( $meeting_settings['auto_record'] ) ? (bool) $meeting_settings['auto_record'] : false,
			'enabledJoinBeforeHost'    => isset( $meeting_settings['join_before_host'] ) ? (bool) $meeting_settings['join_before_host'] : false,
		);

		// Display time in user timezone (Webex field expects boolean).
		if ( isset( $meeting_settings['display_time_user_zone'] ) ) {
			$meeting_arr['displayTimeInUserTimeZone'] = (bool) $meeting_settings['display_time_user_zone'];
		}

		// Optional: top-level sendEmail toggle.
		if ( isset( $meeting_settings['send_host_email'] ) ) {
			$meeting_arr['sendEmail'] = (bool) $meeting_settings['send_host_email'];
		}

		// Map meeting_type to Webex scheduledType - basic meeting types only in free version.
		$meeting_type   = isset( $meeting_settings['meeting_type'] ) ? $meeting_settings['meeting_type'] : 'meeting';
		$scheduled_type = 'meeting';
		if ( 'personalRoomMeeting' === $meeting_type ) {
			$scheduled_type = 'personalRoomMeeting';
		}
		$meeting_arr['scheduledType'] = $scheduled_type;

		// Personal room meetings do not support auto-record; force disable to avoid API errors.
		if ( 'personalRoomMeeting' === $scheduled_type ) {
			$meeting_arr['enabledAutoRecordMeeting'] = false;
		}

		if ( ! $meeting_arr['enabledJoinBeforeHost'] ) {
			$meeting_arr['enableConnectAudioBeforeHost'] = false;
		}

		// Join Before Host minutes and audio connection when JBH enabled.
		if ( ! empty( $meeting_settings['join_before_host'] ) ) {
			if ( isset( $meeting_settings['join_before_host_minutes'] ) ) {
				$meeting_arr['joinBeforeHostMinutes'] = (int) $meeting_settings['join_before_host_minutes'];
			}
			if ( isset( $meeting_settings['enable_connect_audio_before_host'] ) ) {
				$meeting_arr['enableConnectAudioBeforeHost'] = (bool) $meeting_settings['enable_connect_audio_before_host'];
			}
		}

		return $meeting_arr;
	}
}

if ( ! function_exists( 'mhub_get_gmt_offset' ) ) {
	/**
	 * Get the GMT offset and its value for a given timezone.
	 *
	 * @param string $timezone The timezone identifier, e.g., 'Asia/Dhaka'.
	 *
	 * @return array An array containing the GMT offset string and its value.
	 */
	function mhub_get_gmt_offset( $timezone ) {
		$dtz               = new DateTimeZone( $timezone );
		$offset_in_seconds = $dtz->getOffset( new DateTime() );
		$hours             = floor( abs( $offset_in_seconds ) / 3600 );
		$minutes           = floor( ( abs( $offset_in_seconds ) % 3600 ) / 60 );
		$gmt_offset        = ( $offset_in_seconds < 0 ? '-' : '+' ) . sprintf( '%02d:%02d', $hours, $minutes );

		list( $hours, $minutes ) = explode( ':', $gmt_offset );
		$gmt_offset_val          = ( $hours * 3600 ) + ( $minutes * 60 );

		return array(
			'gmt_offset'     => $gmt_offset,
			'gmt_offset_val' => $gmt_offset_val,
		);
	}
}

if ( ! function_exists( 'mhub_prepare_google_meet_data' ) ) {
	/**
	 * Prepares meeting data for Webex API.
	 *
	 * @param string $title The title of the meeting.
	 * @param array  $meeting_settings Meeting settings including start time, agenda, password, and more.
	 * @param int    $duration The duration of the meeting in minutes.
	 *
	 * @return array Formatted meeting data for Webex API.
	 */
	function mhub_prepare_google_meet_data( $title, $meeting_settings, $duration ) {
		$gmt_array      = mhub_get_gmt_offset( $meeting_settings['meeting_timezone'] );
		$gmt_offset_val = $gmt_array['gmt_offset_val'];

		// Calculate timestamps.
		$start_timestamp    = strtotime( $meeting_settings['startDateTime'] ) + $gmt_offset_val;
		$iso_start_datetime = gmdate( 'Y-m-d\TH:i:sP', $start_timestamp );
		$duration_seconds   = $duration * 60;
		$end_timestamp      = $start_timestamp + $duration_seconds;
		$iso_end_datetime   = gmdate( 'Y-m-d\TH:i:sP', $end_timestamp );

		// Prepare meeting array.
		$meeting_arr = array(
			'summary'        => $title,
			'description'    => $meeting_settings['meeting_summary'],
			'start'          => array(
				'dateTime' => $iso_start_datetime,
				'timeZone' => $meeting_settings['meeting_timezone'],
			),
			'end'            => array(
				'dateTime' => $iso_end_datetime,
				'timeZone' => $meeting_settings['meeting_timezone'],
			),
			'timezone'       => $meeting_settings['meeting_timezone'],
			'attendees'      => isset( $meeting_settings['attendees'] ) ? $meeting_settings['attendees'] : array(),
			'reminders'      => array(
				'useDefault' => false,
				'overrides'  => array(
					array(
						'method'  => 'email',
						'minutes' => isset( $meeting_settings['reminder_time'] ) ? $meeting_settings['reminder_time'] : 30,
					),
					array(
						'method'  => 'popup',
						'minutes' => isset( $meeting_settings['reminder_time'] ) ? $meeting_settings['reminder_time'] : 30,
					),
				),
			),
			'sendUpdates'    => isset( $meeting_settings['send_updates'] ) ? $meeting_settings['send_updates'] : 'all',
			'transparency'   => isset( $meeting_settings['transparency'] ) ? $meeting_settings['transparency'] : 'transparent',
			'visibility'     => isset( $meeting_settings['event_visibility'] ) ? $meeting_settings['event_visibility'] : 'public',
			'status'         => isset( $meeting_settings['event_status'] ) ? $meeting_settings['event_status'] : 'confirmed',
			'conferenceData' => array(
				'createRequest' => array(
					'requestId' => 'meet_demo_' . microtime( true ),
				),
			),
		);

		return $meeting_arr;
	}

	/**
	 * Prepare Microsoft Teams meeting data for API request.
	 *
	 * @param string $title           The title of the meeting.
	 * @param array  $meeting_settings The meeting settings array.
	 * @param int    $duration        The duration of the meeting in minutes.
	 *
	 * @return array Formatted meeting data for Microsoft Teams API.
	 */
	function mhub_prepare_msteams_meeting_data( $title, $meeting_settings, $duration ) {
		// Use DateTime to correctly parse startDateTime (which includes timezone offset),
		// then convert to UTC. This avoids the double-offset bug where strtotime() already
		// accounts for the embedded offset and then gmt_offset_val would shift it again.
		$start_datetime_obj = new DateTime( $meeting_settings['startDateTime'] );
		$start_datetime_obj->setTimezone( new DateTimeZone( 'UTC' ) );
		$iso_start_datetime = $start_datetime_obj->format( 'Y-m-d\\TH:i:s\\Z' );
		$duration_seconds   = $duration * 60;
		$end_datetime_obj   = clone $start_datetime_obj;
		$end_datetime_obj->modify( '+' . $duration_seconds . ' seconds' );
		$iso_end_datetime   = $end_datetime_obj->format( 'Y-m-d\\TH:i:s\\Z' );

		// Prepare meeting array for Microsoft Teams (simplified for testing).
		$meeting_arr = array(
			'subject'       => $title,
			'startDateTime' => $iso_start_datetime,
			'endDateTime'   => $iso_end_datetime,
			'agenda'        => isset( $meeting_settings['agenda'] ) ? $meeting_settings['agenda'] : '',
			'meeting_timezone' => isset( $meeting_settings['meeting_timezone'] ) ? $meeting_settings['meeting_timezone'] : 'UTC',
		);

		return $meeting_arr;
	}

}


/**
 * Get meetings by mode with visibility filtering.
 *
 * @param string $mode              Mode for filtering meetings ('user', 'role', 'all').
 * @param int    $page_id           Page ID to get saved visibility settings from.
 * @param array  $roles             Array of roles to filter by.
 * @param array  $users             Array of user IDs to filter by.
 * @param bool   $use_passed_values Whether to use passed values instead of saved meta.
 *
 * @return array Array of meeting data.
 */
function mhub_meetings_by_mode( $mode = 'user', $page_id = 0, $roles = array(), $users = array(), $use_passed_values = false ) {
	$args = array(
		'post_type'      => 'mhub_meeting',
		'post_status'    => 'publish',
		'posts_per_page' => -1,
	);

	if ( 'role' === $mode ) {
		// Use passed roles if use_passed_values is true, otherwise get saved roles.
		if ( ! $use_passed_values && empty( $roles ) && $page_id ) {
			$roles = get_post_meta( $page_id, '_mhub_visibility_roles', true );
			if ( ! $roles ) {
				$roles = array();
			}
		}

		if ( ! empty( $roles ) ) {
			$role_users = get_users(
				array(
					'role__in' => $roles,
					'fields'   => array( 'ID' ),
				)
			);

			$user_ids = array_map(
				function ( $user ) {
					return $user->ID;
				},
				$role_users
			);

			if ( ! empty( $user_ids ) ) {
				$args['author__in'] = $user_ids;
			} else {
				// No users found with these roles, return empty.
				$args['author__in'] = array( 0 ); // This will return no posts.
			}
		} else {
			// No roles selected, return empty.
			$args['author__in'] = array( 0 );
		}
	} elseif ( 'user' === $mode ) {
		// Use passed users if use_passed_values is true, otherwise get saved users.
		if ( ! $use_passed_values && empty( $users ) && $page_id ) {
			$users = get_post_meta( $page_id, '_mhub_visibility_users', true );
			if ( ! $users ) {
				$users = array();
			}
		}

		if ( ! empty( $users ) ) {
			$args['author__in'] = array_map( 'absint', $users );
		} else {
			// No users selected, return empty.
			$args['author__in'] = array( 0 );
		}
	}

	$wp_query = new WP_Query( $args );
	$meetings = array();

	foreach ( $wp_query->posts as $post ) {
		$settings = maybe_unserialize( get_post_meta( $post->ID, 'mhub__meeting_settings', true ) );

		// Ensure settings is an array.
		if ( ! is_array( $settings ) ) {
			$settings = array();
		}

		$settings['url'] = get_permalink( $post->ID );

		// Expose the meeting end/resume status so the frontend can filter by availability.
		$settings['mhub_meeting_status'] = get_post_meta( $post->ID, 'mhub_meeting_status', true );

		// For Microsoft Teams: Get join link and calendar link
		$join_link = '';
		$view_in_calendar_link = '';

		if ( isset( $settings['selected_platform'] ) && 'msteams' === $settings['selected_platform'] ) {
			$join_link = get_post_meta( $post->ID, 'mhub_msteams_join_url', true );
			
			if ( empty( $join_link ) ) {
				// Try to get webLink from saved response if join link is missing (e.g. personal account)
				$saved_details = get_post_meta( $post->ID, 'mhub_msteams_details', true );
				if ( ! empty( $saved_details ) && isset( $saved_details['webLink'] ) ) {
					$view_in_calendar_link = $saved_details['webLink'];
				}
			}
		}

		$meetings[] = array(
			'id'       => $post->ID,
			'title'    => $post->post_title,
			'date'     => $post->post_date,
			'settings' => $settings,
			'join_link' => $join_link,
			'view_in_calendar_link' => $view_in_calendar_link,
		);
	}

	wp_reset_postdata();
	return $meetings;
}

if ( ! function_exists( 'mhub_store_zoom_users' ) ) {
	/**
	 * Fetches and stores Zoom users using the provided Zoom API class instance.
	 *
	 * @param object $zoom_api Zoom API class instance with a `list_users` method.
	 *
	 * @return void
	 */
	function mhub_store_zoom_users( $zoom_api ) {
		$page      = 1;
		$user_data = $zoom_api->list_users( $page );
		$user_data = json_decode( $user_data, true );

		if ( ! empty( $user_data ) && isset( $user_data['users'] ) && is_array( $user_data['users'] ) && count( $user_data['users'] ) > 0 ) {
			$users_data = array();

			foreach ( $user_data['users'] as $user ) {
				$users_data[] = array(
					'id'           => $user['id'],
					'email'        => $user['email'],
					'display_name' => $user['display_name'],
				);
			}

			update_option( 'mhub_zoom_users', $users_data );
		} else {
			update_option( 'mhub_zoom_users', array() );
		}
	}
}

if ( ! function_exists( 'mhub_parse_webex_recurrence_data' ) ) {
	/**
	 * Parse Webex recurring meeting data from API response and meeting settings.
	 *
	 * @param array $webex_response The Webex API response containing meeting data.
	 * @param array $meeting_settings The meeting settings from WordPress.
	 *
	 * @return array Parsed recurrence data structure.
	 */
	function mhub_parse_webex_recurrence_data( $webex_response, $meeting_settings ) {
		// Initialize default non-recurring structure.
		$recurrence_data = array(
			'is_recurring'     => false,
			'pattern'          => null,
			'series_start'     => null,
			'series_end'       => null,
			'next_occurrence'  => null,
			'duration_minutes' => 0,
			'description'      => '',
		);

		// Check if meeting has recurring settings enabled.
		if ( empty( $meeting_settings['enable_recurring_meeting'] ) ) {
			return $recurrence_data;
		}

		// Check for recurrence data in Webex response or meeting settings.
		$rrule = '';
		if ( ! empty( $webex_response['recurrence'] ) ) {
			$rrule = $webex_response['recurrence'];
		} elseif ( ! empty( $meeting_settings['recurrence'] ) ) {
			$rrule = $meeting_settings['recurrence'];
		}

		if ( empty( $rrule ) ) {
			return $recurrence_data;
		}

		// Parse RRULE string.
		$parsed_rrule = mhub_parse_rrule_string( $rrule );
		if ( empty( $parsed_rrule ) ) {
			return $recurrence_data;
		}

		// Extract meeting start time and duration.
		$start_time = isset( $webex_response['start'] ) ? $webex_response['start'] : $meeting_settings['startDateTime'];
		$end_time   = isset( $webex_response['end'] ) ? $webex_response['end'] : null;
		// Calculate duration.
		$duration_minutes = 0;
		if ( $end_time && $start_time ) {
			$start_timestamp  = strtotime( $start_time );
			$end_timestamp    = strtotime( $end_time );
			$duration_minutes = ( $end_timestamp - $start_timestamp ) / 60;
		} elseif ( ! empty( $meeting_settings['duration_hours'] ) || ! empty( $meeting_settings['duration_minutes'] ) ) {
			$duration_minutes = mhub_convert_to_minutes(
				intval( $meeting_settings['duration_hours'] ),
				intval( $meeting_settings['duration_minutes'] )
			);
		}

		// Build recurrence data structure.
		$recurrence_data = array(
			'is_recurring'     => true,
			'pattern'          => $parsed_rrule,
			'series_start'     => $start_time,
			'series_end'       => mhub_calculate_series_end_date( $start_time, $parsed_rrule ),
			'next_occurrence'  => mhub_calculate_next_occurrence( $start_time, $parsed_rrule ),
			'duration_minutes' => $duration_minutes,
			'description'      => mhub_generate_recurrence_description( $parsed_rrule ),
		);

		return $recurrence_data;
	}
}

if ( ! function_exists( 'mhub_parse_rrule_string' ) ) {
	/**
	 * Parse an RRULE string into structured components.
	 *
	 * @param string $rrule The RRULE string to parse.
	 *
	 * @return array|null Parsed RRULE components or null if invalid.
	 */
	function mhub_parse_rrule_string( $rrule ) {
		if ( empty( $rrule ) || ! is_string( $rrule ) ) {
			return null;
		}

		// Initialize default structure.
		$parsed = array(
			'frequency' => '',
			'interval'  => 1,
			'days'      => array(),
			'monthday'  => null,
			'count'     => null,
			'until'     => null,
		);

		// Split RRULE into components.
		$parts = explode( ';', $rrule );

		foreach ( $parts as $part ) {
			$part = trim( $part );
			if ( empty( $part ) ) {
				continue;
			}

			$key_value = explode( '=', $part, 2 );
			if ( count( $key_value ) !== 2 ) {
				continue;
			}

			$key   = trim( $key_value[0] );
			$value = trim( $key_value[1] );

			switch ( $key ) {
				case 'FREQ':
					$parsed['frequency'] = $value;
					break;
				case 'INTERVAL':
					$parsed['interval'] = intval( $value );
					break;
				case 'BYDAY':
					$parsed['days'] = explode( ',', $value );
					break;
				case 'BYMONTHDAY':
					$parsed['monthday'] = intval( $value );
					break;
				case 'COUNT':
					$parsed['count'] = intval( $value );
					break;
				case 'UNTIL':
					$parsed['until'] = $value;
					break;
				default:
					// Ignore unknown parameters for graceful degradation.
					break;
			}
		}

		// Validate required frequency.
		if ( empty( $parsed['frequency'] ) || ! in_array( $parsed['frequency'], array( 'DAILY', 'WEEKLY', 'MONTHLY' ) ) ) {
			return null;
		}

		return $parsed;
	}
}

if ( ! function_exists( 'mhub_generate_recurrence_description' ) ) {
	/**
	 * Generate human-readable recurrence description from parsed RRULE.
	 *
	 * @param array $parsed_rrule Parsed RRULE components.
	 *
	 * @return string Human-readable description.
	 */
	function mhub_generate_recurrence_description( $parsed_rrule ) {
		if ( empty( $parsed_rrule['frequency'] ) ) {
			return '';
		}

		$frequency = $parsed_rrule['frequency'];
		$interval  = isset( $parsed_rrule['interval'] ) ? $parsed_rrule['interval'] : 1;
		$days      = isset( $parsed_rrule['days'] ) ? $parsed_rrule['days'] : array();

		$description = __( 'Every', 'meetinghub' ) . ' ';

		// Add interval if greater than 1.
		if ( $interval > 1 ) {
			$description .= $interval . ' ';
		}

		// Add frequency description.
		switch ( $frequency ) {
			case 'DAILY':
				if ( ! empty( $days ) ) {
					// Weekdays only.
					$description .= ( $interval > 1 ) ? __( 'weekdays', 'meetinghub' ) : __( 'weekday', 'meetinghub' );
				} else {
					$description .= ( $interval > 1 ) ? __( 'days', 'meetinghub' ) : __( 'day', 'meetinghub' );
				}
				break;
			case 'WEEKLY':
				$description .= ( $interval > 1 ) ? __( 'weeks', 'meetinghub' ) : __( 'week', 'meetinghub' );
				if ( ! empty( $days ) ) {
					$day_names     = mhub_get_day_names();
					$readable_days = array();
					foreach ( $days as $day ) {
						if ( isset( $day_names[ $day ] ) ) {
							$readable_days[] = $day_names[ $day ];
						}
					}
					if ( ! empty( $readable_days ) ) {
						$description .= ' ' . __( 'on', 'meetinghub' ) . ' ' . implode( ', ', $readable_days );
					}
				}
				break;
			case 'MONTHLY':
				$description .= ( $interval > 1 ) ? __( 'months', 'meetinghub' ) : __( 'month', 'meetinghub' );
				if ( isset( $parsed_rrule['monthday'] ) ) {
					$description .= ' ' . __( 'on day', 'meetinghub' ) . ' ' . $parsed_rrule['monthday'];
				}
				break;
			default:
				$description .= strtolower( $frequency );
		}

		return $description;
	}
}

if ( ! function_exists( 'mhub_get_day_names' ) ) {
	/**
	 * Get mapping of day abbreviations to full names.
	 *
	 * @return array Day name mapping.
	 */
	function mhub_get_day_names() {
		return array(
			'SU' => __( 'Sunday', 'meetinghub' ),
			'MO' => __( 'Monday', 'meetinghub' ),
			'TU' => __( 'Tuesday', 'meetinghub' ),
			'WE' => __( 'Wednesday', 'meetinghub' ),
			'TH' => __( 'Thursday', 'meetinghub' ),
			'FR' => __( 'Friday', 'meetinghub' ),
			'SA' => __( 'Saturday', 'meetinghub' ),
		);
	}
}

if ( ! function_exists( 'mhub_calculate_series_end_date' ) ) {
	/**
	 * Calculate the end date of a recurring meeting series.
	 *
	 * @param string $start_time The series start time.
	 * @param array  $parsed_rrule Parsed RRULE components.
	 *
	 * @return string|null Series end date or null if indefinite.
	 */
	function mhub_calculate_series_end_date( $start_time, $parsed_rrule ) {
		if ( empty( $start_time ) || empty( $parsed_rrule ) ) {
			return null;
		}

		// If UNTIL is specified, use it.
		if ( ! empty( $parsed_rrule['until'] ) ) {
			return $parsed_rrule['until'];
		}

		// If COUNT is specified, calculate end date.
		if ( ! empty( $parsed_rrule['count'] ) ) {
			$start_timestamp = strtotime( $start_time );
			$frequency       = $parsed_rrule['frequency'];
			$interval        = isset( $parsed_rrule['interval'] ) ? $parsed_rrule['interval'] : 1;
			$count           = $parsed_rrule['count'];

			// Calculate approximate end date based on frequency.
			switch ( $frequency ) {
				case 'DAILY':
					$end_timestamp = $start_timestamp + ( ( $count - 1 ) * $interval * DAY_IN_SECONDS );
					break;
				case 'WEEKLY':
					$end_timestamp = $start_timestamp + ( ( $count - 1 ) * $interval * WEEK_IN_SECONDS );
					break;
				case 'MONTHLY':
					// Approximate monthly calculation (30 days).
					$end_timestamp = $start_timestamp + ( ( $count - 1 ) * $interval * 30 * DAY_IN_SECONDS );
					break;
				default:
					return null;
			}

			return gmdate( 'Y-m-d\TH:i:s\Z', $end_timestamp );
		}

		// No end date specified (indefinite series).
		return null;
	}
}

if ( ! function_exists( 'mhub_calculate_next_occurrence' ) ) {
	/**
	 * Calculate the next occurrence of a recurring meeting.
	 *
	 * @param string $start_time The series start time.
	 * @param array  $parsed_rrule Parsed RRULE components.
	 *
	 * @return string|null Next occurrence time or null if series ended.
	 */
	function mhub_calculate_next_occurrence( $start_time, $parsed_rrule ) {
		if ( empty( $start_time ) || empty( $parsed_rrule ) ) {
			return null;
		}

		$current_time    = time();
		$start_timestamp = strtotime( $start_time );
		// If start time is in the future, return it.
		if ( $start_timestamp > $current_time ) {
			return $start_time;
		}

		$frequency = $parsed_rrule['frequency'];
		$interval  = isset( $parsed_rrule['interval'] ) ? $parsed_rrule['interval'] : 1;

		// Calculate next occurrence based on frequency.
		$next_timestamp = $start_timestamp;
		$max_iterations = 1000; // Prevent infinite loops.
		$iterations = 0;

		while ( $next_timestamp <= $current_time && $iterations < $max_iterations ) {
			switch ( $frequency ) {
				case 'DAILY':
					$next_timestamp += ( $interval * DAY_IN_SECONDS );
					break;
				case 'WEEKLY':
					$next_timestamp += ( $interval * WEEK_IN_SECONDS );
					break;
				case 'MONTHLY':
					// Add months using date functions for accuracy.
					$date = new DateTime( '@' . $next_timestamp );
					$date->modify( '+' . $interval . ' months' );
					$next_timestamp = $date->getTimestamp();
					break;
				default:
					return null;
			}
			$iterations++;
		}

		// Check if series has ended.
		$series_end = mhub_calculate_series_end_date( $start_time, $parsed_rrule );
		if ( $series_end && $next_timestamp > strtotime( $series_end ) ) {
			return null;
		}

		return gmdate( 'Y-m-d\TH:i:s\Z', $next_timestamp );
	}
}

if ( ! function_exists( 'mhub_get_webex_next_occurrence' ) ) {
	/**
	 * Get next occurrence time and meeting status for Webex recurring meetings.
	 *
	 * @param array  $recurrence_data Parsed recurrence data.
	 * @param int    $current_time Current timestamp.
	 * @param string $timezone User timezone.
	 *
	 * @return array Next occurrence data with status.
	 */
	function mhub_get_webex_next_occurrence( $recurrence_data, $current_time = null, $timezone = 'UTC' ) {
		if ( empty( $recurrence_data ) || ! $recurrence_data['is_recurring'] ) {
			return array(
				'next_time'  => null,
				'status'     => 'single_meeting',
				'is_running' => false,
			);
		}

		if ( null === $current_time ) {
			$current_time = time();
		}

		$next_occurrence  = $recurrence_data['next_occurrence'];
		$duration_minutes = $recurrence_data['duration_minutes'];
		$series_end       = $recurrence_data['series_end'];

		// Check if series has ended.
		if ( $series_end && $current_time > strtotime( $series_end ) ) {
			return array(
				'next_time'  => null,
				'status'     => 'series_completed',
				'is_running' => false,
			);
		}

		// If no next occurrence, series is completed.
		if ( empty( $next_occurrence ) ) {
			return array(
				'next_time'  => null,
				'status'     => 'series_completed',
				'is_running' => false,
			);
		}

		$next_timestamp        = strtotime( $next_occurrence );
		$meeting_end_timestamp = $next_timestamp + ( $duration_minutes * 60 );

		// Check if meeting is currently running.
		if ( $current_time >= $next_timestamp && $current_time <= $meeting_end_timestamp ) {
			return array(
				'next_time'  => $next_occurrence,
				'status'     => 'running',
				'is_running' => true,
			);
		}

		// Check if next occurrence is in the future.
		if ( $next_timestamp > $current_time ) {
			return array(
				'next_time'  => $next_occurrence,
				'status'     => 'scheduled',
				'is_running' => false,
			);
		}

		// Meeting occurrence has passed, calculate next one.
		$updated_recurrence = mhub_calculate_next_occurrence( $recurrence_data['series_start'], $recurrence_data['pattern'] );

		if ( empty( $updated_recurrence ) ) {
			return array(
				'next_time'  => null,
				'status'     => 'series_completed',
				'is_running' => false,
			);
		}

		return array(
			'next_time'  => $updated_recurrence,
			'status'     => 'scheduled',
			'is_running' => false,
		);
	}
}

if ( ! function_exists( 'mhub_format_webex_recurrence_display' ) ) {
	/**
	 * Format recurrence information for template display.
	 *
	 * @param array $recurrence_data Parsed recurrence data.
	 * @param array $next_occurrence Next occurrence data.
	 * @param int   $duration Duration in minutes.
	 *
	 * @return array Formatted display data.
	 */
	function mhub_format_webex_recurrence_display( $recurrence_data, $next_occurrence, $duration ) {
		$display_data = array(
			'is_recurring'  => false,
			'description'   => '',
			'series_start'  => '',
			'series_end'    => '',
			'next_time'     => '',
			'duration_text' => '',
			'status_text'   => '',
		);

		if ( empty( $recurrence_data ) || ! $recurrence_data['is_recurring'] ) {
			return $display_data;
		}

		$display_data['is_recurring'] = true;
		$display_data['description']  = $recurrence_data['description'];

		// Format series dates.
		if ( ! empty( $recurrence_data['series_start'] ) ) {
			$display_data['series_start'] = gmdate( 'd M Y', strtotime( $recurrence_data['series_start'] ) );
		}

		if ( ! empty( $recurrence_data['series_end'] ) ) {
			$display_data['series_end'] = gmdate( 'd M Y', strtotime( $recurrence_data['series_end'] ) );
		}

		// Format next occurrence time.
		if ( ! empty( $next_occurrence['next_time'] ) ) {
			$display_data['next_time'] = gmdate( 'l, F j, Y g:i A', strtotime( $next_occurrence['next_time'] ) );
		}

		// Format duration.
		$display_data['duration_text'] = mhub_format_duration_display( $duration );

		// Format status text.
		$display_data['status_text'] = mhub_get_meeting_status_text( $next_occurrence['status'] );

		return $display_data;
	}
}

if ( ! function_exists( 'mhub_format_datetime_with_timezone' ) ) {
	/**
	 * Format datetime string with timezone conversion.
	 *
	 * @param string $datetime_string The datetime string to format.
	 * @param string $format The desired output format.
	 * @param string $timezone The target timezone.
	 *
	 * @return string Formatted datetime string.
	 */
	function mhub_format_datetime_with_timezone( $datetime_string, $format, $timezone = 'UTC' ) {
		if ( empty( $datetime_string ) ) {
			return '';
		}

		try {
			// Create DateTime object from the input string.
			$datetime = new DateTime( $datetime_string );

			// Convert to target timezone.
			if ( 'UTC' !== $timezone ) {
				$target_timezone = new DateTimeZone( $timezone );
				$datetime->setTimezone( $target_timezone );
			}

			// Format and return.
			return $datetime->format( $format );
		} catch ( Exception $e ) {
			// Fallback to gmdate if timezone conversion fails.
			return gmdate( $format, strtotime( $datetime_string ) );
		}
	}
}

if ( ! function_exists( 'mhub_format_duration_display' ) ) {
	/**
	 * Format meeting duration for display with proper pluralization.
	 *
	 * @param int $duration_minutes Duration in minutes.
	 *
	 * @return string Formatted duration text.
	 */
	function mhub_format_duration_display( $duration_minutes ) {
		if ( empty( $duration_minutes ) || $duration_minutes <= 0 ) {
			return '';
		}

		if ( $duration_minutes >= 60 ) {
			$hours   = floor( $duration_minutes / 60 );
			$minutes = $duration_minutes % 60;

			$hour_text = ( 1 === $hours ) ? __( 'hour', 'meetinghub' ) : __( 'hours', 'meetinghub' );
			$minute_text = ( 1 === $minutes ) ? __( 'minute', 'meetinghub' ) : __( 'minutes', 'meetinghub' );

			$duration_text = number_format_i18n( $hours ) . ' ' . $hour_text;

			if ( $minutes > 0 ) {
				$duration_text .= ' ' . number_format_i18n( $minutes ) . ' ' . $minute_text;
			}

			return $duration_text;
		} else {
			$minute_text = ( 1 === $duration_minutes ) ? __( 'minute', 'meetinghub' ) : __( 'minutes', 'meetinghub' );
			return number_format_i18n( $duration_minutes ) . ' ' . $minute_text;
		}
	}
}

if ( ! function_exists( 'mhub_get_meeting_status_text' ) ) {
	/**
	 * Get human-readable meeting status text.
	 *
	 * @param string $status Meeting status.
	 *
	 * @return string Status text.
	 */
	function mhub_get_meeting_status_text( $status ) {
		switch ( $status ) {
			case 'scheduled':
				return __( 'Meeting is scheduled', 'meetinghub' );
			case 'running':
				return __( 'Meeting is running', 'meetinghub' );
			case 'ended':
				return __( 'Meeting has ended', 'meetinghub' );
			case 'series_completed':
				return __( 'Meeting series completed', 'meetinghub' );
			default:
				return __( 'Meeting status unknown', 'meetinghub' );
		}
	}
}

if ( ! function_exists( 'mhub_validate_webex_rrule' ) ) {
	/**
	 * Validate and fix RRULE for Webex API compatibility.
	 *
	 * @param string $rrule The RRULE string to validate.
	 *
	 * @return string|null Valid RRULE or null if invalid.
	 */
	function mhub_validate_webex_rrule( $rrule ) {
		if ( empty( $rrule ) || ! is_string( $rrule ) ) {
			return null;
		}

		// Parse the RRULE to understand its components.
		$parsed = mhub_parse_rrule_string( $rrule );
		if ( empty( $parsed ) ) {
			return null;
		}

		$frequency = $parsed['frequency'];
		$interval  = isset( $parsed['interval'] ) ? $parsed['interval'] : 1;

		// Handle different frequency types.
		switch ( $frequency ) {
			case 'DAILY':
				// Daily recurrence is usually well supported.
				$rrule_parts = array( "FREQ=DAILY", "INTERVAL={$interval}" );

				if ( ! empty( $parsed['count'] ) ) {
					$rrule_parts[] = "COUNT={$parsed['count']}";
				} elseif ( ! empty( $parsed['until'] ) ) {
					$rrule_parts[] = "UNTIL={$parsed['until']}";
				}

				return implode( ';', $rrule_parts );

			case 'WEEKLY':
				// Weekly recurrence with specific days.
				$rrule_parts = array( "FREQ=WEEKLY", "INTERVAL={$interval}" );

				if ( ! empty( $parsed['days'] ) ) {
					$days = implode( ',', $parsed['days'] );
					$rrule_parts[] = "BYDAY={$days}";
				}

				if ( ! empty( $parsed['count'] ) ) {
					$rrule_parts[] = "COUNT={$parsed['count']}";
				} elseif ( ! empty( $parsed['until'] ) ) {
					$rrule_parts[] = "UNTIL={$parsed['until']}";
				}

				return implode( ';', $rrule_parts );

			case 'MONTHLY':
				// Monthly recurrence - this is where most Webex API issues occur.
				// We need to be very careful with monthly patterns.
				$rrule_parts = array( "FREQ=MONTHLY", "INTERVAL={$interval}" );

				// Check if this is a complex monthly pattern that might cause issues.
				if ( ! empty( $parsed['days'] ) ) {
					// This is a BYDAY pattern (e.g., first Wednesday of month).
					// Some Webex APIs don't handle complex BYDAY patterns well.
					// Let's try to simplify or provide a fallback.

					// Check if it's a simple day pattern (like "WE" for Wednesday).
					$days            = $parsed['days'];
					$simple_days     = array();
					$complex_pattern = false;

					foreach ( $days as $day ) {
						// Check if it contains numbers (like "1WE" for first Wednesday).
						if ( preg_match( '/\d/', $day ) ) {
							$complex_pattern = true;
							break;
						}
						$simple_days[] = $day;
					}

					if ( $complex_pattern ) {
						// Complex pattern detected. For Webex compatibility,
						// we'll fall back to a simpler monthly pattern.
						// Use BYMONTHDAY=1 as a safe fallback.
						$rrule_parts[] = "BYMONTHDAY=1";
					} else {
						// Simple BYDAY pattern, should be safe.
						$days_str      = implode( ',', $simple_days );
						$rrule_parts[] = "BYDAY={$days_str}";
					}
				} elseif ( ! empty( $parsed['monthday'] ) ) {
					// BYMONTHDAY pattern - this is usually safe.
					$rrule_parts[] = "BYMONTHDAY={$parsed['monthday']}";
				} else {
					// No specific day specified, use a safe default.
					$rrule_parts[] = "BYMONTHDAY=1";
				}

				if ( ! empty( $parsed['count'] ) ) {
					$rrule_parts[] = "COUNT={$parsed['count']}";
				} elseif ( ! empty( $parsed['until'] ) ) {
					$rrule_parts[] = "UNTIL={$parsed['until']}";
				}

				return implode( ';', $rrule_parts );

			default:
				// Unsupported frequency.
				return null;
		}
	}
}

if ( ! function_exists( 'mhub_is_jitsi_connected' ) ) {
	/**
	 * Check if Jitsi is connected.
	 *
	 * @return bool
	 */
	function mhub_is_jitsi_connected() {
		$mhub_jitsi_settings_serialized = get_option( 'mhub_jitsi_settings' );
		$mhub_jitsi_settings            = maybe_unserialize( $mhub_jitsi_settings_serialized );

		if ( ! empty( $mhub_jitsi_settings ) ) {
			$domain_type = isset( $mhub_jitsi_settings['domain_type'] ) ? $mhub_jitsi_settings['domain_type'] : '';

			if ( 'jitsi_random_public' === $domain_type ) {
				return true;
			} elseif ( 'jitsi_self_hosted' === $domain_type ) {
				$custom_domain = isset( $mhub_jitsi_settings['custom_domain'] ) ? $mhub_jitsi_settings['custom_domain'] : '';
				if ( ! empty( $custom_domain ) ) {
					return true;
				}
			} elseif ( 'jitsi_jass_premium' === $domain_type ) {
				$jwt_token = get_transient( 'mhub_jitsi_jwt_token' );
				if ( ! empty( $jwt_token ) ) {
					return true;
				}
			}
		}
		return false;
	}
}

if ( ! function_exists( 'mhub_get_active_integrations_count' ) ) {
	/**
	 * Get the total count of active integrations.
	 *
	 * @return int
	 */
	function mhub_get_active_integrations_count() {
		$count = 0;

		// 1. Zoom (OAuth or SDK).
		if ( mhub_is_zoom_oauth_connected() || mhub_is_sdk_prepare() ) {
			$count++;
		}

		// 2. Google Meet.
		if ( mhub_is_google_meet_connected() ) {
			$count++;
		}

		// 3. Webex.
		if ( mhub_is_webex_connected() ) {
			$count++;
		}

		// 4. MS Teams.
		if ( mhub_is_msteams_connected() ) {
			$count++;
		}

		// 5. Jitsi.
		if ( mhub_is_jitsi_connected() ) {
			$count++;
		}

		return $count;
	}
}

if ( ! function_exists( 'mhub_get_dashboard_detailed_stats' ) ) {
	/**
	 * Get detailed stats for the dashboard.
	 *
	 * @return array
	 */
	function mhub_get_dashboard_detailed_stats() {
		$stats = array(
			'total_meetings'    => 0,
			'upcoming_meetings' => 0,
			'woo_stats'         => array(
				'active' => false,
				'count'  => 0,
			),
			'platform_stats'    => array(
				'zoom'        => 0,
				'google_meet' => 0,
				'webex'       => 0,
				'msteams'     => 0,
				'jitsi_meet'  => 0,
			),
			'pro_modules_count' => 0,
		);

		// 1. Total and Upcoming.
		$stats['total_meetings']    = (int) wp_count_posts( 'mhub_meeting' )->publish;
		$stats['upcoming_meetings'] = function_exists( 'mhub_count_upcoming_meetings' ) ? mhub_count_upcoming_meetings() : 0;

		// 2. WooCommerce Stats.
		if ( class_exists( 'WooCommerce' ) ) {
			$stats['woo_stats']['active'] = true;
			$args = array(
				'post_type'      => 'mhub_meeting',
				'post_status'    => 'publish',
				'posts_per_page' => -1,
				'meta_query'     => array(
					'relation' => 'OR',
					array(
						'key'     => 'mhub_connect_as_product',
						'value'   => 'yes',
						'compare' => '=',
					),
					array(
						'key'     => '_product_id',
						'compare' => 'EXISTS',
					),
				),
			);
			$query = new \WP_Query( $args );
			$stats['woo_stats']['count'] = $query->found_posts;
		}

		// 3. Platform breakdown.
		$args = array(
			'post_type'      => 'mhub_meeting',
			'post_status'    => 'publish',
			'posts_per_page' => -1,
			'fields'         => 'ids',
		);
		$query = new \WP_Query( $args );
		$ids   = (array) $query->posts;

		foreach ( $ids as $id ) {
			$settings = get_post_meta( $id, 'mhub__meeting_settings', true );
			$settings = maybe_unserialize( $settings );
			$platform = isset( $settings['selected_platform'] ) ? $settings['selected_platform'] : '';
			if ( array_key_exists( $platform, $stats['platform_stats'] ) ) {
				$stats['platform_stats'][ $platform ]++;
			}
		}

		// 4. Pro Modules Count.
		if ( function_exists( 'mhub_pro_plugin_exists' ) && mhub_pro_plugin_exists() ) {
			$integrations_dir = WP_PLUGIN_DIR . '/meetinghub-pro/includes/Integrations';
			if ( is_dir( $integrations_dir ) ) {
				$files = scandir( $integrations_dir );
				$count = 0;
				foreach ( $files as $file ) {
					if ( substr( $file, -4 ) === '.php' ) {
						$count++;
					}
				}
				$stats['pro_modules_count'] = $count;
			}
		} else {
			// If pro not active, keep a default available list count.
			$stats['pro_modules_count'] = 12;
		}

		return $stats;
	}
}
