<?php

namespace Planaday\Html\Parsers\DynamicDataParsers;

use ArrayAccess;
use Planaday\Utility\DependencyInjection\Attributes\Scoped;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use ReflectionFunctionAbstract;
use ReflectionMethod;

#[Scoped]
class StdDynamicDataParser implements DynamicDataParser {
	public function parseRaw( string $target, array|object &$data): mixed {
		$targetAsArray = explode('->', $target);
		$output =& $data;

		foreach ($targetAsArray as $key){
			if (
				( is_array($output) && array_key_exists($key, $output) ) ||
				( $output instanceof ArrayAccess && $output->offsetExists($key) )
			){
				$output =& $output[$key];
			} elseif (is_object($output)){
				$output =& $this->parseObject($output, $key);
			} else {
				return null;
			}

			if (is_callable($output)){
				try {
					$reflection = $this->reflectCallable($output);
					if ($reflection->getNumberOfParameters() === 0){
						$output = $output();
					}
				} catch (ReflectionException) {}
			}
		}

		return $output;
	}

	public function parseForString( string $target, array|object &$data, bool $blockCallables = false ): null|string {
		$value = $this->parseRaw($target, $data);
		return is_scalar($value) || (is_object($value) && method_exists($value, '__toString')) ? (string)$value : null;
	}

	protected function &parseObject(object $object, string $key): mixed {
		$reflection = new ReflectionClass($object);

		try {
			$reflectionProperty = $reflection->getProperty($key);
			if ($reflectionProperty->isPublic()){
				return $object->{$key};
			}
		} catch (ReflectionException) {}

		try {
			$reflectionMethod = $reflection->getMethod($key);
			if ($reflectionMethod->getNumberOfRequiredParameters() === 0 && $reflectionMethod->isPublic()){
				$result = $reflectionMethod->invoke($object);
				return $result;
			}
		} catch (ReflectionException) {}

		try {
			$reflectionMethod = $reflection->getMethod("get" . ucfirst($key));
			if ($reflectionMethod->getNumberOfRequiredParameters() === 0 && $reflectionMethod->isPublic()){
				$result = $reflectionMethod->invoke($object);
				return $result;
			}
		} catch (ReflectionException) {}

		try {
			$reflectionMethod = $reflection->getMethod("is" . ucfirst($key));
			if ($reflectionMethod->getNumberOfRequiredParameters() === 0 && $reflectionMethod->isPublic()){
				$result = $reflectionMethod->invoke($object);
				return $result;
			}
		} catch (ReflectionException) {}

		$result = null;
		return $result;
	}

	/**
	 * @throws ReflectionException
	 */
	function reflectCallable( callable $arg ): ReflectionFunctionAbstract {
		if (is_array($arg)) {
			$ref = new ReflectionMethod(...$arg);
		} else {
			$ref = new ReflectionFunction($arg);
		}

		return $ref;
	}
}