<?php

namespace Shop\Infrastructure\Repositories\ProductCategory;

trait RecursiveTrait
{
    private static  $id = 'Id';
    private static  $parentId = 'ParentId';
    private static  $children = 'Children';
    private static  $parents = 'Parents';

    private static  $categoryTree = null;
    private static  $allCategories = null;
    private static  $categoryTreeByLevel = null;

    private static  $ttl = 600;
    private static  $maxNestingLevel = 5;

    /**
     * Получить весь список категорий
     */
    public static function getAllCategories(): ?array
    {
        $cacheKey = static::getCacheKeyOfAllCategories();

        if (!static::$allCategories) {
            static::$allCategories = Cache::remember($cacheKey, self::$ttl, function () {
                return static::findAllCategories();
            });
        }

        return static::$allCategories;
    }

    /**
     * Получить многоуровневое дерево категорий
     */
    public static function getCategoriesTree(): ?array
    {
        $cacheKey = static::getCacheKeyOfCategoryTree();

        if (!static::$categoryTree) {
            static::$categoryTree = Cache::remember($cacheKey, self::$ttl, function () {
                $productCategories = static::getAllCategories();

                return static::buildCategoryTree($productCategories);
            });
        }

        return static::$categoryTree;
    }

    /**
     * Рекурсивное создание дерева категорий
     */
    public static function buildCategoryTree(array $productCategories, ?int $parentId = null): array
    {
        $tree = [];

        foreach ($productCategories as $productCategory) {

            if ($productCategory[self::$parentId] === $parentId) {
                $children = static::buildCategoryTree(
                    $productCategories,
                    $productCategory[self::$id]
                );
                $productCategory[self::$children] = null;
                $productCategory[self::$parents] = null;

                if ($children) {
                    $productCategory[self::$children] = array_values($children);
                }

                $tree[$productCategory[self::$id]] = $productCategory;
            }
        }

        return $tree;
    }

    /**
     * Получить текущую категорию из дерева с массивами потомков и предков
     */
    public static function getCategoriesTreeById(int $id): array
    {
        $productCategoryParents = [];
        $productCategoryParentsIds = static::getCategoryParentsById($id);

        if (count($productCategoryParentsIds) > 0) {
            $productCategories = static::getAllCategories();

            foreach ($productCategoryParentsIds as $productCategoryParentId) {
                $productCategoryParents[$productCategoryParentId] = array_merge(
                    $productCategories[$productCategoryParentId],
                    ['children' => null, 'parents' => null]
                );
            }
        }

        $current = self::getCategoryByIdsPath(array_merge($productCategoryParentsIds, [$id]), static::getCategoriesTree());

        if (empty($current)) {
            return [];
        }
        $current['parents'] = array_values($productCategoryParents);

        return $current;
    }

    /**
     * Получить детей категории по идентификатору
     */
    public static function getCategoryChildrenById(int $id): array
    {
        $result = self::getCategoryByIdsPath(array_merge(static::getCategoryParentsById($id), [$id]), static::getCategoriesTree());

        return $result[self::$children] ?? [];
    }

    /**
     * Получить текущую категорию из дерева по пути идентификаторов
     */
    private static function getCategoryByIdsPath(array $ids, array $categoriesTree): array
    {
        $id = array_shift($ids);

        foreach ($categoriesTree as $category) {

            if ($id === $category[self::$id]) {

                if (empty($ids) || !$category[self::$children]) {
                    return $category;
                }

                return self::getCategoryByIdsPath($ids, $category[self::$children]);
            }
        }

        return [];
    }

    /**
     * Получить категории по уровню вложенности с массивами потомков и предков
     */
    public static function getCategoriesTreeByLevel(int $level): array
    {
        if ($level > self::$maxNestingLevel) {
            throw new \Exception('Maximum nesting level is ' . self::$maxNestingLevel);
        }

        $cacheKey = static::getCacheKeyOfCategoryTreeByLevel($level);

        if (!static::$categoryTreeByLevel) {
            static::$categoryTreeByLevel = Cache::remember($cacheKey, self::$ttl, function () use ($level) {
                $productCategories = static::findAllCategoriesByLevel($level);
                $categoryTreeByLevel = [];

                foreach ($productCategories as $productCategory) {
                    $categoryTreeByLevel[] = static::getCategoriesTreeById($productCategory['id']);
                }

                return array_filter($categoryTreeByLevel);
            });
        }

        return static::$categoryTreeByLevel;
    }

    public static function clearCacheOfCategories(): void
    {
        Cache::forget(static::getCacheKeyOfCategoryTree());
        Cache::forget(static::getCacheKeyOfAllCategories());

        for ($i = 0; $i <= self::$maxNestingLevel; $i++) {
            Cache::forget(static::getCacheKeyOfCategoryTreeByLevel($i));
        }
    }

    /**
     * Получить массив с id всех предков
     */
    public static function getCategoryParentsById(int $id, bool $withUnpublished = false, array &$parents = []): array
    {
        if (!static::$allCategories) {
            static::getAllCategories();
        }
        $parentId = (isset(static::$allCategories[$id]) && ($withUnpublished)) ? static::$allCategories[$id][self::$parentId] : null;

        if (isset($parentId)) {
            array_unshift($parents, $parentId);
            return static::getCategoryParentsById($parentId, $withUnpublished, $parents);
        }

        return $parents;
    }

    /**
     * Получить дерево категорий продавца
     */
    public static function getCategoriesTreeBySellerCategoriesIds(array $sellerCategoriesIds): array
    {
        $allCategories = static::getAllCategories();
        $usedCategoriesIds = [];
        $categoriesTree = [];

        foreach ($sellerCategoriesIds as $categoryId) {
            if (in_array($categoryId, $usedCategoriesIds)) {
                continue;
            }

            $currentCategory = $allCategories[$categoryId];
            $categoriesTree[] = $currentCategory;
            $usedCategoriesIds[] = $categoryId;
            $parentId = $currentCategory[self::$parentId];

            if ($parentId === null) {
                continue;
            }

            static::addParentCategory($parentId, $allCategories, $usedCategoriesIds, $categoriesTree);
        }

        return static::buildCategoryTree(
            $categoriesTree
        );
    }

    private static function addParentCategory(int $parentId, array $allCategories, array &$usedCategoriesIds, array &$categoriesTree): void
    {
        if (in_array($parentId, $usedCategoriesIds)) {
            return;
        }

        $parentCategory = $allCategories[$parentId];
        $categoriesTree[] = $parentCategory;
        $usedCategoriesIds[] = $parentId;
        $parentId = $parentCategory[self::$parentId];

        if ($parentId === null) {
            return;
        }

        static::addParentCategory($parentId, $allCategories, $usedCategoriesIds, $categoriesTree);
    }

    /**
     * Получить массив с id категории и всех ее потомков
     */
    public static function getChildrenCategoriesIds(int $id): array
    {
        $ids[] = $id;
        $categoryTree = static::getCategoryChildrenById($id);

        return self::getChildrenCategoryId($categoryTree, $ids);
    }

    private static function getChildrenCategoryId(array $categoriesTree, array $ids): array
    {
        foreach ($categoriesTree as $category) {
            $ids[] = $category[self::$id];

            if (!$category[self::$children]) {
                continue;
            }

            return self::getChildrenCategoryId($category[self::$children], $ids);
        }

        return $ids;
    }

    private static function getCacheKeyOfCategoryTree(): string
    {
        return __CLASS__ . ":cacheCategoriesTree";
    }

    private static function getCacheKeyOfCategoryTreeByLevel(int $level): string
    {
        return __CLASS__ . ":cacheCategoriesTreeByLevel" . $level;
    }

    private static function getCacheKeyOfAllCategories(): string
    {
        return __CLASS__ . ":cacheAllCategories";
    }
}