<?php
/**
 * Value handler for pseudo autoincrement fields.
 *
 * @package Ealab\Deployer
 */

namespace Ealab\Deployer\Tracking\ItemValueHandler;

use Dakwamine\Component\RootDependencyDefinition;
use Ealab\Deployer\Exception\WpDeployException;
use Ealab\Deployer\Tracking\SqlChangesTracker;
use Ealab\Deployer\Tracking\TrackingManager;
use Ealab\Deployer\Utility\Database;

/**
 * Value handler for pseudo autoincrement fields.
 */
class PseudoAutoincrementItemValueHandler extends ItemValueHandlerBase {
	/**
	 * Contains the current max value in the database.
	 *
	 * This value prevents wrong database reads on SELECT MAX()
	 * if multiple calls are done in the same transaction.
	 *
	 * Use generateValue() to get a new value.
	 *
	 * @var int
	 */
	private static int $current_max_value;

	/**
	 * The tracking manager.
	 *
	 * @var \Ealab\Deployer\Tracking\TrackingManager|null
	 */
	private ?TrackingManager $tracking_manager = null;

	/**
	 * Generates a new value.
	 *
	 * @return int The generated value.
	 *
	 * @throws \Ealab\Deployer\Exception\WpDeployException Tracker error.
	 */
	protected function generateValue(): int {
		$wpdb = Database::getDatabaseHandle();

		$short_field_name = $this->getShortFieldName();

		$data_table_name = $this->getHolder()->getRegistry()->getItemTracker()->getDataTableName();

		$db_max_value = (int) $wpdb->get_var(
			$wpdb->prepare(
				'SELECT MAX(%i) FROM %i;',
				array(
					$short_field_name,
					$data_table_name,
				)
			) . SqlChangesTracker::IGNORE_TRACKING
		) ?? 0;

		if ( ! empty( static::$current_max_value ) ) {
			// Use the greatest of the values.
			static::$current_max_value = max( static::$current_max_value, $db_max_value );

			++static::$current_max_value;

			return static::$current_max_value;
		}

		static::$current_max_value = 1 + $db_max_value;

		return static::$current_max_value;
	}

	/**
	 * Gets the value already saved, if any.
	 *
	 * @return int|null The value if found, null otherwise.
	 *
	 * @throws \Ealab\Deployer\Exception\WpDeployException Tracker error.
	 */
	private function getCurrentValue(): ?int {
		$original_field_values = $this->getHolder()->getOriginalDatabaseFieldValues();

		if ( empty( $original_field_values ) ) {
			// This is a new item, so there is no current value.
			return null;
		}

		foreach ( $original_field_values as $full_field_name => $original_field_value ) {
			if ( $this->full_field_name !== $full_field_name ) {
				// Not our field.
				continue;
			}

			// We have found our current value.
			return $original_field_value;
		}

		// This is probably an error, or the table schema has changed recently.
		throw new WpDeployException(
			sprintf(
				_dpgettext(
					'deployer-core',
					'item value handler',
					'The current value could not be found for the field %s of the item %s.'
				),
				$this->full_field_name,
				$this->getHolder()->getUuid( true )
			)
		);
	}

	/**
	 * {@inheritDoc}
	 */
	public function getDependencyDefinitions(): array {
		$definitions = parent::getDependencyDefinitions();

		$definitions[] = new RootDependencyDefinition( TrackingManager::class, $this->tracking_manager );

		return $definitions;
	}

	/**
	 * {@inheritDoc}
	 */
	public static function getHandlerBaseId(): string {
		return 'pseudo_autoincrement';
	}

	/**
	 * {@inheritDoc}
	 */
	public static function getLabel(): string {
		return _dpgettext( 'deployer-core', 'processor label', 'Pseudo autoincrement' );
	}

	/**
	 * {@inheritDoc}
	 */
	public function getUnprocessedValue( int $operation, ?callable $progress_callback = null ) {
		switch ( $operation ) {
			case ItemValueHandlerBase::OPERATION__EXPORT:
				// Export as a placeholder. We don't need to export anything.
				return static::getProcessorPlaceholder();

			case ItemValueHandlerBase::OPERATION__IMPORT__FINALIZE:
				return $this->value;

			case ItemValueHandlerBase::OPERATION__IMPORT__PRESAVE:
				// We use the existing value, or generate a new one.
				$this->value = $this->getCurrentValue() ?? $this->generateValue();

				return $this->value;

			default:
				throw new WpDeployException( sprintf( _dpgettext( 'deployer-core', 'item value handler', 'Invalid operation in %s: %s.' ), static::class, $operation ) );
		}
	}
}
