<?php
/**
 * File Path: helpers/class-mbs-affiliate-rewriter.php
 *
 * My Book Showroom — Affiliate Rewriter
 *
 * Appends affiliate codes to buy link URLs at render time only.
 * Never modifies the stored URL in the database (AP-2 non-destructive).
 *
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class MBS_Affiliate_Rewriter { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/**
	 * Rewrite a buy link URL with the appropriate affiliate code.
	 *
	 * @param string $retailer_key Retailer key (amazon, bn, etc.) or null for custom stores.
	 * @return string Rewritten URL with affiliate code (or original if none applies).
	 */
	public static function rewrite( string $url, ?string $retailer_key = null ): string {
		if ( empty( $url ) || ! mbs_is_pro() ) {
			return $url;
		}

		// INSIGHTS-1: Never rewrite MBS redirect endpoints -- they are already
		// the final destination tracker and must not be double-wrapped.
		if ( false !== strpos( $url, 'mbs_click=' ) ) {
			return $url;
		}

		// Apple Books and Google Play are never rewritten — check first
		if ( in_array( $retailer_key, array( 'apple', 'google' ), true ) ) {
			return $url;
		}

		// ── Custom store: null retailer key → affiliate_url lookup (RF-012) ──
		if ( null === $retailer_key ) {
			$url_host = wp_parse_url( $url, PHP_URL_HOST );

			// Primary source (RF-012): the mbs_affiliate_stores table. Its
			// affiliate_url is pre-computed at save time with {CODE} substituted.
			$stores = MBS_Affiliate_Store_DAO::get_all();
			foreach ( $stores as $store ) {
				$aff_url    = $store['affiliate_url'] ?? '';
				$store_host = wp_parse_url( $aff_url, PHP_URL_HOST );
				if ( $store_host && $url_host === $store_host && '' !== $aff_url ) {
					// F-012: substitute any residual {CODE} placeholder before returning.
					return self::substitute_code( $aff_url, (string) ( $store['code'] ?? '' ) );
				}
			}

			// Fallback (pre-migration installs / legacy data): the
			// mbs_setting_affiliate_custom_stores option holds {url, pattern, code}
			// entries. Only consulted when the table has no rows, so a populated
			// table (the production source of truth) is never overridden.
			if ( empty( $stores ) ) {
				foreach ( self::legacy_custom_stores() as $store ) {
					$match_host = wp_parse_url( $store['url'] ?? '', PHP_URL_HOST );
					if ( $match_host && $url_host === $match_host ) {
						$pattern = (string) ( $store['pattern'] ?? '' );
						$result  = ( '' !== $pattern ) ? $pattern : (string) ( $store['url'] ?? '' );
						return self::substitute_code( $result, (string) ( $store['code'] ?? '' ) );
					}
				}
			}

			return $url;
		}

		$affiliate_code = '';

		// Known retailers — look up code from settings.
		// Look up the affiliate code from settings.
		$code = mbs_get_setting( 'affiliate', (string) $retailer_key, '' );
		if ( empty( $code ) ) {
			return $url;
		}

		// Map retailer key to query-string parameter name.
		$param_map = array(
			'amazon'      => 'tag',
			'bn'          => 'sid',
			'kobo'        => 'utm_source',
			'audible'     => 'source_code',
			'bam'         => 'aff',
			'bookshop'    => 'a_aid',
			'waterstones' => 'aff',
			'powells'     => 'affiliate',
		);

		if ( ! isset( $param_map[ $retailer_key ] ) ) {
			return $url; // Unknown retailer key — return URL unmodified.
		}

		$param = $param_map[ $retailer_key ];

		// V6-E-002: use add_query_arg() — avoids duplicate-param risk when the URL
		// already contains the affiliate query parameter.
		return add_query_arg( $param, rawurlencode( $code ), $url );
	}

	/**
	 * Substitute the {CODE} placeholder in a custom-store URL with the
	 * affiliate code (URL-encoded). An unresolved placeholder is stripped so a
	 * literal {CODE} never leaks into a rendered link.
	 *
	 * @param string $url  Custom-store URL, possibly containing {CODE}.
	 * @param string $code Affiliate code to inject.
	 * @return string
	 * @since 1.3.9
	 */
	private static function substitute_code( string $url, string $code ): string {
		if ( false === strpos( $url, '{CODE}' ) ) {
			return $url;
		}
		if ( '' !== $code ) {
			return str_replace( '{CODE}', rawurlencode( $code ), $url );
		}
		return str_replace( '{CODE}', '', $url );
	}

	/**
	 * Read legacy custom stores from the mbs_setting_affiliate_custom_stores
	 * option (JS format: {id, name, url, code, pattern, favicon_url}).
	 *
	 * @return array<int, array<string, mixed>>
	 * @since 1.3.9
	 */
	private static function legacy_custom_stores(): array {
		$raw = get_option( 'mbs_setting_affiliate_custom_stores', array() );
		if ( is_string( $raw ) ) {
			$raw = json_decode( $raw, true );
		}
		return is_array( $raw ) ? $raw : array();
	}
}
