<?php
/**
 * Normalizes WordPress credential-failure errors to reduce username enumeration.
 *
 * @package WP_PowerSuite\Auth
 * @since 1.2.0
 */

namespace WP_PowerSuite\Auth;

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

/**
 * Shared credential error code and HTML leak detection.
 */
final class CredentialErrorNormalizer {

	public const LOGIN_ID_TYPE_CODE = 'login_id_type';

	/** @var list<string> */
	public const CREDENTIAL_CODES = [ 'invalid_username', 'incorrect_password', 'invalid_email' ];

	/**
	 * @return list<string>
	 */
	public static function getStripCodes( bool $include_login_id_type = false ): array {
		$codes = self::CREDENTIAL_CODES;
		if ( $include_login_id_type ) {
			$codes[] = self::LOGIN_ID_TYPE_CODE;
		}

		return $codes;
	}

	public static function errorIsCredentialFailure( \WP_Error $errors, bool $include_login_id_type = false ): bool {
		return (bool) array_intersect( self::getStripCodes( $include_login_id_type ), $errors->get_error_codes() );
	}

	public static function stripCredentialErrorCodes( \WP_Error $errors, bool $include_login_id_type = false ): bool {
		$changed = false;

		foreach ( self::getStripCodes( $include_login_id_type ) as $code ) {
			if ( in_array( $code, $errors->get_error_codes(), true ) ) {
				$errors->remove( $code );
				$changed = true;
			}
		}

		return $changed;
	}

	/**
	 * @return list<string>
	 */
	public static function getCredentialLeakMarkers(): array {
		static $markers = null;
		if ( $markers !== null ) {
			return $markers;
		}

		$built = [
			'not registered on this site',
			'Unknown username',
			'Unknown email address',
			'entered for the username',
			'entered for the email address',
			'There is no account with that username or email address',
		];

		$core_msgids = [
			'<strong>Error:</strong> The username <strong>%s</strong> is not registered on this site. If you are unsure of your username, try your email address instead.',
			'<strong>Error:</strong> The password you entered for the username <strong>%s</strong> is incorrect.',
			'<strong>Error:</strong> Unknown username. Check again or try your email address.',
			'<strong>Error:</strong> Unknown email address. Check again or try your username.',
			'<strong>Error:</strong> The password you entered for the email address <strong>%s</strong> is incorrect.',
			'<strong>Error:</strong> There is no account with that username or email address.',
		];

		foreach ( $core_msgids as $msgid ) {
			// phpcs:ignore WordPress.WP.I18n.LowLevelTranslationFunction, WordPress.WP.I18n.TextDomainMismatch, WordPress.WP.I18n.NonSingularStringLiteralText -- look up core login msgids in the site locale.
			$plain = wp_strip_all_tags( translate( $msgid, 'default' ) );
			if ( $plain !== '' ) {
				$built[] = $plain;
			}
		}

		$markers = array_values( array_unique( array_filter( $built ) ) );

		return $markers;
	}

	public static function loginErrorHtmlLooksLikeCredentialLeak( string $html, bool $use_extension_filters = false ): bool {
		if ( $use_extension_filters && apply_filters( 'wp_powersuite_generic_login_force_replace_message', false, $html ) ) {
			return true;
		}

		$haystack = wp_strip_all_tags( $html );
		if ( $haystack === '' ) {
			return false;
		}

		foreach ( self::getCredentialLeakMarkers() as $needle ) {
			if ( $needle !== '' && stripos( $haystack, $needle ) !== false ) {
				return true;
			}
		}

		if ( $use_extension_filters ) {
			return (bool) apply_filters( 'wp_powersuite_generic_login_match_error_html', false, $html );
		}

		return false;
	}
}
