<?php
/**
 * Product taxonomy term operations.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChangesWooCommerce\CoreCompat\Discovery;

// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are escaped at UI boundaries.

use ScheduledChanges\Provider\OperationResult;
use ScheduledChangesWooCommerce\CoreCompat\WooCommerce\ProductSynchronizer;
use Throwable;

/**
 * Applies add, remove, replace, and valid term-order operations.
 */
final class TaxonomyTermService {
	private TaxonomyCatalog $taxonomies;
	private ProductSynchronizer $synchronizer;
	public function __construct( ?TaxonomyCatalog $taxonomies = null, ?ProductSynchronizer $synchronizer = null ) {
		$taxonomies       ??= new TaxonomyCatalog();
		$synchronizer     ??= new ProductSynchronizer();
		$this->taxonomies   = $taxonomies;
		$this->synchronizer = $synchronizer;
	}

	/**
	 * @param int[] $term_ids Term IDs.
	 */
	public function apply(
		int $product_id,
		string $taxonomy_name,
		string $mode,
		array $term_ids
	): OperationResult {
		$taxonomy = $this->taxonomies->productTaxonomies()[ $taxonomy_name ] ?? null;

		if ( ! $taxonomy instanceof \WP_Taxonomy ) {
			return OperationResult::failure( 'taxonomy_not_supported', 'Product taxonomy is not discoverable.' );
		}

		$product = wc_get_product( $product_id );
		if ( ! $product instanceof \WC_Product || $product instanceof \WC_Product_Variation ) {
			return OperationResult::failure( 'product_not_found', 'Taxonomy operations require a parent product.' );
		}

		if ( ! current_user_can( $taxonomy->cap->assign_terms ) ) {
			return OperationResult::failure( 'permission_denied', 'The current user cannot assign terms in this taxonomy.' );
		}

		$term_ids = array_values( array_unique( array_map( 'intval', $term_ids ) ) );

		foreach ( $term_ids as $term_id ) {
			if ( $term_id <= 0 || ! term_exists( $term_id, $taxonomy_name ) ) {
				return OperationResult::failure( 'term_not_found', 'A requested taxonomy term does not exist.' );
			}
		}

		$current = $this->termIds( $product_id, $taxonomy );

		switch ( $mode ) {
			case TaxonomyOperationMode::ADD:
				$next = array_values( array_unique( array_merge( $current, $term_ids ) ) );
				break;
			case TaxonomyOperationMode::REMOVE:
				$next = array_values( array_diff( $current, $term_ids ) );
				break;
			case TaxonomyOperationMode::REPLACE:
				$next = $term_ids;
				break;
			case TaxonomyOperationMode::REORDER:
				$next = $term_ids;
				break;
			default:
				return OperationResult::failure( 'taxonomy_mode_invalid', 'The requested taxonomy operation mode is not supported.' );
		}

		if ( ! (bool) $taxonomy->sort ) {
			sort( $next );
		}

		if ( TaxonomyOperationMode::REORDER === $mode ) {
			if ( ! (bool) $taxonomy->sort ) {
				return OperationResult::failure( 'taxonomy_not_sortable', 'This taxonomy does not support relationship term order.' );
			}

			$expected = $current;
			$provided = $next;
			sort( $expected );
			sort( $provided );

			if ( $expected !== $provided ) {
				return OperationResult::failure( 'invalid_term_order', 'Reorder must contain every assigned term exactly once.' );
			}
		}

		if ( $current === $next ) {
			return OperationResult::success( 'Taxonomy terms are already applied.', false );
		}

		try {
			$result = wp_set_object_terms( $product_id, $next, $taxonomy_name, false );

			if ( is_wp_error( $result ) ) {
				throw new \RuntimeException( $result->get_error_message() );
			}

			$this->synchronizer->synchronize( $product );
			$after = $this->termIds( $product_id, $taxonomy );

			if ( $after !== $next ) {
				return OperationResult::failure( 'verification_failed', 'Taxonomy term readback did not match.' );
			}

			return OperationResult::success( 'Taxonomy terms saved and verified.' );
		} catch ( Throwable $throwable ) {
			return OperationResult::failure( 'taxonomy_write_failed', $throwable->getMessage() );
		}
	}

	/**
	 * @return int[]
	 */
	public function termIds( int $product_id, \WP_Taxonomy $taxonomy ): array {
		$terms = wp_get_object_terms(
			$product_id,
			$taxonomy->name,
			array(
				'fields'  => 'ids',
				'orderby' => (bool) $taxonomy->sort ? 'term_order' : 'term_id',
				'order'   => 'ASC',
			)
		);

		if ( is_wp_error( $terms ) ) {
			throw new \RuntimeException( $terms->get_error_message() );
		}

		return array_values( array_map( 'intval', $terms ) );
	}
}
