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

namespace Hafenstudios\Kurato\Mail;

defined( 'ABSPATH' ) || exit;

/**
 * Uebersetzt einen bereits gerenderten HTML-Body (spaeter die do_blocks-Ausgabe
 * eines `hs_mail`-CPT, hier ein HTML-String) in mailclient-taugliches HTML
 * (K4-Konzept Abschnitt 8).
 *
 * Leistet drei Dinge:
 * 1. Wrappt den Inhalt in ein einspaltiges 600px-Basistemplate (Tabellen-Layout,
 *    Inline-CSS, hell/dunkel-tauglich).
 * 2. Ersetzt die Personalisierungs-Platzhalter je Empfaenger.
 * 3. Erzwingt den Pflicht-Footer: fehlt der Abmeldelink-Platzhalter im Inhalt,
 *    wird ein Standard-Footer (Absenderzeile, Abmeldelink, Praeferenzen-Link)
 *    angehaengt. Das laesst sich nicht abschalten.
 *
 * Die Ersetzungs- und Footer-Logik ist bewusst frei von WordPress-Aufrufen bis
 * auf `esc_html`/`esc_url`/`__`, damit sie ohne WP-Installation testbar bleibt.
 */
final class EmailRenderer {

	/**
	 * Der Platzhalter, dessen Fehlen den Pflicht-Footer ausloest.
	 */
	public const UNSUBSCRIBE_PLACEHOLDER = '{{unsubscribe_url}}';

	/**
	 * Platzhalter, deren Werte als URL (esc_url) statt als Text (esc_html)
	 * behandelt werden.
	 *
	 * @var array<int, string>
	 */
	private const URL_KEYS = array( 'unsubscribe_url', 'preferences_url' );

	/**
	 * Rendert den fertigen HTML-Body einer Marketing-Mail.
	 *
	 * @param string               $inner_html Bereits gerenderter Inhalt (do_blocks).
	 * @param array<string, mixed> $context    Personalisierungswerte (first_name,
	 *                                          display_name, email, unsubscribe_url,
	 *                                          preferences_url, optional sender_name,
	 *                                          sender_address).
	 * @return string
	 */
	public function render_html( string $inner_html, array $context ): string {
		$body = $inner_html;
		if ( false === strpos( $inner_html, self::UNSUBSCRIBE_PLACEHOLDER ) ) {
			$body .= $this->footer_html( $context );
		}

		$document = $this->wrap( $this->replace( $body, $context, true ), $context );

		return $document;
	}

	/**
	 * Erzeugt die Plain-Text-Alternative (Multipart).
	 *
	 * @param string               $inner_html Bereits gerenderter Inhalt.
	 * @param array<string, mixed> $context    Personalisierungswerte.
	 * @return string
	 */
	public function render_text( string $inner_html, array $context ): string {
		$needs_footer = false === strpos( $inner_html, self::UNSUBSCRIBE_PLACEHOLDER );

		// Zeilenweise Struktur erhalten, bevor die Tags fallen.
		$text = preg_replace( '#<\s*(br|/p|/div|/h[1-6]|/li|/tr)\s*/?>#i', "\n", $inner_html );
		$text = $this->strip_tags_safe( (string) $text );
		$text = $this->normalize_text( $text );

		if ( $needs_footer ) {
			$text .= "\n\n" . $this->footer_text( $context );
		}

		// Platzhalter (auch die im Footer) erst am Ende aufloesen.
		$text = $this->replace( $text, $context, false );

		return trim( $text ) . "\n";
	}

	/**
	 * Ersetzt {{key}} und {{key|Fallback}}. Der Fallback greift, wenn der Wert
	 * im Kontext leer ist.
	 *
	 * @param string               $subject Roher String mit Platzhaltern.
	 * @param array<string, mixed> $context Werte.
	 * @param bool                 $html    true: HTML-Escaping; false: Klartext.
	 * @return string
	 */
	private function replace( string $subject, array $context, bool $html ): string {
		return (string) preg_replace_callback(
			'/\{\{\s*([a-z_]+)\s*(?:\|([^}]*))?\}\}/',
			function ( array $m ) use ( $context, $html ): string {
				$key      = $m[1];
				$fallback = isset( $m[2] ) ? $m[2] : '';
				$value    = isset( $context[ $key ] ) ? (string) $context[ $key ] : '';
				if ( '' === $value ) {
					$value = $fallback;
				}
				if ( ! $html ) {
					return $value;
				}
				return in_array( $key, self::URL_KEYS, true ) ? esc_url( $value ) : esc_html( $value );
			},
			$subject
		);
	}

	/**
	 * Baut den Pflicht-Footer als HTML (nutzt weiterhin die Platzhalter, damit
	 * sie im anschliessenden replace() empfaengerbezogen aufgeloest werden).
	 *
	 * @param array<string, mixed> $context Werte (fuer die Absenderzeile).
	 * @return string
	 */
	private function footer_html( array $context ): string {
		$sender = $this->sender_line( $context );

		$intro = esc_html__( 'Sie erhalten diese Mail, weil Sie unseren Newsletter abonniert haben.', 'kurato' );
		$unsub = esc_html__( 'Abmelden', 'kurato' );
		$pref  = esc_html__( 'Einstellungen', 'kurato' );

		$out  = '<tr><td style="padding:24px 32px;border-top:1px solid #e3e3e3;font-family:Arial,Helvetica,sans-serif;font-size:12px;line-height:18px;color:#777777;">';
		if ( '' !== $sender ) {
			$out .= '<p style="margin:0 0 8px;color:#777777;">' . esc_html( $sender ) . '</p>';
		}
		$out .= '<p style="margin:0 0 8px;color:#777777;">' . $intro . '</p>';
		$out .= '<p style="margin:0;color:#777777;">';
		$out .= '<a href="' . self::UNSUBSCRIBE_PLACEHOLDER . '" style="color:#777777;text-decoration:underline;">' . $unsub . '</a>';
		$out .= ' &nbsp;|&nbsp; ';
		$out .= '<a href="{{preferences_url}}" style="color:#777777;text-decoration:underline;">' . $pref . '</a>';
		$out .= '</p>';
		$out .= '</td></tr>';

		return $out;
	}

	/**
	 * Pflicht-Footer als Text.
	 *
	 * @param array<string, mixed> $context Werte.
	 * @return string
	 */
	private function footer_text( array $context ): string {
		$lines  = array();
		$sender = $this->sender_line( $context );
		if ( '' !== $sender ) {
			$lines[] = $sender;
		}
		$lines[] = __( 'Sie erhalten diese Mail, weil Sie unseren Newsletter abonniert haben.', 'kurato' );
		$lines[] = __( 'Abmelden:', 'kurato' ) . ' ' . self::UNSUBSCRIBE_PLACEHOLDER;
		$lines[] = __( 'Einstellungen:', 'kurato' ) . ' {{preferences_url}}';

		return implode( "\n", $lines );
	}

	/**
	 * Absenderzeile aus optionalen Kontextwerten (Name, Anschrift). Fehlt beides,
	 * bleibt die Zeile leer und wird nicht ausgegeben.
	 *
	 * @param array<string, mixed> $context Werte.
	 * @return string
	 */
	private function sender_line( array $context ): string {
		$name    = isset( $context['sender_name'] ) ? trim( (string) $context['sender_name'] ) : '';
		$address = isset( $context['sender_address'] ) ? trim( (string) $context['sender_address'] ) : '';

		if ( '' !== $name && '' !== $address ) {
			return $name . ', ' . $address;
		}
		return '' !== $name ? $name : $address;
	}

	/**
	 * Legt den Inhalt in das einspaltige 600px-Basistemplate.
	 *
	 * @param string               $content Bereits ersetzter Inhalt (HTML).
	 * @param array<string, mixed> $context Werte (fuer Preheader/Titel optional).
	 * @return string
	 */
	private function wrap( string $content, array $context ): string {
		$title = isset( $context['subject'] ) ? esc_html( (string) $context['subject'] ) : '';

		$head = '<!DOCTYPE html>' . "\n"
			. '<html lang="de" xmlns="http://www.w3.org/1999/xhtml">' . "\n"
			. '<head>'
			. '<meta charset="utf-8" />'
			. '<meta name="viewport" content="width=device-width, initial-scale=1" />'
			. '<meta name="color-scheme" content="light dark" />'
			. '<meta name="supported-color-schemes" content="light dark" />'
			. '<title>' . $title . '</title>'
			. '<style>'
			. 'body{margin:0;padding:0;background:#f4f4f4;}'
			. '@media (prefers-color-scheme: dark){'
			. 'body,.hs-bg{background:#121212 !important;}'
			. '.hs-card{background:#1e1e1e !important;}'
			. '.hs-text,.hs-text p,.hs-text td{color:#eaeaea !important;}'
			. '}'
			. '</style>'
			. '</head>';

		$open = '<body style="margin:0;padding:0;background:#f4f4f4;">'
			. '<table role="presentation" class="hs-bg" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#f4f4f4;">'
			. '<tr><td align="center" style="padding:24px 12px;">'
			. '<table role="presentation" class="hs-card" width="600" cellpadding="0" cellspacing="0" border="0" style="width:600px;max-width:600px;background:#ffffff;border-radius:6px;overflow:hidden;">'
			. '<tr><td class="hs-text" style="padding:32px;font-family:Arial,Helvetica,sans-serif;font-size:16px;line-height:24px;color:#222222;">'
			. $content
			. '</td></tr>';

		$close = '</table></td></tr></table></body></html>';

		return $head . $open . $close;
	}

	/**
	 * Raeumt Text-Whitespace auf (mehrfache Leerzeilen, Zeilenraender).
	 *
	 * @param string $text Roher Text.
	 * @return string
	 */
	private function normalize_text( string $text ): string {
		$text = str_replace( "\r\n", "\n", $text );
		$text = (string) preg_replace( '/[ \t]+/', ' ', $text );
		$text = (string) preg_replace( '/ *\n */', "\n", $text );
		$text = (string) preg_replace( '/\n{3,}/', "\n\n", $text );

		return trim( $text );
	}

	/**
	 * Streift Tags, ohne von WordPress abzuhaengen. In WP existiert
	 * `wp_strip_all_tags`; im Test nutzen wir den nativen Fallback.
	 *
	 * @param string $text HTML.
	 * @return string
	 */
	private function strip_tags_safe( string $text ): string {
		if ( function_exists( 'wp_strip_all_tags' ) ) {
			return (string) wp_strip_all_tags( $text );
		}
		$text = (string) preg_replace( '@<(script|style)[^>]*?>.*?</\\1>@si', '', $text );
		return trim( (string) preg_replace( '/<[^>]*>/', '', $text ) );
	}
}
