<?php

namespace Limb_Chatbot\Includes\Services\Actions;

/**
 * Class Variable_Replacer
 *
 * Dynamically replaces variables in config/templates with extracted callback variables.
 *
 * Supports single format with auto-detection:
 * - {key} - No dots → lookup in extracted_vars['key']
 * - {callback.path.to.value} - Has dots → lookup with dot notation
 *
 * Variables not found are left unchanged: {not_found} stays as {not_found}
 *
 * @package Limb_Chatbot\Includes\Services\Actions
 * @since 1.0.0
 */
class Variable_Replacer {

	/**
	 * @var array Extracted variables from callbacks
	 */
	private array $extracted_vars = [];

	/**
	 * Constructor
	 *
	 * @param array $extracted_vars Extracted variables from executed callbacks
	 */
	public function __construct( array $extracted_vars = [] ) {
		$this->extracted_vars = $extracted_vars;
	}

	/**
	 * Replace variables in mixed data (string, array, object)
	 *
	 * @param mixed $data Data to process
	 * @return mixed Processed data with variables replaced
	 */
	public function replace( $data ) {
		if ( is_string( $data ) ) {
			return $this->replace_in_string( $data );
		}

		if ( is_array( $data ) ) {
			$result = [];
			foreach ( $data as $key => $value ) {
				$result[ $key ] = $this->replace( $value );
			}
			return $result;
		}

		if ( is_object( $data ) ) {
			$processed = clone $data;
			foreach ( $processed as $key => $value ) {
				$processed->$key = $this->replace( $value );
			}
			return $processed;
		}

		return $data;
	}

	/**
	 * Replace variables in a string using {key} format
	 *
	 * Format: {key}
	 * - {simple_key} → extracted_vars['simple_key']
	 * - {nested.key.value} → extracted_vars['nested']['key']['value']
	 *
	 * Variables not found remain unchanged.
	 *
	 * @param string $string String to process
	 * @return string Processed string
	 */
	private function replace_in_string( string $string ): string {
		// Replace {key} and {nested.path.value} format
		return preg_replace_callback( '/\{([a-zA-Z_][a-zA-Z0-9_\.]*)\}/', function ( $matches ) {
			$path = $matches[1];
			$value = $this->get_value( $path );

			// If not found, keep the original {key}
			if ( $value === null ) {
				return $matches[0];
			}

			return (string) $value;
		}, $string );
	}

	/**
	 * Get value from extracted variables using dot notation
	 *
	 * @param string $path Path in dot notation (e.g., 'callback_name.data.value')
	 * @return mixed|null Value or null if not found
	 */
	private function get_value( string $path ) {
		return $this->array_get( $this->extracted_vars, $path );
	}

	/**
	 * Get array value using dot notation
	 *
	 * @param array $array Array to search
	 * @param string $path Path in dot notation
	 * @return mixed|null
	 */
	private function array_get( array $array, string $path ) {
		$keys = explode( '.', $path );

		foreach ( $keys as $key ) {
			// Try numeric key (array index)
			if ( is_numeric( $key ) ) {
				$key = (int) $key;
			}

			if ( ! is_array( $array ) || ! array_key_exists( $key, $array ) ) {
				return null;
			}

			$array = $array[ $key ];
		}

		return $array;
	}

	/**
	 * Merge additional extracted variables
	 *
	 * @param array $new_vars Variables to merge
	 * @return void
	 */
	public function merge_extracted_vars( array $new_vars ): void {
		$this->extracted_vars = array_merge( $this->extracted_vars, $new_vars );
	}

	/**
	 * Get all extracted variables
	 *
	 * @return array
	 */
	public function get_all_variables(): array {
		return $this->extracted_vars;
	}
}
