<?php
/**
 * Asset enqueue for admin + frontend.
 *
 * The actual bundle entries (`frontend.js`, `admin.js`) are ES modules
 * emitted by Vite. WP's `wp_enqueue_script` produces a `<script src>`
 * tag without `type="module"`, and our earlier approach of adding the
 * attribute via `script_loader_tag` lost to budget themes that force
 * `type="text/javascript"` on every script through their own filter
 * (StoreX, Digital Storefront, Designer Boutique Shop, Shoes Footwear…).
 *
 * Bulletproof workaround: enqueue a tiny CLASSIC-script loader
 * (`assets/findastra-loader.js`) that programmatically creates the
 * real `<script type="module">` element via `document.createElement`.
 * Themes can't filter or rewrite dynamically-created nodes —
 * `script_loader_tag` only fires on enqueued scripts.
 *
 * @package FindAstra
 */

defined( 'ABSPATH' ) || exit;

final class FindAstra_Assets {

	public const FRONTEND_HANDLE = 'findastra-frontend';
	public const ADMIN_HANDLE    = 'findastra-admin';

	public function register(): void {
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin' ) );
	}

	public function enqueue_frontend(): void {
		$entry = FINDASTRA_DIR . 'dist/frontend.js';
		if ( ! is_readable( $entry ) ) {
			return;
		}
		$this->enqueue_loader( self::FRONTEND_HANDLE, array( 'frontend.js' ), false );
		// CSS is bundled by Vite alongside frontend.js (path lives in dist/).
		$css_path = FINDASTRA_DIR . 'dist/frontend.css';
		if ( is_readable( $css_path ) ) {
			wp_enqueue_style(
				self::FRONTEND_HANDLE,
				FINDASTRA_URL . 'dist/frontend.css',
				array(),
				FINDASTRA_VERSION
			);
		}
	}

	public function enqueue_admin( string $hook ): void {
		if ( ! str_contains( (string) $hook, 'findastra' ) ) {
			return;
		}

		// Settings-page styles + tab/engine-picker JS. Both small and hand-written;
		// loaded as plain enqueues, no Vite build step needed.
		wp_enqueue_style(
			self::ADMIN_HANDLE . '-page',
			FINDASTRA_URL . 'assets/admin.css',
			array(),
			FINDASTRA_VERSION
		);
		wp_enqueue_script(
			self::ADMIN_HANDLE . '-page',
			FINDASTRA_URL . 'assets/admin-page.js',
			array(),
			FINDASTRA_VERSION,
			array( 'in_footer' => true, 'strategy' => 'defer' )
		);

		$entry = FINDASTRA_DIR . 'dist/admin.js';
		if ( ! is_readable( $entry ) ) {
			return;
		}
		$this->enqueue_loader( self::ADMIN_HANDLE, array( 'admin.js' ), true );
	}

	/**
	 * Enqueue the classic-script loader for a set of module entries.
	 * The loader reads `window.FindAstraConfig.assets.entries` and
	 * injects a `<script type="module">` for each file at runtime.
	 *
	 * @param string[] $entries Filenames inside dist/ (e.g. ['frontend.js']).
	 */
	private function enqueue_loader( string $handle, array $entries, bool $admin ): void {
		wp_enqueue_script(
			$handle,
			FINDASTRA_URL . 'assets/findastra-loader.js',
			array(),
			FINDASTRA_VERSION,
			array( 'in_footer' => true, 'strategy' => 'defer' )
		);
		$this->inline_config( $handle, $admin, $entries );
	}

	/**
	 * @param string[] $entries
	 */
	private function inline_config( string $handle, bool $admin, array $entries ): void {
		$provider = FindAstra_Provider_Factory::current();
		$settings = get_option( FindAstra_Admin::SETTINGS_OPTION, array() );
		$config   = array(
			'version'  => FINDASTRA_VERSION,
			'rest'     => array(
				'root'  => esc_url_raw( get_rest_url( null, FindAstra_REST_Controller::NAMESPACE ) ),
				'nonce' => $admin ? wp_create_nonce( 'wp_rest' ) : '',
			),
			'assets'   => array(
				'dist'     => esc_url_raw( FINDASTRA_URL . 'dist/' ),
				'entries'  => array_values( $entries ),
				// REST asset server with forced application/wasm Content-Type.
				// Trailing slash matters — ORT appends file names directly.
				'wasm_via' => esc_url_raw( trailingslashit( get_rest_url( null, FindAstra_REST_Controller::NAMESPACE . '/asset' ) ) ),
			),
			'theme'    => array(
				// Default ON: opt-in is the wrong default for a search plugin.
				'autocomplete' => '0' !== (string) ( $settings['autocomplete_attach'] ?? '1' ),
			),
			'provider' => array(
				'key'            => $provider->key(),
				'model'          => $provider->model(),
				'dims'           => $provider->dims(),
				'is_server_side' => $provider->is_server_side(),
			),
			'language' => $admin ? '' : self::current_request_language(),
			// License snapshot — admin JS uses this to swap the index button
			// for an upgrade prompt when the buyer hits the free-tier cap.
			// Frontend JS only needs `is_free` for "Pro" badges, so we ship
			// the full snapshot in admin and a trimmed version on the front.
			'license'  => $admin
				? FindAstra_License::js_snapshot()
				: array(
					'plan'    => FindAstra_License::current_plan(),
					'is_free' => FindAstra_License::is_free(),
				),
			'i18n'     => array(
				'placeholder'         => __( 'Search products…', 'findastra' ),
				'no_results'          => __( 'No matching products.', 'findastra' ),
				'no_results_fallback' => __( 'No exact matches — here are some popular products:', 'findastra' ),
				'loading_model'       => __( 'Loading search model (one-time, ~33 MB)…', 'findastra' ),
				'searching'           => __( 'Searching…', 'findastra' ),
				'error'               => __( 'Search failed. Please try again.', 'findastra' ),
			),
		);

		wp_add_inline_script(
			$handle,
			'window.FindAstraConfig = ' . wp_json_encode( $config ) . ';',
			'before'
		);
	}

	/**
	 * Detect the current frontend page's language for the FindAstraConfig
	 * bootstrap. Surfaces WPML and Polylang automatically; sites without
	 * either return '' and the search continues to behave as before
	 * (no language filter applied at REST time).
	 */
	public static function current_request_language(): string {
		// Per-language search is a paid-tier capability. The WPML / Polylang
		// resolution lives in FindAstra_Multilingual, which ships only in the
		// paid build. When that class is absent (the free build) we return '',
		// so the search pipeline treats the install as mono-lingual — matching
		// the empty-tag rows the indexer writes. Both halves move together: no
		// half-applied state where indexing forgets the language but search
		// looks for it.
		if ( class_exists( 'FindAstra_Multilingual' ) ) {
			return FindAstra_Multilingual::current_request_language();
		}
		return '';
	}
}
