<?php
/**
 * Copyright 2020 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\Context;

use Magento\Mray\CodeStructuralElement\Php\Reflection;

trait VariableScope
{
    /**
     * @var Reflection\Variable[]
     */
    private $variables = [];

    /**
     * @return Global_
     */
    abstract public function getGlobal(): Global_;

    /**
     * @param Reflection\VariableDefinition $def
     */
    public function registerVariableDefinition(Reflection\VariableDefinition $def): void
    {
        $accessor = $def->getAccessor();
        if (!$accessor->resolvable()) { // name cannot be resolved, ignore
            return;
        }
        $accessPath = $accessor->getPath();
        /** @var Reflection\ValuePointer $expectedVar */
        $expectedVar = array_shift($accessPath);
        $varName = $expectedVar->getName();

        $knownVariable = $this->variables[$varName] ?? null;
        if (!$knownVariable && $def instanceof Reflection\VariableDefinition\GlobalVariableUse) {
            $knownVariable = $this->getGlobal()->getVariable($varName);
        }

        $isVariableDefinition = empty($accessPath);
        if ($isVariableDefinition && $knownVariable) {
            $this->variables[$varName] = $knownVariable->redefine($def);
        } elseif ($isVariableDefinition && !$knownVariable) {
            $this->variables[$varName] = new Reflection\Variable($def);
        } elseif (!$isVariableDefinition && $knownVariable) {
            $this->variables[$varName] = $knownVariable;
        } elseif (!$isVariableDefinition && !$knownVariable) {
            $this->variables[$varName] = new Reflection\Variable(
                new Reflection\VariableDefinition\ImplicitlyDefinedByValueAccess(
                    new Reflection\ValueAccessor\KnownPath([$expectedVar])
                )
            );
        }

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

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

        $superGlobals = $this->getGlobal()->getSuperGlobalVariables();
        if (isset($superGlobals[$name])) {
            return $superGlobals[$name];
        }

        return new Reflection\Variable(
            new Reflection\VariableDefinition\Undefined($name)
        );
    }

    /**
     * @return Reflection\Variable[]
     */
    public function getVariables(): array
    {
        return array_merge($this->getGlobal()->getSuperGlobalVariables(), $this->variables);
    }
}
