<?php
/**
 * Factory core bootstrap.
 *
 * Each plugin carries its OWN Strauss-prefixed copy of this package, so within a
 * given plugin the core is effectively a singleton. Every runtime identifier
 * (option keys, hook tags, nonce actions, capabilities, cron names) is derived
 * from the plugin SLUG injected at boot — never from this PHP namespace — so that
 * string-based scoping (Strauss) cannot silently collide two plugins.
 *
 * @package Hakki\WooCore
 */

namespace ErenLabs\SmartCategories\Vendor\Hakki\WooCore;

use ErenLabs\SmartCategories\Vendor\Hakki\WooCore\Activation\Guard;
use ErenLabs\SmartCategories\Vendor\Hakki\WooCore\Migration\Runner;
use ErenLabs\SmartCategories\Vendor\Hakki\WooCore\Security\Caps;
use ErenLabs\SmartCategories\Vendor\Hakki\WooCore\Security\Request;
use ErenLabs\SmartCategories\Vendor\Hakki\WooCore\Support\Assets;
use ErenLabs\SmartCategories\Vendor\Hakki\WooCore\Support\Options;

defined( 'ABSPATH' ) || exit;

/**
 * Per-plugin core instance. Created once via Core::boot().
 */
final class Core {

	/**
	 * Registry of booted cores, keyed by slug.
	 *
	 * @var array<string,Core>
	 */
	private static $instances = array();

	/** @var string */
	private $slug;
	/** @var string Absolute path to the plugin main file. */
	private $file;
	/** @var string */
	private $version;
	/** @var string b2b|b2c|blog */
	private $group;
	/** @var bool */
	private $requires_woo;
	/** @var string Minimum WooCommerce version. */
	private $min_woo;
	/** @var callable|null Feature gate resolver (Freemius-backed in real plugins). */
	private $can_resolver;

	/** @var Guard */
	private $guard;

	/**
	 * @param array{slug:string,file:string,version:string,group?:string,requires_woo?:bool,min_woo?:string,can?:callable|null} $args
	 */
	private function __construct( array $args ) {
		$this->slug         = (string) $args['slug'];
		$this->file         = (string) $args['file'];
		$this->version      = (string) ( $args['version'] ?? '0.0.0' );
		$this->group        = (string) ( $args['group'] ?? 'b2c' );
		$this->requires_woo = (bool) ( $args['requires_woo'] ?? true );
		$this->min_woo      = (string) ( $args['min_woo'] ?? '8.0' );
		$this->can_resolver = isset( $args['can'] ) && is_callable( $args['can'] ) ? $args['can'] : null;
		$this->guard        = new Guard( $this );
	}

	/**
	 * Boot (or fetch) the core for a plugin. Declares HPOS compatibility immediately.
	 *
	 * @param array $args See constructor.
	 */
	public static function boot( array $args ): self {
		$slug = (string) $args['slug'];
		if ( isset( self::$instances[ $slug ] ) ) {
			return self::$instances[ $slug ];
		}
		$core                     = new self( $args );
		self::$instances[ $slug ] = $core;
		$core->guard->declare_compatibility();
		return $core;
	}

	/**
	 * Fetch a previously booted core.
	 */
	public static function get( string $slug ): ?self {
		return self::$instances[ $slug ] ?? null;
	}

	/* --------------------------------------------------------------------- *
	 * Identity (slug-driven)
	 * --------------------------------------------------------------------- */

	public function slug(): string {
		return $this->slug;
	}

	public function file(): string {
		return $this->file;
	}

	public function version(): string {
		return $this->version;
	}

	public function group(): string {
		return $this->group;
	}

	public function requires_woo(): bool {
		return $this->requires_woo;
	}

	public function min_woo(): string {
		return $this->min_woo;
	}

	public function guard(): Guard {
		return $this->guard;
	}

	/** Underscored prefix for option/meta/hook identifiers, e.g. "quoteguard_for_woocommerce_". */
	public function prefix(): string {
		return str_replace( '-', '_', $this->slug ) . '_';
	}

	/** Build a slug-scoped identifier (option key, hook tag, cron name). */
	public function id( string $suffix ): string {
		return $this->prefix() . $suffix;
	}

	/** Build a slug-scoped nonce action. */
	public function nonce_action( string $suffix = 'action' ): string {
		return $this->prefix() . 'nonce_' . $suffix;
	}

	/** UPPER_SNAKE constant name derived from slug, e.g. QUOTEGUARD_FOR_WOOCOMMERCE_PRO_ACTIVE. */
	public function const_name( string $suffix ): string {
		return strtoupper( str_replace( '-', '_', $this->slug ) ) . '_' . $suffix;
	}

	/* --------------------------------------------------------------------- *
	 * Free / Pro
	 * --------------------------------------------------------------------- */

	/**
	 * Runtime feature gate (Freemius-backed in real plugins). NOT a code lock —
	 * premium code is physically absent from the free build.
	 */
	public function can( string $feature ): bool {
		if ( null !== $this->can_resolver ) {
			return (bool) call_user_func( $this->can_resolver, $feature );
		}
		return (bool) apply_filters( $this->id( 'can' ), false, $feature, $this );
	}

	/**
	 * Free self-guard: the free build must NOT bootstrap when its pro counterpart
	 * is active. Pro signals presence by defining <SLUG>_PRO_ACTIVE = its own main
	 * file at file-load time (before plugins_loaded), so ordering is irrelevant.
	 * The constant's VALUE lets the pro plugin recognise itself and keep booting;
	 * any other plugin instance seeing the constant yields. Free never touches
	 * another plugin's activation state.
	 */
	public function should_boot(): bool {
		$pro_const = $this->const_name( 'PRO_ACTIVE' );
		if ( defined( $pro_const ) && constant( $pro_const ) && constant( $pro_const ) !== $this->file ) {
			return false;
		}
		if ( (bool) apply_filters( $this->id( 'pro_active' ), false ) ) {
			return false;
		}
		return (bool) apply_filters( $this->id( 'should_boot' ), true, $this );
	}

	/* --------------------------------------------------------------------- *
	 * Contract helpers (thin wrappers around the shared services)
	 * --------------------------------------------------------------------- */

	public function caps(): Caps {
		return new Caps( $this );
	}

	public function request(): Request {
		return new Request( $this );
	}

	public function migrations(): Runner {
		return new Runner( $this );
	}

	public function options(): Options {
		return new Options( $this );
	}

	public function assets(): Assets {
		return new Assets( $this );
	}
}
