<?php
/**
 * Cookie scanner: detects, stores and identifies cookies found on the site.
 *
 * @package LetsCodeCookieBanner
 */

defined( 'ABSPATH' ) || exit;

/**
 * Cookie scanner: matches cookie names found on the frontend against a curated
 * signature database and persists the results for the admin dashboard.
 *
 * IMPORTANT: document.cookie only returns non-HttpOnly cookies. HttpOnly cookies
 * (e.g. wordpress_logged_in_*) are invisible to JavaScript and will not appear
 * in the detected list — this is expected behaviour for security reasons.
 */
class LCCB_Cookie_Scanner {

	const DETECTED_OPTION         = 'lccb_detected_cookies';
	const DETECTED_SCRIPTS_OPTION = 'lccb_detected_scripts';

	// ---- Persistence ---------------------------------------------------------

	/**
	 * Receives a list of cookie names from the frontend and persists new findings
	 * to the option table. Vendor/category metadata is left empty until the user
	 * runs "Update from global database" to enrich them via the backoffice API.
	 *
	 * @param  string[] $names  Cookie names sent by JS.
	 * @return void
	 */
	public static function scan_and_store( $names ) {
		$existing = get_option( self::DETECTED_OPTION, array() );
		$changed  = false;

		foreach ( $names as $raw ) {
			$name = sanitize_text_field( wp_unslash( $raw ) );
			if ( '' === $name || isset( $existing[ $name ] ) ) {
				continue;
			}
			$existing[ $name ] = array(
				'name'        => $name,
				'vendor'      => '',
				'category'    => '',
				'duration'    => '',
				'description' => '',
				'first_seen'  => current_time( 'mysql' ),
			);
			$changed           = true;
		}

		if ( $changed ) {
			update_option( self::DETECTED_OPTION, $existing );
		}
	}

	/**
	 * Returns all cookies detected so far.
	 *
	 * @return array
	 */
	public static function get_detected() {
		return get_option( self::DETECTED_OPTION, array() );
	}

	/**
	 * Clears the list of detected cookies.
	 */
	public static function clear_detected() {
		delete_option( self::DETECTED_OPTION );
	}

	// ---- Script detection -------------------------------------------------------

	/**
	 * Receives a list of third-party script items from the frontend and persists them.
	 * Each item is an array with 'url' (string) and 'cookies' (string[]).
	 * URLs are normalized to scheme + host + path (no query/fragment).
	 * When a script already exists, any new cookie attributions are merged in.
	 *
	 * @param  array[] $items Script items with 'url' and 'cookies' keys.
	 * @return void
	 */
	public static function scan_and_store_scripts( array $items ) {
		$existing = get_option( self::DETECTED_SCRIPTS_OPTION, array() );
		$changed  = false;
		$home     = wp_parse_url( home_url(), PHP_URL_HOST );

		foreach ( $items as $item ) {
			$raw_url     = is_array( $item ) ? ( $item['url'] ?? '' ) : (string) $item;
			$new_cookies = ( is_array( $item ) && ! empty( $item['cookies'] ) ) ? array_values( (array) $item['cookies'] ) : array();

			$parsed = wp_parse_url( $raw_url );
			if ( ! $parsed || empty( $parsed['host'] ) ) {
				continue;
			}
			if ( ! in_array( $parsed['scheme'] ?? '', array( 'http', 'https' ), true ) ) {
				continue;
			}
			if ( $parsed['host'] === $home ) {
				continue;
			}
			$normalized = $parsed['scheme'] . '://' . $parsed['host'] . ( $parsed['path'] ?? '/' );
			if ( strlen( $normalized ) > 2048 ) {
				continue;
			}

			if ( isset( $existing[ $normalized ] ) ) {
				// Script already known — merge any new cookie attributions.
				if ( ! empty( $new_cookies ) ) {
					$merged = array_values( array_unique( array_merge( $existing[ $normalized ]['cookies'] ?? array(), $new_cookies ) ) );
					if ( ( $existing[ $normalized ]['cookies'] ?? array() ) !== $merged ) {
						$existing[ $normalized ]['cookies'] = $merged;
						$changed                            = true;
					}
				}
				continue;
			}

			$existing[ $normalized ] = array(
				'url'        => $normalized,
				'domain'     => $parsed['host'],
				'first_seen' => current_time( 'mysql' ),
				'synced'     => false,
				'cookies'    => $new_cookies,
			);
			$changed                 = true;
		}

		if ( $changed ) {
			update_option( self::DETECTED_SCRIPTS_OPTION, $existing );
		}
	}

	/**
	 * Returns all detected third-party script URLs.
	 *
	 * @return array
	 */
	public static function get_detected_scripts() {
		return get_option( self::DETECTED_SCRIPTS_OPTION, array() );
	}

	/**
	 * Marks the given script URLs as synced with the global database.
	 *
	 * @param string[] $urls Normalized script URLs to mark as synced.
	 * @return void
	 */
	public static function mark_scripts_synced( array $urls ) {
		$existing = get_option( self::DETECTED_SCRIPTS_OPTION, array() );
		$changed  = false;

		foreach ( $urls as $url ) {
			if ( isset( $existing[ $url ] ) && empty( $existing[ $url ]['synced'] ) ) {
				$existing[ $url ]['synced'] = true;
				$changed                    = true;
			}
		}

		if ( $changed ) {
			update_option( self::DETECTED_SCRIPTS_OPTION, $existing );
		}
	}

	/**
	 * Clears the list of detected script URLs.
	 */
	public static function clear_detected_scripts() {
		delete_option( self::DETECTED_SCRIPTS_OPTION );
	}
}
