<?php

namespace The7\Admin\Notices\Announcements;

defined( 'ABSPATH' ) || exit;

/**
 * Announcement-only shortcode vocabulary.
 *
 * Server-authored announcement messages may embed a small set of theme-provided
 * shortcodes (e.g. [upgrade_button]). This class expands them at render time and
 * strips any unsupported shortcode-shaped tokens. The tags are never registered as
 * global WordPress shortcodes: they exist only for the duration of process().
 * See docs/adr/0008-announcement-shortcodes.md.
 */
class Announcement_Shortcodes {

	/**
	 * Render a raw announcement message: sanitize, expand our shortcodes, strip the rest.
	 *
	 * @param string $raw Raw server-authored message.
	 *
	 * @return string
	 */
	public function process( $raw ) {
		$content = wp_kses_post( (string) $raw );
		$content = $this->expand_shortcodes( $content );
		$content = $this->strip_unsupported_shortcodes( $content );

		return $content;
	}

	/**
	 * Announcement-scoped shortcode registry: tag => callback.
	 *
	 * @return array
	 */
	private function get_registry() {
		return [
			'upgrade_button' => [ $this, 'upgrade_button' ],
		];
	}

	/**
	 * Expand our shortcodes while our registry is the only one WordPress knows about.
	 *
	 * The global $shortcode_tags is swapped for our private registry, run through WP's
	 * own parser, and restored in a finally block so a callback fault cannot leak our
	 * registry (or an emptied registry) into the rest of the request.
	 *
	 * @param string $content Sanitized content.
	 *
	 * @return string
	 */
	private function expand_shortcodes( $content ) {
		global $shortcode_tags;

		$backup         = $shortcode_tags;
		$shortcode_tags = $this->get_registry();

		try {
			return do_shortcode( $content );
		} finally {
			$shortcode_tags = $backup;
		}
	}

	/**
	 * Remove any remaining (unsupported) shortcode-shaped tokens.
	 *
	 * After expansion the only bracketed tokens left are shortcodes we do not support.
	 * The pattern is kept conservative (a leading bracket followed by a tag-name shape)
	 * to minimize false positives on literal brackets in server prose.
	 *
	 * @param string $content Expanded content.
	 *
	 * @return string
	 */
	private function strip_unsupported_shortcodes( $content ) {
		return preg_replace( '/\[\/?[a-z0-9_-]+[^\]]*\]/i', '', $content );
	}

	/**
	 * [upgrade_button]Label[/upgrade_button] — theme-owned upgrade button.
	 *
	 * Forces the newer line from the Upgrade Channel through WP's theme upgrader. The
	 * label (shortcode content) is server-authored, so it is escaped here; expansion
	 * runs after wp_kses_post, so the callback owns the safety of its own output.
	 * Renders nothing when the label is empty or the theme-upgrade module is absent.
	 *
	 * @param array       $atts    Shortcode attributes (unused).
	 * @param string|null $content Enclosed label text.
	 *
	 * @return string
	 */
	public function upgrade_button( $atts, $content = null ) {
		$text = trim( (string) $content );

		if ( '' === $text ) {
			return '';
		}

		if ( ! class_exists( \Presscore_Modules_ThemeUpdateModule::class ) ) {
			return '';
		}

		return sprintf(
			'<a class="button button-primary" href="%s">%s</a>',
			esc_url( \Presscore_Modules_ThemeUpdateModule::get_theme_upgrade_url() ),
			esc_html( $text )
		);
	}
}
