<?php

/**
 * Rank Math SEO.
 *
 * @link    https://aiopodcasting.com
 * @since   1.0.0
 *
 * @package AiO_Podcast_Studio
 */

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

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

	/**
	 * Updates the page titles on the plugin pages.
	 *
	 * @since  1.0.0
	 * @param  string $title Default page title.
	 * @return string $title The filtered page title.
	 */
	public function meta_title( $title ) {
		global $post;

		// Category Page
		if ( $slug = get_query_var( 'aiopod_category' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_categories' ) ) {
				// Get the global title template for the category pages 
				$title_template = RankMath\Helper::get_settings( 'titles.tax_aiopod_categories_title' );

				// Get the title template for the current category page (if available)
				$current_term_title_template = get_term_meta( $term->term_id, 'rank_math_title', true );
				if ( ! empty( $current_term_title_template ) ) {
					$title_template = $current_term_title_template;
				}
				
				if ( ! empty( $title_template ) ) {
					$title = RankMath\Helper::replace_vars( $title_template, $term );
				}
			}
		}

		// Tag Page
		if ( $slug = get_query_var( 'aiopod_tag' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_tags' ) ) {
				// Get the global title template for the tag pages 
				$title_template = RankMath\Helper::get_settings( 'titles.tax_aiopod_tags_title' );			

				// Get the title template for the current tag page (if available)
				$current_term_title_template = get_term_meta( $term->term_id, 'rank_math_title', true );
				if ( ! empty( $current_term_title_template ) ) {
					$title_template = $current_term_title_template;
				}
				
				if ( ! empty( $title_template ) ) {
					$title = RankMath\Helper::replace_vars( $title_template, $term );
				}
			}
		}

		// Participant Page
		if ( $slug = get_query_var( 'aiopod_participant' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_participants' ) ) {
				// Get the global title template for the participant pages 
				$title_template = RankMath\Helper::get_settings( 'titles.tax_aiopod_participants_title' );			

				// Get the title template for the current participant page (if available)
				$current_term_title_template = get_term_meta( $term->term_id, 'rank_math_title', true );
				if ( ! empty( $current_term_title_template ) ) {
					$title_template = $current_term_title_template;
				}
				
				if ( ! empty( $title_template ) ) {
					$title = RankMath\Helper::replace_vars( $title_template, $term );
				}
			}
		}

		return $title;
	}

	/**
	 * Updates the page description on the plugin pages.
	 *
	 * @since  1.0.0
	 * @param  string $description Default page description.
	 * @return string $description The filtered page description.
	 */
	public function meta_description( $description ) {
		global $post;

		// Category Page
		if ( $slug = get_query_var( 'aiopod_category' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_categories' ) ) {
				// Get the global description template for the category pages 
				$description_template = RankMath\Helper::get_settings( 'titles.tax_aiopod_categories_description' );

				// Get the description template for the current category page (if available)
				$current_term_description_template = get_term_meta( $term->term_id, 'rank_math_description', true );
				if ( ! empty( $current_term_description_template ) ) {
					$description_template = $current_term_description_template;
				}
				
				if ( ! empty( $description_template ) ) {
					$description = RankMath\Helper::replace_vars( $description_template, $term );
				}
			}
		}

		// Tag Page
		if ( $slug = get_query_var( 'aiopod_tag' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_tags' ) ) {
				// Get the global description template for the tag pages 
				$description_template = RankMath\Helper::get_settings( 'titles.tax_aiopod_tags_description' );

				// Get the description template for the current tag page (if available)
				$current_term_description_template = get_term_meta( $term->term_id, 'rank_math_description', true );
				if ( ! empty( $current_term_description_template ) ) {
					$description_template = $current_term_description_template;
				}

				if ( ! empty( $description_template ) ) {
					$description = RankMath\Helper::replace_vars( $description_template, $term );
				}
			}
		}

		// Participant Page
		if ( $slug = get_query_var( 'aiopod_participant' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_participants' ) ) {
				// Get the global description template for the participant pages 
				$description_template = RankMath\Helper::get_settings( 'titles.tax_aiopod_participants_description' );

				// Get the description template for the current participant page (if available)
				$current_term_description_template = get_term_meta( $term->term_id, 'rank_math_description', true );
				if ( ! empty( $current_term_description_template ) ) {
					$description_template = $current_term_description_template;
				}

				if ( ! empty( $description_template ) ) {
					$description = RankMath\Helper::replace_vars( $description_template, $term );
				}
			}
		}

		return $description;
	}

	/**
	 * Updates the canonical URL on the plugin pages.
	 *
	 * @since  1.0.0
	 * @param  string $canonical_url The canonical URL.
	 * @return string $canonical_url The filtered canonical URL.
	 */
	public function canonical_url( $canonical_url ) {	
		// Category Page
		if ( $slug = get_query_var( 'aiopod_category' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_categories' ) ) {
				$canonical_url = aiopod_get_category_page_url( $term );
			}
		}				

		// Tag Page
		if ( $slug = get_query_var( 'aiopod_tag' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_tags' ) ) {
				$canonical_url = aiopod_get_tag_page_url( $term );
			}
		}	
		
		// Participant Page
		if ( $slug = get_query_var( 'aiopod_participant' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_participants' ) ) {
				$canonical_url = aiopod_get_participant_page_url( $term );
			}
		}
			
		return $canonical_url;	
	}

	/**
	 * Updates the Open Graph image URL on the plugin pages.
	 *
	 * @since  1.0.0
	 * @param  string $image_url The Image URL.
	 * @return string $image_url The filtered image URL.
	 */
	public function opengraph_image_url( $image_url ) {
		// Category Page
		if ( $slug = get_query_var( 'aiopod_category' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_categories' ) ) {
				// Get the opengraph image URL for the current category page (if available)
				$opengraph_image_url = get_term_meta( $term->term_id, 'rank_math_facebook_image', true );
				if ( ! empty( $opengraph_image_url ) ) {
					$image_url = $opengraph_image_url;
				} else {
					// Get the actual image URL for the current category page (if available)
					$image_data = aiopod_get_image(	array(
						'term_id' => $term->term_id,
						'size'    => 'large',
						'alt'     => ''
					) );
					
					if ( ! empty( $image_data['src'] ) ) {
						$image_url = $image_data['src'];
					}
				}
			}
		}

		// Tag Page
		if ( $slug = get_query_var( 'aiopod_tag' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_tags' ) ) {
				// Get the opengraph image URL for the current tag page (if available)
				$opengraph_image_url = get_term_meta( $term->term_id, 'rank_math_facebook_image', true );
				if ( ! empty( $opengraph_image_url ) ) {
					$image_url = $opengraph_image_url;
				}
			}
		}

		// Participant Page
		if ( $slug = get_query_var( 'aiopod_participant' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_participants' ) ) {
				// Get the opengraph image URL for the current participant page (if available)
				$opengraph_image_url = get_term_meta( $term->term_id, 'rank_math_facebook_image', true );
				if ( ! empty( $opengraph_image_url ) ) {
					$image_url = $opengraph_image_url;
				}
			}
		}

		return $image_url;
	}

	/**
	 * Filters the Breadcrumb items.
	 *
	 * @since  1.0.0
	 * @param  array $crumbs Array of crumbs.
	 * @return array $crumbs The filtered array of crumbs.
	 */
	public function breadcrumb_items( $crumbs ) {
		// Explicit CPT → option key mapping (future-proof)
		$archive_map = array(
			'aiopod_podcasts' => 'podcast_archive_page',
			'aiopod_episodes' => 'episode_archive_page'
		);

		// Single Podcast and Episode Pages
		if ( is_singular( array_keys( $archive_map ) ) ) {
			// The queried object, not the global $post, so a theme calling the breadcrumbs from
			// inside a secondary loop still gets the trail for the page being viewed
			return $this->add_archive_page_crumbs( $crumbs, get_post_type( get_queried_object_id() ), $archive_map );
		}

		// On every page the plugin manages, fill in any ancestor Rank Math has not supplied
		// itself, so a page nested under the podcast archive reads as the whole path to it
		$pages_settings = aiopod_get_option( 'aiopod_pages_settings' );
		$page_id        = get_queried_object_id();
		$plugin_pages   = array( 'category', 'tag', 'participant', 'podcast_archive_page', 'episode_archive_page' );

		foreach ( $plugin_pages as $page_key ) {
			if ( ! empty( $pages_settings[ $page_key ] ) && $page_id === (int) $pages_settings[ $page_key ] ) {
				$crumbs = $this->add_page_ancestor_crumbs( $crumbs, $page_id );

				break;
			}
		}

		// Category Page
		if ( $slug = get_query_var( 'aiopod_category' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_categories' ) ) {
				// Include the parent categories if available
				if ( $ancestors = get_ancestors( $term->term_id, 'aiopod_categories' ) ) {
					$ancestors = array_reverse( $ancestors );

					foreach ( $ancestors as $term_id ) {
						if ( $parent_term = get_term_by( 'term_id', $term_id, 'aiopod_categories' ) ) {
							$crumbs[] = array(
								0 => $parent_term->name,
								1 => aiopod_get_category_page_url( $parent_term )
							);
						}
					}
				}

				// Include the current category page. The URL matters: Rank Math leaves a crumb
				// out of its BreadcrumbList entirely when it has no URL
				$crumbs[] = array(
					0 => $term->name,
					1 => aiopod_get_category_page_url( $term )
				);	
			}
		}
			
		// Tag Page
		if ( $slug = get_query_var( 'aiopod_tag' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_tags' ) ) {
				$crumbs[] = array(
					0 => $term->name,
					1 => aiopod_get_tag_page_url( $term )
				);
			}
		}

		// Participant Page
		if ( $slug = get_query_var( 'aiopod_participant' ) ) {
			if ( $term = get_term_by( 'slug', $slug, 'aiopod_participants' ) ) {
				$crumbs[] = array(
					0 => $term->name,
					1 => aiopod_get_participant_page_url( $term )
				);
			}
		}

		return $crumbs;
	}

	/**
	 * Build the trail of a single podcast or episode.
	 *
	 * An episode reads `Home > Podcasts > Show > Episode`: the show it belongs to is the useful step,
	 * and the episode archive is not on the way to it, so that crumb is replaced rather than kept.
	 * Rank Math already walks `post_parent`, which is where an episode keeps its show, so the show
	 * crumb is usually there already and the dedupe leaves it where it is.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  array  $crumbs      Array of crumbs.
	 * @param  string $post_type   Post type of the single being viewed.
	 * @param  array  $archive_map CPT → archive page option key.
	 * @return array  $crumbs      The filtered array of crumbs.
	 */
	private function add_archive_page_crumbs( $crumbs, $post_type, $archive_map ) {
		if ( empty( $archive_map[ $post_type ] ) ) {
			return $crumbs;
		}

		if ( 'aiopod_episodes' === $post_type ) {
			$episode_id = get_queried_object_id();
			$podcast_id = aiopod_get_episode_podcast_id( $episode_id );

			// An episode with no published show of its own keeps the episode archive trail
			if ( $podcast_id ) {
				$chain = $this->get_archive_crumbs( 'aiopod_podcasts', $archive_map );

				if ( ! empty( $chain ) ) {
					$chain[] = $this->get_page_crumb( $podcast_id );

					$crumbs = $this->remove_archive_crumb( $crumbs, $post_type );

					return $this->insert_crumb_chain( $crumbs, $chain );
				}
			}

			$crumbs = $this->remove_unpublished_podcast_crumb( $crumbs, $episode_id );
		}

		$chain = $this->get_archive_crumbs( $post_type, $archive_map, true );

		if ( empty( $chain ) ) {
			return $crumbs;
		}

		// Where Rank Math drew its own archive crumb, it is left alone and only the URL is redirected
		$page_url    = $chain[ count( $chain ) - 1 ][1];
		$archive_url = get_post_type_archive_link( $post_type );

		if ( $archive_url ) {
			foreach ( $crumbs as $index => $crumb ) {
				if ( ! empty( $crumb[1] ) && untrailingslashit( $crumb[1] ) === untrailingslashit( $archive_url ) ) {
					$crumbs[ $index ][1] = $page_url;

					return $crumbs;
				}
			}
		}

		return $this->insert_crumb_chain( $crumbs, $chain );
	}

	/**
	 * Build the crumbs that stand for a post type's archive.
	 *
	 * The assigned archive page where there is one — with its own ancestors, so a nested page reads
	 * as the whole path to it — and the post type's own archive where there is not.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  string $post_type   Post type whose archive is wanted.
	 * @param  array  $archive_map CPT → archive page option key.
	 * @param  bool   $page_only   Return nothing rather than fall back to the post type archive.
	 * @return array               Crumbs, outermost first. Empty when there is no archive to point at.
	 */
	private function get_archive_crumbs( $post_type, $archive_map, $page_only = false ) {
		$chain = array();

		$pages_settings = aiopod_get_option( 'aiopod_pages_settings' );
		$option_key     = isset( $archive_map[ $post_type ] ) ? $archive_map[ $post_type ] : '';

		if ( $option_key && ! empty( $pages_settings[ $option_key ] ) ) {
			$page_id = (int) $pages_settings[ $option_key ];
			$page    = get_post( $page_id );

			if ( $page && 'publish' === $page->post_status && get_permalink( $page_id ) ) {
				foreach ( array_reverse( get_post_ancestors( $page_id ) ) as $ancestor_id ) {
					$ancestor = get_post( $ancestor_id );

					if ( $ancestor && 'publish' === $ancestor->post_status ) {
						$chain[] = $this->get_page_crumb( $ancestor_id );
					}
				}

				$chain[] = $this->get_page_crumb( $page_id );

				return $chain;
			}
		}

		if ( $page_only ) {
			return $chain;
		}

		$url = get_post_type_archive_link( $post_type );
		$obj = get_post_type_object( $post_type );

		if ( ! $url || ! $obj ) {
			return $chain;
		}

		// The same label Rank Math uses for a post type archive crumb of its own
		$chain[] = array(
			0 => $obj->labels->name,
			1 => $url
		);

		return $chain;
	}

	/**
	 * Drop the post type archive crumb Rank Math drew for a post type.
	 *
	 * Rank Math marks nothing, so the archive URL is what identifies the crumb.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  array  $crumbs    Array of crumbs.
	 * @param  string $post_type Post type whose archive crumb is not wanted.
	 * @return array  $crumbs    The filtered array of crumbs.
	 */
	private function remove_archive_crumb( $crumbs, $post_type ) {
		$archive_url = get_post_type_archive_link( $post_type );

		if ( ! $archive_url ) {
			return $crumbs;
		}

		foreach ( $crumbs as $index => $crumb ) {
			if ( ! empty( $crumb[1] ) && untrailingslashit( $crumb[1] ) === untrailingslashit( $archive_url ) ) {
				unset( $crumbs[ $index ] );

				return array_values( $crumbs );
			}
		}

		return $crumbs;
	}

	/**
	 * Take an unpublished show out of an episode's trail.
	 *
	 * Both SEO plugins build a post's ancestors from `post_parent` without checking status, so a
	 * podcast reverted to draft still appears in the breadcrumbs of its published episodes, named
	 * and linked to an address that answers 404. Declining to add the crumb is not enough — it is
	 * already there, and has to be taken out.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  array $crumbs     Array of crumbs.
	 * @param  int   $episode_id ID of the episode being viewed.
	 * @return array $crumbs     The filtered array of crumbs.
	 */
	private function remove_unpublished_podcast_crumb( $crumbs, $episode_id ) {
		$podcast_id = (int) get_post_field( 'post_parent', $episode_id );

		// `get_post( 0 )` falls back to the global post, so the parent has to be checked first
		if ( $podcast_id <= 0 ) {
			return $crumbs;
		}

		$podcast = get_post( $podcast_id );

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

		$index = $this->find_crumb( $crumbs, $this->get_page_crumb( $podcast_id ) );

		if ( null === $index ) {
			return $crumbs;
		}

		unset( $crumbs[ $index ] );

		return array_values( $crumbs );
	}

	/**
	 * Fill in the ancestors of one of the plugin's overview pages.
	 *
	 * On the category, tag and participant pages the plugin takes the trail over, so any ancestor
	 * Rank Math has not supplied itself has to be filled in here.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  array $crumbs  Array of crumbs.
	 * @param  int   $page_id ID of the page being viewed.
	 * @return array $crumbs  The filtered array of crumbs.
	 */
	private function add_page_ancestor_crumbs( $crumbs, $page_id ) {
		$chain = array();

		foreach ( array_reverse( get_post_ancestors( $page_id ) ) as $ancestor_id ) {
			$ancestor = get_post( $ancestor_id );

			if ( $ancestor && 'publish' === $ancestor->post_status ) {
				$chain[] = $this->get_page_crumb( $ancestor_id );
			}
		}

		return $this->insert_crumb_chain( $crumbs, $chain );
	}

	/**
	 * Insert a top-down chain of crumbs into the trail, skipping any already in it.
	 *
	 * The cursor is what keeps a partly-built trail in order: a crumb that is already present moves
	 * the insertion point to just after itself, so anything still missing below it lands in the
	 * right place rather than all of it being stacked behind the home crumb.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  array $crumbs Array of crumbs.
	 * @param  array $chain  Crumbs to insert, outermost first.
	 * @return array $crumbs The filtered array of crumbs.
	 */
	private function insert_crumb_chain( $crumbs, $chain ) {
		$position = $this->get_crumb_insert_position( $crumbs );

		foreach ( $chain as $crumb ) {
			$existing = $this->find_crumb( $crumbs, $crumb );

			if ( null !== $existing ) {
				$position = $existing + 1;

				continue;
			}

			array_splice( $crumbs, $position, 0, array( $crumb ) );

			$position++;
		}

		return $crumbs;
	}

	/**
	 * Build a Rank Math breadcrumb for a post or page.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  int   $post_id ID of the post or page.
	 * @return array          Breadcrumb for it.
	 */
	private function get_page_crumb( $post_id ) {
		// Rank Math's own Breadcrumb Title field, so an inserted crumb reads the way Rank Math
		// would have titled it had it built the crumb itself
		$text = get_post_meta( $post_id, 'rank_math_breadcrumb_title', true );

		if ( empty( $text ) ) {
			$text = get_the_title( $post_id );
		}

		return array(
			0 => $text,
			1 => get_permalink( $post_id )
		);
	}

	/**
	 * Find a crumb already in the trail.
	 *
	 * Rank Math crumbs carry no post ID, so the URL is all there is to match on.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  array $crumbs Array of crumbs.
	 * @param  array $needle Crumb to look for.
	 * @return int|null      Index of the crumb, or null when it is not in the trail.
	 */
	private function find_crumb( $crumbs, $needle ) {
		if ( empty( $needle[1] ) ) {
			return null;
		}

		foreach ( $crumbs as $index => $crumb ) {
			if ( ! empty( $crumb[1] ) && untrailingslashit( $crumb[1] ) === untrailingslashit( $needle[1] ) ) {
				return $index;
			}
		}

		return null;
	}

	/**
	 * Find where an inserted crumb belongs — after the home crumb, where there is one.
	 *
	 * @access private
	 * @since  1.0.4
	 * @param  array $crumbs Array of crumbs.
	 * @return int           Index to insert at.
	 */
	private function get_crumb_insert_position( $crumbs ) {
		if ( empty( $crumbs ) ) {
			return 0;
		}

		// Rank Math draws its home crumb first when the setting is on, and lets an owner point it
		// anywhere — so the setting is the only reliable test, not the crumb's URL
		if ( class_exists( 'RankMath\Helper' ) ) {
			return \RankMath\Helper::get_settings( 'general.breadcrumbs_home' ) ? 1 : 0;
		}

		if ( ! empty( $crumbs[0][1] ) && untrailingslashit( $crumbs[0][1] ) === untrailingslashit( home_url() ) ) {
			return 1;
		}

		return 0;
	}

}
