<?php
/**
 * Data-driven generic consent adapter presets.
 *
 * @package Carticy\Conversions\Consent
 */

declare(strict_types=1);

namespace Carticy\Conversions\Consent;

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

/**
 * Keeps CMP coverage in data rows instead of per-vendor adapter classes.
 */
final class GenericConsentPresetRegistry {

	/**
	 * Preset rows for popular CMPs that do not always feed WP Consent API.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	public function presets(): array {
		$presets = array(
			array(
				'source'                 => 'cookiebot-cookie',
				'source_version'         => 'preset-cookiebot-raw-cookie',
				'cookie_name'            => 'CookieConsent',
				// Cookiebot cookies enumerate every category; an unmatched
				// category is a real "not consented".
				'missing_category_state' => 'denied',
				// method:'implied' means no affirmative user act - such a
				// reading must not count as an explicit decision.
				'explicit_pattern'       => '/method:\s*\'?"?explicit/i',
				'categories'             => array(
					'marketing'  => array(
						'grant_pattern' => '/(?:^|[,{;\s])"?marketing"?\s*[:=]\s*(?:true|1|granted|allow|yes)/i',
						'deny_pattern'  => '/(?:^|[,{;\s])"?marketing"?\s*[:=]\s*(?:false|0|denied|deny|no)/i',
					),
					'statistics' => array(
						'grant_pattern' => '/(?:^|[,{;\s])"?statistics"?\s*[:=]\s*(?:true|1|granted|allow|yes)/i',
						'deny_pattern'  => '/(?:^|[,{;\s])"?statistics"?\s*[:=]\s*(?:false|0|denied|deny|no)/i',
					),
				),
				'services'               => array(
					'meta'       => 'marketing',
					'google-ads' => 'marketing',
					'ga4'        => 'statistics',
				),
				'sample'                 => '{stamp:%27SANITIZED%27%2Cnecessary:true%2Cpreferences:true%2Cstatistics:true%2Cmarketing:true%2Cmethod:%27explicit%27%2Cver:19%2Cutc:1780919886494%2Cregion:%27tw%27}',
			),
			array(
				'source'                 => 'onetrust-optanon-cookie',
				'source_version'         => 'preset-onetrust-optanonconsent',
				'cookie_name'            => 'OptanonConsent',
				// OptanonConsent's groups list is exhaustive: a group absent
				// from the list was not consented.
				'missing_category_state' => 'denied',
				'categories'             => array(
					'marketing'  => array(
						'grant_pattern' => '/(?:groups=|%2C|,|&)(?:C0004|4):1/i',
						'deny_pattern'  => '/(?:groups=|%2C|,|&)(?:C0004|4):0/i',
					),
					'statistics' => array(
						'grant_pattern' => '/(?:groups=|%2C|,|&)(?:C0002|2):1/i',
						'deny_pattern'  => '/(?:groups=|%2C|,|&)(?:C0002|2):0/i',
					),
				),
				'services'               => array(
					'meta'       => 'marketing',
					'google-ads' => 'marketing',
					'ga4'        => 'statistics',
				),
				'sample'                 => 'isGpcEnabled=0&version=202602.1.0&groups=1%3A1%2C3%3A1%2C2%3A1&vs=S1%3A1%2CS842%3A1%2CS57%3A1%2CS15%3A1%2CS12%3A1',
			),
		);

		$filtered = apply_filters( 'carticy_conversions_generic_consent_presets', $presets );

		return is_array( $filtered ) ? $filtered : $presets;
	}

	/**
	 * Browser-side generic adapters.
	 *
	 * @return array<int, array<string, mixed>>
	 */
	public function browser_adapters(): array {
		$adapters = array(
			array(
				'source'                 => 'cookiebot-js-global',
				'source_version'         => 'preset-cookiebot-js-api',
				'global_path'            => 'Cookiebot.consent',
				// Cookiebot.consent enumerates every category; implied-mode
				// consent (method !== 'explicit') is not an affirmative act.
				'missing_category_state' => 'denied',
				'explicit_pattern'       => '"?method"?\\s*:\\s*"?explicit',
				'events'                 => array( 'CookiebotOnConsentReady', 'CookiebotOnAccept', 'CookiebotOnDecline', 'CookiebotOnLoad' ),
				'data_layer_events'      => array( 'cookie_consent_update', 'cookie_consent_statistics', 'cookie_consent_marketing' ),
				'categories'             => array(
					'marketing'  => array(
						'grant_pattern' => '"?marketing"?\\s*:\\s*(?:true|1|"true"|"granted")',
						'deny_pattern'  => '"?marketing"?\\s*:\\s*(?:false|0|"false"|"denied")',
					),
					'statistics' => array(
						'grant_pattern' => '"?statistics"?\\s*:\\s*(?:true|1|"true"|"granted")',
						'deny_pattern'  => '"?statistics"?\\s*:\\s*(?:false|0|"false"|"denied")',
					),
				),
				'services'               => array(
					'meta'       => 'marketing',
					'google-ads' => 'marketing',
					'ga4'        => 'statistics',
				),
			),
			array(
				'source'                 => 'onetrust-active-groups',
				'source_version'         => 'preset-onetrust-js-global',
				'global_path'            => 'OnetrustActiveGroups',
				// The active-groups string is exhaustive: a group absent from
				// it was not consented.
				'missing_category_state' => 'denied',
				'events'                 => array( 'OneTrustGroupsUpdated', 'OTConsentApplied' ),
				'data_layer_events'      => array( 'OneTrustGroupsUpdated', 'OneTrustLoaded', 'OptanonLoaded' ),
				'categories'             => array(
					'marketing'  => array(
						'grant_pattern' => '(?:^|,)(?:C0004|4)(?:,|$)',
					),
					'statistics' => array(
						'grant_pattern' => '(?:^|,)(?:C0002|2)(?:,|$)',
					),
				),
				'services'               => array(
					'meta'       => 'marketing',
					'google-ads' => 'marketing',
					'ga4'        => 'statistics',
				),
			),
			array(
				'source'            => 'termly-js-api',
				'source_version'    => 'preset-termly-get-consent-state',
				'global_path'       => 'Termly.getConsentState',
				'call'              => true,
				'subscriptions'     => array(
					array(
						'path'  => 'Termly.on',
						'event' => 'initialized',
					),
					array(
						'path'  => 'Termly.on',
						'event' => 'consent',
					),
				),
				'data_layer_events' => array( 'userPrefUpdate', 'Termly.consentSaveDone' ),
				'categories'        => array(
					'marketing'  => array(
						'grant_pattern' => '"?advertising"?\\s*:\\s*(?:true|1|"true"|"granted")',
						'deny_pattern'  => '"?advertising"?\\s*:\\s*(?:false|0|"false"|"denied")',
					),
					'statistics' => array(
						'grant_pattern' => '"?analytics"?\\s*:\\s*(?:true|1|"true"|"granted")',
						'deny_pattern'  => '"?analytics"?\\s*:\\s*(?:false|0|"false"|"denied")',
					),
				),
				'services'          => array(
					'meta'       => 'marketing',
					'google-ads' => 'marketing',
					'ga4'        => 'statistics',
				),
			),
		);

		$filtered = apply_filters( 'carticy_conversions_generic_consent_browser_adapters', $adapters );

		return is_array( $filtered ) ? $this->normalize_browser_adapters( $filtered ) : $this->normalize_browser_adapters( $adapters );
	}

	/**
	 * Normalize PHP row keys to the browser bridge contract.
	 *
	 * @param array<int, array<string, mixed>> $adapters Adapter rows.
	 * @return array<int, array<string, mixed>>
	 */
	private function normalize_browser_adapters( array $adapters ): array {
		$normalized = array();

		foreach ( $adapters as $adapter ) {
			if ( ! is_array( $adapter ) || empty( $adapter['source'] ) || empty( $adapter['global_path'] ) ) {
				continue;
			}

			$normalized[] = array(
				'source'               => (string) $adapter['source'],
				'sourceVersion'        => (string) ( $adapter['source_version'] ?? 'generic-js-global' ),
				'globalPath'           => (string) $adapter['global_path'],
				'call'                 => (bool) ( $adapter['call'] ?? false ),
				'events'               => is_array( $adapter['events'] ?? null ) ? array_values( $adapter['events'] ) : array(),
				'dataLayerEvents'      => is_array( $adapter['data_layer_events'] ?? null ) ? array_values( $adapter['data_layer_events'] ) : array(),
				'subscriptions'        => is_array( $adapter['subscriptions'] ?? null ) ? array_values( $adapter['subscriptions'] ) : array(),
				'categories'           => is_array( $adapter['categories'] ?? null ) ? $adapter['categories'] : array(),
				'services'             => is_array( $adapter['services'] ?? null ) ? $adapter['services'] : array(),
				'missingCategoryState' => (string) ( $adapter['missing_category_state'] ?? 'unset' ),
				'explicitPattern'      => (string) ( $adapter['explicit_pattern'] ?? '' ),
			);
		}

		return $normalized;
	}
}
