<?php
/**
 * Scan post block content for DSM library icon picker values.
 *
 * @package DiviSupreme
 */

declare(strict_types=1);

namespace DiviSupreme\Extensions\IconLibraries;

if ( ! defined( 'ABSPATH' ) ) {
	die( 'Direct access forbidden.' );
}

/**
 * Collects icon settings from saved block JSON for conditional asset loading.
 */
final class PostContentIconScanner {

	/**
	 * Scan post block content for DSM icon settings used on the page.
	 *
	 * @param int $post_id Post ID.
	 * @return array<string, array<string, mixed>>
	 */
	public static function collect_from_post_content( int $post_id ): array {
		if ( $post_id <= 0 ) {
			return array();
		}

		$collected = array();
		$content   = (string) get_post_field( 'post_content', $post_id );

		if ( '' !== trim( $content ) && function_exists( 'parse_blocks' ) && strlen( $content ) <= 1_048_576 ) {
			foreach ( parse_blocks( $content ) as $block ) {
				if ( is_array( $block ) ) {
					self::walk_block( $block, $collected );
				}
			}
		}

		foreach ( self::request_content_blobs_for_icon_scan( $post_id ) as $blob ) {
			if ( ! is_string( $blob ) || '' === $blob ) {
				continue;
			}

			self::collect_from_content_substring( $blob, $collected );
		}

		/**
		 * Allow themes/plugins to supply additional icon settings for the current request.
		 *
		 * @param array<string, array<string, mixed>> $collected Unique icon settings.
		 * @param int                                 $post_id   Post ID.
		 */
		return apply_filters( 'dsm_icon_libraries_collected_icons', $collected, $post_id );
	}

	/**
	 * Serialized content blobs for icon scans (TB merge, Divi DA — substring only, no parse_blocks).
	 *
	 * @param int $post_id Post ID.
	 * @return list<string>
	 */
	private static function request_content_blobs_for_icon_scan( int $post_id ): array {
		$blobs = array();

		if (
			class_exists( \DiviSupreme\Includes\DsmFrontendAssetRegistry::class )
			&& method_exists( \DiviSupreme\Includes\DsmFrontendAssetRegistry::class, 'get_da_all_content_for_pattern_scan' )
		) {
			$da_blob = \DiviSupreme\Includes\DsmFrontendAssetRegistry::get_da_all_content_for_pattern_scan();
			if ( is_string( $da_blob ) && '' !== $da_blob ) {
				$blobs[] = $da_blob;
			}
		}

		if ( class_exists( \DiviSupreme\Includes\DsmFrontendDynamicAssets::class ) ) {
			$base = \DiviSupreme\Includes\DsmFrontendDynamicAssets::get_base_page_content_for_detection();
			if ( is_string( $base ) && '' !== $base ) {
				$blobs[] = $base;
			}
		}

		if ( class_exists( \Feature\ContentRetriever\ET_Builder_Content_Retriever::class ) && $post_id > 0 ) {
			$retriever = \Feature\ContentRetriever\ET_Builder_Content_Retriever::init();
			$post      = get_post( $post_id );

			if (
				is_object( $retriever )
				&& method_exists( $retriever, 'get_entire_page_content' )
				&& $post instanceof \WP_Post
			) {
				$entire = $retriever->get_entire_page_content( $post );
				if ( is_string( $entire ) && '' !== $entire ) {
					$blobs[] = $entire;
				}
			}
		}

		return array_values( array_unique( $blobs ) );
	}

	/**
	 * Locate DSM library icon picker JSON in raw serialized content (no parse_blocks).
	 *
	 * @param string                              $content   Serialized block / shortcode content.
	 * @param array<string, array<string, mixed>> $collected Unique icons keyed by cache key.
	 * @return void
	 */
	private static function collect_from_content_substring( string $content, array &$collected ): void {
		if ( '' === $content ) {
			return;
		}

		foreach ( IconLibraryRegistry::enabled() as $library_id => $library ) {
			unset( $library );

			$library_id = sanitize_key( (string) $library_id );
			if ( '' === $library_id ) {
				continue;
			}

			$pattern = '/"type"\s*:\s*"' . preg_quote( $library_id, '/' ) . '".{0,800}?"unicode"\s*:\s*"([^"]+)"/s';

			if ( ! preg_match_all( $pattern, $content, $matches, PREG_SET_ORDER ) ) {
				continue;
			}

			foreach ( $matches as $match ) {
				if ( ! isset( $match[0], $match[1] ) || ! is_string( $match[1] ) || '' === $match[1] ) {
					continue;
				}

				$icon = array(
					'type'    => $library_id,
					'unicode' => sanitize_key( $match[1] ),
				);

				if ( preg_match( '/"variant"\s*:\s*"([^"]+)"/', $match[0], $variant_match )
					&& is_string( $variant_match[1] )
					&& '' !== $variant_match[1] ) {
					$icon['variant'] = sanitize_key( $variant_match[1] );
				}

				if ( ! IconLibraryRegistry::is_dsm_library_icon( $icon ) ) {
					continue;
				}

				$variant = IconLibraryRegistry::resolve_variant_from_icon( $icon );
				$key     = $library_id . ':' . $icon['unicode'] . ':' . $variant;

				if ( ! isset( $collected[ $key ] ) ) {
					$collected[ $key ] = $icon;
				}
			}
		}
	}

	/**
	 * Recursively scan a parsed block (including nested innerBlocks) for DSM icons.
	 *
	 * @param array<string, mixed>              $block     Parsed block.
	 * @param array<string, array<string, mixed>> $collected Unique icons keyed by cache key.
	 * @return void
	 */
	private static function walk_block( array $block, array &$collected ): void {
		self::walk( $block, $collected );

		if ( ! isset( $block['innerBlocks'] ) || ! is_array( $block['innerBlocks'] ) ) {
			return;
		}

		foreach ( $block['innerBlocks'] as $inner ) {
			if ( is_array( $inner ) ) {
				self::walk_block( $inner, $collected );
			}
		}
	}

	/**
	 * @param mixed                               $value     Node to walk.
	 * @param array<string, array<string, mixed>> $collected Unique icons keyed by cache key.
	 * @return void
	 */
	private static function walk( $value, array &$collected ): void {
		if ( ! is_array( $value ) ) {
			return;
		}

		if (
			isset( $value['type'], $value['unicode'] )
			&& is_string( $value['type'] )
			&& is_string( $value['unicode'] )
			&& IconLibraryRegistry::is_dsm_library_icon( $value )
		) {
			$library_id = sanitize_key( $value['type'] );
			$icon_id    = sanitize_key( $value['unicode'] );
			$variant    = IconLibraryRegistry::resolve_variant_from_icon( $value );
			$key        = $library_id . ':' . $icon_id . ':' . $variant;

			if ( ! isset( $collected[ $key ] ) ) {
				$collected[ $key ] = $value;
			}
		}

		foreach ( $value as $child ) {
			self::walk( $child, $collected );
		}
	}
}
