<?php
/**
 * @package Hafenstudios\Wellenbrecher
 */

namespace Hafenstudios\Wellenbrecher\Rules;

use Hafenstudios\Wellenbrecher\Engine\Context;
use Hafenstudios\Wellenbrecher\Engine\RuleResult;

defined( 'ABSPATH' ) || exit;

/**
 * Spam-Phrasen-Sets (ASB-Muster modernisiert): ein Set trifft, wenn JEDE
 * seiner Feld-Bedingungen erfuellt ist (UND ueber Felder), wobei innerhalb
 * einer Bedingung EIN Teilstring-Treffer genuegt (ODER innerhalb der
 * Liste). Case-insensitiv, reine String-Suche (kein Nutzer-Regex in Free).
 *
 * Set-Format (siehe src/Data/phrase-sets.php): jede Bedingung nennt ein Feld
 * und eine Teilstring-Liste; ALLE Bedingungen muessen treffen.
 * array(
 *   'id'   => 'casino_de',
 *   'must' => array(
 *     array( 'field' => 'content', 'any' => array( 'casino', ... ) ),
 *     array( 'field' => 'content', 'any' => array( 'bonus', ... ) ),
 *   ),
 * )
 */
class PhraseSetRule extends AbstractRule {

	public const ID             = 'phrase_set';
	public const DEFAULT_POINTS = 25;

	/**
	 * Phrasen-Sets.
	 *
	 * @var array<int, array<string, mixed>>
	 */
	protected array $sets;

	/**
	 * @param array<int, array<string, mixed>> $sets   Phrasen-Sets.
	 * @param int                              $points Punkte.
	 */
	public function __construct( array $sets, int $points = self::DEFAULT_POINTS ) {
		parent::__construct( $points );
		$this->sets = $sets;
	}

	public function id(): string {
		return self::ID;
	}

	public function stage(): int {
		return 2;
	}

	public function evaluate( Context $context ): RuleResult {
		foreach ( $this->sets as $set ) {
			if ( ! is_array( $set['must'] ?? null ) || array() === $set['must'] ) {
				continue;
			}
			if ( $this->set_matches( $set['must'], $context ) ) {
				return $this->hit();
			}
		}
		return $this->miss();
	}

	/**
	 * Prueft ein einzelnes Set (UND ueber alle Bedingungen, ODER innerhalb
	 * einer Bedingungs-Liste).
	 *
	 * @param array<int, array<string, mixed>> $must    Bedingungen.
	 * @param Context                          $context Kontext.
	 * @return bool
	 */
	protected function set_matches( array $must, Context $context ): bool {
		foreach ( $must as $condition ) {
			if ( ! is_array( $condition ) ) {
				return false;
			}
			$haystack = mb_strtolower( $context->field( (string) ( $condition['field'] ?? '' ) ) );
			if ( '' === $haystack ) {
				return false;
			}
			$found = false;
			foreach ( (array) ( $condition['any'] ?? array() ) as $needle ) {
				$needle = mb_strtolower( trim( (string) $needle ) );
				if ( '' !== $needle && false !== mb_strpos( $haystack, $needle ) ) {
					$found = true;
					break;
				}
			}
			if ( ! $found ) {
				return false;
			}
		}
		return true;
	}
}
