<?php

/**
 * The representation of a builder parameter.
 *
 * @package TEC\Common\lucatume\DI52\Builders
 */
namespace TEC\Common\lucatume\DI52\Builders;

use TEC\Common\lucatume\DI52\ContainerException;
use TEC\Common\lucatume\DI52\NestedParseError;
use ParseError;
use ReflectionException;
use ReflectionParameter;
/**
 * Class Parameter
 *
 * @package \TEC\Common\lucatume\DI52\Builders
 */
class Parameter
{
    /**
     * The parameter type or `null` if the type cannot be parsed.
     *
     * @var string|null
     */
    protected $type;
    /**
     * Whether the parameter is an optional one or not.
     *
     * @var bool
     */
    protected $isOptional;
    /**
     * The parameter default value, or `null` if not available.
     *
     * @var mixed|null
     */
    protected $defaultValue;
    /**
     * Whether the parameter is a class or not.
     *
     * @var bool
     */
    protected $isClass;
    /**
     * A list of the types that are NOT classes.
     *
     * @var array<string>
     */
    protected static $nonClassTypes = ['string', 'int', 'bool', 'float', 'double', 'array', 'resource', 'callable', 'iterable', 'union'];
    /**
     * A map relating the string output type to the internal, type-hintable, type.
     *
     * @var array<string>
     */
    protected static $conversionMap = ['integer' => 'int', 'boolean' => 'bool', 'double' => 'float'];
    /**
     * The parameter name.
     *
     * @var string
     */
    protected $name;
    /**
     * Parameter constructor.
     *
     * @param int $index The parameter position in the list of parameters.
     * @param ReflectionParameter $reflectionParameter The parameter reflection to extract the information from.
     *
     * @throws ReflectionException
     */
    public function __construct($index, ReflectionParameter $reflectionParameter)
    {
        $string = $reflectionParameter->__toString();
        $s = trim(str_replace('Parameter #' . $index, '', $string), '[ ]');
        $frags = explode(' ', $s);
        $this->name = $reflectionParameter->name;
        $this->type = strpos($frags[1], '$') === 0 ? null : $frags[1];
        // PHP 8.0 nullables.
        $this->type = str_replace('?', '', (string) $this->type);
        // PHP 8.0 Union types.
        if (strpos($this->type, '|') !== false) {
            $this->type = 'union';
        }
        if (isset(static::$conversionMap[$this->type])) {
            $this->type = static::$conversionMap[$this->type];
            // @codeCoverageIgnore
        }
        $this->isClass = $this->type && $this->isClass();
        $this->isOptional = $frags[0] === '<optional>';
        $this->defaultValue = $this->isOptional ? $reflectionParameter->getDefaultValue() : null;
    }
    /**
     * Returns the parameter extracted data.
     *
     * @return array<string,string|bool|mixed> A map of the parameter data.
     */
    public function getData()
    {
        return ['type' => $this->type, 'isOptional' => $this->isOptional, 'defaultValue' => $this->defaultValue];
    }
    /**
     * Returns the parameter default value, if any.
     *
     * @return mixed|null The parameter default value, if any.
     */
    public function getDefaultValue()
    {
        return $this->defaultValue;
    }
    /**
     * Returns the parameter class name, if any.
     *
     * @return string|null The parameter class name, if any.
     */
    public function getClass()
    {
        return $this->isClass ? $this->type : null;
    }
    /**
     * Returns the parameter name.
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
    /**
     * Returns the parameter type, if any.
     *
     * @return string|null The parameter type, if any.
     */
    public function getType()
    {
        return $this->type;
    }
    /**
     * Either return the parameter default value, or die trying.
     *
     * @return mixed|null The parameter default value.
     * @throws ContainerException If the parameter does not have a default value.
     */
    public function getDefaultValueOrFail()
    {
        if ($this->isOptional) {
            return $this->defaultValue;
        }
        if (!$this->isClass) {
            $format = 'Parameter $%s is not optional and is not type-hinted: auto-wiring is not magic.';
            $message = sprintf($format, $this->name);
        } else {
            $format = 'Parameter $%s is not optional and its type (%s) cannot be resolved to a concrete class.';
            $message = sprintf($format, $this->name, $this->getClass());
        }
        throw new ContainerException($message);
    }
    /**
     * Check if the parameter type is a class.
     *
     * @suppress PhanUndeclaredFunction
     *
     * @return bool
     *
     * @throws NestedParseError If a parsing error occurs while assessing the parameter type.
     */
    private function isClass()
    {
        if (in_array($this->type, static::$nonClassTypes, true)) {
            return false;
        }
        try {
            if (function_exists('enum_exists') && enum_exists((string) $this->type)) {
                return false;
            }
        } catch (ParseError $e) {
            throw new NestedParseError($e->getMessage(), $e->getCode(), $e, (string) $this->type, $this->name);
        }
        return true;
    }
}