<?php
/**
 * Auto-reply email preheader helpers.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\AutoReplies;

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

/**
 * Builds a hidden, plain-text preheader without mutating the message content.
 */
class AutoReplyPreheader {
	const MARKER_ATTRIBUTE = 'data-subin-auto-reply-preheader';
	const MAX_CHARACTERS = 500;

	/**
	 * Inject one hidden preheader into an HTML document or fragment.
	 *
	 * Existing helper-generated preheaders are replaced so repeated preparation
	 * remains idempotent. An empty preheader returns the original body byte-for-byte.
	 *
	 * @param mixed $body Raw HTML body or fragment.
	 * @param mixed $preheader Plain-text preheader.
	 * @return string
	 */
	public static function inject( $body, $preheader ) {
		$body      = is_scalar( $body ) ? (string) $body : '';
		$preheader = self::normalize_text( $preheader );
		if ( '' === $preheader ) {
			return $body;
		}

		$body   = self::without_injected_preheaders( $body );
		$markup = self::markup( $preheader );
		$count  = 0;
		$result = preg_replace_callback(
			'/<body\b[^>]*>/i',
			static function ( array $match ) use ( $markup ) {
				return $match[0] . $markup;
			},
			$body,
			1,
			$count
		);

		if ( is_string( $result ) && $count > 0 ) {
			return $result;
		}

		return $markup . $body;
	}

	/**
	 * Determine whether an HTML message has visible content or a usable image.
	 *
	 * The hidden preheader is delivery metadata and must not make an otherwise
	 * empty message eligible for sending.
	 *
	 * @param mixed $html HTML body or fragment.
	 * @return bool
	 */
	public static function has_meaningful_content( $html ) {
		$html = is_scalar( $html ) ? (string) $html : '';
		$html = self::without_injected_preheaders( $html );

		$without_nonvisual = preg_replace( '/<(?:style|head)\b[^>]*>[\s\S]*?<\/(?:style|head)\s*>/i', '', $html );
		if ( ! is_string( $without_nonvisual ) ) {
			return false;
		}

		$stripped = function_exists( 'wp_strip_all_tags' )
			? wp_strip_all_tags( $without_nonvisual )
			: preg_replace( '/<[^>]*>/', '', $without_nonvisual );
		$text = html_entity_decode( (string) $stripped, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
		$text = str_replace(
			array( "\xC2\xA0", "\xE2\x80\x8B", "\xE2\x80\x8C", "\xE2\x80\x8D", "\xEF\xBB\xBF" ),
			' ',
			$text
		);
		if ( '' !== trim( $text ) ) {
			return true;
		}

		return 1 === preg_match( '/<img\b/i', $without_nonvisual );
	}

	/**
	 * Build the email-client-compatible hidden block.
	 *
	 * @param string $preheader Normalized plain text.
	 * @return string
	 */
	private static function markup( $preheader ) {
		$escaped = htmlspecialchars( $preheader, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8' );

		return '<div ' . self::MARKER_ATTRIBUTE . '="1" aria-hidden="true" style="display:none !important;visibility:hidden;mso-hide:all;font-size:1px;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;color:transparent;">' . $escaped . '</div>';
	}

	/**
	 * Remove helper-generated blocks before replacement or content validation.
	 *
	 * @param string $html HTML body or fragment.
	 * @return string
	 */
	private static function without_injected_preheaders( $html ) {
		$pattern = '/<div\s+' . preg_quote( self::MARKER_ATTRIBUTE, '/' ) . '\s*=\s*(?:"1"|\'1\'|1)[^>]*>[\s\S]*?<\/div\s*>/i';
		$result  = preg_replace( $pattern, '', $html );

		return is_string( $result ) ? $result : $html;
	}

	/**
	 * Normalize unsafe control bytes and incidental whitespace in preview text.
	 *
	 * @param mixed $value Raw value.
	 * @return string
	 */
	public static function normalize_text( $value ) {
		if ( ! is_scalar( $value ) ) {
			return '';
		}

		$value = preg_replace( '/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/', '', (string) $value );
		if ( ! is_string( $value ) ) {
			return '';
		}
		$value = preg_replace( '/[\r\n\t ]+/', ' ', $value );
		$value = is_string( $value ) ? trim( $value ) : '';
		if ( self::length( $value ) <= self::MAX_CHARACTERS ) {
			return $value;
		}
		if ( function_exists( 'mb_substr' ) ) {
			return rtrim( mb_substr( $value, 0, self::MAX_CHARACTERS, 'UTF-8' ) );
		}
		$characters = preg_split( '//u', $value, -1, PREG_SPLIT_NO_EMPTY );
		if ( is_array( $characters ) ) {
			return rtrim( implode( '', array_slice( $characters, 0, self::MAX_CHARACTERS ) ) );
		}

		return rtrim( substr( $value, 0, self::MAX_CHARACTERS ) );
	}

	/**
	 * Count Unicode characters for the editor's user-facing limit.
	 *
	 * @param mixed $value Text value.
	 * @return int
	 */
	public static function length( $value ) {
		$value = is_scalar( $value ) ? (string) $value : '';
		if ( function_exists( 'mb_strlen' ) ) {
			return (int) mb_strlen( $value, 'UTF-8' );
		}
		$matched = preg_match_all( '/./us', $value, $matches );

		return false === $matched ? strlen( $value ) : (int) $matched;
	}
}
