<?php

namespace ChangeLoginURL;

use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\ModuleManager;
use WP_PowerSuite\Core\OptionStoreModuleSettingsRepository;

class Module extends AbstractModule {

	private const MODULE_ID = 'change-login-url';
	private const MODULE_VERSION = '2.3.0';

	/** @var list<string> */
	private const RESERVED_SLUGS = [
		'wp-admin',
		'wp-login',
		'wp-login.php',
		'wp-content',
		'wp-includes',
		'wp-json',
		'xmlrpc.php',
		'admin',
		'dashboard',
		'feed',
		'rss',
		'robots.txt',
		'login',
		'my-account',
		'cart',
		'checkout',
		'shop',
	];

	/**
	 * Actions that must remain usable for logged-in users on the custom login URL.
	 *
	 * @var list<string>
	 */
	private const LOGGED_IN_CUSTOM_LOGIN_ACTIONS = [
		'logout', 'postpass', 'confirmaction', 'confirm_admin_email', 'rp', 'resetpass',
		'export_personal_data', 'erase_personal_data',
		'validate_2fa', 'wpps_2fa', 'wpps_2fa_cancel', 'switch_to_user', 'switch_to_olduser',
	];

	/**
	 * Direct wp-login.php hits with these actions are redirected to the custom slug (query preserved).
	 *
	 * @var list<string>
	 */
	private const REDIRECTABLE_WP_LOGIN_ACTIONS = [
		'logout', 'lostpassword', 'retrievepassword', 'resetpass', 'rp', 'register',
		'confirmaction', 'confirm_admin_email', 'postpass',
		'export_personal_data', 'erase_personal_data',
		'validate_2fa', 'wpps_2fa', 'wpps_2fa_cancel',
	];

	/** @var array<string, mixed>|null */
	private ?array $settings = null;

	private ?string $custom_slug_cache = null;

	private ?string $home_path_prefix_cache = null;

	private ?string $relative_request_path_cache = null;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Change Login URL', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Use a custom login URL instead of the default one bots love to hammer; bookmark your new address.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return 'both';
	}

	public function getDependencies(): array {
		return [];
	}

	protected function registerHooks(): void {
		add_action( 'wp_powersuite_module_toggled', [ $this, 'onModuleToggled' ], 10, 3 );
		add_action( 'wp_powersuite_module_settings_saved', [ $this, 'onSettingsSaved' ], 10, 2 );
		add_action( 'wp', [ $this, 'handleCustomLoginUrl' ], 0 );
		add_action( 'template_redirect', [ $this, 'maybeRedirectGravityFormsPreview' ], 0 );
		add_action( 'template_redirect', [ $this, 'maybeRedirectWooCommerceLostPassword' ], 0 );
		add_action( 'template_redirect', [ $this, 'maybeRedirectWooCommerceGuestAccount' ], 1 );
		add_action( 'init', [ $this, 'blockDefaultLoginUrls' ], 1 );

		add_filter( 'wp_powersuite_is_custom_login_url_request', [ $this, 'filterIsCustomLoginUrlRequest' ], 10, 1 );
		add_filter( 'wp_powersuite_skip_404_path', [ $this, 'filterSkip404Path' ], 10, 2 );
		add_filter( 'is_login', [ $this, 'filterIsLogin' ], 10, 1 );

		add_filter( 'site_url', [ $this, 'filterLoginUrl' ], 10, 3 );
		add_filter( 'network_site_url', [ $this, 'filterLoginUrl' ], 10, 3 );
		add_filter( 'wp_redirect', [ $this, 'filterRedirectUrl' ], 10, 2 );
		add_filter( 'logout_url', [ $this, 'filterLogoutUrl' ], 10, 2 );
		add_filter( 'login_url', [ $this, 'filterLoginUrlDirect' ], 10, 3 );
		// After WooCommerce's lostpassword_url callback (priority 10).
		add_filter( 'lostpassword_url', [ $this, 'filterLostPasswordUrl' ], 20, 2 );
		add_filter( 'register_url', [ $this, 'filterRegisterUrl' ], 10, 1 );
		add_filter( 'retrieve_password_message', [ $this, 'filterRetrievePasswordMessage' ], 10, 4 );
		add_filter( 'wp_new_user_notification_email', [ $this, 'filterNewUserNotificationEmail' ], 10, 3 );

		if ( class_exists( 'WooCommerce', false ) ) {
			add_filter( 'woocommerce_lostpassword_url', [ $this, 'filterWooCommerceLostPasswordUrl' ], 20, 2 );
			add_filter( 'woocommerce_mail_content', [ $this, 'filterWooCommerceEmailContent' ], 20, 1 );
		}
	}

	/**
	 * Whether the request URI targets the custom login slug (same rules as {@see handleCustomLoginUrl()}).
	 *
	 * @param string $request_uri Raw request URI (e.g. $_SERVER['REQUEST_URI']).
	 */
	private function matchesCustomLoginRequestUri( string $request_uri ): bool {
		if ( $this->isStaticAssetRequest( $request_uri ) ) {
			return false;
		}

		$parsed_url = wp_parse_url( $request_uri );
		if ( ! $parsed_url || ! isset( $parsed_url['path'] ) ) {
			return false;
		}

		$full_path = trim( $parsed_url['path'], '/' );
		$home_path = $this->getHomePathPrefix();
		$path      = ( $home_path !== '' && strpos( $full_path, $home_path ) === 0 )
			? trim( substr( $full_path, strlen( $home_path ) ), '/' )
			: $full_path;

		$custom_slug = $this->getSlug();

		return $path === $custom_slug || str_starts_with( $path, $custom_slug . '/' );
	}

	/**
	 * @param bool $is Prior value from other callbacks.
	 */
	public function filterIsCustomLoginUrlRequest( bool $is ): bool {
		if ( $is ) {
			return true;
		}
		if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
			return false;
		}

		return $this->matchesCustomLoginRequestUri( wp_unslash( $_SERVER['REQUEST_URI'] ) );
	}

	/**
	 * Make core/plugin `is_login()` true on the masked route.
	 */
	public function filterIsLogin( bool $is ): bool {
		if ( $is ) {
			return true;
		}

		return (bool) apply_filters( 'wp_powersuite_is_custom_login_url_request', false );
	}

	public function handleCustomLoginUrl(): void {
		static $handled = false;
		if ( $handled || is_admin() ) {
			return;
		}

		if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
			return;
		}

		$request_uri = wp_unslash( $_SERVER['REQUEST_URI'] );

		if ( ! $this->matchesCustomLoginRequestUri( $request_uri ) ) {
			return;
		}

		$handled = true;

		$parsed_url = wp_parse_url( $request_uri );
		if ( ! $parsed_url || ! isset( $parsed_url['path'] ) ) {
			return;
		}

		$query_params = [];
		if ( isset( $parsed_url['query'] ) ) {
			parse_str( $parsed_url['query'], $query_params );
		}

		// Map WooCommerce-style new-account links onto core resetpass/rp handling.
		if ( isset( $query_params['action'] ) && (string) $query_params['action'] === 'newaccount' ) {
			$query_params['action'] = 'rp';
			$_GET['action']         = 'rp';
			$_REQUEST['action']     = 'rp';
		}

		if ( is_user_logged_in() && $this->shouldRedirectLoggedInUserFromCustomLogin( $query_params ) ) {
			$target = apply_filters( 'wp_powersuite_change_login_logged_in_redirect', admin_url(), wp_get_current_user(), $query_params );
			$target = wp_validate_redirect( $target, admin_url() );
			wp_safe_redirect( $target );
			exit;
		}

		if ( isset( $query_params['loggedout'] ) && $query_params['loggedout'] === 'true' ) {
			$this->renderLoggedOutInterstitial();
		}

		/*
		 * Critical: keep the public REQUEST_URI (custom slug). Core wp-login.php sets the
		 * password-reset cookie path from REQUEST_URI; rewriting it to /wp-login.php makes
		 * the cookie invisible on the follow-up /{slug}?action=rp request (invalidkey).
		 */
		$_SERVER['PHP_SELF']        = $this->getWpLoginScriptPath();
		$_SERVER['SCRIPT_NAME']     = $this->getWpLoginScriptPath();
		$_SERVER['SCRIPT_FILENAME'] = ABSPATH . 'wp-login.php';

		if ( isset( $parsed_url['query'] ) ) {
			$_SERVER['QUERY_STRING'] = $parsed_url['query'];
			parse_str( $parsed_url['query'], $_GET );
			if ( isset( $_GET['action'] ) && (string) $_GET['action'] === 'newaccount' ) {
				$_GET['action']     = 'rp';
				$_REQUEST['action'] = 'rp';
			}
			$_REQUEST = array_merge( $_REQUEST, $_GET );
		} else {
			unset( $_SERVER['QUERY_STRING'] );
		}

		$login_file = ABSPATH . 'wp-login.php';
		if ( ! is_readable( $login_file ) ) {
			wp_die( esc_html__( 'Login file not found.', 'wp-powersuite' ), esc_html__( 'Error', 'wp-powersuite' ), [ 'response' => 500 ] );
		}

		global $pagenow;
		$pagenow = 'wp-login.php';

		global $user_login, $error, $interim_login, $action, $redirect_to, $reauth;

		$user_login    = $user_login ?? '';
		$error         = $error ?? '';
		$interim_login = $interim_login ?? false;
		$action        = $action ?? ( isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : 'login' );
		$redirect_to   = $redirect_to ?? ( isset( $_GET['redirect_to'] ) ? esc_url_raw( wp_unslash( $_GET['redirect_to'] ) ) : '' );
		$reauth        = $reauth ?? ( isset( $_GET['reauth'] ) ? (bool) $_GET['reauth'] : false );

		// Login-only modules are not loaded on normal frontend requests; bootstrap them here.
		ModuleManager::getInstance()->loadEnabledModulesForContext( 'login' );

		/**
		 * Fires before Change Login URL includes wp-login.php on the masked route.
		 * Other modules (e.g. Login Page Customizer, CAPTCHA) can prepare the login screen here.
		 */
		do_action( 'wpps_before_custom_login_render' );

		nocache_headers();
		ob_start();
		status_header( 200 );
		include $login_file;
		echo ob_get_clean();
		exit;
	}

	private function getSettings(): array {
		if ( $this->settings !== null ) {
			return $this->settings;
		}

		$this->settings = array_merge( $this->getDefaultSettings(), $this->moduleSettings()->all( [] ) );
		return $this->settings;
	}

	public function getDefaultSettings(): array {
		return [
			'login_slug'                   => 'login',
			'blocked_behavior'             => '404',
			'blocked_redirect_url'         => '',
			// Off by default: redirecting wp-login.php?action=* to the custom slug
			// reveals the hidden login URL to anyone probing legacy endpoints.
			'redirect_wp_login_actions'    => false,
			'woocommerce_guest_redirect'   => true,
			'password_url_mode'            => 'custom',
			'rewrite_woocommerce_emails'   => true,
		];
	}

	public function blockDefaultLoginUrls(): void {
		global $pagenow;

		if ( isset( $_SERVER['REQUEST_URI'] ) && $this->isStaticAssetRequest( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) {
			return;
		}

		if ( $this->isSystemRequestBypass() ) {
			return;
		}

		if ( is_user_logged_in() && is_admin() && ! wp_doing_ajax() && ( $pagenow ?? '' ) !== 'wp-login.php' ) {
			return;
		}

		if ( ! $this->isPathBlockingCandidate( $pagenow ?? '' ) ) {
			return;
		}

		$custom_slug = $this->getSlug();

		if ( ( $pagenow ?? '' ) === 'wp-login.php' ) {
			if ( $this->maybeRedirectLegacyWpLoginRequest() ) {
				return;
			}
			$this->serveBlockedResponse();
			exit;
		}

		if ( ! is_user_logged_in() ) {
			if ( $this->shouldBlockWpAdminRequest() ) {
				$this->serveBlockedResponse();
				exit;
			}
			if ( $this->shouldBlockCommonLoginAliases( $custom_slug ) ) {
				$this->serveBlockedResponse();
				exit;
			}
		}
	}

	/**
	 * Redirect legitimate legacy wp-login.php flows (reset, logout, etc.) to the custom slug.
	 */
	private function maybeRedirectLegacyWpLoginRequest(): bool {
		$settings = $this->getSettings();
		if ( empty( $settings['redirect_wp_login_actions'] ) ) {
			return false;
		}

		$action = isset( $_REQUEST['action'] ) && is_string( $_REQUEST['action'] )
			? sanitize_key( wp_unslash( $_REQUEST['action'] ) )
			: '';

		$has_reset_key = isset( $_GET['key'] ) && ( isset( $_GET['login'] ) || isset( $_GET['id'] ) || isset( $_GET['user'] ) );
		$checkemail    = isset( $_GET['checkemail'] ) ? sanitize_key( wp_unslash( $_GET['checkemail'] ) ) : '';
		$loggedout     = isset( $_GET['loggedout'] );

		$should_redirect = $has_reset_key
			|| $loggedout
			|| ( $checkemail !== '' && in_array( $checkemail, [ 'confirm', 'registered' ], true ) )
			|| ( $action !== '' && in_array( $action, self::REDIRECTABLE_WP_LOGIN_ACTIONS, true ) );

		if ( ! $should_redirect ) {
			return false;
		}

		$args = wp_unslash( $_GET );
		if ( ! is_array( $args ) ) {
			$args = [];
		}
		if ( isset( $args['action'] ) && (string) $args['action'] === 'newaccount' ) {
			$args['action'] = 'rp';
		}

		$target = add_query_arg( $args, $this->getFrontLoginBaseUrl() );
		wp_safe_redirect( $target, 302 );
		exit;
	}

	public function maybeRedirectGravityFormsPreview(): void {
		if ( ! class_exists( 'GFForms', false ) ) {
			return;
		}
		if ( is_user_logged_in() || wp_doing_ajax() ) {
			return;
		}
		if ( ( $_SERVER['REQUEST_METHOD'] ?? '' ) !== 'GET' || ! isset( $_GET['gf_page'] ) ) {
			return;
		}

		$rel    = $this->getRelativeRequestPath();
		$custom = $this->getSlug();
		if ( $rel !== '' && ( strcasecmp( $rel, $custom ) === 0 || str_starts_with( strtolower( $rel ), strtolower( $custom ) . '/' ) ) ) {
			return;
		}

		wp_safe_redirect( home_url( '/' ) );
		exit;
	}

	/**
	 * When forcing custom login password URLs, send WooCommerce lost-password traffic there.
	 */
	public function maybeRedirectWooCommerceLostPassword(): void {
		if ( $this->getPasswordUrlMode() !== 'custom' ) {
			return;
		}
		if ( ! class_exists( 'WooCommerce', false ) || ! function_exists( 'is_wc_endpoint_url' ) ) {
			return;
		}

		$slug = (string) get_option( 'woocommerce_myaccount_lost_password_endpoint', 'lost-password' );
		if ( $slug === '' || ! is_wc_endpoint_url( $slug ) ) {
			return;
		}

		$key = isset( $_GET['key'] ) ? (string) wp_unslash( $_GET['key'] ) : '';
		if ( $key !== '' ) {
			$login = isset( $_GET['login'] ) ? rawurldecode( (string) wp_unslash( $_GET['login'] ) ) : '';
			if ( $login === '' && isset( $_GET['id'] ) ) {
				$user  = get_user_by( 'id', (int) $_GET['id'] );
				$login = $user instanceof \WP_User ? $user->user_login : '';
			}
			if ( $login !== '' ) {
				wp_safe_redirect( $this->buildPasswordResetUrl( $key, $login ), 302 );
				exit;
			}
		}

		wp_safe_redirect( $this->buildCustomLostPasswordUrl( '' ), 302 );
		exit;
	}

	/**
	 * Optionally send guests away from the WooCommerce My Account login form.
	 */
	public function maybeRedirectWooCommerceGuestAccount(): void {
		if ( empty( $this->getSettings()['woocommerce_guest_redirect'] ) ) {
			return;
		}
		if ( ! class_exists( 'WooCommerce', false ) || is_user_logged_in() || wp_doing_ajax() ) {
			return;
		}
		if ( ! function_exists( 'is_account_page' ) || ! is_account_page() ) {
			return;
		}

		// Keep password set/reset and other endpoints usable.
		if ( function_exists( 'is_wc_endpoint_url' ) && is_wc_endpoint_url() ) {
			return;
		}

		// Registration form on My Account (action=register).
		$action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : '';
		if ( $action === 'register' && $this->getPasswordUrlMode() !== 'custom' ) {
			return;
		}

		$redirect_to = function_exists( 'wc_get_page_permalink' ) ? (string) wc_get_page_permalink( 'myaccount' ) : home_url( '/' );
		wp_safe_redirect( wp_login_url( $redirect_to ), 302 );
		exit;
	}

	public function filterLostPasswordUrl( string $lostpassword_url, string $redirect ): string {
		$mode = $this->getPasswordUrlMode();

		if ( $mode === 'woocommerce' ) {
			$wc = $this->getWooCommerceLostPasswordEndpointUrl( $redirect );
			return $wc !== '' ? $wc : $this->buildCustomLostPasswordUrl( $redirect );
		}

		if ( $mode === 'custom' ) {
			return $this->buildCustomLostPasswordUrl( $redirect );
		}

		// auto
		if ( $this->shouldUsePublicPluginAccountUrls() ) {
			$public = $this->getPublicLostPasswordUrl( $redirect );
			if ( $public !== '' ) {
				return $public;
			}
		}

		return $this->buildCustomLostPasswordUrl( $redirect );
	}

	private function buildCustomLostPasswordUrl( string $redirect ): string {
		$url = add_query_arg( 'action', 'lostpassword', $this->getFrontLoginBaseUrl() );
		return $this->appendValidatedRedirectArg( $url, $redirect );
	}

	public function filterRegisterUrl( string $registration_url ): string {
		if ( $this->getPasswordUrlMode() === 'custom' ) {
			return add_query_arg( 'action', 'register', $this->getFrontLoginBaseUrl() );
		}

		if ( $this->shouldUsePublicPluginAccountUrls() ) {
			$public = $this->getPublicRegisterUrl();
			if ( $public !== '' ) {
				return $public;
			}
		}

		return add_query_arg( 'action', 'register', $this->getFrontLoginBaseUrl() );
	}

	public function filterWooCommerceLostPasswordUrl( string $url, string $redirect = '' ): string {
		$mode = $this->getPasswordUrlMode();

		if ( $mode === 'custom' ) {
			return $this->buildCustomLostPasswordUrl( $redirect );
		}

		if ( $mode === 'woocommerce' || $mode === 'auto' ) {
			$wc_url = $this->getWooCommerceLostPasswordEndpointUrl( $redirect );
			if ( $wc_url !== '' ) {
				return $wc_url;
			}
		}

		if ( strpos( $url, 'wp-login.php' ) === false ) {
			return $url;
		}

		return $this->replaceWpLoginInUrl( $url );
	}

	public function filterRetrievePasswordMessage( string $message, string $key, string $user_login, \WP_User $user_data ): string {
		unset( $user_data );
		$reset_url = $this->buildPasswordResetUrl( $key, $user_login );

		// Replace any lingering core login URLs in the message body.
		if ( str_contains( $message, 'wp-login.php' ) ) {
			$message = $this->replaceWpLoginInUrl( $message );
		}

		// Prefer an explicit reset URL rebuild when the message still lacks our slug.
		$slug = $this->getSlug();
		if ( $reset_url !== '' && ! str_contains( $message, '/' . $slug ) && str_contains( $message, 'action=rp' ) ) {
			$message = (string) preg_replace(
				'#https?://[^\s<>"]+wp-login\.php\?[^\s<>"]*action=rp[^\s<>"]*#i',
				$reset_url,
				$message
			);
		}

		return $message;
	}

	/**
	 * @param array<string, mixed> $email
	 * @return array<string, mixed>
	 */
	public function filterNewUserNotificationEmail( array $email, \WP_User $user, string $blogname ): array {
		unset( $blogname );
		if ( empty( $email['message'] ) || ! is_string( $email['message'] ) ) {
			return $email;
		}

		$message = $email['message'];
		if ( str_contains( $message, 'wp-login.php' ) ) {
			$email['message'] = $this->replaceWpLoginInUrl( $message );
		}

		return $email;
	}

	/**
	 * Rewrite WooCommerce new-account / reset-password email bodies onto the custom login URL when enabled.
	 */
	public function filterWooCommerceEmailContent( string $content ): string {
		if ( empty( $this->getSettings()['rewrite_woocommerce_emails'] ) ) {
			return $content;
		}
		if ( $this->getPasswordUrlMode() === 'woocommerce' ) {
			return $content;
		}

		return $this->rewriteWooCommercePasswordLinksInContent( $content );
	}

	private function rewriteWooCommercePasswordLinksInContent( string $content ): string {
		if ( $content === '' ) {
			return $content;
		}

		return (string) preg_replace_callback(
			'#https?://[^\s"\'<>]+lost-password/?\?[^\s"\'<>]+#i',
			function ( array $m ): string {
				$url    = html_entity_decode( $m[0], ENT_QUOTES, 'UTF-8' );
				$parsed = wp_parse_url( $url );
				if ( ! is_array( $parsed ) || empty( $parsed['query'] ) ) {
					return $m[0];
				}
				parse_str( $parsed['query'], $args );
				$key   = isset( $args['key'] ) ? (string) $args['key'] : '';
				$login = isset( $args['login'] ) ? rawurldecode( (string) $args['login'] ) : '';
				if ( $key === '' ) {
					return $m[0];
				}
				if ( $login === '' && isset( $args['id'] ) ) {
					$user  = get_user_by( 'id', (int) $args['id'] );
					$login = $user instanceof \WP_User ? $user->user_login : '';
				}
				if ( $login === '' ) {
					return $m[0];
				}

				return $this->buildPasswordResetUrl( $key, $login );
			},
			$content
		);
	}

	private function buildPasswordResetUrl( string $key, string $user_login ): string {
		if ( $key === '' || $user_login === '' ) {
			return '';
		}

		return add_query_arg(
			[
				'action' => 'rp',
				'key'    => $key,
				'login'  => rawurlencode( $user_login ),
			],
			$this->getFrontLoginBaseUrl()
		);
	}

	/**
	 * @return list<string>
	 */
	public static function discouragedSlugs(): array {
		return self::RESERVED_SLUGS;
	}

	/**
	 * Sanitize settings on save (server-side slug validation).
	 *
	 * @param array<string, mixed> $settings Raw settings from the admin UI.
	 * @param array<string, mixed> $existing Previously stored settings.
	 * @return array{0: array<string, mixed>, 1: string|null} Tuple of sanitized settings and optional error message.
	 */
	public static function sanitizeSettings( array $settings, array $existing = [] ): array {
		$defaults = [
			'login_slug'                 => 'login',
			'blocked_behavior'           => '404',
			'blocked_redirect_url'       => '',
			'redirect_wp_login_actions'  => false,
			'woocommerce_guest_redirect' => true,
			'password_url_mode'          => 'custom',
			'rewrite_woocommerce_emails' => true,
		];
		$merged = array_merge( $defaults, $existing, $settings );

		$raw = isset( $merged['login_slug'] ) ? trim( (string) $merged['login_slug'] ) : '';
		if ( $raw === '' ) {
			$existing_slug = sanitize_key( (string) ( $existing['login_slug'] ?? '' ) );
			$raw           = $existing_slug !== '' ? $existing_slug : 'login';
		}

		if ( ! preg_match( '/^[a-z0-9-]+$/', $raw ) ) {
			return [
				[],
				__( 'Login slug may only contain lowercase letters, numbers, and hyphens.', 'wp-powersuite' ),
			];
		}

		$slug = sanitize_key( $raw );
		if ( $slug === '' || $slug !== $raw ) {
			return [
				[],
				__( 'Invalid login slug.', 'wp-powersuite' ),
			];
		}

		if ( in_array( $slug, self::RESERVED_SLUGS, true ) ) {
			$existing_slug = sanitize_key( (string) ( $existing['login_slug'] ?? '' ) );
			if ( $slug !== $existing_slug ) {
				return [
					[],
					__( 'That slug is reserved or conflicts with WordPress / WooCommerce paths. Choose a unique phrase (e.g. your-site-crew).', 'wp-powersuite' ),
				];
			}
		}

		$page = get_page_by_path( $slug );
		if ( $page instanceof \WP_Post && $page->post_status === 'publish' ) {
			$existing_slug = sanitize_key( (string) ( $existing['login_slug'] ?? '' ) );
			if ( $slug !== $existing_slug ) {
				return [
					[],
					__( 'That slug matches an existing page. Choose a different login slug.', 'wp-powersuite' ),
				];
			}
		}

		$behavior = sanitize_key( (string) ( $merged['blocked_behavior'] ?? '404' ) );
		if ( ! in_array( $behavior, [ '404', 'redirect' ], true ) ) {
			$behavior = '404';
		}

		$redirect_url = esc_url_raw( trim( (string) ( $merged['blocked_redirect_url'] ?? '' ) ) );
		if ( $redirect_url !== '' && wp_validate_redirect( $redirect_url, false ) === false ) {
			$redirect_url = '';
		}

		$mode = sanitize_key( (string) ( $merged['password_url_mode'] ?? 'auto' ) );
		if ( ! in_array( $mode, [ 'auto', 'custom', 'woocommerce' ], true ) ) {
			$mode = 'auto';
		}

		return [
			[
				'login_slug'                 => $slug,
				'blocked_behavior'           => $behavior,
				'blocked_redirect_url'       => $redirect_url,
				'redirect_wp_login_actions'  => self::toBool( $merged['redirect_wp_login_actions'] ?? false ),
				'woocommerce_guest_redirect' => self::toBool( $merged['woocommerce_guest_redirect'] ?? false ),
				'password_url_mode'          => $mode,
				'rewrite_woocommerce_emails' => self::toBool( $merged['rewrite_woocommerce_emails'] ?? true ),
			],
			null,
		];
	}

	/**
	 * @param mixed $value Raw setting.
	 */
	private static function toBool( $value ): bool {
		if ( is_bool( $value ) ) {
			return $value;
		}
		if ( is_string( $value ) ) {
			return in_array( strtolower( $value ), [ '1', 'true', 'yes', 'on' ], true );
		}

		return (bool) $value;
	}

	/**
	 * Normalize a stored or submitted slug for display and runtime use.
	 */
	public static function normalizeLoginSlug( string $slug ): string {
		$slug = sanitize_key( $slug );

		return $slug !== '' ? $slug : 'login';
	}

	private function getSlug(): string {
		if ( $this->custom_slug_cache !== null ) {
			return $this->custom_slug_cache;
		}
		$this->custom_slug_cache = self::normalizeLoginSlug( (string) ( $this->getSettings()['login_slug'] ?? 'login' ) );
		return $this->custom_slug_cache;
	}

	private function getPasswordUrlMode(): string {
		$mode = sanitize_key( (string) ( $this->getSettings()['password_url_mode'] ?? 'auto' ) );
		return in_array( $mode, [ 'auto', 'custom', 'woocommerce' ], true ) ? $mode : 'auto';
	}

	private function getFrontLoginBaseUrl(): string {
		return home_url( '/' . $this->getSlug() );
	}

	private function getWpLoginScriptPath(): string {
		$home_path = $this->getHomePathPrefix();
		return $home_path !== '' ? '/' . $home_path . '/wp-login.php' : '/wp-login.php';
	}

	private function getHomePathPrefix(): string {
		if ( $this->home_path_prefix_cache !== null ) {
			return $this->home_path_prefix_cache;
		}
		$parsed = wp_parse_url( home_url() );
		$this->home_path_prefix_cache = isset( $parsed['path'] ) ? trim( $parsed['path'], '/' ) : '';
		return $this->home_path_prefix_cache;
	}

	private function appendValidatedRedirectArg( string $url, string $redirect ): string {
		if ( $redirect === '' ) {
			return $url;
		}
		$validated = wp_validate_redirect( $redirect, false );
		if ( ! is_string( $validated ) || $validated === '' ) {
			return $url;
		}
		return add_query_arg( 'redirect_to', $validated, $url );
	}

	private function shouldUsePublicPluginAccountUrls(): bool {
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			return false;
		}
		if ( wp_doing_cron() ) {
			return false;
		}
		if ( is_admin() && ! wp_doing_ajax() ) {
			return false;
		}
		return (bool) apply_filters( 'wp_powersuite_change_login_use_public_account_urls', true );
	}

	private function getPublicLostPasswordUrl( string $redirect ): string {
		$url = apply_filters( 'wp_powersuite_change_login_public_lostpassword_url', '', $redirect );
		if ( is_string( $url ) && $url !== '' ) {
			return $this->appendValidatedRedirectArg( $url, $redirect );
		}

		if ( function_exists( 'um_get_core_page' ) ) {
			$um = um_get_core_page( 'password-reset' );
			if ( is_string( $um ) && $um !== '' ) {
				return $this->appendValidatedRedirectArg( $um, $redirect );
			}
		}

		$mp = $this->getMemberPressForgotPasswordUrl();
		if ( $mp !== '' ) {
			return $this->appendValidatedRedirectArg( $mp, $redirect );
		}

		$pmpro = $this->getPaidMembershipsProLostPasswordUrl();
		if ( $pmpro !== '' ) {
			return $this->appendValidatedRedirectArg( $pmpro, $redirect );
		}

		if ( $this->isWooCommerceStorefrontLostPasswordContext() ) {
			$wc = $this->getWooCommerceLostPasswordEndpointUrl( $redirect );
			if ( $wc !== '' ) {
				return $wc;
			}
		}

		return '';
	}

	private function getPublicRegisterUrl(): string {
		$url = apply_filters( 'wp_powersuite_change_login_public_register_url', '' );
		if ( is_string( $url ) && $url !== '' ) {
			return $url;
		}

		if ( function_exists( 'um_get_core_page' ) ) {
			$um = um_get_core_page( 'register' );
			if ( is_string( $um ) && $um !== '' ) {
				return $um;
			}
		}

		if ( function_exists( 'pmpro_url' ) ) {
			$pm_login = pmpro_url( 'login' );
			if ( is_string( $pm_login ) && $pm_login !== '' ) {
				return add_query_arg( 'action', 'register', $pm_login );
			}
		}

		$wc = $this->getWooCommerceRegisterEndpointUrl();
		if ( $wc !== '' ) {
			return $wc;
		}

		if ( function_exists( 'bp_get_signup_page' ) ) {
			$bp = bp_get_signup_page();
			if ( is_numeric( $bp ) ) {
				$bp = get_permalink( (int) $bp );
			}
			if ( is_string( $bp ) && $bp !== '' ) {
				return $bp;
			}
		}

		return '';
	}

	private function getWooCommerceRegisterEndpointUrl(): string {
		if ( ! class_exists( 'WooCommerce', false ) ) {
			return '';
		}
		if ( ! function_exists( 'wc_get_page_permalink' ) || ! function_exists( 'wc_get_page_id' ) ) {
			return '';
		}
		if ( wc_get_page_id( 'myaccount' ) <= 0 ) {
			return '';
		}

		$wc_registration = get_option( 'woocommerce_enable_myaccount_registration' );
		$wp_registration = (bool) get_option( 'users_can_register' );
		if ( $wc_registration !== 'yes' && ! $wp_registration ) {
			return '';
		}

		return add_query_arg( 'action', 'register', wc_get_page_permalink( 'myaccount' ) );
	}

	private function getMemberPressForgotPasswordUrl(): string {
		if ( ! class_exists( 'MeprOptions', false ) || ! is_callable( [ 'MeprOptions', 'fetch' ] ) ) {
			return '';
		}
		$opts = \MeprOptions::fetch();
		if ( ! $opts || (int) $opts->login_page_id <= 0 ) {
			return '';
		}
		$login_url = $opts->login_page_url();
		if ( ! is_string( $login_url ) || $login_url === '' ) {
			return '';
		}
		$delim = str_contains( $login_url, '?' ) ? '&' : '?';
		return $login_url . $delim . 'action=forgot_password';
	}

	private function getPaidMembershipsProLostPasswordUrl(): string {
		if ( ! function_exists( 'pmpro_url' ) ) {
			return '';
		}
		$login = pmpro_url( 'login' );
		if ( ! is_string( $login ) || $login === '' ) {
			return '';
		}
		return add_query_arg( 'action', 'reset_pass', $login );
	}

	private function getWooCommerceLostPasswordEndpointUrl( string $redirect = '' ): string {
		if ( ! function_exists( 'wc_get_page_id' ) || ! function_exists( 'wc_get_page_permalink' ) ) {
			return '';
		}
		if ( wc_get_page_id( 'myaccount' ) <= 0 ) {
			return '';
		}

		if ( function_exists( 'wc_get_account_endpoint_url' ) ) {
			$url = wc_get_account_endpoint_url( 'lost-password' );
		} elseif ( function_exists( 'wc_get_endpoint_url' ) ) {
			$url = wc_get_endpoint_url( 'lost-password', '', wc_get_page_permalink( 'myaccount' ) );
		} else {
			return '';
		}

		if ( ! is_string( $url ) || $url === '' ) {
			return '';
		}

		return $this->appendValidatedRedirectArg( $url, $redirect );
	}

	private function isWooCommerceStorefrontLostPasswordContext(): bool {
		if ( ! class_exists( 'WooCommerce', false ) || ! function_exists( 'wc_get_page_id' ) ) {
			return false;
		}
		if ( wc_get_page_id( 'myaccount' ) <= 0 ) {
			return false;
		}
		if ( is_admin() && ! wp_doing_ajax() ) {
			return false;
		}

		if ( function_exists( 'is_account_page' ) && is_account_page() ) {
			return true;
		}
		if ( function_exists( 'is_cart' ) && is_cart() ) {
			return true;
		}
		if ( function_exists( 'is_checkout' ) && is_checkout() ) {
			return true;
		}
		if ( function_exists( 'is_woocommerce' ) && is_woocommerce() ) {
			return true;
		}

		return (bool) apply_filters( 'wp_powersuite_change_login_use_wc_lost_password', false );
	}

	private function getRelativeRequestPath(): string {
		if ( $this->relative_request_path_cache !== null ) {
			return $this->relative_request_path_cache;
		}
		if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
			$this->relative_request_path_cache = '';
			return '';
		}
		$path = wp_parse_url( wp_unslash( $_SERVER['REQUEST_URI'] ), PHP_URL_PATH );
		if ( ! is_string( $path ) ) {
			$this->relative_request_path_cache = '';
			return '';
		}
		$full_path = trim( $path, '/' );
		$home_path = $this->getHomePathPrefix();
		if ( $home_path !== '' && strpos( $full_path, $home_path ) === 0 ) {
			$this->relative_request_path_cache = trim( substr( $full_path, strlen( $home_path ) ), '/' );
			return $this->relative_request_path_cache;
		}
		$this->relative_request_path_cache = $full_path;
		return $this->relative_request_path_cache;
	}

	private function shouldRedirectLoggedInUserFromCustomLogin( array $query_params ): bool {
		if (
			isset( $query_params['wpps_lpc_preview'] )
			&& (string) $query_params['wpps_lpc_preview'] === '1'
			&& current_user_can( 'manage_options' )
		) {
			return false;
		}

		$action = isset( $query_params['action'] ) ? (string) $query_params['action'] : '';
		if ( in_array( $action, self::LOGGED_IN_CUSTOM_LOGIN_ACTIONS, true ) ) {
			return false;
		}
		if ( isset( $query_params['interim-login'] ) && (string) $query_params['interim-login'] === '1' ) {
			return false;
		}
		if ( isset( $query_params['checkemail'] ) && in_array( (string) $query_params['checkemail'], [ 'confirm', 'registered' ], true ) ) {
			return false;
		}
		return true;
	}

	private function shouldBlockWpAdminRequest(): bool {
		if ( $this->isExcludedWpAdminRequest() ) {
			return false;
		}

		$rel = strtolower( $this->getRelativeRequestPath() );

		return $rel === 'wp-admin' || str_starts_with( $rel, 'wp-admin/' );
	}

	private function shouldBlockCommonLoginAliases( string $custom_slug ): bool {
		$rel = $this->getRelativeRequestPath();
		if ( $rel === '' ) {
			return false;
		}
		$custom_lower = strtolower( $custom_slug );
		$segments = explode( '/', $rel );
		$first = strtolower( $segments[0] ?? '' );
		if ( $first === $custom_lower ) {
			return false;
		}
		if ( in_array( $first, [ 'wp-login', 'login.php' ], true ) ) {
			return true;
		}
		$second = strtolower( (string) ( $segments[1] ?? '' ) );
		if ( $first === 'admin' ) {
			return ( $segments[1] ?? '' ) === '' || str_contains( $second, '.php' );
		}
		if ( $first === 'login' ) {
			return ( $segments[1] ?? '' ) === '' || str_contains( $second, '.php' );
		}
		return false;
	}

	private function isSystemRequestBypass(): bool {
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			return true;
		}
		if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
			return true;
		}
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return true;
		}
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
			return true;
		}
		return (bool) apply_filters( 'wp_powersuite_change_login_url_skip_path_block', false );
	}

	private function serveBlockedResponse(): void {
		$settings = $this->getSettings();
		if ( ( $settings['blocked_behavior'] ?? '404' ) === 'redirect' ) {
			$target = (string) ( $settings['blocked_redirect_url'] ?? '' );
			if ( $target === '' || wp_validate_redirect( $target, false ) === false ) {
				$target = home_url( '/' );
			}
			wp_safe_redirect( $target, 302 );
			exit;
		}

		wp_die(
			esc_html__( 'Page not found.', 'wp-powersuite' ),
			esc_html__( '404 - Page Not Found', 'wp-powersuite' ),
			[ 'response' => 404 ]
		);
	}

	private function renderLoggedOutInterstitial(): void {
		$login_url = esc_url( $this->getFrontLoginBaseUrl() );
		wp_die(
			'<p>' . esc_html__( 'You have successfully logged out.', 'wp-powersuite' ) . '</p>' .
			'<p><a href="' . $login_url . '">' . esc_html__( 'Sign in again', 'wp-powersuite' ) . '</a> | ' .
			'<a href="' . esc_url( home_url( '/' ) ) . '">' . esc_html__( 'Go back to site', 'wp-powersuite' ) . '</a></p>',
			esc_html__( 'Logged Out', 'wp-powersuite' ),
			[ 'response' => 200 ]
		);
	}

	private function isStaticAssetRequest( string $request_uri ): bool {
		$path = wp_parse_url( $request_uri, PHP_URL_PATH );
		if ( ! is_string( $path ) ) {
			return false;
		}
		$path = strtolower( $path );
		static $exts = [ '.css', '.js', '.woff', '.woff2', '.ttf', '.otf', '.eot', '.ico', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.map' ];
		foreach ( $exts as $ext ) {
			if ( substr( $path, -strlen( $ext ) ) === $ext ) {
				return true;
			}
		}
		return strpos( $path, '/wp-content/plugins/' ) !== false
			|| strpos( $path, '/wp-content/themes/' ) !== false
			|| strpos( $path, '/wp-includes/' ) !== false;
	}

	private function isExcludedWpAdminRequest(): bool {
		$rel = strtolower( $this->getRelativeRequestPath() );

		return str_contains( $rel, 'admin-ajax.php' )
			|| str_contains( $rel, 'async-upload.php' )
			|| str_contains( $rel, 'admin-post.php' );
	}

	/**
	 * Skip path blocking when the request clearly is not login or wp-admin related.
	 */
	private function isPathBlockingCandidate( string $pagenow ): bool {
		if ( $pagenow === 'wp-login.php' ) {
			return true;
		}

		$rel = strtolower( $this->getRelativeRequestPath() );
		if ( $rel === '' ) {
			return false;
		}

		$first = explode( '/', $rel )[0] ?? '';
		foreach ( [ 'wp-admin', 'wp-login', 'admin', 'login' ] as $prefix ) {
			if ( $first === $prefix || str_starts_with( $rel, $prefix . '/' ) ) {
				return true;
			}
		}

		$custom = strtolower( $this->getSlug() );

		return $first === $custom || str_starts_with( $rel, $custom . '/' );
	}

	/**
	 * Rewrite site_url / network_site_url paths that target wp-login.php.
	 */
	public function filterLoginUrl( string $url, string $path, ?string $scheme ): string {
		unset( $scheme );
		if ( $path !== 'wp-login.php' && ! str_contains( (string) $path, 'wp-login.php' ) ) {
			return $url;
		}

		return $this->replaceWpLoginInUrl( $url );
	}

	public function filterRedirectUrl( string $location, int $status ): string {
		unset( $status );
		if ( ! str_contains( $location, 'wp-login.php' ) ) {
			return $location;
		}

		$parsed_check = wp_parse_url( $location );
		$home_parsed  = wp_parse_url( home_url() );

		// Absolute off-site redirects must not be rewritten.
		if (
			is_array( $parsed_check ) && ! empty( $parsed_check['host'] )
			&& is_array( $home_parsed ) && ! empty( $home_parsed['host'] )
			&& strtolower( (string) $parsed_check['host'] ) !== strtolower( (string) $home_parsed['host'] )
		) {
			return $location;
		}

		$slug = $this->getSlug();

		// Relative redirects from wp-login.php (e.g. "wp-login.php?checkemail=confirm").
		if ( ! preg_match( '#^[a-z][a-z0-9+.-]*:#i', $location ) ) {
			$relative = preg_replace( '#^/?wp-login\.php#', '/' . $slug, $location, 1 );
			if ( ! is_string( $relative ) || $relative === '' ) {
				return $location;
			}
			return home_url( $relative );
		}

		return $this->replaceWpLoginInUrl( $location );
	}

	public function filterLogoutUrl( string $logout_url, string $redirect ): string {
		unset( $redirect );
		if ( ! str_contains( $logout_url, 'wp-login.php' ) ) {
			return $logout_url;
		}

		return $this->replaceWpLoginInUrl( $logout_url );
	}

	public function filterLoginUrlDirect( string $login_url, string $redirect, bool $force_reauth ): string {
		if ( ! str_contains( $login_url, 'wp-login.php' ) ) {
			return $login_url;
		}

		$url    = $this->getFrontLoginBaseUrl();
		$parsed = wp_parse_url( $login_url );
		if ( is_array( $parsed ) && ! empty( $parsed['query'] ) ) {
			parse_str( $parsed['query'], $args );
			unset( $args['redirect_to'], $args['reauth'] );
			if ( ! empty( $args ) ) {
				$url = add_query_arg( $args, $url );
			}
		}

		$url = $this->appendValidatedRedirectArg( $url, $redirect );
		if ( $force_reauth ) {
			$url = add_query_arg( 'reauth', '1', $url );
		}
		return $url;
	}

	private function replaceWpLoginInUrl( string $url ): string {
		$slug = $this->getSlug();
		$replaced = preg_replace( '#(?<=/|^)wp-login\.php#', $slug, $url );
		return is_string( $replaced ) ? $replaced : $url;
	}

	public function onModuleToggled( string $module_id, bool $enable, array $module_metadata ): void {
		unset( $module_metadata );
		if ( $module_id !== self::MODULE_ID ) {
			return;
		}
		self::refreshRoutingState();
	}

	/**
	 * @param array<string, mixed> $settings
	 */
	public function onSettingsSaved( string $module_id, array $settings ): void {
		unset( $settings );
		if ( $module_id !== self::MODULE_ID ) {
			return;
		}
		self::refreshRoutingState();
	}

	/**
	 * Bust cached slug/settings and rewrite rules after slug or module state changes.
	 */
	public static function refreshRoutingState(): void {
		OptionStoreModuleSettingsRepository::clearCache();
		flush_rewrite_rules( false );
	}

	/**
	 * Prevent catch-all 404 redirect modules from hijacking the custom login path.
	 *
	 * @param bool   $skip Skip this path.
	 * @param string $path Request path (may include leading slash).
	 */
	public function filterSkip404Path( bool $skip, string $path ): bool {
		if ( $skip ) {
			return true;
		}

		return $this->pathMatchesCustomLoginSlug( $path );
	}

	private function pathMatchesCustomLoginSlug( string $path ): bool {
		$full_path = trim( $path, '/' );
		if ( $full_path === '' ) {
			return false;
		}

		$home_path = $this->getHomePathPrefix();
		$relative  = ( $home_path !== '' && str_starts_with( $full_path, $home_path . '/' ) )
			? trim( substr( $full_path, strlen( $home_path ) ), '/' )
			: ( ( $home_path !== '' && $full_path === $home_path ) ? '' : $full_path );

		$slug = $this->getSlug();

		return $relative === $slug || str_starts_with( $relative, $slug . '/' );
	}
}
