<?php

namespace EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility;

use EaLab\WP_Deployer\vendor\Dakwamine\Component\ComponentBasedObject;
use EaLab\WP_Deployer\vendor\Dakwamine\Component\Exception\UnmetDependencyException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\SqlChangesTracker;
use wpdb;

class_alias(
	( function () {
		global $wpdb;

		return get_class( $wpdb );
	} )(),
	'\EaLab\WP_Deployer\vendor\Ealab\Deployer\Utility\WpdbAlias'
);

register_shutdown_function( function () {
	$GLOBALS['dsc.shutdown'] = true;
} );

/**
 * Wrapper around the current Wpdb class.
 */
class WpdbWrapper extends WpdbAlias {
	/**
	 * The SQL changes tracker.
	 *
	 * @var SqlChangesTracker|null
	 */
	protected ?SqlChangesTracker $sql_changes_tracker = null;

	/**
	 * Whether the wrapper is unregistering.
	 *
	 * If true, the wrapper will not rewrap the global wpdb with a new instance of WpdbWrapper.
	 * It will also not track SQL changes.
	 *
	 * @var bool
	 */
	protected bool $unregistering = false;

	public function __destruct() {
		if ( ! empty( $GLOBALS['dsc.shutdown'] ) ) {
			// Shutdown is in progress.
			return;
		}

		if ( ! empty( $this->unregistering ) ) {
			// The wrapper is unregistering.
			// Let's not rewrap the global wpdb with a new instance of WpdbWrapper.
			return;
		}

		// Rewrap the global wpdb with a new instance of WpdbWrapper which will inherit from the wpdb class.
		// This case happens when another plugin has swapped the global wpdb with their own.
		deploy_sync_content__wrap_wpdb();
	}

	/**
	 * Gets the SQL changes tracker.
	 *
	 * @return SqlChangesTracker The SQL changes tracker.
	 *
	 * @throws UnmetDependencyException If a dependency is unavailable.
	 */
	protected function getSqlChangesTracker(): SqlChangesTracker {
		if ( null === $this->sql_changes_tracker ) {
			$this->sql_changes_tracker = ComponentBasedObject::getRootComponentByClassName( SqlChangesTracker::class, true );
		}

		return $this->sql_changes_tracker;
	}

	/**
	 * {@inheritDoc}
	 */
	public function query( $query ) {
		// Dev note: get the SqlChangesTracker component right now.
		// This is very important to have it before the first query
		// because if the SqlChangesTracker is not yet instantiated,
		// this will result in database requests for Deploy & Sync Content
		// that will break the $query that we have to process.
		//
		// In other words:
		//
		// If not placed here, the $query will be performed,
		// then on SqlChangesTracker init, some queries
		// will be made (select, or whatever), then the $this->last_result
		// property of this db instance will be the one of the
		// SqlChangesTracker init, not the current $query to process.
		$sql_changes_tracker = $this->getSqlChangesTracker();

		$result = parent::query( $query );

		if ( $this->unregistering ) {
			// The wrapper is unregistering.
			// Simply return the result.
			return $result;
		}

		if ( $result ) {
			$parsed = Database::parseSqlQuery( $query );

			if ( ! $sql_changes_tracker->isTrackingQueryToIgnore( $parsed ) ) {
				$sql_changes_tracker->trackOnSqlChanges( $parsed );
			}
		}

		return $result;
	}

	/**
	 * Call this if the wrapper is not needed anymore.
	 *
	 * Use it when deactivating the plugin for example, before dropping the tables.
	 */
	public function unregisterWrapper(): void {
		$this->unregistering = true;
	}
}

/**
 * Wraps wpdb with the WpdbWrapper.
 */
function deploy_sync_content__wrap_wpdb() {
	// Swap the original Wpdb class with our dynamic wrapper.
	global $wpdb;

	if ( $wpdb instanceof WpdbWrapper ) {
		// Already swapped.
		return;
	}

	// Retrieve the original wpdb instance values.
	$dbuser      = \Closure::fromCallable( function () {
		return $this->dbuser;
	} )->call( $wpdb );
	$dbpassword  = \Closure::fromCallable( function () {
		return $this->dbpassword;
	} )->call( $wpdb );
	$dbname      = \Closure::fromCallable( function () {
		return $this->dbname;
	} )->call( $wpdb );
	$dbhost      = \Closure::fromCallable( function () {
		return $this->dbhost;
	} )->call( $wpdb );
	$object_vars = \Closure::fromCallable( function () {
		return get_object_vars( $this );
	} )->call( $wpdb );

	// Instantiate the wpdb wrapper.
	$wpdb = new WpdbWrapper( $dbuser, $dbpassword, $dbname, $dbhost );

	// Copy the original wpdb instance values to the new instance.
	foreach ( $object_vars as $key => $value ) {
		\Closure::fromCallable( function () use ( $key, $value ) {
			$this->{$key} = $value;
		} )->call( $wpdb );
	}
}

// Wrap it by default.
deploy_sync_content__wrap_wpdb();
