<?php
/**
 * The7 FSE Font Manager.
 *
 * @package The7
 */

namespace The7\Mods\Compatibility\Gutenberg\Block_Theme;

use WP_Error;
use WP_Font_Face;
use WP_Font_Face_Resolver;
use WP_Font_Utils;
use WP_REST_Request;

defined( 'ABSPATH' ) || exit;

/**
 * Serve + upload API for the FSE font download pipeline.
 */
class The7_FSE_Font_Manager {

	/**
	 * @var The7_FSE_Font_Manager
	 */
	public static $instance;

	/**
	 * The7_FSE_Font_Manager constructor.
	 *
	 * @return The7_FSE_Font_Manager
	 */
	public static function instance() {
		if ( ! self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Initialize all necessary hooks.
	 *
	 * @return void
	 */
	public function init() {
		// Rewrite the font faces printing functions to replace the font URLs.
		add_action(
			'admin_print_styles',
			function () {
				remove_action( 'admin_print_styles', 'wp_print_font_faces_from_style_variations', 50 );
				remove_action( 'admin_print_styles', 'wp_print_font_faces', 50 );
			}
		);

		add_filter(
			'block_editor_settings_all',
			function ( $settings ) {
				if ( isset( $settings['__unstableResolvedAssets']['styles'] ) ) {
					$settings['__unstableResolvedAssets']['styles'] = $this->replace_font_src( $settings['__unstableResolvedAssets']['styles'] );
				}

				return $settings;
			}
		);

		add_action( 'admin_print_styles', [ $this, 'print_font_faces_from_style_variations' ], 50 );
		add_action( 'admin_print_styles', [ $this, 'print_font_faces' ], 50 );

		remove_action( 'wp_head', 'wp_print_font_faces', 50 );
		add_action( 'wp_head', [ $this, 'print_font_faces' ], 50 );
		// End of rewrite the font faces printing functions.
	}

	/**
	 * @return void
	 */
	public function enqueue_downloader_script() {
		the7_register_script( 'the7-fse-fonts-downloader', PRESSCORE_ADMIN_URI . '/assets/js/fse-fonts-downloader.js', [ 'jquery', 'wp-api-request' ], false, true );
		wp_enqueue_script( 'the7-fse-fonts-downloader' );
	}

	/**
	 * @return void
	 */
	public function admin_enqueue_scripts() {
		$this->enqueue_downloader_script();
	}

	/**
	 * Create REST API endpoint to download fonts.
	 *
	 * @return void
	 */
	public function register_download_rest_routes() {
		register_rest_route(
			'the7/v1',
			'/fse-font',
			[
				'methods'             => 'POST',
				'callback'            => [ $this, 'rest_upload_font' ],
				'permission_callback' => [ $this, 'check_permission' ],
			]
		);
	}

	/**
	 * Create REST API endpoint to download fonts.
	 *
	 * @return void
	 */
	public function register_rest_route() {
		$this->register_download_rest_routes();
	}

	/**
	 * Print font faces from style variations.
	 *
	 * @return void
	 */
	public function print_font_faces_from_style_variations() {
		$fonts = WP_Font_Face_Resolver::get_fonts_from_style_variations();

		if ( empty( $fonts ) ) {
			return;
		}
		$this->print_font_faces( $fonts );
	}

	/**
	 * Reads existing font files from the font folder.
	 *
	 * @return array List of existing font files.
	 */
	public function fetch_existing_fonts() {
		$font_dir   = untrailingslashit( wp_get_font_dir()['basedir'] );
		$font_files = array_merge(
			glob( $font_dir . '/*.woff' ) ?: [],
			glob( $font_dir . '/*.woff2' ) ?: [],
			glob( $font_dir . '/*.ttf' ) ?: [],
			glob( $font_dir . '/*.otf' ) ?: []
		);

		return array_map( 'basename', $font_files );
	}

	/**
	 * Print font faces.
	 *
	 * @param array $fonts List of fonts to print.
	 *
	 * @return void
	 */
	public function print_font_faces( $fonts = [] ) {
		if ( empty( $fonts ) ) {
			$fonts = WP_Font_Face_Resolver::get_fonts_from_theme_json();
		}

		if ( empty( $fonts ) ) {
			return;
		}

		$wp_font_face = new WP_Font_Face();

		$this->replace_font_src_recursive( $fonts );

		$wp_font_face->generate_and_print( $fonts );
	}

	/**
	 * Replace the font URLs with the local font URLs.
	 *
	 * @param array $fonts_array   Array to replace the URLs.
	 *
	 * @return void
	 */
	public function replace_font_src_recursive( &$fonts_array ) {
		foreach ( $fonts_array as &$value ) {
			if ( is_array( $value ) ) {
				if ( isset( $value['src'] ) && is_array( $value['src'] ) ) {
					foreach ( $value['src'] as &$src_val ) {
						if ( is_string( $src_val ) ) {
							$src_val = $this->replace_font_src( $src_val );
						}
					}
					unset( $src_val );
				}
				$this->replace_font_src_recursive( $value );
			}
		}
	}

	/**
	 * Replace the font URL with the local font URL.
	 *
	 * @param string $font Font URL.
	 *
	 * @return string
	 */
	protected function replace_font_src( $font ) {
		return str_replace(
			get_template_directory_uri() . '/load__local__font/',
			trailingslashit( wp_get_font_dir()['baseurl'] ),
			$font
		);
	}

	/**
	 * Check if the user has permission to download fonts.
	 *
	 * @return bool
	 */
	public function check_permission() {
		return current_user_can( 'switch_themes' );
	}

	/**
	 * Upload font.
	 *
	 * @param WP_REST_Request $request Request object.
	 *
	 * @return WP_Error|array
	 */
	public function rest_upload_font( WP_REST_Request $request ) {
		$file_params = $request->get_file_params();

		if ( empty( $file_params['font'] ) ) {
			return new WP_Error( 'no_font', 'No font to download', [ 'status' => 400 ] );
		}

		$font     = $file_params['font'];
		$filename = $font['name'];
		$font_dir = untrailingslashit( wp_font_dir()['basedir'] );

		if ( file_exists( $font_dir . '/' . $filename ) ) {
			return [
				'info' => 'Font already exists',
			];
		}

		return $this->handle_font_file_upload( $font );
	}

	/**
	 * Handles the upload of a font file using wp_handle_upload().
	 *
	 * @param array $file Single file item from $_FILES.
	 *
	 * @return array|WP_Error Array containing uploaded file attributes on success, or WP_Error object on failure.
	 */
	protected function handle_font_file_upload( $file ) {
		if ( ! function_exists( 'WP_Filesystem' ) ) {
			require_once ABSPATH . 'wp-admin/includes/file.php';
		}

		$mimes_hook_name = 'upload_' . 'mimes'; // phpcs:ignore Generic.Strings.UnnecessaryStringConcat
		add_filter( $mimes_hook_name, [ 'WP_Font_Utils', 'get_allowed_font_mime_types' ] );
		// Filter the upload directory to return the fonts directory.
		add_filter( 'upload_dir', '_wp_filter_font_directory' );

		$overrides = [
			'upload_error_handler' => [ $this, 'handle_font_file_upload_error' ],
			// Not testing a form submission.
			'test_form'            => false,
			// Only allow uploading font files for this request.
			'mimes'                => WP_Font_Utils::get_allowed_font_mime_types(),
		];

		$uploaded_file = wp_handle_upload( $file, $overrides );

		remove_filter( 'upload_dir', '_wp_filter_font_directory' );
		remove_filter( $mimes_hook_name, [ 'WP_Font_Utils', 'get_allowed_font_mime_types' ] );

		return $uploaded_file;
	}

	/**
	 * Handles file upload error.
	 *
	 * @param array  $file    File upload data.
	 * @param string $message Error message from wp_handle_upload().
	 *
	 * @return WP_Error WP_Error object.
	 */
	public function handle_font_file_upload_error( $file, $message ) {
		$status = 500;
		$code   = 'rest_font_upload_unknown_error';

		if ( __( 'Sorry, you are not allowed to upload this file type.' ) === $message ) {
			$status = 400;
			$code   = 'rest_font_upload_invalid_file_type';
		}

		return new WP_Error( $code, $message, [ 'status' => $status ] );
	}
}
