<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */
declare (strict_types=1);
namespace Packetery\Nette\DI\Extensions;

use Packetery\Nette;
use Packetery\Nette\DI\Config\Helpers;
use Packetery\Nette\DI\Definitions;
use Packetery\Nette\DI\Definitions\Statement;
use Packetery\Nette\Schema\Context;
use Packetery\Nette\Schema\Expect;
use Packetery\Nette\Schema\Schema;
/**
 * Service configuration schema.
 */
class DefinitionSchema implements Schema
{
    use \Packetery\Nette\SmartObject;
    /** @var \Packetery\Nette\DI\ContainerBuilder */
    private $builder;
    public function __construct(\Packetery\Nette\DI\ContainerBuilder $builder)
    {
        $this->builder = $builder;
    }
    public function complete($def, Context $context)
    {
        if ($def === [\false]) {
            return (object) $def;
        }
        if (Helpers::takeParent($def)) {
            $def['reset']['all'] = \true;
        }
        foreach (['arguments', 'setup', 'tags'] as $k) {
            if (isset($def[$k]) && Helpers::takeParent($def[$k])) {
                $def['reset'][$k] = \true;
            }
        }
        $def = $this->expandParameters($def);
        $type = $this->sniffType(\end($context->path), $def);
        $def = $this->getSchema($type)->complete($def, $context);
        if ($def) {
            $def->defType = $type;
        }
        return $def;
    }
    public function merge($def, $base)
    {
        if (!empty($def['alteration'])) {
            unset($def['alteration']);
        }
        return \Packetery\Nette\Schema\Helpers::merge($def, $base);
    }
    /**
     * Normalizes configuration of service definitions.
     */
    public function normalize($def, Context $context)
    {
        if ($def === null || $def === \false) {
            return (array) $def;
        } elseif (\is_string($def) && \interface_exists($def)) {
            return ['implement' => $def];
        } elseif ($def instanceof Statement && \is_string($def->getEntity()) && \interface_exists($def->getEntity())) {
            $res = ['implement' => $def->getEntity()];
            if (\array_keys($def->arguments) === ['tagged']) {
                $res += $def->arguments;
            } elseif (\array_keys($def->arguments) === [0]) {
                $res['create'] = $def->arguments[0];
            } elseif ($def->arguments) {
                $res['references'] = $def->arguments;
            }
            return $res;
        } elseif (!\is_array($def) || isset($def[0], $def[1])) {
            return ['create' => $def];
        } else {
            // back compatibility
            if (isset($def['factory']) && !isset($def['create'])) {
                $def['create'] = $def['factory'];
                unset($def['factory']);
            }
            if (isset($def['class']) && !isset($def['type']) && !isset($def['dynamic']) && !isset($def['imported'])) {
                $def[isset($def['create']) ? 'type' : 'create'] = $def['class'];
                unset($def['class']);
            }
            foreach (['class' => 'type', 'dynamic' => 'imported'] as $alias => $original) {
                if (\array_key_exists($alias, $def)) {
                    if (\array_key_exists($original, $def)) {
                        throw new \Packetery\Nette\DI\InvalidConfigurationException(\sprintf("Options '%s' and '%s' are aliases, use only '%s'.", $alias, $original, $original));
                    }
                    \trigger_error(\sprintf("Service '%s': option '{$alias}' should be changed to '{$original}'.", \end($context->path)), \E_USER_DEPRECATED);
                    $def[$original] = $def[$alias];
                    unset($def[$alias]);
                }
            }
            return $def;
        }
    }
    public function completeDefault(Context $context)
    {
    }
    private function sniffType($key, array $def) : string
    {
        if (\is_string($key)) {
            $name = \preg_match('#^@[\\w\\\\]+$#D', $key) ? $this->builder->getByType(\substr($key, 1)) : $key;
            if ($name && $this->builder->hasDefinition($name)) {
                return \get_class($this->builder->getDefinition($name));
            }
        }
        if (isset($def['implement'], $def['references']) || isset($def['implement'], $def['tagged'])) {
            return Definitions\LocatorDefinition::class;
        } elseif (isset($def['implement'])) {
            return \method_exists($def['implement'], 'create') ? Definitions\FactoryDefinition::class : Definitions\AccessorDefinition::class;
        } elseif (isset($def['imported'])) {
            return Definitions\ImportedDefinition::class;
        } elseif (!$def) {
            throw new \Packetery\Nette\DI\InvalidConfigurationException("Service '{$key}': Empty definition.");
        } else {
            return Definitions\ServiceDefinition::class;
        }
    }
    private function expandParameters(array $config) : array
    {
        $params = $this->builder->parameters;
        if (isset($config['parameters'])) {
            foreach ((array) $config['parameters'] as $k => $v) {
                $v = \explode(' ', \is_int($k) ? $v : $k);
                $params[\end($v)] = $this->builder::literal('$' . \end($v));
            }
        }
        return \Packetery\Nette\DI\Helpers::expand($config, $params);
    }
    private static function getSchema(string $type) : Schema
    {
        static $cache;
        $cache = $cache ?: [Definitions\ServiceDefinition::class => self::getServiceSchema(), Definitions\AccessorDefinition::class => self::getAccessorSchema(), Definitions\FactoryDefinition::class => self::getFactorySchema(), Definitions\LocatorDefinition::class => self::getLocatorSchema(), Definitions\ImportedDefinition::class => self::getImportedSchema()];
        return $cache[$type];
    }
    private static function getServiceSchema() : Schema
    {
        return Expect::structure(['type' => Expect::type('string'), 'create' => Expect::type('callable|\\Packetery\\Nette\\DI\\Definitions\\Statement'), 'arguments' => Expect::array(), 'setup' => Expect::listOf('callable|\\Packetery\\Nette\\DI\\Definitions\\Statement|array:1'), 'inject' => Expect::bool(), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array(), 'reset' => Expect::array(), 'alteration' => Expect::bool()]);
    }
    private static function getAccessorSchema() : Schema
    {
        return Expect::structure(['type' => Expect::string(), 'implement' => Expect::string(), 'create' => Expect::type('callable|\\Packetery\\Nette\\DI\\Definitions\\Statement'), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array()]);
    }
    private static function getFactorySchema() : Schema
    {
        return Expect::structure(['type' => Expect::string(), 'create' => Expect::type('callable|\\Packetery\\Nette\\DI\\Definitions\\Statement'), 'implement' => Expect::string(), 'arguments' => Expect::array(), 'setup' => Expect::listOf('callable|\\Packetery\\Nette\\DI\\Definitions\\Statement|array:1'), 'parameters' => Expect::array(), 'references' => Expect::array(), 'tagged' => Expect::string(), 'inject' => Expect::bool(), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array(), 'reset' => Expect::array()]);
    }
    private static function getLocatorSchema() : Schema
    {
        return Expect::structure(['implement' => Expect::string(), 'references' => Expect::array(), 'tagged' => Expect::string(), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array()]);
    }
    private static function getImportedSchema() : Schema
    {
        return Expect::structure(['type' => Expect::string(), 'imported' => Expect::bool(), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array()]);
    }
}
