<?php

namespace Shm\ShmTypes\Utils;

use Shm\Shm;
use Shm\ShmTypes\StructureType;
use Shm\ShmTypes\BaseType;
use Shm\ShmDB\mDB;
use Shm\ShmDB\mDBRedis;
use Shm\ShmTypes\ArrayOfType;
use Shm\ShmTypes\CompositeTypes\FileTypes\FileIDType;
use Shm\ShmTypes\CompositeTypes\FileTypes\Utils\FileIDResolver;
use Shm\ShmTypes\IDsType;
use Shm\ShmTypes\IDType;
use Shm\ShmUtils\Response;

class ExtensionsResolver
{



    public BaseType | null $structure = null;
    public mixed $data = null;


    public array $rootExtensions = [];
    public array $oneSubLevelExtensions = [];
    public array $twoSubLevelExtensions = [];



    public function __construct($structure, mixed $data, array $extensions)
    {

        foreach ($extensions as $extension) {
            $parts = explode('->', $extension);
            if (count($parts) == 1) {
                $this->rootExtensions[] = $parts[0];
            } else {
                $this->rootExtensions[] = $parts[0];

                if (isset($parts[1])) {
                    if (!isset($this->oneSubLevelExtensions[$parts[0]])) {
                        $this->oneSubLevelExtensions[$parts[0]] = [];
                    }
                    $this->oneSubLevelExtensions[$parts[0]][] = $parts[1];
                }

                if (isset($parts[2])) {
                    if (!isset($this->twoSubLevelExtensions[$parts[0]])) {
                        $this->twoSubLevelExtensions[$parts[0]] = [];
                    }
                    $this->twoSubLevelExtensions[$parts[0]][] = $parts[2];
                }
            }
        }



        $this->structure = $structure;
        $this->data = $data;
    }

    private function getPathValues(array $path, mixed $value): array
    {

        $result = [];

        $firstSegment = array_shift($path);
        if (count($path) == 0) {

            if ($firstSegment === '[]') {
                if ($value) {
                    return $value;
                } else {
                    return [];
                }
            } else {
                if (isset($value[$firstSegment])) {
                    return [$value[$firstSegment]];
                } else {
                    return [];
                }
            }
        }





        if ($firstSegment === '[]') {



            if (is_object($value) || is_array($value) || $value instanceof \Traversable) {
                foreach ($value as $item) {


                    $vals = $this->getPathValues($path, $item);
                    $result = [...$result, ...$vals];
                }
            }
        } else {



            if (isset($value[$firstSegment])) {

                $vals = $this->getPathValues($path, $value[$firstSegment]);


                $result = [...$result, ...$vals];
            }
        }


        return $result;
    }



    private $extensionsData = [];

    private $resultExtensionItems = [];


    private function loadExtensionsData(StructureType | ArrayOfType $structure, array $extensionsCollections, $data)
    {


        if ($structure instanceof ArrayOfType) {
            $structure->itemType->updateKeys();
        } else {
            $structure->updateKeys();
        }
        $structure->updatePath();



        $extensionsStructure = $structure->extensionsStructure();


        $enabledExtensions = array_keys($extensionsStructure);



        //Если нет коллекций, то выходим
        if (count($extensionsCollections) == 0) {
            return;
        }

        $extensionsCollections = array_intersect($extensionsCollections, $enabledExtensions);

        //Если нет доступных расширений, то выходим
        if (count($extensionsCollections) == 0) {
            return;
        }



        //Находим ключи для загрузки вложенных данных
        $extensionItems = $structure->findItemsByCondition(function ($item) use ($extensionsCollections) {
            if ($item instanceof IDType || $item instanceof IDsType) {

                if (in_array($item->collection, $extensionsCollections)) {
                    return true;
                }
            }
            return false;
        });



        if (count($extensionItems) == 0) {
            return;
        }


        //ID что нужны по каждой колекции
        $documentIds = [];




        foreach ($extensionItems as $extensionItem) {
            $path = $extensionItem->path;

            if ($extensionItem instanceof IDsType) {
                $path[] = '[]';
            }
            $documentIdsFromPath =  $this->getPathValues($path, $data);

            if (isset($documentIds[$extensionItem->collection])) {
                $documentIds[$extensionItem->collection] = [...$documentIds[$extensionItem->collection], ...$documentIdsFromPath];
            } else {
                $documentIds[$extensionItem->collection] = $documentIdsFromPath;
            }
        }




        if (count($documentIds) == 0) {
            return;
        }



        //Документы что нужно загрузить из mongodb которых нет Redis
        $_documentIds = [];

        foreach ($documentIds as $collection => $documentIds) {

            $documentIds = array_unique($documentIds);

            $documentIdsForMongoDB = [];

            foreach ($documentIds as $documentId) {
                $cacheValue = mDBRedis::get($collection, (string)$documentId);
                if ($cacheValue) {


                    if (!isset($this->extensionsData[$collection])) {
                        $this->extensionsData[$collection] = [];
                    }



                    $this->extensionsData[$collection][] = $cacheValue;
                } else {
                    $documentIdsForMongoDB[] = $documentId;
                }
            }


            $_documentIds[$collection] = $documentIdsForMongoDB;
        }






        //Загружаем данные из mongodb
        foreach ($_documentIds as $collection => $documentIds) {





            $structure = $extensionsStructure[$collection];

            if ($structure instanceof StructureType) {

                $arrayOfStructure = Shm::arrayOf($structure);

                $this->resultExtensionItems[$collection] =  $arrayOfStructure;

                if (count($documentIds) == 0) {
                    continue;
                }



                $_data = $structure->find(['_id' => ['$in' => $documentIds]]);

                foreach ($_data as $item) {
                    mDBRedis::save($structure->collection, (string)$item['_id'], $item);
                }


                if (isset($this->extensionsData[$collection])) {
                    $this->extensionsData[$collection] = [...$this->extensionsData[$collection], ...$_data];
                } else {
                    $this->extensionsData[$collection] = $_data;
                }
            }
        }
    }



    public function resolve()
    {



        if (!$this->structure || !$this->data || count($this->rootExtensions) == 0) {
            return null;
        }


        if (!($this->structure instanceof StructureType || $this->structure instanceof ArrayOfType)) {
            return null;
        }

        Response::startTraceTiming("extensionsResolver");






        $this->loadExtensionsData($this->structure, $this->rootExtensions, $this->data);



        if (count($this->oneSubLevelExtensions) > 0) {


            //TODO: тут можно оптимизировать загружая данные сразу для нескольких $collection если у них повторяются ключи
            foreach ($this->oneSubLevelExtensions as $collection => $extensions) {



                if (isset($this->resultExtensionItems[$collection]) && isset($this->extensionsData[$collection])) {
                    $this->loadExtensionsData($this->resultExtensionItems[$collection], $extensions, $this->extensionsData[$collection]);
                }
            }
        }

        if (count($this->twoSubLevelExtensions) > 0) {


            //TODO: тут можно оптимизировать загружая данные сразу для нескольких $collection если у них повторяются ключи
            foreach ($this->twoSubLevelExtensions as $collection => $extensions) {


                if (isset($this->resultExtensionItems[$collection]) && isset($this->extensionsData[$collection])) {
                    $this->loadExtensionsData($this->resultExtensionItems[$collection], $extensions, $this->extensionsData[$collection]);
                }
            }
        }

        $resultStructure = Shm::structure($this->resultExtensionItems)->key("base");



        $this->extensionsData = $resultStructure->normalize($this->extensionsData);
        $this->extensionsData = $resultStructure->removeOtherItems($this->extensionsData);



        $fileIDResolver = new FileIDResolver($resultStructure, $this->extensionsData);
        $this->extensionsData = $fileIDResolver->resolve();



        $this->extensionsData = mDB::replaceObjectIdsToString($this->extensionsData);




        Response::endTraceTiming("extensionsResolver");

        return  $this->extensionsData;
    }
}