<?php
/**
 * Handles Google Fonts enqueueing.
 *
 * Author:          Andrei Baicus <andrei@themeisle.com>
 * Created on:      22/08/2018
 *
 * @package Neve\Views
 */

namespace Neve\Views;

use Neve\Core\Settings\Config;
use Neve\Core\Settings\Mods;

/**
 * Class Typography
 *
 * @package Neve\Views
 */
class Font_Manager extends Base_View {
	/**
	 * The font families buffer.
	 *
	 * @var array
	 */
	public static $font_families = array();

	/**
	 * Body font family variants.
	 *
	 * @var array
	 */
	public static $body_variants = array();

	/**
	 * Add a google font.
	 *
	 * @param string $font_family font family.
	 * @param string $font_weight font weight.
	 */
	final public static function add_google_font( $font_family, $font_weight = '400' ) {
		if ( empty( $font_family ) ) {
			$body_font = self::get_body_font_family();
			if ( empty( $body_font ) ) {
				return;
			}
			$font_family = $body_font;
		}

		$allowed_variants = [
			'100',
			'200',
			'300',
			'400',
			'500',
			'600',
			'700',
			'800',
			'900',
			'100italic',
			'200italic',
			'300italic',
			'400italic',
			'500italic',
			'600italic',
			'700italic',
			'800italic',
			'900italic',
		];

		if ( ! in_array( $font_weight, $allowed_variants, true ) ) {
			$font_weight = '400';
		}

		if ( array_key_exists( $font_family, self::$font_families ) ) {
			self::$font_families[ $font_family ][] = $font_weight;
			self::$font_families[ $font_family ]   = array_unique( self::$font_families[ $font_family ] );

			return;
		}
		self::$font_families[ $font_family ] = [ $font_weight ];
	}

	/**
	 * Function that is run after instantiation.
	 *
	 * @return void
	 */
	public function init() {
		$this->add_body_variants();
		add_action( 'wp_enqueue_scripts', array( $this, 'register_google_fonts' ), 200 );
		add_action( 'enqueue_block_editor_assets', array( $this, 'register_google_fonts' ), 200 );
		add_action( 'enqueue_block_editor_assets', array( $this, 'do_editor_styles_google_fonts' ), 210 );
		add_action( 'wp_print_styles', array( $this, 'load_external_fonts_locally' ), PHP_INT_MAX );
		add_filter( 'style_loader_tag', array( $this, 'add_rel_preload' ), 10, 2 );
	}

	/**
	 * Add body font family variants.
	 */
	private function add_body_variants() {
		$body_font   = self::get_body_font_family();
		$gfonts_data = neve_get_google_fonts( true );

		// Variations are only available for Google fonts.
		if ( ! in_array( $body_font, array_keys( $gfonts_data ) ) ) {
			return;
		}

		// If there are no variations statically defined then don't do anything.
		if ( empty( $gfonts_data[ $body_font ] ) ) {
			return;
		}

		$variations = $this->get_body_variations();

		sort( $variations );

		foreach ( $variations as $variation ) {
			self::add_google_font( $body_font, $variation );
		}
	}

	/**
	 * Get body font family.
	 *
	 * @return string
	 */
	public static function get_body_font_family() {
		$body_mod     = Config::MODS_FONT_GENERAL;
		$body_default = Mods::get_alternative_mod_default( Config::MODS_FONT_GENERAL );

		return Mods::get( $body_mod, $body_default );
	}

	/**
	 * Get body variations.
	 *
	 * @return string[]
	 */
	private function get_body_variations() {
		return get_theme_mod( Config::MODS_FONT_GENERAL_VARIANTS, [] );
	}

	/**
	 * Register the fonts that are selected in customizer.
	 */
	public function register_google_fonts() {
		foreach ( self::$font_families as $font_family => $font_weights ) {
			$this->enqueue_google_font( $font_family, $font_weights );
		}
	}

	/**
	 * Will register fonts for the editor preview (mobile|tablet)
	 */
	public function do_editor_styles_google_fonts() {
		if ( empty( self::$font_families ) ) {
			return;
		}
		add_theme_support( 'editor-styles' );
		$style_array = array(
			'editor-styles.css',
		);
		foreach ( self::$font_families as $font_family => $font_weights ) {
			$url = $this->enqueue_google_font( $font_family, $font_weights, true );
			if ( ! empty( $url ) ) {
				array_push( $style_array, 'https:' . $url );
			}
		}
		add_editor_style(
			$style_array
		);
	}

	/**
	 * Enqueues a Google Font
	 *
	 * @param string  $font font string.
	 * @param array   $weights font weights.
	 * @param boolean $skip_enqueue flag to skip enqueue and return url.
	 *
	 * @since 1.1.38
	 */
	private function enqueue_google_font( $font, $weights = [], $skip_enqueue = false ) {
		// Get list of all Google Fonts.
		$google_fonts = neve_get_google_fonts( true );

		$font = str_replace( '"', '', $font );

		// Make sure font is in our list of fonts.
		if ( ! in_array( $font, array_keys( $google_fonts ), true ) ) {
			return;
		}

		// Make sure 400 font weight is always included.
		$weights = array_unique( array_merge( $weights, [ '400' ] ) );

		// In customizer, all font weights should be active for the preview.
		if ( is_customize_preview() ) {
			$weights = isset( $google_fonts[ $font ] ) ? $google_fonts[ $font ] : [
				'100',
				'200',
				'300',
				'400',
				'500',
				'600',
				'700',
				'800',
				'900',
			];
		}

		// Sanitize font name.
		$url_string = trim( $font );

		$base_url = '//fonts.googleapis.com/css';

		// Add weights to URL.
		if ( ! empty( $weights ) ) {
			$url_string .= ':' . implode( ',', $weights );
		}

		$query_args = array(
			'family'  => urlencode( $url_string ),
			'display' => 'swap',
		);

		$subsets = [
			'ru_RU' => 'cyrillic',
			'bg_BG' => 'cyrillic',
			'he_IL' => 'hebrew',
			'el'    => 'greek',
			'vi'    => 'vietnamese',
			'uk'    => 'cyrillic',
			'cs_CZ' => 'latin-ext',
			'ro_RO' => 'latin-ext',
			'pl_PL' => 'latin-ext',
		];
		$locale  = get_locale();

		if ( isset( $subsets[ $locale ] ) ) {
			$query_args['subset'] = urlencode( $subsets[ $locale ] );
		}
		$url = add_query_arg( $query_args, $base_url );

		if ( $skip_enqueue ) {
			return $url;
		}

		wp_enqueue_style( 'neve-google-font-' . str_replace( ' ', '-', strtolower( $font ) ), $url, array(), NEVE_VERSION );
	}

	/**
	 * Add onload, rel and as attributes for Google Font stylesheets.
	 * Implements lazy loading with preload for better performance.
	 *
	 * @param string $html   Current html code.
	 * @param string $handle Current script handle.
	 *
	 * @return string
	 */
	public function add_rel_preload( $html, $handle ) {
		if ( is_admin() ) {
			return $html;
		}

		$preload_enabled = get_theme_mod( Config::MODS_PRELOAD_FONTS, false );

		/**
		 * Filters whether fonts should be preloaded.
		 *
		 * @param bool $preload_enabled Whether fonts should be preloaded. Default value is false.
		 *
		 * @since 3.9.0
		 */
		$should_preload = apply_filters( 'neve_preload_fonts', $preload_enabled );

		if ( ! (bool) $should_preload ) {
			return $html;
		}

		// Only preload Google Font stylesheets
		if ( strpos( $handle, 'neve-google-font-' ) === 0 ) {
			// Lazy load with JS, but also add noscript in case no JS
			$no_script = '<noscript>' . $html . '</noscript>';
			// Add onload, rel="preload", as="style", and put together with noscript
			$html = str_replace( 'rel=\'stylesheet\'', 'rel="preload" as="style" onload="this.rel=\'stylesheet\';"', $html ) . $no_script;
		}

		return $html;
	}

	/**
	 * Load Google Fonts locally.
	 *
	 * @return void
	 */
	public function load_external_fonts_locally() {

		$toggle = get_option( Config::OPTION_LOCAL_GOOGLE_FONTS_HOSTING, false );

		/**
		 * Filters whether the remote fonts should be hosted locally.
		 *
		 * This filter applies for both Google Fonts and Typekit Fonts if the Typekit module is used.
		 *
		 * @param bool $should_enqueue_locally Whether the Google Fonts should be hosted locally. Default value is false.
		 *
		 * @since 2.11
		 */
		$should_enqueue_locally = apply_filters( 'neve_load_remote_fonts_locally', $toggle );

		if ( ! (bool) $should_enqueue_locally ) {
			return;
		}

		$is_admin_context = is_admin() || is_customize_preview();
		$wptt_vendor_file = trailingslashit( get_template_directory() ) . 'vendor/wptt/webfont-loader/wptt-webfont-loader.php';

		if ( $is_admin_context || ! is_readable( $wptt_vendor_file ) ) {
			return;
		}

		/**
		 * Bail when in Elementor edit mode.
		 *
		 * We do this so that initial load of Elementor editor will not be slowed down by Neve Pro downloading Roboto fonts.
		 */
		if ( class_exists( '\Elementor\Plugin' ) ) {
			global $post;
			if ( \Elementor\Plugin::$instance->editor->is_edit_mode() ||
				\Elementor\Plugin::$instance->preview->is_preview_mode()
				) {
				return;
			}
		}

		/**
		 * Allow filtering for additional external font providers.
		 *
		 * The domains provided to this filter should be from services that link directly to a CSS file or else WPTT will not be able to download the fonts.
		 */
		$external_font_domains = apply_filters(
			'neve_font_providers',
			array(
				'fonts.googleapis.com/css',
				'use.typekit.net',
			)
		);

		global $wp_styles;
		$enqueued_styles = $wp_styles->queue ?? '';

		if ( ! is_array( $enqueued_styles ) ) {
			return;
		}

		$external_fonts = array();
		$handles        = array();

		foreach ( $enqueued_styles as $style ) {

			$source = $wp_styles->registered[ $style ]->src;

			foreach ( $external_font_domains as $domain ) {

				if ( strpos( $source, $domain ) !== false ) {
					$parts = wp_parse_url( $source );
					/**
					 * Some plugins might set font url without protocol example //fonts.googleapis.com...
					 * Lets build the url ourselves to be sure the its whats expected.
					*/
					$normalized_source = 'https://' . $parts['host'] . $parts['path'] . ( isset( $parts['query'] ) ? '?' . $parts['query'] : '' );
					$external_fonts[]  = $normalized_source;
					$handles[]         = $style;
				}
			}
		}

		$external_fonts = array_unique( $external_fonts );

		require_once $wptt_vendor_file;

		/**
		 * The vendor file was readable at the top of this method, but confirm it
		 * actually declared the loader before dequeuing anything.
		 */
		if ( ! function_exists( 'wptt_get_webfont_styles' ) ) {
			return;
		}

		// Dequeue these handles since we are going to load the fonts locally.
		foreach ( array_unique( $handles ) as $handle ) {
			wp_dequeue_style( $handle );
		}

		foreach ( $external_fonts as $font_link ) {
			wp_add_inline_style( 'neve-style', wptt_get_webfont_styles( $font_link ) );
		}

	}

}
