<?php
/**
 * Core plugin class.
 *
 * This is the main class that handles all plugin functionality including
 * initialization, activation, deactivation, and internationalization.
 *
 * @package   Agenistrator
 * @since     1.0.0
 * @author    Alex Ruban <alex@maximum.guru>
 * @author    Maximum Software Inc <contact@maximum.software>
 * @license   GPL-3.0-only
 * @link      https://agenistrator.com/
 *
 * SPDX-FileCopyrightText: 2025-2026 Alex Ruban <alex@maximum.guru>
 * SPDX-FileCopyrightText: 2025-2026 Maximum Software Inc <contact@maximum.software>
 * SPDX-License-Identifier: GPL-3.0-only
 */

namespace Agenistrator;

// Direct access prevention
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Main class.
 */
class Main implements Maintenance {
	use Singleton;

	/**
	 * Minimum requirements for the plugin.
	 */
	const MIN_PHP_VERSION     = '7.4';
	const MIN_WP_VERSION      = '5.3';
	const MIN_MYSQL_VERSION   = '5.7.8';
	const MIN_MARIADB_VERSION = '10.2.7';

	/**
	 * Cached requirements check result.
	 *
	 * @var bool|null
	 */
	private $requirements_met = null;

	/**
	 * Cached error messages when requirements are not met.
	 *
	 * @var array|null
	 */
	private $requirement_errors = null;

	/**
	 * Get all plugin components
	 *
	 * @return Maintenance[] Array of component instances.
	 */
	private function get_components() {
		return array(
			Provider_Manager::get_instance(),
			LLM_Manager::get_instance(),
			Embeddings_Manager::get_instance(),
			Tool_Manager::get_instance(),
			Chat_Log_Manager::get_instance(),
			Reporter::get_instance(),
			Thread_Manager::get_instance(),
			Event_Queue::get_instance(),
			Admin::get_instance(),
		);
	}

	/**
	 * Invoke a method on all components
	 *
	 * @param string $method The method name to invoke.
	 */
	private function invoke_on_components( $method ) {
		foreach ( $this->get_components() as $component ) {
			$component->$method();
		}
	}

	/**
	 * Check if all minimum requirements are met.
	 *
	 * @return bool True if all requirements are met, false otherwise.
	 * @since     1.0.0
	 */
	public function check_requirements() {
		if ( null !== $this->requirements_met ) {
			return $this->requirements_met;
		}

		$this->requirements_met   = true;
		$this->requirement_errors = array();

		// Check PHP version
		if ( version_compare( PHP_VERSION, self::MIN_PHP_VERSION, '<' ) ) {
			$this->requirements_met     = false;
			$this->requirement_errors[] = array(
				'software' => 'PHP',
				'current'  => PHP_VERSION,
				'required' => self::MIN_PHP_VERSION,
			);
		}

		// Check WordPress version
		$wp_version = get_bloginfo( 'version' );
		if ( version_compare( $wp_version, self::MIN_WP_VERSION, '<' ) ) {
			$this->requirements_met     = false;
			$this->requirement_errors[] = array(
				'software' => 'WordPress',
				'current'  => $wp_version,
				'required' => self::MIN_WP_VERSION,
			);
		}

		// Check database version
		$db_info = $this->get_db_type_and_version();
		if ( 'mariadb' === $db_info['type'] ) {
			if ( version_compare( $db_info['version'], self::MIN_MARIADB_VERSION, '<' ) ) {
				$this->requirements_met     = false;
				$this->requirement_errors[] = array(
					'software' => 'MariaDB',
					'current'  => $db_info['version'],
					'required' => self::MIN_MARIADB_VERSION,
				);
			}
		} elseif ( version_compare( $db_info['version'], self::MIN_MYSQL_VERSION, '<' ) ) {
			$this->requirements_met     = false;
			$this->requirement_errors[] = array(
				'software' => 'MySQL',
				'current'  => $db_info['version'],
				'required' => self::MIN_MYSQL_VERSION,
			);
		}

		return $this->requirements_met;
	}

	/**
	 * Get database type and version.
	 *
	 * Detects whether MariaDB or MySQL is being used and extracts the version.
	 * MariaDB self-identifies in its version string. MySQL is the default
	 * fallback, covering vanilla MySQL, Percona Server, AWS Aurora, and other
	 * forks that do not reliably self-identify in db_server_info().
	 *
	 * @return array Array with 'type' ('mysql' or 'mariadb') and 'version' keys.
	 */
	public function get_db_type_and_version() {
		global $wpdb;

		$info = $wpdb->db_server_info();

		if ( stripos( $info, 'mariadb' ) !== false ) {
			// Handle MariaDB's "5.5.5-" version prefix in older PHP versions.
			// MariaDB 10.x adds a "5.5.5-" prefix to the version string in the MySQL protocol
			// for replication compatibility. PHP 8.0.16+ and 8.1.3+ strip this automatically.
			// phpcs:ignore PHPCompatibility.Constants.NewConstants.php_version_idFound -- PHP_VERSION_ID available since PHP 5.2.7.
			if ( ( PHP_VERSION_ID < 80016 || ( PHP_VERSION_ID >= 80100 && PHP_VERSION_ID < 80103 ) )
				&& strpos( $info, '5.5.5-' ) === 0 ) {
				$info = substr( $info, 6 );
			}
			return array(
				'type'    => 'mariadb',
				'version' => $this->extract_version( $info ),
			);
		}

		return array(
			'type'    => 'mysql',
			'version' => $this->extract_version( $info ),
		);
	}

	/**
	 * Extract a dotted version number (e.g., "10.5.18") from a database server info string.
	 *
	 * @param string $info Raw server info string from $wpdb->db_server_info().
	 * @return string Extracted version, or '0.0.0' if no version-like pattern is found.
	 */
	private function extract_version( $info ) {
		return preg_match( '/(\d+\.\d+\.\d+)/', $info, $matches ) ? $matches[1] : '0.0.0';
	}

	/**
	 * Display admin notice when requirements are not met.
	 * @since     1.0.0
	 */
	public function admin_notice_requirements() {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$error_template = __( '{software} version {current} is installed, but version {required} or higher is required', 'agenistrator' );
		$error_messages = array();
		foreach ( $this->requirement_errors as $error ) {
			$error_messages[] = esc_html( String_Utils::replace_tags( $error_template, $error ) );
		}

		$message = String_Utils::replace_tags(
			esc_html__( 'The plugin cannot run because minimum requirements are not met: {errors}', 'agenistrator' ),
			array( 'errors' => implode( esc_html__( ', ', 'agenistrator' ), $error_messages ) )
		);

		$notice = String_Utils::render_error_notice(
			'agenistrator-requirements',
			array(
				'label'   => esc_html__( 'Agenistrator', 'agenistrator' ),
				'message' => $message,
			)
		);

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- All values are escaped above
		echo $notice;
	}

	/**
	 * Register hooks for the main class and all components
	 * @since     1.0.0
	 */
	public function register_hooks() {
		// Register internationalization early
		add_action( 'init', array( $this, 'load_plugin_textdomain' ) );

		// Check requirements before registering other hooks
		if ( ! $this->check_requirements() ) {
			add_action( 'admin_notices', array( $this, 'admin_notice_requirements' ) );
			return;
		}

		// Register activation and deactivation hooks
		register_activation_hook( AGENISTRATOR_PLUGIN_FILE, array( $this, 'activate_plugin' ) );
		register_deactivation_hook( AGENISTRATOR_PLUGIN_FILE, array( $this, 'deactivate_plugin' ) );

		// Register hooks for all components
		$this->invoke_on_components( 'register_hooks' );
	}

	/**
	 * Plugin activation tasks
	 *
	 * Called when the plugin is activated. Initializes all components
	 * and schedules initial embedding generation.
	 * @since     1.0.0
	 */
	public function activate_plugin() {
		// Activate each component
		$this->invoke_on_components( 'activate_plugin' );
	}

	/**
	 * Plugin deactivation tasks
	 *
	 * Called when the plugin is deactivated. Cleans up temporary data
	 * but preserves user settings.
	 * @since     1.0.0
	 */
	public function deactivate_plugin() {
		// Deactivate each component
		$this->invoke_on_components( 'deactivate_plugin' );
	}

	/**
	 * Uninstall plugin
	 * @since     1.0.0
	 */
	public function uninstall_plugin() {
		// Uninstall each component
		$this->invoke_on_components( 'uninstall_plugin' );
	}

	/**
	 * Load text domain for translation.
	 *
	 * We explicitly load translations from the plugin's languages/ directory rather than
	 * relying solely on WordPress.org's automatic translation system. This ensures:
	 *
	 * 1. Translations work when WordPress.org is blocked (hosting provider firewall, etc.)
	 * 2. We can ship translations bundled with the plugin for immediate availability
	 *
	 * WordPress.org translations require the plugin to be hosted there AND for the site to
	 * have internet access to download translation packs from translate.wordpress.org.
	 * @since     1.0.0
	 */
	public function load_plugin_textdomain() {
		// phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Intentionally bundling translations for restricted network use; see PHPDoc above.
		load_plugin_textdomain(
			'agenistrator',
			false,
			dirname( plugin_basename( AGENISTRATOR_PLUGIN_FILE ) ) . '/languages/'
		);
	}
}
