<?php
/**
 * NotFound theme color resolver.
 *
 * @package SEOvaultSEO\NotFound
 */

namespace SEOvaultSEO\NotFound;

use SEOvaultSEO\Core\Options;

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

/**
 * Resolves accent / theme colors for the recovery page.
 */
class ThemeColors {

	/**
	 * Initialize theme color hooks if needed.
	 *
	 * Stub — no hooks in Phase 2.
	 */
	public static function init() {
	}

	/**
	 * Resolve safe recovery-page color hints from the active theme.
	 *
	 * Theme palette settings take priority, followed by commonly supported
	 * Customizer values and CSS-variable integrations. The stored accent is
	 * always the final fallback.
	 *
	 * @return array{accent:string,background:string,text:string}
	 */
	public static function resolve() {
		$stored_accent = Options::get_notfound_recovery_accent();

		if ( ! Options::is_notfound_recovery_use_theme_colors() ) {
			return array(
				'accent'     => $stored_accent,
				'background' => '',
				'text'       => '',
			);
		}

		$colors = self::empty_colors();
		$colors = self::fill_missing_colors( $colors, self::get_theme_json_colors() );
		$colors = self::fill_missing_colors( $colors, self::get_theme_mod_colors() );
		$colors = self::fill_missing_colors( $colors, self::get_css_variable_colors() );
		if ( '' === $colors['accent'] ) {
			$colors['accent'] = $stored_accent;
		}

		return $colors;
	}

	/**
	 * Get active-theme colors from its resolved theme.json settings.
	 *
	 * @return array{accent:string,background:string,text:string}
	 */
	private static function get_theme_json_colors() {
		$settings = function_exists( 'wp_get_global_settings' )
			? wp_get_global_settings()
			: array();
		$settings = apply_filters( 'seovaultseo_notfound_theme_color_settings', $settings );

		if ( ! is_array( $settings ) ) {
			return self::empty_colors();
		}

		$palette = $settings['color']['palette'] ?? array();
		$palette = is_array( $palette ) ? $palette : array();

		return array(
			'accent'     => self::find_palette_color( $palette, array( 'accent', 'primary', 'link', 'secondary' ), true ),
			'background' => self::find_palette_color( $palette, array( 'background', 'base' ), false ),
			'text'       => self::find_palette_color( $palette, array( 'text', 'foreground', 'contrast' ), false ),
		);
	}

	/**
	 * Get commonly supported Customizer color values.
	 *
	 * The custom logo ID is passed to a filter so a theme integration can provide
	 * a known logo-color context without analyzing an image at request time.
	 *
	 * @return array{accent:string,background:string,text:string}
	 */
	private static function get_theme_mod_colors() {
		$colors = apply_filters(
			'seovaultseo_notfound_theme_mod_colors',
			array(
				'accent'     => self::first_valid_color(
					array(
						get_theme_mod( 'accent_color', '' ),
						get_theme_mod( 'primary_color', '' ),
						get_theme_mod( 'link_color', '' ),
					)
				),
				'background' => self::sanitize_color( get_theme_mod( 'background_color', '' ) ),
				'text'       => self::sanitize_color( get_theme_mod( 'header_textcolor', '' ) ),
			),
			(int) get_theme_mod( 'custom_logo', 0 )
		);

		if ( ! is_array( $colors ) ) {
			return self::empty_colors();
		}

		return array(
			'accent'     => self::sanitize_color( $colors['accent'] ?? '' ),
			'background' => self::sanitize_color( $colors['background'] ?? '' ),
			'text'       => self::sanitize_color( $colors['text'] ?? '' ),
		);
	}

	/**
	 * Get optional normalized CSS variable fallbacks from theme integrations.
	 *
	 * CSS custom properties cannot be safely resolved from PHP. This filter lets
	 * a theme integration provide their resolved values without returning raw CSS
	 * variables to the recovery template.
	 *
	 * @return array{accent:string,background:string,text:string}
	 */
	private static function get_css_variable_colors() {
		$colors = apply_filters( 'seovaultseo_notfound_theme_css_variable_colors', self::empty_colors() );

		if ( ! is_array( $colors ) ) {
			return self::empty_colors();
		}

		return array(
			'accent'     => self::sanitize_color( $colors['accent'] ?? '' ),
			'background' => self::sanitize_color( $colors['background'] ?? '' ),
			'text'       => self::sanitize_color( $colors['text'] ?? '' ),
		);
	}

	/**
	 * Fill only unavailable color hints.
	 *
	 * @param array{accent:string,background:string,text:string} $colors     Existing colors.
	 * @param array{accent:string,background:string,text:string} $candidates Candidate colors.
	 * @return array{accent:string,background:string,text:string}
	 */
	private static function fill_missing_colors( array $colors, array $candidates ) {
		foreach ( array( 'accent', 'background', 'text' ) as $key ) {
			if ( '' === $colors[ $key ] && '' !== $candidates[ $key ] ) {
				$colors[ $key ] = $candidates[ $key ];
			}
		}

		return $colors;
	}

	/**
	 * Find a named color in a theme.json palette.
	 *
	 * @param array              $palette         Theme palette data.
	 * @param array<int, string> $preferred_slugs Preferred palette slugs.
	 * @param bool               $allow_first     Whether any valid palette color may be used.
	 * @return string
	 */
	private static function find_palette_color( array $palette, array $preferred_slugs, $allow_first ) {
		$entries = self::get_palette_entries( $palette );

		foreach ( $preferred_slugs as $slug ) {
			foreach ( $entries as $entry ) {
				if ( $slug === $entry['slug'] && '' !== $entry['color'] ) {
					return $entry['color'];
				}
			}
		}

		if ( $allow_first && ! empty( $entries ) ) {
			return $entries[0]['color'];
		}

		return '';
	}

	/**
	 * Flatten palette origins to sanitized color entries.
	 *
	 * @param array $palette Theme palette data.
	 * @return array<int, array{slug:string,color:string}>
	 */
	private static function get_palette_entries( array $palette ) {
		$entries = array();

		foreach ( $palette as $entry ) {
			if ( ! is_array( $entry ) ) {
				continue;
			}

			if ( isset( $entry['color'] ) ) {
				$color = self::sanitize_color( $entry['color'] );
				if ( '' !== $color ) {
					$entries[] = array(
						'slug'  => isset( $entry['slug'] ) && is_string( $entry['slug'] )
							? strtolower( $entry['slug'] )
							: '',
						'color' => $color,
					);
				}
				continue;
			}

			$entries = array_merge( $entries, self::get_palette_entries( $entry ) );
		}

		return $entries;
	}

	/**
	 * Return the first accepted color candidate.
	 *
	 * @param array<int, mixed> $candidates Color candidates.
	 * @return string
	 */
	private static function first_valid_color( array $candidates ) {
		foreach ( $candidates as $candidate ) {
			$color = self::sanitize_color( $candidate );
			if ( '' !== $color ) {
				return $color;
			}
		}

		return '';
	}

	/**
	 * Restrict color output to hex or rgb() CSS color values.
	 *
	 * @param mixed $color Raw color value.
	 * @return string
	 */
	private static function sanitize_color( $color ) {
		if ( ! is_string( $color ) ) {
			return '';
		}

		$color = trim( $color );
		if ( preg_match( '/^#(?:[a-fA-F0-9]{3}|[a-fA-F0-9]{6})$/', $color ) ) {
			return strtolower( $color );
		}

		if (
			preg_match(
				'/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i',
				$color,
				$matches
			) &&
			(int) $matches[1] <= 255 &&
			(int) $matches[2] <= 255 &&
			(int) $matches[3] <= 255
		) {
			return sprintf( 'rgb(%d, %d, %d)', $matches[1], $matches[2], $matches[3] );
		}

		return '';
	}

	/**
	 * Return an empty color-hint object.
	 *
	 * @return array{accent:string,background:string,text:string}
	 */
	private static function empty_colors() {
		return array(
			'accent'     => '',
			'background' => '',
			'text'       => '',
		);
	}
}
