<?php

/**
 * Episodes List.
 *
 * @link    https://aiopodcasting.com
 * @since   1.0.0
 *
 * @package AiO_Podcast_Studio
 */

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

/**
 * AIOPOD_Public_Episodes class.
 *
 * @since 1.0.0
 */
class AIOPOD_Public_Episodes {

	/**
	 * The only instance of the class.
	 *
	 * @since  1.0.0
	 * @access private
	 * @static
	 * @var    AIOPOD_Public_Episodes|null
	 */
	private static $instance = null;

	/**
	 * The default shortcode attribute values.
	 *
	 * @since  1.0.0
	 * @access private
	 * @var    array
	 */
	private $defaults = array();

	/**
	 * Create a new instance of the main class.
	 *
	 * @since  1.0.0
	 * @static
	 * @return AIOPOD_Public_Episodes
	 */
	public static function get_instance() {
		if ( self::$instance === null ) {
            self::$instance = new self();
        }

		return self::$instance;
	}

	/**
	 * Prevent cloning of the singleton instance.
	 *
	 * @since  1.0.0
	 * @access private
	 */
	private function __clone() {}

	/**
	 * Prevent unserializing of the singleton instance.
	 *
	 * @since 1.0.0
	 */
	public function __wakeup() {}

	/**
	 * Get things started.
	 *
	 * @since  1.0.0
	 * @access private
	 */
	private function __construct() {
		// Register shortcodes
		add_shortcode( 'aiopod_episodes', array( $this, 'render_shortcode_episodes' ) );
	}

	/**
	 * Render the shortcode [aiopod_episodes].
	 *
	 * @since 1.0.0
	 * @param array $atts An associative array of attributes.
	 */
	public function render_shortcode_episodes( $atts ) {
		$attributes = shortcode_atts( $this->get_defaults(), $atts, 'aiopod_episodes' );
		$attributes = $this->resolve_attributes( $attributes );	
		
		// Enqueue dependencies
		wp_enqueue_style( AIOPOD_PLUGIN_SLUG . '-public' );
		wp_enqueue_script( AIOPOD_PLUGIN_SLUG . '-public' );
		
		// Output
		$content = $this->get_content( $attributes );
		return $content;	
	}

	/**
	 * Load episodes using Ajax.
	 *
	 * @since 1.0.0
	 */
	public function ajax_callback_load_episodes() {
		check_ajax_referer( 'aiopod_ajax_nonce', 'security' );

		$attributes = $this->sanitize_ajax_attributes();
		$content    = $this->get_content( $attributes );

		wp_send_json_success( array( 'html' => $content ) );
	}

	/**
	 * Return the allowed $_POST keys and their sanitization types for AJAX requests.
	 *
	 * Derived from aiopod_get_shortcode_fields() so premium-added fields are
	 * automatically included. Internal/pagination state fields are appended below.
	 *
	 * @since  1.0.0
	 * @return array Map of attribute key to sanitization type string.
	 */
	private function get_allowed_attributes() {
		$type_map = array(
			'checkbox'     => 'bool',
			'number'       => 'int',
			'select'       => 'key',
			'text'         => 'text',
			'podcasts'     => 'int',
			'categories'   => 'array_int',
			'participants' => 'array_int',
			'tags'         => 'array_int',
		);

		$fields  = aiopod_get_shortcode_fields();
		$allowed = array();

		foreach ( $fields['episodes']['sections'] as $section ) {
			foreach ( $section['fields'] as $field ) {
				if ( ! isset( $type_map[ $field['type'] ] ) ) {
					continue;
				}

				$allowed[ $field['name'] ] = $type_map[ $field['type'] ];
			}
		}

		// Internal / pagination state — not defined in aiopod_get_shortcode_fields()
		$allowed['source']           = 'key';
		$allowed['title']            = 'text';
		$allowed['count']            = 'int';
		$allowed['paged']            = 'int';
		$allowed['max_num_pages']    = 'int';
		$allowed['include']          = 'text';
		$allowed['exclude']          = 'text';
		$allowed['season']           = 'int';
		$allowed['interactive_mode'] = 'int';
		$allowed['hide_if_empty']    = 'int';
		$allowed['player_hotkeys']   = 'bool';
		$allowed['search_query']     = 'text';

		return $allowed;
	}

	/**
	 * Sanitize AJAX $_POST attributes against the allowed list.
	 *
	 * @since  1.0.0
	 * @return array Sanitized attributes.
	 */
	private function sanitize_ajax_attributes() {
		$attributes = array();

		foreach ( $this->get_allowed_attributes() as $key => $type ) {
			if ( ! isset( $_POST[ $key ] ) ) {
				continue;
			}

			switch ( $type ) {
				case 'int':
					$attributes[ $key ] = (int) wp_unslash( $_POST[ $key ] );
					break;

				case 'bool':
					$raw = wp_unslash( $_POST[ $key ] );

					if ( 'false' === $raw ) {
						$attributes[ $key ] = 0;
					} elseif ( 'true' === $raw ) {
						$attributes[ $key ] = 1;
					} else {
						$attributes[ $key ] = (int) $raw;
					}
					break;

				case 'key':
					$attributes[ $key ] = sanitize_key( wp_unslash( $_POST[ $key ] ) );
					break;

				case 'text':
					$attributes[ $key ] = sanitize_text_field( wp_unslash( $_POST[ $key ] ) );
					break;

				case 'array_int':
					$attributes[ $key ] = array_map( 'intval', wp_unslash( (array) $_POST[ $key ] ) );
					break;
			}
		}
		
		return $attributes;
	}
	
	/**
	 * Get the html output.
	 *
	 * @since  1.0.0
	 * @param  array  $attributes An associative array of attributes.
	 * @return string $content    HTML output.
	 */
	public function get_content( $attributes ) {		
		// Define the query
		$args = array(				
			'post_type'      => 'aiopod_episodes',
			'posts_per_page' => ! empty( $attributes['limit'] ) ? (int) $attributes['limit'] : 100,
			'post_status'    => array( 'publish' )
		);

		if ( ! empty( $attributes['podcast'] ) ) { // Podcast ID
			$args['post_parent'] = (int) $attributes['podcast'];			
		}

		if ( 'none' !== $attributes['pagination'] ) { // Pagination
			$args['paged'] = (int) $attributes['paged'];
		}
		
		if ( ! empty( $attributes['search_query'] ) ) { // Search
			$args['s'] = sanitize_text_field( $attributes['search_query'] );
		}
		
		if ( isset( $attributes['include'] ) ) { // Include episode IDs
			$include = is_array( $attributes['include'] ) ? array_map( 'intval', $attributes['include'] ) : array_map( 'intval', explode( ',', $attributes['include'] ) );
			$include = array_filter( $include );

			if ( ! empty( $include ) ) {
				$args['post__in'] = $include;
			}
		}

		if ( isset( $attributes['exclude'] ) ) { // Exclude episode IDs
			$exclude = is_array( $attributes['exclude'] ) ? array_map( 'intval', $attributes['exclude'] ) : array_map( 'intval', explode( ',', $attributes['exclude'] ) );
			$exclude = array_filter( $exclude );

			if ( ! empty( $exclude ) ) {
				$args['post__not_in'] = $exclude;
			}
		}

		// Taxonomy Parameters
		$tax_queries = array();		

		if ( ! empty( $attributes['tags'] ) ) { // Tags
			$tags = array_map( 'intval', ( is_array( $attributes['tags'] ) ? $attributes['tags'] : explode( ',', $attributes['tags'] ) ) );
			$tags = array_filter( $tags );

			if ( ! empty( $tags ) ) {
				$tax_queries[] = array(
					'taxonomy'         => 'aiopod_tags',
					'field'            => 'term_id',
					'terms'            => $tags,
					'include_children' => false
				);
			}
		}

		if ( ! empty( $attributes['participants'] ) ) { // Participants
			$participants = array_map( 'intval', ( is_array( $attributes['participants'] ) ? $attributes['participants'] : explode( ',', $attributes['participants'] ) ) );
			$participants = array_filter( $participants );

			if ( ! empty( $participants ) ) {
				$tax_queries[] = array(
					'taxonomy'         => 'aiopod_participants',
					'field'            => 'term_id',
					'terms'            => $participants,
					'include_children' => false
				);
			}
		}
		
		$count_tax_queries = count( $tax_queries );
		if ( $count_tax_queries ) {
			$args['tax_query'] = ( $count_tax_queries > 1 ) ? array_merge( array( 'relation' => 'AND' ), $tax_queries ) : $tax_queries;
		}

		// Custom Field (post meta) Parameters
		$meta_queries = array();

		if ( ! empty( $attributes['type'] ) ) { // Episode Type
			$allowed_types = aiopod_get_episode_types();
			$allowed_types = array_keys( $allowed_types );

			$types = is_array( $attributes['type'] ) ? $attributes['type'] : explode( ',', $attributes['type'] );
			$types = array_map( 'sanitize_key', array_map( 'trim', $types ) );
			$types = array_intersect( $types, $allowed_types );

			if ( ! empty( $types ) && count( $types ) !== count( $allowed_types ) ) {
				$meta_queries[] = array(
					'key'     => 'episode_type',
					'value'   => $types,
					'compare' => 'IN'
				);
			}
		}

		if ( ! empty( $attributes['featured'] ) ) { // Featured			
			$meta_queries[] = array(
				'key'     => 'is_featured',
				'value'   => 1,
				'compare' => '='
			);
		}

		if ( ! empty( $attributes['season'] ) ) { // Season Number
			$season = absint( $attributes['season'] );
			
			if ( $season > 0 ) {
				$meta_queries[] = array(
					'key'     => 'season_number',
					'value'   => $season,
					'compare' => '=',
					'type'    => 'NUMERIC'
				);
			}
		}
		
		$count_meta_queries = count( $meta_queries );
		if ( $count_meta_queries ) {
			$args['meta_query'] = ( $count_meta_queries > 1 ) ? array_merge( array( 'relation' => 'AND' ), $meta_queries ) : $meta_queries;
		}
		
		// Order & Orderby Parameters
		$orderby = sanitize_text_field( $attributes['orderby'] );
		$order   = sanitize_text_field( $attributes['order'] );

		switch ( $orderby ) {
			case 'popularity':
				$args['orderby']  = 'meta_value_num';
				$args['meta_key'] = 'total_downloads_plays';
				$args['order']    = $order;
				break;

			case 'downloads':
				$args['orderby']  = 'meta_value_num';
				$args['meta_key'] = 'total_downloads';
				$args['order']    = $order;
				break;

			case 'plays':
				$args['orderby']  = 'meta_value_num';
				$args['meta_key'] = 'total_plays';
				$args['order']    = $order;
				break;

			case 'rand':
				$seed = aiopod_get_orderby_rand_seed();
				$args['orderby']  = 'RAND(' . $seed . ')';
				break;

			default:
				$args['orderby']  = $orderby;
				$args['order']    = $order;
		}
	
		$args = apply_filters( 'aiopod_query_args', $args, $attributes );
		$aiopod_query = new WP_Query( $args );		

		// Process output
		if ( $aiopod_query->have_posts() ) {		
			if ( 'none' === $attributes['pagination'] ) {
				$attributes['count'] = $aiopod_query->post_count;
			} else {
				$attributes['count'] = $aiopod_query->found_posts;
			}

			if ( 'none' !== $attributes['pagination'] ) {
				$attributes['max_num_pages'] = $aiopod_query->max_num_pages;
			}
		}
			
		ob_start();
		include apply_filters( 'aiopod_load_template', AIOPOD_PLUGIN_DIR . 'public/templates/episodes/episodes-' . sanitize_file_name( $attributes['layout'] ) . '.php', $attributes );
		$content = ob_get_clean();			

		return $content;	
	}

	/**
	 * Resolves attributes into a valid, final configuration.
	 * 
	 * This method guarantees that the returned attributes array is safe,
	 * consistent, and ready for query building and rendering.
	 *
	 * @since  1.0.0
	 * @param  array  $attributes Raw attributes merged from defaults and user input.
	 * @return array              Fully resolved attributes array.
	 */
	private function resolve_attributes( $attributes ) {
		// Layout
		if ( ! in_array( $attributes['layout'], array( 'grid', 'list', 'compact', 'slider' ) ) ) {
			$attributes['layout'] = 'grid';
		}

		// Player
		if ( 'compact' === $attributes['layout'] ) {
			$attributes['enable_player'] = 1;
		}

		if ( ! empty( $attributes['enable_player'] ) ) {
			$attributes['interactive_mode'] = 1;
		}

		// Pagination
		if ( empty( $attributes['pagination'] ) ) {
			$attributes['pagination'] = $this->defaults['pagination'];
		}

		// Filters
		if (
			empty( $attributes['filters_keyword'] ) &&
			empty( $attributes['filters_tags'] ) &&
			empty( $attributes['filters_season'] ) &&
			empty( $attributes['filters_sort'] )
		) {
			$attributes['enable_filters'] = 0;
		}

		if ( ! empty( $attributes['enable_filters'] ) ) {
			// Search
			if ( ! empty( $attributes['filters_keyword'] ) ) {
				$search_query = isset( $_GET['q'] ) ? sanitize_text_field( wp_unslash( $_GET['q'] ) ) : '';
				if ( ! empty( $search_query ) ) {
					$attributes['search_query'] = $search_query;
				}
			}

			// Tags
			if ( ! empty( $attributes['filters_tags'] ) ) {
				$tags = isset( $_GET['topic'] ) ? array_filter( array_map( 'absint', (array) wp_unslash( $_GET['topic'] ) ) ) : array();
				if ( ! empty( $tags ) ) {
					$attributes['tags'] = $tags;
				}
			}

			// Season
			if ( ! empty( $attributes['filters_season'] ) ) {
				$season = isset( $_GET['season'] ) ? absint( wp_unslash( $_GET['season'] ) ) : 0;
				if ( $season > 0 ) {
					$attributes['season'] = $season;
				}
			}

			// Sort
			if ( ! empty( $attributes['filters_sort'] ) ) {
				$sort = isset( $_GET['sort'] ) ? sanitize_key( wp_unslash( $_GET['sort'] ) ) : '';
				if ( ! empty( $sort ) ) {
					$sort = array_filter( array_map( 'trim', explode( '-', $sort ) ) );
		
					if ( ! empty( $sort ) ) {
						$attributes['orderby'] = $sort[0];
						
						if ( count( $sort ) > 1 ) {
							$attributes['order'] = $sort[1];
						}
					}
				}
			}

			// Pagination
			if ( 'ajax' === $attributes['filters_mode'] ) {
				if ( ! empty( $attributes['pagination'] ) ) {
					$attributes['pagination'] = 'loadmore';
				}
			}
		}

		// Return resolved array
		return $attributes;
	}

	/**
	 * Get the default shortcode attribute values.
	 *
	 * @since  1.0.0
	 * @return array An associative array of attributes.
	 */
	public function get_defaults() {	
		if ( empty( $this->defaults ) ) {	
			$episodes_settings   = aiopod_get_option( 'aiopod_episodes_settings' ); 	
			$pagination_settings = aiopod_get_option( 'aiopod_pagination_settings' ); 
			$player_settings     = aiopod_get_option( 'aiopod_player_settings' ); 

			$fields = aiopod_get_shortcode_fields();

			foreach ( $fields['episodes']['sections'] as $section ) {
				foreach ( $section['fields'] as $field ) {
					$this->defaults[ $field['name'] ] = $field['value'];
				}
			}
		
			$this->defaults['source']             = 'episodes';
			$this->defaults['title']              = '';				
			$this->defaults['count']              = 0;
			$this->defaults['paged']              = aiopod_get_page_number();	
			$this->defaults['max_num_pages']      = 1;			
			$this->defaults['include']            = '';	
			$this->defaults['exclude']            = '';
			$this->defaults['type']               = '';
			$this->defaults['season']             = '';
			$this->defaults['interactive_mode']   = 0;
			$this->defaults['player_autoadvance'] = $player_settings['autoadvance'];
			$this->defaults['player_loop']        = $player_settings['loop'];
			$this->defaults['player_hotkeys']     = $player_settings['hotkeys'];
			$this->defaults['pagination']         = ! empty( $episodes_settings['pagination'] ) ? $episodes_settings['pagination'] : $pagination_settings['layout'];
			$this->defaults['hide_if_empty']      = 0;
		}

		return $this->defaults;
	}
	
}
