<?php
/**
 * Comment-link SEO — strip ?replytocom, redirect legacy URLs, merge rel tokens.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Frontend;

use SEOvaultSEO\Compatibility\Detector;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Reduces duplicate comment-reply crawl URLs and hardens comment link rel tokens.
 */
class CommentSeo {

	/**
	 * Register front-end hooks.
	 */
	public static function init() {
		if ( ! Detector::should_output_seo() ) {
			return;
		}

		if ( apply_filters( 'seovault_remove_replytocom', true ) ) {
			add_filter( 'comment_reply_link', array( __CLASS__, 'strip_replytocom_from_reply_link' ), 10, 1 );
			add_action( 'template_redirect', array( __CLASS__, 'redirect_replytocom_request' ), 1 );
		}

		add_filter( 'comment_reply_link', array( __CLASS__, 'add_rel_to_reply_link' ), 20, 1 );
		add_filter( 'comments_popup_link_attributes', array( __CLASS__, 'add_rel_to_comments_popup_link' ), 10, 1 );
	}

	/**
	 * Strip replytocom from comment reply link hrefs; keep fragment anchors.
	 *
	 * @param string $link Comment reply link HTML.
	 * @return string
	 */
	public static function strip_replytocom_from_reply_link( $link ) {
		if ( ! is_string( $link ) || false === strpos( $link, 'replytocom' ) ) {
			return $link;
		}

		$updated = preg_replace_callback(
			'/href=([\'"])(.*?)\1/',
			static function ( $matches ) {
				return 'href=' . $matches[1] . esc_url( remove_query_arg( 'replytocom', $matches[2] ) ) . $matches[1];
			},
			$link
		);

		return null !== $updated ? $updated : $link;
	}

	/**
	 * 301 legacy ?replytocom= requests to the clean URL (+ #comment-{id} when valid).
	 */
	public static function redirect_replytocom_request() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only canonical redirect; no state change.
		if ( ! isset( $_GET['replytocom'] ) ) {
			return;
		}

		if ( is_admin() || is_feed() || wp_doing_ajax() || wp_doing_cron() || wp_is_json_request() ) {
			return;
		}

		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return;
		}

		$request_uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
		if ( '' === $request_uri ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only canonical redirect; no state change.
		$comment_id = absint( wp_unslash( $_GET['replytocom'] ) );

		$clean = remove_query_arg( 'replytocom', $request_uri );
		$clean = strtok( $clean, '#' );
		if ( ! is_string( $clean ) || '' === $clean ) {
			return;
		}

		$location = home_url( $clean );

		if ( $comment_id > 0 && get_comment( $comment_id ) ) {
			$location .= '#comment-' . $comment_id;
		}

		wp_safe_redirect( $location, 301 );
		exit;
	}

	/**
	 * Merge filterable rel tokens into comment reply link HTML.
	 *
	 * @param string $link Comment reply link HTML.
	 * @return string
	 */
	public static function add_rel_to_reply_link( $link ) {
		if ( ! is_string( $link ) || '' === $link ) {
			return $link;
		}

		$tokens = self::get_rel_tokens();
		if ( empty( $tokens ) ) {
			return $link;
		}

		if ( preg_match( '/\srel\s*=\s*([\'"])(.*?)\1/i', $link, $matches ) ) {
			$existing = preg_split( '/\s+/', trim( $matches[2] ), -1, PREG_SPLIT_NO_EMPTY );
			$merged   = array_values( array_unique( array_merge( $existing ? $existing : array(), $tokens ) ) );
			$rel_attr = ' rel=' . $matches[1] . esc_attr( implode( ' ', $merged ) ) . $matches[1];

			return preg_replace( '/\srel\s*=\s*[\'"].*?[\'"]/i', $rel_attr, $link, 1 );
		}

		$rel_attr = ' rel="' . esc_attr( implode( ' ', $tokens ) ) . '"';

		return preg_replace( '/<a\b/i', '<a' . $rel_attr, $link, 1 );
	}

	/**
	 * Merge filterable rel tokens into comments_popup_link attributes.
	 *
	 * @param string $attributes Existing anchor attributes.
	 * @return string
	 */
	public static function add_rel_to_comments_popup_link( $attributes ) {
		if ( ! is_string( $attributes ) ) {
			$attributes = '';
		}

		$tokens = self::get_rel_tokens();
		if ( empty( $tokens ) ) {
			return $attributes;
		}

		if ( preg_match( '/(^|\s)rel\s*=\s*([\'"])(.*?)\2/i', $attributes, $matches ) ) {
			$existing    = preg_split( '/\s+/', trim( $matches[3] ), -1, PREG_SPLIT_NO_EMPTY );
			$merged      = array_values( array_unique( array_merge( $existing ? $existing : array(), $tokens ) ) );
			$replacement = $matches[1] . 'rel=' . $matches[2] . esc_attr( implode( ' ', $merged ) ) . $matches[2];

			return str_replace( $matches[0], $replacement, $attributes );
		}

		return trim( $attributes . ' rel="' . esc_attr( implode( ' ', $tokens ) ) . '"' );
	}

	/**
	 * Resolve filterable rel tokens (string or array).
	 *
	 * @return string[]
	 */
	private static function get_rel_tokens() {
		$rel = apply_filters( 'seovault_comment_reply_rel', 'nofollow noopener noreferrer' );

		if ( is_array( $rel ) ) {
			$tokens = array_map( 'strval', $rel );
		} elseif ( is_string( $rel ) ) {
			$tokens = preg_split( '/\s+/', trim( $rel ), -1, PREG_SPLIT_NO_EMPTY );
		} else {
			return array();
		}

		if ( ! is_array( $tokens ) ) {
			return array();
		}

		$clean = array();
		foreach ( $tokens as $token ) {
			$token = preg_replace( '/[^a-zA-Z0-9\-]/', '', (string) $token );
			if ( is_string( $token ) && '' !== $token ) {
				$clean[] = $token;
			}
		}

		return array_values( array_unique( $clean ) );
	}
}
