<?php

namespace KupShop\FeedGeneratorBundle\Expressions;

use KupShop\FeedGeneratorBundle\Utils\ObjectInfoTrait;
use phpDocumentor\Reflection\DocBlockFactory;

trait ExpressionObjectTrait
{
    use ObjectInfoTrait;
    public $jsObject;

    /**
     * @throws \ReflectionException
     *
     * @private
     */
    public function getRawFieldsAndMethods(): array
    {
        // TODO: refactor because accessing static members in traits is deprecated since PHP 8.1
        // https://www.php.net/manual/en/language.oop5.traits.php#language.oop5.traits.static
        static $cache;
        // reasoning for using [static::class]
        // static is supposed to be stored in the context of a specific class, but sometimes it doesn't work #fuckPHP
        if (isset($cache[static::class])) {
            return $cache[static::class];
        }

        $introspection = [];

        $f = new \ReflectionClass($this);

        $docBlockFactory = DocBlockFactory::createInstance();

        $ignoredClasses = [
            \KupShop\FeedsBundle\Wrapper\BaseWrapper::class,
            \KupShop\FeedsBundle\Wrapper\MultiWrapper::class,
            \KupShop\FeedsBundle\Wrapper\ObjectMultiWrapper::class,
        ];
        foreach ($f->getMethods() as $method) {
            if (array_search($method->class, $ignoredClasses) !== false || !$method->isPublic() || $method->isConstructor()) {
                continue;
            }

            if ($method->getDocComment()) {
                $docBlock = $docBlockFactory->create($method);

                if ($docBlock->getTagsByName('private') || $docBlock->getTagsByName('implementedInJS') || $this->requiredModuleIsDisabled($docBlock)) {
                    continue;
                }
            }

            if (substr($method->name, 0, 6) == 'field_') {
                $name = substr($method->name, 6);
                $isMethod = false;
            } else {
                $name = $method->name;
                $isMethod = true;
            }

            $introspection[] = ['name' => $name, 'method' => $isMethod];
        }

        $cache = $cache ?? []; // make sure $cache is an array

        return $cache[static::class] = $introspection;
    }

    /**
     * @throws \ReflectionException
     *
     * @private
     */
    public function getFieldsAndMethodsNames(): array
    {
        // TODO: refactor because accessing static members in traits is deprecated since PHP 8.1
        // https://www.php.net/manual/en/language.oop5.traits.php#language.oop5.traits.static
        static $cache;
        // reasoning for using [static::class]
        // static is supposed to be stored in the context of a specific class, but sometimes it doesn't work #fuckPHP
        if (isset($cache[static::class])) {
            return $cache[static::class];
        }

        $cache = $cache ?? []; // make sure $cache is an array

        return $cache[static::class] = array_map(function ($introspection) { return $introspection['name']; }, $this->getRawFieldsAndMethods());
    }

    /** @private */
    protected function isOffsetAllowed($name): bool
    {
        // TODO: refactor because accessing static members in traits is deprecated since PHP 8.1
        // https://www.php.net/manual/en/language.oop5.traits.php#language.oop5.traits.static
        static $cache;
        // reasoning for using [static::class]
        // static is supposed to be stored in the context of a specific class, but sometimes it doesn't work #fuckPHP

        $cache = $cache ?? []; // make sure $cache is an array

        if (!isset($cache[static::class])) {
            $cache[static::class] = array_combine($this->getFieldsAndMethodsNames(), $this->getFieldsAndMethodsNames());
        }

        return isset($cache[static::class][$name]);
    }

    /**
     * @private
     */
    public function offsetExists($offset): bool
    {
        return $this->isOffsetAllowed($offset) && (isset($this->{$offset}) ? true : method_exists($this, $this->offsetMethod($offset)));
    }

    /**
     * @private
     */
    public function offsetUnset($offset): void
    {
        unset($this->{$offset});
    }

    /**
     * @return mixed|null
     *
     * @private
     */
    public function offsetGet($offset): mixed
    {
        // protect private properties
        if (!$this->isOffsetAllowed($offset)) {
            return null;
        }

        $method = $this->offsetMethod($offset);
        if (method_exists($this, $method)) {
            $res = call_user_func([$this, $method]);

            return $res;
        }

        return isset($this->{$offset}) ? $this->{$offset} : null;
    }

    /**
     * @private
     */
    public function offsetSet($offset, $value): void
    {
        // TODO: Implement offsetSet() method.
    }

    /**
     * @return string
     *
     * @private
     */
    public function offsetMethod($offset)
    {
        return 'field_'.$offset;
    }

    /** @private */
    public function __set($name, $value)
    {
        return $this->offsetSet($name, $value);
    }

    /** @private */
    public function __get($name)
    {
        return $this->offsetGet($name);
    }

    /** @private */
    public function __isset($name)
    {
        return $this->offsetExists($name);
    }

    /** @private */
    public function __unset($name)
    {
        $this->offsetUnset($name);

        return null;
    }
}
