<?php
/**
 * Post-Type Archive Sitemap
 *
 * Generates a single sub-sitemap of public has_archive post-type landing URLs.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Sitemap;

use SEOvaultSEO\Core\Options;

/**
 * Builds the archives sub-sitemap.
 */
class Archives {

	/**
	 * Sitemap slug / cache key token.
	 */
	const SLUG = 'archives';

	/**
	 * Output the archives sub-sitemap.
	 *
	 * @param string $slug Sitemap slug.
	 * @param int    $page Page number (archives are never paginated).
	 */
	public static function output( $slug, $page ) {
		$slug = Sanitizer::slug( $slug );
		if ( self::SLUG !== $slug && 0 !== strpos( $slug, self::SLUG . '-' ) ) {
			Router::send_404();
			return;
		}

		$page = max( 1, (int) $page );
		if ( $page > 1 ) {
			Router::send_404();
			return;
		}

		$urls = self::build_urls();
		if ( empty( $urls ) ) {
			Router::send_404();
			return;
		}

		$cache_key = 'sitemap/' . self::SLUG . '-page-1.xml';
		$cached    = Cache::get( $cache_key );
		if ( false !== $cached ) {
			Renderer::set_stylesheet_url( home_url( '/seovault-sitemap.xsl' ) );
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Pre-built XML sitemap output.
			echo $cached;
			return;
		}

		$xml = self::render( $urls );
		Cache::store( $cache_key, $xml );
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Pre-built XML sitemap output.
		echo $xml;
	}

	/**
	 * Whether the archives sitemap has any entries.
	 *
	 * @return bool
	 */
	public static function has_entries() {
		return ! empty( self::build_urls() );
	}

	/**
	 * Build archive URL entries.
	 *
	 * @return array<int, array{loc:string,lastmod:string}>
	 */
	public static function build_urls() {
		if ( apply_filters( 'seovaultseo_exclude_archives_from_sitemap', false ) ) {
			return array();
		}

		$types = self::get_qualifying_post_types();

		/**
		 * Filter post-type slugs whose archive pages appear in the sitemap.
		 *
		 * @param array<int, string> $types Qualifying post-type slugs.
		 */
		$types = apply_filters( 'seovaultseo_sitemap_archive_post_types', $types );
		if ( ! is_array( $types ) || empty( $types ) ) {
			return array();
		}

		$urls = array();
		foreach ( $types as $post_type ) {
			if ( ! is_string( $post_type ) || '' === $post_type ) {
				continue;
			}

			$object = get_post_type_object( $post_type );
			if ( ! $object ) {
				continue;
			}
			if ( is_array( $object->rewrite ) && ! isset( $object->rewrite['with_front'] ) ) {
				$object->rewrite['with_front'] = true;
			}

			$link = get_post_type_archive_link( $post_type );
			if ( empty( $link ) ) {
				continue;
			}

			// Never duplicate the site home URL (post/page archives resolve here).
			if ( untrailingslashit( (string) $link ) === untrailingslashit( home_url( '/' ) ) ) {
				continue;
			}

			$urls[] = array(
				'loc'     => $link,
				'lastmod' => self::get_archive_lastmod( $post_type ),
			);
		}

		return $urls;
	}

	/**
	 * Post types eligible for an archive sitemap entry.
	 *
	 * Qualifies when the type is sitemap-included, public with has_archive,
	 * not noindexed at the post-type level, and has at least one indexable post.
	 * Built-in post/page are skipped (their archive is home/blog).
	 *
	 * @return array<int, string>
	 */
	public static function get_qualifying_post_types() {
		$types   = array();
		$enabled = Options::get_sitemap_post_types();

		foreach ( $enabled as $post_type ) {
			$post_type = Sanitizer::slug( $post_type );
			if ( '' === $post_type ) {
				continue;
			}

			if ( in_array( $post_type, array( 'post', 'page' ), true ) ) {
				continue;
			}

			$object = get_post_type_object( $post_type );
			if ( ! $object || empty( $object->public ) || empty( $object->has_archive ) ) {
				continue;
			}

			if ( Options::is_post_type_noindex( $post_type ) ) {
				continue;
			}

			if ( Query::get_post_type_count( $post_type ) <= 0 ) {
				continue;
			}

			$types[] = $post_type;
		}

		return array_values( array_unique( $types ) );
	}

	/**
	 * Last-modified date for a post-type archive (most recently modified post).
	 *
	 * @param string $post_type Post type slug.
	 * @return string
	 */
	public static function get_archive_lastmod( $post_type ) {
		$ids = Query::get_post_type_ids( $post_type, 1, 1 );
		if ( ! empty( $ids[0] ) ) {
			$modified = get_the_modified_date( 'Y-m-d\TH:i:sP', $ids[0] );
			if ( ! empty( $modified ) ) {
				return $modified;
			}
		}

		return gmdate( 'Y-m-d\TH:i:sP' );
	}

	/**
	 * Render URLs to XML.
	 *
	 * @param array $urls URLs.
	 * @return string
	 */
	private static function render( $urls ) {
		Renderer::set_stylesheet_url( home_url( '/seovault-sitemap.xsl' ) );
		return Renderer::urlset( $urls );
	}
}
