<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Pro-Site-Override für beliebige Plugin-Strings.
 *
 * Hängt sich in die WP-eigenen gettext-Filter ein (gleiche Mechanik wie WooCommerce
 * String-Translation). Wenn ein msgid eine Override-Eintragung in der Option
 * `ccgal_text_overrides` hat, wird diese statt der .mo-Übersetzung zurückgegeben.
 *
 * Lookup ist O(1), die Override-Tabelle wird einmal pro Request gecached.
 * Filter feuert NUR für die Text-Domain `customer-cloud-gallery` — andere Plugins/Themes
 * bleiben unberührt.
 */
class CCGAL_Text_Overrides {

	const OPTION_KEY = 'ccgal_text_overrides';
	const TEXT_DOMAIN = 'customer-cloud-gallery';

	/** @var array<string,string>|null */
	private static $cache = null;

	public static function init() {
		add_filter( 'gettext',                array( __CLASS__, 'filter_gettext' ),                20, 3 );
		add_filter( 'gettext_with_context',   array( __CLASS__, 'filter_gettext_with_context' ),   20, 4 );
		add_filter( 'ngettext',               array( __CLASS__, 'filter_ngettext' ),               20, 5 );
		add_filter( 'ngettext_with_context',  array( __CLASS__, 'filter_ngettext_with_context' ),  20, 6 );
	}

	/**
	 * @return array<string,string>
	 */
	public static function get_overrides() {
		if ( null === self::$cache ) {
			$stored = get_option( self::OPTION_KEY, array() );
			self::$cache = is_array( $stored ) ? $stored : array();
		}
		return self::$cache;
	}

	public static function get_override_for( $msgid ) {
		$overrides = self::get_overrides();
		$msgid     = (string) $msgid;
		if ( isset( $overrides[ $msgid ] ) && '' !== $overrides[ $msgid ] ) {
			return $overrides[ $msgid ];
		}
		return null;
	}

	/**
	 * Liefert den Default-Wert für einen msgid, OHNE durch unseren eigenen
	 * Override-Filter zu laufen. Ergibt also entweder die .mo-Übersetzung der
	 * aktuellen WP-Sprache, oder — falls keine Übersetzung vorliegt — den
	 * englischen Source-String.
	 *
	 * Wird im Settings-UI gebraucht: dort soll der „echte" Default angezeigt
	 * werden, nicht der bereits gespeicherte Override (sonst sieht der Nutzer
	 * den Override sowohl im Input als auch im Placeholder/Hinweistext und
	 * versteht nicht, was beim Leeren passiert).
	 */
	public static function get_untranslated_default( $msgid ) {
		// Eigene Filter temporär abkoppeln, damit translate() nur die .mo
		// (oder den englischen Source-String) zurückliefert.
		remove_filter( 'gettext',              array( __CLASS__, 'filter_gettext' ),              20 );
		remove_filter( 'gettext_with_context', array( __CLASS__, 'filter_gettext_with_context' ), 20 );
		// phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.LowLevelTranslationFunction -- This is a meta-translation lookup. $msgid is one of this plugin's own static msgids that exists in customer-cloud-gallery.pot; the override UI must look it up dynamically because the user picks WHICH msgid to override at runtime. The msgids are sourced from get_curated_groups() (literal arrays) and the previously-saved ccgal_text_overrides option, so translate() is appropriate here despite being a low-level function.
		$value = translate( (string) $msgid, 'customer-cloud-gallery' );
		add_filter( 'gettext',              array( __CLASS__, 'filter_gettext' ),              20, 3 );
		add_filter( 'gettext_with_context', array( __CLASS__, 'filter_gettext_with_context' ), 20, 4 );
		return $value;
	}

	public static function filter_gettext( $translation, $text, $domain ) {
		if ( self::TEXT_DOMAIN !== $domain ) {
			return $translation;
		}
		$override = self::get_override_for( $text );
		return null !== $override ? $override : $translation;
	}

	public static function filter_gettext_with_context( $translation, $text, $context, $domain ) {
		if ( self::TEXT_DOMAIN !== $domain ) {
			return $translation;
		}
		$override = self::get_override_for( $text );
		return null !== $override ? $override : $translation;
	}

	public static function filter_ngettext( $translation, $single, $plural, $number, $domain ) {
		if ( self::TEXT_DOMAIN !== $domain ) {
			return $translation;
		}
		// Singular und Plural separat überschreibbar.
		$key      = ( 1 == $number ) ? $single : $plural; // phpcs:ignore WordPress.PHP.StrictComparisons
		$override = self::get_override_for( $key );
		return null !== $override ? $override : $translation;
	}

	public static function filter_ngettext_with_context( $translation, $single, $plural, $number, $context, $domain ) {
		if ( self::TEXT_DOMAIN !== $domain ) {
			return $translation;
		}
		$key      = ( 1 == $number ) ? $single : $plural; // phpcs:ignore WordPress.PHP.StrictComparisons
		$override = self::get_override_for( $key );
		return null !== $override ? $override : $translation;
	}

	/**
	 * Speichert die Overrides. Erwartet ein Array msgid => custom-string.
	 * Leere/whitespace-only Werte werden NICHT gespeichert (gibt Default frei).
	 *
	 * Liefert ein Validierungs-Resultat zurück:
	 *   array(
	 *     'saved'    => int  Anzahl gespeicherter Overrides,
	 *     'cleared'  => int  Anzahl entfernter (leerer) Overrides,
	 *     'warnings' => array<string,string>  msgid => Warnung (z. B. fehlender Platzhalter),
	 *   )
	 */
	public static function save_overrides( array $input ) {
		$clean    = array();
		$warnings = array();

		foreach ( $input as $msgid => $value ) {
			$msgid = (string) $msgid;
			$value = is_string( $value ) ? trim( wp_kses_post( wp_unslash( $value ) ) ) : '';

			if ( '' === $value ) {
				continue;
			}

			// Wenn der eingegebene Wert exakt dem aktuellen Default (.mo / Source-String)
			// entspricht, gibt es nichts zu überschreiben — als „kein Override" behandeln.
			// Spart eine DB-Zeile und erzeugt keine unnötigen sprintf-Pfade.
			$default = self::get_untranslated_default( $msgid );
			if ( $value === $default ) {
				continue;
			}

			// Platzhalter-Check: wenn der originale msgid Platzhalter (%s, %d, %1$s, …) enthält,
			// muss der Override sie auch enthalten — sonst werden später bei sprintf falsche
			// oder leere Inhalte gerendert. Wir warnen, speichern aber trotzdem
			// (manche Nutzer wollen den Wert bewusst „hart" überschreiben).
			$expected = self::extract_placeholders( $msgid );
			$got      = self::extract_placeholders( $value );
			$missing  = array_diff( $expected, $got );
			if ( ! empty( $missing ) ) {
				$warnings[ $msgid ] = sprintf(
					/* translators: 1: msgid, 2: comma-separated list of missing placeholders. */
					__( 'Custom text for "%1$s" is missing placeholder(s): %2$s — saved anyway, but the affected output may break.', 'customer-cloud-gallery' ),
					$msgid,
					implode( ', ', $missing )
				);
			}

			$clean[ $msgid ] = $value;
		}

		$previous = self::get_overrides();
		$cleared  = count( array_diff_key( $previous, $clean ) );

		update_option( self::OPTION_KEY, $clean, false );
		self::$cache = $clean;

		return array(
			'saved'    => count( $clean ),
			'cleared'  => $cleared,
			'warnings' => $warnings,
		);
	}

	private static function extract_placeholders( $text ) {
		$found = array();
		if ( preg_match_all( '/%(?:\d+\$)?[+-]?[0-9]*\.?[0-9]*[a-zA-Z%]/', (string) $text, $m ) ) {
			foreach ( $m[0] as $p ) {
				if ( '%%' === $p ) {
					continue;
				}
				$found[] = $p;
			}
		}
		return $found;
	}

	/**
	 * Kuratierte Liste der wichtigsten überschreibbaren Strings, gruppiert nach Bereich.
	 * Wird im Settings-UI als Tabelle gerendert. Andere Strings können über die
	 * „Erweitert"-Sektion (Freitext) überschrieben werden.
	 *
	 * @return array<string,array{label:string,strings:array<int,string>}>
	 */
	public static function get_curated_groups() {
		return array(
			'gallery_toolbar' => array(
				'label'   => __( 'Gallery — toolbar', 'customer-cloud-gallery' ),
				'strings' => array(
					'Download all images',
					'Show only favorites',
					'Show all',
					'Download favorites',
					'Download favorites (%d)',
					'Order prints from selection',
					'Download selection',
					'Clear selection',
					'Load more',
					'All images loaded',
					'Loading images…',
					'No favorites marked yet.',
					'This gallery runs without cookies. Favorites are disabled.',
				),
			),
			'gallery_lightbox' => array(
				'label'   => __( 'Gallery — lightbox', 'customer-cloud-gallery' ),
				'strings' => array(
					'Add to favorites',
					'Remove from favorites',
					'Add to selection',
					'Remove from selection',
					'Download this image',
					'Close',
					'Previous image',
					'Next image',
					'Play video',
				),
			),
			'print_form' => array(
				'label'   => __( 'Print order — form', 'customer-cloud-gallery' ),
				'strings' => array(
					'Choose format …',
					'Quantity',
					'Paper',
					'Subtotal',
					'Subtotal:',
					'Total',
					'Shipping',
					'Free shipping',
					'Free shipping from %s',
					'Your details',
					'Name *',
					'Email *',
					'Phone (optional)',
					'Shipping address *',
					'Message (optional)',
					'I have read the %s and agree to the processing of my data.',
					'privacy policy',
					'Choose payment method',
					'PayPal',
					'Bank transfer (advance payment)',
					'Place order',
					'Sending…',
					'← Back to gallery',
				),
			),
			'print_confirmation' => array(
				'label'   => __( 'Print order — confirmation', 'customer-cloud-gallery' ),
				'strings' => array(
					'Order received!',
					'Thank you for your order. You will receive a confirmation email shortly.',
					'Order number',
					'Pay now via PayPal',
					'Bank details',
					'Account holder',
					'IBAN',
					'BIC',
					'Bank',
					'Reference',
					'Amount',
					'Copy',
					'Copied!',
				),
			),
			'print_validation' => array(
				'label'   => __( 'Print order — validation messages', 'customer-cloud-gallery' ),
				'strings' => array(
					'Please select a format for every image.',
					'Please enter your name.',
					'Please enter a valid email address.',
					'Please enter a shipping address.',
					'Please confirm the privacy policy.',
					'Please confirm all required fields.',
					'An error occurred. Please reload the page and try again.',
				),
			),
		);
	}
}
