<?php
/**
 * Client tracking settings.
 *
 * @package Carticy\Conversions\Client
 */

declare(strict_types=1);

namespace Carticy\Conversions\Client;

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

/**
 * Normalizes browser tracking configuration without exposing server secrets.
 */
final class ClientTrackingSettings {

	public const OPTION_NAME = 'carticy_conversions_client_tracking';

	/**
	 * Raw normalized option data.
	 *
	 * @var array<string, mixed>
	 */
	private array $data;

	/**
	 * Constructor.
	 *
	 * @param array<string, mixed> $data Raw settings.
	 */
	public function __construct( array $data = array() ) {
		$this->data = $this->normalize( $data );
	}

	/**
	 * Load settings from WordPress options.
	 *
	 * @return self
	 */
	public static function from_options(): self {
		$value = get_option( self::OPTION_NAME, array() );

		return new self( is_array( $value ) ? $value : array() );
	}

	/**
	 * Register the settings option.
	 */
	public function register_hooks(): void {
		add_action( 'admin_init', array( $this, 'register_setting' ) );
	}

	/**
	 * Register the client tracking setting for later admin surfaces.
	 */
	public function register_setting(): void {
		if ( ! function_exists( 'register_setting' ) ) {
			return;
		}

		register_setting(
			'carticy_conversions',
			self::OPTION_NAME,
			array(
				'type'              => 'array',
				'sanitize_callback' => array( self::class, 'sanitize_option' ),
				'default'           => array(),
				'show_in_rest'      => false,
			)
		);
	}

	/**
	 * Sanitize option data.
	 *
	 * @param mixed $value Raw option.
	 * @return array<string, mixed>
	 */
	public static function sanitize_option( $value ): array {
		return ( new self( is_array( $value ) ? $value : array() ) )->to_array();
	}

	/**
	 * Get normalized settings.
	 *
	 * @return array<string, mixed>
	 */
	public function to_array(): array {
		return $this->data;
	}

	/**
	 * Check whether any browser destination is configured.
	 *
	 * @return bool
	 */
	public function has_active_destinations(): bool {
		foreach ( array( 'ga4', 'google_ads', 'meta' ) as $provider ) {
			if ( $this->provider_enabled( $provider ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Check whether a provider can fire in the browser.
	 *
	 * @param string $provider Provider key.
	 * @return bool
	 */
	public function provider_enabled( string $provider ): bool {
		$config = $this->provider_config( $provider );

		return ! empty( $config['enabled'] );
	}

	/**
	 * Get provider config.
	 *
	 * @param string $provider Provider key.
	 * @return array<string, mixed>
	 */
	public function provider_config( string $provider ): array {
		$key = sanitize_key( $provider );

		return is_array( $this->data['providers'][ $key ] ?? null ) ? $this->data['providers'][ $key ] : array();
	}

	/**
	 * Build safe storefront configuration.
	 *
	 * @param array<int, array<string, mixed>> $events Pre-hydrated events.
	 * @param array<string, mixed>             $context Storefront context.
	 * @return array<string, mixed>
	 */
	public function storefront_config( array $events = array(), array $context = array() ): array {
		return array(
			'enabled'                          => $this->has_active_destinations(),
			'providers'                        => $this->data['providers'],
			'gtm'                              => $this->data['gtm'],
			'conflicts'                        => $this->conflicts(),
			'allowDuplicateClientDestinations' => ! empty( $this->data['allow_duplicate_client_destinations'] ),
			'events'                           => array_values( $events ),
			'context'                          => $context,
			'currentConsent'                   => array(
				'state'    => 'unset',
				'services' => array(),
			),
			'dedupStorageKey'                  => 'carticy_conversions_client_sent_v1',
			'dataLayerName'                    => 'dataLayer',
			'consentDefault'                   => 'denied',
			'version'                          => defined( 'CARTICY_CONVERSIONS_VERSION' ) ? CARTICY_CONVERSIONS_VERSION : '',
		);
	}

	/**
	 * Get conflict warnings.
	 *
	 * @return array<int, array<string, string>>
	 */
	public function conflicts(): array {
		return is_array( $this->data['conflicts'] ) ? $this->data['conflicts'] : array();
	}

	/**
	 * Normalize raw settings.
	 *
	 * @param array<string, mixed> $data Raw data.
	 * @return array<string, mixed>
	 */
	private function normalize( array $data ): array {
		$allow_duplicate = ! empty( $data['allow_duplicate_client_destinations'] );
		$gtm_id          = $this->normalize_gtm_id( (string) ( $data['gtm_container_id'] ?? '' ) );

		$providers = array(
			'ga4'        => $this->provider(
				'ga4',
				$this->normalize_ga4_id( (string) ( $data['ga4_measurement_id'] ?? '' ) ),
				(string) ( $data['ga4_mode'] ?? 'off' ),
				$allow_duplicate,
				$gtm_id,
				array( 'measurementId' => $this->normalize_ga4_id( (string) ( $data['ga4_measurement_id'] ?? '' ) ) )
			),
			'google_ads' => $this->provider(
				'google_ads',
				$this->normalize_ads_id( (string) ( $data['google_ads_conversion_id'] ?? '' ) ),
				(string) ( $data['google_ads_mode'] ?? 'off' ),
				$allow_duplicate,
				$gtm_id,
				array(
					'conversionId'    => $this->normalize_ads_id( (string) ( $data['google_ads_conversion_id'] ?? '' ) ),
					'conversionLabel' => $this->normalize_label( (string) ( $data['google_ads_conversion_label'] ?? '' ) ),
				)
			),
			'meta'       => $this->provider(
				'meta',
				$this->normalize_meta_id( (string) ( $data['meta_pixel_id'] ?? '' ) ),
				(string) ( $data['meta_mode'] ?? 'off' ),
				$allow_duplicate,
				$gtm_id,
				array( 'pixelId' => $this->normalize_meta_id( (string) ( $data['meta_pixel_id'] ?? '' ) ) )
			),
		);

		$conflicts = array();
		foreach ( $providers as $key => $provider ) {
			if ( 'both' === $provider['mode'] && ! $allow_duplicate ) {
				$conflicts[]               = array(
					'provider' => $key,
					'type'     => 'direct_gtm_duplicate',
					'message'  => 'Direct and GTM browser delivery require an explicit duplicate-destination opt-in.',
				);
				$providers[ $key ]['mode'] = 'direct';
			}
			$providers[ $key ]['enabled'] = $this->is_provider_active( $providers[ $key ], $gtm_id );
		}

		return array(
			'providers'                           => $providers,
			'gtm'                                 => array(
				'containerId' => $gtm_id,
				'enabled'     => '' !== $gtm_id,
			),
			'allow_duplicate_client_destinations' => $allow_duplicate,
			'conflicts'                           => $conflicts,
		);
	}

	/**
	 * Build one provider config.
	 *
	 * @param string               $provider Provider key.
	 * @param string               $identifier Main provider ID.
	 * @param string               $mode Raw mode.
	 * @param bool                 $allow_duplicate Allow both direct and GTM.
	 * @param string               $gtm_id GTM container ID.
	 * @param array<string,string> $ids Provider IDs.
	 * @return array<string, mixed>
	 */
	private function provider( string $provider, string $identifier, string $mode, bool $allow_duplicate, string $gtm_id, array $ids ): array {
		unset( $allow_duplicate, $gtm_id );

		$normalized_mode = sanitize_key( $mode );
		if ( ! in_array( $normalized_mode, array( 'off', 'direct', 'gtm', 'both' ), true ) ) {
			$normalized_mode = '' !== $identifier ? 'direct' : 'off';
		}

		return array_merge(
			array(
				'provider' => sanitize_key( $provider ),
				'mode'     => $normalized_mode,
				'enabled'  => false,
			),
			$ids
		);
	}

	/**
	 * Determine active provider state.
	 *
	 * @param array<string, mixed> $provider Provider config.
	 * @param string               $gtm_id GTM container ID.
	 * @return bool
	 */
	private function is_provider_active( array $provider, string $gtm_id ): bool {
		$mode = (string) ( $provider['mode'] ?? 'off' );
		if ( 'off' === $mode ) {
			return false;
		}
		if ( in_array( $mode, array( 'gtm', 'both' ), true ) && '' === $gtm_id ) {
			return false;
		}

		if ( 'ga4' === ( $provider['provider'] ?? '' ) ) {
			return '' !== (string) ( $provider['measurementId'] ?? '' );
		}
		if ( 'google_ads' === ( $provider['provider'] ?? '' ) ) {
			return '' !== (string) ( $provider['conversionId'] ?? '' ) && '' !== (string) ( $provider['conversionLabel'] ?? '' );
		}
		if ( 'meta' === ( $provider['provider'] ?? '' ) ) {
			return '' !== (string) ( $provider['pixelId'] ?? '' );
		}

		return false;
	}

	/**
	 * Normalize a GA4 measurement ID.
	 *
	 * @param string $value Raw ID.
	 * @return string
	 */
	private function normalize_ga4_id( string $value ): string {
		$value = strtoupper( trim( $value ) );

		return 1 === preg_match( '/^G-[A-Z0-9]+$/', $value ) ? $value : '';
	}

	/**
	 * Normalize a Google Ads conversion ID.
	 *
	 * @param string $value Raw ID.
	 * @return string
	 */
	private function normalize_ads_id( string $value ): string {
		$value = strtoupper( trim( $value ) );

		return 1 === preg_match( '/^AW-[0-9]+$/', $value ) ? $value : '';
	}

	/**
	 * Normalize a Google Ads conversion label.
	 *
	 * @param string $value Raw label.
	 * @return string
	 */
	private function normalize_label( string $value ): string {
		$value = trim( $value );

		return 1 === preg_match( '/^[A-Za-z0-9_\-]+$/', $value ) ? $value : '';
	}

	/**
	 * Normalize a Meta Pixel ID.
	 *
	 * @param string $value Raw ID.
	 * @return string
	 */
	private function normalize_meta_id( string $value ): string {
		$value = trim( $value );

		return 1 === preg_match( '/^[0-9]{5,}$/', $value ) ? $value : '';
	}

	/**
	 * Normalize a GTM public container ID.
	 *
	 * @param string $value Raw ID.
	 * @return string
	 */
	private function normalize_gtm_id( string $value ): string {
		$value = strtoupper( trim( $value ) );

		return 1 === preg_match( '/^GTM-[A-Z0-9]+$/', $value ) ? $value : '';
	}
}
