<?php

namespace WPML\ST\TranslationFile;

use stdClass;
use WPML\Collect\Support\Collection;
use WPML\Element\API\Languages;

class UpdateHooks implements \IWPML_Action {

	/** @var Manager $file_manager */
	private $file_manager;

	/** @var DomainsLocalesMapper $domains_locales_mapper */
	private $domains_locales_mapper;

	/** @var array $updated_translation_ids */
	private $updated_translation_ids = [];

	/** @var Collection $entities_to_update */
	private $entities_to_update;

	/** @var callable */
	private $resetDomainsCache;

	/**
	 * Constructor.
	 *
	 * @param Manager              $file_manager
	 * @param DomainsLocalesMapper $domains_locales_mapper
	 * @param callable|null        $resetDomainsCache
	 */
	public function __construct(
		Manager $file_manager,
		DomainsLocalesMapper $domains_locales_mapper,
		?callable $resetDomainsCache = null
	) {
		$this->file_manager           = $file_manager;
		$this->domains_locales_mapper = $domains_locales_mapper;
		$this->entities_to_update     = wpml_collect( [] );
		$this->resetDomainsCache      = $resetDomainsCache ?: [ Domains::class, 'resetCache' ];
	}

	public function add_hooks() {
		add_action( 'wpml_st_strings_table_altered', [ Domains::class, 'invalidateMODomainCache' ] );
		add_action( 'wpml_st_string_registered', [ Domains::class, 'invalidateMODomainCache' ] );
		add_action( 'wpml_st_string_unregistered', [ Domains::class, 'invalidateMODomainCache' ] );
		add_action( 'wpml_st_string_updated', [ Domains::class, 'invalidateMODomainCache' ] );

		add_action( 'wpml_st_add_string_translation', array( $this, 'add_to_update_queue' ) );
		add_action( 'wpml_st_update_string', array( $this, 'refresh_after_update_original_string' ), 10, 6 );
		add_action( 'wpml_st_before_remove_strings', array( $this, 'refresh_before_remove_strings' ) );
		add_action( 'wpml_st_translation_files_process_queue', [ $this, 'process_update_queue_action' ] );

		/**
		 * @see UpdateHooks::refresh_domain
		 * @since @3.1.0
		 */
		add_action( 'wpml_st_refresh_domain', [ $this, 'refresh_domain' ] );

		if ( ! $this->file_manager->isPartialFile() ) {
			add_action( 'wpml_st_translations_file_post_import', array( $this, 'update_imported_file' ) );
		}
	}

	/** @param int $string_translation_id */
	public function add_to_update_queue( $string_translation_id ) {
		if ( ! in_array( $string_translation_id, $this->updated_translation_ids, true ) ) {
			$this->updated_translation_ids[] = $string_translation_id;
			$this->add_shutdown_action();
		}
	}

	private function add_shutdown_action() {
		if ( ! has_action( 'shutdown', array( $this, 'process_update_queue_action' ) ) ) {
			add_action( 'shutdown', array( $this, 'process_update_queue_action' ) );
		}
	}

	public function process_update_queue_action() {
		remove_action( 'shutdown', array( $this, 'process_update_queue_action' ) );

		$this->process_update_queue();
	}

	/**
	 * @return array
	 */
	public function process_update_queue() {
		call_user_func( $this->resetDomainsCache );

		$outdated_entities        = $this->domains_locales_mapper->get_from_translation_ids( $this->updated_translation_ids );
		$this->entities_to_update = $this->entities_to_update->merge( $outdated_entities );

		$this->entities_to_update->each(
			function ( $entity ) {
				$updatedFilename = $this->update_file( $entity->domain, $entity->locale );
				do_action( 'wpml_st_translation_file_updated', $updatedFilename, $entity->domain, $entity->locale );
			}
		);

		return $this->entities_to_update->toArray();
	}

	/**
	 * @param string     $domain
	 * @param string     $name
	 * @param string     $old_value
	 * @param string     $new_value
	 * @param bool|false $force_complete
	 * @param stdClass   $string
	 */
	public function refresh_after_update_original_string( $domain, $name, $old_value, $new_value, $force_complete, $string ) {
		$outdated_entities        = $this->domains_locales_mapper->get_from_string_ids( [ $string->id ] );
		$this->entities_to_update = $this->entities_to_update->merge( $outdated_entities );
		$this->add_shutdown_action();
	}

	public function update_imported_file( \WPML_ST_Translations_File_Entry $file_entry ) {
		if ( $file_entry->get_status() === \WPML_ST_Translations_File_Entry::IMPORTED ) {
			$updatedFilename = $this->update_file( $file_entry->get_domain(), $file_entry->get_file_locale() );
			if ( $updatedFilename ) {
				do_action( 'wpml_st_translation_file_updated', $updatedFilename, $file_entry->get_domain(), $file_entry->get_file_locale() );
			}
		}
	}

	/**
	 * It dispatches the regeneration of MO files for a specific domain in all active languages.
	 *
	 * @param string $domain
	 */
	public function refresh_domain( $domain ) {
		$outdated_entities        = $this->domains_locales_mapper->get_from_domain(
			[ Languages::class, 'getActive' ],
			$domain
		);
		$this->entities_to_update = $this->entities_to_update->merge( $outdated_entities );
		$this->add_shutdown_action();
	}

	/**
	 * We need to refresh before the strings are deleted,
	 * otherwise we can't determine which domains to refresh.
	 *
	 * @param array $string_ids
	 */
	public function refresh_before_remove_strings( array $string_ids ) {
		$outdated_entities        = $this->domains_locales_mapper->get_from_string_ids( $string_ids );
		$this->entities_to_update = $this->entities_to_update->merge( $outdated_entities );
		$this->add_shutdown_action();
	}

	/**
	 * @param string $domain
	 * @param string $locale
	 *
	 * @return false|string
	 */
	private function update_file( $domain, $locale ) {
		/**
		 * It does not matter whether MO/JED file can be handled or not, that's why `remove` method is placed before `handles` check.
		 * If a file is not `handable` then it can still should be removed.
		 */
		$this->file_manager->remove( $domain, $locale );
		if ( $this->file_manager->handles( $domain ) ) {
			return $this->file_manager->add( $domain, $locale );
		}
		return false;
	}
}
