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

namespace Hafenstudios\Kurato\Campaign;

use Hafenstudios\Kurato\Db\SubscriptionRepository;

defined( 'ABSPATH' ) || exit;

/**
 * Reine, testbare Helfer rund um die Empfaengerauswahl einer Kampagne
 * (K4-Konzept Abschnitt 4).
 *
 * Die Kampagnen-Oberflaeche (CampaignsPage) braucht dieselbe Auswahl-Semantik wie
 * der CampaignMaterializer schon zur Anzeige der Vorschau-Zahl, bevor tatsaechlich
 * materialisiert wird. Diese Klasse haelt genau diesen Kern (Sanitisierung der
 * Listen-IDs, deduplizierte Vorschau-Zahl) frei von WP-DB-Aufrufen, damit er ohne
 * echtes WordPress pruefbar bleibt. Die endgueltige Suppression-Pruefung je Kontakt
 * bleibt beim Materializer; die Vorschau zeigt daher eine Obergrenze.
 */
final class AudienceSelector {

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

	/**
	 * Baut die `audience`-Struktur einer Kampagne aus den Rohwerten.
	 *
	 * Der segments-Schluessel ist rueckwaertskompatibel: fehlt er, entsteht ein
	 * leeres Array (K4d). Bestehende Kampagnen ohne Segmente bleiben unveraendert.
	 *
	 * @param mixed $lists    Ziel-Listen (Rohwert).
	 * @param mixed $exclude  Ausschluss-Listen (Rohwert).
	 * @param mixed $segments Ziel-Segmente (Rohwert).
	 * @return array{lists:array<int,int>,exclude_lists:array<int,int>,segments:array<int,int>}
	 */
	public static function sanitize_audience( $lists, $exclude, $segments = array() ): array {
		return array(
			'lists'         => self::sanitize_ids( $lists ),
			'exclude_lists' => self::sanitize_ids( $exclude ),
			'segments'      => self::sanitize_ids( $segments ),
		);
	}

	/**
	 * Deduplizierte Vorschau der Empfaengerzahl: Vereinigung der bestaetigten
	 * Abonnenten der Ziel-Listen minus die Ausschluss-Listen.
	 *
	 * Deckt sich mit der Materializer-Logik, ohne die Suppression je Kontakt zu
	 * pruefen (das braucht das Kontakt-Repository); die Zahl ist damit eine
	 * Obergrenze der spaeter tatsaechlich versendeten Mails.
	 *
	 * Segment-Treffer werden bereits aufgeloest (und mit den irgendwo bestaetigten
	 * Abonnenten geschnitten) uebergeben, damit dieser Helfer rein und ohne wpdb
	 * bleibt; die Aufloesung uebernimmt der Aufrufer via SegmentQuery.
	 *
	 * @param SubscriptionRepository $subs                Abo-Repository.
	 * @param array<int, int>        $lists               Ziel-Listen-IDs.
	 * @param array<int, int>        $exclude             Ausschluss-Listen-IDs.
	 * @param array<int, int>        $segment_contact_ids Bereits aufgeloeste Segment-Treffer (Kontakt-IDs).
	 * @return int
	 */
	public static function preview_count( SubscriptionRepository $subs, array $lists, array $exclude, array $segment_contact_ids = array() ): int {
		$include = self::collect( $subs, $lists );
		foreach ( $segment_contact_ids as $contact_id ) {
			$include[ (int) $contact_id ] = true;
		}
		$exclude_set = self::collect( $subs, $exclude );
		return count( array_diff_key( $include, $exclude_set ) );
	}

	/**
	 * Sammelt die bestaetigten Kontakt-IDs mehrerer Listen als Menge
	 * (contact_id => true), dedupliziert ueber Listen hinweg.
	 *
	 * @param SubscriptionRepository $subs     Abo-Repository.
	 * @param array<int, int>        $list_ids Listen-IDs.
	 * @return array<int, bool>
	 */
	private static function collect( SubscriptionRepository $subs, array $list_ids ): array {
		$set = array();
		foreach ( $list_ids as $list_id ) {
			foreach ( $subs->confirmed_contact_ids( (int) $list_id ) as $contact_id ) {
				$set[ (int) $contact_id ] = true;
			}
		}
		return $set;
	}
}
