<?php
/**
 * Sitemap Renderer
 *
 * Renders XML sitemapindex and urlset documents using DOMDocument.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Sitemap;

/**
 * XML renderer for sitemaps
 */
class Renderer {

	/**
	 * XSL enabled flag.
	 *
	 * @var bool
	 */
	private static $xsl_enabled = true;

	/**
	 * XSL stylesheet URL.
	 *
	 * @var string
	 */
	private static $stylesheet_url = '';

	/**
	 * Set XSL stylesheet enabled state.
	 *
	 * @param bool $enabled Enabled.
	 */
	public static function set_xsl( $enabled ) {
		self::$xsl_enabled = (bool) $enabled;
	}

	/**
	 * Set stylesheet URL.
	 *
	 * @param string $url Stylesheet URL.
	 */
	public static function set_stylesheet_url( $url ) {
		self::$stylesheet_url = esc_url( $url );
	}

	/**
	 * Render a sitemap index.
	 *
	 * @param array $entries Array of [loc, lastmod].
	 * @return string
	 * @throws \RuntimeException If XML generation fails.
	 */
	public static function index( $entries ) {
		$doc  = self::create_document( 'sitemapindex' );
		$root = $doc->documentElement;
		$root->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9' );

		foreach ( $entries as $entry ) {
			if ( empty( $entry['loc'] ) ) {
				continue;
			}
			$sitemap = $doc->createElement( 'sitemap' );
			$loc     = $doc->createElement( 'loc', self::escape_xml( esc_url( $entry['loc'] ) ) );
			$sitemap->appendChild( $loc );
			if ( ! empty( $entry['lastmod'] ) ) {
				$lastmod = $doc->createElement( 'lastmod', self::escape_xml( $entry['lastmod'] ) );
				$sitemap->appendChild( $lastmod );
			}
			$root->appendChild( $sitemap );
		}

		return self::output( $doc );
	}

	/**
	 * Render a URL set.
	 *
	 * @param array $urls Array of url arrays with optional lastmod, images, news.
	 * @return string
	 * @throws \RuntimeException If XML generation fails.
	 */
	public static function urlset( $urls ) {
		$doc  = self::create_document( 'urlset' );
		$root = $doc->documentElement;
		$root->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9' );
		$root->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:image', 'http://www.google.com/schemas/sitemap-image/1.1' );
		$root->setAttributeNS( 'http://www.w3.org/2000/xmlns/', 'xmlns:news', 'http://www.google.com/schemas/sitemap-news/0.9' );

		foreach ( $urls as $url ) {
			if ( empty( $url['loc'] ) ) {
				continue;
			}
			$url_node = $doc->createElement( 'url' );

			$loc = $doc->createElement( 'loc', self::escape_xml( esc_url( $url['loc'] ) ) );
			$url_node->appendChild( $loc );

			if ( ! empty( $url['lastmod'] ) ) {
				$lastmod = $doc->createElement( 'lastmod', self::escape_xml( $url['lastmod'] ) );
				$url_node->appendChild( $lastmod );
			}

			if ( ! empty( $url['images'] ) && is_array( $url['images'] ) ) {
				foreach ( $url['images'] as $image_url ) {
					if ( empty( $image_url ) ) {
						continue;
					}
					$image     = $doc->createElement( 'image:image' );
					$loc_image = $doc->createElement( 'image:loc', self::escape_xml( esc_url( $image_url ) ) );
					$image->appendChild( $loc_image );
					$url_node->appendChild( $image );
				}
			}

			if ( ! empty( $url['news'] ) && is_array( $url['news'] ) ) {
				$news = $doc->createElement( 'news:news' );
				if ( ! empty( $url['news']['publication'] ) ) {
					$publication = $doc->createElement( 'news:publication' );
					if ( ! empty( $url['news']['publication']['name'] ) ) {
						$publication->appendChild( $doc->createElement( 'news:name', self::escape_xml( $url['news']['publication']['name'] ) ) );
					}
					if ( ! empty( $url['news']['publication']['language'] ) ) {
						$publication->appendChild( $doc->createElement( 'news:language', self::escape_xml( $url['news']['publication']['language'] ) ) );
					}
					$news->appendChild( $publication );
				}
				if ( ! empty( $url['news']['publication_date'] ) ) {
					$news->appendChild( $doc->createElement( 'news:publication_date', self::escape_xml( $url['news']['publication_date'] ) ) );
				}
				if ( ! empty( $url['news']['title'] ) ) {
					$news->appendChild( $doc->createElement( 'news:title', self::escape_xml( $url['news']['title'] ) ) );
				}
				$url_node->appendChild( $news );
			}

			$root->appendChild( $url_node );
		}

		return self::output( $doc );
	}

	/**
	 * Create a new XML document.
	 *
	 * @param string $root_name Root element name.
	 * @return \DOMDocument
	 */
	private static function create_document( $root_name ) {
		$doc                     = new \DOMDocument( '1.0', 'UTF-8' );
		$doc->formatOutput       = true;
		$doc->preserveWhiteSpace = false;
		$doc->appendChild( $doc->createElement( $root_name ) );
		return $doc;
	}

	/**
	 * Output the XML document.
	 *
	 * @param \DOMDocument $doc Document.
	 * @return string
	 * @throws \RuntimeException If saveXML fails.
	 */
	private static function output( $doc ) {
		if ( self::$xsl_enabled && ! empty( self::$stylesheet_url ) ) {
			$pi = $doc->createProcessingInstruction(
				'xml-stylesheet',
				'type="text/xsl" href="' . self::escape_xml_attribute( self::$stylesheet_url ) . '"'
			);
			$doc->insertBefore( $pi, $doc->documentElement );
		}

		$xml = $doc->saveXML();
		if ( false === $xml ) {
			throw new \RuntimeException( 'Failed to generate XML sitemap.' );
		}

		$xml = ltrim( $xml );
		if ( 0 === strpos( $xml, '<?xml' ) ) {
			return $xml;
		}
		return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" . $xml;
	}

	/**
	 * Escape XML text content.
	 *
	 * @param string $text Text.
	 * @return string
	 */
	private static function escape_xml( $text ) {
		return str_replace(
			array( '&', '<', '>', '"', "'" ),
			array( '&amp;', '&lt;', '&gt;', '&quot;', '&apos;' ),
			$text
		);
	}

	/**
	 * Escape XML attribute value.
	 *
	 * @param string $text Text.
	 * @return string
	 */
	private static function escape_xml_attribute( $text ) {
		return self::escape_xml( $text );
	}
}
