<?php

namespace Snaju\Inception\ORM;

use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Collections\Collection;
use Laminas\Code\Reflection\ClassReflection;
use Snaju\Inception\Annotation\LazyLoader;
use Snaju\Inception\Annotation\Load;
use Snaju\Inception\Util\ArrayUtil;

class DataModelLazyLoader
{

    private DataModel $model;

    private array $array = [];

    /**
     * @param DataModel $model
     */
    public function __construct(DataModel $model)
    {
        $this->model = $model;
        $this->model->toTemplateObject();
        $this->model->_hydrate();
    }

    public function load(string ...$multiKeys): DataModelLazyLoader
    {
        foreach ($multiKeys as $key) {
            $pointer = &$this->model;
            $keys = explode(".", $key);
            foreach ($keys as $k) {
                if (!isset($pointer->{$k})) {
                    return $this;
                }

                $pointer = &$pointer->{$k};
            }

            ArrayUtil::toArray($this->array, $key, $pointer);
        }

        return $this;
    }

    public function set(string $key, $data)
    {
        ArrayUtil::toArray($this->array, $key, $data);
    }

    private function getPointer(string $key)
    {
        $keys = explode(".", $key);
        foreach ($keys as $k) {
            if (!isset($pointer->{$k})) {
                return $this;
            }

            $pointer = &$pointer->{$k};
        }
        return $pointer;
    }

    public function loadCollection(string $mainKey, bool $loadMeta = true, string ...$multiKeys): DataModelLazyLoader
    {
        $pointer = $this->getPointer($mainKey);
        if ($pointer instanceof Collection) {
            foreach ($pointer as $obj) {
                if ($obj instanceof DataModel) {
                    $l2 = new DataModelLazyLoader($obj);
                    if ($loadMeta) {
                        $l2->loadMeta();
                    }
                    foreach ($multiKeys as $key) {
                        $l2->load($key);
                    }

                    ArrayUtil::toArray($this->array, $mainKey, $l2->get());
                }
            }
        }
        return $this;
    }

    public function loadChild(string $mainKey, bool $loadMeta = true, string ...$multiKeys): DataModelLazyLoader
    {
        $l2 = new DataModelLazyLoader($this->getPointer($mainKey));
        if ($loadMeta) {
            $l2->loadMeta();
        }
        foreach ($multiKeys as $k) {
            $l2->load($k);
        }
//        $this->array = array_merge_recursive($this->array, $l2->get());
        ArrayUtil::toArray($this->array, $mainKey, $l2->get());
        return $this;
    }

    public function lazyLoader(string $mainKey, DataModelLazyLoader $loader): DataModelLazyLoader
    {
        ArrayUtil::toArray($this->array, $mainKey, $loader->get());
        return $this;
    }

    public function loadMeta(string $loadToKey = null): DataModelLazyLoader
    {
        if ($loadToKey == null) {
            $this->array = array_merge_recursive($this->array, $this->model->getMetaArray());
        } else {
            ArrayUtil::toArray($this->array, $loadToKey, $this->model->getMetaArray());
        }

        return $this;
    }

    public function get(): array
    {
        return $this->array;
    }

    public static function fromAnnotations(DataModel $model): ?DataModelLazyLoader
    {
        $reader = new AnnotationReader();

        $class = new ClassReflection($model::getClassName());
        if ($class instanceof ClassReflection) {

            $ll = $reader->getClassAnnotation($class, LazyLoader::class);
            if ($ll instanceof LazyLoader) {

                $l = new DataModelLazyLoader($model);
                if ($ll->loadMetaToKey != null) {
                    $l->loadMeta($ll->loadMetaToKey);
                }

                foreach ($class->getProperties() as $property) {
                    if ($property instanceof \ReflectionProperty) {

                        $p = $reader->getPropertyAnnotation($property, Load::class);
                        if ($p instanceof Load) {

                            // Is a LazyLoader property
                            $val = $model->{$property->getName()};
                            if ($val instanceof DataModel) {
                                $l->lazyLoader($property->getName(), self::fromAnnotations($val));
                            } else if ($val instanceof Collection) {
                                $a = [];
                                foreach ($val as $subK => $subV) {
                                    $l2 = self::fromAnnotations($subV);
                                    if ($l2 instanceof DataModelLazyLoader) {
                                        $a[$subK] = $l2->get();
                                    }
                                }
                                $l->set($property->getName(), $a);
                            } else {
                                $l->load($property->getName());
                            }
                        }
                    }
                }

                return $l;
            }
        }

        return null;
    }

}