<?php
namespace AIOSEO\Plugin\Pro\Sitemap;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use AIOSEO\Plugin\Common\Sitemap as CommonSitemap;

/**
 * Handles all complex queries for the sitemap.
 *
 * @since 4.0.0
 */
class Query extends CommonSitemap\Query {
	/**
	 * Returns all eligible sitemap entries for a given taxonomy.
	 *
	 * @since 4.0.0
	 *
	 * @param  string    $taxonomy       The taxonomy.
	 * @param  array     $additionalArgs Any additional arguments for the term query.
	 * @return array|int                 The term objects or the term count.
	 */
	public function terms( $taxonomy, $additionalArgs = [] ) {
		// Let's just make sure the tables exist. This should never be a problem outside of our dev environments.
		if ( ! aioseo()->core->db->tableExists( 'aioseo_terms' ) ) {
			aioseo()->updates->addInitialCustomTablesForV4();
		}

		// Set defaults.
		$fields  = 't.term_id, at.priority, at.frequency, tt.count';
		$offset  = aioseo()->sitemap->offset;

		// Include term name for llms sitemap type
		if ( 'llms' === aioseo()->sitemap->type ) {
			$fields .= ', t.name';
		}

		// Override defaults if passed as additional arg.
		foreach ( $additionalArgs as $name => $value ) {
			$$name = esc_sql( $value );
			if ( 'root' === $name && $value ) {
				$fields = 't.term_id';
			}
			if ( 'count' === $name && $value ) {
				$fields = 'count(t.term_id) as total';
			}
		}

		$termRelationshipsTable = aioseo()->core->db->db->prefix . 'term_relationships';

		// Determine the robots meta conditions based on whether the taxonomy is noindexed.
		// For noindexed taxonomies, we only include terms that have explicitly set robots_noindex to false.
		// For indexed taxonomies, we include terms where robots_noindex is null, default, or explicitly false.
		$isTaxonomyNoindexed = aioseo()->helpers->isTaxonomyNoindexed( $taxonomy );

		// Include all terms that have assigned posts or whose children have assigned posts.
		// The term also needs to be indexed and not excluded.
		// Optimized query: replaced IN subquery with direct JOIN on term_taxonomy,
		// and replaced EXISTS subquery with LEFT JOIN for child term checking.
		if ( $isTaxonomyNoindexed ) {
			// For noindexed taxonomies, only include terms that have explicitly opted in (robots_default = 0 AND robots_noindex = 0).
			// Use INNER JOIN since we need matching records in aioseo_terms.
			$query = aioseo()->core->db
				->start( aioseo()->core->db->db->terms . ' as t', true )
				->select( $fields )
				->join( 'aioseo_terms as at', '`at`.`term_id` = `t`.`term_id` AND `at`.`robots_default` = 0 AND `at`.`robots_noindex` = 0' )
				->join( 'term_taxonomy as tt', "`tt`.`term_id` = `t`.`term_id` AND `tt`.`taxonomy` = '$taxonomy'" )
				->leftJoin( 'term_taxonomy as child', '`child`.`parent` = `tt`.`term_id` AND `child`.`count` > 0' );
		} else {
			// For indexed taxonomies, include terms where noindex is null, default, or explicitly false.
			// Use LEFT JOIN on aioseo_terms to include terms without AIOSEO records.
			$query = aioseo()->core->db
				->start( aioseo()->core->db->db->terms . ' as t', true )
				->select( $fields )
				->leftJoin( 'aioseo_terms as at', '`at`.`term_id` = `t`.`term_id`' )
				->join( 'term_taxonomy as tt', "`tt`.`term_id` = `t`.`term_id` AND `tt`.`taxonomy` = '$taxonomy'" )
				->leftJoin( 'term_taxonomy as child', '`child`.`parent` = `tt`.`term_id` AND `child`.`count` > 0' );

			// For indexed taxonomies, filter to include terms where noindex is null, default, or explicitly false.
			$query->whereRaw( '( `at`.`robots_noindex` IS NULL OR `at`.`robots_default` = 1 OR `at`.`robots_noindex` = 0 )' );
		}

		// Include terms that have posts or whose children have posts.
		$query->whereRaw( '( `tt`.`count` > 0 OR `child`.`term_id` IS NOT NULL )' );

		// Group by term_id to prevent duplicate rows from the LEFT JOIN on child terms.
		$query->groupBy( 't.term_id' );

		$excludedTerms = aioseo()->sitemap->helpers->excludedTerms();
		if ( $excludedTerms ) {
			$query->whereRaw("
				( `t`.`term_id` NOT IN
					(
						SELECT `tr`.`term_taxonomy_id`
						FROM `$termRelationshipsTable` as tr
						WHERE `tr`.`term_taxonomy_id` IN ( $excludedTerms )
					)
				)" );
		}

		if (
			aioseo()->sitemap->indexes &&
			empty( $additionalArgs['root'] ) &&
			( empty( $additionalArgs['count'] ) || ! $additionalArgs['count'] )
		) {
			$query->limit( aioseo()->sitemap->linksPerIndex, $offset );
		}

		// Return the total if we are just counting the terms.
		if ( ! empty( $additionalArgs['count'] ) && $additionalArgs['count'] ) {
			return (int) $query->run( true, 'var' )
				->result();
		}

		$terms = $query->orderBy( 't.term_id ASC' )
			->run()
			->result();

		foreach ( $terms as $term ) {
			// Convert ID from string to int.
			$term->term_id = (int) $term->term_id;
			// Add taxonomy name to object manually instead of querying it to prevent redundant join.
			$term->taxonomy = $taxonomy;
		}

		return $terms;
	}
}