<?php
/**
 * Login page renderer.
 *
 * @package LoginPageCustomizerAndDesigner
 */

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

/**
 * Class LPCD_Login_Renderer
 *
 * Hooks into WordPress login page to apply template styles and content.
 */
class LPCD_Login_Renderer {

	/**
	 * Cached template data for the current request.
	 *
	 * @var array|null
	 */
	private $template = null;

	/**
	 * Whether we are in preview mode.
	 *
	 * @var bool
	 */
	private $is_preview = false;

	/**
	 * Check if the active template is the Yeti Character template.
	 *
	 * @return bool True if Yeti template is active.
	 */
	private function is_yeti_template() {
		$template = $this->get_template();
		if ( ! $template ) {
			return false;
		}
		// Check for the yeti-character ID in both the jsonData and template-level ID.
		$blocks = isset( $template['blocks'] ) ? $template['blocks'] : array();
		$id     = isset( $template['id'] ) ? $template['id'] : '';
		if ( 'yeti-character' === $id ) {
			return true;
		}
		// Fallback: check if file-based ID matches.
		foreach ( $blocks as $block ) {
			if ( isset( $block['id'] ) && 'loginform-1' === $block['id'] ) {
				// This is the Special One / Yeti template if the page background matches.
				$bg = isset( $template['pageSettings']['backgroundColor'] ) ? $template['pageSettings']['backgroundColor'] : '';
				if ( '#eff3f4' === $bg ) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Constructor.
	 */
	public function __construct() {
		add_action( 'init', array( $this, 'handle_preview_request' ), 1 );
		add_action( 'login_enqueue_scripts', array( $this, 'enqueue_assets' ) );
		add_action( 'login_head', array( $this, 'output_head' ) );
		add_filter( 'login_body_class', array( $this, 'add_body_classes' ) );
		add_filter( 'login_message', array( $this, 'render_blocks_above_form' ) );
		add_filter( 'login_headerurl', array( $this, 'custom_logo_url' ) );
		add_filter( 'login_headertext', array( $this, 'custom_logo_text' ) );
		add_filter( 'login_title', array( $this, 'custom_page_title' ), 10, 2 );
		add_filter( 'wp_login_errors', array( $this, 'custom_error_messages' ), 999, 2 );
		add_filter( 'login_redirect', array( $this, 'custom_login_redirect' ), 10, 3 );

		// Branding panel for split layouts.
		add_action( 'login_header', array( $this, 'render_branding_panel' ) );

		// CAPTCHA hooks (dispatches by provider).
		add_action( 'login_form', array( $this, 'render_captcha_login' ) );
		add_action( 'lostpassword_form', array( $this, 'render_captcha_lostpw' ) );
		add_filter( 'wp_authenticate_user', array( $this, 'verify_captcha_login' ), 10, 2 );
		add_action( 'lostpassword_post', array( $this, 'verify_captcha_lostpw' ) );

		// Advanced: custom JS footer.
		add_action( 'login_footer', array( $this, 'output_custom_js_footer' ) );

		// WooCommerce support.
		if ( LPCD_Settings_Manager::get( 'woocommerceEnabled', false ) ) {
			add_filter( 'woocommerce_login_redirect', array( $this, 'custom_login_redirect' ), 10, 2 );
		}
	}

	/**
	 * Handle preview requests via query parameter.
	 */
	public function handle_preview_request() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
		if ( ! isset( $_GET['lpcd_preview'] ) ) {
			return;
		}

		if ( ! isset( $_GET['preview_token'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['preview_token'] ) ), 'lpcd_preview_nonce' ) ) {
			wp_die(
				esc_html__( 'Unauthorized preview request.', 'login-page-customizer-and-designer' ),
				'',
				array( 'response' => 403 )
			);
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die(
				esc_html__( 'You do not have permission to preview.', 'login-page-customizer-and-designer' ),
				'',
				array( 'response' => 403 )
			);
		}

		$template_id = isset( $_GET['template_id'] ) ? absint( $_GET['template_id'] ) : 0;
		if ( $template_id > 0 ) {
			$template = LPCD_Template_Manager::get( $template_id );
			if ( $template && isset( $template['jsonData'] ) ) {
				$this->template   = $template['jsonData'];
				$this->is_preview = true;
			}
		}
	}

	/**
	 * Load the active template data.
	 *
	 * @return array|null Template data or null.
	 */
	private function get_template() {
		if ( null !== $this->template ) {
			return $this->template;
		}

		// Check if plugin is enabled.
		$enabled = LPCD_Settings_Manager::get( 'pluginEnabled', true );
		if ( ! $enabled || '0' === $enabled ) {
			return null;
		}

		$active = LPCD_Template_Manager::get_active();
		if ( $active && isset( $active['jsonData'] ) ) {
			$this->template = $active['jsonData'];
		}

		return $this->template;
	}

	/**
	 * Enqueue login page assets.
	 */
	public function enqueue_assets() {
		$template = $this->get_template();
		if ( ! $template ) {
			return;
		}

		// Animations CSS.
		wp_enqueue_style(
			'lpcd-animations',
			LPCD_URL . 'assets/css/animations.css',
			array(),
			LPCD_VERSION
		);

		// Yeti Character template assets.
		if ( $this->is_yeti_template() ) {
			wp_enqueue_style(
				'lpcd-yeti',
				LPCD_URL . 'assets/css/lpcd-yeti.css',
				array(),
				LPCD_VERSION
			);
			wp_enqueue_script(
				'lpcd-yeti',
				LPCD_URL . 'assets/js/lpcd-yeti.js',
				array(),
				LPCD_VERSION,
				true
			);
		}

		// Register a lightweight anchor script for inline JS.
		wp_register_script( 'lpcd-login-init', '', array(), LPCD_VERSION, true );
		wp_enqueue_script( 'lpcd-login-init' );

		// Build and attach all inline JS.
		$inline_js = $this->build_layout_script();

		$blocks = isset( $template['blocks'] ) ? $template['blocks'] : array();
		foreach ( $blocks as $block ) {
			if ( 'loginForm' === $block['type'] ) {
				if ( ! empty( $block['settings']['submitText'] ) ) {
					$inline_js .= $this->build_custom_button_text_script( sanitize_text_field( $block['settings']['submitText'] ) );
				}
				if ( isset( $block['settings'] ) ) {
					$inline_js .= $this->build_placeholder_script( $block['settings'] );
					$inline_js .= $this->build_custom_labels_script( $block['settings'] );
				}
				break;
			}
		}

		if ( ! empty( $inline_js ) ) {
			wp_add_inline_script( 'lpcd-login-init', $inline_js );
		}

		// Loading spinner JS.
		foreach ( $blocks as $block ) {
			if ( 'loginForm' === $block['type'] && ! empty( $block['settings']['showLoadingSpinner'] ) ) {
				$spinner_js = "document.addEventListener('DOMContentLoaded',function(){"
					. "var f=document.getElementById('loginform'),b=document.getElementById('wp-submit');"
					. "if(!f||!b)return;"
					. "f.addEventListener('submit',function(){"
					. "b.disabled=true;b.style.position='relative';b.style.color='transparent';"
					. "var s=document.createElement('span');"
					. "s.style.cssText='position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:18px;height:18px;border:2px solid rgba(255,255,255,0.4);border-top-color:#fff;border-radius:50%;animation:lpcd-spin 0.7s linear infinite;';"
					. "b.appendChild(s);});"
					. "});";
				wp_add_inline_script( 'lpcd-login-init', $spinner_js );
				break;
			}
		}
	}

	/**
	 * Output dynamic CSS and inline scripts in login head.
	 */
	public function output_head() {
		$template = $this->get_template();
		if ( ! $template ) {
			return;
		}

		// Generated CSS.
		$category = isset( $template['category'] ) ? $template['category'] : '';
		$css      = LPCD_Css_Generator::generate( $template, $category );
		if ( ! empty( $css ) ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			printf( "<style id=\"lpcd-dynamic-css\">\n%s\n</style>\n", wp_strip_all_tags( $css ) );
		}

		// Custom favicon.
		$favicon = LPCD_Settings_Manager::get( 'customFavicon', '' );
		if ( ! empty( $favicon ) ) {
			echo '<link rel="icon" href="' . esc_url( $favicon ) . '" />' . "\n";
		}

		// Video background.
		$this->render_video_background( $template );

		// Global custom CSS.
		$custom_css = LPCD_Settings_Manager::get( 'globalCustomCss', '' );
		if ( ! empty( $custom_css ) ) {
			// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			printf( "<style id=\"lpcd-global-custom-css\">\n%s\n</style>\n", wp_strip_all_tags( $custom_css ) );
		}

		// Custom JS Head.
		$js_head = LPCD_Settings_Manager::get( 'customJsHead', '' );
		if ( ! empty( $js_head ) ) {
			wp_register_script( 'lpcd-custom-js-head-dummy', '', array(), LPCD_VERSION, false );
			wp_enqueue_script( 'lpcd-custom-js-head-dummy' );
			wp_add_inline_script( 'lpcd-custom-js-head-dummy', $js_head );
		}
	}

	/**
	 * Build inline JS for structural layout manipulations.
	 *
	 * @return string JavaScript code.
	 */
	private function build_layout_script() {
		return "document.addEventListener('DOMContentLoaded',function(){" .
			"var forms=document.querySelectorAll('form#loginform,form#lostpasswordform,form#registerform');" .
			"forms.forEach(function(form){" .
			"var nav=document.getElementById('nav');" .
			"var back=document.getElementById('backtoblog');" .
			"if(nav)form.appendChild(nav);" .
			"if(back)form.appendChild(back);" .
			"});" .
			"});\n";
	}

	/**
	 * Build inline JS to change the submit button text.
	 *
	 * Only applies on the default login form, not on register or lostpassword.
	 *
	 * @param string $text Custom button text.
	 * @return string JavaScript code.
	 */
	private function build_custom_button_text_script( $text ) {
		$encoded = wp_json_encode( $text );
		return "document.addEventListener('DOMContentLoaded',function(){" .
			"var p=new URLSearchParams(window.location.search);" .
			"var a=p.get('action')||'';" .
			"if(a==='register'||a==='lostpassword'||a==='rp'||a==='resetpass'||a==='retrievepassword')return;" .
			"var btn=document.getElementById('wp-submit');" .
			"if(btn)btn.value=" . $encoded . ";" .
			"});\n";
	}

	/**
	 * Build inline JS for custom placeholder texts.
	 *
	 * @param array $settings Login form settings.
	 * @return string JavaScript code.
	 */
	private function build_placeholder_script( $settings ) {
		$user_ph = isset( $settings['usernamePlaceholder'] ) ? sanitize_text_field( $settings['usernamePlaceholder'] ) : '';
		$pw_ph   = isset( $settings['passwordPlaceholder'] ) ? sanitize_text_field( $settings['passwordPlaceholder'] ) : '';

		if ( empty( $user_ph ) && empty( $pw_ph ) ) {
			return '';
		}

		$js = "document.addEventListener('DOMContentLoaded',function(){";

		if ( ! empty( $user_ph ) ) {
			$js .= "var userEl=document.querySelector('input[name=\"log\"]');";
			$js .= 'if(userEl)userEl.placeholder=' . wp_json_encode( $user_ph ) . ';';
		}

		if ( ! empty( $pw_ph ) ) {
			$js .= "var pwEl=document.querySelector('input[name=\"pwd\"]');";
			$js .= 'if(pwEl)pwEl.placeholder=' . wp_json_encode( $pw_ph ) . ';';
		}

		$js .= "});\n";
		return $js;
	}

	/**
	 * Build inline JS for custom label texts.
	 *
	 * Replaces WordPress default labels (Username, Password, Remember Me, Lost Password)
	 * with custom text from template settings.
	 *
	 * @param array $settings Login form settings.
	 * @return string JavaScript code.
	 */
	private function build_custom_labels_script( $settings ) {
		$username_label    = isset( $settings['usernameLabel'] ) ? sanitize_text_field( $settings['usernameLabel'] ) : '';
		$password_label    = isset( $settings['passwordLabel'] ) ? sanitize_text_field( $settings['passwordLabel'] ) : '';
		$remember_me_text  = isset( $settings['rememberMeText'] ) ? sanitize_text_field( $settings['rememberMeText'] ) : '';
		$lost_password_txt = isset( $settings['lostPasswordText'] ) ? sanitize_text_field( $settings['lostPasswordText'] ) : '';
		$show_remember_me  = isset( $settings['showRememberMe'] ) ? $settings['showRememberMe'] : true;
		$show_lost_pass    = isset( $settings['showLostPassword'] ) ? $settings['showLostPassword'] : true;
		$show_back_link    = isset( $settings['showBackLink'] ) ? $settings['showBackLink'] : true;
		$show_register     = isset( $settings['showRegisterLink'] ) ? $settings['showRegisterLink'] : true;

		$has_changes = ! empty( $username_label ) || ! empty( $password_label )
			|| ! empty( $remember_me_text ) || ! empty( $lost_password_txt )
			|| false === $show_remember_me || false === $show_lost_pass
			|| false === $show_back_link || false === $show_register;

		if ( ! $has_changes ) {
			return '';
		}

		$js = "document.addEventListener('DOMContentLoaded',function(){";
		$js .= "var p=new URLSearchParams(window.location.search);";
		$js .= "var a=p.get('action')||'';";
		$js .= "var isLogin=(a===''||a==='login');";

		// Replace username label.
		if ( ! empty( $username_label ) ) {
			$js .= "if(isLogin){";
			$js .= "var ul=document.querySelector('label[for=\"user_login\"]');";
			$js .= "if(ul){ul.childNodes[0].textContent=" . wp_json_encode( $username_label ) . ";}";
			$js .= "}";
		}

		// Replace password label.
		if ( ! empty( $password_label ) ) {
			$js .= "if(isLogin){";
			$js .= "var pl=document.querySelector('label[for=\"user_pass\"]');";
			$js .= "if(pl){pl.childNodes[0].textContent=" . wp_json_encode( $password_label ) . ";}";
			$js .= "}";
		}

		// Replace remember me text.
		if ( ! empty( $remember_me_text ) ) {
			$js .= "if(isLogin){";
			$js .= "var rm=document.querySelector('.forgetmenot label');";
			$js .= "if(rm){var cb=rm.querySelector('input');if(cb){rm.textContent='';rm.prepend(cb);rm.append(" . wp_json_encode( ' ' . $remember_me_text ) . ");}}";
			$js .= "}";
		}

		// Hide remember me.
		if ( false === $show_remember_me ) {
			$js .= "var fmn=document.querySelector('.forgetmenot');";
			$js .= "if(fmn)fmn.style.display='none';";
		}

		// Replace lost password text.
		if ( ! empty( $lost_password_txt ) ) {
			$js .= "if(isLogin){";
			$js .= "var lp=document.querySelector('#nav a');";
			$js .= "if(lp&&lp.href.indexOf('lostpassword')>-1){lp.textContent=" . wp_json_encode( $lost_password_txt ) . ";}";
			$js .= "}";
		}

		// Hide lost password link.
		if ( false === $show_lost_pass ) {
			$js .= "var nav=document.getElementById('nav');";
			$js .= "if(nav)nav.style.display='none';";
		}

		// Hide back to site link.
		if ( false === $show_back_link ) {
			$js .= "var bl=document.getElementById('backtoblog');";
			$js .= "if(bl)bl.style.display='none';";
		}

		// Hide register link (in the #nav element).
		if ( false === $show_register ) {
			$js .= "var navEl=document.getElementById('nav');";
			$js .= "if(navEl){var links=navEl.querySelectorAll('a');";
			$js .= "links.forEach(function(l){if(l.href.indexOf('action=register')>-1)l.style.display='none';});}";
		}

		$js .= "});\n";
		return $js;
	}

	/**
	 * Add CSS classes to the login body tag.
	 *
	 * @param array $classes Existing body classes.
	 * @return array Modified body classes.
	 */
	public function add_body_classes( $classes ) {
		$template = $this->get_template();
		if ( ! $template ) {
			return $classes;
		}

		$classes[] = 'lpcd-active';

	if ( $this->is_yeti_template() ) {
		$classes[] = 'lpcd-yeti';
	}

		// Page animation class.
		$anim_type = isset( $template['pageSettings']['pageAnimation']['type'] ) ? $template['pageSettings']['pageAnimation']['type'] : 'none';
		if ( 'none' !== $anim_type ) {
			$classes[] = 'lpcd-anim-' . sanitize_html_class( $anim_type );
		}

		return $classes;
	}

	/**
	 * Render non-form blocks above the login form.
	 *
	 * @param string $message Existing login message.
	 * @return string Modified login message.
	 */
	public function render_blocks_above_form( $message ) {
		$template = $this->get_template();
		if ( ! $template ) {
			return $message;
		}

		// Yeti Character template: inject the SVG character above the form.
		if ( $this->is_yeti_template() ) {
			$svg_file = LPCD_DIR . 'assets/svg/yeti.svg';
			$svg_html = '';
			if ( file_exists( $svg_file ) ) {
				$svg_html = file_get_contents( $svg_file ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
			}
			if ( ! empty( $svg_html ) ) {
				$yeti = '<div class="lpcd-yeti-container" style="order:0;padding:0;margin:0 auto 1.5em;">'
					. '<div class="lpcd-yeti-inner">' . $svg_html . '</div></div>';
				return $yeti . $message;
			}
		}

		// In split layout mode, non-form blocks are rendered in the branding panel instead.
		$layout_type = isset( $template['pageSettings']['layoutType'] ) ? $template['pageSettings']['layoutType'] : 'centered';
		if ( 'split_left' === $layout_type || 'split_right' === $layout_type ) {
			return $message;
		}

		$blocks = isset( $template['blocks'] ) ? $template['blocks'] : array();

		// Sort blocks by order.
		usort(
			$blocks,
			function ( $a, $b ) {
				$a_order = isset( $a['order'] ) ? absint( $a['order'] ) : 0;
				$b_order = isset( $b['order'] ) ? absint( $b['order'] ) : 0;
				return $a_order - $b_order;
			}
		);

		$html = '';

		foreach ( $blocks as $block ) {
			if ( 'loginForm' === $block['type'] ) {
				continue;
			}

			// In split layout, skip branding panel blocks.
			$layout_type = isset( $template['pageSettings']['layoutType'] ) ? $template['pageSettings']['layoutType'] : 'centered';
			if ( 'split_left' === $layout_type || 'split_right' === $layout_type ) {
				$block_panel = isset( $block['panel'] ) ? $block['panel'] : 'branding';
				if ( 'branding' === $block_panel ) {
					continue;
				}
			}

			$s     = isset( $block['settings'] ) ? $block['settings'] : array();
			$order = isset( $block['order'] ) ? absint( $block['order'] ) : 0;
			$style = 'style="order: ' . $order . '; width: 100%; margin: 0; padding: 0;"';

			$block_html = '';
			switch ( $block['type'] ) {
				case 'logo':
					$block_html = $this->render_logo_block( $s );
					break;
				case 'heading':
					$block_html = $this->render_heading_block( $s );
					break;
				case 'text':
					$block_html = $this->render_text_block( $s );
					break;
				case 'divider':
					$block_html = $this->render_divider_block( $s );
					break;
				case 'customHtml':
					$block_html = $this->render_custom_html_block( $s );
					break;
			}

			if ( ! empty( $block_html ) ) {
				$html .= '<div class="lpcd-form-block-wrapper" ' . $style . '>' . $block_html . '</div>';
			}
		}

		return $html . $message;
	}

	/**
	 * Render the branding panel for split layouts.
	 *
	 * Outputs a side panel with non-form blocks (logo, heading, text, divider)
	 * outside the #login container.
	 */
	public function render_branding_panel() {
		$template = $this->get_template();
		if ( ! $template ) {
			return;
		}

		$layout_type = isset( $template['pageSettings']['layoutType'] ) ? $template['pageSettings']['layoutType'] : 'centered';
		if ( 'split_left' !== $layout_type && 'split_right' !== $layout_type ) {
			return;
		}

		$blocks = isset( $template['blocks'] ) ? $template['blocks'] : array();

		// Sort blocks by order.
		usort(
			$blocks,
			function ( $a, $b ) {
				$a_order = isset( $a['order'] ) ? absint( $a['order'] ) : 0;
				$b_order = isset( $b['order'] ) ? absint( $b['order'] ) : 0;
				return $a_order - $b_order;
			}
		);

		$html = '';

		foreach ( $blocks as $block ) {
			// Use panel property; fallback: loginForm→form, others→branding.
			$block_panel = isset( $block['panel'] ) ? $block['panel'] : ( 'loginForm' === $block['type'] ? 'form' : 'branding' );
			if ( 'branding' !== $block_panel ) {
				continue;
			}

			$s     = isset( $block['settings'] ) ? $block['settings'] : array();
			$order = isset( $block['order'] ) ? absint( $block['order'] ) : 0;
			$style = 'style="order: ' . $order . '; width: 100%; margin: 0; padding: 0;"';

			$block_html = '';
			switch ( $block['type'] ) {
				case 'logo':
					$block_html = $this->render_logo_block( $s );
					break;
				case 'heading':
					$block_html = $this->render_heading_block( $s );
					break;
				case 'text':
					$block_html = $this->render_text_block( $s );
					break;
				case 'divider':
					$block_html = $this->render_divider_block( $s );
					break;
			}

			if ( ! empty( $block_html ) ) {
				$html .= '<div class="lpcd-branding-block-wrapper" ' . $style . '>' . $block_html . '</div>';
			}
		}

		echo wp_kses_post( '<div class="lpcd-branding-panel"><div class="lpcd-branding-content">' . $html . '</div></div>' );
	}

	/**
	 * Render logo block HTML.
	 *
	 * @param array $s Block settings.
	 * @return string HTML output.
	 */
	private function render_logo_block( $s ) {
		$source   = isset( $s['source'] ) ? $s['source'] : 'site';
		$img_url  = '';
		$link_url = isset( $s['linkUrl'] ) && ! empty( $s['linkUrl'] ) ? $s['linkUrl'] : home_url( '/' );

		switch ( $source ) {
			case 'site':
				$custom_logo_id = get_theme_mod( 'custom_logo' );
				if ( $custom_logo_id ) {
					$img = wp_get_attachment_image_src( $custom_logo_id, 'full' );
					if ( $img ) {
						$img_url = $img[0];
					}
				}
				break;
			case 'upload':
			case 'url':
				$img_url = isset( $s['imageUrl'] ) ? $s['imageUrl'] : '';
				break;
		}

		if ( empty( $img_url ) ) {
			return '';
		}

		$html  = '<div class="lpcd-block-logo">';
		$html .= '<a href="' . esc_url( $link_url ) . '">';
		$html .= '<img src="' . esc_url( $img_url ) . '" alt="' . esc_attr( get_bloginfo( 'name' ) ) . '" />';
		$html .= '</a>';
		$html .= '</div>';

		return $html;
	}

	/**
	 * Render heading block HTML.
	 *
	 * @param array $s Block settings.
	 * @return string HTML output.
	 */
	private function render_heading_block( $s ) {
		$text = isset( $s['text'] ) ? $s['text'] : 'Welcome Back';
		$tag  = isset( $s['tag'] ) ? $s['tag'] : 'h1';

		$allowed_tags = array( 'h1', 'h2', 'h3', 'h4', 'p' );
		if ( ! in_array( $tag, $allowed_tags, true ) ) {
			$tag = 'h1';
		}

		return '<' . $tag . ' class="lpcd-block-heading">' . esc_html( $text ) . '</' . $tag . '>';
	}

	/**
	 * Render text block HTML.
	 *
	 * @param array $s Block settings.
	 * @return string HTML output.
	 */
	private function render_text_block( $s ) {
		$text = isset( $s['text'] ) ? $s['text'] : 'Please sign in to continue.';
		return '<p class="lpcd-block-text">' . esc_html( $text ) . '</p>';
	}

	/**
	 * Render divider block HTML.
	 *
	 * @param array $s Block settings.
	 * @return string HTML output.
	 */
	private function render_divider_block( $s ) {
		return '<div class="lpcd-block-divider"><hr /></div>';
	}

	/**
	 * Custom logo URL.
	 *
	 * @param string $url Default URL.
	 * @return string Custom URL.
	 */
	public function custom_logo_url( $url ) {
		$template = $this->get_template();
		if ( ! $template ) {
			return $url;
		}

		$blocks = isset( $template['blocks'] ) ? $template['blocks'] : array();
		foreach ( $blocks as $block ) {
			if ( 'logo' === $block['type'] && ! empty( $block['settings']['linkUrl'] ) ) {
				return esc_url( $block['settings']['linkUrl'] );
			}
		}

		return home_url( '/' );
	}

	/**
	 * Custom logo text (alt text for WP login header link).
	 *
	 * @param string $text Default text.
	 * @return string Custom text.
	 */
	public function custom_logo_text( $text ) {
		return get_bloginfo( 'name' );
	}

	/**
	 * Custom page title for the login page.
	 *
	 * @param string $title Default title.
	 * @param string $action Current action.
	 * @return string Custom title.
	 */
	public function custom_page_title( $title, $action ) {
		$custom_title = LPCD_Settings_Manager::get( 'loginPageTitle', '' );
		if ( ! empty( $custom_title ) ) {
			return esc_html( $custom_title );
		}
		return $title;
	}

	/**
	 * Custom error messages.
	 *
	 * @param WP_Error $errors  Login errors.
	 * @param string   $redirect_to Redirect URL.
	 * @return WP_Error Modified errors.
	 */
	public function custom_error_messages( $errors, $redirect_to ) {
		// Only apply if plugin is enabled.
		if ( ! $this->get_template() ) {
			return $errors;
		}

		$settings = LPCD_Settings_Manager::get_all();

		// Generic error - replace everything with one message.
		if ( ! empty( $settings['genericErrorMessages'] ) ) {
			if ( $errors->has_errors() ) {
				$codes = $errors->get_error_codes();
				$errors = new WP_Error();
				$errors->add(
					'lpcd_generic',
					esc_html__( 'Invalid username or password.', 'login-page-customizer-and-designer' )
				);
			}
			return $errors;
		}

		// Map specific error codes to custom messages.
		$error_map = array(
			'incorrect_password'  => 'wrongPasswordMessage',
			'authentication_failed' => 'wrongPasswordMessage',
			'invalid_username'    => 'invalidUsernameMessage',
			'invalid_email'       => 'invalidEmailMessage',
			'empty_username'      => 'emptyUsernameMessage',
			'empty_password'      => 'emptyPasswordMessage',
		);

		foreach ( $error_map as $code => $setting_key ) {
			if ( $errors->get_error_message( $code ) && ! empty( $settings[ $setting_key ] ) ) {
				$errors->remove( $code );
				$errors->add( $code, esc_html( $settings[ $setting_key ] ) );
			}
		}

		return $errors;
	}

	/**
	 * Custom login redirect.
	 *
	 * @param string  $redirect_to Redirect URL.
	 * @param string  $requested Requested redirect URL.
	 * @param WP_User $user User object.
	 * @return string Redirect URL.
	 */
	public function custom_login_redirect( $redirect_to, $requested, $user ) {
		if ( is_wp_error( $user ) ) {
			return $redirect_to;
		}

		$type = LPCD_Settings_Manager::get( 'loginRedirectType', 'default' );

		switch ( $type ) {
			case 'homepage':
				return home_url( '/' );
			case 'custom':
				$custom_url = LPCD_Settings_Manager::get( 'loginRedirectUrl', '' );
				if ( ! empty( $custom_url ) ) {
					return esc_url_raw( $custom_url );
				}
				return $redirect_to;
			default:
				return $redirect_to;
		}
	}

	/**
	 * Render CAPTCHA widget on login form (dispatcher).
	 */
	public function render_captcha_login() {
		$settings = LPCD_Settings_Manager::get_all();
		$provider = isset( $settings['captchaProvider'] ) ? $settings['captchaProvider'] : 'none';

		switch ( $provider ) {
			case 'recaptcha_v2':
				if ( ! empty( $settings['recaptchaSiteKey'] ) && ! empty( $settings['recaptchaOnLogin'] ) ) {
					$this->enqueue_recaptcha_script();
					printf(
						'<div class="lpcd-recaptcha-wrap"><div class="g-recaptcha" data-sitekey="%1$s" data-theme="%2$s" data-size="%3$s"></div></div>',
						esc_attr( $settings['recaptchaSiteKey'] ),
						esc_attr( $settings['recaptchaTheme'] ),
						esc_attr( $settings['recaptchaSize'] )
					);
				}
				break;

			case 'recaptcha_v3':
				if ( ! empty( $settings['recaptchaV3SiteKey'] ) && ! empty( $settings['recaptchaV3OnLogin'] ) ) {
					$this->render_recaptcha_v3( $settings, 'login' );
				}
				break;

			case 'hcaptcha':
				if ( ! empty( $settings['hcaptchaSiteKey'] ) && ! empty( $settings['hcaptchaOnLogin'] ) ) {
					$this->render_hcaptcha( $settings );
				}
				break;

			case 'turnstile':
				if ( ! empty( $settings['turnstileSiteKey'] ) && ! empty( $settings['turnstileOnLogin'] ) ) {
					$this->render_turnstile( $settings );
				}
				break;
		}
	}

	/**
	 * Render CAPTCHA widget on lost password form (dispatcher).
	 */
	public function render_captcha_lostpw() {
		$settings = LPCD_Settings_Manager::get_all();
		$provider = isset( $settings['captchaProvider'] ) ? $settings['captchaProvider'] : 'none';

		switch ( $provider ) {
			case 'recaptcha_v2':
				if ( ! empty( $settings['recaptchaSiteKey'] ) && ! empty( $settings['recaptchaOnLostPw'] ) ) {
					$this->enqueue_recaptcha_script();
					printf(
						'<div class="lpcd-recaptcha-wrap"><div class="g-recaptcha" data-sitekey="%1$s" data-theme="%2$s" data-size="%3$s"></div></div>',
						esc_attr( $settings['recaptchaSiteKey'] ),
						esc_attr( $settings['recaptchaTheme'] ),
						esc_attr( $settings['recaptchaSize'] )
					);
				}
				break;

			case 'recaptcha_v3':
				if ( ! empty( $settings['recaptchaV3SiteKey'] ) && ! empty( $settings['recaptchaV3OnLostPw'] ) ) {
					$this->render_recaptcha_v3( $settings, 'lostpassword' );
				}
				break;

			case 'hcaptcha':
				if ( ! empty( $settings['hcaptchaSiteKey'] ) && ! empty( $settings['hcaptchaOnLostPw'] ) ) {
					$this->render_hcaptcha( $settings );
				}
				break;

			case 'turnstile':
				if ( ! empty( $settings['turnstileSiteKey'] ) && ! empty( $settings['turnstileOnLostPw'] ) ) {
					$this->render_turnstile( $settings );
				}
				break;
		}
	}

	/**
	 * Render reCAPTCHA v3 (invisible).
	 *
	 * @param array  $settings Plugin settings.
	 * @param string $action   Action name for v3.
	 */
	private function render_recaptcha_v3( $settings, $action = 'login' ) {
		wp_enqueue_script(
			'lpcd-recaptcha-v3',
			'https://www.google.com/recaptcha/api.js?render=' . esc_attr( $settings['recaptchaV3SiteKey'] ),
			array(),
			null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
			true
		);
		echo '<input type="hidden" name="g-recaptcha-response" id="lpcd-recaptcha-v3-token" />';
		$inline_js = "grecaptcha.ready(function(){grecaptcha.execute('" . esc_js( $settings['recaptchaV3SiteKey'] ) . "',{action:'" . esc_js( $action ) . "'}).then(function(t){document.getElementById('lpcd-recaptcha-v3-token').value=t;});});";
		wp_add_inline_script( 'lpcd-recaptcha-v3', $inline_js );
	}

	/**
	 * Render hCaptcha widget.
	 *
	 * @param array $settings Plugin settings.
	 */
	private function render_hcaptcha( $settings ) {
		wp_enqueue_script(
			'lpcd-hcaptcha',
			'https://js.hcaptcha.com/1/api.js',
			array(),
			null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
			true
		);
		printf(
			'<div class="lpcd-recaptcha-wrap"><div class="h-captcha" data-sitekey="%1$s" data-theme="%2$s"></div></div>',
			esc_attr( $settings['hcaptchaSiteKey'] ),
			esc_attr( $settings['hcaptchaTheme'] )
		);
	}

	/**
	 * Render Cloudflare Turnstile widget.
	 *
	 * @param array $settings Plugin settings.
	 */
	private function render_turnstile( $settings ) {
		wp_enqueue_script(
			'lpcd-turnstile',
			'https://challenges.cloudflare.com/turnstile/v0/api.js',
			array(),
			null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
			true
		);
		printf(
			'<div class="lpcd-recaptcha-wrap"><div class="cf-turnstile" data-sitekey="%1$s" data-theme="%2$s"></div></div>',
			esc_attr( $settings['turnstileSiteKey'] ),
			esc_attr( $settings['turnstileTheme'] )
		);
	}

	/**
	 * Enqueue the reCAPTCHA API script (only once).
	 */
	private function enqueue_recaptcha_script() {
		if ( ! wp_script_is( 'lpcd-recaptcha', 'enqueued' ) ) {
			wp_enqueue_script(
				'lpcd-recaptcha',
				'https://www.google.com/recaptcha/api.js',
				array(),
				null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- External API, no version control.
				true
			);
		}
	}

	/**
	 * Verify CAPTCHA on login (dispatcher).
	 *
	 * @param WP_User|WP_Error $user     User object or error.
	 * @param string           $password Password.
	 * @return WP_User|WP_Error User object or error.
	 */
	public function verify_captcha_login( $user, $password ) {
		$settings = LPCD_Settings_Manager::get_all();
		$provider = isset( $settings['captchaProvider'] ) ? $settings['captchaProvider'] : 'none';
		$msg      = esc_html( $settings['captchaFailedMessage'] );

		switch ( $provider ) {
			case 'recaptcha_v2':
				if ( empty( $settings['recaptchaOnLogin'] ) || empty( $settings['recaptchaSiteKey'] ) ) return $user;
				$token = isset( $_POST['g-recaptcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
				if ( empty( $token ) || ! $this->verify_recaptcha_token( $token, $settings['recaptchaSecretKey'] ) ) {
					return new WP_Error( 'lpcd_captcha_failed', $msg );
				}
				break;

			case 'recaptcha_v3':
				if ( empty( $settings['recaptchaV3OnLogin'] ) || empty( $settings['recaptchaV3SiteKey'] ) ) return $user;
				$token = isset( $_POST['g-recaptcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
				if ( empty( $token ) || ! $this->verify_recaptcha_v3_token( $token, $settings ) ) {
					return new WP_Error( 'lpcd_captcha_failed', $msg );
				}
				break;

			case 'hcaptcha':
				if ( empty( $settings['hcaptchaOnLogin'] ) || empty( $settings['hcaptchaSiteKey'] ) ) return $user;
				$token = isset( $_POST['h-captcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['h-captcha-response'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
				if ( empty( $token ) || ! $this->verify_hcaptcha_token( $token, $settings['hcaptchaSecretKey'] ) ) {
					return new WP_Error( 'lpcd_captcha_failed', $msg );
				}
				break;

			case 'turnstile':
				if ( empty( $settings['turnstileOnLogin'] ) || empty( $settings['turnstileSiteKey'] ) ) return $user;
				$token = isset( $_POST['cf-turnstile-response'] ) ? sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
				if ( empty( $token ) || ! $this->verify_turnstile_token( $token, $settings['turnstileSecretKey'] ) ) {
					return new WP_Error( 'lpcd_captcha_failed', $msg );
				}
				break;
		}

		return $user;
	}

	/**
	 * Verify CAPTCHA on lost password (dispatcher).
	 *
	 * @param WP_Error $errors Errors object.
	 */
	public function verify_captcha_lostpw( $errors ) {
		$settings = LPCD_Settings_Manager::get_all();
		$provider = isset( $settings['captchaProvider'] ) ? $settings['captchaProvider'] : 'none';
		$msg      = esc_html( $settings['captchaFailedMessage'] );

		$check = function( $condition, $post_key, $verify_fn ) use ( $errors, $msg ) {
			if ( ! $condition ) return;
			$token = isset( $_POST[ $post_key ] ) ? sanitize_text_field( wp_unslash( $_POST[ $post_key ] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
			if ( empty( $token ) || ! $verify_fn( $token ) ) {
				$errors->add( 'lpcd_captcha_failed', $msg );
			}
		};

		switch ( $provider ) {
			case 'recaptcha_v2':
				$check(
					! empty( $settings['recaptchaOnLostPw'] ) && ! empty( $settings['recaptchaSiteKey'] ),
					'g-recaptcha-response',
					function( $t ) use ( $settings ) { return $this->verify_recaptcha_token( $t, $settings['recaptchaSecretKey'] ); }
				);
				break;

			case 'recaptcha_v3':
				$check(
					! empty( $settings['recaptchaV3OnLostPw'] ) && ! empty( $settings['recaptchaV3SiteKey'] ),
					'g-recaptcha-response',
					function( $t ) use ( $settings ) { return $this->verify_recaptcha_v3_token( $t, $settings ); }
				);
				break;

			case 'hcaptcha':
				$check(
					! empty( $settings['hcaptchaOnLostPw'] ) && ! empty( $settings['hcaptchaSiteKey'] ),
					'h-captcha-response',
					function( $t ) use ( $settings ) { return $this->verify_hcaptcha_token( $t, $settings['hcaptchaSecretKey'] ); }
				);
				break;

			case 'turnstile':
				$check(
					! empty( $settings['turnstileOnLostPw'] ) && ! empty( $settings['turnstileSiteKey'] ),
					'cf-turnstile-response',
					function( $t ) use ( $settings ) { return $this->verify_turnstile_token( $t, $settings['turnstileSecretKey'] ); }
				);
				break;
		}
	}

	/**
	 * Verify reCAPTCHA v3 token.
	 *
	 * @param string $token    Response token.
	 * @param array  $settings Plugin settings.
	 * @return bool True if verified.
	 */
	private function verify_recaptcha_v3_token( $token, $settings ) {
		$response = wp_remote_post(
			'https://www.google.com/recaptcha/api/siteverify',
			array(
				'body' => array(
					'secret'   => $settings['recaptchaV3SecretKey'],
					'response' => $token,
					'remoteip' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return false;
		}

		$body      = json_decode( wp_remote_retrieve_body( $response ), true );
		$threshold = isset( $settings['recaptchaV3Threshold'] ) ? floatval( $settings['recaptchaV3Threshold'] ) : 0.5;

		return isset( $body['success'] ) && true === $body['success']
			&& isset( $body['score'] ) && floatval( $body['score'] ) >= $threshold;
	}

	/**
	 * Verify hCaptcha token.
	 *
	 * @param string $token  Response token.
	 * @param string $secret Secret key.
	 * @return bool True if verified.
	 */
	private function verify_hcaptcha_token( $token, $secret ) {
		$response = wp_remote_post(
			'https://hcaptcha.com/siteverify',
			array(
				'body' => array(
					'secret'   => $secret,
					'response' => $token,
					'remoteip' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return false;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		return isset( $body['success'] ) && true === $body['success'];
	}

	/**
	 * Verify Turnstile token.
	 *
	 * @param string $token  Response token.
	 * @param string $secret Secret key.
	 * @return bool True if verified.
	 */
	private function verify_turnstile_token( $token, $secret ) {
		$response = wp_remote_post(
			'https://challenges.cloudflare.com/turnstile/v0/siteverify',
			array(
				'body' => array(
					'secret'   => $secret,
					'response' => $token,
					'remoteip' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return false;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		return isset( $body['success'] ) && true === $body['success'];
	}

	/**
	 * Make the reCAPTCHA verification API call.
	 *
	 * @param string $token  User's reCAPTCHA response token.
	 * @param string $secret Server secret key.
	 * @return bool True if verified.
	 */
	private function verify_recaptcha_token( $token, $secret ) {
		$response = wp_remote_post(
			'https://www.google.com/recaptcha/api/siteverify',
			array(
				'body' => array(
					'secret'   => $secret,
					'response' => $token,
					'remoteip' => isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return false;
		}

		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		return isset( $body['success'] ) && true === $body['success'];
	}

	/**
	 * Render video background if configured in page settings.
	 *
	 * @param array $template Template data.
	 */
	private function render_video_background( $template ) {
		$ps = isset( $template['pageSettings'] ) ? $template['pageSettings'] : array();

		if ( 'video' !== ( isset( $ps['backgroundType'] ) ? $ps['backgroundType'] : '' ) ) {
			return;
		}

		$source = isset( $ps['videoSource'] ) ? $ps['videoSource'] : 'youtube';
		$object_fit = isset( $ps['videoObjectFit'] ) ? $ps['videoObjectFit'] : 'cover';

		// Video wrapper CSS.
		$css  = ".lpcd-video-bg-wrap { position:fixed;top:0;left:0;width:100%;height:100%;z-index:-1;overflow:hidden; }\n";
		
		if ( 'contain' === $object_fit ) {
			$css .= ".lpcd-video-bg-wrap iframe,.lpcd-video-bg-wrap video { position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);max-width:100%;max-height:100%;width:100%;height:100%;object-fit:contain; }\n";
		} elseif ( 'fill' === $object_fit ) {
			$css .= ".lpcd-video-bg-wrap iframe,.lpcd-video-bg-wrap video { position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:100%;height:100%;object-fit:fill; }\n";
		} else {
			// cover
			$css .= ".lpcd-video-bg-wrap iframe,.lpcd-video-bg-wrap video { position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);min-width:100%;min-height:100%;width:auto;height:auto;object-fit:cover; }\n";
		}

		// Overlay.
		$overlay_color   = ! empty( $ps['overlayEnabled'] ) ? esc_attr( $ps['overlayColor'] ) : 'transparent';
		$overlay_opacity = ! empty( $ps['overlayEnabled'] ) ? floatval( $ps['overlayOpacity'] ) : 0;
		$css .= ".lpcd-video-overlay { position:absolute;top:0;left:0;width:100%;height:100%;background:" . $overlay_color . ";opacity:" . esc_attr( $overlay_opacity ) . "; }\n";

		// Mobile fallback.
		$play_on_mobile = ! empty( $ps['videoPlayOnMobile'] );
		$fallback = isset( $ps['videoFallbackImage'] ) ? $ps['videoFallbackImage'] : '';
		
		if ( ! $play_on_mobile ) {
			if ( ! empty( $fallback ) ) {
				$css .= "@media(max-width:768px){ .lpcd-video-bg-wrap{display:none;} body.login{background-image:url('" . esc_url( $fallback ) . "');background-size:cover;background-position:center;} }\n";
			} else {
				$css .= "@media(max-width:768px){ .lpcd-video-bg-wrap{display:none;} }\n";
			}
		} else {
			// If playing on mobile, we still set the fallback image to body so it displays while loading
			if ( ! empty( $fallback ) ) {
				$css .= "@media(max-width:768px){ body.login{background-image:url('" . esc_url( $fallback ) . "');background-size:cover;background-position:center;} }\n";
			}
		}

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		printf( "<style id=\"lpcd-video-bg-css\">\n%s\n</style>\n", wp_strip_all_tags( $css ) );

		// Output video HTML.
		echo '<div class="lpcd-video-bg-wrap">';

		if ( 'youtube' === $source && ! empty( $ps['videoYoutubeUrl'] ) ) {
			$video_id = $this->extract_youtube_id( $ps['videoYoutubeUrl'] );
			if ( $video_id ) {
				$params  = 'autoplay=1&mute=1&loop=1&controls=0&playsinline=1&playlist=' . esc_attr( $video_id );
				$src     = 'https://www.youtube.com/embed/' . esc_attr( $video_id ) . '?' . $params;
				printf( '<iframe src="%s" frameborder="0" allowfullscreen allow="autoplay"></iframe>', esc_url( $src ) );
			}
		} elseif ( 'upload' === $source && ! empty( $ps['videoUploadUrl'] ) ) {
			printf( '<video autoplay muted loop playsinline><source src="%s" type="video/mp4"></video>', esc_url( $ps['videoUploadUrl'] ) );
		}

		echo '<div class="lpcd-video-overlay"></div>';
		echo '</div>';
	}

	/**
	 * Extract YouTube video ID from a URL.
	 *
	 * @param string $url YouTube URL.
	 * @return string|null Video ID or null.
	 */
	private function extract_youtube_id( $url ) {
		if ( preg_match( '/(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/|v\/))([a-zA-Z0-9_-]{11})/', $url, $matches ) ) {
			return $matches[1];
		}
		return null;
	}

	/**
	 * Render Custom HTML block.
	 *
	 * @param array $s Block settings.
	 * @return string HTML output.
	 */
	private function render_custom_html_block( $s ) {
		$content = isset( $s['htmlContent'] ) ? $s['htmlContent'] : '';
		if ( empty( $content ) ) {
			return '';
		}
		// Sanitize then run shortcodes.
		$content = wp_kses_post( $content );
		$content = do_shortcode( $content );
		return '<div class="lpcd-custom-html-block">' . $content . '</div>';
	}

	/**
	 * Output custom JavaScript in the login footer.
	 */
	public function output_custom_js_footer() {
		$js = LPCD_Settings_Manager::get( 'customJsFooter', '' );
		if ( ! empty( $js ) ) {
			wp_register_script( 'lpcd-custom-js-footer-dummy', '', array(), LPCD_VERSION, true );
			wp_enqueue_script( 'lpcd-custom-js-footer-dummy' );
			wp_add_inline_script( 'lpcd-custom-js-footer-dummy', $js );
		}
	}
}
