<?php

/**
 * General helper functions.
 *
 * @link    https://aiopodcasting.com
 * @since   1.0.0
 *
 * @package AiO_Podcast_Studio
 */

// Exit if accessed directly
if ( ! defined( 'WPINC' ) ) {
	die;
}

// If your PHP version is < 8
if ( ! function_exists( 'str_contains' ) ) {
    /**
     * Polyfill for str_contains(), available natively in PHP 8.0+.
     *
     * @since  1.0.0
     * @param  string  $haystack The string to search in.
     * @param  string  $needle   The substring to search for in the haystack.
     * @return boolean           Returns true if needle is in haystack, false otherwise.
     */
    function str_contains( $haystack, $needle ) {
        return $needle !== '' && mb_strpos( $haystack, $needle ) !== false;
    }
}

/**
 * Clear season caches for a podcast and global list.
 *
 * @since 1.0.0
 * @param int   $podcast_id Podcast ID to clear. If 0, only clears global cache.
 */
function aiopod_clear_season_cache( $podcast_id = 0 ) {
	// Clear per-podcast cache
	if ( $podcast_id ) {
		wp_cache_delete( "aiopod_podcast_seasons_{$podcast_id}", 'aiopod' );
	}

	// Always clear the global cache
	wp_cache_delete( "aiopod_all_podcast_seasons", 'aiopod' );
}

/**
 * Convert a filename into a human-readable title.
 *
 * @since  1.0.0
 * @param  string $filename The file name (with or without extension).
 * @return string           A cleaned, title-cased string.
 */
function aiopod_convert_filename_to_title( $filename ) {
	// Remove extension
	$filename = pathinfo( $filename, PATHINFO_FILENAME );

	// Replace separators with spaces
	$filename = str_replace( array( '-', '_' ), ' ', $filename );

	// Trim and convert to title case
	$filename = ucwords( trim( $filename ) );

	return $filename;
}

/**
 * Converts a timestamp string into seconds.
 *
 * @since  1.0.0
 * @param  string|int $time The timestamp string.
 * @return int|string       Seconds as int, or empty string if invalid.
 */
function aiopod_convert_time_to_seconds( $time ) {
    $time = trim( (string) $time );

    if ( '' === $time ) {
        return '';
    }

    // Numeric seconds
    if ( ctype_digit( $time ) ) {
        return (int) $time;
    }

    // Validate MM:SS or HH:MM:SS
    if ( ! preg_match( '/^(\d{1,2}:)?\d{1,2}:\d{2}$/', $time ) ) {
        return '';
    }

    $parts = array_map( 'intval', explode( ':', $time ) );

    if ( 3 === count( $parts ) ) {
        return ( $parts[0] * 3600 ) + ( $parts[1] * 60 ) + $parts[2];
    }

    // MM:SS
    return ( $parts[0] * 60 ) + $parts[1];
}

/**
 * Create an attachment from an external image URL.
 *
 * @since  1.0.0
 * @param  string $image_url The external image URL.
 * @param  int    $post_id   The post to attach the image to.
 * @return int               Attachment ID on success, 0 on failure.
 */
function aiopod_create_attachment_from_external_image_url( $image_url, $post_id ) {
	if ( empty( $image_url ) || empty( $post_id ) ) {
		return 0;
	}

	$image_url = esc_url_raw( $image_url );

	// Local images are already in the media library — look them up instead of re-downloading.
	if ( 0 === strpos( $image_url, site_url() ) ) {
		$local_id = attachment_url_to_postid( $image_url );
		return $local_id ? (int) $local_id : 0;
	}

	// Prevent duplicates.
	$meta_key = 'image_' . md5( $image_url );
	$existing = get_post_meta( $post_id, $meta_key, true );

	if ( $existing && wp_attachment_is( 'image', $existing ) ) {
		return (int) $existing;
	}

	require_once ABSPATH . 'wp-admin/includes/file.php';
	require_once ABSPATH . 'wp-admin/includes/media.php';
	require_once ABSPATH . 'wp-admin/includes/image.php';

	$ua_filter = function ( $ua ) {
		$version = defined( 'AIOPOD_PLUGIN_VERSION' ) ? AIOPOD_PLUGIN_VERSION : '1.0.0';
    	return "AiO-Podcast-Studio/{$version}; " . home_url();
	};

	$args_filter = function ( $args, $url ) use ( $image_url ) {
		if ( $url === $image_url ) {
			$args['timeout'] = 10;
			$args['headers']['Accept'] = 'image/avif,image/webp,image/jpeg,image/png,image/*;q=0.8';
		}

		return $args;
	};

	add_filter( 'http_headers_useragent', $ua_filter );
	add_filter( 'http_request_args', $args_filter, 10, 2 );

	try {
		$tmp_file = download_url( $image_url, 10 );
	} finally {
		remove_filter( 'http_headers_useragent', $ua_filter );
		remove_filter( 'http_request_args', $args_filter, 10 );
	}

	if ( is_wp_error( $tmp_file ) ) {
		return 0;
	}

	// Detect real file type from binary content.
	$file_check = wp_check_filetype_and_ext( $tmp_file, 'image.jpg' );
	$ext        = $file_check['ext'] ?: 'jpg';
	$type       = $file_check['type'] ?: 'image/jpeg';
	$size       = filesize( $tmp_file );

	if ( ! $size ) {
        @unlink( $tmp_file );
        return 0;
    }

	$filename = sprintf(
		'artwork-%d-%s.%s',
		$post_id,
		substr( md5( $image_url ), 0, 8 ),
		$ext
	);

	$file_array = array(
		'name'     => $filename,
		'tmp_name' => $tmp_file,
		'type'     => $type,
		'error'    => 0,
		'size'     => $size
	);

	$attachment_id = media_handle_sideload( $file_array, $post_id );

	if ( is_wp_error( $attachment_id ) ) {
		@unlink( $tmp_file );
		return 0;
	}

	update_post_meta( $post_id, $meta_key, (int) $attachment_id );

	return (int) $attachment_id;
}

/**
 * Whether the current user has a specific capability.
 *
 * @since  1.0.0
 * @param  string $capability Capability name.
 * @param  int    $post_id    Optional. ID of the specific object to check against if
 *							  `$capability` is a "meta" cap.
 * @return bool               True if the current user has the capability, false if not.
 */
function aiopod_current_user_can( $capability, $post_id = 0 ) {
	$user_id = get_current_user_id();
	
	// If viewing a podcast or episode, check user permissions
	if ( 'view_aiopod_podcast' === $capability ) {
		$post_id = (int) $post_id;

		// Site owners and the post author have access to their own content by default
		$has_owner_access = current_user_can( 'manage_aiopod_options' );

		if ( ! $has_owner_access && $post_id > 0 && get_post( $post_id ) ) {
			$has_owner_access = aiopod_current_user_can( 'edit_aiopod_podcast', $post_id );
		}

		// An access-control integration that owns the decision for this post can drop the
		// override, so that its own rules apply to everyone the same way
		if ( apply_filters( 'aiopod_can_owner_access', $has_owner_access, $post_id ) ) {
			return true;
		}

		return apply_filters( 'aiopod_current_user_can', true, $capability, $post_id );
	}

	// If editing, deleting, or reading a podcast, get the post and post type object
	if ( 'edit_aiopod_podcast' === $capability || 'delete_aiopod_podcast' === $capability || 'read_aiopod_podcast' === $capability ) {
		$post = get_post( $post_id );
		$post_type = get_post_type_object( $post->post_type );

		// If editing a podcast, assign the required capability
		if ( 'edit_aiopod_podcast' === $capability ) {
			if ( $user_id == $post->post_author ) {
				$capability = 'edit_aiopod_podcasts';
			} else {
				$capability = 'edit_others_aiopod_podcasts';
			}
		}
		
		// If deleting a podcast, assign the required capability
		elseif ( 'delete_aiopod_podcast' === $capability ) {
			if ( $user_id == $post->post_author ) {
				$capability = 'delete_aiopod_podcasts';
			} else {
				$capability = 'delete_others_aiopod_podcasts';
			}
		}
		
		// If reading a private podcast, assign the required capability
		elseif ( 'read_aiopod_podcast' === $capability ) {
			if ( 'private' !== $post->post_status ) {
				$capability = 'read';
			} elseif ( $user_id == $post->post_author ) {
				$capability = 'read';
			} else {
				$capability = 'read_private_aiopod_podcasts';
			}
		}		
	}
		
	return current_user_can( $capability );	
}

/**
 * Delete episode attachments.
 *
 * @since 1.0.0
 * @param int   $post_id Post ID.
 */
function aiopod_delete_episode_attachments( $post_id ) {	
	$cleanup_settings = aiopod_get_option( 'aiopod_cleanup_settings' );
	
	if ( ! empty( $cleanup_settings['delete_media_files'] ) ) {
		$episode_file_id = get_post_meta( $post_id, 'episode_file_id', true );
		if ( ! empty( $episode_file_id ) ) wp_delete_attachment( $episode_file_id, true );

		$image_id = get_post_meta( $post_id, 'image_id', true );
		if ( ! empty( $image_id ) ) wp_delete_attachment( $image_id, true );
	}
}

/**
 * Delete podcast attachments.
 *
 * @since 1.0.0
 * @param int   $post_id Post ID.
 */
function aiopod_delete_podcast_attachments( $post_id ) {	
	$cleanup_settings = aiopod_get_option( 'aiopod_cleanup_settings' );
	
	if ( ! empty( $cleanup_settings['delete_media_files'] ) ) {
		$image_id = get_post_meta( $post_id, 'image_id', true );
		if ( ! empty( $image_id ) ) wp_delete_attachment( $image_id, true );
	}
}

/**
 * Delete term attachments.
 *
 * @since 1.0.0
 * @param int   $term_id Term ID.
 */
function aiopod_delete_term_attachments( $term_id ) {
	$cleanup_settings = aiopod_get_option( 'aiopod_cleanup_settings' );
	
	if ( ! empty( $cleanup_settings['delete_media_files'] ) ) {
		$image_id = get_term_meta( $term_id, 'image_id', true );
		if ( ! empty( $image_id ) ) wp_delete_attachment( $image_id, true );
	}
}

/**
 * Format duration from seconds into a human-friendly string.
 *
 * @since  1.0.0
 * @param  int    $seconds Duration in seconds.
 * @param  string $style   Format style: 'compact', 'simple' or 'player'.
 * @return string
 */
function aiopod_format_duration( $seconds, $style = 'compact' ) {
	if ( ! is_numeric( $seconds ) || $seconds <= 0 ) {
		return '';
	}

	$seconds = (int) $seconds;

	$hours   = (int) floor( $seconds / 3600 );
	$minutes = (int) floor( ( $seconds % 3600 ) / 60 );
	$secs    = (int) ( $seconds % 60 );

	$style = apply_filters( 'aiopod_duration_format', $style, $seconds );
	$show_seconds_with_hours = apply_filters( 'aiopod_duration_show_seconds_with_hours', false );

	// Player style: H:MM:SS or M:SS (used by audio/video players)
	if ( 'player' === $style ) {
		if ( $hours > 0 ) {
			return sprintf( '%02d:%02d:%02d', $hours, $minutes, $secs );
		}

		return sprintf( '%02d:%02d', $minutes, $secs );
	}

	// Compact style: 1h 2m 31s / 1m 31s / 31s
	if ( 'compact' === $style ) {
		$output = array();

		if ( $hours > 0 ) {
			$output[] = $hours . 'h';
		}

		if ( $minutes > 0 ) {
			$output[] = $minutes . 'm';
		}

		if ( $secs > 0 && ( $hours === 0 || $show_seconds_with_hours ) ) {
			$output[] = $secs . 's';
		}

		return implode( ' ', $output );
	}

	// Simple style (default): human readable, translation ready
	if ( $hours > 0 ) {
		$parts = array(
			sprintf( _n( '%d hour', '%d hours', $hours, 'aio-podcast-studio' ), $hours ),
		);

		if ( $minutes > 0 ) {
			$parts[] = sprintf( _n( '%d minute', '%d minutes', $minutes, 'aio-podcast-studio' ),	$minutes );
		}

		if ( $secs > 0 && $show_seconds_with_hours ) {
			$parts[] = sprintf(	_n( '%d second', '%d seconds', $secs, 'aio-podcast-studio' ), $secs );
		}

		return implode( ' ', $parts );
	}

	if ( $minutes > 0 ) {
		$parts = array(
			sprintf( _n( '%d minute', '%d minutes', $minutes, 'aio-podcast-studio' ),	$minutes ),
		);

		if ( $secs > 0 ) {
			$parts[] = sprintf(	_n( '%d second', '%d seconds', $secs, 'aio-podcast-studio' ), $secs );
		}

		return implode( ' ', $parts );
	}

	return sprintf(	_n( '%d second', '%d seconds', $secs, 'aio-podcast-studio' ), $secs );
}

/**
 * Format file size in human-readable form.
 *
 * @since  1.0.0
 * @param  int|float|string $bytes    File size in bytes.
 * @param  int|null         $decimals Optional. Precision of number of decimal places.
 * @return string                     Formatted file size.
 */
function aiopod_format_file_size( $bytes, $decimals = null ) {
    // 1. Validation: Ensure we have a numeric value
    if ( ! is_numeric( $bytes ) || $bytes <= 0 ) {
        return '';
    }

    $bytes = (float) $bytes;

    /**
     * Filter the base used for file size calculations.
     * 1024 = Binary (standard for servers/IT)
     * 1000 = Decimal (standard for marketing/consumer storage)
     */
    $base  = (int) apply_filters( 'aiopod_file_size_base', 1000 );
    
    // Set units based on the base to follow IEC/SI standards
    if ( 1024 === $base ) {
        $units = array( 'B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB' );
    } else {
        $units = array( 'B', 'KB', 'MB', 'GB', 'TB', 'PB' );
    }

    // 2. Accurate Calculation
    $power = floor( log( $bytes, $base ) );
    
    // Correct for floating point inaccuracies at boundaries
    if ( $bytes >= pow( $base, $power + 1 ) && $power < count( $units ) - 1 ) {
        $power++;
    }
    
    $power = (int) min( $power, count( $units ) - 1 );
    $value = $bytes / pow( $base, $power );

    // 3. Determine Decimals (Fixed Logic)
    if ( is_null( $decimals ) ) {
        // Default behavior: 0 decimals for B/KB, 1 for everything else
        $decimals = ( $power < 2 ) ? 0 : 1;
    } else {
        // If the user provided a value, clamp it between 0 and 6
        $decimals = max( 0, min( 6, (int) $decimals ) );
    }

    return sprintf(
        /* translators: 1: formatted number, 2: unit label (e.g. MiB) */
        __( '%1$s %2$s', 'aio-podcast-studio' ),
        number_format_i18n( $value, $decimals ),
        $units[ $power ]
    );
}

/**
 * Format a large integer respecting the site's Number Format setting.
 *
 * When the setting is 'short', large numbers are abbreviated (e.g. 1.2K, 3.4M).
 * When 'full' (the default), the locale-aware full number is returned instead.
 *
 * @since  1.0.0
 * @param  int    $number The raw integer value.
 * @return string         Formatted string.
 */
function aiopod_format_number( $number ) {
	$formatting_settings = aiopod_get_option( 'aiopod_formatting_settings' );

	$format = isset( $formatting_settings['number_format'] ) ? $formatting_settings['number_format'] : 'full';

	if ( 'short' === $format ) {
		if ( $number >= 1000000 ) {
			return number_format_i18n( $number / 1000000, 1 ) . 'M';
		}

		if ( $number >= 1000 ) {
			return number_format_i18n( $number / 1000, 1 ) . 'K';
		}
	}

	return number_format_i18n( $number );
}

/**
 * Get the access-denied output for a restricted podcast or episode.
 *
 * Access-control integrations hook `aiopod_access_denied_html` to replace the default
 * output with their own native markup. Only our own message is escaped here, filtered
 * HTML is returned untouched, so anything hooked to that filter is responsible for its
 * own escaping.
 *
 * @since  1.0.3
 * @param  int    $post_id Podcast or episode ID.
 * @param  string $context Context of the check, either 'podcast' or 'episode'.
 * @param  string $content Optional. Content already produced upstream, when called from
 *                         a `the_content` filter. Empty string otherwise.
 * @return string          Access-denied HTML, or an empty string.
 */
function aiopod_get_access_denied_message( $post_id, $context = 'episode', $content = '' ) {
	$post_id = (int) $post_id;
	$context = ( 'podcast' === $context ) ? 'podcast' : 'episode';

	// Default message for the requested context
	if ( 'podcast' === $context ) {
		$message = __( 'Access to this podcast is restricted.', 'aio-podcast-studio' );
	} else {
		$message = __( 'Access to this episode is restricted.', 'aio-podcast-studio' );
	}

	$message = apply_filters( 'aiopod_' . $context . '_access_denied_message', $message, $post_id );

	$html = ( ! empty( $message ) ) ? '<p>' . wp_kses_post( $message ) . '</p>' : '';

	// Integrations return their own already-escaped markup, so it is not escaped again
	return apply_filters( 'aiopod_access_denied_html', $html, $post_id, $context, $content );
}

/**
 * Retrieve and sanitize chapters for a given episode.
 *
 * @since  1.0.0
 * @param  int   $episode_id The ID of the episode post.
 * @return array             An array of sanitized chapters with time in seconds.
 */
function aiopod_get_chapters( $episode_id ) {
	$raw_chapters = get_post_meta( $episode_id, 'chapters', false );
	$chapters     = array();

	if ( is_array( $raw_chapters ) && ! empty( $raw_chapters ) ) {
		foreach ( $raw_chapters as $chapter ) {
			if ( ! is_array( $chapter ) || empty( $chapter['time'] ) || empty( $chapter['label'] ) ) {
				continue;
			}

			$seconds = aiopod_convert_time_to_seconds( $chapter['time'] );

			$chapters[ $seconds ] = array(
				'time'  => $seconds,
				'label' => sanitize_text_field( $chapter['label'] ),
			);
		}
	}

	return apply_filters( 'aiopod_get_chapters', array_values( $chapters ), $episode_id );
}

/**
 * Retrieves the current page ID.
 *
 * @since  1.0.0
 * @return int   The current page ID if found, otherwise 0.
 */
function aiopod_get_current_page_id() {
    global $wp_the_query;

    if ( isset( $wp_the_query ) && is_object( $wp_the_query ) ) {
        $post_id = $wp_the_query->get_queried_object_id();

        // Ensure it's a valid post/page ID (avoid returning term IDs)
        if ( is_singular() || is_front_page() ) {
            return $post_id;
        }
    }

    return 0; // Default to 0 if no valid page ID is found
}

/**
 * Retrieves the URL of the page currently being viewed.
 *
 * @since  1.0.0
 * @return string The current page URL.
 */
function aiopod_get_current_page_url() {
	if ( is_front_page() ) {
		$url = home_url( '/' );
	} elseif ( is_home() ) {
		$page_for_posts = (int) get_option( 'page_for_posts' );
		$url            = $page_for_posts ? get_permalink( $page_for_posts ) : home_url( '/' );
	} elseif ( is_singular() ) {
		$url = get_permalink( aiopod_get_current_page_id() );
	} else {
		global $wp;
		$url = home_url( add_query_arg( array(), $wp->request ) );
	}

	return apply_filters( 'aiopod_current_page_url', $url );
}

/**
 * Get the list of custom plugin pages.
 * 
 * @since  1.0.0
 * @return array $pages Array of pages.
 */
function aiopod_get_custom_pages_list() {
	$pages = array(
		'category'    => array( 
			'title'   => __( 'Podcast Category', 'aio-podcast-studio' ), 
			'content' => '[aiopod_category]' 
		),
		'tag'         => array( 
			'title'   => __( 'Episode Tag', 'aio-podcast-studio' ), 
			'content' => '[aiopod_tag]' 
		),
		'participant' => array( 
			'title'   => __( 'Participant', 'aio-podcast-studio' ), 
			'content' => '[aiopod_participant]' 
		)
	);

	return apply_filters( 'aiopod_custom_pages_list', $pages );
}

/**
 * Get default plugin settings.
 *
 * @since  1.0.0
 * @return array $defaults Array of plugin settings.
 */
function aiopod_get_default_settings() {
	static $defaults = null;

	if ( null !== $defaults ) {
		return $defaults;
	}
	
	$defaults = array(
		'aiopod_appearance_settings' => array(
			'primary_color'             => '#00b2ff',
			'enable_featured_images'    => 1,
			'download_external_images'  => 1,
			'image_size'                => 'medium',
			'fallback_podcast_image'    => 1,
			'hide_on_single_post_pages' => 1
		),
		'aiopod_pages_settings' => aiopod_insert_custom_pages(),
		'aiopod_taxonomies_settings' => array(
			'wp_taxonomies'            => array(),
			'include_in_core_archives' => 1
		),
		'aiopod_performance_settings' => array(
			'lazyloading'               => 1,
			'force_load_assets'         => array(),
			'maybe_flush_rewrite_rules' => 0,
			'episode_file_url_format'   => 'extension'
		),
		'aiopod_formatting_settings' => array(
			'datetime_format' => '',
			'number_format'   => 'full'
			
		),
		'aiopod_pagination_settings' => array(
			'layout'            => 'loadmore',
			'page_range'        => 2,
			'more_button_label' => __( 'Load More', 'aio-podcast-studio' )
		),
		'aiopod_cleanup_settings' => array(
			'delete_plugin_data'           => 1,
			'delete_media_files'           => 1,
			'delete_episodes_with_podcast' => 0,
			'stats_log_retention'          => ''		
		),
		'aiopod_player_settings' => array(
			'autoplay'         => 0,
			'autoadvance'      => 1,
			'loop'             => 0,
			'muted'            => 0,
			'preload'          => 'auto',
			'playsinline'      => 1,			
			'controls'         => array(
				'play'        => 'play',				
				'progress'    => 'progress', 
				'currenttime' => 'currenttime',
				'duration'    => 'duration',
				'volume'      => 'volume', 
				'speed'       => 'speed',
				'chapters'    => 'chapters',
				'download'    => 'download',
				'fullscreen'  => 'fullscreen'								
			),
			'episode_details'  => array( 
				'episode_title'  => 'episode_title',
				'podcast_title'  => 'podcast_title',
				'episode_tags'   => 'episode_tags',
				'episode_type'   => 'episode_type',
				'season_number'  => 'season_number',
				'episode_number' => 'episode_number',
				'explicit_badge' => 'explicit_badge'
			),
			'excerpt_length'   => 100,
			'social_buttons'   => array( 
				'subscribe_buttons' => 'subscribe_buttons',
				'follow_buttons'    => 'follow_buttons',
				'share_buttons'     => 'share_buttons'
			),
			'hotkeys'          => 0
		),
		'aiopod_podcasts_settings' => array(
			'show_count'      => 0,
			'columns'         => 3,
			'limit'           => 9,
			'orderby'         => 'date',
			'order'           => 'desc',
			'display_options' => array(
				'podcast_title'  => 'podcast_title',
				'episodes_count' => 'episodes_count',
				'explicit_badge' => 'explicit_badge'
			),
			'excerpt_length'  => 120,
			'pagination'      => ''
		),		
		'aiopod_podcast_settings' => array(
			'display_metadata'       => array( 
				'categories'     => 'categories',
				'episodes_count' => 'episodes_count',
				'explicit_badge' => 'explicit_badge'				
			),
			'social_buttons'         => array( 
				'subscribe_buttons' => 'subscribe_buttons',
				'follow_buttons'    => 'follow_buttons',
				'share_buttons'     => 'share_buttons'
			),
			'show_episodes'          => 1,
			'episodes_title'         => __( 'Latest Episodes', 'aio-podcast-studio' ),
			'show_trailer_episodes'  => 0,
			'trailer_episodes_title' => __( 'Trailers & More', 'aio-podcast-studio' ),
			'show_bonus_episodes'    => 0,
			'bonus_episodes_title'   => __( 'Bonus Episodes', 'aio-podcast-studio' ),
			'slides_per_view'        => 3,
			'space_between'          => 24,
			'show_dots'              => 1,
			'show_arrows'            => 1,
			'show_participants'      => 1,
			'participants_title'     => __( 'Creators and Guests', 'aio-podcast-studio' )
		),		
		'aiopod_episodes_settings' => array(
			'show_count'       => 0,
			'layout'           => 'grid',			
			'columns'          => 3,
			'limit'            => 9,
			'orderby'          => 'date',
			'order'            => 'desc',
			'display_options'  => array(
				'episode_title'  => 'episode_title',
				'podcast_title'  => 'podcast_title',
				'episode_tags'   => 'episode_tags',
				'episode_type'   => 'episode_type',
				'explicit_badge' => 'explicit_badge'				
			),
			'excerpt_length'   => 120,
			'max_height'       => 480,
			'slides_per_view'  => 3,
			'space_between'    => 24,
			'show_dots'        => 1,
			'show_arrows'      => 1,
			'enable_player'    => 1,
			'enable_filters'   => 0,
			'scroll_offset'    => 20,
			'pagination'       => ''
		),
		'aiopod_filters_settings' => array(
			'display_options' => array(
				'keyword' => 'keyword',
				'tags'    => 'tags',
				'season'  => 'season',
				'sort'    => 'sort',
				'reset'   => 'reset'
			),
			'layout'          => 'horizontal',
			'mode' 	          => 'ajax'
		),
		'aiopod_episode_settings' => array(
			'show_related_episodes'  => 1,
			'related_episodes_title' => __( 'Related Episodes', 'aio-podcast-studio' ),
			'show_participants'      => 1,
			'participants_title'     => __( 'Creators and Guests', 'aio-podcast-studio' ),
			'comments'               => 'inherit'
		),
		'aiopod_related_episodes_settings' => array(
			'columns'         => 3,
			'limit'           => 9,
			'orderby'         => 'date',
			'order'           => 'desc',
			'display_options' => array(
				'episode_title'  => 'episode_title',
				'podcast_title'  => 'podcast_title',
				'episode_tags'   => 'episode_tags',
				'episode_type'   => 'episode_type',
				'explicit_badge' => 'explicit_badge'
			),
			'excerpt_length'  => 120,
			'enable_filters'  => 0,
			'pagination'      => ''
		),
		'aiopod_categories_settings' => array(
			'layout'           => 'grid',
			'columns'          => 3,
			'limit'            => 9,
			'orderby'          => 'name',
			'order'            => 'asc',
			'hierarchical'     => 1,
			'show_name'        => 1,
			'show_description' => 0,
			'show_count'       => 1,				
			'hide_empty'       => 0,
			'pagination'       => ''
		),
		'aiopod_category_settings' => array(
			'show_breadcrumbs'       => 1,
			'show_subcategories'     => 1,			
			'subcategories_title'    => __( 'Subcategories', 'aio-podcast-studio' ),
			'subcategories_position' => 'before_podcasts',
			'show_podcasts'          => 1,
			'podcasts_title'         => __( 'Podcasts', 'aio-podcast-studio' )
		),
		'aiopod_tags_settings' => array(
			'layout'     => 'cloud',
			'orderby'    => 'name',
			'order'      => 'asc',
			'show_count' => 1,				
			'hide_empty' => 0
		),
		'aiopod_tag_settings' => array(
			'show_breadcrumbs' => 1,
			'show_episodes'    => 1,
			'episodes_title'   => __( 'Episodes', 'aio-podcast-studio' )
		),
		'aiopod_participants_settings' => array(
			'show_count'      => 0,
			'columns'         => 3,
			'limit'           => 9,
			'orderby'         => 'name',
			'order'           => 'asc',
			'display_options' => array(
				'name'           => 'name',
				'job_title'      => 'job_title',
				'podcasts_count' => 'podcasts_count',
				'episodes_count' => 'episodes_count'
			),
			'pagination'      => ''
		),
		'aiopod_participant_settings' => array(
			'show_breadcrumbs'    => 1,
			'display_metadata'    => array( 
				'job_title'      => 'job_title',
				'company_name'   => 'company_name', 
				'website_url'    => 'website_url', 
				'podcasts_count' => 'podcasts_count',
				'episodes_count' => 'episodes_count'				
			),
			'show_follow_buttons' => 1,
			'show_podcasts'       => 1,
			'podcasts_title'      => __( 'Podcasts', 'aio-podcast-studio' ),
			'show_episodes'       => 1,
			'episodes_title'      => __( 'Episodes', 'aio-podcast-studio' )
		),	
		'aiopod_seo_settings' => array(
			'open_graph_tags'  => array(
				'podcast'     => 'podcast',
				'episode'     => 'episode',
				'category'    => 'category',
				'tag'         => 'tag',
				'participant' => 'participant'
			),
			'twitter_username' => ''
		),
		'aiopod_share_buttons_settings' => array(				
			'buttons' => array( 
				'facebook'  => 'facebook',
				'twitter'   => 'twitter',				
				'linkedin'  => 'linkedin',
				'pinterest' => 'pinterest',
				'tumblr'    => 'tumblr',
				'whatsapp'  => 'whatsapp',
				'email'     => 'email'
			)	
		),
		'aiopod_privacy_settings' => array(
			'disable_cookies' => array(
				'aiopod_rand_seed' => 'aiopod_rand_seed'
			)
		)
	);

	return $defaults;
}

/**
 * Resolves the episode file type for front-end player rendering.
 *
 * @since  1.0.0
 * @param  int|string $post_id_or_type Episode post ID (int) or direct MIME type (string).
 * @param  string     $url             Episode media URL (optional).
 * @return string                      Resolved file type (e.g. video/mp4, audio/mpeg, video/youtube).
 */
function aiopod_get_episode_file_type( $post_id_or_type, $url = '' ) {
    // If a non-empty MIME type string is passed directly, return it
    if ( ! empty( $post_id_or_type ) && is_string( $post_id_or_type ) && ! is_numeric( $post_id_or_type ) ) {
        return $post_id_or_type;
    }

    // Otherwise, treat as post ID
    $post_id = absint( $post_id_or_type );
    
    // Use stored type if available (canonical source)
    if ( $post_id > 0 ) {
        $type = get_post_meta( $post_id, 'episode_file_type', true );
        if ( ! empty( $type ) ) {
            return $type;
        }
    }

    // No stored type → fallback to URL-based detection
    if ( empty( $url ) ) {
        return '';
    }

    // External video providers
    if ( preg_match( '#(youtube\.com|youtu\.be)#i', $url ) ) {
        return 'video/youtube';
    }

    if ( preg_match( '#vimeo\.com#i', $url ) ) {
        return 'video/vimeo';
    }

    // Extract file extension
    $path = wp_parse_url( $url, PHP_URL_PATH );
    $ext  = strtolower( pathinfo( $path, PATHINFO_EXTENSION ) );

    // Streaming formats
    if ( 'm3u8' === $ext ) { // HLS
        return 'application/x-mpegURL';
    }

    if ( 'mpd' === $ext ) { // DASH
        return 'application/dash+xml';
    }

    // Audio formats
    $audio_exts = [ 'mp3', 'm4a', 'aac', 'ogg', 'wav', 'flac' ];
    if ( in_array( $ext, $audio_exts, true ) ) {
        return ( 'mp3' === $ext ) ? 'audio/mpeg' : 'audio/' . $ext;
    }

    // Video formats
    $video_exts = [ 'mp4', 'm4v', 'webm', 'ogv', 'mov' ];
    if ( in_array( $ext, $video_exts, true ) ) {
        return 'video/' . $ext;
    }

    return '';
}

/**
 * Get the podcast an episode belongs to.
 *
 * An episode stores its podcast in `post_parent`. This is the plain relationship — unlike
 * `aiopod_get_inherited_podcast_id()`, which answers the narrower question of where an episode
 * inherits its access restrictions from and can be switched off per episode by a filter.
 *
 * @since  1.0.4
 * @param  int $episode_id Episode ID to check.
 * @return int             Podcast ID, or 0 when the episode has no published podcast.
 */
function aiopod_get_episode_podcast_id( $episode_id ) {
	$episode = get_post( $episode_id );

	if ( ! $episode || 'aiopod_episodes' !== $episode->post_type ) {
		return 0;
	}

	$podcast_id = (int) $episode->post_parent;

	if ( $podcast_id <= 0 ) {
		return 0;
	}

	$podcast = get_post( $podcast_id );

	if ( ! $podcast || 'aiopod_podcasts' !== $podcast->post_type || 'publish' !== $podcast->post_status ) {
		return 0;
	}

	return $podcast_id;
}

/**
 * Get the episode type for a given episode.
 *
 * @since  1.0.0
 * @param  int    $post_id Post ID.
 * @return string          Episode type label.
 */
function aiopod_get_episode_type( $post_id ) {
	$types = aiopod_get_episode_types();

	$type = get_post_meta( $post_id, 'episode_type', true );
	if ( empty( $type ) || ! in_array( $type, array_keys( $types ), true ) ) {
		$type = 'full';
	}

	return $types[ $type ];
}

/**
 * Get predefined episode types for selection.
 *
 * @since  1.0.0
 * @return array Array of episode types with slugs as keys and translated labels as values.
 */
function aiopod_get_episode_types() {
	$options = array(
		'full'    => __( 'Full', 'aio-podcast-studio' ),
		'trailer' => __( 'Trailer', 'aio-podcast-studio' ),
		'bonus'   => __( 'Bonus', 'aio-podcast-studio' )
	);

	return apply_filters( 'aiopod_get_episode_types', $options );
}

/**
 * Get the podcast excerpt.
 *
 * @since  1.0.0
 * @param  int    $post_id     Post ID.
 * @param  int    $char_length Excerpt length.
 * @param  string $append      String to append to the end of the excerpt.
 * @param  bool   $allow_html  Allow HTML in the excerpt.
 * @return string $content     Excerpt content.
 */
function aiopod_get_excerpt( $post_id = 0, $char_length = 55, $append = '...', $allow_html = true ) {
	$content = '';

	if ( $post_id > 0 ) {
		$post = get_post( $post_id );
	} else {
		global $post;
	}

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

	if ( ! empty( $post->post_excerpt ) ) {
		$content = $allow_html ? $post->post_excerpt : wp_strip_all_tags( $post->post_excerpt, true );
	} elseif ( ! empty( $post->post_content ) ) {
		$raw_content = $allow_html ? $post->post_content : wp_strip_all_tags( $post->post_content, true );

		if ( 0 === $char_length ) {
			$content = $raw_content;
		} else {
			$excerpt = wp_strip_all_tags( $raw_content, true );

			if ( mb_strlen( $excerpt ) > $char_length ) {
				$content = mb_substr( $excerpt, 0, $char_length );

				// Trim incomplete last word (only if length is reasonable)
				if ( $char_length > 3 ) {
					$content = preg_replace( '/\s+\S*$/u', '', $content );
				}

				$content .= $append;
			} else {
				$content = $excerpt;
			}
		}
	}

	$content = trim( $content );
	return apply_filters( 'aiopod_excerpt', $content, $post_id, $char_length, $append, $allow_html );	
}

/**
 * Get the sorting options for the filters form.
 * 
 * @since  1.0.0
 * @return array $options Array of options.
 */
function aiopod_get_filters_form_sort_options() {
	$options = array(
		'title-asc'       => __( 'Title - Ascending', 'aio-podcast-studio' ),
		'title-desc'      => __( 'Title - Descending', 'aio-podcast-studio' ),
		'date-desc'       => __( 'Newest First', 'aio-podcast-studio' ),
		'date-asc'        => __( 'Oldest First', 'aio-podcast-studio' ),
		'popularity-desc' => __( 'Most Popular', 'aio-podcast-studio' ),
		'downloads-desc'  => __( 'Most Downloaded', 'aio-podcast-studio' ),
		'plays-desc'      => __( 'Most Played', 'aio-podcast-studio' )
	);

	return apply_filters( 'aiopod_filters_form_sort_options', $options );
}

/**
 * Retrieve the Follow Us buttons for a given post or taxonomy term.
 *
 * @since  1.0.0
 * @param  int    $object_id   Post ID or Term ID.
 * @param  string $object_type Optional. 'post' or 'term'. Default 'post'.
 * @return array               Array of follow button data.
 */
function aiopod_get_follow_buttons( $object_id, $object_type = 'post' ) {
    $object_id   = (int) $object_id;
    $object_type = ( 'term' === $object_type ) ? 'term' : 'post';

    if ( $object_id <= 0 ) {
        return array();
    }

    if ( 'term' === $object_type ) {
		$object_meta = get_term_meta( $object_id );		
    } else {
        $object_meta = get_post_meta( $object_id );	
    }

	$follow_buttons = array();

	// Facebook
	$facebook_url = isset( $object_meta['facebook_url'] ) ? $object_meta['facebook_url'][0] : '';

	if ( ! empty( $facebook_url ) ) {
		$follow_buttons[] = array(
			'label' => __( 'Facebook', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-facebook',
			'url'   => $facebook_url		
		);
	} 

	// Twitter
    $twitter_url = isset( $object_meta['twitter_url'] ) ? $object_meta['twitter_url'][0] : '';

	if ( ! empty( $twitter_url ) ) {
		$follow_buttons[] = array(
			'label' => __( 'Twitter / X', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-twitter',
			'url'   => $twitter_url
		);
	} 

	// Instagram
    $instagram_url = isset( $object_meta['instagram_url'] ) ? $object_meta['instagram_url'][0] : '';

	if ( ! empty( $instagram_url ) ) {
		$follow_buttons[] = array(
			'label' => __( 'Instagram', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-instagram',
			'url'   => $instagram_url	
		);
	} 

	// Linkedin
    $linkedin_url = isset( $object_meta['linkedin_url'] ) ? $object_meta['linkedin_url'][0] : '';

	if ( ! empty( $linkedin_url ) ) {
		$follow_buttons[] = array(
			'label' => __( 'LinkedIn', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-linkedin',
			'url'   => $linkedin_url		
		);
	} 

	// YouTube
    $youtube_url = isset( $object_meta['youtube_url'] ) ? $object_meta['youtube_url'][0] : '';

	if ( ! empty( $youtube_url ) ) {
		$follow_buttons[] = array(
			'label' => __( 'YouTube', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-youtube',
			'url'   => $youtube_url		
		);
	} 

    return apply_filters( 'aiopod_get_follow_buttons', $follow_buttons, $object_id, $object_type );
}

/**
 * Resolve image source and alt text for a post or taxonomy term.
 *
 * @since  1.0.0
 * @param  array $args Array of image args.
 * @return array { src, alt }
 */
function aiopod_get_image( $args = array() ) {
	$appearance_settings = aiopod_get_option( 'aiopod_appearance_settings' );

	$defaults = array(
		'post'          => null,
		'post_id'       => 0,
		'term'          => null,
		'term_id'       => 0,
		'taxonomy'      => '',
		'attachment_id' => 0,
		'size'          => '',
		'src'           => '',
		'alt'           => null,
		'placeholder'   => '',
	);

	$args = wp_parse_args( $args, $defaults );

	$post          = $args['post'];
	$post_id       = ( $post instanceof WP_Post ) ? (int) $post->ID : (int) $args['post_id'];
	$term          = $args['term'];
	$term_id       = ( $term instanceof WP_Term ) ? (int) $term->term_id : (int) $args['term_id'];
	$taxonomy      = $args['taxonomy'];
	$attachment_id = (int) $args['attachment_id'];
	$size          = $args['size'];
	$src           = $args['src'];
	$alt           = $args['alt'];
	$placeholder   = $args['placeholder'];

	// Default image size
	if ( empty( $size ) ) {
		$size = ! empty( $appearance_settings['image_size'] ) ? sanitize_text_field( $appearance_settings['image_size'] ) : 'full';
	}

	// Explicit attachment ID (highest priority)
	if ( $attachment_id > 0 ) {
		if ( $image = wp_get_attachment_image_src( $attachment_id, $size ) ) {
			$src = $image[0];
		}

		if ( null === $alt ) {
			$alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
			$alt = ( '' === $alt ) ? null : $alt;
		}
	}

	// Post or term meta
	if ( empty( $src ) ) {
		if ( $post_id > 0 ) {
			// Prefer the attachment ID so the configured image size can be applied (resolved
			// below). The stored URL is read as a fallback for when no attachment is available.
			$attachment_id = (int) get_post_meta( $post_id, 'image_id', true );
			$src           = get_post_meta( $post_id, 'image', true );

			// Fallback to the featured image if available
			if ( empty( $attachment_id ) && empty( $src ) ) {
				$attachment_id = (int) get_post_meta( $post_id, '_thumbnail_id', true );
			}

			// Fallback to the post parent if available (podcast)
			if ( empty( $attachment_id ) && empty( $src ) && ! empty( $appearance_settings['fallback_podcast_image'] ) ) {
				if ( ! $post instanceof WP_Post ) {
					$post = get_post( $post_id );
				}				

				if ( $post && $post->post_parent > 0 ) {
					$attachment_id = (int) get_post_meta( $post->post_parent, 'image_id', true );
					$src           = get_post_meta( $post->post_parent, 'image', true );

					// Fallback to the post parent featured image if available
					if ( empty( $attachment_id ) && empty( $src ) ) {
						$attachment_id = (int) get_post_meta( $post->post_parent, '_thumbnail_id', true );
					}
				}
			}
		} elseif ( $term_id > 0 ) {
			$attachment_id = (int) get_term_meta( $term_id, 'image_id', true );
			$src           = get_term_meta( $term_id, 'image', true );
		}

		if ( $attachment_id > 0 ) {
			if ( $image = wp_get_attachment_image_src( $attachment_id, $size ) ) {
				$src = $image[0];
			}

			if ( null === $alt ) {
				$alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
				$alt = ( '' === $alt ) ? null : $alt;
			}
		}
	}

	// Fallback alt
	if ( null === $alt ) {
		if ( $post_id > 0 ) {
			if ( ! $post instanceof WP_Post ) {
				$post = get_post( $post_id );
			}	

			if ( $post ) {
				$alt = $post->post_title;
			}
		} elseif ( $term_id > 0 && $taxonomy ) {
			if ( ! $term instanceof WP_Term ) {
				$term = get_term( $term_id, $taxonomy );
			}
			
			if ( $term && ! is_wp_error( $term ) ) {
				$alt = $term->name;
			}
		}
	}

	// Placeholder
	if ( empty( $src ) && ! empty( $placeholder ) ) {
		$src = $placeholder;
	}

	// Return the constructed image array
	$image = array(
		'src'         => $src,
		'alt'         => (string) $alt,
		'placeholder' => $placeholder
	);

	return apply_filters( 'aiopod_get_image', $image, $args );
}

/**
 * Get a concise import statistics summary for a podcast.
 *
 * Displays only the latest import result and the next scheduled
 * import time. If the last import resulted in an error, only the
 * error message is shown.
 *
 * @since  1.0.0
 * @param  int    $podcast_id The podcast post ID.
 * @return string             Formatted import statistics text.
 */
function aiopod_get_import_statistics_text( $podcast_id ) {
	if ( empty( $podcast_id ) ) {
		return '';
	}

	$logs          = get_post_meta( $podcast_id, 'import_logs' );
	$next_schedule = get_post_meta( $podcast_id, 'import_next_schedule', true );

	$lines = array();

	// Latest Import
	if ( ! empty( $logs ) ) {
		$latest   = end( $logs );
		$is_error = ! empty( $latest['error'] );

		$lines[] = sprintf(
			'%s: %s',
			esc_html__( 'Status', 'aio-podcast-studio' ),
			$is_error ? esc_html__( 'Error', 'aio-podcast-studio' ) : esc_html__( 'Completed', 'aio-podcast-studio' )
		);

		$lines[] = sprintf(
			'%s: %s',
			esc_html__( 'Last Import', 'aio-podcast-studio' ),
			wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $latest['date'] ) )
		);

		$lines[] = '';

		if ( $is_error ) {
			$lines[] = sprintf(
				'%s: %s',
				esc_html__( 'Error Message', 'aio-podcast-studio' ),
				sanitize_text_field( $latest['error'] )
			);
		} else {
			$labels = array(
				'imported' => esc_html__( 'Imported', 'aio-podcast-studio' ),
				'updated'  => esc_html__( 'Updated', 'aio-podcast-studio' ),
				'skipped'  => esc_html__( 'Skipped (Duplicates)', 'aio-podcast-studio' ),
				'failed'   => esc_html__( 'Failed', 'aio-podcast-studio' )
			);

			foreach ( $labels as $key => $label ) {
				if ( isset( $latest[ $key ] ) ) {
					$lines[] = sprintf(
						'%s: %d',
						$label,
						(int) $latest[ $key ]
					);
				}
			}
		}
	} else {
		$lines[] = esc_html__( 'No imports have been run yet.', 'aio-podcast-studio' );
	}

	// Next Scheduled Import
	$lines[] = '';

	if ( ! empty( $next_schedule ) && strtotime( $next_schedule ) ) {
		$lines[] = sprintf(
			'%s: %s',
			esc_html__( 'Next Scheduled Import', 'aio-podcast-studio' ),
			wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), strtotime( $next_schedule )	)
		);
	} else {
		$lines[] = sprintf(
			'%s: %s',
			esc_html__( 'Next Scheduled Import', 'aio-podcast-studio' ),
			esc_html__( 'Not scheduled', 'aio-podcast-studio' )
		);
	}

	return implode( "\n", $lines );
}

/**
 * Get the podcast an episode inherits its access restrictions from.
 *
 * Restricting a podcast restricts the episodes it contains, so that a show can be closed
 * off without editing every episode, and so that episodes added later by an RSS import or
 * a scheduled sync are covered without anyone having to remember to restrict them.
 *
 * Shared by every access-control mechanism so they all resolve inheritance the same way.
 * An episode carrying a restriction of its own is not affected, the more specific setting
 * is always the one that applies.
 *
 * Not to be confused with `aiopod_get_episode_podcast_id()`, which answers the plain question
 * of which podcast an episode belongs to. This one is deliberately laxer about status — a
 * restricted podcast still in draft must go on protecting its published episodes — and it
 * honours the filter below. Do not merge the two.
 *
 * @since  1.0.3
 * @param  int $episode_id Episode ID to check.
 * @return int             Podcast ID to inherit from, or 0 when inheritance does not apply.
 */
function aiopod_get_inherited_podcast_id( $episode_id ) {
	$episode = get_post( $episode_id );

	if ( ! $episode || 'aiopod_episodes' !== $episode->post_type ) {
		return 0;
	}

	$podcast_id = (int) $episode->post_parent;

	if ( $podcast_id <= 0 ) {
		return 0;
	}

	$podcast = get_post( $podcast_id );

	if ( ! $podcast || 'aiopod_podcasts' !== $podcast->post_type || 'trash' === $podcast->post_status ) {
		return 0;
	}

	/**
	 * Filter whether an episode inherits the access restrictions of its podcast.
	 *
	 * @since 1.0.3
	 * @param bool $inherit    Whether to inherit. Default true.
	 * @param int  $episode_id ID of the episode being checked.
	 */
	if ( ! apply_filters( 'aiopod_inherit_podcast_access', true, (int) $episode->ID ) ) {
		return 0;
	}

	return $podcast_id;
}

/**
 * Retrieve a plugin option with fallback to default settings.
 *
 * @since  1.0.0
 * @param  string $option The option name to retrieve.
 * @return array          The option value from the database or the default value.
 */
function aiopod_get_option( $option ) {
	$settings = get_option( $option );

	if ( ! is_array( $settings ) || empty( $settings ) ) {
		$defaults = aiopod_get_default_settings();
		$settings = isset( $defaults[ $option ] ) ? $defaults[ $option ] : array();
	}

	return $settings;
}

/**
 * Get MySQL's RAND function seed value.
 * 
 * @since  1.0.0
 * @return string $seed Seed value.
 */
function aiopod_get_orderby_rand_seed( $paged = 'no_longer_required' ) {
	$seed = wp_rand( 1, 999999 );

	if ( isset( $_COOKIE['aiopod_rand_seed'] ) ) {
		$seed = absint( $_COOKIE['aiopod_rand_seed'] );
	}

	return $seed;
}

/**
 * Retrieves a page given its title.
 *
 * @since  1.0.0
 * @param  string       $page_title Page title.
 * @param  string       $output     The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond
 *                                  to a WP_Post object, an associative array, or a numeric array, respectively.
 * @param  string|array $post_type  Optional. Post type or array of post types. Default 'page'.
 * @return WP_Post|array|null       WP_Post (or array) on success, or null on failure.
 */
function aiopod_get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
	global $wpdb;

	if ( is_array( $post_type ) ) {
		$placeholders = implode( ', ', array_fill( 0, count( $post_type ), '%s' ) );
		$prepare_args = array_merge( array( $page_title ), $post_type );
		$sql          = $wpdb->prepare(
			"SELECT ID
			FROM $wpdb->posts
			WHERE post_title = %s
			AND post_type IN ($placeholders)",
			$prepare_args
		);
	} else {
		$sql = $wpdb->prepare(
			"SELECT ID
			FROM $wpdb->posts
			WHERE post_title = %s
			AND post_type = %s",
			$page_title,
			$post_type
		);
	}

	$page = $wpdb->get_var( $sql );

	if ( $page ) {
		return get_post( $page, $output );
	}

	return null;
}

/**
 * Get current page number.
 *
 * @since  1.0.0
 * @return int   $paged The current page number.
 */
function aiopod_get_page_number() {
	global $paged;
	
	if ( get_query_var( 'paged' ) ) {
    	$paged = get_query_var( 'paged' );
	} elseif ( get_query_var( 'page' ) ) {
    	$paged = get_query_var( 'page' );
	} else {
		$paged = 1;
	}
    	
	return absint( $paged );		
}

/**
 * Get the count of posts of a given post type associated with a participant.
 *
 * - Participants are stored as a taxonomy (aiopod_participants)
 * - For episodes, excludes trailers and bonus episodes
 *
 * @since  1.0.0
 * @param  int    $participant_id Participant term ID.
 * @param  string $post_type      Custom post type name.
 * @return int                    Number of matching posts.
 */
function aiopod_get_participant_posts_count( $participant_id, $post_type ) {
    $participant_id = (int) $participant_id;
    $post_type      = sanitize_key( $post_type );

    if ( $participant_id <= 0 || empty( $post_type ) ) {
        return 0;
    }

    // Post statuses
    $post_status = is_admin() ? array( 'publish', 'draft', 'private', 'pending' ) : array( 'publish' );

    $args = array(
        'post_type'              => $post_type,
        'post_status'            => $post_status,
        'posts_per_page'         => 1,      // We only need the count
        'fields'                 => 'ids',
        'no_found_rows'          => false,  // Required for found_posts
        'update_post_meta_cache' => false,
        'update_post_term_cache' => false,
        'tax_query'              => array(
            array(
                'taxonomy' => 'aiopod_participants',
                'field'    => 'term_id',
                'terms'    => $participant_id
            )
        )
    );

    $query = new WP_Query( $args );

    return (int) $query->found_posts;
}

/**
 * Get the list of participant roles.
 *
 * @since  1.0.0
 * @return array Array of participant roles.
 */
function aiopod_get_participant_roles() {
	$options = array(
		__( 'Host', 'aio-podcast-studio' ),
		__( 'Co-Host', 'aio-podcast-studio' ),
		__( 'Guest', 'aio-podcast-studio' ),
		__( 'Producer', 'aio-podcast-studio' ),
		__( 'Director', 'aio-podcast-studio' ),
		__( 'Narrator', 'aio-podcast-studio' ),
		__( 'Author', 'aio-podcast-studio' ),
		__( 'Writer', 'aio-podcast-studio' ),
		__( 'Editor', 'aio-podcast-studio' ),
		__( 'Engineer', 'aio-podcast-studio' ),
		__( 'Composer', 'aio-podcast-studio' ),
		__( 'Donor', 'aio-podcast-studio' ),
		__( 'Contributor', 'aio-podcast-studio' ),
		__( 'Custom', 'aio-podcast-studio' )
	);

	return apply_filters( 'aiopod_get_participant_roles', $options );
}

/**
 * Retrieve a list of all available participants.
 *
 * @since  1.0.0
 * @return array List of participant terms.
 */
function aiopod_get_participants() {
    $terms = get_terms( array(
        'taxonomy'   => 'aiopod_participants',
        'hide_empty' => false,
        'orderby'    => 'name',
        'order'      => 'ASC',
        'fields'     => 'all'
    ) );

    if ( is_wp_error( $terms ) || empty( $terms ) ) {
        return array();
    }

    $result = array();

    foreach ( $terms as $term ) {
        $result[ $term->term_id ] = $term->name;
    }

    return $result;
}

/**
 * Get the count of episodes linked to a given podcast.
 *
 * @since  1.0.0
 * @param  int $podcast_id The podcast post ID.
 * @return int             Number of episodes linked to this podcast.
 */
function aiopod_get_podcast_episodes_count( $podcast_id ) {
	global $wpdb;

	$podcast_id = (int) $podcast_id;

	if ( $podcast_id <= 0 ) {
		return 0;
	}

	// Post statuses
	$statuses     = is_admin() ? array( 'publish', 'draft', 'private', 'pending' ) : array( 'publish' );
	$placeholders = implode( ', ', array_fill( 0, count( $statuses ), '%s' ) );
	$prepare_args = array_merge( array( $podcast_id ), $statuses );

	$sql = $wpdb->prepare(
		"
		SELECT COUNT(ID)
		FROM {$wpdb->posts}
		WHERE post_type = 'aiopod_episodes'
		  AND post_parent = %d
		  AND post_status IN ($placeholders)
		",
		$prepare_args
	);

	return (int) $wpdb->get_var( $sql );
}

/**
 * Retrieve participants for a given post (podcast or episode).
 *
 * - Participants are stored as taxonomy terms
 * - Roles are stored as post meta
 *
 * @since  1.0.0
 * @param  int   $post_id The ID of the post.
 * @return array          An array of participants.
 */
function aiopod_get_post_participants( $post_id ) {
    $post_id = (int) $post_id;

    if ( $post_id <= 0 ) {
        return array();
    }

    // Participant roles stored as post meta (contextual)
    $raw_participants = get_post_meta( $post_id, 'participants', false );
    $participants     = array();

    if ( empty( $raw_participants ) || ! is_array( $raw_participants ) ) {
        return apply_filters( 'aiopod_get_participants', $participants, $post_id );
    }

    foreach ( $raw_participants as $participant ) {
        if ( ! is_array( $participant ) || empty( $participant['id'] ) || empty( $participant['role'] ) ) {
            continue;
        }

        $term_id = (int) $participant['id'];
        $role    = sanitize_text_field( $participant['role'] );

        // Get participant term
        $term = get_term( $term_id, 'aiopod_participants' );

        if ( ! $term || is_wp_error( $term ) ) {
            continue;
        }

        // Get participant image (term meta–based)
        $image = aiopod_get_image( array( 
			'term'        => $term,
			'taxonomy'    => 'aiopod_participants', 
			'placeholder' => AIOPOD_PLUGIN_PLACEHOLDER_AVATAR_URL  
		) );

        $participants[] = array(
            'id'    => $term_id,
            'name'  => $term->name,
            'url'   => aiopod_get_participant_page_url( $term ),
            'role'  => $role,
            'image' => $image
        );
    }

    return apply_filters( 'aiopod_get_post_participants', $participants, $post_id );
}

/**
 * Get unique seasons for a podcast or across all podcasts.
 *
 * @since  1.0.0
 * @param  int   $podcast_id Podcast post ID. If 0, returns seasons for all podcasts.
 * @return array             List of unique season numbers (sorted).
 */
function aiopod_get_seasons( $podcast_id = 0 ) {
	global $wpdb;

	$podcast_id = (int) $podcast_id;
	$cache_key  = $podcast_id ? 'aiopod_podcast_seasons_' . $podcast_id : 'aiopod_all_podcast_seasons';

	// Try cache first
	$seasons = wp_cache_get( $cache_key, 'aiopod' );
	if ( false !== $seasons ) {
		return $seasons;
	}

	if ( $podcast_id > 0 ) {
		// Seasons for a single podcast
		$sql = $wpdb->prepare(
			"
			SELECT DISTINCT pm.meta_value
			FROM {$wpdb->posts} p
			INNER JOIN {$wpdb->postmeta} pm
				ON pm.post_id = p.ID
				AND pm.meta_key = 'season_number'
			WHERE p.post_type = 'aiopod_episodes'
			  AND p.post_status = 'publish'
			  AND p.post_parent = %d
			ORDER BY CAST(pm.meta_value AS UNSIGNED) ASC
			",
			$podcast_id
		);

		$seasons = $wpdb->get_col( $sql );
	} else {
		// Seasons across all podcasts
		$seasons = $wpdb->get_col(
			"
			SELECT DISTINCT pm.meta_value
			FROM {$wpdb->posts} p
			INNER JOIN {$wpdb->postmeta} pm
				ON pm.post_id = p.ID
				AND pm.meta_key = 'season_number'
			WHERE p.post_type = 'aiopod_episodes'
			  AND p.post_status = 'publish'
			ORDER BY CAST(pm.meta_value AS UNSIGNED) ASC
			"	
		);
	}

	// Sanitize to integers, filter empties
	$seasons = array_values( array_filter( array_map( 'intval', (array) $seasons ) ) );
	$seasons = array_unique( $seasons );

	// Cache result
	wp_cache_set( $cache_key, $seasons, 'aiopod' );

	return $seasons;
}

/**
 * Retrieve the Share buttons for a given post (podcast or episode).
 *
 * @since  1.0.0
 * @param  int   $post_id The ID of the post.
 * @return array          Array of share button data.
 */
function aiopod_get_share_buttons( $post_id ) {
	$share_buttons_settings = aiopod_get_option( 'aiopod_share_buttons_settings' );

	if ( empty( $share_buttons_settings['buttons'] ) ) {
		return array();
	}

	$url         = get_permalink( $post_id );
	$title_raw   = get_the_title( $post_id );
	$title       = rawurlencode( $title_raw );
	$description = aiopod_get_excerpt( $post_id, 160, '', false );
	$image       = aiopod_get_image( array( 'post_id' => $post_id, 'alt' => '' ) );

	$share_buttons = array();

	if ( isset( $share_buttons_settings['buttons']['facebook'] ) ) {
		$share_buttons[] = array(
			'label' => __( 'Facebook', 'aio-podcast-studio' ),			
			'icon'  => 'aiopod-icon-facebook',
			'url'   => "https://www.facebook.com/sharer/sharer.php?u={$url}"
		);
	}

	if ( isset( $share_buttons_settings['buttons']['twitter'] ) ) {
		$share_buttons[] = array(
			'label' => __( 'Twitter / X', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-twitter',
			'url'   => "https://twitter.com/intent/tweet?text={$title}&url={$url}"
		);
	}

	if ( isset( $share_buttons_settings['buttons']['linkedin'] ) ) {
		$share_buttons[] = array(
			'label' => __( 'LinkedIn', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-linkedin',
			'url'   => "https://www.linkedin.com/shareArticle?url={$url}&title={$title}"
		);
	}

	if ( isset( $share_buttons_settings['buttons']['pinterest'] ) ) {
		$pinterest_url = "https://pinterest.com/pin/create/button/?url={$url}&description={$title}";
		if ( ! empty( $image ) && ! empty( $image['src'] ) ) {
			$pinterest_url .= '&media=' . rawurlencode( $image['src'] );
		}

		$share_buttons[] = array(
			'label' => __( 'Pin It', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-pinterest',
			'url'   => $pinterest_url			
		);
	}
	
	if ( isset( $share_buttons_settings['buttons']['tumblr'] ) ) {
		$tumblr_url = "https://www.tumblr.com/share/link?url={$url}&name={$title}";
		if ( ! empty( $description ) ) {
			$tumblr_url .= '&description=' . rawurlencode( $description );
		}

		$share_buttons[] = array(
			'label' => __( 'Tumblr', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-tumblr',
			'url'   => $tumblr_url			
		);
	}

	if ( isset( $share_buttons_settings['buttons']['whatsapp'] ) ) {
		$whatsapp_url = wp_is_mobile() ? "whatsapp://send?text={$title} " . rawurlencode( $url ) : "https://api.whatsapp.com/send?text={$title}%20" . rawurlencode( $url );

		$share_buttons[] = array(
			'label' => __( 'WhatsApp', 'aio-podcast-studio' ),			
			'icon'  => 'aiopod-icon-whatsapp',
			'url'   => $whatsapp_url
		);
	}

	if ( isset( $share_buttons_settings['buttons']['email'] ) ) {
		$email_subject = rawurlencode( sprintf( __( 'Check out the "%s"', 'aio-podcast-studio' ), $title_raw ) );
		$email_body    = rawurlencode( sprintf( __( 'Check out the "%s" at %s', 'aio-podcast-studio' ), $title_raw, $url ) );
		$email_url     = "mailto:?subject={$email_subject}&body={$email_body}";

		$share_buttons[] = array(
			'label' => __( 'Email', 'aio-podcast-studio' ),
			'icon'  => 'aiopod-icon-email',
			'url'   => $email_url
		);
	}

	return apply_filters( 'aiopod_get_share_buttons', $share_buttons, $post_id );
}

/**
 * Get shortcode builder form fields.
 *
 * @since 1.0.0
 */
function aiopod_get_shortcode_fields() {
	$defaults = aiopod_get_default_settings();
	
	$podcasts_settings     = aiopod_get_option( 'aiopod_podcasts_settings' );
	$episodes_settings     = aiopod_get_option( 'aiopod_episodes_settings' );
	$categories_settings   = aiopod_get_option( 'aiopod_categories_settings' );
	$tags_settings         = aiopod_get_option( 'aiopod_tags_settings' );
	$participants_settings = aiopod_get_option( 'aiopod_participants_settings' );
	$player_settings       = aiopod_get_option( 'aiopod_player_settings' );
	$filters_settings      = aiopod_get_option( 'aiopod_filters_settings' );
	
	// Fields	
	$fields = array(
		'podcasts' => array(
			'title'    => __( 'Podcasts List', 'aio-podcast-studio' ),
			'sections' => array(
				'source' => array(
					'title'  => __( 'Content Source', 'aio-podcast-studio' ),
					'fields' => array(
						array( 
							'name'        => 'categories',
							'label'       => __( 'Select Categories', 'aio-podcast-studio' ),
							'description' => __( 'Select one or more categories to filter the displayed podcasts. Leave empty to show podcasts from all categories.', 'aio-podcast-studio' ),
							'type'        => 'categories',
							'value'       => array()
						),
						array( 
							'name'        => 'participants',
							'label'       => __( 'Select Participants', 'aio-podcast-studio' ),
							'description' => __( 'Select one or more participants to filter the displayed podcasts. Leave empty to show podcasts from all participants.', 'aio-podcast-studio' ),
							'type'        => 'participants',
							'value'       => array()
						)
					)
				),
				'display' => array(
					'title'  => __( 'Display Options', 'aio-podcast-studio' ),
					'fields' => array(
						array(
							'name'        => 'show_count',
							'label'       => __( 'Display Total Podcasts', 'aio-podcast-studio' ),
							'description' => __( 'Show the total number of podcasts above the list.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $podcasts_settings['show_count']
						),
						array(
							'name'        => 'columns',
							'label'       => __( 'Columns', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of columns to display in the list.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'min'         => 1,
							'max'         => 12,
							'step'        => 1,
							'value'       => $podcasts_settings['columns']
						),
						array(
							'name'        => 'limit',
							'label'       => __( 'Limit (per page)', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of podcasts to display per page. Leave blank for the default value (100).', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => $podcasts_settings['limit']
						),
						array(
							'name'        => 'orderby',
							'label'       => __( 'Order By', 'aio-podcast-studio' ),
							'description' => __( 'Select how podcasts are sorted.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'title'      => __( 'Title', 'aio-podcast-studio' ),
								'date'       => __( 'Date Added', 'aio-podcast-studio' ),
								'popularity' => __( 'Popularity (Plays + Downloads)', 'aio-podcast-studio' ),
								'downloads'  => __( 'Downloads', 'aio-podcast-studio' ),
								'plays'      => __( 'Plays', 'aio-podcast-studio' ),
								'rand'       => __( 'Random', 'aio-podcast-studio' ),
								'menu_order' => __( 'Menu Order', 'aio-podcast-studio' )
							),
							'value'       => $podcasts_settings['orderby']
						),
						array(
							'name'        => 'order',
							'label'       => __( 'Order Direction', 'aio-podcast-studio' ),
							'description' => __( 'Choose ascending or descending order.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'asc'  => __( 'Ascending', 'aio-podcast-studio' ),
								'desc' => __( 'Descending', 'aio-podcast-studio' )
							),
							'value'       => $podcasts_settings['order']
						),
						array(
							'name'        => 'show_podcast_title',
							'label'       => __( 'Show Podcast Title', 'aio-podcast-studio' ),
							'description' => __( 'Display the podcast title.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $podcasts_settings['display_options']['podcast_title'] )
						),
						array(
							'name'        => 'show_podcast_excerpt',
							'label'       => __( 'Show Podcast Description', 'aio-podcast-studio' ),
							'description' => __( 'Display a short description of the podcast.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $podcasts_settings['display_options']['podcast_excerpt'] )
						),
						array(
							'name'        => 'show_podcast_categories',
							'label'       => __( 'Show Podcast Categories', 'aio-podcast-studio' ),
							'description' => __( 'Display associated podcast categories.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $podcasts_settings['display_options']['podcast_categories'] )
						),
						array(
							'name'        => 'show_podcast_date',
							'label'       => __( 'Show Podcast Date', 'aio-podcast-studio' ),
							'description' => __( 'Display the date the podcast was added.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $podcasts_settings['display_options']['podcast_date'] )
						),
						array(
							'name'        => 'show_episodes_count',
							'label'       => __( 'Show Episodes Count', 'aio-podcast-studio' ),
							'description' => __( 'Display the total number of episodes in each podcast.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $podcasts_settings['display_options']['episodes_count'] )
						),
						array(
							'name'        => 'show_explicit_badge',
							'label'       => __( 'Show Explicit Badge', 'aio-podcast-studio' ),
							'description' => __( 'Display explicit content badge if applicable.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $podcasts_settings['display_options']['explicit_badge'] )
						),
						array(
							'name'        => 'show_downloads_count',
							'label'       => __( 'Show Downloads Count', 'aio-podcast-studio' ),
							'description' => __( 'Display the total number of downloads.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $podcasts_settings['display_options']['downloads_count'] )
						),
						array(
							'name'        => 'show_plays_count',
							'label'       => __( 'Show Plays Count', 'aio-podcast-studio' ),
							'description' => __( 'Display the total number of plays.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $podcasts_settings['display_options']['plays_count'] )
						),
						array(
							'name'        => 'excerpt_length',
							'label'       => __( 'Excerpt Length', 'aio-podcast-studio' ),
							'description' => __( 'Set the maximum number of characters for podcast descriptions. Enter 0 to display the full description.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => $podcasts_settings['excerpt_length']
						),
						array(
							'name'        => 'pagination',
							'label'       => __( 'Pagination Type', 'aio-podcast-studio' ),
							'description' => __( 'Choose the pagination style: None, Numeric, or More Button (Ajax).', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								''         => '— ' . __( 'Use Global Setting', 'aio-podcast-studio' ) . ' —' ,
								'none'     => __( 'None', 'aio-podcast-studio' ),
								'numeric'  => __( 'Numeric Pagination', 'aio-podcast-studio' ),
								'loadmore' => __( 'More Button (Ajax)', 'aio-podcast-studio' )
							),
							'value'       => ''
						)
					)
				)
			)
		),
		'episodes' => array(
			'title'    => __( 'Episodes List', 'aio-podcast-studio' ),
			'sections' => array(
				'source' => array(
					'title'  => __( 'Content Source', 'aio-podcast-studio' ),
					'fields' => array(
						array( 
							'name'        => 'podcast',
							'label'       => __( 'Select Podcast', 'aio-podcast-studio' ),
							'description' => __( 'Select a podcast to display its episodes. Leave empty to show episodes from all podcasts.', 'aio-podcast-studio' ),
							'type'        => 'podcasts',
							'value'       => ''
						),
						array(
							'name'        => 'tags',
							'label'       => __( 'Select Tags', 'aio-podcast-studio' ),
							'description' => __( 'Select one or more tags to filter the displayed episodes. Leave empty to show episodes from all tags.', 'aio-podcast-studio' ),
							'type'        => 'tags',
							'value'       => array()
						),
						array( 
							'name'        => 'participants',
							'label'       => __( 'Select Participants', 'aio-podcast-studio' ),
							'description' => __( 'Select one or more participants to filter the displayed episodes. Leave empty to show episodes from all participants.', 'aio-podcast-studio' ),
							'type'        => 'participants',
							'value'       => array()
						),
						array(
							'name'        => 'type',
							'label'       => __( 'Select Episode Type', 'aio-podcast-studio' ),
							'description' => __( 'Select an episode type to filter the displayed episodes.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array_merge(
								array( '' => '— ' . __( 'All Types', 'aio-podcast-studio' ) . ' —' ),
								aiopod_get_episode_types()
							),
							'value'       => ''
						),
						array(
							'name'        => 'featured',
							'label'       => __( 'Show Only Featured Episodes?', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'value'       => 0
						)
					)
				),
				'display' => array(
					'title'  => __( 'Display Options', 'aio-podcast-studio' ),
					'fields' => array(
						array(
							'name'        => 'show_count',
							'label'       => __( 'Display Total Episodes', 'aio-podcast-studio' ),
							'description' => __( 'Show the total number of episodes above the list.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $episodes_settings['show_count']
						),
						array(
							'name'        => 'layout',
							'label'       => __( 'Select Layout', 'aio-podcast-studio' ),
							'description' => __( 'Choose a layout for displaying episodes.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'grid'    => __( 'Grid', 'aio-podcast-studio' ),
								'list'    => __( 'List', 'aio-podcast-studio' ),
								'compact' => __( 'Compact', 'aio-podcast-studio' ),
								'slider'  => __( 'Slider', 'aio-podcast-studio' )
							),
							'value'       => $episodes_settings['layout']
						),
						array(
							'name'        => 'columns',
							'label'       => __( 'Columns', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of columns to display in the list.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'min'         => 1,
							'max'         => 12,
							'step'        => 1,
							'value'       => $episodes_settings['columns']
						),
						array(
							'name'        => 'slides_per_view',
							'label'       => __( 'Slides Per View', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of slides visible at the same time.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => $episodes_settings['slides_per_view']
						),
						array(
							'name'        => 'space_between',
							'label'       => __( 'Space Between', 'aio-podcast-studio' ),
							'description' => __( 'Enter the space between slides (in pixels).', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => $episodes_settings['space_between']
						),
						array(
							'name'        => 'show_dots',
							'label'       => __( 'Show Dots', 'aio-podcast-studio' ),
							'description' => __( 'Show pagination dots below the slider.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $episodes_settings['show_dots']
						),
						array(
							'name'        => 'show_arrows',
							'label'       => __( 'Show Arrows', 'aio-podcast-studio' ),
							'description' => __( 'Show previous and next navigation arrows.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $episodes_settings['show_arrows']
						),
						array(
							'name'        => 'limit',
							'label'       => __( 'Limit (per page)', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of episodes to display per page. Leave blank for the default value (100).', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => $episodes_settings['limit']
						),
						array(
							'name'        => 'orderby',
							'label'       => __( 'Order By', 'aio-podcast-studio' ),
							'description' => __( 'Select how episodes are sorted.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'title'      => __( 'Title', 'aio-podcast-studio' ),
								'date'       => __( 'Date Added', 'aio-podcast-studio' ),
								'popularity' => __( 'Popularity (Plays + Downloads)', 'aio-podcast-studio' ),
								'downloads'  => __( 'Downloads', 'aio-podcast-studio' ),
								'plays'      => __( 'Plays', 'aio-podcast-studio' ),
								'rand'       => __( 'Random', 'aio-podcast-studio' ),
								'menu_order' => __( 'Menu Order', 'aio-podcast-studio' )
							),
							'value'       => $episodes_settings['orderby']
						),
						array(
							'name'        => 'order',
							'label'       => __( 'Order Direction', 'aio-podcast-studio' ),
							'description' => __( 'Choose ascending or descending order.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'asc'  => __( 'Ascending', 'aio-podcast-studio' ),
								'desc' => __( 'Descending', 'aio-podcast-studio' )
							),
							'value'       => $episodes_settings['order']
						),
						array(
							'name'        => 'show_episode_title',
							'label'       => __( 'Show Episode Title', 'aio-podcast-studio' ),
							'description' => __( 'Display the episode title.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['episode_title'] )
						),
						array(
							'name'        => 'show_episode_excerpt',
							'label'       => __( 'Show Episode Description (Excerpt)', 'aio-podcast-studio' ),
							'description' => __( 'Display a short description of the episode.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['episode_excerpt'] )
						),
						array(
							'name'        => 'show_episode_date',
							'label'       => __( 'Show Episode Date', 'aio-podcast-studio' ),
							'description' => __( 'Display the date the episode was added.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['episode_date'] )
						),
						array(
							'name'        => 'show_podcast_title',
							'label'       => __( 'Show Podcast Title', 'aio-podcast-studio' ),
							'description' => __( 'Display associated podcast title.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['podcast_title'] )
						),
						array(
							'name'        => 'show_episode_tags',
							'label'       => __( 'Show Episode Tags', 'aio-podcast-studio' ),
							'description' => __( 'Display episode tags.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['episode_tags'] )
						),
						array(
							'name'        => 'show_episode_type',
							'label'       => __( 'Show Episode Type', 'aio-podcast-studio' ),
							'description' => __( 'Display episode type labels for Trailer and Bonus episodes.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['episode_type'] )
						),
						array(
							'name'        => 'show_season_number',
							'label'       => __( 'Show Season Number', 'aio-podcast-studio' ),
							'description' => __( 'Display the season number.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['season_number'] )
						),
						array(
							'name'        => 'show_episode_number',
							'label'       => __( 'Show Episode Number', 'aio-podcast-studio' ),
							'description' => __( 'Display the episode number.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['episode_number'] )
						),
						array(
							'name'        => 'show_episode_file_size',
							'label'       => __( 'Show Episode File Size', 'aio-podcast-studio' ),
							'description' => __( 'Display the file size of the episode.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['episode_file_size'] )
						),
						array(
							'name'        => 'show_episode_duration',
							'label'       => __( 'Show Episode Duration', 'aio-podcast-studio' ),
							'description' => __( 'Display the duration of the episode.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['episode_duration'] )
						),
						array(
							'name'        => 'show_explicit_badge',
							'label'       => __( 'Show Explicit Badge', 'aio-podcast-studio' ),
							'description' => __( 'Display explicit content badge if applicable.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['explicit_badge'] )
						),
						array(
							'name'        => 'show_downloads_count',
							'label'       => __( 'Show Downloads Count', 'aio-podcast-studio' ),
							'description' => __( 'Display the total number of downloads.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['downloads_count'] )
						),
						array(
							'name'        => 'show_plays_count',
							'label'       => __( 'Show Plays Count', 'aio-podcast-studio' ),
							'description' => __( 'Display the total number of plays.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $episodes_settings['display_options']['plays_count'] )
						),
						array(
							'name'        => 'excerpt_length',
							'label'       => __( 'Excerpt Length', 'aio-podcast-studio' ),
							'description' => __( 'Set the maximum number of characters for episode descriptions. Enter 0 to display the full description.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => $episodes_settings['excerpt_length']
						),
						array(
							'name'        => 'max_height',
							'label'       => __( 'Maximum List Height', 'aio-podcast-studio' ),
							'description' => __( 'Set the maximum height (in pixels) for the episode list container. If the content exceeds this height, a scrollbar will appear. Leave empty to display the full content.', 'aio-podcast-studio' ),
							'type'        => 'text',
							'value'       => $episodes_settings['max_height']
						),
						array(
							'name'        => 'pagination',
							'label'       => __( 'Pagination Type', 'aio-podcast-studio' ),
							'description' => __( 'Choose the pagination style: None, Numeric, or More Button (Ajax).', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								''         => '— ' . __( 'Use Global Setting', 'aio-podcast-studio' ) . ' —' ,
								'none'     => __( 'None', 'aio-podcast-studio' ),
								'numeric'  => __( 'Numeric Pagination', 'aio-podcast-studio' ),
								'loadmore' => __( 'More Button (Ajax)', 'aio-podcast-studio' )
							),
							'value'       => ''
						)
					)
				),
				'player' => array(
					'title'  => __( 'Player Options', 'aio-podcast-studio' ),
					'fields' => array(
						array(
							'name'        => 'enable_player',
							'label'       => __( 'Enable Player', 'aio-podcast-studio' ),
							'description' => sprintf( 
								__( 'Enable direct playback on episode lists. Otherwise, episodes link to their single episode page. Configure player settings under <a href="%s" target="_blank" rel="noopener noreferrer">Player</a>. The player is always enabled for the Compact layout.', 'aio-podcast-studio' ),
								esc_url( admin_url( 'admin.php?page=aiopod_settings&tab=display&section=aiopod_player_settings' ) )
							),
							'type'        => 'checkbox',
							'value'       => $episodes_settings['enable_player']
						),
						array(
							'name'        => 'player_autoplay',
							'label'       => __( 'Autoplay', 'aio-podcast-studio' ),
							'description' => __( 'Automatically start playback when the player is ready.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $player_settings['autoplay']
						),
						array(
							'name'        => 'player_autoadvance',
							'label'       => __( 'Autoplay Next Episode', 'aio-podcast-studio' ),
							'description' => __( 'Automatically play the next episode in the list when the current episode ends.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $player_settings['autoadvance']
						),
						array(
							'name'        => 'player_loop',
							'label'       => __( 'Loop', 'aio-podcast-studio' ),
							'description' => __( 'Continuously loop the media playback.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $player_settings['loop']
						),
						array(
							'name'        => 'player_muted',
							'label'       => __( 'Muted', 'aio-podcast-studio' ),
							'description' => __( 'Mute audio by default when playback starts.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $player_settings['muted']
						),
						array(
							'name'        => 'player_preload',
							'label'       => __( 'Preload', 'aio-podcast-studio' ),
                    		'description' => __( 'Choose how media should load: <strong>Auto</strong> (load entire media), <strong>Metadata</strong> (load metadata only), or <strong>None</strong> (do not preload).', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'auto'     => __( 'Auto', 'aio-podcast-studio' ),
								'metadata' => __( 'Metadata', 'aio-podcast-studio' ),
								'none'     => __( 'None', 'aio-podcast-studio' )
							),
							'value'       => $player_settings['preload']
						),
						array(
							'name'        => 'player_playsinline',
							'label'       => __( 'Plays Inline on Mobile', 'aio-podcast-studio' ),
							'description' => __( 'Prevent fullscreen mode on mobile devices during playback.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $player_settings['playsinline']
						),
						array(
							'name'        => 'player_controls',
							'label'       => __( 'Player Controls', 'aio-podcast-studio' ),
							'description' => __( 'Select which controls to display on the player.', 'aio-podcast-studio' ),
							'type'        => 'header',
							'value'       => 0
						),
						array(
							'name'        => 'player_control_rewind',
							'label'       => __( 'Rewind Button', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['rewind'] )
						),
						array(
							'name'        => 'player_control_play',
							'label'       => __( 'Play Button', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['play'] )
						),
						array(
							'name'        => 'player_control_fastforward',
							'label'       => __( 'Forward Button', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['fastforward'] )
						),
						array(
							'name'        => 'player_control_progress',
							'label'       => __( 'Progressbar', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['progress'] )
						),
						array(
							'name'        => 'player_control_currenttime',
							'label'       => __( 'Current Time', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['currenttime'] )
						),						
						array(
							'name'        => 'player_control_duration',
							'label'       => __( 'Duration', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['duration'] )
						),
						array(
							'name'        => 'player_control_volume',
							'label'       => __( 'Volume Button', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['volume'] )
						),
						array(
							'name'        => 'player_control_speed',
							'label'       => __( 'Speed Control', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['speed'] )
						),
						array(
							'name'        => 'player_control_chapters',
							'label'       => __( 'Chapters', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['chapters'] )
						),
						array(
							'name'        => 'player_control_pip',
							'label'       => __( 'Picture-in-Picture Button', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['pip'] )
						),
						array(
							'name'        => 'player_control_download',
							'label'       => __( 'Download Button', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['download'] )
						),
						array(
							'name'        => 'player_control_fullscreen',
							'label'       => __( 'Fullscreen Button', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['controls']['fullscreen'] )
						),
						array(
							'name'        => 'player_episode_details',
							'label'       => __( 'Episode Details', 'aio-podcast-studio' ),
							'description' => __( 'Select which episode details to display on the player.', 'aio-podcast-studio' ),
							'type'        => 'header',
							'value'       => 0
						),
						array(
							'name'        => 'show_player_episode_title',
							'label'       => __( 'Episode Title', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['episode_title'] )
						),
						array(
							'name'        => 'show_player_episode_excerpt',
							'label'       => __( 'Episode Description (Excerpt)', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['episode_excerpt'] )
						),
						array(
							'name'        => 'show_player_episode_date',
							'label'       => __( 'Episode Date', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'value'       => isset( $player_settings['episode_details']['episode_date'] )
						),
						array(
							'name'        => 'show_player_podcast_title',
							'label'       => __( 'Podcast Title', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['podcast_title'] )
						),
						array(
							'name'        => 'show_player_episode_tags',
							'label'       => __( 'Episode Tags', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['episode_tags'] )
						),
						array(
							'name'        => 'show_player_episode_type',
							'label'       => __( 'Episode Type (Trailer / Bonus)', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['episode_type'] )
						),
						array(
							'name'        => 'show_player_season_number',
							'label'       => __( 'Season Number', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['season_number'] )
						),
						array(
							'name'        => 'show_player_episode_number',
							'label'       => __( 'Episode Number', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['episode_number'] )
						),
						array(
							'name'        => 'show_player_episode_file_size',
							'label'       => __( 'Episode File Size', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['episode_file_size'] )
						),
						array(
							'name'        => 'show_player_episode_duration',
							'label'       => __( 'Episode Duration', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['episode_duration'] )
						),
						array(
							'name'        => 'show_player_explicit_badge',
							'label'       => __( 'Explicit Badge', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['explicit_badge'] )
						),
						array(
							'name'        => 'show_player_downloads_count',
							'label'       => __( 'Downloads Count', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['downloads_count'] )
						),
						array(
							'name'        => 'show_player_plays_count',
							'label'       => __( 'Plays Count', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['episode_details']['plays_count'] )
						),
						array(
							'name'        => 'player_excerpt_length',
							'label'       => __( 'Excerpt Length', 'aio-podcast-studio' ),
							'description' => __( 'Set the maximum number of characters for the episode description on the player. Enter 0 to display the full description.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => isset( $player_settings['excerpt_length'] ) ? $player_settings['excerpt_length'] : 100
						),
						array(
							'name'        => 'player_social_buttons',
							'label'       => __( 'Social Media Buttons', 'aio-podcast-studio' ),
							'description' =>sprintf( 
								__( 'Select which social buttons to display on the player. Configure <a href="%s" target="_blank" rel="noopener noreferrer">Share Buttons</a>.', 'aio-podcast-studio' ),
								esc_url( admin_url( 'admin.php?page=aiopod_settings&tab=seo&section=aiopod_share_buttons_settings' ) )
							),
							'type'        => 'header',
							'value'       => 0
						),
						array(
							'name'        => 'show_player_subscribe_buttons',
							'label'       => __( 'Subscribe Buttons', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['social_buttons']['subscribe_buttons'] )
						),
						array(
							'name'        => 'show_player_follow_buttons',
							'label'       => __( 'Follow Us Buttons', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['social_buttons']['follow_buttons'] )
						),
						array(
							'name'        => 'show_player_share_buttons',
							'label'       => __( 'Share Buttons', 'aio-podcast-studio' ),
							'description' => '',
							'type'        => 'checkbox',
							'class'       => 'aiopod-font-normal',
							'value'       => isset( $player_settings['social_buttons']['share_buttons'] )
						)
					)
				),
				'filters' => array(
					'title'  => __( 'Filter Options', 'aio-podcast-studio' ),
					'fields' => array(
						array(
							'name'        => 'enable_filters',
							'label'       => __( 'Enable Filters', 'aio-podcast-studio' ),
							'description' => __( 'Enable this option to display filters above the epsiodes list.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $episodes_settings['enable_filters']
						),
						array(
							'name'        => 'filters_keyword',
							'label'       => __( 'Filter by Episode Title & Description', 'aio-podcast-studio' ),
							'description' => __( 'Allow filtering episodes by title and description.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $filters_settings['display_options']['keyword'] )
						),
						array(
							'name'        => 'filters_tags',
							'label'       => __( 'Filter by Tags', 'aio-podcast-studio' ),
							'description' => __( 'Allow filtering episodes by tags.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $filters_settings['display_options']['tags'] )
						),
						array(
							'name'        => 'filters_season',
							'label'       => __( 'Filter by Season Number', 'aio-podcast-studio' ),
							'description' => __( 'Allow filtering episodes by season number.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $filters_settings['display_options']['season'] )
						),
						array(
							'name'        => 'filters_sort',
							'label'       => __( 'Enable Sort Dropdown', 'aio-podcast-studio' ),
							'description' => __( 'Allow users to sort episodes using a dropdown.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $filters_settings['display_options']['sort'] )
						),
						array(
							'name'        => 'filters_reset',
							'label'       => __( 'Show Reset Button', 'aio-podcast-studio' ),
							'description' => __( 'Display a reset button to clear all applied filters.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $filters_settings['display_options']['reset'] )
						),
						array(
							'name'        => 'filters_layout',
							'label'       => __( 'Filters Layout', 'aio-podcast-studio' ),
							'description' => __( 'Choose how filters are displayed: vertically (stacked) or horizontally (inline).', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'vertical'   => __( 'Vertical', 'aio-podcast-studio' ),
								'horizontal' => __( 'Horizontal', 'aio-podcast-studio' )
							),
							'value'       => $filters_settings['layout']
						),
						array(
							'name'        => 'filters_mode',
							'label'       => __( 'Filters Behavior', 'aio-podcast-studio' ),
							'description' => __( 'Choose how filters behave when visitors interact with them.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'live'   => __( 'Live - Update instantly', 'aio-podcast-studio' ),
								'ajax'   => __( 'Ajax - Update instantly without page reload', 'aio-podcast-studio' ),
								'search' => __( 'Search - Update on button click', 'aio-podcast-studio' )
							),
							'value'       => $filters_settings['mode']
						)
					)
				)
			)
		),
		'categories' => array(
			'title'    => __( 'Categories List', 'aio-podcast-studio' ),
			'sections' => array(
				'display' => array(
					'title'  => __( 'Display Options', 'aio-podcast-studio' ),
					'fields' => array(
						array(
							'name'        => 'layout',
							'label'       => __( 'Select Layout', 'aio-podcast-studio' ),
							'description' => __( 'Choose a layout to display categories.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'grid'     => __( 'Grid', 'aio-podcast-studio' ),
								'list'     => __( 'List', 'aio-podcast-studio' ),
								'dropdown' => __( 'Dropdown', 'aio-podcast-studio' )
							),
							'value'       => $categories_settings['layout']
						),
						array(
							'name'        => 'columns',
							'label'       => __( 'Columns', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of columns to display in the list.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'min'         => 1,
							'max'         => 12,
							'step'        => 1,
							'value'       => $categories_settings['columns']
						),
						array(
							'name'        => 'limit',
							'label'       => __( 'Limit (per page)', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of categories to display per page. Leave blank to show all.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => $categories_settings['limit']
						),
						array(
							'name'        => 'orderby',
							'label'       => __( 'Order By', 'aio-podcast-studio' ),
							'description' => __( 'Select how categories are sorted.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'id'         => __( 'ID', 'aio-podcast-studio' ),
								'count'      => __( 'Count', 'aio-podcast-studio' ),
								'name'       => __( 'Name', 'aio-podcast-studio' ),
								'slug'       => __( 'Slug', 'aio-podcast-studio' ),
								'menu_order' => __( 'Menu Order', 'aio-podcast-studio' )
							),
							'value'       => $categories_settings['orderby']
						),
						array(
							'name'        => 'order',
							'label'       => __( 'Order Direction', 'aio-podcast-studio' ),
							'description' => __( 'Choose ascending or descending order.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'asc'  => __( 'Ascending', 'aio-podcast-studio' ),
								'desc' => __( 'Descending', 'aio-podcast-studio' )
							),
							'value'       => $categories_settings['order']
						),
						array(
							'name'        => 'hierarchical',
							'label'       => __( 'Show Hierarchy', 'aio-podcast-studio' ),
							'description' => __( 'Enable this option to display subcategories under their parent categories.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $categories_settings['hierarchical']
						),
						array(
							'name'        => 'show_name',
							'label'       => __( 'Show Name', 'aio-podcast-studio' ),
							'description' => __( 'Enable this option to display category name.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $categories_settings['show_name']
						),
						array(
							'name'        => 'show_description',
							'label'       => __( 'Show Description', 'aio-podcast-studio' ),
							'description' => __( 'Enable this option to display category descriptions.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $categories_settings['show_description']
						),
						array(
							'name'        => 'show_count',
							'label'       => __( 'Show Podcasts Count', 'aio-podcast-studio' ),
							'description' => __( 'Enable this option to display the number of podcasts in each category.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $categories_settings['show_count']
						),
						array(
							'name'        => 'hide_empty',
							'label'       => __( 'Hide Empty Categories', 'aio-podcast-studio' ),
							'description' => __( 'Enable this option to hide categories that do not contain any podcasts.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $categories_settings['hide_empty']
						),
						array(
							'name'        => 'pagination',
							'label'       => __( 'Pagination Type', 'aio-podcast-studio' ),
							'description' => __( 'Choose the pagination style: None, Numeric, or More Button (Ajax).', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								''         => '— ' . __( 'Use Global Setting', 'aio-podcast-studio' ) . ' —' ,
								'none'     => __( 'None', 'aio-podcast-studio' ),
								'numeric'  => __( 'Numeric Pagination', 'aio-podcast-studio' ),
								'loadmore' => __( 'More Button (Ajax)', 'aio-podcast-studio' )
							),
							'value'       => ''
						)
					)
				)
			)
		),	
		'tags' => array(
			'title'    => __( 'Tags List', 'aio-podcast-studio' ),
			'sections' => array(
				'display' => array(
					'title'  => __( 'Display Options', 'aio-podcast-studio' ),
					'fields' => array(
						array(
							'name'        => 'layout',
							'label'       => __( 'Select Layout', 'aio-podcast-studio' ),
							'description' => __( 'Choose a layout to display tags.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'cloud'     => __( 'Tag Cloud', 'aio-podcast-studio' ),
								'list'      => __( 'List', 'aio-podcast-studio' ),
								'dropdown'  => __( 'Dropdown', 'aio-podcast-studio' )
							),
							'value'       => $tags_settings['layout']
						),
						array(
							'name'        => 'orderby',
							'label'       => __( 'Order By', 'aio-podcast-studio' ),
							'description' => __( 'Select how tags are sorted.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'id'         => __( 'ID', 'aio-podcast-studio' ),
								'count'      => __( 'Count', 'aio-podcast-studio' ),
								'name'       => __( 'Name', 'aio-podcast-studio' ),
								'slug'       => __( 'Slug', 'aio-podcast-studio' ),
								'menu_order' => __( 'Menu Order', 'aio-podcast-studio' )
							),
							'value'       => $tags_settings['orderby']
						),
						array(
							'name'        => 'order',
							'label'       => __( 'Order Direction', 'aio-podcast-studio' ),
							'description' => __( 'Choose ascending or descending order.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'asc'  => __( 'Ascending', 'aio-podcast-studio' ),
								'desc' => __( 'Descending', 'aio-podcast-studio' )
							),
							'value'       => $tags_settings['order']
						),
						array(
							'name'        => 'show_count',
							'label'       => __( 'Show Episodes Count', 'aio-podcast-studio' ),
							'description' => __( 'Tag counts use WordPress defaults and may include trailers and bonus episodes. Episode listings display only full episodes. Disable counts if you tag trailers or bonus episodes.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $tags_settings['show_count']
						),
						array(
							'name'        => 'hide_empty',
							'label'       => __( 'Hide Empty Tags', 'aio-podcast-studio' ),
							'description' => __( 'Enable this option to hide tags that do not contain any episodes.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $tags_settings['hide_empty']
						)
					)
				)
			)
		),
		'participants' => array(
			'title'    => __( 'Participants List', 'aio-podcast-studio' ),
			'sections' => array(
				'display' => array(
					'title'  => __( 'Display Options', 'aio-podcast-studio' ),
					'fields' => array(
						array(
							'name'        => 'show_count',
							'label'       => __( 'Display Total Participants', 'aio-podcast-studio' ),
							'description' => __( 'Show the total number of participants above the list.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => $participants_settings['show_count']
						),
						array(
							'name'        => 'columns',
							'label'       => __( 'Columns', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of columns to display in the list.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'min'         => 1,
							'max'         => 12,
							'step'        => 1,
							'value'       => $participants_settings['columns']
						),
						array(
							'name'        => 'limit',
							'label'       => __( 'Limit (per page)', 'aio-podcast-studio' ),
							'description' => __( 'Enter the number of participants to display per page. Leave blank to show all.', 'aio-podcast-studio' ),
							'type'        => 'number',
							'value'       => $participants_settings['limit']
						),
						array(
							'name'        => 'orderby',
							'label'       => __( 'Order By', 'aio-podcast-studio' ),
							'description' => __( 'Select how participants are sorted.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'id'         => __( 'ID', 'aio-podcast-studio' ),
								'count'      => __( 'Count', 'aio-podcast-studio' ),
								'name'       => __( 'Name', 'aio-podcast-studio' ),
								'slug'       => __( 'Slug', 'aio-podcast-studio' ),
								'menu_order' => __( 'Menu Order', 'aio-podcast-studio' )
							),
							'value'       => $participants_settings['orderby']
						),
						array(
							'name'        => 'order',
							'label'       => __( 'Order Direction', 'aio-podcast-studio' ),
							'description' => __( 'Choose ascending or descending order.', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								'asc'  => __( 'Ascending', 'aio-podcast-studio' ),
								'desc' => __( 'Descending', 'aio-podcast-studio' )
							),
							'value'       => $participants_settings['order']
						),
						array(
							'name'        => 'show_name',
							'label'       => __( 'Show Participant Name', 'aio-podcast-studio' ),
							'description' => __( 'Display the participant\'s name.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $participants_settings['display_options']['name'] )
						),
						array(
							'name'        => 'show_description',
							'label'       => __( 'Show Description (Biography)', 'aio-podcast-studio' ),
							'description' => __( 'Display the participant\'s biography.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $participants_settings['display_options']['description'] )
						),
						array(
							'name'        => 'show_job_title',
							'label'       => __( 'Show Job Title', 'aio-podcast-studio' ),
							'description' => __( 'Display the participant\'s job title.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $participants_settings['display_options']['job_title'] )
						),
						array(
							'name'        => 'show_company_name',
							'label'       => __( 'Show Company Name', 'aio-podcast-studio' ),
							'description' => __( 'Display the participant\'s company name.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $participants_settings['display_options']['company_name'] )
						),
						array(
							'name'        => 'show_website_url',
							'label'       => __( 'Show Website URL', 'aio-podcast-studio' ),
							'description' => __( 'Display the participant\'s website URL.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $participants_settings['display_options']['website_url'] )
						),
						array(
							'name'        => 'show_podcasts_count',
							'label'       => __( 'Show Podcasts Count', 'aio-podcast-studio' ),
							'description' => __( 'Display the total number of podcasts the participant is involved in.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $participants_settings['display_options']['podcasts_count'] )
						),
						array(
							'name'        => 'show_episodes_count',
							'label'       => __( 'Show Episodes Count', 'aio-podcast-studio' ),
							'description' => __( 'Display the total number of episodes the participant is involved in.', 'aio-podcast-studio' ),
							'type'        => 'checkbox',
							'value'       => isset( $participants_settings['display_options']['episodes_count'] )
						),
						array(
							'name'        => 'pagination',
							'label'       => __( 'Pagination Type', 'aio-podcast-studio' ),
							'description' => __( 'Choose the pagination style: None, Numeric, or More Button (Ajax).', 'aio-podcast-studio' ),
							'type'        => 'select',
							'options'     => array(
								''         => '— ' . __( 'Use Global Setting', 'aio-podcast-studio' ) . ' —' ,
								'none'     => __( 'None', 'aio-podcast-studio' ),
								'numeric'  => __( 'Numeric Pagination', 'aio-podcast-studio' ),
								'loadmore' => __( 'More Button (Ajax)', 'aio-podcast-studio' )
							),
							'value'       => ''
						)
					)
				)
			)
		)
	);

	return apply_filters( 'aiopod_shortcode_fields', $fields );
}

/**
 * Retrieve the Subscribe buttons for a given post (podcast or episode).
 *
 * @since  1.0.0
 * @param  int   $post_id The ID of the post.
 * @return array          Array of subscribe button data.
 */
function aiopod_get_subscribe_buttons( $post_id ) {
	$raw_subscribe_buttons = get_post_meta( $post_id, 'subscribe_buttons', false );
	$subscribe_buttons     = array();

	if ( is_array( $raw_subscribe_buttons ) && ! empty( $raw_subscribe_buttons ) ) {
		foreach ( $raw_subscribe_buttons as $subscribe_button ) {
			if ( ! is_array( $subscribe_button ) || empty( $subscribe_button['platform'] ) || empty( $subscribe_button['url'] ) ) {
				continue;
			}

			$platform = $subscribe_button['platform'];

			$subscribe_buttons[] = array(
				'label' => sprintf( __( 'Listen on %s', 'aio-podcast-studio' ), aiopod_convert_filename_to_title( $platform ) ),				
				'image' => AIOPOD_PLUGIN_URL . 'public/assets/images/' . $platform,
				'url'   => $subscribe_button['url']
			);
		}
	}

	return apply_filters( 'aiopod_get_subscribe_buttons', $subscribe_buttons, $post_id );
}

/**
 * Returns an inline SVG icon by name.
 *
 * All icons are Heroicons (outline, 24px, stroke-width 1.5).
 *
 * @since  1.0.0
 * @param  string $name Icon name: calendar | microphone | tag | download | episodes | folder | globe | speaker.
 * @return string       SVG markup, or empty string for unknown names.
 */
function aiopod_get_svg( $name ) {
	$base = 'xmlns="http://www.w3.org/2000/svg" fill="none" width="16" height="16" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true"';

	switch ( $name ) {
		case 'calendar':
			return '<svg ' . $base . '><path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" /></svg>';

		case 'microphone':
			return '<svg ' . $base . '><path stroke-linecap="round" stroke-linejoin="round" d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z" /></svg>';

		case 'tag':
			return '<svg ' . $base . '><path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6Z" /></svg>';

		case 'download':
			return '<svg ' . $base . '><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" /></svg>';

		case 'episodes':
			return '<svg ' . $base . '><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.347a1.125 1.125 0 0 1 0 1.972l-11.54 6.347a1.125 1.125 0 0 1-1.667-.986V5.653Z" /></svg>';

		case 'folder':
			return '<svg ' . $base . '><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 0 0-1.883 2.542l.857 6a2.25 2.25 0 0 0 2.227 1.932H19.05a2.25 2.25 0 0 0 2.227-1.932l.857-6a2.25 2.25 0 0 0-1.883-2.542m-16.5 0V6A2.25 2.25 0 0 1 6 3.75h3.879a1.5 1.5 0 0 1 1.06.44l2.122 2.12a1.5 1.5 0 0 0 1.06.44H18A2.25 2.25 0 0 1 20.25 9v.776" /></svg>';

		case 'globe':
			return '<svg ' . $base . '><path stroke-linecap="round" stroke-linejoin="round" d="M12.75 3.03v.568c0 .334.148.65.405.864l1.068.89c.442.369.535 1.01.216 1.49l-.51.766a2.25 2.25 0 0 1-1.161.886l-.143.048a1.107 1.107 0 0 0-.57 1.664c.369.555.169 1.307-.427 1.605L9 13.125l.423 1.059a.956.956 0 0 1-1.652.928l-.679-.906a1.125 1.125 0 0 0-1.906.172L4.5 15.75l-.612.153M12.75 3.031a9 9 0 0 0-8.862 12.872M12.75 3.031a9 9 0 0 1 6.69 14.036m0 0-.177-.529A2.25 2.25 0 0 0 17.128 15H16.5l-.324-.324a1.453 1.453 0 0 0-2.328.377l-.036.073a1.586 1.586 0 0 1-.982.816l-.99.282c-.55.157-.894.702-.8 1.267l.073.438c.08.474.49.821.97.821.846 0 1.598.542 1.865 1.345l.215.643m5.276-3.67a9.012 9.012 0 0 1-5.276 3.67m0 0a9 9 0 0 1-10.275-4.835M15.75 9c0 .896-.393 1.7-1.016 2.25" /></svg>';

		case 'speaker':
			return '<svg ' . $base . '><path stroke-linecap="round" stroke-linejoin="round" d="M19.114 5.636a9 9 0 0 1 0 12.728M16.463 8.288a5.25 5.25 0 0 1 0 7.424M6.75 8.25l4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z" /></svg>';

		default:
			return '';
	}
}

/**
 * Get linked term names for a given post and taxonomy.
 *
 * @since 1.0.0
 * @param int    $post_id  The post ID (e.g., podcast or episode).
 * @param string $taxonomy The taxonomy name (e.g., 'aiopod_categories').
 * @return array           Array of linked term names. Empty array if none found.
 */
function aiopod_get_term_links( $post_id, $taxonomy = 'aiopod_categories' ) {
	$terms = wp_get_object_terms( $post_id, $taxonomy );
	$links = array();

	if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
		foreach ( $terms as $term ) {
			$links[] = sprintf(
				'<a href="%s" class="aiopod-link-term">%s</a>',
				esc_url( get_term_link( $term ) ),
				esc_html( $term->name )
			);
		}
	}

	return apply_filters( 'aiopod_get_term_links', $links, $post_id, $taxonomy );
}

/**
 * Retrieves the date on which the podcast was added.
 *
 * @since  1.0.0
 * @param  int|WP_Post|null $post Optional. Post ID or WP_Post object. Default is global $post.
 * @return string                 Date the current podcast was added.
 */
function aiopod_get_the_date( $post = null ) {
	$formatting_settings = aiopod_get_option( 'aiopod_formatting_settings' );

	if ( isset( $formatting_settings['datetime_format'] ) && ! empty( $formatting_settings['datetime_format'] ) ) {
		$date = get_the_date( $formatting_settings['datetime_format'], $post );
	} else {
		$date = sprintf(
			__( '%s ago', 'aio-podcast-studio' ),
			human_time_diff( get_the_time( 'U', $post ), current_time( 'timestamp' ) )
		);
	}

	return apply_filters( 'aiopod_get_the_date', $date );
}

/**
 * Get a list of user roles.
 *
 * @since  1.0.0
 * @return array Array of user roles.
 */
function aiopod_get_user_roles() {
	$roles = wp_roles()->get_names();
	asort( $roles );
	
	return $roles;
}

/**
 * Get the core WordPress taxonomies enabled for a plugin post type.
 *
 * Core registers 'category' and 'post_tag' on 'init' at priority 0 and the plugin
 * post types register at priority 10, so the names returned here are always
 * available by the time register_post_type() receives them.
 *
 * @since  1.0.4
 * @param  string     $post_type Post type name.
 * @param  array|null $settings  Optional. An `aiopod_taxonomies_settings` value to read instead
 *                               of the stored one. Pass it to ask what a value other than the
 *                               current one means — the previous value during a save, or an
 *                               empty one to ask what "switched off" would mean.
 * @return array                 Core taxonomy names. Empty array when the setting is off.
 */
function aiopod_get_wp_taxonomies( $post_type, $settings = null ) {
	$taxonomies_settings = is_array( $settings ) ? $settings : aiopod_get_option( 'aiopod_taxonomies_settings' );
	$taxonomies          = array();

	if ( isset( $taxonomies_settings['wp_taxonomies']['categories'] ) ) {
		$taxonomies[] = 'category';
	}

	if ( isset( $taxonomies_settings['wp_taxonomies']['tags'] ) ) {
		$taxonomies[] = 'post_tag';
	}

	return apply_filters( 'aiopod_wp_taxonomy_object_types', $taxonomies, $post_type );
}

/**
 * Inserts a new key/value after the key in the array.
 *
 * @since  1.0.0
 * @param  string $key       The key to insert after.
 * @param  array  $array     An array to insert in to.
 * @param  array  $new_array An array to insert.
 * @return                   The new array if the key exists, FALSE otherwise.
 */
function aiopod_insert_array_after( $key, $array, $new_array ) {
	if ( array_key_exists( $key, $array ) ) {
    	$new = array();
    	foreach ( $array as $k => $value ) {
      		$new[ $k ] = $value;
      		if ( $k === $key ) {
				foreach ( $new_array as $new_key => $new_value ) {
        			$new[ $new_key ] = $new_value;
				}
      		}
    	}
    	return $new;
  	}
		
  	return $array;  
}

/**
 * Insert required custom pages and return their IDs as array.
 * 
 * @since  1.0.0
 * @return array Array of created page IDs.
 */
function aiopod_insert_custom_pages() {
	// Vars
	$pages_settings = get_option( 'aiopod_pages_settings' );

	if ( ! is_array( $pages_settings ) || empty( $pages_settings ) ) {		
		$pages = array();
		$page_definitions = aiopod_get_custom_pages_list();
		
		foreach ( $page_definitions as $slug => $page ) {
			$page_check = aiopod_get_page_by_title( $page['title'] );

			if ( ! isset( $page_check->ID ) ) {
				$id = wp_insert_post(
					array(
						'post_title'     => $page['title'],
						'post_content'   => $page['content'],
						'post_status'    => 'publish',
						'post_author'    => 1,
						'post_type'      => 'page',
						'comment_status' => 'closed'
					)
				);
					
				$pages[ $slug ] = $id;	
			} else {
				$pages[ $slug ] = $page_check->ID;	
			}		
		}
	} else {
		$pages = $pages_settings;
	}

	return $pages;
}

/**
 * Whether a podcast or episode is restricted to a limited audience.
 *
 * Companion to `aiopod_current_user_can()`, answering the other half of the question: not
 * whether the current user may view the post, but whether the post is restricted at all.
 * The answer does not depend on who is asking, which makes it safe to use where output is
 * shared between visitors, such as the cached RSS feed.
 *
 * @since  1.0.3
 * @param  int  $post_id Podcast or episode ID.
 * @return bool          True if the content is restricted, false if it is open to all.
 */
function aiopod_is_content_restricted( $post_id ) {
	return (bool) apply_filters( 'aiopod_is_content_restricted', false, (int) $post_id );
}

/**
 * Determines whether an episode file is a video.
 *
 * @since  1.0.0
 * @param  int|string $post_id_or_type Episode post ID (int) or direct MIME type (string).
 * @param  string     $url             Episode media URL (optional).
 * @return bool
 */
function aiopod_is_video( $post_id_or_type, $url = '' ) {
    $type = aiopod_get_episode_file_type( $post_id_or_type, $url );
    
    if ( empty( $type ) ) {
        return false;
    }
    
    return 0 === strpos( $type, 'video/' ) || in_array( $type, [ 'application/x-mpegURL', 'application/dash+xml' ], true );
}

/**
 * Determines whether an episode should be rendered as video on the front end.
 *
 * @since  1.0.0
 * @param  int|string $post_id_or_type Episode post ID (int) or direct MIME type (string).
 * @param  string     $url             Episode media URL (optional).
 * @return bool
 */
function aiopod_is_video_supported( $post_id_or_type, $url = '' ) {
	if ( ! aiopod_is_video( $post_id_or_type, $url ) ) {
		return false;
	}

	return (bool) apply_filters( 'aiopod_player_can_play_video', false );
}

/**
 * Check if Yoast or the Rank Math SEO plugin is active.
 *
 * @since  1.0.0
 * @return bool  True if active, false if not.
 */
function aiopod_is_yoast_or_rank_math_active() {	
	$active_plugins = apply_filters( 'active_plugins', get_option( 'active_plugins' ) );

	// Check if Yoast SEO plugin is active
	if ( in_array( 'wordpress-seo/wp-seo.php', $active_plugins ) || in_array( 'wordpress-seo-premium/wp-seo-premium.php', $active_plugins ) ) {
		return true;
	}

	// Check if Rank Math SEO plugin is active
	if ( in_array( 'seo-by-rank-math/rank-math.php', $active_plugins ) ) {
		return true;
	}

	return false;
}

/**
 * Prepares shortcode attributes for AJAX requests.
 *
 * This function removes unwanted attributes from the shortcode attributes array
 * before passing it as JSON parameters in AJAX requests.
 *
 * @since  1.0.0
 * @param  array $attributes Shortcode attributes.
 * @return array             Filtered attributes suitable for AJAX requests.
 */
function aiopod_prepare_attributes_for_ajax( $attributes ) {    
    $exclude_attributes = array(
		'enable_filters',
		'filters_keyword',
		'filters_tags',
		'filters_season',
		'filters_sort',
		'filters_reset',
		'filters_layout',
		'filters_mode'
	);

    foreach ( $exclude_attributes as $attribute ) {
        unset( $attributes[ $attribute ] );
    }

    return $attributes;
}

/**
 * Recount the terms of a core taxonomy that our post types are related to.
 *
 * Only those terms. A site can hold thousands of categories our content never touches, and
 * their counts cannot have changed. One join over `term_relationships` finds the ones that
 * can; walking the taxonomy would find the same terms and a great deal of nothing.
 *
 * The query here only reads. The write is done by `wp_update_term_count_now()`, which is
 * core's own entry point — it fires `edited_term_taxonomy` and clears the term cache, neither
 * of which a hand-written UPDATE would do.
 *
 * @since  1.0.4
 * @access private
 * @param  string $taxonomy   Core taxonomy name.
 * @param  array  $post_types Plugin post types whose relationship to the taxonomy changed.
 */
function aiopod_recount_core_terms( $taxonomy, $post_types ) {
	global $wpdb;

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

	$placeholders = implode( ', ', array_fill( 0, count( $post_types ), '%s' ) );

	$term_taxonomy_ids = $wpdb->get_col( $wpdb->prepare(
		"SELECT DISTINCT tr.term_taxonomy_id
		 FROM {$wpdb->term_relationships} AS tr
		 INNER JOIN {$wpdb->term_taxonomy} AS tt ON tt.term_taxonomy_id = tr.term_taxonomy_id
		 INNER JOIN {$wpdb->posts} AS p ON p.ID = tr.object_id
		 WHERE tt.taxonomy = %s
		 AND p.post_type IN ( {$placeholders} )",
		array_merge( array( $taxonomy ), array_values( $post_types ) )
	) );

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

	// Term taxonomy IDs, not term IDs — `_update_post_term_count()` matches on
	// `term_taxonomy_id`. The whole batch goes in one call, so there is nothing for
	// `wp_defer_term_counting()` to collect and flush
	wp_update_term_count_now( $term_taxonomy_ids, $taxonomy );
}

/**
  * Removes an item or list from the query string.
  *
  * @since  1.0.0
  * @param  string|array $key   Query key or keys to remove.
  * @param  bool|string  $query When false uses the $_SERVER value. Default false.
  * @return string              New URL query string.
  */
function aiopod_remove_query_arg( $key, $query = false ) {
	if ( is_array( $key ) ) { // removing multiple keys
		foreach ( $key as $k ) {
			$query = str_replace( '#038;', '&', $query );
			$query = add_query_arg( $k, false, $query );
		}
		
		return $query;
	}
		
	return add_query_arg( $key, false, $query );	
}

/**
 * Remove 'unfiltered_html' capability from editors.
 * 
 * @since 1.0.0
 */
function aiopod_remove_unfiltered_html_capability_from_editors() {
    // Get the Editor role
    $editor = get_role( 'editor' );

    // Remove the unfiltered_html capability if it exists
    if ( $editor && $editor->has_cap( 'unfiltered_html' ) ) {
        $editor->remove_cap( 'unfiltered_html' );
    }
}

/**
 * Output a single hidden form field, recursing into array values.
 *
 * @since 1.0.0
 * @param string       $name  Field name.
 * @param string|array $value Field value.
 */
function aiopod_render_hidden_field( $name, $value ) {
	if ( is_array( $value ) ) {
		foreach ( $value as $sub_key => $sub_value ) {
			aiopod_render_hidden_field( $name . '[' . sanitize_key( $sub_key ) . ']', $sub_value );
		}

		return;
	}

	printf(
		'<input type="hidden" name="%s" value="%s" />',
		esc_attr( $name ),
		esc_attr( sanitize_text_field( $value ) )
	);
}

/**
 * Output hidden inputs preserving the current query args.
 *
 * @since 1.0.0
 * @param array $exclude Query keys owned by the form (and therefore skipped).
 */
function aiopod_render_preserved_query_fields( $exclude = array() ) {
	if ( empty( $_GET ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		return;
	}

	// Always reset pagination when filters change.
	$exclude = array_merge( $exclude, array( 'paged', 'page' ) );
	$exclude = apply_filters( 'aiopod_filters_form_reserved_keys', $exclude );

	foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$key = sanitize_key( $key );

		if ( '' === $key || in_array( $key, $exclude, true ) ) {
			continue;
		}

		aiopod_render_hidden_field( $key, wp_unslash( $value ) );
	}
}

/**
 * Sanitize the array inputs.
 *
 * @since  1.0.0
 * @param  array $value Input array.
 * @return array        Sanitized array.
 */
function aiopod_sanitize_array( $value ) {
	return ! empty( $value ) ? array_map( 'sanitize_text_field', $value ) : array();
}

/**
 * Sanitize the integer inputs, accepts empty values.
 *
 * @since  1.0.0
 * @param  string|int $value Input value.
 * @return string|int        Sanitized value.
 */
function aiopod_sanitize_int( $value ) {
	$value = intval( $value );
	return ( 0 === $value ) ? '' : $value;	
}

/**
 * Sanitize the URLs. Accepts relative file paths, spaces.
 *
 * @since  1.0.0
 * @param  string $value Input value.
 * @return string        Sanitized value.
 */
function aiopod_sanitize_url( $value ) {
	if ( ! empty( $value ) ) {
		$value = trim( $value );
	}
	
	return sanitize_url( $value );
}

/**
 * Truncate text to a specific character length.
 *
 * @since 1.0.0
 * @param string $text        Text to be truncated.
 * @param int    $char_length Maximum character length.
 * @param string $append      String to append at the end if truncated.
 * @return string             Truncated text.
 */
function aiopod_truncate_text( $text, $char_length = 55, $append = '...' ) {
	if ( empty( $char_length ) )	{
		return $text;
	}

	$text = strip_tags( $text );
	
	if ( $char_length > 0 && strlen( $text ) > $char_length ) {
		$tmp = substr( $text, 0, $char_length );
		$tmp = substr( $tmp, 0, strrpos( $tmp, ' ' ) );

		if ( strlen( $tmp ) >= $char_length - 3 ) {
			$tmp = substr( $tmp, 0, strrpos( $tmp, ' ' ) );
		}

		$text = $tmp . '...';
	}

	return $text;
}

/**
 * Recount core taxonomy terms when the WordPress taxonomies setting changes.
 *
 * `_update_post_term_count()` counts published posts across every object type registered to a
 * taxonomy, so attaching or detaching our post types changes what every core term counts.
 * WordPress only recalculates a count when one of the term's relationships changes, so without
 * this a term nobody edits keeps counting podcasts its archive no longer lists — and turning
 * the setting off would leave those inflated counts in the sidebar permanently.
 *
 * Called on the settings transition, and by the activator and deactivator, which describe
 * themselves as a transition to or from "nothing enabled".
 *
 * @since 1.0.4
 * @param mixed $old_value Value before the change. Not necessarily an array: the option is
 *                         created empty by AIOPOD_Admin_Settings::initialize_settings().
 * @param mixed $value     Value after it.
 */
function aiopod_update_core_term_counts( $old_value, $value ) {
	$changed = array();

	foreach ( array( 'aiopod_podcasts', 'aiopod_episodes' ) as $post_type ) {
		// Both sides are read through aiopod_get_wp_taxonomies(), so the
		// `aiopod_wp_taxonomy_object_types` filter decides here exactly as it decides at
		// registration. A site that uses it to attach categories to podcasts alone must not
		// have its episodes counted into those terms
		$was = aiopod_get_wp_taxonomies( $post_type, is_array( $old_value ) ? $old_value : array() );
		$is  = aiopod_get_wp_taxonomies( $post_type, is_array( $value ) ? $value : array() );

		// The taxonomy was registered at `init`, from the setting as it stood before this
		// change, and `_update_post_term_count()` reads the object types straight off that
		// registration. Recounting first would just rewrite the number we are correcting
		foreach ( array_diff( $is, $was ) as $taxonomy ) {
			register_taxonomy_for_object_type( $taxonomy, $post_type );
			$changed[ $taxonomy ][] = $post_type;
		}

		foreach ( array_diff( $was, $is ) as $taxonomy ) {
			unregister_taxonomy_for_object_type( $taxonomy, $post_type );
			$changed[ $taxonomy ][] = $post_type;
		}
	}

	// Only a transition changes what the terms count. A settings save that leaves the
	// checkboxes alone leaves this empty and recounts nothing
	foreach ( $changed as $taxonomy => $post_types ) {
		aiopod_recount_core_terms( $taxonomy, array_unique( $post_types ) );
	}
}
