<?php
/**
 * Plugin bootstrap singleton — wires admin UI, REST controller, assets,
 * shortcode, classic widget, and load_textdomain.
 *
 * @package FindAstra
 */

defined( 'ABSPATH' ) || exit;

final class FindAstra_Plugin {

	private static ?self $instance = null;

	private bool $booted = false;

	public static function instance(): self {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	public function boot(): void {
		if ( $this->booted ) {
			return;
		}
		$this->booted = true;

		add_action( 'init', array( $this, 'load_textdomain' ) );
		add_action( 'admin_init', array( 'FindAstra_Activator', 'maybe_upgrade' ) );
		add_action( 'admin_init', array( $this, 'maybe_redirect_to_settings' ) );

		if ( is_admin() ) {
			( new FindAstra_Admin() )->register();
		}

		( new FindAstra_Assets() )->register();
		( new FindAstra_Shortcode() )->register();
		( new FindAstra_Query_Hijack() )->register();
		( new FindAstra_Block() )->register();
		( new FindAstra_Asset_Server() )->register();

		// Registered unconditionally (not just is_admin): the Action Scheduler
		// job that drives background indexing runs in a cron / loopback request,
		// so its hook handler must be attached on every request.
		( new FindAstra_Background_Indexer() )->register();

		add_action( 'widgets_init', static function () {
			register_widget( 'FindAstra_Widget' );
		} );

		add_action( 'rest_api_init', array( new FindAstra_REST_Controller(), 'register_routes' ) );

		// Test-mode mock provider hooks in its lowered gating thresholds here.
		// No-op in production builds (constant undefined).
		FindAstra_Provider_Mock::register_filters();

		// Admin-saved match-quality gates override the in-code defaults via
		// the same filter hooks buyers can hit from code. No-op when the
		// option fields are blank.
		FindAstra_Admin::register_gate_filters();
	}

	/**
	 * On the first admin pageload after activation, send the user straight to
	 * the FindAstra settings page so they don't have to hunt for it.
	 */
	public function maybe_redirect_to_settings(): void {
		if ( '1' !== get_option( FindAstra_Activator::ACTIVATION_FLAG_OPTION, '0' ) ) {
			return;
		}
		// Clear the flag immediately so we don't redirect twice.
		delete_option( FindAstra_Activator::ACTIVATION_FLAG_OPTION );
		if ( wp_doing_ajax() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ) {
			return;
		}
		if ( isset( $_GET['activate-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
			return;
		}
		if ( ! current_user_can( FindAstra_Admin::CAPABILITY ) ) {
			return;
		}
		wp_safe_redirect( admin_url( 'admin.php?page=' . FindAstra_Admin::MENU_SLUG ) );
		exit;
	}

	public function load_textdomain(): void {
		// WordPress.org (free) build: since WP 4.6 the just-in-time loader handles
		// translations for .org-hosted plugins automatically, so calling
		// load_plugin_textdomain() is redundant. Skip it entirely — this keeps the
		// free build clean for Plugin Check and the .org review.
		//
		// Paid build (distributed off .org via findastra.com): keep the manual
		// call so bundled MO files register against the plugin's own /languages
		// directory on WordPress 6.4–6.6, where JIT does not yet auto-scan it (it
		// does from 6.7). The Freemius free build reports is_premium() === false,
		// so the early return below means the call ships only in the paid build.
		if ( ! function_exists( 'findastra_fs' ) || ! findastra_fs()->is_premium() ) {
			return;
		}
		// phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- runs only in the off-.org paid build; see above.
		load_plugin_textdomain(
			'findastra',
			false,
			dirname( FINDASTRA_BASENAME ) . '/languages'
		);
	}
}
