<?php

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

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

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

	/**
	 * The only instance of the class.
	 *
	 * @since  1.0.0
	 * @access private
	 * @static
	 * @var    AIOPOD_Public_Podcasts|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_Podcasts
	 */
	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_podcasts', array( $this, 'render_shortcode_podcasts' ) );
	}

	/**
	 * Render the shortcode [aiopod_podcasts].
	 *
	 * @since 1.0.0
	 * @param array $atts An associative array of attributes.
	 */
	public function render_shortcode_podcasts( $atts ) {		
		$attributes = shortcode_atts( $this->get_defaults(), $atts, 'aiopod_podcasts' );
		$attributes = $this->resolve_attributes( $attributes );			
		
		// Enqueue style dependencies
		wp_enqueue_style( AIOPOD_PLUGIN_SLUG . '-public' );
		wp_enqueue_script( AIOPOD_PLUGIN_SLUG . '-public' );

		// Process output
		$content = $this->get_content( $attributes );
		
		return $content;
	}

	/**
	 * Load podcasts using Ajax.
	 *
	 * @since 1.0.0
	 */
	public function ajax_callback_load_podcasts() {
		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['podcasts']['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['hide_if_empty'] = 'int';

		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_podcasts',
			'posts_per_page' => ! empty( $attributes['limit'] ) ? (int) $attributes['limit'] : 100,
			'post_status'    => array( 'publish' )
		);

		if ( 'none' !== $attributes['pagination'] ) { // Pagination
			$args['paged'] = (int) $attributes['paged'];
		}

		if ( isset( $attributes['include'] ) ) { // Include podcast 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 podcast 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['categories'] ) ) { // Categories
			$categories = is_array( $attributes['categories'] ) ? array_map( 'intval', $attributes['categories'] ) : array_map( 'intval', explode( ',', $attributes['categories'] ) );
			$categories = array_filter( $categories );

			if ( ! empty( $categories ) ) {
				$tax_queries[] = array(
					'taxonomy'         => 'aiopod_categories',
					'field'            => 'term_id',
					'terms'            => $categories,
					'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;
		}
		
		// 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/podcasts/podcasts-grid.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 ) {
		// Pagination
		if ( empty( $attributes['pagination'] ) ) {
			$attributes['pagination'] = $this->defaults['pagination'];
		}

		// 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 ) ) {	
			$podcasts_settings   = aiopod_get_option( 'aiopod_podcasts_settings' ); 	
			$pagination_settings = aiopod_get_option( 'aiopod_pagination_settings' ); 

			$fields = aiopod_get_shortcode_fields();

			foreach ( $fields['podcasts']['sections'] as $section ) {
				foreach ( $section['fields'] as $field ) {
					$this->defaults[ $field['name'] ] = $field['value'];
				}
			}
			
			$this->defaults['source']        = 'podcasts';
			$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['pagination']    = ! empty( $podcasts_settings['pagination'] ) ? $podcasts_settings['pagination'] : $pagination_settings['layout'];
			$this->defaults['hide_if_empty'] = 0;
		}

		return $this->defaults;
	}
	
}
