<?php
/**
 * Cookie registry: manages the list of cookies declared by the site.
 *
 * @package LetsCodeCookieBanner
 */

defined( 'ABSPATH' ) || exit;

/**
 * Cookie registry: stores the detailed list of cookies used by the site.
 * Data is persisted in the WordPress options table under 'lccb_cookie_registry'.
 * If no custom list has been saved the built-in defaults are returned.
 */
class LCCB_Cookie_Registry {

	const OPTION_KEY = 'lccb_cookie_registry';

	// ---- CRUD ----------------------------------------------------------------

	/**
	 * Returns the full cookie list, or an empty array if none has been saved yet.
	 *
	 * @return array
	 */
	public static function get_all() {
		return get_option( self::OPTION_KEY, array() );
	}

	/**
	 * Returns cookies belonging to a specific category.
	 *
	 * @param string $category Category slug: necessary|statistics|marketing|preferences.
	 * @return array
	 */
	public static function get_by_category( $category ) {
		$all = self::get_all();
		return array_values(
			array_filter(
				$all,
				function ( $c ) use ( $category ) {
					return isset( $c['category'] ) && $c['category'] === $category;
				}
			)
		);
	}

	/**
	 * Returns all cookies grouped by category.
	 *
	 * @return array<string, array> Keys: necessary, statistics, marketing, preferences.
	 */
	public static function get_grouped() {
		$all    = self::get_all();
		$groups = array(
			'necessary'   => array(),
			'statistics'  => array(),
			'marketing'   => array(),
			'preferences' => array(),
		);
		foreach ( $all as $c ) {
			$cat = isset( $c['category'] ) ? $c['category'] : 'necessary';
			if ( isset( $groups[ $cat ] ) ) {
				$groups[ $cat ][] = $c;
			}
		}
		return $groups;
	}

	/**
	 * Persists the full cookie list to the options table.
	 *
	 * @param array $cookies Cookie list to save.
	 */
	public static function save_all( $cookies ) {
		update_option( self::OPTION_KEY, $cookies );
	}

	/**
	 * Adds a new cookie entry.
	 *
	 * @param array $data Unsanitized cookie fields.
	 * @return array Sanitized entry that was saved.
	 */
	public static function add( $data ) {
		$all       = self::get_all();
		$new       = self::sanitize_entry( $data );
		$new['id'] = 'custom_' . uniqid( '', true );
		$all[]     = $new;
		self::save_all( $all );
		return $new;
	}

	/**
	 * Updates an existing cookie entry by ID.
	 *
	 * @param string $id   Cookie entry ID.
	 * @param array  $data Updated fields.
	 */
	public static function update( $id, $data ) {
		$all = self::get_all();
		foreach ( $all as &$c ) {
			if ( isset( $c['id'] ) && $c['id'] === $id ) {
				$updated = self::sanitize_entry( $data );
				if ( ! array_key_exists( 'name', $data ) ) {
					unset( $updated['name'] );
				}
				$c = array_merge( $c, $updated );
				// Allow callers to explicitly set the source ('manual' or 'global_db').
				if ( isset( $data['source'] ) && in_array( $data['source'], array( 'manual', 'global_db' ), true ) ) {
					$c['source'] = $data['source'];
				}
				break;
			}
		}
		unset( $c );
		self::save_all( $all );
	}

	/**
	 * Removes a cookie entry by ID.
	 *
	 * @param string $id Cookie entry ID.
	 */
	public static function delete( $id ) {
		$all = array_values(
			array_filter(
				self::get_all(),
				function ( $c ) use ( $id ) {
					return ! ( isset( $c['id'] ) && $c['id'] === $id );
				}
			)
		);
		self::save_all( $all );
	}

	/**
	 * Clears the saved cookie list, leaving the registry empty.
	 */
	public static function reset_to_defaults() {
		delete_option( self::OPTION_KEY );
	}

	// ---- Helpers -------------------------------------------------------------

	/**
	 * Sanitizes and normalises a raw cookie data array.
	 *
	 * @param array $data Raw cookie fields.
	 * @return array
	 */
	private static function sanitize_entry( $data ) {
		$allowed_cats = array( 'necessary', 'statistics', 'marketing', 'preferences' );
		$cat          = isset( $data['category'] ) && in_array( $data['category'], $allowed_cats, true )
			? $data['category'] : 'necessary';

		$entry = array(
			'name'        => isset( $data['name'] ) ? sanitize_text_field( $data['name'] ) : '',
			'category'    => $cat,
			'vendor'      => isset( $data['vendor'] ) ? sanitize_text_field( $data['vendor'] ) : '',
			'duration'    => isset( $data['duration'] ) ? sanitize_text_field( $data['duration'] ) : '',
			'description' => isset( $data['description'] ) ? sanitize_textarea_field( $data['description'] ) : '',
		);

		// script_urls is set only by the API lookup — not exposed in the manual admin form.
		if ( array_key_exists( 'script_urls', $data ) ) {
			$entry['script_urls'] = array_values(
				array_filter( array_map( 'esc_url_raw', (array) $data['script_urls'] ), 'strlen' )
			);
		}

		return $entry;
	}

	/**
	 * Returns script URL patterns grouped by consent category, derived from the local registry.
	 * Used by the wp_script_attributes filter to block known third-party scripts.
	 *
	 * @return array<string, string[]> category => list of URL substrings to match against script src.
	 */
	public static function get_script_patterns() {
		$patterns = array(
			'statistics'  => array(),
			'marketing'   => array(),
			'preferences' => array(),
		);

		foreach ( self::get_all() as $c ) {
			$cat = isset( $c['category'] ) ? $c['category'] : '';
			if ( ! isset( $patterns[ $cat ] ) ) {
				continue;
			}
			if ( empty( $c['script_urls'] ) || ! is_array( $c['script_urls'] ) ) {
				continue;
			}
			foreach ( $c['script_urls'] as $url ) {
				if ( ! in_array( $url, $patterns[ $cat ], true ) ) {
					$patterns[ $cat ][] = $url;
				}
			}
		}

		return $patterns;
	}
}
