<?php
/**
 * Frontend Module
 *
 * Registers the Ambassador Signup/Login block and handles form submissions.
 *
 * @package AmbassadorHub\Frontend
 */

namespace AmbassadorHub\Frontend;

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

use AmbassadorHub\Admin\Setup\PageInstaller;
use function add_action;
use function add_filter;
use function get_option;

final class FrontendModule {
	/**
	 * Initialize frontend module hooks.
	 *
	 * @return void
	 */
	public static function init(): void {
		// Boot the central block template loader. Individual modules register
		// their templates via BlockTemplateLoader::register() in their own init().
		BlockTemplateLoader::boot();

		// Register plugin utility pages (Account, Signup, etc.) through the same
		// BlockTemplateLoader mechanism as fundraiser/campaign CPT pages. This lets
		// the active theme's page.php provide the outer chrome (header, footer,
		// sidebar, container style) in a generic, theme-agnostic way.
		add_action( 'init', array( self::class, 'register_page_templates' ), 25 );

		// Opt classic (non-FSE) themes into wide/full alignment support so that
		// wide-aligned blocks inside the plugin's block-template-fallback render
		// correctly even when the theme doesn't explicitly declare this support.
		add_action( 'after_setup_theme', array( self::class, 'add_classic_theme_support' ) );

		// Output theme-adaptive CSS custom properties so block styles pick up
		// the active theme's primary/accent colour instead of hardcoded values.
		add_action( 'wp_enqueue_scripts', array( self::class, 'enqueue_theme_vars' ) );
		add_action( 'enqueue_block_editor_assets', array( self::class, 'enqueue_theme_vars' ) );

		add_action( 'block_categories_all', array( self::class, 'register_block_category' ), 10, 2 );
	}

	/**
	 * Register plugin utility pages as BlockTemplateLoader templates.
	 *
	 * Runs at init priority 25 (after FSE template registration at priority 20)
	 * so the page IDs are available from wp_options. For each page definition
	 * that declares a template path, the page's block template is injected via
	 * the_content — exactly the same pipeline fundraiser/campaign CPT pages use.
	 *
	 * Classic themes: template_include returns page.php and inject_block_content
	 * replaces the DB content with the rendered template HTML.
	 * FSE themes: the DB block content is used directly (no action needed here).
	 *
	 * @return void
	 */
	public static function register_page_templates(): void {
		foreach ( PageInstaller::get_definitions() as $key => $definition ) {
			if ( empty( $definition['template'] ) ) {
				continue;
			}

			$page_id = (int) get_option( $definition['option_key'], 0 );
			if ( $page_id <= 0 ) {
				continue;
			}

			BlockTemplateLoader::register(
				array(
					'slug'       => 'page-' . $key,
					'title'      => $definition['title'],
					'post_types' => array(),
					'page_ids'   => array( $page_id ),
					'path'       => $definition['template'],
				)
			);
		}
	}

	/**
	 * Output theme-adaptive CSS custom properties for AmbassadorHub blocks.
	 *
	 * Registers --ambh-color-primary (resolved from the active theme's palette),
	 * plus --ambh-color-primary-hover and --ambh-color-primary-focus which are
	 * derived automatically via color-mix(). All block CSS files use these vars
	 * so buttons, progress bars, and interactive elements adopt the theme's
	 * primary accent colour without any hardcoded values.
	 *
	 * For block (FSE) themes the variable chains to --wp--preset--color--primary
	 * so it tracks live theme.json edits. For classic themes the colour is
	 * resolved from the registered editor-color-palette if available.
	 *
	 * @return void
	 */
	public static function enqueue_theme_vars(): void {
		[ 'hex' => $hex, 'css_var' => $css_var, 'secondary_hex' => $sec_hex, 'secondary_css_var' => $sec_var ] = self::resolve_theme_color();
		[ 'hex' => $contrast_hex, 'css_var' => $contrast_var ] = self::resolve_theme_contrast_color();

		$primary_css = $css_var
			? 'var(' . $css_var . ', ' . esc_attr( $hex ) . ')'
			: esc_attr( $hex );

		$secondary_css = $sec_var
			? 'var(' . $sec_var . ', ' . esc_attr( $sec_hex ) . ')'
			: esc_attr( $sec_hex );

		$contrast_css = $contrast_var
			? 'var(' . $contrast_var . ', ' . esc_attr( $contrast_hex ) . ')'
			: esc_attr( $contrast_hex );

		$progress_css = 'var(--ambh-color-secondary)';
		$text_css     = 'var(--wp--style--color--text,#1e1e1e)';
		$muted_css    = '#646970';
		$border_css   = '#e0e0e0';
		$bg_css       = '#f6f7f7';

		$css = sprintf(
            // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
			':root{--ambh-color-primary:%s;--ambh-color-primary-hover:color-mix(in srgb,var(--ambh-color-primary) 82%%,#000);--ambh-color-primary-focus:color-mix(in srgb,var(--ambh-color-primary) 25%%,transparent);--ambh-color-secondary:%s;--ambh-color-secondary-hover:color-mix(in srgb,var(--ambh-color-secondary) 82%%,#000);--ambh-color-progress:%s;--ambh-color-contrast:%s;--ambh-color-contrast-hover:color-mix(in srgb,var(--ambh-color-contrast) 82%%,#000);--ambh-color-contrast-text:var(--wp--preset--color--base,#fff);--ambh-text-color:%s;--ambh-text-muted:%s;--ambh-border-color:%s;--ambh-bg-subtle:%s;--ambh-font-xs:0.75rem;--ambh-font-sm:0.875rem;--ambh-font-base:1rem;--ambh-font-md:1.25rem;--ambh-font-lg:1.5rem;--ambh-font-xl:2rem;}',
			$primary_css,
			$secondary_css,
			$progress_css,
			$contrast_css,
			$text_css,
			$muted_css,
			$border_css,
			$bg_css
		);

        // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
		wp_register_style( 'ambh-theme-vars', false, array(), null );
		wp_enqueue_style( 'ambh-theme-vars' );
		wp_add_inline_style( 'ambh-theme-vars', $css );
	}

	/**
	 * Resolve the best primary colour from the active theme.
	 *
	 * Returns an array with:
	 *   'hex'     — six-digit hex string to use as a fallback value, e.g. "#503aa8".
	 *   'css_var' — CSS custom property name to chain to for block themes, e.g.
	 *               "--wp--preset--color--accent-3", or null for classic themes.
	 *
	 * For block themes colours come from theme.json via wp_get_global_settings(),
	 * which is the authoritative source and stays in sync with Site Editor edits.
	 * For classic themes editor-color-palette theme support is used.
	 *
	 * Preferred slug lookup (in order):
	 *   primary → accent → accent-1 → button → action → cta → brand → link
	 *
	 * If none match, the first palette colour that passes the button-viability
	 * check is used (not neutral AND enough contrast against white text).
	 *
	 * @return array{hex: string, css_var: string|null, secondary_hex: string, secondary_css_var: string|null}
	 */
	private static function resolve_theme_color(): array {
		$preferred           = array( 'primary', 'accent', 'accent-1', 'button', 'action', 'cta', 'brand', 'link' );
		$preferred_secondary = array( 'secondary', 'accent-2', 'accent-3', 'success', 'positive', 'emerald', 'green', 'support-2' );
		$default             = array(
			'hex'               => '#2271b1',
			'css_var'           => null,
			'secondary_hex'     => '#2271b1',
			'secondary_css_var' => null,
		);

		// ── Block themes ──────────────────────────────────────────────────────
		if ( wp_is_block_theme() && function_exists( 'wp_get_global_settings' ) ) {
			$settings = wp_get_global_settings( array( 'color', 'palette' ) );
			$palette  = array_merge(
				$settings['user'] ?? array(),
				$settings['theme'] ?? array()
			);

			$primary = null;

			// Preferred slugs first.
			foreach ( $preferred as $target ) {
				foreach ( $palette as $entry ) {
					if (
						isset( $entry['slug'], $entry['color'] )
						&& $entry['slug'] === $target
						&& self::is_viable_button_color( $entry['color'] )
					) {
						$primary = array(
							'hex'     => $entry['color'],
							'css_var' => '--wp--preset--color--' . $entry['slug'],
						);
						break 2;
					}
				}
			}

			// Fall back to first viable colour in palette order.
			if ( ! $primary ) {
				foreach ( $palette as $entry ) {
					if ( ! empty( $entry['color'] ) && isset( $entry['slug'] ) && self::is_viable_button_color( $entry['color'] ) ) {
						$primary = array(
							'hex'     => $entry['color'],
							'css_var' => self::is_safe_theme_slug( $entry['slug'] ) ? ( '--wp--preset--color--' . $entry['slug'] ) : null,
						);
						break;
					}
				}
			}

			if ( ! $primary ) {
				return $default;
			}

			// Secondary: prefer explicit non-primary palette slots first.
			$secondary = null;
			foreach ( $preferred_secondary as $target ) {
				foreach ( $palette as $entry ) {
					if (
						isset( $entry['slug'], $entry['color'] )
						&& $entry['slug'] === $target
						&& $entry['color'] !== $primary['hex']
						&& '--wp--preset--color--' . $entry['slug'] !== $primary['css_var']
						&& self::is_viable_secondary_color( $entry['color'] )
					) {
						$secondary = array(
							'secondary_hex'     => $entry['color'],
							'secondary_css_var' => '--wp--preset--color--' . $entry['slug'],
						);
						break 2;
					}
				}
			}

			// Fall back to the first distinct palette colour that works as a
			// progress/stat accent, even if it would be too light for a CTA.
			if ( ! $secondary ) {
				foreach ( $palette as $entry ) {
					$candidate_css_var = isset( $entry['slug'] ) && self::is_safe_theme_slug( $entry['slug'] )
						? ( '--wp--preset--color--' . $entry['slug'] )
						: null;
					if (
						! empty( $entry['color'] )
						&& isset( $entry['slug'] )
						&& $entry['color'] !== $primary['hex']
						&& $candidate_css_var !== $primary['css_var']
						&& self::is_viable_secondary_color( $entry['color'] )
					) {
						$secondary = array(
							'secondary_hex'     => $entry['color'],
							'secondary_css_var' => $candidate_css_var,
						);
						break;
					}
				}
			}

			return $primary + ( $secondary ?? array(
				'secondary_hex'     => $primary['hex'],
				'secondary_css_var' => $primary['css_var'],
			) );
		}

		// ── Classic themes ────────────────────────────────────────────────────
		$palette = get_theme_support( 'editor-color-palette' );
		if ( empty( $palette[0] ) || ! is_array( $palette[0] ) ) {
			return $default;
		}

		$colors  = $palette[0];
		$primary = null;

		foreach ( $preferred as $target ) {
			foreach ( $colors as $entry ) {
				if (
					isset( $entry['slug'], $entry['color'] )
					&& $entry['slug'] === $target
					&& self::is_viable_button_color( $entry['color'] )
				) {
					$primary = array(
						'hex'     => $entry['color'],
						'css_var' => null,
					);
					break 2;
				}
			}
		}

		if ( ! $primary ) {
			foreach ( $colors as $entry ) {
				if ( ! empty( $entry['color'] ) && self::is_viable_button_color( $entry['color'] ) ) {
					$primary = array(
						'hex'     => $entry['color'],
						'css_var' => null,
					);
					break;
				}
			}
		}

		if ( ! $primary ) {
			return $default;
		}

		$secondary = null;
		foreach ( $preferred_secondary as $target ) {
			foreach ( $colors as $entry ) {
				if (
					isset( $entry['slug'], $entry['color'] )
					&& $entry['slug'] === $target
					&& $entry['color'] !== $primary['hex']
					&& self::is_viable_secondary_color( $entry['color'] )
				) {
					$secondary = array(
						'secondary_hex'     => $entry['color'],
						'secondary_css_var' => null,
					);
					break 2;
				}
			}
		}

		if ( ! $secondary ) {
			foreach ( $colors as $entry ) {
				if (
					! empty( $entry['color'] )
					&& $entry['color'] !== $primary['hex']
					&& self::is_viable_secondary_color( $entry['color'] )
				) {
					$secondary = array(
						'secondary_hex'     => $entry['color'],
						'secondary_css_var' => null,
					);
					break;
				}
			}
		}

		return $primary + ( $secondary ?? array(
			'secondary_hex'     => $primary['hex'],
			'secondary_css_var' => $primary['css_var'],
		) );
	}

	/**
	 * Resolve a contrast colour for alternate CTA usage.
	 *
	 * This is intended for cases like the donation submit button where the main
	 * primary accent is already used elsewhere on the page. It prefers explicit
	 * contrast/text palette slots exposed by the theme.
	 *
	 * @return array{hex: string, css_var: string|null}
	 */
	private static function resolve_theme_contrast_color(): array {
		$preferred = array( 'contrast', 'foreground', 'text', 'strong', 'neutral-900', 'neutral-1000', 'dark', 'black' );
		$default   = array(
			'hex'     => '#1e1e1e',
			'css_var' => null,
		);

		if ( wp_is_block_theme() && function_exists( 'wp_get_global_settings' ) ) {
			$settings = wp_get_global_settings( array( 'color', 'palette' ) );
			$palette  = array_merge(
				$settings['user'] ?? array(),
				$settings['theme'] ?? array()
			);

			foreach ( $preferred as $target ) {
				foreach ( $palette as $entry ) {
					if ( isset( $entry['slug'], $entry['color'] ) && $entry['slug'] === $target && self::is_viable_secondary_color( $entry['color'] ) ) {
						return array(
							'hex'     => $entry['color'],
							'css_var' => '--wp--preset--color--' . $entry['slug'],
						);
					}
				}
			}

			foreach ( $palette as $entry ) {
				if ( ! empty( $entry['color'] ) && isset( $entry['slug'] ) && self::is_viable_button_color( $entry['color'] ) ) {
					return array(
						'hex'     => $entry['color'],
						'css_var' => self::is_safe_theme_slug( $entry['slug'] ) ? ( '--wp--preset--color--' . $entry['slug'] ) : null,
					);
				}
			}

			return $default;
		}

		$palette = get_theme_support( 'editor-color-palette' );
		if ( empty( $palette[0] ) || ! is_array( $palette[0] ) ) {
			return $default;
		}

		foreach ( $preferred as $target ) {
			foreach ( $palette[0] as $entry ) {
				if ( isset( $entry['slug'], $entry['color'] ) && $entry['slug'] === $target && self::is_viable_secondary_color( $entry['color'] ) ) {
					return array(
						'hex'     => $entry['color'],
						'css_var' => null,
					);
				}
			}
		}

		foreach ( $palette[0] as $entry ) {
			if ( ! empty( $entry['color'] ) && self::is_viable_button_color( $entry['color'] ) ) {
				return array(
					'hex'     => $entry['color'],
					'css_var' => null,
				);
			}
		}

		return $default;
	}

	/**
	 * Return true when a colour is suitable as a secondary stats/progress accent.
	 *
	 * This is intentionally less strict than button viability: the colour does
	 * not need to support white CTA text, it only needs to be a real, visible
	 * palette colour that is not effectively white or black.
	 *
	 * @param string $hex Colour value from the palette.
	 *
	 * @return bool
	 */
	private static function is_viable_secondary_color( string $hex ): bool {
		$hex = ltrim( $hex, '#' );
		if ( strlen( $hex ) === 3 ) {
			$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
		}
		if ( strlen( $hex ) !== 6 || ! ctype_xdigit( $hex ) ) {
			return false;
		}

		$r = hexdec( substr( $hex, 0, 2 ) );
		$g = hexdec( substr( $hex, 2, 2 ) );
		$b = hexdec( substr( $hex, 4, 2 ) );

		if ( $r > 245 && $g > 245 && $b > 245 ) {
			return false; }
		if ( $r < 12 && $g < 12 && $b < 12 ) {
			return false; }

		return true;
	}

	/**
	 * Return true when a theme.json palette slug is safe to interpolate into
	 * a raw `--wp--preset--color--{slug}` CSS custom property name.
	 *
	 * The "preferred slug" lookups above only ever match a slug against our
	 * own hardcoded $preferred/$preferred_secondary lists (via strict ===),
	 * so those are already constrained to known-safe values. This guard is
	 * for the "first viable palette entry" fallback loops, which accept
	 * whatever slug the active theme happens to define — normally a plain
	 * kebab-case token, but nothing stops a theme.json from defining
	 * something else, and this string ends up directly inside a CSS block
	 * (see enqueue_theme_vars()).
	 *
	 * @param mixed $slug Palette entry slug from wp_get_global_settings().
	 *
	 * @return bool
	 */
	private static function is_safe_theme_slug( $slug ): bool {
		return is_string( $slug ) && 1 === preg_match( '/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $slug );
	}

	/**
	 * Return true when a hex colour is suitable as a primary button colour.
	 *
	 * A viable button colour must:
	 *   1. Be a parseable 3- or 6-digit hex value (not a CSS function like color-mix()).
	 *   2. Not be near-neutral (not near-white, near-black, or near-gray).
	 *   3. Have a relative luminance ≤ 0.40 so white (#fff) text achieves at
	 *      least ~2.6:1 contrast — enough for large bold button text and
	 *      sufficient to rule out pastels and yellows that look wrong on CTAs.
	 *
	 * @param string $hex Colour value from the palette (may be hex or CSS function).
	 *
	 * @return bool
	 */
	private static function is_viable_button_color( string $hex ): bool {
		$hex = ltrim( $hex, '#' );
		if ( strlen( $hex ) === 3 ) {
			$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
		}
		if ( strlen( $hex ) !== 6 || ! ctype_xdigit( $hex ) ) {
			return false; // CSS function (e.g. color-mix) or other non-hex value.
		}

		$r = hexdec( substr( $hex, 0, 2 ) );
		$g = hexdec( substr( $hex, 2, 2 ) );
		$b = hexdec( substr( $hex, 4, 2 ) );

		// Reject near-white.
		if ( $r > 230 && $g > 230 && $b > 230 ) {
			return false; }
		// Reject near-black.
		if ( $r < 25 && $g < 25 && $b < 25 ) {
			return false; }
		// Reject near-gray (low saturation).
		if ( ( max( $r, $g, $b ) - min( $r, $g, $b ) ) < 30 ) {
			return false; }

		// Compute relative luminance per WCAG 2 specification.
		$to_linear = static function ( int $channel ): float {
			$s = $channel / 255.0;
			return $s <= 0.03928 ? $s / 12.92 : ( ( $s + 0.055 ) / 1.055 ) ** 2.4;
		};
		$luminance = 0.2126 * $to_linear( $r ) + 0.7152 * $to_linear( $g ) + 0.0722 * $to_linear( $b );

		// Reject colours too light for comfortable white text (L > 0.40).
		return $luminance <= 0.40;
	}

	/**
	 * Add wide/full alignment theme support for classic (non-FSE) themes.
	 *
	 * Classic themes are not required to declare align-wide support, but the
	 * plugin's block-template-fallback renders .html templates that contain
	 * alignwide blocks. Without this, WP core does not output the alignment
	 * CSS classes/wrappers needed for those blocks to be positioned correctly.
	 *
	 * Block themes already handle alignment natively — this hook is a no-op
	 * for them because wp_is_block_theme() is checked before adding support.
	 *
	 * @return void
	 */
	public static function add_classic_theme_support(): void {
		if ( wp_is_block_theme() ) {
			return;
		}

		add_theme_support( 'align-wide' );
	}

	/**
	 * Register custom block category for AmbassadorHub blocks.
	 *
	 * @param array  $categories Array of block categories.
	 * @param object $context    Block editor context.
	 *
	 * @return array Modified categories.
	 */
	public static function register_block_category( $categories, $context ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- required by block_categories_all filter signature
		return array_merge(
			array(
				array(
					'slug'  => 'ambassadorhub',
					'title' => __( 'AmbassadorHub', 'ambassadorhub' ),
					'icon'  => null,
				),
			),
			$categories
		);
	}
}
