<?php
/**
 * WordPress field provider lifecycle.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Provider;

use ScheduledChanges\Extension\PublicHooks;
use ScheduledChanges\Field\FieldRegistry;
use Throwable;

/**
 * Loads filtered provider services after WordPress plugin filters are available.
 */
final class ProviderManager {
	private ProviderRegistry $providers;
	private FieldRegistry $fields;
	/**
	 * @var FieldProvider[]
	 */
	private array $builtins = array();
	/**
	 * @var string[]
	 */
	private array $errors = array();

	private bool $loaded = false;

	/**
	 * @param FieldProvider[] $builtins Built-in providers registered before filters.
	 */
	public function __construct( ProviderRegistry $providers, FieldRegistry $fields, array $builtins = array() ) {
		$this->providers = $providers;
		$this->fields    = $fields;
		$this->builtins  = $builtins;
	}

	public function registerHooks(): void {
		add_action( 'init', array( $this, 'load' ), 5 );

		foreach (
			array(
				'activated_plugin',
				'deactivated_plugin',
				'registered_taxonomy',
				'acf/update_field_group',
			) as $hook
		) {
			add_action( $hook, array( $this, 'invalidateDiscoveryCache' ), 20 );
		}
	}

	public function load(): void {
		if ( $this->loaded ) {
			return;
		}

		$this->loaded = true;

		$filtered_providers = PublicHooks::fieldProviders( $this->builtins );
		$builtin_ids        = array();

		foreach ( $this->builtins as $builtin ) {
			if ( $builtin instanceof FieldProvider ) {
				$builtin_ids[ $builtin->id() ] = true;
			}
		}

		$providers = $this->builtins;

		foreach ( $filtered_providers as $provider ) {
			if ( $provider instanceof FieldProvider && isset( $builtin_ids[ $provider->id() ] ) ) {
				continue;
			}

			$providers[] = $provider;
		}

		foreach ( $providers as $index => $provider ) {
			if ( ! $provider instanceof FieldProvider ) {
				$this->errors[] = sprintf( 'Filtered provider #%d does not implement FieldProvider.', (int) $index );
				continue;
			}

			try {
				$this->providers->register( $provider );
			} catch ( Throwable $throwable ) {
				$this->errors[] = sprintf(
					'Field provider %1$s could not be registered: %2$s',
					$provider->id(),
					$this->diagnosticMessage( $throwable )
				);
			}
		}
	}

	public function invalidateDiscoveryCache(): void {
		$this->fields->invalidateCache();
	}

	public function providers(): ProviderRegistry {
		return $this->providers;
	}

	public function fields(): FieldRegistry {
		return $this->fields;
	}

	public function loaded(): bool {
		return $this->loaded;
	}

	/**
	 * @return string[]
	 */
	public function errors(): array {
		return $this->errors;
	}

	private function diagnosticMessage( Throwable $throwable ): string {
		return trim( (string) preg_replace( '/[\r\n\t]+/', ' ', $throwable->getMessage() ) );
	}
}
