<?php
/**
 * ElementorDataProcessor — walks `_elementor_data` JSON and processes
 * inline-HTML fields through a callback.
 *
 * Uses json_decode / json_encode (no regex on JSON). The walker traverses
 * every node's `settings` array, identifies string values containing `<a`,
 * and hands them to the callback. Recursively descends into child elements.
 *
 * @package Platform\Helm\Support
 */

declare( strict_types = 1 );

namespace Platform\Helm\Support;

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

final class ElementorDataProcessor {

	/**
	 * Process a JSON blob and return [new_json, count, urls_matched].
	 *
	 * The callback receives a string of HTML and returns
	 * [new_html, removed_count, matched_urls].
	 *
	 * @param string   $json    Raw `_elementor_data` from postmeta.
	 * @param callable $processor function(string $html): array{0:string,1:int,2:string[]}
	 * @param bool     $dry_run  If true, don't re-encode JSON.
	 * @return array{0:string,1:int,2:string[]} [new_json_or_original, total_removed, urls_matched]
	 */
	public function process( string $json, callable $processor, bool $dry_run ): array {
		$decoded = json_decode( $json, true );
		if ( ! is_array( $decoded ) ) {
			return [ $json, 0, [] ];
		}
		$total        = 0;
		$matched_urls = [];

		$this->walk( $decoded, $processor, $dry_run, $total, $matched_urls );

		if ( $dry_run || 0 === $total ) {
			return [ $json, $total, array_values( array_unique( $matched_urls ) ) ];
		}

		$encoded = wp_json_encode( $decoded, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		return [
			false !== $encoded ? $encoded : $json,
			$total,
			array_values( array_unique( $matched_urls ) ),
		];
	}

	/**
	 * @param array<mixed>     $elements
	 * @param callable         $processor
	 * @param string[]         $matched_urls
	 */
	private function walk(
		array &$elements,
		callable $processor,
		bool $dry_run,
		int &$total,
		array &$matched_urls
	): void {
		foreach ( $elements as &$el ) {
			if ( ! is_array( $el ) ) {
				continue;
			}
			if ( isset( $el['settings'] ) && is_array( $el['settings'] ) ) {
				foreach ( $el['settings'] as $key => &$value ) {
					if ( ! is_string( $value ) || false === stripos( $value, '<a' ) ) {
						continue;
					}
					[ $new, $count, $urls_hit ] = $processor( $value );
					if ( $count > 0 ) {
						$total       += $count;
						$matched_urls = array_merge( $matched_urls, $urls_hit );
						if ( ! $dry_run ) {
							$value = $new;
						}
					}
				}
				unset( $value );
			}
			if ( ! empty( $el['elements'] ) && is_array( $el['elements'] ) ) {
				$this->walk( $el['elements'], $processor, $dry_run, $total, $matched_urls );
			}
		}
		unset( $el );
	}
}
