<?php
/**
 * Copyright 2022 Adobe
 * All Rights Reserved.
 *
 * NOTICE: Adobe permits you to use, modify, and distribute this file in
 * accordance with the terms of the Adobe license agreement accompanying
 * it.
 */
declare(strict_types=1);

namespace Magento\Mray\CodeStructuralElement\Php\Reflection;

use RuntimeException;

class InnerContainer implements VariablesContainer
{
    /**
     * @var ValuePointer[]
     */
    private $baseAccessPath;

    /**
     * @var Variable[]
     */
    private $variables = [];

    /**
     * @param array $baseAccessPath
     */
    public function __construct(array $baseAccessPath)
    {
        $this->baseAccessPath = $baseAccessPath;
    }

    /**
     * @param VariableDefinition $def
     */
    public function registerVariableDefinition(VariableDefinition $def): void
    {
        $accessor = $def->getAccessor();
        if (!$accessor->resolvable()) {
            return;
        }

        $relativeAccessPath = $this->getRelativeAccessPath($accessor->getPath());
        /** @var ValuePointer $expectedVar */
        $expectedVar = array_shift($relativeAccessPath);
        $varName = $expectedVar->getName();

        $isKnown = isset($this->variables[$varName]);
        $isLeaf = empty($relativeAccessPath);
        if ($isLeaf && $isKnown) {
            $this->variables[$varName] = $this->variables[$varName]->redefine($def);
        } elseif ($isLeaf && !$isKnown) {
            $this->variables[$varName] = new Variable($def);
        } elseif (!$isLeaf && !$isKnown) {
            $fullAccessPath = $this->baseAccessPath;
            $fullAccessPath[] = $expectedVar;
            $this->variables[$varName] = new Variable(
                new VariableDefinition\ImplicitlyDefinedByValueAccess(
                    new ValueAccessor\KnownPath($fullAccessPath)
                )
            );
        }

        if (!$isLeaf) {
            $this->variables[$varName]->getInnerContainer()->registerVariableDefinition($def);
        }
    }

    /**
     * @param array $accessPath
     * @return array
     */
    private function getRelativeAccessPath(array $accessPath): array
    {
        foreach ($this->baseAccessPath as $cursor) {
            $accessPathPointer = array_shift($accessPath);
            if ($cursor->getName() !== $accessPathPointer->getName()) {
                throw new RuntimeException(
                    'Provided value access path is not does not have expected base path.'
                );
            }
        }
        return $accessPath;
    }

    /**
     * @param string $name
     * @return Variable
     */
    public function getVariable(string $name): Variable
    {
        if (isset($this->variables[$name])) {
            return $this->variables[$name];
        }

        return new Variable(
            new VariableDefinition\Unknown($name)
        );
    }

    /**
     * @inheritDoc
     */
    public function getVariables(): array
    {
        return $this->variables;
    }
}
