<?php
/**
 * Engine: boot + shared runtime accessors.
 *
 * The generic machinery that turns "this site's own content" into "the final
 * rendered HTML of each item, produced safely and at scale" and hands that
 * HTML to a consumer-supplied analyzer. It owns the render door, the
 * crash-safe scan queue (a phase-agnostic runner, instantiated per queue),
 * content discovery, and the item-exclusion store + its core-list-table bulk
 * actions. It owns NO analyzer, NO results schema, NO settings, and NO admin
 * UI. Logging is the separate shared-logging component (a hard dependency
 * since 0.2.0 - the consumer vendors and requires it alongside the engine;
 * the engine boots and logs through it). See _standards/docs/RENDER-ENGINE.md.
 *
 * It is given config + a handler at boot and never references a consumer's
 * own classes. Runtime names (query vars, transients, option/hook names) are
 * built from config['prefix'] so they differ per consumer without code edits;
 * the class prefixes (SSAA_Engine, and SSAA_Log* for the logging
 * component's classes) and the text domain (search-social-appearance-audit) are the only
 * things rewritten at vendoring time by sync.sh.
 *
 * CANONICAL ENGINE SOURCE - edit here in _shared/shared-render-engine/src/, then re-run
 * sync.sh into each consumer. Never edit the vendored copy.
 *
 * @package plugupp-render-engine
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class SSAA_Engine {

	/** Engine source version. A consumer records the bundled value in its own
	 * {PREFIX}_ENGINE_VERSION constant (set by the consumer at sync time). */
	const VERSION = '0.4.0';

	/** @var array Boot config supplied by the consuming plugin. */
	private static $config = array();

	/** @var SSAA_Engine_Handler|null The consumer's seam handler. */
	private static $handler = null;

	/** @var SSAA_Engine_Queue[] Configured queue runners, keyed by name. */
	private static $queues = array();

	/** @var bool */
	private static $booted = false;

	/**
	 * Boot the engine. Call once, on plugins_loaded, from the consumer's
	 * bootstrap. Idempotent.
	 *
	 * @param array                 $config  Engine config (see INTEGRATION.md).
	 * @param SSAA_Engine_Handler $handler The consumer's seam handler.
	 */
	public static function boot( array $config, SSAA_Engine_Handler $handler ) {
		if ( self::$booted ) {
			return;
		}

		self::$config  = $config;
		self::$handler = $handler;

		// The render door (incoming self-requests). parse_request runs only on
		// front-end requests, before WP resolves the main query.
		add_action( 'parse_request', array( 'SSAA_Engine_Renderer', 'guard_door' ), 0 );

		// The chain-hop endpoint (incoming queue-continuation self-requests) -
		// same trust model as the door (SPEC-queue-loopback-chain.md).
		add_action( 'parse_request', array( 'SSAA_Engine_Queue', 'guard_chain' ), 0 );

		// Build each configured queue runner and register its cron continuation
		// hook. Every queue is the same phase-agnostic runner; the consumer
		// decides how many and how they chain.
		foreach ( (array) self::config( 'queues', array() ) as $name => $qcfg ) {
			$qcfg         = (array) $qcfg;
			$qcfg['name'] = (string) $name;
			$queue        = new SSAA_Engine_Queue( $qcfg );
			$queue->init();
			self::$queues[ $name ] = $queue;
		}

		// Logging is the shared-logging component (vendored alongside the
		// engine since 0.2.0 - a HARD dependency; the consumer requires its
		// three files before booting the engine). The engine maps its own
		// config through, so consumers configure logging once, here. The
		// component's boot is idempotent: a consumer that boots SSAA_Log
		// itself first wins.
		SSAA_Log::boot( array(
			'slug'             => (string) self::config( 'slug', 'search-social-appearance-audit' ),
			'prefix'           => self::prefix(),
			'log_level_getter' => self::config( 'log_level_getter' ),
			'option_key'       => (string) self::log_cfg( 'option_key' ),
			'settings_slug'    => (string) self::log_cfg( 'settings_slug' ),
		) );

		// Item exclusion: core-list-table bulk actions + admin notices.
		if ( is_admin() ) {
			SSAA_Engine_Exclusions::init();
		}

		self::$booted = true;
	}

	/**
	 * A key from the 'logs' sub-config (option_key / settings_slug).
	 *
	 * @param string $key Sub-config key.
	 * @return string
	 */
	private static function log_cfg( $key ) {
		$cfg = (array) self::config( 'logs', array() );
		return isset( $cfg[ $key ] ) ? (string) $cfg[ $key ] : '';
	}

	/**
	 * Read a boot config value.
	 *
	 * @param string|null $key     Config key, or null for the whole array.
	 * @param mixed       $default Fallback when the key is absent.
	 * @return mixed
	 */
	public static function config( $key = null, $default = null ) {
		if ( null === $key ) {
			return self::$config;
		}
		return array_key_exists( $key, self::$config ) ? self::$config[ $key ] : $default;
	}

	/**
	 * The consumer's seam handler.
	 *
	 * @return SSAA_Engine_Handler|null
	 */
	public static function handler() {
		return self::$handler;
	}

	/**
	 * A configured queue runner by name (as keyed in config['queues']).
	 *
	 * @param string $name Queue name.
	 * @return SSAA_Engine_Queue|null
	 */
	public static function queue( $name ) {
		return isset( self::$queues[ $name ] ) ? self::$queues[ $name ] : null;
	}

	/**
	 * Lower-case consumer prefix (e.g. 'liha').
	 *
	 * @return string
	 */
	public static function prefix() {
		return strtolower( (string) self::config( 'prefix', 'engine' ) );
	}

	/**
	 * Build a prefixed runtime name for a query var / option / hook / transient.
	 * E.g. key('render') => 'liha_render'.
	 *
	 * @param string $suffix Unprefixed suffix.
	 * @return string
	 */
	public static function key( $suffix ) {
		return self::prefix() . '_' . ltrim( (string) $suffix, '_' );
	}

	/**
	 * Canonical engine ref for a post.
	 *
	 * @param int $post_id Post ID.
	 * @return string
	 */
	public static function post_ref( $post_id ) {
		return 'post:' . absint( $post_id );
	}

	/**
	 * Canonical engine ref for a URL (sha1 of the URL as supplied).
	 *
	 * @param string $url URL.
	 * @return string
	 */
	public static function url_ref( $url ) {
		return 'url:' . sha1( (string) $url );
	}
}
