<?php
/**
 * File to load admin css/js.
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

/**
 * Class to enqueue admin assets.
 */
class SimpleStats_Admin_Assets {

	/**
	 * Hold the instance.
	 *
	 * @var self
	 */
	private static $instance;

	/**
	 * manifest.json file data.
	 *
	 * @var array
	 */
	private $manifest = array();

	/**
	 * Whether the manifest read has already been attempted this request.
	 *
	 * @var bool
	 */
	private $manifest_loaded = false;

	/**
	 * Add handles which we will update module.
	 *
	 * @var array
	 */
	private $own_handles = array(
		'simplestats-vite-client',
		'simplestats-dashboard',
		'simplestats-authentication',
	);

	/**
	 * Hold the enqueued css handles.
	 *
	 * @var array
	 */
	public $enqueued_css = array();

	/**
	 * Get instance of class.
	 *
	 * @return self
	 */
	public static function get_instance() {
		if ( ! self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/**
	 * Constructor function of the class.
	 */
	private function __construct() {
		add_action( 'admin_enqueue_scripts', array( $this, 'admin_dashboard_scripts' ) );
		add_filter( 'script_loader_tag', array( $this, 'script_loader_tag' ), 99999, 3 );
		add_filter( 'script_loader_src', array( $this, 'script_loader_src' ), 10, 2 );
	}

	/**
	 * Admin scripts: branch on which simplestats admin page we're on.
	 *
	 * @param string $page Page hook slug.
	 */
	public function admin_dashboard_scripts( $page ) {
		if ( 'toplevel_page_simplestats' === $page ) {
			$this->enqueue_dashboard_spa();
			return;
		}

		// Auth page hook is "admin_page_simplestats_authentication" (parent=null submenu).
		if ( false !== strpos( (string) $page, 'simplestats_authentication' ) ) {
			$this->enqueue_authentication_spa();
			return;
		}
	}

	/**
	 * Enqueue the dashboard SPA.
	 */
	private function enqueue_dashboard_spa() {
		$this->enqueue_script_specific_css( 'vue/dashboard.js' );

		// Vite client to show error overlay.
		if ( defined( 'SIMPLESTATS_LOCAL_JS_URL' ) ) {
			wp_enqueue_script( 'simplestats-vite-client', SIMPLESTATS_LOCAL_JS_URL . '@vite/client', array(), SIMPLESTATS_PLUGIN_VERSION, true );
		}

		wp_enqueue_script( 'wp-i18n' );
		wp_enqueue_script( 'wp-util' );

		wp_enqueue_script(
			'simplestats-dashboard',
			$this->get_js_src( 'vue/dashboard.js' ),
			array( 'wp-util' ),
			SIMPLESTATS_PLUGIN_VERSION,
			true
		);

		$this->common_localize_script( 'simplestats-dashboard' );

		wp_set_script_translations( 'simplestats-dashboard', 'simplestats-friendly-website-analytics', SIMPLESTATS_PLUGIN_PATH . 'languages' );
	}

	/**
	 * Enqueue the authentication SPA.
	 */
	private function enqueue_authentication_spa() {
		$this->enqueue_script_specific_css( 'vue/authentication.js' );

		if ( defined( 'SIMPLESTATS_LOCAL_JS_URL' ) ) {
			wp_enqueue_script( 'simplestats-vite-client', SIMPLESTATS_LOCAL_JS_URL . '@vite/client', array(), SIMPLESTATS_PLUGIN_VERSION, true );
		}

		wp_enqueue_script( 'wp-i18n' );
		wp_enqueue_script( 'wp-util' );

		wp_enqueue_script(
			'simplestats-authentication',
			$this->get_js_src( 'vue/authentication.js' ),
			array( 'wp-util' ),
			SIMPLESTATS_PLUGIN_VERSION,
			true
		);

		$this->common_localize_script( 'simplestats-authentication' );

		wp_set_script_translations( 'simplestats-authentication', 'simplestats-friendly-website-analytics', SIMPLESTATS_PLUGIN_PATH . 'languages' );
	}

	/**
	 * Read and decode assets/vue/manifest.json, at most once per request.
	 *
	 * Called lazily from the two accessors below rather than from the
	 * constructor. The constructor runs on *every* request, front end included,
	 * but the manifest is only ever needed while enqueueing one of the two
	 * SimpleStats admin screens.
	 *
	 * The file is read directly instead of through WP_Filesystem(). This is a
	 * build artefact we ship inside the plugin, not user-supplied content in an
	 * arbitrary location, so the credentialed abstraction buys nothing here and
	 * costs a great deal:
	 *
	 *   - WP_Filesystem() pulls in wp-admin/includes/file.php, and
	 *     get_filesystem_method() creates and deletes a probe file inside
	 *     wp-content on every call unless FS_METHOD is defined.
	 *   - It returns false with $wp_filesystem left unset when no method can be
	 *     resolved, so calling ->get_contents() on it was a fatal error.
	 *   - On hosts pinned to an ftpext/ssh2 method there are no credentials in
	 *     this context, so the read failed, the manifest stayed empty and the
	 *     admin app silently never loaded.
	 *
	 * @return void
	 */
	private function load_manifest() {
		if ( $this->manifest_loaded ) {
			return;
		}

		$this->manifest_loaded = true;

		// No need to process manifest data in dev mode.
		if ( defined( 'SIMPLESTATS_LOCAL_JS_URL' ) ) {
			return;
		}

		$path = SIMPLESTATS_PLUGIN_PATH . 'assets/vue/manifest.json';

		if ( ! is_readable( $path ) ) {
			return;
		}

		// Read directly rather than via wp_json_file_decode(): that helper is WP
		// 5.9+ and this plugin supports 5.6, and gating it behind function_exists()
		// still trips the "not compatible with Requires at least" check.
		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local, plugin-owned build manifest, not a remote or user-supplied path.
		$contents = file_get_contents( $path );
		$decoded  = ( false === $contents ) ? null : json_decode( $contents, true );

		$this->manifest = is_array( $decoded ) ? $decoded : array();
	}

	/**
	 * Get full URL of the manifest src.
	 *
	 * @param string $name
	 *
	 * @return string|void
	 */
	public function get_js_src( $name ) {
		if ( defined( 'SIMPLESTATS_LOCAL_JS_URL' ) ) {
			return SIMPLESTATS_LOCAL_JS_URL . $name;
		}

		$this->load_manifest();

		if ( isset( $this->manifest[ $name ]['file'] ) ) {
			return SIMPLESTATS_PLUGIN_URL . 'assets/vue/' . $this->manifest[ $name ]['file'];
		}
	}

	/**
	 * Update script tag.
	 * The vue code needs type=module.
	 */
	public function script_loader_tag( $tag, $handle, $src ) {

		if ( ! in_array( $handle, $this->own_handles, true ) ) {
			return $tag;
		}

		if ( ! is_string( $tag ) || '' === $tag ) {
			return $tag;
		}

		// Change the script tag by adding type="module" and return it.
		$html = str_replace( '></script>', ' type="module"></script>', $tag );

		return $html;
	}

	/**
	 * Update source of a script.
	 */
	public function script_loader_src( $src, $handle ) {
		// Remove version from vite client.
		if ( 'simplestats-vite-client' === $handle ) {
			return SIMPLESTATS_LOCAL_JS_URL . '@vite/client';
		}

		return $src;
	}


	/**
	 * Load CSS from manifest.json
	 */
	public function enqueue_script_specific_css( $js_file_path ) {
		if ( defined( 'SIMPLESTATS_LOCAL_JS_URL' ) ) {
			return;
		}

		$this->load_manifest();

		if ( ! isset( $this->manifest[ $js_file_path ] ) ) {
			return;
		}

		// Add JS own CSS file.
		if ( isset( $this->manifest[ $js_file_path ]['css'] ) ) {
			$this->add_js_own_css_files( $this->manifest[ $js_file_path ]['css'] );
		}
	}

	/**
	 * Add JS its own CSS build file.
	 */
	private function add_js_own_css_files( $css_files ) {
		foreach ( $css_files as $css_filename ) {
			$handle               = 'simplestats-style-' . basename( $css_filename );
			$this->enqueued_css[] = $handle;

			wp_enqueue_style(
				$handle,
				SIMPLESTATS_PLUGIN_URL . 'assets/vue/' . $css_filename,
				array(),
				SIMPLESTATS_PLUGIN_VERSION
			);
		}
	}

	/**
	 * Add common localize script.
	 *
	 * @param string $handle       The script handle to attach data to.
	 * @param array  $extra_params Extra fields to merge into the localized object.
	 */
	public function common_localize_script( $handle, $extra_params = array() ) {
		$wpnonce    = wp_create_nonce( 'simplestats-action' );
		$connection = simplestats_get_connection();

		$items = array(
			'assetsUrl'         => SIMPLESTATS_PLUGIN_URL . 'assets',
			'authenticationUrl' => admin_url( 'admin.php?page=simplestats_authentication#/sign-up' ),
			'dashboardUrl'      => admin_url( 'admin.php?page=simplestats#/dashboard' ),
			'wpNonce'           => $wpnonce,
			'ajaxUrl'           => admin_url( 'admin-ajax.php' ),
			'allStatsUrl'       => simplestats_get_app_url() . '/dashboard',
			'isUserLoggedIn'    => simplestats_is_connected(),
			'connection'        => array(
				'plan'      => is_array( $connection ) && ! empty( $connection['plan'] ) ? $connection['plan'] : 'free',
				'plan_name' => is_array( $connection ) && ! empty( $connection['plan_name'] ) ? $connection['plan_name'] : '',
			),
			'user'              => array(
				'displayName' => wp_get_current_user()->display_name,
			),
			'todayIso'          => current_time( 'Y-m-d' ),
			'upgradeUrl'        => simplestats_get_app_url() . '/pricing',
			'addons'            => include __DIR__ . '/addons-list.php',
			'pluginNonce'       => wp_create_nonce( 'updates' ),
			'settings'          => simplestats_get_settings(),
		);

		wp_localize_script( $handle, 'SimpleStatsVue', array_merge( $items, $extra_params ) );
	}
}
