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

namespace Hafenstudios\Kurato\Campaign;

defined( 'ABSPATH' ) || exit;

/**
 * Reiner, testbarer Helfer fuer das Segment-Filter-JSON (K4-Konzept Abschnitt 4).
 *
 * Format v1 (Free-Umfang, Inhaber-Entscheidung 22.07.2026): flache UND-Verknuepfung
 * (match=all) mit den drei Feldern Tag, Stufe und Liste:
 *
 * { "match": "all", "conditions": [
 *   { "field": "tag",   "op": "has|has_not", "value": <tag_id> },
 *   { "field": "stage", "op": "in|not_in",   "value": [<stage_ids>] },
 *   { "field": "list",  "op": "subscribed",  "value": <list_id> }
 * ] }
 *
 * OR-Verknuepfung, verschachtelte Gruppen und das Feld last_activity sind Pro/spaeter
 * (das Format traegt sie ohne Migration, analog D14). Diese Sanitisierung ist bewusst
 * defensiv: unbekannte Felder, ungueltige Operatoren und leere Werte werden verworfen,
 * nicht als Fehler behandelt. Sie ist frei von WP-DB-Aufrufen und daher ohne echtes
 * WordPress pruefbar.
 */
final class SegmentDefinition {

	/**
	 * Erlaubte Operatoren je Feld (v1).
	 *
	 * @var array<string, array<int, string>>
	 */
	private const OPERATORS = array(
		'tag'   => array( 'has', 'has_not' ),
		'stage' => array( 'in', 'not_in' ),
		'list'  => array( 'subscribed' ),
	);

	/**
	 * Normalisiert eine rohe (dekodierte) Definition auf das v1-Format.
	 *
	 * Liefert immer die volle Struktur (match + conditions), auch bei Muell-Input.
	 *
	 * @param mixed $raw Rohwert (z.B. json_decode-Ergebnis oder POST-Array).
	 * @return array{match:string,conditions:array<int,array<string,mixed>>}
	 */
	public static function sanitize( $raw ): array {
		$raw  = is_array( $raw ) ? $raw : array();
		$list = ( isset( $raw['conditions'] ) && is_array( $raw['conditions'] ) ) ? $raw['conditions'] : array();

		$conditions = array();
		foreach ( $list as $cond ) {
			if ( ! is_array( $cond ) ) {
				continue;
			}
			$clean = self::sanitize_condition(
				isset( $cond['field'] ) ? (string) $cond['field'] : '',
				isset( $cond['op'] ) ? (string) $cond['op'] : '',
				$cond['value'] ?? null
			);
			if ( null !== $clean ) {
				$conditions[] = $clean;
			}
		}

		// v1 kennt nur die UND-Verknuepfung; OR ist Pro. Wir erzwingen match=all,
		// damit gespeicherte Definition und Auswertung nie auseinanderlaufen.
		return array(
			'match'      => 'all',
			'conditions' => $conditions,
		);
	}

	/**
	 * Kodiert eine rohe Definition als (sanitisiertes) JSON fuer die Ablage.
	 *
	 * @param mixed $raw Rohwert.
	 * @return string
	 */
	public static function encode( $raw ): string {
		return (string) wp_json_encode( self::sanitize( $raw ) );
	}

	/**
	 * Dekodiert und sanitisiert ein gespeichertes Definition-JSON.
	 *
	 * @param string $json Roh-JSON.
	 * @return array{match:string,conditions:array<int,array<string,mixed>>}
	 */
	public static function decode( string $json ): array {
		$data = '' !== $json ? json_decode( $json, true ) : array();
		return self::sanitize( $data );
	}

	/**
	 * Prueft/normalisiert eine einzelne Bedingung. Gibt null zurueck, wenn sie
	 * verworfen werden muss (unbekanntes Feld, ungueltiger Operator/Wert).
	 *
	 * @param string $field Feldname.
	 * @param string $op    Operator.
	 * @param mixed  $value Wert.
	 * @return array<string, mixed>|null
	 */
	private static function sanitize_condition( string $field, string $op, $value ): ?array {
		if ( ! isset( self::OPERATORS[ $field ] ) || ! in_array( $op, self::OPERATORS[ $field ], true ) ) {
			return null;
		}

		switch ( $field ) {
			case 'tag':
			case 'list':
				$id = absint( $value );
				if ( $id <= 0 ) {
					return null;
				}
				return array(
					'field' => $field,
					'op'    => $op,
					'value' => $id,
				);

			case 'stage':
				$ids = self::int_list( $value );
				if ( array() === $ids ) {
					return null;
				}
				return array(
					'field' => 'stage',
					'op'    => $op,
					'value' => $ids,
				);
		}

		return null;
	}

	/**
	 * Normalisiert einen Wert zu einer Liste eindeutiger, positiver Integer-IDs.
	 *
	 * @param mixed $value Rohwert.
	 * @return array<int, int>
	 */
	private static function int_list( $value ): array {
		if ( ! is_array( $value ) ) {
			$value = array( $value );
		}
		$out = array();
		foreach ( $value as $item ) {
			$id = absint( $item );
			if ( $id > 0 ) {
				$out[ $id ] = $id;
			}
		}
		return array_values( $out );
	}
}
