<?php
/**
 * Screen label formatter using the label of a referenced deployment item.
 *
 * @package Ealab\Deployer
 */

namespace EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ScreenLabelFormatter;

use EaLab\WP_Deployer\vendor\Dakwamine\Component\RootDependencyDefinition;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\InfiniteRecursionException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Exception\LabelFormatterException;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\ScreenLabelFormatterManager;
use EaLab\WP_Deployer\vendor\Ealab\Deployer\Tracking\TrackableItem;
use EaLab\WP_Deployer\vendor\Symfony\Component\Yaml\Yaml;

/**
 * Screen label formatter using the label of a referenced deployment item.
 *
 * This formatter does not return default values on formatting errors,
 * but throws errors to prevent the system from showing wrong intermediary
 * reference labels.
 */
class ReferencedItemLabelScreenLabelFormatter extends ScreenLabelFormatterBase {
	/**
	 * The screen label formatter manager.
	 *
	 * @var ScreenLabelFormatterManager|null
	 */
	protected ?ScreenLabelFormatterManager $screen_label_formatter_manager = null;

	/**
	 * {@inheritDoc}
	 */
	public function formatFromFileItem( string $bundle_subdirectory, array $file_item, ?array $config = null ): string {
		// Determine the tracker of the item.
		$tracker = $this->tracking_manager->getItemTrackerByTrackerId( $file_item['tracker'] ?? null ) ?? null;

		if ( empty( $tracker ) ) {
			// The tracker is not available.
			return parent::formatFromFileItem( $bundle_subdirectory, $file_item, $config );
		}

		if ( ! isset( $config ) ) {
			// Use the tracker's default configuration if none is provided.
			$config = $config ?? $tracker->getScreenLabelFormatterConfig() ?? array();
		}

		$field_name = $config['field_name'] ?? '';

		if ( empty( $field_name ) ) {
			return parent::formatFromFileItem( $bundle_subdirectory, $file_item, $config );
		}

		$item_full_uuid = $file_item['fields'][ $file_item['tracker'] . '.' . $field_name ] ?? '';

		if ( empty( $item_full_uuid ) ) {
			return parent::formatFromFileItem( $bundle_subdirectory, $file_item, $config );
		}

		if ( $this->screen_label_formatter_manager->isStacked( $item_full_uuid ) ) {
			// Prevent recursion.
			throw new InfiniteRecursionException( 'Could not determine the label of a deployment item due to a detected infinite recursion.' );
		}

		$this->screen_label_formatter_manager->stack( $item_full_uuid );

		try {
			// Load the referenced item from the file system.
			$referenced_file_items = $this->file_manager->loadDeploymentItemsData( $bundle_subdirectory, array( $item_full_uuid ) );

			if ( 1 !== count( $referenced_file_items ) ) {
				// This should never happen because it means that the referenced item is not available in the bundle.
				throw new LabelFormatterException( 'The referenced item is not available in the deployment bundle.' );
			}

			$referenced_file_item = reset( $referenced_file_items );

			return $this->screen_label_formatter_manager->formatFromFileItemRecursive( $bundle_subdirectory, $referenced_file_item );
		} finally {
			$this->screen_label_formatter_manager->unstack( $item_full_uuid );
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public function formatFromTrackableItem( TrackableItem $item, ?array $config = null ): string {
		$item_tracker = $item->getRegistry()->getItemTracker();

		// Use the tracker's default configuration if none is provided.
		$config = $config ?? $item_tracker->getScreenLabelFormatterConfig() ?? array();

		$field_name = $config['field_name'] ?? '';

		if ( empty( $field_name ) ) {
			throw new LabelFormatterException( 'An invalid config was supplied to the referenced_item formatter.' );
		}

		// When loaded from the database, the field contains the local ID.
		$local_id = $item->getRawDataFields()[ $field_name ] ?? '';

		if ( empty( $local_id ) ) {
			throw new LabelFormatterException( 'Could not get the local ID of the referenced item, or there is no referenced item at all.' );
		}

		// Get the full UUID of the referenced item.
		// Note: this seems inefficient, but it is the simplest and safest way
		// to get the full UUID of the referenced item.
		$deployment_data = $item->buildExportData();

		$item_full_uuid = $deployment_data['fields'][ $item_tracker->getTrackerId() . '.' . $field_name ];

		if ( $this->screen_label_formatter_manager->isStacked( $item_full_uuid ) ) {
			// Prevent recursion.
			throw new InfiniteRecursionException( 'Could not determine the label of a deployment item due to a detected infinite recursion.' );
		}

		$this->screen_label_formatter_manager->stack( $item_full_uuid );

		try {
			// Load the referenced item tracker and item.
			$referenced_item_tracker = $this->tracking_manager->getItemTrackerByItemFullUuid( $item_full_uuid );

			if ( empty( $referenced_item_tracker ) ) {
				// This should never happen because if we have are working with trackable items,
				// it means we are in a context where the references link to available items
				// in the database, so the referenced items are handled by a tracker.
				throw new LabelFormatterException( 'The referenced item tracker is not available.' );
			}

			// Force only_modified_force to false to ensure we can load referenced items
			// even if they haven't been modified recently (they are dependencies).
			$referenced_item = $referenced_item_tracker->loadTrackableItem(
				$item_full_uuid,
				array( 'only_modified_force' => false )
			);

			if ( empty( $referenced_item ) ) {
				throw new LabelFormatterException( 'The referenced item does not exist.' );
			}

			return $this->screen_label_formatter_manager->formatFromTrackableItemRecursive( $referenced_item );
		} finally {
			$this->screen_label_formatter_manager->unstack( $item_full_uuid );
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public function getDependencyDefinitions(): array {
		$definitions   = parent::getDependencyDefinitions();
		$definitions[] = new RootDependencyDefinition( ScreenLabelFormatterManager::class, $this->screen_label_formatter_manager );

		return $definitions;
	}

	/**
	 * {@inheritDoc}
	 */
	public static function getFormBuild(): ?array {
		$build = Yaml::parse(
			<<<YAML
			field_name:
			  label:
			    load: field_label
			    content: Field where to get the reference from
			  select:
			    type: SelectField
			    dataLocation: ~.screen_label_formatter.config.field_name
			    dynamicOptions: ~._meta.column_names
			YAML
		);

		// Translations.
		$build['field_name']['label']['content'] = _dpgettext( 'deployer-core', 'screen label formatter', 'Field where to get the reference from' );

		return $build;
	}

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

	/**
	 * {@inheritDoc}
	 */
	public static function getLabel(): string {
		return _dpgettext( 'deployer-core', 'screen label formatter', 'Referenced item' );
	}
}
