<?php
/**
 * Connexion et réinitialisation mot de passe (shortcodes front, sans wp-login.php).
 *
 * @package Avision
 */

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

/**
 * Slug de la page connexion (path segment, sans slashes). Défaut SaaS EN : login → /login/.
 *
 * @return string
 */
function avision_custom_auth_login_slug() {
	$slug = (string) apply_filters( 'avision_custom_auth_login_slug', 'login' );
	$slug = sanitize_title( $slug );
	return $slug !== '' ? $slug : 'login';
}

/**
 * Slug de la page réinitialisation / activation (shortcode reset). Défaut : activation → /activation/ (aligné email Brevo / CRM).
 *
 * @return string
 */
function avision_custom_auth_reset_password_slug() {
	$slug = (string) apply_filters( 'avision_custom_auth_reset_password_slug', 'activation' );
	$slug = sanitize_title( $slug );
	return $slug !== '' ? $slug : 'activation';
}

/**
 * Slug page reset LocalEdge (défaut localedge-activation → /localedge-activation/).
 *
 * @return string
 */
function avision_localedge_auth_reset_password_slug() {
	$slug = (string) apply_filters( 'avision_localedge_auth_reset_password_slug', 'localedge-activation' );
	$slug = sanitize_title( $slug );
	return $slug !== '' ? $slug : 'localedge-activation';
}

/**
 * URL de la page contenant [avision_localedge_reset_password].
 *
 * @return string
 */
function avision_localedge_auth_reset_page_url() {
	$path    = avision_localedge_auth_reset_password_slug();
	$default = trailingslashit( home_url( '/' . $path . '/' ) );
	return (string) apply_filters( 'avision_localedge_auth_reset_page_url', $default );
}

/**
 * URL login LocalEdge hosted (/localedge-login/ + lang).
 *
 * @param string $lang Code langue (en, fr, es, pt, tr, de).
 * @return string
 */
function avision_localedge_auth_login_page_url( $lang = 'en' ) {
	$lang = sanitize_text_field( (string) $lang );
	if ( $lang === 'pt-br' ) {
		$lang = 'pt';
	}
	$base = trailingslashit( home_url( '/localedge-login/' ) );
	return add_query_arg( 'lang', $lang !== '' ? $lang : 'en', $base );
}

/**
 * True si l'utilisateur a un rôle LocalEdge hosted.
 *
 * @param \WP_User|null $user Utilisateur WP.
 * @return bool
 */
function avision_auth_user_has_localedge_role( $user ) {
	$roles = array( 'localedge_solo', 'localedge_agency' );
	return $user instanceof WP_User && $user->exists()
		&& (bool) array_intersect( $roles, (array) $user->roles );
}

/**
 * URL de la page contenant [avision_custom_login].
 * Filtre final : avision_custom_auth_login_page_url (défaut : home_url + slug /login/).
 *
 * @return string
 */
function avision_custom_auth_login_page_url() {
	$path = avision_custom_auth_login_slug();
	$default = trailingslashit( home_url( '/' . $path . '/' ) );
	return (string) apply_filters( 'avision_custom_auth_login_page_url', $default );
}

/**
 * URL de la page contenant [avision_custom_reset_password] (lien e-mail reset & onboarding).
 * Filtre final : avision_custom_auth_reset_password_page_url (défaut : home_url + slug /activation/).
 *
 * @return string
 */
function avision_custom_auth_reset_page_url() {
	$path    = avision_custom_auth_reset_password_slug();
	$default = trailingslashit( home_url( '/' . $path . '/' ) );
	return (string) apply_filters( 'avision_custom_auth_reset_password_page_url', $default );
}

/**
 * Lit la clé reset depuis GET ou POST (sanitisée).
 *
 * @param bool $from_post Si true, privilégie POST (formulaire soumis).
 * @return string
 */
function avision_auth_get_reset_key_from_request( $from_post = false ) {
	if ( $from_post ) {
		if ( isset( $_POST['key'] ) ) {
			return sanitize_text_field( wp_unslash( (string) $_POST['key'] ) );
		}
		if ( isset( $_GET['key'] ) ) {
			return sanitize_text_field( wp_unslash( (string) $_GET['key'] ) );
		}
		return '';
	}
	if ( isset( $_GET['key'] ) ) {
		return sanitize_text_field( wp_unslash( (string) $_GET['key'] ) );
	}
	return '';
}

/**
 * Lit le paramètre login (GET ou POST) avec décodage URL propre.
 *
 * @param bool $from_post Si true, privilégie POST.
 * @return string
 */
function avision_auth_get_reset_login_from_request( $from_post = false ) {
	$raw = '';
	if ( $from_post ) {
		if ( isset( $_POST['login'] ) ) {
			$raw = (string) wp_unslash( $_POST['login'] );
		} elseif ( isset( $_GET['login'] ) ) {
			$raw = (string) wp_unslash( $_GET['login'] );
		}
	} elseif ( isset( $_GET['login'] ) ) {
		$raw = (string) wp_unslash( $_GET['login'] );
	}
	$raw = trim( $raw );
	if ( $raw === '' ) {
		return '';
	}
	$decoded = rawurldecode( $raw );
	if ( $decoded !== $raw && strpos( $raw, '%' ) !== false ) {
		$decoded = rawurldecode( $decoded );
	}
	return trim( $decoded );
}

/**
 * Vérifie la clé reset WordPress avec repli login / email si le paramètre ne correspond pas exactement à user_login.
 *
 * @param string $key Clé (déjà sanitisée).
 * @param string $login_param Valeur brute du lien (email ou login).
 * @return WP_User|WP_Error
 */
function avision_auth_check_password_reset_key_robust( $key, $login_param ) {
	$key         = is_string( $key ) ? trim( $key ) : '';
	$login_param = is_string( $login_param ) ? trim( $login_param ) : '';

	$do_log = ( defined( 'WP_DEBUG' ) && WP_DEBUG )
		|| (bool) apply_filters( 'avision_auth_log_password_reset_debug', false );
	if ( $do_log ) {
		$key_log = $key;
		if ( strlen( $key_log ) > 12 ) {
			$key_log = substr( $key_log, 0, 6 ) . '…' . substr( $key_log, -4 );
		}
		error_log( 'AVISION DEBUG: Key: ' . $key_log . ', Login param: ' . $login_param );
	}

	if ( $key === '' || $login_param === '' ) {
		return new WP_Error( 'avision_reset_empty', __( 'Your activation link is invalid or has expired. Please request a new password reset from the login page.', 'avision-dashboard' ) );
	}

	$candidates = array();
	$candidates[] = $login_param;
	$loose        = sanitize_user( $login_param, false );
	if ( $loose !== '' && ! in_array( $loose, $candidates, true ) ) {
		$candidates[] = $loose;
	}
	$strict = sanitize_user( $login_param, true );
	if ( $strict !== '' && ! in_array( $strict, $candidates, true ) ) {
		$candidates[] = $strict;
	}

	foreach ( $candidates as $try_login ) {
		$check = check_password_reset_key( $key, $try_login );
		if ( ! is_wp_error( $check ) ) {
			return $check;
		}
	}

	$user = null;
	if ( is_email( $login_param ) ) {
		$user = get_user_by( 'email', $login_param );
	}
	if ( ! $user ) {
		$user = get_user_by( 'login', $login_param );
	}
	if ( ! $user && $loose !== '' && $loose !== $login_param ) {
		$user = get_user_by( 'login', $loose );
	}
	if ( ! $user && $strict !== '' && $strict !== $login_param && $strict !== $loose ) {
		$user = get_user_by( 'login', $strict );
	}

	if ( $user && isset( $user->user_login ) ) {
		$check = check_password_reset_key( $key, $user->user_login );
		if ( ! is_wp_error( $check ) ) {
			return $check;
		}
	}

	return new WP_Error( 'avision_reset_invalid', __( 'Your activation link is invalid or has expired. Please request a new password reset from the login page.', 'avision-dashboard' ) );
}

/**
 * Indique si la requête est un flux invité reset/activation légitime (?key= & ?login= sur page login ou activation).
 *
 * @return bool
 */
function avision_auth_is_password_reset_guest_flow() {
	if ( empty( $_GET['key'] ) || empty( $_GET['login'] ) ) {
		return false;
	}
	$key = sanitize_text_field( wp_unslash( (string) $_GET['key'] ) );
	if ( $key === '' ) {
		return false;
	}
	$login_raw = isset( $_GET['login'] ) ? wp_unslash( (string) $_GET['login'] ) : '';
	if ( trim( $login_raw ) === '' ) {
		return false;
	}

	if ( ! function_exists( 'avision_route_guard_get_request_path' ) ) {
		return true;
	}

	$path       = avision_route_guard_get_request_path();
	$reset_slug = function_exists( 'avision_custom_auth_reset_password_slug' ) ? avision_custom_auth_reset_password_slug() : 'activation';
	$reset_slug = trim( (string) $reset_slug, '/' );
	$login_slug = function_exists( 'avision_custom_auth_login_slug' ) ? avision_custom_auth_login_slug() : 'login';
	$login_slug = trim( (string) $login_slug, '/' );

	if ( $reset_slug !== '' && ( strpos( $path, '/' . $reset_slug . '/' ) !== false || preg_match( '#/' . preg_quote( $reset_slug, '#' ) . '/?$#i', $path ) ) ) {
		return true;
	}
	$le_reset_slug = function_exists( 'avision_localedge_auth_reset_password_slug' ) ? avision_localedge_auth_reset_password_slug() : 'localedge-activation';
	$le_reset_slug = trim( (string) $le_reset_slug, '/' );
	if ( $le_reset_slug !== '' && ( strpos( $path, '/' . $le_reset_slug . '/' ) !== false || preg_match( '#/' . preg_quote( $le_reset_slug, '#' ) . '/?$#i', $path ) ) ) {
		return true;
	}
	if ( $login_slug !== '' && ( strpos( $path, '/' . $login_slug . '/' ) !== false || preg_match( '#/' . preg_quote( $login_slug, '#' ) . '/?$#i', $path ) ) ) {
		return true;
	}

	return false;
}

/**
 * Indique si l’URL de redirection correspond à la page de connexion (court-circuite les boucles /login → /login).
 *
 * @param string $url URL absolue validée.
 * @return bool
 */
function avision_auth_redirect_target_points_to_login( $url ) {
	if ( ! is_string( $url ) || $url === '' || ! function_exists( 'wp_parse_url' ) ) {
		return false;
	}
	$r_path = wp_parse_url( $url, PHP_URL_PATH );
	if ( ! is_string( $r_path ) || $r_path === '' ) {
		return false;
	}
	$r_norm = strtolower( untrailingslashit( $r_path ) );
	$candidates = array( avision_custom_auth_login_page_url() );
	if ( function_exists( 'wp_login_url' ) ) {
		$candidates[] = wp_login_url();
	}
	foreach ( $candidates as $c ) {
		if ( ! is_string( $c ) || $c === '' ) {
			continue;
		}
		$c_path = wp_parse_url( $c, PHP_URL_PATH );
		if ( ! is_string( $c_path ) || $c_path === '' ) {
			continue;
		}
		if ( $r_norm === strtolower( untrailingslashit( $c_path ) ) ) {
			return true;
		}
	}
	return false;
}

/**
 * URL cockpit par défaut après login custom : agency si rôle avi_agency, sinon premium.
 *
 * @param \WP_User|null $user Utilisateur (courant ou fraîchement authentifié).
 * @return string URL avec slash final.
 */
function avision_custom_login_default_dashboard_url_for_user( $user ) {
	$premium = trailingslashit( home_url( '/premium-dashboard/' ) );
	$agency  = trailingslashit( home_url( '/agency-dashboard/' ) );
	if ( $user instanceof \WP_User && $user->exists() && in_array( 'avi_agency', (array) $user->roles, true ) ) {
		return $agency;
	}
	return $premium;
}

/**
 * Redirige les utilisateurs déjà connectés hors de la page de connexion (évite affichage du formulaire).
 *
 * @return void
 */
function avision_redirect_logged_in_away_from_login_shortcode_page() {
	$req_uri = isset( $_SERVER['REQUEST_URI'] )
		? ( function_exists( 'wp_unslash' ) ? (string) wp_unslash( (string) $_SERVER['REQUEST_URI'] ) : (string) $_SERVER['REQUEST_URI'] )
		: '';
	if (
		( function_exists( 'is_admin' ) && is_admin() )
		|| ( defined( 'DOING_AJAX' ) && DOING_AJAX )
		|| ( $req_uri !== '' && strpos( $req_uri, 'avi-avi-control-center' ) !== false )
	) {
		return;
	}
	if ( ! function_exists( 'is_user_logged_in' ) || ! is_user_logged_in() ) {
		return;
	}
	$slug = function_exists( 'apply_filters' )
		? apply_filters( 'avision_custom_auth_login_page_slug', avision_custom_auth_login_slug() )
		: avision_custom_auth_login_slug();
	$on_login = ( function_exists( 'is_page' ) && is_string( $slug ) && $slug !== '' && is_page( $slug ) );
	if ( ! $on_login && function_exists( 'is_singular' ) && is_singular() ) {
		$post = get_post();
		if ( $post && is_string( $post->post_content ) && function_exists( 'has_shortcode' )
			&& has_shortcode( $post->post_content, 'avision_custom_login' ) ) {
			$on_login = true;
		}
	}
	if ( ! $on_login ) {
		return;
	}
	$current = function_exists( 'wp_get_current_user' ) ? wp_get_current_user() : null;
	$base    = function_exists( 'avision_custom_login_default_dashboard_url_for_user' )
		? avision_custom_login_default_dashboard_url_for_user( $current instanceof \WP_User ? $current : null )
		: trailingslashit( home_url( '/premium-dashboard/' ) );
	$target = function_exists( 'apply_filters' )
		? apply_filters( 'avision_custom_login_logged_in_redirect', $base, $current instanceof \WP_User ? $current : null )
		: $base;
	if ( ! headers_sent() && function_exists( 'wp_safe_redirect' ) ) {
		wp_safe_redirect( $target );
		exit;
	}
}

/**
 * Force text/html pour le prochain envoi lié au reset mot de passe.
 *
 * @return void
 */
function avision_auth_retrieve_password_mail_begin_html() {
	add_filter( 'wp_mail_content_type', 'avision_auth_retrieve_password_mail_content_type_html', 999 );
}

/**
 * Content-Type des e-mails reset (HTML).
 *
 * @return string
 */
function avision_auth_retrieve_password_mail_content_type_html() {
	return 'text/html';
}

/**
 * Retire le filtre wp_mail_content_type après l’e-mail de reset.
 *
 * @return void
 */
function avision_auth_retrieve_password_mail_cleanup_content_type() {
	remove_filter( 'wp_mail_content_type', 'avision_auth_retrieve_password_mail_content_type_html', 999 );
	remove_action( 'shutdown', 'avision_auth_retrieve_password_mail_cleanup_content_type', 999 );
	remove_action( 'wp_mail_succeeded', 'avision_auth_retrieve_password_mail_cleanup_content_type' );
	remove_action( 'wp_mail_failed', 'avision_auth_retrieve_password_mail_cleanup_content_type' );
}

/**
 * Planifie le nettoyage du content-type (succès / échec wp_mail ou fin de requête).
 *
 * @return void
 */
function avision_auth_retrieve_password_mail_schedule_cleanup() {
	add_action( 'shutdown', 'avision_auth_retrieve_password_mail_cleanup_content_type', 999 );
	add_action( 'wp_mail_succeeded', 'avision_auth_retrieve_password_mail_cleanup_content_type' );
	add_action( 'wp_mail_failed', 'avision_auth_retrieve_password_mail_cleanup_content_type' );
}

/**
 * Route l'e-mail reset vers LocalEdge ou Avision selon le rôle.
 *
 * @param string        $message    Corps généré par WP.
 * @param string        $key        Clé reset.
 * @param string        $user_login Login utilisateur.
 * @param \WP_User|null $user_data  Utilisateur (WP 4.3+).
 * @return string
 */
function avision_auth_retrieve_password_message_router( $message, $key, $user_login, $user_data = null ) {
	$user = ( $user_data instanceof WP_User && $user_data->exists() )
		? $user_data
		: get_user_by( 'login', $user_login );
	if ( avision_auth_user_has_localedge_role( $user ) ) {
		return avision_auth_retrieve_password_message_localedge( $message, $key, $user_login, $user );
	}
	return avision_auth_retrieve_password_message_avision( $message, $key, $user_login, $user_data );
}

/**
 * Corps HTML du mail reset mot de passe (page activation Avision), sans reprendre le texte brut WP.
 *
 * @param string        $message    Corps généré par WP (ignoré — construction dédiée).
 * @param string        $key        Clé reset.
 * @param string        $user_login Login utilisateur.
 * @param \WP_User|null $user_data  Utilisateur (WP 4.3+).
 * @return string
 */
function avision_auth_retrieve_password_message_avision( $message, $key, $user_login, $user_data = null ) {
	unset( $user_data, $message );

	avision_auth_retrieve_password_mail_begin_html();
	avision_auth_retrieve_password_mail_schedule_cleanup();

	$reset_base = avision_custom_auth_reset_page_url();
	$reset_url  = add_query_arg(
		array(
			'key'   => $key,
			'login' => rawurlencode( $user_login ),
		),
		$reset_base
	);

	$site_name = function_exists( 'wp_specialchars_decode' ) && function_exists( 'get_option' )
		? wp_specialchars_decode( (string) get_option( 'blogname' ), ENT_QUOTES )
		: '';

	$html  = '<div style="font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif; color: #0f172a; max-width: 600px; margin: 0 auto; line-height: 1.6;">';
	$html .= '<p>' . esc_html__( 'Someone has requested a password reset for the following account:', 'avision-dashboard' ) . '</p>';
	$html .= '<p><strong>' . esc_html__( 'Site Name:', 'avision-dashboard' ) . '</strong> ' . esc_html( $site_name ) . '<br>';
	$html .= '<strong>' . esc_html__( 'Username:', 'avision-dashboard' ) . '</strong> ' . esc_html( $user_login ) . '</p>';
	$html .= '<p>' . esc_html__( 'If this was a mistake, ignore this email and nothing will happen.', 'avision-dashboard' ) . '</p>';
	$html .= '<p>' . esc_html__( 'To reset your password, click the button below:', 'avision-dashboard' ) . '</p>';
	$html .= '<p style="margin: 30px 0;"><a href="' . esc_url( $reset_url ) . '" style="background-color: #f59e0b; color: #0f172a; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: bold; display: inline-block;">' . esc_html__( 'Set my new password', 'avision-dashboard' ) . '</a></p>';
	$html .= '<p style="font-size: 13px; color: #64748b; margin-top: 40px; padding-top: 20px; border-top: 1px solid #e2e8f0;">';
	$html .= esc_html__( 'If the button above doesn\'t work, copy and paste this link into your browser:', 'avision-dashboard' ) . '<br>';
	$html .= '<a href="' . esc_url( $reset_url ) . '" style="color: #3b82f6; word-break: break-all;">' . esc_html( $reset_url ) . '</a></p>';
	$html .= '</div>';

	return $html;
}

/**
 * Textes e-mail reset LocalEdge par langue utilisateur.
 *
 * @param string $lang Code langue.
 * @return array<string, string>
 */
function avision_localedge_reset_email_copy( $lang ) {
	$lang = sanitize_text_field( (string) $lang );
	if ( $lang === 'pt-br' ) {
		$lang = 'pt';
	}
	$copy = array(
		'en' => array(
			'intro'   => 'Someone has requested a password reset for your LocalEdge account:',
			'site'    => 'Product',
			'user'    => 'Email',
			'mistake' => 'If this was a mistake, ignore this email and nothing will happen.',
			'cta'     => 'To reset your password, click the button below:',
			'btn'     => 'Set my new password',
			'fallback'=> 'If the button above does not work, copy and paste this link into your browser:',
		),
		'fr' => array(
			'intro'   => 'Une reinitialisation de mot de passe a ete demandee pour votre compte LocalEdge :',
			'site'    => 'Produit',
			'user'    => 'E-mail',
			'mistake' => 'Si vous n\'etes pas a l\'origine de cette demande, ignorez cet e-mail.',
			'cta'     => 'Pour definir un nouveau mot de passe, cliquez sur le bouton ci-dessous :',
			'btn'     => 'Definir mon nouveau mot de passe',
			'fallback'=> 'Si le bouton ne fonctionne pas, copiez ce lien dans votre navigateur :',
		),
		'es' => array(
			'intro'   => 'Alguien ha solicitado restablecer la contrasena de su cuenta LocalEdge:',
			'site'    => 'Producto',
			'user'    => 'Correo',
			'mistake' => 'Si no fue usted, ignore este correo.',
			'cta'     => 'Para restablecer su contrasena, haga clic en el boton:',
			'btn'     => 'Definir nueva contrasena',
			'fallback'=> 'Si el boton no funciona, copie este enlace en su navegador:',
		),
		'pt' => array(
			'intro'   => 'Foi solicitada a redefinicao de senha da sua conta LocalEdge:',
			'site'    => 'Produto',
			'user'    => 'E-mail',
			'mistake' => 'Se nao foi voce, ignore este e-mail.',
			'cta'     => 'Para redefinir sua senha, clique no botao abaixo:',
			'btn'     => 'Definir nova senha',
			'fallback'=> 'Se o botao nao funcionar, copie este link no navegador:',
		),
		'tr' => array(
			'intro'   => 'LocalEdge hesabiniz icin sifre sifirlama talep edildi:',
			'site'    => 'Urun',
			'user'    => 'E-posta',
			'mistake' => 'Bu istegi siz yapmediyseniz, bu e-postayi yok sayin.',
			'cta'     => 'Sifrenizi sifirlamak icin asagidaki dugmeye tiklayin:',
			'btn'     => 'Yeni sifremi belirle',
			'fallback'=> 'Dugme calismazsa, bu baglantiyi tarayiciniza yapistirin:',
		),
		'de' => array(
			'intro'   => 'Fuer Ihr LocalEdge-Konto wurde eine Passwort-Zuruecksetzung angefordert:',
			'site'    => 'Produkt',
			'user'    => 'E-Mail',
			'mistake' => 'Wenn Sie das nicht waren, ignorieren Sie diese E-Mail.',
			'cta'     => 'Klicken Sie auf die Schaltflaeche, um Ihr Passwort zurueckzusetzen:',
			'btn'     => 'Neues Passwort festlegen',
			'fallback'=> 'Wenn die Schaltflaeche nicht funktioniert, kopieren Sie diesen Link:',
		),
	);
	return isset( $copy[ $lang ] ) ? $copy[ $lang ] : $copy['en'];
}

/**
 * Corps HTML du mail reset mot de passe LocalEdge (/localedge-activation/).
 *
 * @param string        $message    Corps WP (ignore).
 * @param string        $key        Clé reset.
 * @param string        $user_login Login utilisateur.
 * @param \WP_User|null $user       Utilisateur.
 * @return string
 */
function avision_auth_retrieve_password_message_localedge( $message, $key, $user_login, $user = null ) {
	unset( $message );

	avision_auth_retrieve_password_mail_begin_html();
	avision_auth_retrieve_password_mail_schedule_cleanup();

	$lang = 'en';
	if ( $user instanceof WP_User && $user->exists() ) {
		$lang_meta = get_user_meta( $user->ID, 'avi_localedge_lang', true );
		$lang      = is_string( $lang_meta ) && $lang_meta !== '' ? sanitize_text_field( $lang_meta ) : 'en';
	}
	if ( $lang === 'pt-br' ) {
		$lang = 'pt';
	}
	$c = avision_localedge_reset_email_copy( $lang );

	$reset_url = add_query_arg(
		array(
			'key'   => $key,
			'login' => rawurlencode( $user_login ),
			'lang'  => $lang,
		),
		avision_localedge_auth_reset_page_url()
	);

	$html  = '<div style="font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif; color: #0f172a; max-width: 600px; margin: 0 auto; line-height: 1.6;">';
	$html .= '<p>' . esc_html( $c['intro'] ) . '</p>';
	$html .= '<p><strong>' . esc_html( $c['site'] ) . ':</strong> LocalEdge<br>';
	$html .= '<strong>' . esc_html( $c['user'] ) . ':</strong> ' . esc_html( $user_login ) . '</p>';
	$html .= '<p>' . esc_html( $c['mistake'] ) . '</p>';
	$html .= '<p>' . esc_html( $c['cta'] ) . '</p>';
	$html .= '<p style="margin: 30px 0;"><a href="' . esc_url( $reset_url ) . '" style="background-color: #f97316; color: #ffffff; padding: 12px 24px; border-radius: 8px; text-decoration: none; font-weight: bold; display: inline-block;">' . esc_html( $c['btn'] ) . '</a></p>';
	$html .= '<p style="font-size: 13px; color: #64748b; margin-top: 40px; padding-top: 20px; border-top: 1px solid #e2e8f0;">';
	$html .= esc_html( $c['fallback'] ) . '<br>';
	$html .= '<a href="' . esc_url( $reset_url ) . '" style="color: #f97316; word-break: break-all;">' . esc_html( $reset_url ) . '</a></p>';
	$html .= '</div>';

	return $html;
}

/**
 * i18n formulaire reset LocalEdge (shortcode).
 *
 * @return array<string, array<string, string>>
 */
function avision_localedge_reset_i18n() {
	return array(
		'en' => array(
			'reset_title'        => 'Reset your LocalEdge password',
			'reset_pass1'        => 'New password',
			'reset_pass2'        => 'Confirm password',
			'reset_btn'          => 'Reset password',
			'reset_success'      => 'Your password has been changed. You can sign in.',
			'reset_back_login'   => 'Back to sign in',
			'reset_sign_in'      => 'Sign in to LocalEdge',
			'reset_incomplete'   => 'The reset link is incomplete.',
			'reset_mismatch'     => 'Passwords do not match or are empty.',
			'reset_too_short'    => 'Password must be at least 8 characters long.',
			'reset_wrong_product'=> 'This page is for LocalEdge accounts only.',
			'reset_invalid_link' => 'This reset link is invalid or has expired. Request a new email from the sign-in page.',
		),
		'fr' => array(
			'reset_title'        => 'Reinitialiser votre mot de passe LocalEdge',
			'reset_pass1'        => 'Nouveau mot de passe',
			'reset_pass2'        => 'Confirmer le mot de passe',
			'reset_btn'          => 'Reinitialiser',
			'reset_success'      => 'Votre mot de passe a ete modifie. Vous pouvez vous connecter.',
			'reset_back_login'   => 'Retour a la connexion',
			'reset_sign_in'      => 'Se connecter a LocalEdge',
			'reset_incomplete'   => 'Le lien de reinitialisation est incomplet.',
			'reset_mismatch'     => 'Les mots de passe ne correspondent pas.',
			'reset_too_short'    => 'Le mot de passe doit contenir au moins 8 caracteres.',
			'reset_wrong_product'=> 'Cette page est reservee aux comptes LocalEdge.',
			'reset_invalid_link' => 'Ce lien est invalide ou expire. Demandez un nouvel e-mail depuis la page de connexion.',
		),
		'es' => array(
			'reset_title'        => 'Restablecer contrasena LocalEdge',
			'reset_pass1'        => 'Nueva contrasena',
			'reset_pass2'        => 'Confirmar contrasena',
			'reset_btn'          => 'Restablecer',
			'reset_success'      => 'Su contrasena ha sido cambiada. Ya puede iniciar sesion.',
			'reset_back_login'   => 'Volver al inicio de sesion',
			'reset_sign_in'      => 'Iniciar sesion en LocalEdge',
			'reset_incomplete'   => 'El enlace de restablecimiento esta incompleto.',
			'reset_mismatch'     => 'Las contrasenas no coinciden.',
			'reset_too_short'    => 'La contrasena debe tener al menos 8 caracteres.',
			'reset_wrong_product'=> 'Esta pagina es solo para cuentas LocalEdge.',
			'reset_invalid_link' => 'Este enlace no es valido o ha expirado.',
		),
		'pt' => array(
			'reset_title'        => 'Redefinir senha LocalEdge',
			'reset_pass1'        => 'Nova senha',
			'reset_pass2'        => 'Confirmar senha',
			'reset_btn'          => 'Redefinir',
			'reset_success'      => 'Sua senha foi alterada. Voce ja pode entrar.',
			'reset_back_login'   => 'Voltar ao login',
			'reset_sign_in'      => 'Entrar no LocalEdge',
			'reset_incomplete'   => 'O link de redefinicao esta incompleto.',
			'reset_mismatch'     => 'As senhas nao coincidem.',
			'reset_too_short'    => 'A senha deve ter pelo menos 8 caracteres.',
			'reset_wrong_product'=> 'Esta pagina e apenas para contas LocalEdge.',
			'reset_invalid_link' => 'Este link e invalido ou expirou.',
		),
		'tr' => array(
			'reset_title'        => 'LocalEdge sifresini sifirla',
			'reset_pass1'        => 'Yeni sifre',
			'reset_pass2'        => 'Sifreyi onayla',
			'reset_btn'          => 'Sifreyi sifirla',
			'reset_success'      => 'Sifreniz degistirildi. Giris yapabilirsiniz.',
			'reset_back_login'   => 'Giris sayfasina don',
			'reset_sign_in'      => 'LocalEdge\'e giris yap',
			'reset_incomplete'   => 'Sifirlama baglantisi eksik.',
			'reset_mismatch'     => 'Sifreler eslesmiyor.',
			'reset_too_short'    => 'Sifre en az 8 karakter olmali.',
			'reset_wrong_product'=> 'Bu sayfa yalnizca LocalEdge hesaplari icindir.',
			'reset_invalid_link' => 'Bu baglanti gecersiz veya suresi dolmus.',
		),
		'de' => array(
			'reset_title'        => 'LocalEdge-Passwort zuruecksetzen',
			'reset_pass1'        => 'Neues Passwort',
			'reset_pass2'        => 'Passwort bestaetigen',
			'reset_btn'          => 'Passwort zuruecksetzen',
			'reset_success'      => 'Ihr Passwort wurde geaendert. Sie koennen sich anmelden.',
			'reset_back_login'   => 'Zurueck zur Anmeldung',
			'reset_sign_in'      => 'Bei LocalEdge anmelden',
			'reset_incomplete'   => 'Der Zuruecksetzungslink ist unvollstaendig.',
			'reset_mismatch'     => 'Passwoerter stimmen nicht ueberein.',
			'reset_too_short'    => 'Das Passwort muss mindestens 8 Zeichen haben.',
			'reset_wrong_product'=> 'Diese Seite ist nur fuer LocalEdge-Konten.',
			'reset_invalid_link' => 'Dieser Link ist ungueltig oder abgelaufen.',
		),
	);
}

/**
 * lostpassword_url → page connexion Avision (action=lostpassword).
 *
 * @param string $url     URL par défaut.
 * @param string $redirect Redirect optionnel.
 * @return string
 */
function avision_auth_lostpassword_url( $url, $redirect ) {
	unset( $url );
	$base = avision_custom_auth_login_page_url();
	if ( $redirect ) {
		$base = add_query_arg( 'redirect_to', rawurlencode( $redirect ), $base );
	}
	return add_query_arg( 'action', 'lostpassword', $base );
}

/**
 * login_url → page connexion Avision (hors reauth forcé si besoin).
 *
 * @param string $login_url   URL wp-login.
 * @param string $redirect    Redirect.
 * @param bool   $force_reauth Force reauth.
 * @return string
 */
function avision_auth_login_url( $login_url, $redirect, $force_reauth ) {
	unset( $login_url, $force_reauth );
	$base = avision_custom_auth_login_page_url();
	$args = array();
	if ( ! empty( $redirect ) ) {
		$args['redirect_to'] = $redirect;
	}
	return add_query_arg( $args, $base );
}

/**
 * Indique si la requête courante est une route auth (login / activation) par chemin seul.
 *
 * @param string $segment Segment après le premier slash (ex. login, activation).
 * @return bool
 */
function avision_auth_enqueue_uri_path_has_segment( $segment ) {
	$segment = is_string( $segment ) ? trim( $segment, '/' ) : '';
	if ( $segment === '' || ! isset( $_SERVER['REQUEST_URI'] ) ) {
		return false;
	}
	$uri  = function_exists( 'wp_unslash' ) ? wp_unslash( (string) $_SERVER['REQUEST_URI'] ) : (string) $_SERVER['REQUEST_URI'];
	$path = function_exists( 'wp_parse_url' ) ? wp_parse_url( $uri, PHP_URL_PATH ) : parse_url( $uri, PHP_URL_PATH );
	$path = is_string( $path ) ? $path : '';
	if ( $path === '' ) {
		return false;
	}
	return strpos( $path, '/' . $segment ) !== false || preg_match( '#/' . preg_quote( $segment, '#' ) . '/?$#', $path ) === 1;
}

/**
 * Enqueue avision-auth.css : pages login & activation, templates thème AVI, shortcode dans le contenu, ou URL slug filtré.
 *
 * @return void
 */
function avision_enqueue_auth_shortcode_assets() {
	if ( ! function_exists( 'is_singular' ) || ! is_singular() ) {
		return;
	}
	$login_slug = function_exists( 'avision_custom_auth_login_slug' ) ? avision_custom_auth_login_slug() : 'login';
	$reset_slug = function_exists( 'avision_custom_auth_reset_password_slug' ) ? avision_custom_auth_reset_password_slug() : 'activation';

	$need = false;

	// Pages nommées + slugs filtrés + templates « nus » du thème (do_shortcode hors contenu).
	if ( function_exists( 'is_page' ) ) {
		if ( is_page( 'login' ) || is_page( 'activation' ) ) {
			$need = true;
		}
		if ( ! $need && $login_slug !== '' && is_page( $login_slug ) ) {
			$need = true;
		}
		if ( ! $need && $reset_slug !== '' && is_page( $reset_slug ) ) {
			$need = true;
		}
	}
	if ( ! $need && function_exists( 'is_page_template' ) ) {
		if ( is_page_template( 'page-login-avi.php' ) || is_page_template( 'template-login.php' ) ) {
			$need = true;
		}
	}

	$post = get_post();
	$c    = ( $post && is_string( $post->post_content ) ) ? $post->post_content : '';
	if ( ! $need && $c !== '' && function_exists( 'has_shortcode' )
		&& ( has_shortcode( $c, 'avision_custom_login' ) || has_shortcode( $c, 'avision_custom_reset_password' ) ) ) {
		$need = true;
	}

	if ( ! $need && $login_slug !== '' && avision_auth_enqueue_uri_path_has_segment( $login_slug ) ) {
		$need = true;
	}
	if ( ! $need && $reset_slug !== '' && avision_auth_enqueue_uri_path_has_segment( $reset_slug ) ) {
		$need = true;
	}

	if ( ! $need ) {
		return;
	}

	$plugin_file = dirname( __DIR__ ) . '/avision-dashboard.php';
	$base        = defined( 'AVISION_URL' ) ? AVISION_URL : plugin_dir_url( $plugin_file );
	wp_enqueue_style(
		'avision-auth',
		$base . 'assets/css/avision-auth.css',
		array(),
		defined( 'AVISION_VERSION' ) ? AVISION_VERSION : '9.0.3'
	);
}

/**
 * Requête courante = page contenant le formulaire [avision_custom_login] (template_redirect / shortcode).
 *
 * @return bool
 */
function avision_auth_request_is_custom_login_page() {
	if ( function_exists( 'is_admin' ) && is_admin() ) {
		return false;
	}
	$login_slug = function_exists( 'avision_custom_auth_login_slug' ) ? avision_custom_auth_login_slug() : 'login';
	$slug       = function_exists( 'apply_filters' )
		? apply_filters( 'avision_custom_auth_login_page_slug', $login_slug )
		: $login_slug;

	if ( function_exists( 'is_singular' ) && is_singular() ) {
		if ( function_exists( 'is_page' ) && is_string( $slug ) && $slug !== '' && is_page( $slug ) ) {
			return true;
		}
		if ( function_exists( 'is_page' ) && is_page( 'login' ) ) {
			return true;
		}
		$post = get_queried_object();
		if ( $post instanceof WP_Post && is_string( $post->post_content ) && function_exists( 'has_shortcode' )
			&& has_shortcode( $post->post_content, 'avision_custom_login' ) ) {
			return true;
		}
		if ( function_exists( 'is_page_template' ) ) {
			if ( is_page_template( 'page-login-avi.php' ) || is_page_template( 'template-login.php' ) ) {
				return true;
			}
		}
	}

	return ( $login_slug !== '' && avision_auth_enqueue_uri_path_has_segment( $login_slug ) );
}

/**
 * Traite le POST login avant tout rendu HTML (wp_signon + redirection HTTP).
 * Hook template_redirect priorité 5 : avant le rendu du thème (pas d’en-têtes envoyés), page /login/ hors garde premium.
 *
 * @return void
 */
function avision_auth_handle_login_post_early() {
	if ( ( isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( (string) $_SERVER['REQUEST_METHOD'] ) : '' ) !== 'POST' ) {
		return;
	}
	if ( empty( $_POST['avision_login_submit'] ) ) {
		return;
	}
	if ( function_exists( 'is_admin' ) && is_admin() ) {
		return;
	}
	if ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) {
		return;
	}
	if ( ! avision_auth_request_is_custom_login_page() ) {
		return;
	}

	$login_url = avision_custom_auth_login_page_url();
	$log_pref  = isset( $_POST['log'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['log'] ) ) : '';
	$redir_raw = isset( $_POST['redirect_to'] ) ? wp_unslash( (string) $_POST['redirect_to'] ) : '';
	$redir_ok  = '';
	if ( $redir_raw !== '' && function_exists( 'wp_validate_redirect' ) && function_exists( 'esc_url_raw' ) ) {
		$redir_ok = wp_validate_redirect( esc_url_raw( $redir_raw ), '' );
	}

	$nonce_ok = function_exists( 'wp_verify_nonce' )
		&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['avision_login_nonce'] ?? '' ) ), 'avision_custom_login' );
	if ( ! $nonce_ok ) {
		$key = wp_generate_password( 20, false, false );
		set_transient(
			'avision_le_' . $key,
			array(
				'msg'         => __( 'Your session has expired. Please try again.', 'avision-dashboard' ),
				'log'         => $log_pref,
				'redirect_to' => $redir_ok,
			),
			300
		);
		wp_safe_redirect( add_query_arg( 'avision_le', $key, $login_url ) );
		exit;
	}

	if ( function_exists( 'is_user_logged_in' ) && is_user_logged_in() ) {
		$logged = function_exists( 'wp_get_current_user' ) ? wp_get_current_user() : null;
		$default_redirect = function_exists( 'avision_custom_login_default_dashboard_url_for_user' )
			? avision_custom_login_default_dashboard_url_for_user( $logged instanceof \WP_User ? $logged : null )
			: trailingslashit( home_url( '/premium-dashboard/' ) );
		if ( function_exists( 'apply_filters' ) ) {
			$default_redirect = apply_filters( 'avision_custom_login_success_redirect', $default_redirect, $logged instanceof \WP_User ? $logged : null );
		}
		wp_safe_redirect( $default_redirect );
		exit;
	}

	$pwd = isset( $_POST['pwd'] ) ? (string) wp_unslash( $_POST['pwd'] ) : '';
	$creds = array(
		'user_login'    => $log_pref,
		'user_password' => $pwd,
		'remember'      => ! empty( $_POST['rememberme'] ),
	);
	$user = wp_signon( $creds, is_ssl() );
	if ( is_wp_error( $user ) ) {
		$key = wp_generate_password( 20, false, false );
		set_transient(
			'avision_le_' . $key,
			array(
				'msg'         => wp_strip_all_tags( $user->get_error_message() ),
				'log'         => $log_pref,
				'redirect_to' => $redir_ok,
			),
			300
		);
		wp_safe_redirect( add_query_arg( 'avision_le', $key, $login_url ) );
		exit;
	}

	$raw_redirect = '';
	if ( isset( $_REQUEST['redirect_to'] ) ) {
		$raw_redirect = wp_unslash( (string) $_REQUEST['redirect_to'] );
	}
	$redirect = ( $raw_redirect !== '' && function_exists( 'wp_validate_redirect' ) && function_exists( 'esc_url_raw' ) )
		? wp_validate_redirect( esc_url_raw( $raw_redirect ), '' )
		: '';
	$default_redirect = function_exists( 'avision_custom_login_default_dashboard_url_for_user' )
		? avision_custom_login_default_dashboard_url_for_user( $user )
		: trailingslashit( home_url( '/premium-dashboard/' ) );
	if ( function_exists( 'apply_filters' ) ) {
		$default_redirect = apply_filters( 'avision_custom_login_success_redirect', $default_redirect, $user );
	}
	if ( $redirect === '' || avision_auth_redirect_target_points_to_login( $redirect ) ) {
		$redirect = $default_redirect;
	}
	wp_safe_redirect( $redirect );
	exit;
}

/**
 * Shortcode [avision_custom_login].
 *
 * @param array|string $atts Attributs.
 * @return string
 */
function avision_render_custom_login_shortcode( $atts ) {
	$atts = shortcode_atts(
		array(),
		$atts,
		'avision_custom_login'
	);
	unset( $atts );

	if ( function_exists( 'is_user_logged_in' ) && is_user_logged_in() ) {
		return '';
	}

	$avision_auth_login_url       = avision_custom_auth_login_page_url();
	$avision_auth_reset_page_url  = avision_custom_auth_reset_page_url();
	$avision_auth_login_error     = '';
	$avision_auth_lost_error      = '';
	$avision_auth_lost_success    = '';
	$avision_auth_prefill_log     = '';
	$avision_auth_flash_redirect_to = '';
	$avision_auth_show_lost       = isset( $_GET['action'] ) && sanitize_key( wp_unslash( (string) $_GET['action'] ) ) === 'lostpassword';

	if ( isset( $_GET['avision_le'] ) && is_string( $_GET['avision_le'] ) && $_GET['avision_le'] !== '' ) {
		$le_key = sanitize_text_field( wp_unslash( $_GET['avision_le'] ) );
		if ( preg_match( '/^[A-Za-z0-9]{12,64}$/', $le_key ) ) {
			$flash = get_transient( 'avision_le_' . $le_key );
			delete_transient( 'avision_le_' . $le_key );
			if ( is_array( $flash ) ) {
				if ( ! empty( $flash['msg'] ) && is_string( $flash['msg'] ) ) {
					$avision_auth_login_error = $flash['msg'];
				}
				if ( ! empty( $flash['log'] ) && is_string( $flash['log'] ) ) {
					$avision_auth_prefill_log = sanitize_text_field( $flash['log'] );
				}
				if ( ! empty( $flash['redirect_to'] ) && is_string( $flash['redirect_to'] ) ) {
					$avision_auth_flash_redirect_to = $flash['redirect_to'];
				}
			}
		}
	}

	if ( isset( $_POST['avision_do_lost_password'] ) && function_exists( 'wp_verify_nonce' ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['avision_lost_password_nonce'] ?? '' ) ), 'avision_lost_password' ) ) {
		$avision_auth_show_lost = true;
		$ulog                   = isset( $_POST['user_login'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['user_login'] ) ) : '';
		if ( $ulog === '' ) {
			$avision_auth_lost_error = __( 'Please enter your email or username.', 'avision-dashboard' );
		} elseif ( function_exists( 'retrieve_password' ) ) {
			$result = retrieve_password( $ulog );
			if ( is_wp_error( $result ) ) {
				$avision_auth_lost_error = $result->get_error_message();
			} else {
				$avision_auth_lost_success = __( 'If an account exists for this address, a password reset email has been sent.', 'avision-dashboard' );
			}
		}
	}

	ob_start();
	$tpl = defined( 'AVISION_PATH' ) ? AVISION_PATH . 'templates/shortcode-custom-login.php' : '';
	if ( $tpl !== '' && is_readable( $tpl ) ) {
		// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
		include $tpl;
	} else {
		echo '<p>' . esc_html__( 'Login template not found.', 'avision-dashboard' ) . '</p>';
	}
	return ob_get_clean();
}

/**
 * Shortcode [avision_custom_reset_password].
 *
 * @param array|string $atts Attributs.
 * @return string
 */
function avision_render_custom_reset_password_shortcode( $atts ) {
	$atts = shortcode_atts(
		array(),
		$atts,
		'avision_custom_reset_password'
	);
	unset( $atts );

	$avision_auth_login_url      = avision_custom_auth_login_page_url();
	$avision_auth_reset_page_url = avision_custom_auth_reset_page_url();
	$avision_auth_reset_error    = '';
	$avision_auth_reset_success  = '';
	$avision_auth_invalid_link   = '';
	$avision_auth_key            = avision_auth_get_reset_key_from_request( false );
	$avision_auth_login          = avision_auth_get_reset_login_from_request( false );

	if ( isset( $_POST['avision_do_reset_password'] ) && function_exists( 'wp_verify_nonce' ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['avision_reset_password_nonce'] ?? '' ) ), 'avision_reset_password_form' ) ) {
		$avision_auth_key   = avision_auth_get_reset_key_from_request( true );
		$avision_auth_login = avision_auth_get_reset_login_from_request( true );
		$pass1              = isset( $_POST['pass1'] ) ? (string) wp_unslash( $_POST['pass1'] ) : '';
		$pass2              = isset( $_POST['pass2'] ) ? (string) wp_unslash( $_POST['pass2'] ) : '';

		if ( $avision_auth_key === '' || $avision_auth_login === '' ) {
			$avision_auth_reset_error = __( 'The reset link is incomplete.', 'avision-dashboard' );
		} elseif ( $pass1 === '' || $pass1 !== $pass2 ) {
			$avision_auth_reset_error = __( 'Passwords do not match or are empty.', 'avision-dashboard' );
		} elseif ( strlen( $pass1 ) < 8 ) {
			$avision_auth_reset_error = __( 'Password must be at least 8 characters long.', 'avision-dashboard' );
		} else {
			$user = avision_auth_check_password_reset_key_robust( $avision_auth_key, $avision_auth_login );
			if ( is_wp_error( $user ) ) {
				$avision_auth_reset_error = $user->get_error_message();
			} elseif ( avision_auth_user_has_localedge_role( $user ) ) {
				$avision_auth_reset_error = __( 'Please use the LocalEdge password reset link sent to your email.', 'avision-dashboard' );
			} else {
				wp_set_password( $pass1, $user->ID );
				$avision_auth_reset_success = __( 'Your password has been changed. You can log in.', 'avision-dashboard' );
			}
		}
	}

	if ( $avision_auth_reset_success === '' && ( $avision_auth_key === '' || $avision_auth_login === '' ) && ! isset( $_POST['avision_do_reset_password'] ) ) {
		$avision_auth_invalid_link = __( 'This reset link is incomplete or has expired. Request a new email from the log in page.', 'avision-dashboard' );
	} elseif ( $avision_auth_reset_success === '' && ! isset( $_POST['avision_do_reset_password'] ) && $avision_auth_key !== '' && $avision_auth_login !== '' ) {
		$user_check = avision_auth_check_password_reset_key_robust( $avision_auth_key, $avision_auth_login );
		if ( is_wp_error( $user_check ) ) {
			$avision_auth_invalid_link = $user_check->get_error_message();
		} elseif ( avision_auth_user_has_localedge_role( $user_check ) ) {
			$avision_auth_invalid_link = __( 'Please use the LocalEdge password reset link sent to your email.', 'avision-dashboard' );
		}
	}

	ob_start();
	$tpl = defined( 'AVISION_PATH' ) ? AVISION_PATH . 'templates/shortcode-custom-reset-password.php' : '';
	if ( $tpl !== '' && is_readable( $tpl ) ) {
		// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
		include $tpl;
	} else {
		echo '<p>' . esc_html__( 'Password reset template not found.', 'avision-dashboard' ) . '</p>';
	}
	return ob_get_clean();
}

/**
 * Shortcode [avision_localedge_reset_password] — reset mot de passe LocalEdge hosted.
 *
 * @param array|string $atts Attributs.
 * @return string
 */
function avision_render_localedge_reset_password_shortcode( $atts ) {
	$atts = shortcode_atts(
		array(),
		$atts,
		'avision_localedge_reset_password'
	);
	unset( $atts );

	$le_lang = sanitize_text_field(
		isset( $_GET['lang'] ) ? wp_unslash( $_GET['lang'] ) : 'en'
	);
	if ( $le_lang === 'pt-br' ) {
		$le_lang = 'pt';
	}

	$le_i18n_all       = avision_localedge_reset_i18n();
	$le_t              = isset( $le_i18n_all[ $le_lang ] ) ? $le_i18n_all[ $le_lang ] : $le_i18n_all['en'];
	$le_login_url      = avision_localedge_auth_login_page_url( $le_lang );
	$le_reset_page_url = avision_localedge_auth_reset_page_url();
	$le_brand_name     = 'LocalEdge';
	$le_brand_color    = '#f97316';
	$le_reset_error    = '';
	$le_reset_success  = '';
	$le_invalid_link   = '';
	$le_key            = avision_auth_get_reset_key_from_request( false );
	$le_login_param    = avision_auth_get_reset_login_from_request( false );

	if ( isset( $_POST['le_do_reset_password'] ) && function_exists( 'wp_verify_nonce' ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['le_reset_password_nonce'] ?? '' ) ), 'le_reset_password' ) ) {
		$le_key         = avision_auth_get_reset_key_from_request( true );
		$le_login_param = avision_auth_get_reset_login_from_request( true );
		$pass1          = isset( $_POST['le_pass1'] ) ? (string) wp_unslash( $_POST['le_pass1'] ) : '';
		$pass2          = isset( $_POST['le_pass2'] ) ? (string) wp_unslash( $_POST['le_pass2'] ) : '';

		if ( $le_key === '' || $le_login_param === '' ) {
			$le_reset_error = $le_t['reset_incomplete'];
		} elseif ( $pass1 === '' || $pass1 !== $pass2 ) {
			$le_reset_error = $le_t['reset_mismatch'];
		} elseif ( strlen( $pass1 ) < 8 ) {
			$le_reset_error = $le_t['reset_too_short'];
		} else {
			$user = avision_auth_check_password_reset_key_robust( $le_key, $le_login_param );
			if ( is_wp_error( $user ) ) {
				$le_reset_error = $user->get_error_message();
			} elseif ( ! avision_auth_user_has_localedge_role( $user ) ) {
				$le_reset_error = $le_t['reset_wrong_product'];
			} else {
				wp_set_password( $pass1, $user->ID );
				$le_reset_success = $le_t['reset_success'];
				$lang_meta        = get_user_meta( $user->ID, 'avi_localedge_lang', true );
				$user_lang        = is_string( $lang_meta ) && $lang_meta !== '' ? sanitize_text_field( $lang_meta ) : $le_lang;
				if ( $user_lang === 'pt-br' ) {
					$user_lang = 'pt';
				}
				$le_login_url = avision_localedge_auth_login_page_url( $user_lang );
			}
		}
	}

	if ( $le_reset_success === '' && ( $le_key === '' || $le_login_param === '' ) && ! isset( $_POST['le_do_reset_password'] ) ) {
		$le_invalid_link = $le_t['reset_invalid_link'];
	} elseif ( $le_reset_success === '' && ! isset( $_POST['le_do_reset_password'] ) && $le_key !== '' && $le_login_param !== '' ) {
		$user_check = avision_auth_check_password_reset_key_robust( $le_key, $le_login_param );
		if ( is_wp_error( $user_check ) ) {
			$le_invalid_link = $user_check->get_error_message();
		} elseif ( ! avision_auth_user_has_localedge_role( $user_check ) ) {
			$le_invalid_link = $le_t['reset_wrong_product'];
		}
	}

	ob_start();
	$tpl = defined( 'AVISION_PATH' ) ? AVISION_PATH . 'templates/shortcode-localedge-reset-password.php' : '';
	if ( $tpl !== '' && is_readable( $tpl ) ) {
		// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
		include $tpl;
	} else {
		echo '<p>' . esc_html( $le_t['reset_invalid_link'] ) . '</p>';
	}
	return ob_get_clean();
}

/**
 * Évite un 404 WordPress sur la page de connexion avec ?action=lostpassword (query réservée / conflit de réécriture).
 *
 * @param bool     $preempt  Si déjà true, ne pas modifier.
 * @param \WP_Query $wp_query Requête principale.
 * @return bool
 */
function avision_auth_prevent_login_lostpassword_404( $preempt, $wp_query ) {
	unset( $wp_query );
	if ( $preempt ) {
		return $preempt;
	}
	if ( ! isset( $_GET['action'] ) || ! function_exists( 'sanitize_key' ) || ! function_exists( 'wp_unslash' ) ) {
		return $preempt;
	}
	if ( sanitize_key( wp_unslash( (string) $_GET['action'] ) ) !== 'lostpassword' ) {
		return $preempt;
	}
	if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
		return $preempt;
	}
	$uri      = function_exists( 'wp_unslash' ) ? wp_unslash( (string) $_SERVER['REQUEST_URI'] ) : (string) $_SERVER['REQUEST_URI'];
	$uri_path = function_exists( 'wp_parse_url' ) ? (string) wp_parse_url( $uri, PHP_URL_PATH ) : (string) parse_url( $uri, PHP_URL_PATH );
	$slug     = function_exists( 'avision_custom_auth_login_slug' ) ? avision_custom_auth_login_slug() : 'login';
	$slug     = trim( (string) $slug, '/' );
	if ( $slug === '' ) {
		$slug = 'login';
	}
	if ( $uri_path !== '' && preg_match( '#/' . preg_quote( $slug, '#' ) . '/?$#', $uri_path ) ) {
		return true;
	}
	if ( strpos( $uri, '/' . $slug . '/' ) !== false || strpos( $uri, '/' . $slug . '?' ) !== false ) {
		return true;
	}
	$le_login_slug = 'localedge-login';
	if ( $uri_path !== '' && preg_match( '#/' . preg_quote( $le_login_slug, '#' ) . '/?$#', $uri_path ) ) {
		return true;
	}
	if ( strpos( $uri, '/' . $le_login_slug . '/' ) !== false || strpos( $uri, '/' . $le_login_slug . '?' ) !== false ) {
		return true;
	}
	return $preempt;
}

/**
 * Enregistre shortcodes, filtres URL et assets.
 *
 * @return void
 */
function avision_register_auth_shortcodes() {
	add_shortcode( 'avision_custom_login', 'avision_render_custom_login_shortcode' );
	add_shortcode( 'avision_custom_reset_password', 'avision_render_custom_reset_password_shortcode' );
	add_shortcode( 'avision_localedge_reset_password', 'avision_render_localedge_reset_password_shortcode' );
	add_action( 'template_redirect', 'avision_auth_handle_login_post_early', 5 );
	add_action( 'template_redirect', 'avision_redirect_logged_in_away_from_login_shortcode_page', 3 );
	add_action( 'wp_enqueue_scripts', 'avision_enqueue_auth_shortcode_assets', 15 );
	add_filter( 'pre_handle_404', 'avision_auth_prevent_login_lostpassword_404', 5, 2 );
	add_filter( 'retrieve_password_message', 'avision_auth_retrieve_password_message_router', 10, 4 );
	add_filter( 'lostpassword_url', 'avision_auth_lostpassword_url', 10, 2 );
	add_filter( 'login_url', 'avision_auth_login_url', 10, 3 );
	add_filter( 'avision_dashboard_guard_login_url', 'avision_custom_auth_login_page_url', 10 );
	add_filter( 'avision_premium_dashboard_login_redirect_url', 'avision_custom_auth_login_page_url', 10 );
}

add_action( 'init', 'avision_register_auth_shortcodes', 20 );
