<?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\Config\Adapters;

use Packetery\Nette;
use Packetery\Nette\DI\Config\Helpers;
use Packetery\Nette\DI\Definitions\Reference;
use Packetery\Nette\DI\Definitions\Statement;
use Packetery\Nette\Neon;
/**
 * Reading and generating NEON files.
 */
final class NeonAdapter implements \Packetery\Nette\DI\Config\Adapter
{
    use \Packetery\Nette\SmartObject;
    private const PreventMergingSuffix = '!';
    /** @var string */
    private $file;
    /**
     * Reads configuration from NEON file.
     */
    public function load(string $file) : array
    {
        $input = \Packetery\Nette\Utils\FileSystem::read($file);
        if (\substr($input, 0, 3) === "﻿") {
            // BOM
            $input = \substr($input, 3);
        }
        $this->file = $file;
        $decoder = new Neon\Decoder();
        $node = $decoder->parseToNode($input);
        $traverser = new Neon\Traverser();
        $node = $traverser->traverse($node, [$this, 'removeUnderscoreVisitor']);
        $node = $traverser->traverse($node, [$this, 'convertAtSignVisitor']);
        return $this->process((array) $node->toValue());
    }
    /** @throws \Packetery\Nette\InvalidStateException */
    public function process(array $arr) : array
    {
        $res = [];
        foreach ($arr as $key => $val) {
            if (\is_string($key) && \substr($key, -1) === self::PreventMergingSuffix) {
                if (!\is_array($val) && $val !== null) {
                    throw new \Packetery\Nette\DI\InvalidConfigurationException(\sprintf("Replacing operator is available only for arrays, item '%s' is not array (used in '%s')", $key, $this->file));
                }
                $key = \substr($key, 0, -1);
                $val[Helpers::PREVENT_MERGING] = \true;
            }
            if (\is_array($val)) {
                $val = $this->process($val);
            } elseif ($val instanceof Neon\Entity) {
                if ($val->value === Neon\Neon::CHAIN) {
                    $tmp = null;
                    foreach ($this->process($val->attributes) as $st) {
                        $tmp = new Statement($tmp === null ? $st->getEntity() : [$tmp, \ltrim(\implode('::', (array) $st->getEntity()), ':')], $st->arguments);
                    }
                    $val = $tmp;
                } else {
                    $tmp = $this->process([$val->value]);
                    if (\is_string($tmp[0]) && \strpos($tmp[0], '?') !== \false) {
                        throw new \Packetery\Nette\DI\InvalidConfigurationException("Operator ? is deprecated in config file (used in '{$this->file}')");
                    }
                    $val = new Statement($tmp[0], $this->process($val->attributes));
                }
            }
            $res[$key] = $val;
        }
        return $res;
    }
    /**
     * Generates configuration in NEON format.
     */
    public function dump(array $data) : string
    {
        \array_walk_recursive($data, function (&$val) : void {
            if ($val instanceof Statement) {
                $val = self::statementToEntity($val);
            }
        });
        return "# generated by \Packetery\Nette\n\n" . Neon\Neon::encode($data, Neon\Neon::BLOCK);
    }
    private static function statementToEntity(Statement $val) : Neon\Entity
    {
        \array_walk_recursive($val->arguments, function (&$val) : void {
            if ($val instanceof Statement) {
                $val = self::statementToEntity($val);
            } elseif ($val instanceof Reference) {
                $val = '@' . $val->getValue();
            }
        });
        $entity = $val->getEntity();
        if ($entity instanceof Reference) {
            $entity = '@' . $entity->getValue();
        } elseif (\is_array($entity)) {
            if ($entity[0] instanceof Statement) {
                return new Neon\Entity(Neon\Neon::CHAIN, [self::statementToEntity($entity[0]), new Neon\Entity('::' . $entity[1], $val->arguments)]);
            } elseif ($entity[0] instanceof Reference) {
                $entity = '@' . $entity[0]->getValue() . '::' . $entity[1];
            } elseif (\is_string($entity[0])) {
                $entity = $entity[0] . '::' . $entity[1];
            }
        }
        return new Neon\Entity($entity, $val->arguments);
    }
    /** @internal */
    public function removeUnderscoreVisitor(Neon\Node $node)
    {
        if (!$node instanceof Neon\Node\EntityNode) {
            return;
        }
        $index = \false;
        foreach ($node->attributes as $i => $attr) {
            if ($attr->key !== null) {
                continue;
            }
            $attr->key = $index ? new Neon\Node\LiteralNode((string) $i) : null;
            if ($attr->value instanceof Neon\Node\LiteralNode && $attr->value->value === '_') {
                unset($node->attributes[$i]);
                $index = \true;
            } elseif ($attr->value instanceof Neon\Node\LiteralNode && $attr->value->value === '...') {
                \trigger_error("Replace ... with _ in configuration file '{$this->file}'.", \E_USER_DEPRECATED);
                unset($node->attributes[$i]);
                $index = \true;
            }
        }
    }
    /** @internal */
    public function convertAtSignVisitor(Neon\Node $node)
    {
        if ($node instanceof Neon\Node\StringNode) {
            if (\substr($node->value, 0, 2) === '@@') {
                \trigger_error("There is no need to escape @ anymore, replace @@ with @ in: '{$node->value}' (used in {$this->file})", \E_USER_DEPRECATED);
            } else {
                $node->value = \preg_replace('#^@#', '$0$0', $node->value);
                // escape
            }
        } elseif ($node instanceof Neon\Node\LiteralNode && \is_string($node->value) && \substr($node->value, 0, 2) === '@@') {
            \trigger_error("There is no need to escape @ anymore, replace @@ with @ and put string in quotes: '{$node->value}' (used in {$this->file})", \E_USER_DEPRECATED);
        }
    }
}
