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

namespace Hafenstudios\Kurato\Campaign;

defined( 'ABSPATH' ) || exit;

/**
 * Link-Registry gegen Open-Redirects (K4-Konzept Abschnitt 7, K4e-2).
 *
 * Beim Umschreiben der Links einer Kampagne (nur bei doppeltem Consent) werden
 * die Original-Ziel-URLs als indexierte Liste JE KAMPAGNE hinterlegt - genau
 * einmal, als Post-Meta am Inhalts-Post (`hs_mail`), nicht je Empfaenger
 * dupliziert. Die Link-Ziele sind fuer alle Empfaenger identisch (der Inhalt ist
 * derselbe), deshalb genuegt die einmalige, stabil indexierte Ablage: der erste
 * gerenderte Empfaenger fuellt sie, alle weiteren treffen dieselben Indizes.
 *
 * Der Click-Redirect loest `token -> (campaign, link_index) -> URL` NUR aus
 * dieser Registry auf und leitet 302 ausschliesslich auf eine dort hinterlegte
 * URL weiter - nie auf einen frei im Request stehenden Wert (kein Open-Redirect).
 *
 * Nicht final, damit Tests die Speicher-Methoden per Test-Double ueberschreiben
 * koennen.
 */
class LinkRegistry {

	/**
	 * Post-Meta-Schluessel der Registry am hs_mail-Post.
	 */
	public const META_KEY = '_kurato_link_registry';

	/**
	 * Liefert den (0-basierten) Index einer URL innerhalb der Registry des
	 * Kampagnen-Posts und legt die URL an, falls sie noch nicht enthalten ist.
	 *
	 * @param int    $post_id Inhalts-Post der Kampagne (hs_mail).
	 * @param string $url     Absolute Ziel-URL.
	 * @return int Index (>= 0), oder -1 bei ungueltigem Post/URL.
	 */
	public function index_for( int $post_id, string $url ): int {
		$url = trim( $url );
		if ( $post_id <= 0 || '' === $url ) {
			return -1;
		}

		$map   = $this->load( $post_id );
		$index = array_search( $url, $map, true );
		if ( false !== $index ) {
			return (int) $index;
		}

		$map[] = $url;
		$this->save( $post_id, $map );

		return count( $map ) - 1;
	}

	/**
	 * Liefert die hinterlegte URL zu einem Index, oder '' wenn nicht vorhanden.
	 * Grundlage der Open-Redirect-sicheren Aufloesung im Click-Endpoint.
	 *
	 * @param int $post_id Inhalts-Post der Kampagne (hs_mail).
	 * @param int $index   Registry-Index.
	 * @return string
	 */
	public function url_for( int $post_id, int $index ): string {
		if ( $post_id <= 0 || $index < 0 ) {
			return '';
		}
		$map = $this->load( $post_id );
		return isset( $map[ $index ] ) ? (string) $map[ $index ] : '';
	}

	/**
	 * Laedt die indexierte URL-Liste eines Posts.
	 *
	 * @param int $post_id Post-ID.
	 * @return array<int, string>
	 */
	protected function load( int $post_id ): array {
		$stored = get_post_meta( $post_id, self::META_KEY, true );
		if ( ! is_array( $stored ) ) {
			return array();
		}
		// Reindizieren auf luecklose 0..n Ganzzahl-Schluessel (Indizes sind stabil,
		// weil nur angehaengt, nie geloescht wird).
		return array_values( array_map( 'strval', $stored ) );
	}

	/**
	 * Speichert die indexierte URL-Liste eines Posts.
	 *
	 * @param int                $post_id Post-ID.
	 * @param array<int, string> $map     URL-Liste.
	 * @return void
	 */
	protected function save( int $post_id, array $map ): void {
		update_post_meta( $post_id, self::META_KEY, array_values( $map ) );
	}
}
