<?php
/**
 * Plugin Name: VidVix
 * Plugin URI: https://theappmill.com/
 * Description: Sell time-limited access to local or remote video files with PayPal and protected same-site playback.
 * Version: 1.1.1
 * Author: Lise Rasmussen
 * License: GPL-2.0-or-later
 * Text Domain: vidvix
 */

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

define('VIDVIX_ACCESS_COOKIE', 'vidvix_access_token');
define('VIDVIX_REST_NAMESPACE', 'vidvix/v1');
define('VIDVIX_SETTINGS_OPTION', 'vidvix_settings');
define('VIDVIX_VERSION', '1.1.1');

register_activation_hook(__FILE__, 'vidvix_activate_plugin');

add_action('admin_init', 'vidvix_register_settings');
add_action('admin_menu', 'vidvix_register_admin_menu');
add_action('current_screen', 'vidvix_suppress_external_admin_notices', PHP_INT_MAX);
add_action('admin_enqueue_scripts', 'vidvix_enqueue_admin_assets');
add_action('admin_post_vidvix_save_settings', 'vidvix_handle_settings_save');
add_action('admin_post_vidvix_export_rentals_csv', 'vidvix_export_rentals_csv');
add_action('init', 'vidvix_create_access_table');
add_action('wp_enqueue_scripts', 'vidvix_enqueue_assets');
add_action('rest_api_init', 'vidvix_register_rest_routes');
add_shortcode('vidvix', 'vidvix_render_shortcode');

function vidvix_suppress_external_admin_notices($screen) {
	if (!is_object($screen)) {
		return;
	}

	$screen_id = isset($screen->id) ? (string) $screen->id : '';
	$vidvix_screens = array(
		'toplevel_page_vidvix',
		'vidvix_page_vidvix-rentals',
		'vidvix_page_vidvix-settings',
	);

	if (!in_array($screen_id, $vidvix_screens, true)) {
		return;
	}

	foreach (array('admin_notices', 'all_admin_notices', 'network_admin_notices', 'user_admin_notices') as $notice_hook) {
		remove_all_actions($notice_hook);
	}
}

function vidvix_activate_plugin() {
	vidvix_create_access_table();

	if (!get_option(VIDVIX_SETTINGS_OPTION)) {
		add_option(VIDVIX_SETTINGS_OPTION, vidvix_get_default_settings());
	}
}

function vidvix_get_default_settings() {
	return array(
		'paypal_client_id' => '',
		'paypal_secret'    => '',
		'paypal_api_base'  => 'https://api-m.paypal.com',
		'rent_hours'       => 48,
		'currency'         => 'USD',
		'default_price'    => '2.99',
		'catalog_items'    => array(),
		'catalog_text'     => '',
	);
}

function vidvix_get_settings() {
	$settings = get_option(VIDVIX_SETTINGS_OPTION, array());

	return wp_parse_args($settings, vidvix_get_default_settings());
}

function vidvix_register_settings() {
	register_setting(
		'vidvix_settings_group',
		VIDVIX_SETTINGS_OPTION,
		array(
			'type'              => 'array',
			'sanitize_callback' => 'vidvix_sanitize_settings',
			'default'           => vidvix_get_default_settings(),
		)
	);
}

function vidvix_sanitize_settings($input) {
	$defaults = vidvix_get_default_settings();
	$input    = is_array($input) ? $input : array();

	$settings = array();
	$settings['paypal_client_id'] = isset($input['paypal_client_id']) ? sanitize_text_field($input['paypal_client_id']) : $defaults['paypal_client_id'];
	$settings['paypal_secret']    = isset($input['paypal_secret']) ? sanitize_text_field($input['paypal_secret']) : $defaults['paypal_secret'];
	$settings['paypal_api_base']  = isset($input['paypal_api_base']) ? esc_url_raw(trim((string) $input['paypal_api_base'])) : $defaults['paypal_api_base'];
	$settings['rent_hours']       = isset($input['rent_hours']) ? max(1, absint($input['rent_hours'])) : $defaults['rent_hours'];
	$settings['currency']         = isset($input['currency']) ? strtoupper(preg_replace('/[^A-Z]/', '', (string) $input['currency'])) : $defaults['currency'];
	$settings['default_price']    = isset($input['default_price']) ? vidvix_normalize_price($input['default_price'], $defaults['default_price']) : $defaults['default_price'];
	$settings['catalog_items']    = isset($input['catalog_items']) ? vidvix_sanitize_catalog_items($input['catalog_items']) : array();
	$settings['catalog_text']     = isset($input['catalog_text']) ? sanitize_textarea_field($input['catalog_text']) : $defaults['catalog_text'];

	if (empty($settings['paypal_api_base'])) {
		$settings['paypal_api_base'] = $defaults['paypal_api_base'];
	}

	if (empty($settings['currency']) || strlen($settings['currency']) !== 3) {
		$settings['currency'] = $defaults['currency'];
	}

	return $settings;
}

function vidvix_handle_settings_save() {
	if (!current_user_can('manage_options')) {
		wp_die(esc_html__('You do not have permission to manage VidVix settings.', 'vidvix'));
	}

	check_admin_referer('vidvix_save_settings', 'vidvix_save_settings_nonce');

	$raw_settings = isset($_POST[VIDVIX_SETTINGS_OPTION]) ? map_deep(wp_unslash($_POST[VIDVIX_SETTINGS_OPTION]), 'sanitize_text_field') : array();
	$raw_settings = wp_parse_args(is_array($raw_settings) ? $raw_settings : array(), vidvix_get_settings());
	$settings     = vidvix_sanitize_settings($raw_settings);
	$redirect_page = isset($_POST['vidvix_redirect_page']) ? sanitize_key(wp_unslash($_POST['vidvix_redirect_page'])) : 'vidvix';

	if (!in_array($redirect_page, array('vidvix', 'vidvix-settings'), true)) {
		$redirect_page = 'vidvix';
	}

	update_option(VIDVIX_SETTINGS_OPTION, $settings);

	$redirect_args = array(
		'page'              => $redirect_page,
		'settings-updated'  => 'true',
	);

	if ('vidvix' === $redirect_page && isset($_POST['vidvix_shortcode_page'])) {
		$redirect_args['vidvix_shortcode_page'] = max(1, absint(wp_unslash($_POST['vidvix_shortcode_page'])));
	}

	wp_safe_redirect(
		add_query_arg(
			$redirect_args,
			admin_url('admin.php')
		)
	);
	exit;
}

function vidvix_sanitize_catalog_items($items) {
	$sanitized = array();
	$used_ids  = array();

	if (!is_array($items)) {
		return $sanitized;
	}

	foreach (array_values($items) as $index => $item) {
		if (!is_array($item)) {
			continue;
		}

		$video_id = isset($item['video_id']) ? sanitize_key($item['video_id']) : '';
		$title    = isset($item['title']) ? sanitize_text_field($item['title']) : '';
		$file_raw = isset($item['file']) ? trim((string) $item['file']) : '';
		$file     = vidvix_is_remote_video_url($file_raw) ? esc_url_raw($file_raw) : wp_normalize_path($file_raw);

		if ($video_id === '' && $title === '' && $file === '') {
			continue;
		}

		if ($video_id === '') {
			$file_path = $file !== '' ? (string) wp_parse_url($file, PHP_URL_PATH) : '';
			$file_name = $file_path !== '' ? pathinfo(wp_basename($file_path), PATHINFO_FILENAME) : '';
			$video_id = sanitize_key($file_name !== '' ? $file_name : 'video-' . ((int) $index + 1));
		}

		if ($video_id === '') {
			$video_id = 'video-' . ((int) $index + 1);
		}

		$base_video_id = $video_id;
		$suffix        = 2;

		while (isset($used_ids[$video_id])) {
			$video_id = $base_video_id . '-' . $suffix;
			$suffix++;
		}

		$used_ids[$video_id] = true;

		if ($title === '') {
			$title = $video_id;
		}

		$sanitized[] = array(
			'video_id' => $video_id,
			'title'    => $title,
			'file'     => $file,
		);
	}

	return $sanitized;
}

function vidvix_normalize_price($value, $fallback = '2.99') {
	$value = preg_replace('/[^0-9.]/', '', (string) $value);

	if ($value === '' || !is_numeric($value)) {
		return $fallback;
	}

	return number_format((float) $value, 2, '.', '');
}

function vidvix_register_admin_menu() {
    global $submenu;
    
	add_menu_page(
		__('VidVix', 'vidvix'),
		__('VidVix', 'vidvix'),
		'manage_options',
		'vidvix',
		'vidvix_render_admin_page',
		'dashicons-lock',
		58
	);

	add_submenu_page(
		'vidvix',
		__('Shortcodes', 'vidvix'),
		__('Shortcodes', 'vidvix'),
		'manage_options',
		'vidvix',
		'vidvix_render_admin_page'
	);

	add_submenu_page(
		'vidvix',
		__('Rentals', 'vidvix'),
		__('Rentals', 'vidvix'),
		'manage_options',
		'vidvix-rentals',
		'vidvix_render_rentals_page'
	);
	
		add_submenu_page(
		'vidvix',
		__('Settings', 'vidvix'),
		__('Settings', 'vidvix'),
		'manage_options',
		'vidvix-settings',
		'vidvix_render_settings_page'
	);

    $submenu['vidvix'][] = [
        __('Upgrade to PRO', 'vidvix'),
        'manage_options',
        vidvix_pro_url(),
    ];
}

function vidvix_pro_url() {
    return 'https://www.theappmill.com/vidvix/index.html';
}

function vidvix_tooltip_icon($label, $text) {
	if (empty($text)) {
		return '';
	}

	return sprintf(
		'<span class="vidvix-tooltip-container tooltip-container"><span class="vidvix-info-icon info-icon" tabindex="0" aria-label="%1$s">i</span><span class="vidvix-tooltip-text tooltip-text">%2$s</span></span>',
		esc_attr($label),
		esc_html($text)
	);
}

function vidvix_get_logo_url() {
	return plugin_dir_url(__FILE__) . 'assets/img/VIDVIX_logo.png';
}

function vidvix_get_asset_version($relative_path) {
	$path = plugin_dir_path(__FILE__) . ltrim($relative_path, '/');

	return file_exists($path) ? VIDVIX_VERSION . '.' . (string) filemtime($path) : VIDVIX_VERSION;
}

function vidvix_render_admin_header($title, $description) {
	?>
	<div class="vidvix-admin-header">
		<div class="vidvix-admin-header-top">
			<img class="vidvix-admin-logo" src="<?php echo esc_url(vidvix_get_logo_url()); ?>" alt="<?php esc_attr_e('VidVix logo', 'vidvix'); ?>">
			<h1><?php echo esc_html($title); ?></h1>
		</div>
		<p><?php echo esc_html($description); ?></p>
	</div>
	<?php
}

function vidvix_enqueue_admin_assets($hook) {
	wp_enqueue_script(
		'vidvix-admin-menu',
		plugin_dir_url(__FILE__) . 'assets/js/vidvix-admin-menu.js',
		array(),
		vidvix_get_asset_version('assets/js/vidvix-admin-menu.js'),
		true
	);

	if (strpos((string) $hook, 'vidvix') === false) {
		return;
	}

	wp_enqueue_style(
		'vidvix-admin',
		plugin_dir_url(__FILE__) . 'assets/css/vidvix-admin.css',
		array(),
		vidvix_get_asset_version('assets/css/vidvix-admin.css')
	);

	wp_enqueue_script(
		'vidvix-admin',
		plugin_dir_url(__FILE__) . 'assets/js/vidvix-admin.js',
		array(),
		vidvix_get_asset_version('assets/js/vidvix-admin.js'),
		true
	);

}

function vidvix_render_admin_page() {
	if (!current_user_can('manage_options')) {
		return;
	}

	$settings               = vidvix_get_settings();
	$catalog                = vidvix_get_catalog();
	$catalog_items          = array_values($catalog);
	$shortcodes_per_page    = 20;
	$shortcodes_total       = count($catalog_items);
	$shortcodes_total_pages = max(1, (int) ceil($shortcodes_total / $shortcodes_per_page));
	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin pagination value.
	$shortcodes_page        = isset($_GET['vidvix_shortcode_page']) ? max(1, absint($_GET['vidvix_shortcode_page'])) : 1;
	$shortcodes_page        = min($shortcodes_page, $shortcodes_total_pages);
	$shortcodes_offset      = ($shortcodes_page - 1) * $shortcodes_per_page;
	$shortcodes_page_items  = array_slice($catalog_items, $shortcodes_offset, $shortcodes_per_page, true);
	$shortcodes_base_url    = admin_url('admin.php?page=vidvix');
	?>
	<div class="wrap vidvix-admin">
		<?php vidvix_render_admin_header(__('Shortcodes', 'vidvix'), __('Generate shortcodes and manage the saved shortcode list for your rentable videos.', 'vidvix')); ?>

		<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- WordPress settings redirect flag. ?>
		<?php if (isset($_GET['settings-updated']) && 'true' === sanitize_text_field(wp_unslash($_GET['settings-updated']))) : ?>
			<div class="notice notice-success is-dismissible vidvix-own-notice">
				<p><?php esc_html_e('VidVix shortcodes saved.', 'vidvix'); ?></p>
			</div>
		<?php endif; ?>

		<form id="vidvix-settings-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" novalidate>
			<input type="hidden" name="action" value="vidvix_save_settings">
			<input type="hidden" name="vidvix_redirect_page" value="vidvix">
			<input type="hidden" name="vidvix_shortcode_page" value="<?php echo esc_attr((string) $shortcodes_page); ?>">
			<?php wp_nonce_field('vidvix_save_settings', 'vidvix_save_settings_nonce'); ?>

			<div class="vidvix-admin-grid">
				<div class="vidvix-admin-main">
					<section id="vidvix-catalog" class="vidvix-section-card">
						<h2 class="vidvix-section-title">
							<img class="vidvix-section-title-icon" src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'assets/img/icon-video.png'); ?>" alt="" aria-hidden="true">
							<span><?php esc_html_e('Shortcode Generator', 'vidvix'); ?></span>
						</h2>
							<p class="vidvix-section-intro"><?php esc_html_e('Each shortcode defines one rentable video. Use either a local server path or an HTTPS video URL. Local example: /site/wp-content/videos/movie-one.mp4. Remote example: https://example.com/videos/movie-two.mp4. Add one shortcode per video.', 'vidvix'); ?></p>
							<div class="vidvix-catalog-list" data-vidvix-catalog-body>
								<div class="vidvix-catalog-row">
									<div class="vidvix-catalog-fields">
										<label>
											<span class="vidvix-catalog-field-label">
												<?php esc_html_e('Video ID', 'vidvix'); ?>
												<?php echo vidvix_tooltip_icon(__('Video ID', 'vidvix'), __('Use a short unique ID for this rental. It becomes the video_id value in the shortcode.', 'vidvix')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
											</span>
											<input type="text" class="vidvix-video-id-input" name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[catalog_items][<?php echo esc_attr((string) $shortcodes_total); ?>][video_id]" value="">
										</label>
										<label>
											<span class="vidvix-catalog-field-label">
												<?php esc_html_e('File or URL', 'vidvix'); ?>
												<?php echo vidvix_tooltip_icon(__('File or URL', 'vidvix'), __('Add a local server path or a full HTTPS video URL. Local example: /site/wp-content/videos/movie.mp4. Remote example: https://example.com/videos/movie.mp4.', 'vidvix')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
											</span>
											<input type="text" class="vidvix-catalog-file-input" name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[catalog_items][<?php echo esc_attr((string) $shortcodes_total); ?>][file]" value="">
										</label>
										<label>
											<span class="vidvix-catalog-field-label">
												<?php esc_html_e('Generated Shortcode', 'vidvix'); ?>
												<?php echo vidvix_tooltip_icon(__('Generated Shortcode', 'vidvix'), __('This shortcode is created automatically from the Video ID. Save the shortcode, then copy it from the Shortcode List into a post or page.', 'vidvix')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
											</span>
											<input type="text" class="vidvix-shortcode-preview" readonly value="">
										</label>
									</div>
								</div>
							</div>
						<div class="vidvix-catalog-actions">
							<?php submit_button(__('Save Shortcode', 'vidvix'), 'button', 'submit', false); ?>
						</div>
						<textarea name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[catalog_text]" rows="1" class="large-text code" hidden><?php echo esc_textarea($settings['catalog_text']); ?></textarea>
					</section>

					<section class="vidvix-section-card vidvix-saved-catalog-card">
						<div class="vidvix-saved-catalog">
							<h2 class="vidvix-section-title">
								<img class="vidvix-section-title-icon" src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'assets/img/icon-video.png'); ?>" alt="" aria-hidden="true">
								<span><?php esc_html_e('Shortcode List', 'vidvix'); ?></span>
							</h2>
							<p class="vidvix-section-intro"><?php esc_html_e('Saved shortcodes for your rentable videos. Each row shows the video ID, media file or URL, shortcode, and row actions.', 'vidvix'); ?></p>

							<?php foreach ($catalog_items as $hidden_index => $hidden_video) : ?>
								<?php if ($hidden_index >= $shortcodes_offset && $hidden_index < $shortcodes_offset + $shortcodes_per_page) : ?>
									<?php continue; ?>
								<?php endif; ?>
								<input type="hidden" name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[catalog_items][<?php echo esc_attr((string) $hidden_index); ?>][video_id]" value="<?php echo esc_attr($hidden_video['video_id']); ?>">
								<input type="hidden" name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[catalog_items][<?php echo esc_attr((string) $hidden_index); ?>][file]" value="<?php echo esc_attr($hidden_video['file']); ?>">
							<?php endforeach; ?>

							<?php if (!empty($catalog_items)) : ?>
								<div class="vidvix-saved-catalog-list">
									<div class="vidvix-saved-catalog-header">
										<span><?php esc_html_e('Video ID', 'vidvix'); ?></span>
										<span><?php esc_html_e('File or URL', 'vidvix'); ?></span>
										<span><?php esc_html_e('Shortcode', 'vidvix'); ?></span>
										<span><?php esc_html_e('Actions', 'vidvix'); ?></span>
									</div>
									<?php foreach ($shortcodes_page_items as $saved_index => $catalog_video) : ?>
										<?php
										$saved_shortcode_id = 'vidvix-saved-shortcode-' . absint($saved_index);
										$saved_shortcode    = sprintf('[vidvix video_id="%s"]', $catalog_video['video_id']);
										?>
										<div class="vidvix-saved-catalog-item" data-catalog-index="<?php echo esc_attr((string) $saved_index); ?>">
											<div class="vidvix-saved-id-cell">
												<input type="hidden" name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[catalog_items][<?php echo esc_attr((string) $saved_index); ?>][video_id]" value="<?php echo esc_attr($catalog_video['video_id']); ?>">
												<input type="hidden" name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[catalog_items][<?php echo esc_attr((string) $saved_index); ?>][file]" value="<?php echo esc_attr($catalog_video['file']); ?>">
												<?php echo esc_html($catalog_video['video_id']); ?>
											</div>
											
											<span class="vidvix-saved-media-url"><?php echo esc_html($catalog_video['file']); ?></span>
											<input id="<?php echo esc_attr($saved_shortcode_id); ?>" class="vidvix-saved-shortcode-input vidvix-shortcode-readonly-clean" type="text" readonly value="<?php echo esc_attr($saved_shortcode); ?>">
											<div class="vidvix-action-buttons">
												<button type="button" class="button vidvix-copy-button vidvix-icon-button" data-copy-target="<?php echo esc_attr($saved_shortcode_id); ?>" aria-label="<?php esc_attr_e('Copy shortcode', 'vidvix'); ?>" title="<?php esc_attr_e('Copy shortcode', 'vidvix'); ?>">
													<img class="vidvix-action-icon" src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'assets/img/icon-copy.svg'); ?>" alt="" aria-hidden="true">
												</button>
												<button type="button" class="button vidvix-remove-row vidvix-icon-button" data-catalog-index="<?php echo esc_attr((string) $saved_index); ?>" aria-label="<?php esc_attr_e('Remove row', 'vidvix'); ?>" title="<?php esc_attr_e('Remove row', 'vidvix'); ?>">
													<img class="vidvix-action-icon" src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'assets/img/icon-trash.svg'); ?>" alt="" aria-hidden="true">
												</button>
											</div>
										</div>
									<?php endforeach; ?>
								</div>

								<?php if ($shortcodes_total_pages > 1) : ?>
									<div class="vidvix-pagination">
										<?php if ($shortcodes_page > 1) : ?>
											<a class="button" href="<?php echo esc_url(add_query_arg(array('vidvix_shortcode_page' => $shortcodes_page - 1), $shortcodes_base_url)); ?>"><?php esc_html_e('Previous', 'vidvix'); ?></a>
										<?php endif; ?>
										<span>
											<?php
											printf(
												esc_html(
													/* translators: 1: current page number, 2: total number of pages. */
													__('Page %1$d of %2$d', 'vidvix')
												),
												absint($shortcodes_page),
												absint($shortcodes_total_pages)
											);
											?>
										</span>
										<?php if ($shortcodes_page < $shortcodes_total_pages) : ?>
											<a class="button" href="<?php echo esc_url(add_query_arg(array('vidvix_shortcode_page' => $shortcodes_page + 1), $shortcodes_base_url)); ?>"><?php esc_html_e('Next', 'vidvix'); ?></a>
										<?php endif; ?>
									</div>
								<?php endif; ?>
							<?php else : ?>
								<p class="vidvix-saved-catalog-empty"><?php esc_html_e('No saved shortcodes yet. Add a shortcode, then save it here.', 'vidvix'); ?></p>
							<?php endif; ?>
						</div>
					</section>
				</div>

				<aside class="vidvix-admin-side">

					<section class="vidvix-side-card">
						<h2><?php esc_html_e('Video Example', 'vidvix'); ?></h2>
						<p><strong>Local video:</strong> 12345, /site/wp-content/videos/movie-one.mp4</p>
						<p><strong>Remote video:</strong> 67890, https://domain.com/videos/movie-two.mp4</p>
					</section>

					<section class="vidvix-side-card">
						<h2><?php esc_html_e('Shortcode example', 'vidvix'); ?></h2>
						<p><code>[vidvix video_id="12345"]</code></p>
						<p><?php esc_html_e('The shortcode is generated automatically when you add a video. Copy and paste it onto a page or post.', 'vidvix'); ?></p>
					</section>
				</aside>
			</div>
		</form>
	</div>
	<?php
}

function vidvix_render_settings_page() {
	if (!current_user_can('manage_options')) {
		return;
	}

	$settings = vidvix_get_settings();
	$catalog  = vidvix_get_catalog();
	?>
	<div class="wrap vidvix-admin">
		<?php vidvix_render_admin_header(__('Settings', 'vidvix'), __('Configure PayPal and the fixed rental price, currency, and duration.', 'vidvix')); ?>

		<?php // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- WordPress settings redirect flag. ?>
		<?php if (isset($_GET['settings-updated']) && 'true' === sanitize_text_field(wp_unslash($_GET['settings-updated']))) : ?>
			<div class="notice notice-success is-dismissible vidvix-own-notice">
				<p><?php esc_html_e('VidVix settings saved.', 'vidvix'); ?></p>
			</div>
		<?php endif; ?>

		<form id="vidvix-settings-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>" novalidate>
			<input type="hidden" name="action" value="vidvix_save_settings">
			<input type="hidden" name="vidvix_redirect_page" value="vidvix-settings">
			<?php wp_nonce_field('vidvix_save_settings', 'vidvix_save_settings_nonce'); ?>

			<div class="vidvix-admin-grid">
				<div class="vidvix-admin-main">
					<section id="vidvix-paypal" class="vidvix-section-card">
						<h2 class="vidvix-section-title">
							<img class="vidvix-section-title-icon vidvix-section-title-icon-paypal" src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'assets/img/PayPal_Logo.svg'); ?>" alt="" aria-hidden="true">
							<span><?php esc_html_e('PayPal Settings', 'vidvix'); ?></span>
						</h2>
						<p class="vidvix-section-intro"><?php esc_html_e('Connect your PayPal account so visitors can unlock video rentals directly on the page.', 'vidvix'); ?></p>
						<table class="form-table" role="presentation">
							<tr>
								<th scope="row"><label for="vidvix_paypal_client_id"><?php esc_html_e('PayPal Client ID', 'vidvix'); ?></label></th>
								<td><input name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[paypal_client_id]" id="vidvix_paypal_client_id" type="text" class="regular-text" value="<?php echo esc_attr($settings['paypal_client_id']); ?>"></td>
							</tr>
							<tr>
								<th scope="row"><label for="vidvix_paypal_secret"><?php esc_html_e('PayPal Secret', 'vidvix'); ?></label></th>
								<td><input name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[paypal_secret]" id="vidvix_paypal_secret" type="text" class="regular-text" value="<?php echo esc_attr($settings['paypal_secret']); ?>"></td>
							</tr>
							<tr>
								<th scope="row"><label for="vidvix_paypal_api_base"><?php esc_html_e('PayPal API Base', 'vidvix'); ?></label></th>
								<td>
									<input name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[paypal_api_base]" id="vidvix_paypal_api_base" type="text" class="regular-text" value="<?php echo esc_attr($settings['paypal_api_base']); ?>">
									<p class="description"><?php esc_html_e('Use https://api-m.sandbox.paypal.com for testing or https://api-m.paypal.com for live.', 'vidvix'); ?></p>
								</td>
							</tr>
						</table>
					</section>

					<section id="vidvix-rental" class="vidvix-section-card">
						<h2 class="vidvix-section-title">
							<img class="vidvix-section-title-icon" src="<?php echo esc_url(plugin_dir_url(__FILE__) . 'assets/img/settings.png'); ?>" alt="" aria-hidden="true">
							<span><?php esc_html_e('Rental Settings', 'vidvix'); ?></span>
						</h2>
						<p class="vidvix-section-intro"><?php esc_html_e('Set a fixed rental duration and fixed price/currency used for every video.', 'vidvix'); ?></p>
						<table class="form-table" role="presentation">
							<tr>
								<th scope="row"><label for="vidvix_rent_hours"><?php esc_html_e('Fixed Rental Hours', 'vidvix'); ?></label></th>
								<td><input name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[rent_hours]" id="vidvix_rent_hours" type="number" min="1" class="small-text" value="<?php echo esc_attr((string) $settings['rent_hours']); ?>"></td>
							</tr>
							<tr>
								<th scope="row"><label for="vidvix_currency"><?php esc_html_e('Currency', 'vidvix'); ?></label></th>
								<td><input name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[currency]" id="vidvix_currency" type="text" maxlength="3" class="small-text" value="<?php echo esc_attr($settings['currency']); ?>"></td>
							</tr>
							<tr>
								<th scope="row"><label for="vidvix_default_price"><?php esc_html_e('Fixed Price', 'vidvix'); ?></label></th>
								<td><input name="<?php echo esc_attr(VIDVIX_SETTINGS_OPTION); ?>[default_price]" id="vidvix_default_price" type="text" class="small-text" value="<?php echo esc_attr($settings['default_price']); ?>"></td>
							</tr>
						</table>
					</section>
					<div class="vidvix-settings-actions">
						<?php submit_button(__('Save Settings', 'vidvix'), 'button', 'submit', false); ?>
					</div>
				</div>

				<aside class="vidvix-admin-side">
					<section class="vidvix-side-card">
						<h2><?php esc_html_e('Quick Summary', 'vidvix'); ?></h2>
						<div class="vidvix-stat-list">
							<div class="vidvix-stat">
								<strong><?php echo esc_html($settings['default_price'] . ' ' . $settings['currency']); ?></strong>
								<span><?php esc_html_e('Fixed rental price', 'vidvix'); ?></span>
							</div>
							<div class="vidvix-stat">
								<strong><?php echo esc_html((string) vidvix_get_rent_hours()); ?></strong>
								<span><?php esc_html_e('Fixed rental duration', 'vidvix'); ?></span>
							</div>
						</div>
					</section>
				</aside>
			</div>
		</form>
	</div>
	<?php
}

function vidvix_get_video_title($video_id) {
	$video = vidvix_get_video($video_id);

	if ($video && !empty($video['title'])) {
		return $video['title'];
	}

	return $video_id;
}

function vidvix_normalize_rental_month($month) {
	$month = sanitize_text_field((string) $month);

	return preg_match('/^\d{4}-\d{2}$/', $month) ? $month : '';
}

function vidvix_get_rental_month_options() {
	global $wpdb;

	$table_name = esc_sql($wpdb->prefix . 'vidvix_movie_access');
	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
	$months     = $wpdb->get_col(
		$wpdb->prepare(
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
			"SELECT DISTINCT DATE_FORMAT(created_at, %s) FROM {$table_name} ORDER BY created_at DESC",
			'%Y-%m'
		)
	);

	return is_array($months) ? array_filter(array_map('vidvix_normalize_rental_month', $months)) : array();
}

function vidvix_get_rentals_data($page = 1, $per_page = 50, $search = '', $month = '') {
	global $wpdb;

	$table_name  = esc_sql($wpdb->prefix . 'vidvix_movie_access');
	$page        = max(1, (int) $page);
	$per_page    = max(1, (int) $per_page);
	$offset      = ($page - 1) * $per_page;
	$month       = vidvix_normalize_rental_month($month);
	$has_search  = $search !== '';
	$has_month   = $month !== '';
	$like        = '%' . $wpdb->esc_like($search) . '%';
	$month_start = $month . '-01 00:00:00';
	$month_end   = gmdate('Y-m-d H:i:s', strtotime($month_start . ' +1 month'));

	if ($has_search && $has_month) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		$total = (int) $wpdb->get_var(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT COUNT(*) FROM {$table_name} WHERE (video_id LIKE %s OR payer_email LIKE %s OR payment_ref LIKE %s) AND created_at >= %s AND created_at < %s",
				$like,
				$like,
				$like,
				$month_start,
				$month_end
			)
		);
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		$rows  = $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT * FROM {$table_name} WHERE (video_id LIKE %s OR payer_email LIKE %s OR payment_ref LIKE %s) AND created_at >= %s AND created_at < %s ORDER BY created_at DESC LIMIT %d OFFSET %d",
				$like,
				$like,
				$like,
				$month_start,
				$month_end,
				$per_page,
				$offset
			),
			ARRAY_A
		);
	} elseif ($has_search) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		$total = (int) $wpdb->get_var(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT COUNT(*) FROM {$table_name} WHERE video_id LIKE %s OR payer_email LIKE %s OR payment_ref LIKE %s",
				$like,
				$like,
				$like
			)
		);
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		$rows  = $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT * FROM {$table_name} WHERE video_id LIKE %s OR payer_email LIKE %s OR payment_ref LIKE %s ORDER BY created_at DESC LIMIT %d OFFSET %d",
				$like,
				$like,
				$like,
				$per_page,
				$offset
			),
			ARRAY_A
		);
	} elseif ($has_month) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		$total = (int) $wpdb->get_var(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT COUNT(*) FROM {$table_name} WHERE created_at >= %s AND created_at < %s",
				$month_start,
				$month_end
			)
		);
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		$rows  = $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT * FROM {$table_name} WHERE created_at >= %s AND created_at < %s ORDER BY created_at DESC LIMIT %d OFFSET %d",
				$month_start,
				$month_end,
				$per_page,
				$offset
			),
			ARRAY_A
		);
	} else {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		$total = (int) $wpdb->get_var(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT COUNT(*) FROM {$table_name} WHERE 1 = %d",
				1
			)
		);
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		$rows  = $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT * FROM {$table_name} WHERE 1 = %d ORDER BY created_at DESC LIMIT %d OFFSET %d",
				1,
				$per_page,
				$offset
			),
			ARRAY_A
		);
	}

	return array(
		'total'    => $total,
		'rows'     => is_array($rows) ? $rows : array(),
		'page'     => $page,
		'per_page' => $per_page,
		'pages'    => max(1, (int) ceil($total / $per_page)),
	);
}

function vidvix_get_rentals_export_rows($search = '', $month = '') {
	global $wpdb;

	$table_name  = esc_sql($wpdb->prefix . 'vidvix_movie_access');
	$month       = vidvix_normalize_rental_month($month);
	$has_search  = $search !== '';
	$has_month   = $month !== '';
	$like        = '%' . $wpdb->esc_like($search) . '%';
	$month_start = $month . '-01 00:00:00';
	$month_end   = gmdate('Y-m-d H:i:s', strtotime($month_start . ' +1 month'));

	if ($has_search && $has_month) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		return $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT * FROM {$table_name} WHERE (video_id LIKE %s OR payer_email LIKE %s OR payment_ref LIKE %s) AND created_at >= %s AND created_at < %s ORDER BY created_at DESC",
				$like,
				$like,
				$like,
				$month_start,
				$month_end
			),
			ARRAY_A
		);
	}

	if ($has_search) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		return $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT * FROM {$table_name} WHERE video_id LIKE %s OR payer_email LIKE %s OR payment_ref LIKE %s ORDER BY created_at DESC",
				$like,
				$like,
				$like
			),
			ARRAY_A
		);
	}

	if ($has_month) {
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
		return $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
				"SELECT * FROM {$table_name} WHERE created_at >= %s AND created_at < %s ORDER BY created_at DESC",
				$month_start,
				$month_end
			),
			ARRAY_A
		);
	}

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
	return $wpdb->get_results(
		$wpdb->prepare(
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
			"SELECT * FROM {$table_name} WHERE 1 = %d ORDER BY created_at DESC",
			1
		),
		ARRAY_A
	);
}

function vidvix_export_rentals_csv() {
	if (!current_user_can('manage_options')) {
		wp_die(esc_html__('You do not have permission to export VidVix rentals.', 'vidvix'));
	}

	check_admin_referer('vidvix_export_rentals_csv');

	$search = isset($_GET['s']) ? sanitize_text_field(wp_unslash($_GET['s'])) : '';
	$raw_month = isset($_GET['m']) ? sanitize_text_field(wp_unslash($_GET['m'])) : '';
	$month     = ('current' === $raw_month) ? gmdate('Y-m') : vidvix_normalize_rental_month($raw_month);
	$rows   = vidvix_get_rentals_export_rows($search, $month);

	if (ob_get_level()) {
		while (ob_get_level()) {
			ob_end_clean();
		}
	}

	nocache_headers();
	header('Content-Type: text/csv; charset=utf-8');
	header('Content-Disposition: attachment; filename="vidvix-rentals-' . gmdate('Y-m-d') . '.csv"');

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Streams CSV directly to the browser.
	$output = fopen('php://output', 'w');

	if (!$output) {
		exit;
	}

	fputcsv(
		$output,
		array(
			__('Video ID', 'vidvix'),
			__('Customer Email', 'vidvix'),
			__('Payment Ref', 'vidvix'),
			__('Created UTC', 'vidvix'),
			__('Expires UTC', 'vidvix'),
			__('Status', 'vidvix'),
		)
	);

	foreach ((array) $rows as $rental) {
		$is_active = !empty($rental['expires_at']) && strtotime($rental['expires_at'] . ' UTC') >= time();

		fputcsv(
			$output,
			array(
				$rental['video_id'],
				$rental['payer_email'],
				$rental['payment_ref'],
				$rental['created_at'],
				$rental['expires_at'],
				$is_active ? __('Active', 'vidvix') : __('Expired', 'vidvix'),
			)
		);
	}

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closes CSV output stream.
	fclose($output);
	exit;
}

function vidvix_render_rentals_page() {
	if (!current_user_can('manage_options')) {
		return;
	}

	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin pagination value.
	$page       = isset($_GET['paged']) ? max(1, absint($_GET['paged'])) : 1;
	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin search filter.
	$search     = isset($_GET['s']) ? sanitize_text_field(wp_unslash($_GET['s'])) : '';
	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin month filter.
	$has_month  = isset($_GET['m']);
	// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin month filter.
	$raw_month  = $has_month ? sanitize_text_field(wp_unslash($_GET['m'])) : 'current';
	$month      = ('current' === $raw_month) ? gmdate('Y-m') : vidvix_normalize_rental_month($raw_month);
	$months     = vidvix_get_rental_month_options();
	$months     = array_values(array_unique(array_merge(array(gmdate('Y-m')), $months)));
	$rentals    = vidvix_get_rentals_data($page, 50, $search, $month);
	$base_url   = admin_url('admin.php?page=vidvix-rentals');
	$export_url = wp_nonce_url(
		add_query_arg(
			array(
				'action' => 'vidvix_export_rentals_csv',
				's'      => $search,
				'm'      => $month,
			),
			admin_url('admin-post.php')
		),
		'vidvix_export_rentals_csv'
	);
	?>
	<div class="wrap vidvix-admin">
		<?php vidvix_render_admin_header(__('Rentals', 'vidvix'), __('Review completed rentals, their expiration dates, and payment references for each rented video.', 'vidvix')); ?>

		<section class="vidvix-section-card">
            <form method="get" class="vidvix-rentals-toolbar">
            	<h2 class="vidvix-rentals-title"><?php esc_html_e('Rental Log', 'vidvix'); ?></h2>
            
            	<div class="vidvix-rentals-controls-row">
            		<div class="vidvix-rentals-filter-row">
            			<input type="hidden" name="page" value="vidvix-rentals">
            
            			<select class="vidvix-rentals-month-select" name="m" aria-label="<?php esc_attr_e('Select month', 'vidvix'); ?>">
            				<option value="current" <?php selected('current', $raw_month); ?>><?php esc_html_e('- Select month -', 'vidvix'); ?></option>
            				<option value=""><?php esc_html_e('All months', 'vidvix'); ?></option>
            				<?php foreach ($months as $available_month) : ?>
            					<option value="<?php echo esc_attr($available_month); ?>" <?php selected($raw_month, $available_month); ?>>
            						<?php echo esc_html(date_i18n('F Y', strtotime($available_month . '-01'))); ?>
            					</option>
            				<?php endforeach; ?>
            			</select>
            
            			<a class="button" href="<?php echo esc_url($export_url); ?>">
            				<?php esc_html_e('Download CSV', 'vidvix'); ?>
            			</a>
            		</div>
            
            		<div class="vidvix-rentals-search">
            			<input type="search" class="vidvix-search-input" name="s" value="<?php echo esc_attr($search); ?>" placeholder="<?php esc_attr_e('Search video ID, email, payment ref', 'vidvix'); ?>">
            			<button type="submit" class="button"><?php esc_html_e('Search', 'vidvix'); ?></button>
            		</div>
            	</div>
            </form>
			<?php if (empty($rentals['rows'])) : ?>
				<div class="vidvix-rentals-empty"><?php esc_html_e('No rentals found yet.', 'vidvix'); ?></div>
			<?php else : ?>
				<div class="vidvix-rentals-table-wrap">
					<table class="vidvix-rentals-table">
						<thead>
							<tr>
								<th><?php esc_html_e('Video ID', 'vidvix'); ?></th>
								<th><?php esc_html_e('Customer Email', 'vidvix'); ?></th>
								<th><?php esc_html_e('Payment Date', 'vidvix'); ?></th>
								<th><?php esc_html_e('Payment Ref', 'vidvix'); ?></th>
								<th><?php esc_html_e('Expires', 'vidvix'); ?></th>
								<th><?php esc_html_e('Status', 'vidvix'); ?></th>
							</tr>
						</thead>
						<tbody>
							<?php foreach ($rentals['rows'] as $rental) : ?>
								<?php
								$is_active = !empty($rental['expires_at']) && strtotime($rental['expires_at'] . ' UTC') >= time();
								?>
								<tr>
									<td><?php echo esc_html($rental['video_id']); ?></td>
									<td><?php echo esc_html($rental['payer_email']); ?></td>
										<td><?php echo esc_html($rental['created_at']); ?></td>
									<td><?php echo esc_html($rental['payment_ref']); ?></td>
									<td><?php echo esc_html($rental['expires_at']); ?></td>
									<td><span class="vidvix-status-pill <?php echo $is_active ? 'active' : 'expired'; ?>"><?php echo $is_active ? esc_html__('Active', 'vidvix') : esc_html__('Expired', 'vidvix'); ?></span></td>
								</tr>
							<?php endforeach; ?>
						</tbody>
					</table>
				</div>

				<?php if ($rentals['pages'] > 1) : ?>
					<div class="vidvix-pagination">
						<?php if ($rentals['page'] > 1) : ?>
							<a class="button" href="<?php echo esc_url(add_query_arg(array('page' => 'vidvix-rentals', 'paged' => $rentals['page'] - 1, 's' => $search, 'm' => $month), $base_url)); ?>"><?php esc_html_e('Previous', 'vidvix'); ?></a>
						<?php endif; ?>
						<span>
							<?php
							printf(
								esc_html(
									/* translators: 1: current page number, 2: total number of pages. */
									__('Page %1$d of %2$d', 'vidvix')
								),
								absint($rentals['page']),
								absint($rentals['pages'])
							);
							?>
						</span>
						<?php if ($rentals['page'] < $rentals['pages']) : ?>
							<a class="button" href="<?php echo esc_url(add_query_arg(array('page' => 'vidvix-rentals', 'paged' => $rentals['page'] + 1, 's' => $search, 'm' => $month), $base_url)); ?>"><?php esc_html_e('Next', 'vidvix'); ?></a>
						<?php endif; ?>
					</div>
				<?php endif; ?>
			<?php endif; ?>
		</section>
	</div>
	<?php
}

function vidvix_get_catalog() {
	$settings = vidvix_get_settings();
	$catalog  = array();

	if (!empty($settings['catalog_items']) && is_array($settings['catalog_items'])) {
		foreach (vidvix_sanitize_catalog_items($settings['catalog_items']) as $item) {
			$catalog[$item['video_id']] = $item;
		}

		return $catalog;
	}

	$lines = preg_split('/\r\n|\r|\n/', (string) $settings['catalog_text']);

	foreach ($lines as $line) {
		$line = trim($line);

		if ($line === '' || strpos($line, '#') === 0) {
			continue;
		}

		$parts = array_map('trim', explode('|', $line));

		if (count($parts) < 3) {
			continue;
		}

		$video_id = sanitize_key($parts[0]);
		$title    = sanitize_text_field($parts[1]);
		$file_raw = trim($parts[2]);
		$file     = vidvix_is_remote_video_url($file_raw) ? esc_url_raw($file_raw) : wp_normalize_path($file_raw);

		if ($video_id === '' || $title === '' || $file === '') {
			continue;
		}

		$catalog[$video_id] = array(
			'video_id' => $video_id,
			'title'    => $title,
			'file'     => $file,
		);
	}

	return $catalog;
}

function vidvix_get_video($video_id) {
	$catalog = vidvix_get_catalog();

	return isset($catalog[$video_id]) ? $catalog[$video_id] : null;
}

function vidvix_create_access_table() {
	global $wpdb;

	require_once ABSPATH . 'wp-admin/includes/upgrade.php';

	$table_name = $wpdb->prefix . 'vidvix_movie_access';
	$sql        = "CREATE TABLE {$table_name} (
		id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
		access_token VARCHAR(64) NOT NULL,
		video_id VARCHAR(64) NOT NULL,
		payer_email VARCHAR(191) NOT NULL DEFAULT '',
		payment_ref VARCHAR(191) NOT NULL DEFAULT '',
		expires_at DATETIME NOT NULL,
		created_at DATETIME NOT NULL,
		PRIMARY KEY (id),
		KEY access_token (access_token),
		KEY video_id (video_id),
		KEY expires_at (expires_at)
	) {$wpdb->get_charset_collate()};";

	dbDelta($sql);
}

function vidvix_is_remote_video_url($value) {
	$url = wp_http_validate_url((string) $value);

	if (!$url) {
		return false;
	}

	$scheme = strtolower((string) wp_parse_url($url, PHP_URL_SCHEME));

	return in_array($scheme, array('http', 'https'), true);
}

function vidvix_get_video_source($video_id) {
	$video = vidvix_get_video($video_id);

	if (!$video || empty($video['file'])) {
		return null;
	}

	if (vidvix_is_remote_video_url($video['file'])) {
		return array(
			'type'  => 'remote',
			'value' => esc_url_raw($video['file']),
		);
	}

	$path = wp_normalize_path($video['file']);

	if ('' === $path || '/' !== substr($path, 0, 1)) {
		return null;
	}

	return array(
		'type'  => 'local',
		'value' => $path,
	);
}

function vidvix_get_source_file_path($video_id) {
	$source = vidvix_get_video_source($video_id);

	if (!$source || 'local' !== $source['type']) {
		return '';
	}

	return $source['value'];
}

function vidvix_get_remote_video_url($video_id) {
	$source = vidvix_get_video_source($video_id);

	if (!$source || 'remote' !== $source['type']) {
		return '';
	}

	return $source['value'];
}

function vidvix_is_video_source_available($video_id) {
	$source = vidvix_get_video_source($video_id);

	if (!$source) {
		return false;
	}

	if ('remote' === $source['type']) {
		return true;
	}

	return file_exists($source['value']) && is_readable($source['value']);
}

function vidvix_generate_access_token() {
	return wp_generate_password(48, false, false);
}

function vidvix_set_access_cookie($token) {
	$expire = time() + (DAY_IN_SECONDS * 30);

	setcookie(
		VIDVIX_ACCESS_COOKIE,
		$token,
		array(
			'expires'  => $expire,
			'path'     => COOKIEPATH ? COOKIEPATH : '/',
			'domain'   => COOKIE_DOMAIN,
			'secure'   => is_ssl(),
			'httponly' => true,
			'samesite' => 'Lax',
		)
	);

	$_COOKIE[VIDVIX_ACCESS_COOKIE] = $token;
}

function vidvix_get_access_token() {
	return isset($_COOKIE[VIDVIX_ACCESS_COOKIE]) ? sanitize_text_field(wp_unslash($_COOKIE[VIDVIX_ACCESS_COOKIE])) : '';
}

function vidvix_has_video_access($video_id) {
	global $wpdb;

	$token = vidvix_get_access_token();

	if (!$token || !$video_id) {
		return false;
	}

	$table_name = $wpdb->prefix . 'vidvix_movie_access';
	$table_name = esc_sql($table_name);
	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
	$row        = $wpdb->get_var(
		$wpdb->prepare(
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is generated from the WordPress database prefix and escaped with esc_sql().
			"SELECT id FROM {$table_name}
				WHERE access_token = %s
				  AND video_id = %s
				  AND expires_at >= UTC_TIMESTAMP()
				LIMIT 1",
			$token,
			$video_id
		)
	);

	return !empty($row);
}

function vidvix_grant_video_access($video_id, $payer_email = '', $payment_ref = '') {
	global $wpdb;

	$video  = vidvix_get_video($video_id);
	$source = vidvix_get_video_source($video_id);

	if (!$video || !$source || !vidvix_is_video_source_available($video_id)) {
		return false;
	}

	$token      = vidvix_generate_access_token();
	$table_name = $wpdb->prefix . 'vidvix_movie_access';

	// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Uses the plugin-owned rental access table.
	$inserted = $wpdb->insert(
		$table_name,
		array(
			'access_token' => $token,
			'video_id'     => $video_id,
			'payer_email'  => sanitize_email($payer_email),
			'payment_ref'  => sanitize_text_field($payment_ref),
			'expires_at'   => gmdate('Y-m-d H:i:s', time() + (vidvix_get_rent_hours() * HOUR_IN_SECONDS)),
			'created_at'   => gmdate('Y-m-d H:i:s'),
		),
		array('%s', '%s', '%s', '%s', '%s', '%s')
	);

	if (!$inserted) {
		return false;
	}

	vidvix_set_access_cookie($token);

	return $token;
}

function vidvix_get_rent_hours() {
	$settings = vidvix_get_settings();

	return max(1, absint($settings['rent_hours']));
}

function vidvix_get_currency() {
	$settings = vidvix_get_settings();

	return $settings['currency'];
}

function vidvix_get_video_price($video_id) {
	$settings = vidvix_get_settings();
	$video    = vidvix_get_video($video_id);

	if (!$video) {
		return null;
	}

	return array(
		'value'    => vidvix_normalize_price($settings['default_price'], '2.99'),
		'currency' => vidvix_get_currency(),
	);
}

function vidvix_get_price_label($video_id) {
	$price = vidvix_get_video_price($video_id);

	if (!$price) {
		return __('Rent now', 'vidvix');
	}

	return sprintf(
		/* translators: %s: price */
		__('Rent this video for %s', 'vidvix'),
		$price['value'] . ' ' . $price['currency']
	);
}

function vidvix_build_stream_signature($video_id, $access_token, $expires) {
	return hash_hmac('sha256', $video_id . '|' . $access_token . '|' . $expires, wp_salt('auth'));
}

function vidvix_get_stream_url($video_id) {
	$access_token = vidvix_get_access_token();

	if (!$access_token || !vidvix_has_video_access($video_id)) {
		return '';
	}

	$expires   = time() + (10 * MINUTE_IN_SECONDS);
	$signature = vidvix_build_stream_signature($video_id, $access_token, $expires);

	return add_query_arg(
		array(
			'e' => $expires,
			's' => $signature,
		),
		rest_url(VIDVIX_REST_NAMESPACE . '/stream/' . rawurlencode($video_id))
	);
}

function vidvix_request_comes_from_site() {
	$site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
	$headers   = array('HTTP_ORIGIN', 'HTTP_REFERER');

	foreach ($headers as $header) {
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Presence check only; value is sanitized before use below.
		if (empty($_SERVER[$header])) {
			continue;
		}

		$host = wp_parse_url(sanitize_text_field(wp_unslash($_SERVER[$header])), PHP_URL_HOST);

		if ($host && $site_host && strtolower($host) === strtolower($site_host)) {
			return true;
		}
	}

	return false;
}

function vidvix_validate_stream_request($video_id, WP_REST_Request $request) {
	$expires      = absint($request->get_param('e'));
	$signature    = sanitize_text_field((string) $request->get_param('s'));
	$access_token = vidvix_get_access_token();

	if (!$expires || !$signature || !$access_token) {
		return new WP_Error('vidvix_forbidden', __('Missing playback credentials.', 'vidvix'), array('status' => 403));
	}

	if ($expires < time()) {
		return new WP_Error('vidvix_expired', __('Playback link has expired.', 'vidvix'), array('status' => 403));
	}

	if (!vidvix_has_video_access($video_id)) {
		return new WP_Error('vidvix_denied', __('Access to this video has expired.', 'vidvix'), array('status' => 403));
	}

	$expected = vidvix_build_stream_signature($video_id, $access_token, $expires);

	if (!hash_equals($expected, $signature)) {
		return new WP_Error('vidvix_invalid_signature', __('Invalid playback signature.', 'vidvix'), array('status' => 403));
	}

	if (!vidvix_request_comes_from_site()) {
		return new WP_Error('vidvix_origin_denied', __('403 Forbidden', 'vidvix'), array('status' => 403));
	}

	return true;
}

function vidvix_stream_local_file($path) {
	if (!file_exists($path) || !is_readable($path)) {
		status_header(404);
		exit;
	}

	if (ob_get_level()) {
		while (ob_get_level()) {
			ob_end_clean();
		}
	}

	$size       = filesize($path);
	$start      = 0;
	$end        = $size - 1;
	$status     = 200;
	$content_tp = wp_check_filetype($path);
	$mime_type  = !empty($content_tp['type']) ? $content_tp['type'] : 'video/mp4';

	$http_range = isset($_SERVER['HTTP_RANGE']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_RANGE'])) : '';
	if ($http_range && preg_match('/bytes=(\d*)-(\d*)/i', $http_range, $matches)) {
		if ($matches[1] !== '') {
			$start = (int) $matches[1];
		}

		if ($matches[2] !== '') {
			$end = (int) $matches[2];
		}

		$start  = max(0, $start);
		$end    = min($end, $size - 1);
		$status = 206;
	}

	$length = ($end - $start) + 1;
	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Required for byte-range video streaming.
	$handle = fopen($path, 'rb');

	if (!$handle) {
		status_header(500);
		exit;
	}

	nocache_headers();
	status_header($status);
	header('Content-Type: ' . $mime_type);
	header('Content-Length: ' . (string) $length);
	header('Accept-Ranges: bytes');
	header('Content-Disposition: inline; filename="' . rawurlencode(basename($path)) . '"');
	header('X-Content-Type-Options: nosniff');
	header('Cross-Origin-Resource-Policy: same-site');

	if (206 === $status) {
		header("Content-Range: bytes {$start}-{$end}/{$size}");
	}

	fseek($handle, $start);

	$bytes_left = $length;
	$chunk_size = 1024 * 1024;

	while (!feof($handle) && $bytes_left > 0) {
		$read_length = min($chunk_size, $bytes_left);
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread -- Required for chunked byte-range video streaming.
		$buffer      = fread($handle, $read_length);

		if (false === $buffer) {
			break;
		}

		echo $buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		flush();
		$bytes_left -= strlen($buffer);
	}

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Required for byte-range video streaming.
	fclose($handle);
	exit;
}

function vidvix_stream_remote_file($url) {
	$temp_file = wp_tempnam(wp_basename((string) wp_parse_url($url, PHP_URL_PATH)));

	if (!$temp_file) {
		status_header(500);
		exit;
	}

	$request_headers = array();

	$http_range = isset($_SERVER['HTTP_RANGE']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_RANGE'])) : '';

	if ($http_range) {
		$request_headers['Range'] = $http_range;
	}

	$response = wp_remote_get(
		$url,
		array(
			'timeout'     => 60,
			'redirection' => 5,
			'headers'     => $request_headers,
			'stream'      => true,
			'filename'    => $temp_file,
		)
	);

	if (is_wp_error($response)) {
		wp_delete_file($temp_file);
		status_header(502);
		exit;
	}

	$status_code = wp_remote_retrieve_response_code($response);

	if ($status_code < 200 || $status_code >= 400) {
		wp_delete_file($temp_file);
		status_header($status_code ? $status_code : 502);
		exit;
	}

	$content_type  = wp_remote_retrieve_header($response, 'content-type');
	$content_len   = wp_remote_retrieve_header($response, 'content-length');
	$content_range = wp_remote_retrieve_header($response, 'content-range');
	$accept_ranges = wp_remote_retrieve_header($response, 'accept-ranges');
	$download_name = wp_basename((string) wp_parse_url($url, PHP_URL_PATH));

	if (!$download_name) {
		$download_name = 'video.mp4';
	}

	if (ob_get_level()) {
		while (ob_get_level()) {
			ob_end_clean();
		}
	}

	nocache_headers();
	status_header($status_code);
	header('Content-Type: ' . ($content_type ? $content_type : 'video/mp4'));
	header('Content-Disposition: inline; filename="' . rawurlencode($download_name) . '"');
	header('X-Content-Type-Options: nosniff');
	header('Cross-Origin-Resource-Policy: same-site');

	if ($content_len) {
		header('Content-Length: ' . $content_len);
	}

	if ($accept_ranges) {
		header('Accept-Ranges: ' . $accept_ranges);
	} else {
		header('Accept-Ranges: bytes');
	}

	if ($content_range) {
		header('Content-Range: ' . $content_range);
	}

	// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_readfile -- Streams the downloaded temporary video file to the browser.
	readfile($temp_file);
	wp_delete_file($temp_file);
	exit;
}

function vidvix_get_paypal_setting($key) {
	$settings = vidvix_get_settings();

	return isset($settings[$key]) ? $settings[$key] : '';
}

function vidvix_paypal_get_access_token() {
	$client_id = vidvix_get_paypal_setting('paypal_client_id');
	$secret    = vidvix_get_paypal_setting('paypal_secret');
	$api_base  = vidvix_get_paypal_setting('paypal_api_base');

	if (!$client_id || !$secret || !$api_base) {
		return new WP_Error('vidvix_paypal_missing', __('PayPal settings are incomplete.', 'vidvix'));
	}

	$response = wp_remote_post(
		$api_base . '/v1/oauth2/token',
		array(
			'timeout' => 30,
			'headers' => array(
				'Authorization' => 'Basic ' . base64_encode($client_id . ':' . $secret),
				'Accept'        => 'application/json',
				'Content-Type'  => 'application/x-www-form-urlencoded',
			),
			'body'    => 'grant_type=client_credentials',
		)
	);

	if (is_wp_error($response)) {
		return $response;
	}

	$code = wp_remote_retrieve_response_code($response);
	$body = json_decode(wp_remote_retrieve_body($response), true);

	if ($code < 200 || $code >= 300 || empty($body['access_token'])) {
		return new WP_Error(
			'vidvix_paypal_auth_failed',
			__('Could not authenticate with PayPal.', 'vidvix'),
			array(
				'code' => $code,
				'body' => $body,
			)
		);
	}

	return $body['access_token'];
}

function vidvix_paypal_api_request($method, $path, $payload = null) {
	$token    = vidvix_paypal_get_access_token();
	$api_base = vidvix_get_paypal_setting('paypal_api_base');

	if (is_wp_error($token)) {
		return $token;
	}

	$args = array(
		'method'  => strtoupper($method),
		'timeout' => 30,
		'headers' => array(
			'Authorization' => 'Bearer ' . $token,
			'Accept'        => 'application/json',
			'Content-Type'  => 'application/json',
		),
	);

	if (null !== $payload) {
		$args['body'] = wp_json_encode($payload);
	}

	$response = wp_remote_request($api_base . $path, $args);

	if (is_wp_error($response)) {
		return $response;
	}

	$code = wp_remote_retrieve_response_code($response);
	$body = json_decode(wp_remote_retrieve_body($response), true);

	if ($code < 200 || $code >= 300) {
		return new WP_Error(
			'vidvix_paypal_api_failed',
			__('PayPal API request failed.', 'vidvix'),
			array(
				'code' => $code,
				'body' => $body,
			)
		);
	}

	return $body;
}

function vidvix_render_shortcode($atts) {
	$atts = shortcode_atts(
		array(
			'video_id' => '',
		),
		$atts,
		'vidvix'
	);

	$video_id = sanitize_key($atts['video_id']);
	$video    = vidvix_get_video($video_id);

	if (!$video_id) {
		return '<p>' . esc_html__('Please provide a video_id in the shortcode.', 'vidvix') . '</p>';
	}

	if (!$video) {
		return '<p>' . esc_html__('This video ID was not found in the VidVix catalog.', 'vidvix') . '</p>';
	}

	ob_start();
	?>
	<div class="vidvix-player" data-video-id="<?php echo esc_attr($video_id); ?>">
		<div class="vidvix-status">
			<?php
			printf(
				esc_html(
					/* translators: %s: video title. */
					__('Preparing %s...', 'vidvix')
				),
				esc_html($video['title'])
			);
			?>
		</div>
		<button type="button" class="vidvix-rent-button" hidden></button>
		<div class="vidvix-paypal-buttons" hidden></div>
		<div class="vidvix-video-wrap" hidden>
			<video class="vidvix-video" controls playsinline preload="metadata"></video>
		</div>
	</div>
	<?php
	return ob_get_clean();
}

function vidvix_enqueue_assets() {
	$should_enqueue = false;

	if (is_singular()) {
		$post = get_post();

		if ($post instanceof WP_Post && has_shortcode($post->post_content, 'vidvix')) {
			$should_enqueue = true;
		}
	}

	if (!$should_enqueue) {
		return;
	}

	wp_enqueue_style(
		'vidvix-frontend',
		plugin_dir_url(__FILE__) . 'assets/css/vidvix-frontend.css',
		array(),
		vidvix_get_asset_version('assets/css/vidvix-frontend.css')
	);

	$client_id = vidvix_get_paypal_setting('paypal_client_id');

	if ($client_id) {
		wp_enqueue_script(
			'paypal-sdk-js',
			'https://www.paypal.com/sdk/js?client-id=' . rawurlencode($client_id) . '&currency=' . rawurlencode(vidvix_get_currency()),
			array(),
			VIDVIX_VERSION,
			true
		);
	} else {
		wp_register_script('paypal-sdk-js', false, array(), '1.0.0', true);
		wp_enqueue_script('paypal-sdk-js');
	}

	wp_enqueue_script(
		'vidvix-frontend',
		plugin_dir_url(__FILE__) . 'assets/js/vidvix-frontend.js',
		array('paypal-sdk-js'),
		vidvix_get_asset_version('assets/js/vidvix-frontend.js'),
		true
	);

	wp_localize_script(
		'vidvix-frontend',
		'VidVix',
		array(
			'restBase'       => esc_url_raw(rest_url(VIDVIX_REST_NAMESPACE . '/')),
			'rentHours'      => vidvix_get_rent_hours(),
			'paypalClientId' => $client_id,
			'currency'       => vidvix_get_currency(),
		)
	);
}

function vidvix_register_rest_routes() {
	register_rest_route(
		VIDVIX_REST_NAMESPACE,
		'/video/(?P<video_id>[a-zA-Z0-9_-]+)/status',
		array(
			'methods'             => WP_REST_Server::READABLE,
			'permission_callback' => '__return_true',
			'callback'            => function (WP_REST_Request $request) {
				$video_id = sanitize_key($request['video_id']);
				$video    = vidvix_get_video($video_id);

				if (!$video) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'status'  => 'missing',
							'message' => __('Sorry, this video is not available for streaming right now.', 'vidvix'),
						),
						404
					);
				}

				if (!vidvix_is_video_source_available($video_id)) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'status'  => 'unavailable',
							'title'   => $video['title'],
							'message' => __('Sorry, this video is not available right now.', 'vidvix'),
						),
						200
					);
				}

				if (!vidvix_has_video_access($video_id)) {
					return new WP_REST_Response(
						array(
							'ok'          => false,
							'status'      => 'locked',
							'title'       => $video['title'],
							'price_label' => vidvix_get_price_label($video_id),
						),
						200
					);
				}

				return new WP_REST_Response(
					array(
						'ok'         => true,
						'status'     => 'unlocked',
						'title'      => $video['title'],
						'stream_url' => vidvix_get_stream_url($video_id),
					),
					200
				);
			},
		)
	);

	register_rest_route(
		VIDVIX_REST_NAMESPACE,
		'/stream/(?P<video_id>[a-zA-Z0-9_-]+)',
		array(
			'methods'             => WP_REST_Server::READABLE,
			'permission_callback' => '__return_true',
			'callback'            => function (WP_REST_Request $request) {
				$video_id = sanitize_key($request['video_id']);
				$valid    = vidvix_validate_stream_request($video_id, $request);

				if (is_wp_error($valid)) {
					return $valid;
				}

				$source = vidvix_get_video_source($video_id);

				if (!$source || !vidvix_is_video_source_available($video_id)) {
					return new WP_Error('vidvix_not_found', __('Video file was not found.', 'vidvix'), array('status' => 404));
				}

				if ('remote' === $source['type']) {
					vidvix_stream_remote_file($source['value']);
				}

				vidvix_stream_local_file($source['value']);
			},
		)
	);

	register_rest_route(
		VIDVIX_REST_NAMESPACE,
		'/paypal/create-order',
		array(
			'methods'             => WP_REST_Server::CREATABLE,
			'permission_callback' => '__return_true',
			'callback'            => function (WP_REST_Request $request) {
				$params   = $request->get_json_params();
				$video_id = isset($params['video_id']) ? sanitize_key($params['video_id']) : '';
				$video    = vidvix_get_video($video_id);
				$price    = vidvix_get_video_price($video_id);

				if (!$video || !$price || !vidvix_is_video_source_available($video_id)) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'message' => __('Video is not available for purchase.', 'vidvix'),
						),
						400
					);
				}

				$order = vidvix_paypal_api_request(
					'POST',
					'/v2/checkout/orders',
					array(
						'intent'         => 'CAPTURE',
						'purchase_units' => array(
							array(
								'custom_id'   => $video_id,
								'description' => $video['title'],
								'amount'      => array(
									'currency_code' => $price['currency'],
									'value'         => $price['value'],
								),
							),
						),
						'payment_source' => array(
							'paypal' => array(
								'experience_context' => array(
									'user_action'         => 'PAY_NOW',
									'shipping_preference' => 'NO_SHIPPING',
								),
							),
						),
					)
				);

				if (is_wp_error($order)) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'message' => __('Could not create PayPal order.', 'vidvix'),
							'error'   => $order->get_error_message(),
						),
						500
					);
				}

				return new WP_REST_Response(
					array(
						'ok'      => true,
						'orderID' => $order['id'],
					),
					200
				);
			},
		)
	);

	register_rest_route(
		VIDVIX_REST_NAMESPACE,
		'/paypal/capture-order',
		array(
			'methods'             => WP_REST_Server::CREATABLE,
			'permission_callback' => '__return_true',
			'callback'            => function (WP_REST_Request $request) {
				$params   = $request->get_json_params();
				$order_id = isset($params['orderID']) ? sanitize_text_field($params['orderID']) : '';
				$video_id = isset($params['video_id']) ? sanitize_key($params['video_id']) : '';

				if (!$order_id || !$video_id) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'message' => __('Missing order or video ID.', 'vidvix'),
						),
						400
					);
				}

				$order = vidvix_paypal_api_request('GET', '/v2/checkout/orders/' . rawurlencode($order_id));

				if (is_wp_error($order)) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'message' => __('Could not load PayPal order.', 'vidvix'),
							'error'   => $order->get_error_message(),
						),
						500
					);
				}

				$order_unit   = !empty($order['purchase_units'][0]) ? $order['purchase_units'][0] : array();
				$order_custom = isset($order_unit['custom_id']) ? $order_unit['custom_id'] : '';

				if ($order_custom !== $video_id) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'message' => __('Video mismatch in PayPal order.', 'vidvix'),
						),
						400
					);
				}

				$capture = vidvix_paypal_api_request('POST', '/v2/checkout/orders/' . rawurlencode($order_id) . '/capture');

				if (is_wp_error($capture)) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'message' => __('Could not capture PayPal order.', 'vidvix'),
							'error'   => $capture->get_error_message(),
						),
						500
					);
				}

				$status = isset($capture['status']) ? $capture['status'] : '';
				$email  = !empty($capture['payer']['email_address']) ? sanitize_email($capture['payer']['email_address']) : '';

				if ('COMPLETED' !== $status) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'message' => __('Payment was not completed.', 'vidvix'),
						),
						400
					);
				}

				$granted = vidvix_grant_video_access($video_id, $email, $order_id);

				if (!$granted) {
					return new WP_REST_Response(
						array(
							'ok'      => false,
							'message' => __('Payment succeeded but access could not be granted.', 'vidvix'),
						),
						500
					);
				}

				return new WP_REST_Response(
					array(
						'ok'         => true,
						'message'    => __('Access granted.', 'vidvix'),
						'stream_url' => vidvix_get_stream_url($video_id),
					),
					200
				);
			},
		)
	);
}
