<?php declare(strict_types = 1);

namespace idoit\Module\SyneticsFlows\Template\TemplateVariables;

use ArrayIterator;
use Exception;
use idoit\Module\SyneticsFlows\Model\Dto\ObjectType;
use idoit\Module\SyneticsFlows\Model\ObjectTypeDao;
use idoit\Module\SyneticsFlows\Serialization\Serializer;
use isys_application;
use isys_exception_database;
use IteratorAggregate;

class ObjectTypesTemplate implements IteratorAggregate
{
    private ?array $types = null;
    private ?ObjectTypeDao $objectTypeDao = null;

    /**
     * @param string $path
     * @return string|null
     * @throws isys_exception_database
     */
    public function getTypeAttribute(string $path): ?string
    {
        $this->resolveTypes();

        [$type, $attribute] = explode('.', $path);

        if (is_numeric($type)) {
            foreach ($this->types as $typeObj) {
                if ((int)$typeObj->getId() === (int) $type) {
                    return $typeObj->$attribute;
                }
            }
            return null;
        }

        if (!str_starts_with($path, 'C__OBJTYPE')) {
            return null;
        }

        return $this->types[$type]->$attribute;

    }

    /**
     * @param string|int $id
     * @return ObjectType|null
     * @throws isys_exception_database
     */
    private function getById(string|int $id): ?ObjectType
    {
        if (!count($this->types)) {
            $this->resolveTypes();
        }

        foreach ($this->types as $typeObj) {
            if ((int)$typeObj->getId() === (int) $id) {
                return $typeObj;
            }
        }

        return null;
    }

    public function __get(string $name)
    {
        $this->resolveTypes();

        if (is_numeric($name)) {
            return $this->getById($name);
        }

        if (isset($this->types[$name])) {
            return $this->types[$name];
        }

        $parts = explode('.', $name);
        if (count($parts) === 2) {
            [$type, $attribute] = $parts;
            $typeObj = null;
            if (str_starts_with($type, 'C__OBJTYPE')) {
                $typeObj = $this->types[$type] ?? null;
            }
            // passed id
            if (is_numeric($type)) {
                $typeObj = $this->getById($name);
            }

            if (!$typeObj) {
                return null;
            }

            $data = Serializer::toJson($typeObj);

            return $data[$attribute] ?? null;
        }

        return null;
    }

    public function __isset(mixed $name)
    {
        return true;
    }

    public function __toString(): string
    {
        $this->resolveTypes();
        return json_encode($this->types, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT);
    }

    /**
     * @return void
     * @throws isys_exception_database
     */
    private function resolveTypes()
    {
        $data = $this->getObjectTypeDao()->getData();
        $typesMap = [];

        foreach ($data as $type) {
            $typesMap[$type->getConstant()] = $type;
        }

        $this->types = $typesMap;
    }

    /**
     * @return ObjectTypeDao
     * @throws Exception
     */
    private function getObjectTypeDao(): ObjectTypeDao
    {
        if (!$this->objectTypeDao) {
            $this->objectTypeDao = isys_application::instance()->container->get('idoit.flows.object-type.dao');
        }
        return $this->objectTypeDao;
    }

    /**
     * @return ArrayIterator
     * @throws isys_exception_database
     */
    public function getIterator(): ArrayIterator
    {
        $this->resolveTypes();
        return new ArrayIterator($this->types);
    }
}