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

namespace Hafenstudios\Leadlotse\Fields;

defined( 'ABSPATH' ) || exit;

/**
 * Conditional Logic: "Feld zeigen, wenn Feld X den Wert Y hat".
 *
 * Datenstruktur je Feld (Schluessel `conditions`):
 *
 * {
 *   "enabled": true,
 *   "action":  "show" | "hide",
 *   "logic":   "and" | "or",
 *   "rules": [
 *     { "field": "feld_id", "operator": "equals", "value": "Y" }
 *   ]
 * }
 *
 * Operatoren: equals, not_equals, contains, not_empty, empty.
 *
 * Die Auswertung laeuft serverseitig beim Submit (Sicherheit: verborgene Felder
 * werden nie validiert und nie gespeichert) und clientseitig im Renderer
 * (Etappe 3) mit derselben Struktur.
 */
final class ConditionalLogic {

	public const OPERATORS = array( 'equals', 'not_equals', 'contains', 'not_empty', 'empty' );

	/**
	 * Ist das Feld bei den gegebenen Eingabewerten sichtbar?
	 *
	 * @param array<string, mixed> $field  Felddefinition.
	 * @param array<string, mixed> $values Eingereichte Werte (feld_id => wert).
	 * @return bool
	 */
	public function is_visible( array $field, array $values ): bool {
		$conditions = $field['conditions'] ?? null;
		if ( ! is_array( $conditions ) || empty( $conditions['enabled'] ) ) {
			return true;
		}

		$rules = is_array( $conditions['rules'] ?? null ) ? $conditions['rules'] : array();
		if ( array() === $rules ) {
			return true;
		}

		$logic   = 'or' === strtolower( (string) ( $conditions['logic'] ?? 'and' ) ) ? 'or' : 'and';
		$action  = 'hide' === strtolower( (string) ( $conditions['action'] ?? 'show' ) ) ? 'hide' : 'show';
		$results = array();

		foreach ( $rules as $rule ) {
			if ( ! is_array( $rule ) ) {
				continue;
			}
			$results[] = $this->matches( $rule, $values );
		}

		if ( array() === $results ) {
			return true;
		}

		$matched = 'and' === $logic ? ! in_array( false, $results, true ) : in_array( true, $results, true );

		return 'show' === $action ? $matched : ! $matched;
	}

	/**
	 * Normalisiert eine Conditions-Struktur (fuer das Definitions-Schema).
	 *
	 * @param mixed $conditions Rohdaten.
	 * @return array<string, mixed>
	 */
	public function sanitize( $conditions ): array {
		if ( ! is_array( $conditions ) ) {
			// Immer die vollstaendige Struktur liefern, damit Konsumenten sich auf
			// 'rules' verlassen koennen (sonst "Undefined array key" im Schema).
			return array(
				'enabled' => false,
				'action'  => 'show',
				'logic'   => 'and',
				'rules'   => array(),
			);
		}

		$rules = array();
		foreach ( (array) ( $conditions['rules'] ?? array() ) as $rule ) {
			if ( ! is_array( $rule ) ) {
				continue;
			}
			$field    = sanitize_key( (string) ( $rule['field'] ?? '' ) );
			$operator = strtolower( sanitize_key( (string) ( $rule['operator'] ?? 'equals' ) ) );
			if ( '' === $field || ! in_array( $operator, self::OPERATORS, true ) ) {
				continue;
			}
			$rules[] = array(
				'field'    => $field,
				'operator' => $operator,
				'value'    => sanitize_text_field( (string) ( $rule['value'] ?? '' ) ),
			);
		}

		return array(
			'enabled' => ! empty( $conditions['enabled'] ) && array() !== $rules,
			'action'  => 'hide' === strtolower( (string) ( $conditions['action'] ?? 'show' ) ) ? 'hide' : 'show',
			'logic'   => 'or' === strtolower( (string) ( $conditions['logic'] ?? 'and' ) ) ? 'or' : 'and',
			'rules'   => $rules,
		);
	}

	/**
	 * Wertet eine einzelne Regel aus.
	 *
	 * @param array<string, mixed> $rule   Regel.
	 * @param array<string, mixed> $values Eingereichte Werte.
	 * @return bool
	 */
	private function matches( array $rule, array $values ): bool {
		$field    = (string) ( $rule['field'] ?? '' );
		$operator = (string) ( $rule['operator'] ?? 'equals' );
		$expected = (string) ( $rule['value'] ?? '' );
		$actual   = $values[ $field ] ?? '';
		$actual   = is_array( $actual ) ? implode( ', ', array_map( 'strval', $actual ) ) : (string) $actual;

		switch ( $operator ) {
			case 'equals':
				return $actual === $expected;
			case 'not_equals':
				return $actual !== $expected;
			case 'contains':
				return '' !== $expected && false !== mb_stripos( $actual, $expected );
			case 'not_empty':
				return '' !== trim( $actual );
			case 'empty':
				return '' === trim( $actual );
			default:
				return false;
		}
	}
}
