<?php
/**
 * Canonical attribute operations.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChangesWooCommerce\CoreCompat\WooCommerce\Attributes;

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

use InvalidArgumentException;

/**
 * Applies add, remove, replace, and reorder operations to attribute schemas.
 */
final class AttributeOperationService {
	/**
	 * @param array<int,array<string,mixed>> $current Current definitions.
	 * @param array<int,array<string,mixed>> $incoming Incoming definitions.
	 * @param string[]                       $order Canonical names in desired order.
	 * @return array<int,array<string,mixed>>
	 */
	public function apply(
		array $current,
		string $mode,
		array $incoming = array(),
		array $order = array()
	): array {
		$indexed = $this->index( $current );

		switch ( $mode ) {
			case AttributeOperationMode::ADD:
				foreach ( $this->index( $incoming ) as $name => $definition ) {
					if ( isset( $indexed[ $name ] ) ) {
						throw new InvalidArgumentException( 'Attribute already exists: ' . $name );
					}

					$indexed[ $name ] = $definition;
				}
				break;

			case AttributeOperationMode::REMOVE:
				foreach ( $this->index( $incoming ) as $name => $definition ) {
					unset( $definition, $indexed[ $name ] );
				}
				break;

			case AttributeOperationMode::REPLACE:
				$indexed = $this->index( $incoming );
				break;

			case AttributeOperationMode::REORDER:
				if ( array() === $order || array_diff( array_keys( $indexed ), $order ) || array_diff( $order, array_keys( $indexed ) ) ) {
					throw new InvalidArgumentException( 'Reorder must include every attribute exactly once.' );
				}

				$reordered = array();
				foreach ( $order as $name ) {
					$reordered[ $name ] = $indexed[ $name ];
				}
				$indexed = $reordered;
				break;
		}

		$result   = array_values( $indexed );
		$position = 0;

		foreach ( $result as &$definition ) {
			$definition['position'] = $position++;
		}
		unset( $definition );

		return $result;
	}

	/**
	 * @param array<int,array<string,mixed>> $definitions Attribute definitions.
	 * @return array<string,array<string,mixed>>
	 */
	private function index( array $definitions ): array {
		$indexed = array();

		foreach ( $definitions as $definition ) {
			$name = $this->canonicalName( (string) ( $definition['name'] ?? '' ) );

			if ( '' === $name ) {
				throw new InvalidArgumentException( 'Attribute names are required.' );
			}

			if ( isset( $indexed[ $name ] ) ) {
				throw new InvalidArgumentException( 'Duplicate attribute definition: ' . $name );
			}

			$definition['name'] = $name;
			$indexed[ $name ]   = $definition;
		}

		return $indexed;
	}

	private function canonicalName( string $name ): string {
		$name = strtolower( trim( $name ) );
		$name = preg_replace( '/[^a-z0-9_-]+/', '-', $name );

		return trim( is_string( $name ) ? $name : '', '-' );
	}
}
