<?php
/**
 * If this file is called directly, abort.
 *
 * @package vidplex
 */

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

/**
 * Generate CSS for border, outline, radius, padding, or margin based on attributes.
 *
 * @param string $type       Type of CSS property (border, outline, radius, padding, margin).
 * @param array  $attributes Associative array of relevant CSS attributes.
 * @return string CSS string.
 */
function vidplex_render_trbl( $type, $attributes ) {
	$sides = array( 'top', 'right', 'bottom', 'left' );

	// Helper function to generate CSS properties conditionally.
	$generate_property = function ( $prop, $side ) use ( $attributes ) {
		return isset( $attributes[ $side ] ) ? "$prop-$side: {$attributes[$side]};" : '';
	};

	switch ( $type ) {
		case 'border':
			if ( ! empty( $attributes['width'] ) || ! empty( $attributes['style'] ) || ! empty( $attributes['color'] ) ) {
				return ( ! empty( $attributes['width'] ) ? "border-width: {$attributes['width']};" : '' )
					. ( ! empty( $attributes['style'] ) ? "border-style: {$attributes['style']};" : '' )
					. ( ! empty( $attributes['color'] ) ? "border-color: {$attributes['color']};" : '' );
			} elseif ( ! empty( $attributes['top'] ) || ! empty( $attributes['right'] ) || ! empty( $attributes['bottom'] ) || ! empty( $attributes['left'] ) ) {
				$css = '';
				foreach ( $sides as $side ) {
					if ( ! empty( $attributes[ $side ]['width'] ) && ! empty( $attributes[ $side ]['style'] ) && ! empty( $attributes[ $side ]['color'] ) ) {
						$css .= "border-$side: {$attributes[$side]['width']} {$attributes[$side]['style']} {$attributes[$side]['color']};\n";
					}
				}
				return $css;
			}
			return '';

		case 'outline':
			if ( ! empty( $attributes['width'] ) || ! empty( $attributes['style'] ) || ! empty( $attributes['color'] ) ) {
				return ( ! empty( $attributes['width'] ) ? "outline-width: {$attributes['width']};" : '' )
					. ( ! empty( $attributes['style'] ) ? "outline-style: {$attributes['style']};" : '' )
					. ( ! empty( $attributes['color'] ) ? "outline-color: {$attributes['color']};" : '' );
			}
			break;

		case 'radius':
			return ( ! empty( $attributes['topL'] ) ? "border-top-left-radius: {$attributes['topL']};" : '' )
				. ( ! empty( $attributes['topR'] ) ? "border-top-right-radius: {$attributes['topR']};" : '' )
				. ( ! empty( $attributes['bottomR'] ) ? "border-bottom-right-radius: {$attributes['bottomR']};" : '' )
				. ( ! empty( $attributes['bottomL'] ) ? "border-bottom-left-radius: {$attributes['bottomL']};" : '' );

		case 'padding':
			$css = '';
			foreach ( $sides as $side ) {
				$css .= $generate_property( 'padding', $side );
			}
			return $css;

		case 'margin':
			$css = '';
			foreach ( $sides as $side ) {
				$css .= $generate_property( 'margin', $side );
			}
			return $css;

		default:
			return '';
	}
}

/**
 * Handles logic for determining or enforcing premium access restrictions.
 *
 * @return bool True if user has premium access, false otherwise.
 */
function vidplex_premium_access() {
	return vidplex_fs()->is__premium_only()
		&& vidplex_fs()->is_plan( 'pro', true )
		&& vidplex_fs()->can_use_premium_code();
}

/**
 * Formats a number into a human-readable abbreviated form.
 *
 * Converts large numbers into a shortened format using suffixes
 * like K (thousand), M (million), B (billion), etc.
 *
 * @param float|int $number   The number to format.
 * @param int       $decimals Number of decimal places to include. Default 1.
 * @return string Formatted number with appropriate abbreviation.
 */
function vidplex_format_number( $number, $decimals = 1 ) {
	if ( $number >= 1000000000000 ) {
		return number_format( $number / 1000000000000, $decimals, '.', ',' ) . 'T';
	} elseif ( $number >= 1000000000 ) {
		return number_format( $number / 1000000000, $decimals, '.', ',' ) . 'B';
	} elseif ( $number >= 1000000 ) {
		return number_format( $number / 1000000, $decimals, '.', ',' ) . 'M';
	} elseif ( $number >= 1000 ) {
		return number_format( $number / 1000, $decimals, '.', ',' ) . 'K';
	} else {
		return number_format( $number, 0, '.', ',' );
	}
}

/**
 * Get time since a given upload date.
 *
 * @param string  $upload_date Date in string format (e.g., "2025-09-16 12:00:00").
 * @param boolean $is_timestamp if the passed paramter is already a timestamp.
 *
 * @return string
 */
function vidplex_format_time_since_upload( $upload_date, $is_timestamp = false ) {
	$seconds = $upload_date;

	if ( ! $is_timestamp ) {
		$now     = new DateTime();
		$past    = new DateTime( $upload_date );
		$seconds = $now->getTimestamp() - $past->getTimestamp();
	}

	$intervals = array(
		'year'   => 31536000,
		'month'  => 2592000,
		'week'   => 604800,
		'day'    => 86400,
		'hour'   => 3600,
		'minute' => 60,
	);

	foreach ( $intervals as $label => $seconds_interval ) {
		$value = floor( $seconds / $seconds_interval );
		if ( $value >= 1 ) {
			return $value . ' ' . $label . ( $value > 1 ? 's' : '' ) . ' ago';
		}
	}

	return 'Just Now';
}

/**
 * AJAX callback to store the Google API key.
 *
 * This function handles an authenticated AJAX request from the plugin
 * settings page to save the API key securely in the WordPress options table.
 *
 * @since 1.0.0
 *
 * @return void
 */
function vidplex_api_key_callback() {
	if ( ! current_user_can( 'manage_options' ) ) {
		wp_send_json_error( 'Permission denied.' );
	}

	check_ajax_referer( 'vidplex-api-key', 'nonce', true );

	$api_key = isset( $_POST['key'] ) && ! empty( $_POST['key'] ) ? wp_unslash( $_POST['key'] ) : '';

	update_option( 'vidplex-api-key', $api_key );

	wp_send_json_success(
		array(
			'message' => 'API key stored successfully!',
		)
	);
}
add_action( 'wp_ajax_vidplex_set_api_key', 'vidplex_api_key_callback' );

/**
 * Callback function for rendering plugin settings.
 *
 * Used as a settings field callback to display input fields,
 * descriptions, or other UI elements on the plugin settings page.
 *
 * @return void Outputs the settings field HTML directly.
 */
function vidplex_settings_callback() {
	if ( ! current_user_can( 'manage_options' ) ) {
		wp_send_json_error( 'Permission denied.' );
	}

	check_ajax_referer( 'vidplex-settings', 'nonce', true );

	$settings = isset( $_POST['settings'] ) && ! empty( $_POST['settings'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['settings'] ) ) ) : array();

	if ( empty( $settings ) ) {
		update_option(
			'vidplex-settings',
			array(
				'video_refresh_interval' => 'fifteen_minutes',
				'videos_per_fetch'       => 5,
				'max_videos_per_channel' => 50,

				'long_shorts_as_video'   => false,

				'optimize_db_storage'    => true,
				'cascade_delete_videos'  => true,
				'cleanup_multiple_data'  => true,

				'api_quota_protection'   => true,
				'log_api_errors'         => true,
			)
		);
		wp_send_json_error( array( 'message' => 'Empty dataset.' ) );
	}

	// Remove the existing schedule to update with the different interval.
	$prev_settings = get_option( 'vidplex-settings' );
	if ( $prev_settings->video_refresh_interval !== $settings->video_refresh_interval ) {
		$timestamp = wp_next_scheduled( 'vidplex_yt_sync' );
		wp_unschedule_event( $timestamp, 'vidplex_yt_sync' );

		$intervals = array(
			'fifteen_minutes',
			'hourly',
			'six_hours',
			'daily',
			'weekly',
			'monthly',
		);

		$interval_period = in_array( $settings->video_refresh_interval, $intervals, true ) ? $settings->video_refresh_interval : 'hourly';

		if ( wp_next_scheduled( 'vidplex_yt_sync' ) ) {
			wp_schedule_event( time(), $interval_period, 'vidplex_yt_sync' );
		}
	}

	update_option( 'vidplex-settings', $settings );

	wp_send_json_success(
		array(
			'message' => 'Settings updated.',
		)
	);
}
add_action( 'wp_ajax_vidplex_settings', 'vidplex_settings_callback' );

/**
 * AJAX callback to store the YouTube channel ID.
 *
 * Handles an authenticated AJAX request from the plugin settings
 * screen to save a YouTube channel ID in the WordPress database.
 *
 * @since 1.0.0
 *
 * @return void
 */
function vidplex_channel_id_callback() {
	if ( ! current_user_can( 'manage_options' ) ) {
		wp_send_json_error( array( 'message' => 'Invalid permission' ) );
	}

	check_ajax_referer( 'vidplex-channel_ID', 'nonce', true );

	$channel_param = isset( $_POST['channelParam'] ) && ! empty( $_POST['channelParam'] ) ? sanitize_text_field( wp_unslash( $_POST['channelParam'] ) ) : '';
	$channel_value = isset( $_POST['channelValue'] ) && ! empty( $_POST['channelValue'] ) ? sanitize_text_field( wp_unslash( $_POST['channelValue'] ) ) : '';

	if ( empty( $channel_param ) || empty( $channel_value ) ) {
		wp_send_json_error( array( 'message' => 'Invalid paramters!' ) );
	}

	$api_key = get_option( 'vidplex-api-key' );

	if ( $api_key ) {
		$url = "https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics,brandingSettings,contentDetails&key={$api_key}&{$channel_param}={$channel_value}";

		$response = wp_remote_get( $url );

		if ( is_wp_error( $response ) ) {
			$error_message = $response->get_error_message();
			wp_send_json_error( array( 'message' => 'Something went wrong!' . $error_message ) );
		} else {
			$body = wp_remote_retrieve_body( $response );
			$data = json_decode( $body, true );

			if ( ! isset( $data['items'] ) || empty( $data['items'] ) ) {
				wp_send_json_error( array( 'message' => 'Invalid channel!' ) );
			}

			$channel_data = $data['items'][0];

			$result = \Vidplex\Store::get_instance()->channels->insert( $channel_data );

			if ( false === $result ) {
				// error_log( 'DB Insert FAILED: ' . $wpdb->last_error );
				wp_send_json_error(
					array(
						'message' => 'We couldn’t connect to the channel.',
					)
				);
			} elseif ( 0 === $result ) {
				wp_send_json_error(
					array(
						'message' => 'Channel already exists!',
					)
				);
			}

			$channel                      = array();
			$channel['channel_id']        = $channel_data['id'];
			$channel['channel_name']      = $channel_data['snippet']['title'];
			$channel['channel_handle']    = $channel_data['snippet']['customUrl'];
			$channel['channel_thumbnail'] = $channel_data['snippet']['thumbnails']['high']['url'];
			$channel['statistics']        = $channel_data['statistics'];
			$channel['branding_settings'] = $channel_data['brandingSettings'];

			$response = wp_remote_get(
				$channel_data['snippet']['thumbnails']['high']['url']
			);

			$image_blob = '';
			$mime_type  = '';

			if ( is_wp_error( $response ) ) {
				$image_blob = null;
			} else {
				$image_blob = wp_remote_retrieve_body( $response );
				$mime_type  = wp_remote_retrieve_header( $response, 'content-type' );
			}

			$channel['thumbnail_blob'] = $image_blob;
			$channel['mime_type']      = $mime_type;

			$render = \Vidplex\Admin\Helpers::channel_render( $channel );

			wp_send_json_success(
				array(
					'message' => 'Channel connected!',
					'render'  => $render,
				)
			);
		}
	}
}
add_action( 'wp_ajax_vidplex_set_channel_ID', 'vidplex_channel_id_callback' );

/**
 * AJAX callback to remove a stored YouTube channel and its related data.
 *
 * Handles an authenticated AJAX request from the plugin dashboard
 * to remove the saved YouTube channel information from the database.
 *
 * This callback is typically used when a user disconnects a channel
 * from the plugin settings.
 *
 * @since 1.0.0
 *
 * @return void
 */
function vidplex_channel_remove_callback() {
	if ( ! current_user_can( 'manage_options' ) ) {
		wp_send_json_error( array( 'message' => 'Invalid permission' ) );
	}

	check_ajax_referer( 'vidplex-channel_ID', 'nonce', true );

	$channel_id = isset( $_POST['channelID'] ) ? sanitize_text_field( wp_unslash( $_POST['channelID'] ) ) : '';

	if ( ! empty( $channel_id ) ) {
		\Vidplex\Store::get_instance()->channels->delete( $channel_id );
		wp_send_json_success( array( 'message' => 'Channel removed!' ) );
	}

	wp_send_json_error( array( 'message' => 'Oops! Something went wrong.' ) );
}
add_action( 'wp_ajax_vidplex_remove_channel', 'vidplex_channel_remove_callback' );

/**
 * Callback function to synchronize videos for a single channel.
 *
 * Fetches and updates the video list for a specific channel,
 * typically triggered via an admin action or AJAX request.
 *
 * @return void Performs synchronization and outputs status or messages.
 */
function vidplex_sync_single_channel_videos_callback() {
	check_ajax_referer( 'vidplex-channel-videos-list', 'nonce', true );

	$channel_id = isset( $_POST['channelID'] ) ? sanitize_text_field( wp_unslash( $_POST['channelID'] ) ) : '';

	if ( empty( $channel_id ) ) {
		wp_send_json_error( array( 'message' => 'Invalid paramters!' ) );
	}

	$result = \Vidplex\Store::get_instance()->videos->sync_channel_videos( $channel_id );

	if ( ! $result ) {
		wp_send_json_error(
			array(
				'message' => 'Unable to sync videos.',
			)
		);
	}

	$count = \Vidplex\Store::get_instance()->videos->get_videos_count_by_channel( $channel_id );

	if ( empty( $videos ) ) {
		wp_send_json_success(
			array(
				'message'    => 'Videos synced.',
				'videoCount' => $count,
			)
		);
	}
}
add_action( 'wp_ajax_vidplex_sync_single_channel_videos', 'vidplex_sync_single_channel_videos_callback' );

/**
 * Remove empty CSS properties from a string of CSS.
 *
 * This function scans the provided CSS string and removes any property
 * declarations that have empty values, including:
 *   - Completely empty values (e.g., `color: ;`)
 *   - Empty strings with single or double quotes (e.g., `font-family: '';`, `border: "";`)
 *
 * This is useful before adding inline styles via wp_add_inline_style
 * to ensure no invalid or empty CSS properties are output.
 *
 * @param string $styles The CSS string to clean.
 * @return string The cleaned CSS string with empty properties removed.
 */
function vidplex_clean_empty_css( $styles ) {
	return preg_replace( '/[a-zA-Z-]+\s*:\s*(["\']{0,1})\s*\1\s*;\s*/', '', $styles );
}

/**
 * Retrieve the upsell URL used in the plugin dashboard.
 *
 * Returns the URL for the upsell or upgrade page displayed
 * within the plugin admin interface. This is typically used
 * for upgrade notices, buttons, or promotional links.
 *
 * @since 1.0.0
 *
 * @return string Upsell URL.
 */
function vidplex_upsell_url() {
	// $url = get_admin_url( null, 'admin.php?page=_vidplex-pricing' );
	$url = 'https://untapwp.com/vidplex/#pricing-and-plans';
	return $url;
}

/**
 * Callback function to load custom videos dynamically.
 *
 * Handles fetching and rendering of videos based on custom
 * criteria, often used for AJAX loading or front-end display.
 *
 * @return void Outputs the loaded videos directly.
 */
function vidplex_custom_videos_loader_callback() {
	check_ajax_referer( 'vidplex-videos-loader', 'nonce', true );

	$attributes      = isset( $_POST['attributes'] ) && ! empty( $_POST['attributes'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['attributes'] ) ), true ) : array();
	$offset          = isset( $_POST['offset'] ) && ! empty( $_POST['offset'] ) ? sanitize_text_field( wp_unslash( $_POST['offset'] ) ) : 0;
	$remaining_quota = isset( $_POST['quota'] ) && ! empty( $_POST['quota'] ) ? sanitize_text_field( wp_unslash( $_POST['quota'] ) ) : $attributes['ajax']['total'];

	if ( empty( $attributes ) ) {
		wp_send_json_error(
			array(
				'message' => 'Empty attributes.',
			)
		);
	}

	$attributes  = (array) $attributes;
	$ajax_query  = (array) $attributes['ajax'];
	$all_videos  = (array) $attributes['customVideos'];
	$total_count = count( $all_videos );

	$offset   = max( 0, (int) $offset );
	$to_fetch = (int) $ajax_query['toFetch'];

	$videos = array_slice( $all_videos, $offset, $to_fetch );

	$new_offset = $offset + count( $videos );
	$has_next   = ( $new_offset < $total_count ) && ( $remaining_quota > 1 );

	// decrement by 1 load.
	$remaining_quota = max( 0, $remaining_quota - 1 );

	$type = 'custom';
	if ( 'gallery' === $attributes['layout'] ) {
		$type = 'gallery';
	}

	wp_send_json_success(
		array(
			'render'  => \Vidplex\Helpers::grid_carousel_render( $type, $videos, $attributes ),
			'offset'  => $new_offset,
			'quota'   => $remaining_quota,
			'hasNext' => $has_next,
			'videos'  => $videos,
		)
	);
}
add_action( 'wp_ajax_vidplex_custom_videos_loader', 'vidplex_custom_videos_loader_callback' );

/**
 * Callback function to load videos for a specific channel.
 *
 * Handles fetching and rendering videos associated with a
 * given channel ID, typically used for AJAX requests or
 * dynamic front-end updates.
 *
 * @return void Outputs the channel's videos directly.
 */
function vidplex_channel_videos_loader_callback() {
	check_ajax_referer( 'vidplex-videos-loader', 'nonce', true );

	$attributes      = isset( $_POST['attributes'] ) && ! empty( $_POST['attributes'] ) ? json_decode( sanitize_text_field( wp_unslash( $_POST['attributes'] ) ), true ) : array();
	$offset          = isset( $_POST['offset'] ) && ! empty( $_POST['offset'] ) ? sanitize_text_field( wp_unslash( $_POST['offset'] ) ) : 0;
	$remaining_quota = isset( $_POST['quota'] ) && ! empty( $_POST['quota'] ) ? sanitize_text_field( wp_unslash( $_POST['quota'] ) ) : $attributes['ajax']['total'];

	if ( empty( $attributes ) ) {
		wp_send_json_error(
			array(
				'message' => 'Empty attributes.',
			)
		);
	}

	$attributes  = (array) $attributes;
	$ajax_query  = (array) $attributes['ajax'];
	$all_videos  = (array) \Vidplex\Helpers::video_filters(
		\Vidplex\Store::get_instance()->videos->get_videos_by_channel( esc_attr( $attributes['channelVideos']['channelId'] ) ),
		array(
			'order'   => esc_attr( $attributes['channelVideos']['order'] ),
			'orderBy' => esc_attr( $attributes['channelVideos']['orderBy'] ),
			'exclude' => $attributes['channelVideos']['exclude'],
		)
	);
	$total_count = count( $all_videos );

	$offset   = max( 0, (int) $offset );
	$to_fetch = (int) $ajax_query['toFetch'];

	$videos = array_slice( $all_videos, $offset, $to_fetch );

	$new_offset = $offset + count( $videos );
	$has_next   = ( $new_offset < $total_count ) && ( $remaining_quota > 1 );

	// decrement by 1 load.
	$remaining_quota = max( 0, $remaining_quota - 1 );

	$type = 'channel';
	if ( 'gallery' === $attributes['layout'] ) {
		$type = 'gallery';
	}

	wp_send_json_success(
		array(
			'render'  => \Vidplex\Helpers::grid_carousel_render( $type, $videos, $attributes ),
			'offset'  => $new_offset,
			'quota'   => $remaining_quota,
			'hasNext' => $has_next,
			'videos'  => $videos,
		)
	);
}
add_action( 'wp_ajax_vidplex_channel_videos_loader', 'vidplex_channel_videos_loader_callback' );

/**
 * Creates a shortened excerpt from YouTube video text.
 *
 * Trims the given text to a specified maximum number of words
 * and appends an ellipsis if the text exceeds the limit.
 *
 * @param string $text       The full text to shorten.
 * @param int    $max_length Maximum number of words for the excerpt. Default 20.
 * @return string Shortened excerpt with a maximum of $max_length words.
 */
function vidplex_create_youtube_excerpt( $text, $max_length = 20 ) {
	if ( ! $text ) {
		return '';
	}

	// Strip HTML.
	$text = html_entity_decode( wp_strip_all_tags( $text ), ENT_QUOTES | ENT_HTML5, 'UTF-8' );

	// Remove shortcodes [like-this].
	$text = preg_replace( '/\[.*?\]/u', '', $text );

	// Remove URLs.
	$text = preg_replace( '/https?:\/\/\S+/iu', '', $text );

	// Remove emojis.
	$text = preg_replace(
		'/[\x{1F100}-\x{1F1FF}'
		. '\x{1F300}-\x{1F5FF}'
		. '\x{1F600}-\x{1F64F}'
		. '\x{1F680}-\x{1F6FF}'
		. '\x{1F700}-\x{1F77F}'
		. '\x{1F780}-\x{1F7FF}'
		. '\x{1F800}-\x{1F8FF}'
		. '\x{1F900}-\x{1F9FF}'
		. '\x{1FA00}-\x{1FAFF}'
		. '\x{2600}-\x{26FF}'
		. '\x{2700}-\x{27BF}]/u',
		'',
		$text
	);

	// Normalize whitespace (new lines, tabs, multiple spaces).
	$text = preg_replace( '/\s+/u', ' ', trim( $text ) );

	// Split words (multibyte safe).
	$words = preg_split( '/\s+/u', $text );

	// Truncate.
	if ( count( $words ) > $max_length ) {
		return implode( ' ', array_slice( $words, 0, $max_length ) ) . '...';
	}

	return $text;
}

function vidplex_refresh_channel_videos_callback() {
	check_ajax_referer( 'vidplex-refresh', 'nonce', true );

	$sync = \Vidplex\Store::get_instance()->refresh();

	if ( ! $sync ) {
		wp_send_json_error(
			array(
				'message' => 'Something went wrong during the sync.',
			)
		);
	}

	wp_send_json_success(
		array(
			'message' => 'Channels synced.',
		)
	);
}
add_action( 'wp_ajax_vidplex_refresh_channel_videos', 'vidplex_refresh_channel_videos_callback' );
