<?php

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Persistence\Database;

/**
 * Eager loading для связанных моделей
 *
 * @package Flowaxy\Infrastructure\Persistence\Database
 */
final class EagerLoader
{
    /**
     * Загрузить связанные модели
     *
     * @param array<int, array<string, mixed>> $models Основные модели
     * @param array<int, string> $relations Список связей для загрузки
     * @param callable $loader Функция загрузки связанных моделей
     * @return array<int, array<string, mixed>>
     */
    public function load(array $models, array $relations, callable $loader): array
    {
        if (empty($models) || empty($relations)) {
            return $models;
        }

        foreach ($relations as $relation) {
            $models = $this->loadRelation($models, $relation, $loader);
        }

        return $models;
    }

    /**
     * Загрузить одну связь
     *
     * @param array<int, array<string, mixed>> $models
     * @param string $relation
     * @param callable $loader
     * @return array<int, array<string, mixed>>
     */
    private function loadRelation(array $models, string $relation, callable $loader): array
    {
        // Извлекаем ключи для загрузки связанных моделей
        $keys = $this->extractKeys($models, $relation);

        if (empty($keys)) {
            return $models;
        }

        // Загружаем связанные модели
        $related = $loader($relation, $keys);

        // Присоединяем связанные модели к основным
        return $this->attachRelations($models, $relation, $related);
    }

    /**
     * Извлечь ключи из моделей
     *
     * @param array<int, array<string, mixed>> $models
     * @param string $relation
     * @return array<int, mixed>
     */
    private function extractKeys(array $models, string $relation): array
    {
        $keys = [];
        $foreignKey = $this->getForeignKey($relation);

        foreach ($models as $model) {
            if (isset($model[$foreignKey])) {
                $keys[] = $model[$foreignKey];
            }
        }

        return array_unique($keys);
    }

    /**
     * Получить имя внешнего ключа для связи
     *
     * @param string $relation
     * @return string
     */
    private function getForeignKey(string $relation): string
    {
        // Простая логика: если связь называется "user", то внешний ключ "user_id"
        return $relation . '_id';
    }

    /**
     * Присоединить связанные модели к основным
     *
     * @param array<int, array<string, mixed>> $models
     * @param string $relation
     * @param array<int, array<string, mixed>> $related
     * @return array<int, array<string, mixed>>
     */
    private function attachRelations(array $models, string $relation, array $related): array
    {
        $foreignKey = $this->getForeignKey($relation);
        $relatedByKey = [];

        // Индексируем связанные модели по ключу
        foreach ($related as $item) {
            $key = $item['id'] ?? null;
            if ($key !== null) {
                $relatedByKey[$key] = $item;
            }
        }

        // Присоединяем связанные модели
        foreach ($models as &$model) {
            $key = $model[$foreignKey] ?? null;
            if ($key !== null && isset($relatedByKey[$key])) {
                $model[$relation] = $relatedByKey[$key];
            } else {
                $model[$relation] = null;
            }
        }

        return $models;
    }
}
