<?php

namespace Velis\Model\Relationship;

use ReflectionException;
use Velis\Arrays;
use Velis\Debug;
use Velis\Exception;
use Velis\Model\DataObject;

/**
 * Helper class to get Models related to the source Model.
 *
 * @author Szymon Janaczek <szymon.janaczek@velistech.com>
 */
class RelationBuilder
{
    /**
     * @var array<Relation>
     */
    private array $relations = [];

    /**
     * @throws InvalidRelationshipTypeException
     */
    public function addRelation(
        string $name,
        string $type,
        string $localKey,
        string $model,
        string $foreignKey,
        ?array $columns = null
    ): self {
        $this->relations[] = new Relation($name, $type, $model, $localKey, $foreignKey, $columns);

        return $this;
    }

    private function keyBy(array $data, string $key): array
    {
        $newData = [];
        foreach ($data as $row) {
            $newData[$row[$key]][] = $row;
        }

        return $newData;
    }

    /**
     * @param array<int, mixed> $list
     * @throws Exception
     * @throws ReflectionException
     */
    public function withRelatedData(array $list): array
    {
        $data = [];
        for ($i = 0; $i < count($this->relations); $i++) {
            $relation = $this->relations[$i];
            $relatedData = $this->loadHasOneForList(
                Arrays::getColumn($list, $relation->localKey),
                $relation->model,
                $relation->foreignKey,
                $relation->columns
            );

            $data[$relation->name] = $this->keyBy($relatedData, $relation->foreignKey);
        }

        array_walk($list, function ($entry) use ($data) {
            foreach ($this->relations as $relation) {
                if (
                    !array_key_exists($relation->name, $data)
                    || !isset($entry[$relation->localKey])
                    || !isset($data[$relation->name][$entry[$relation->localKey]])
                ) {
                    continue;
                }

                $relatedData = $data[$relation->name][$entry[$relation->localKey]];
                if (!is_array($relatedData) || count($relatedData) <= 0) {
                    $entry[$relation->name] = null;
                    continue;
                }

                if ($relation->type == RelationType::HAS_ONE) {
                    $relatedData = array_values($relatedData)[0];
                }

                if ($relation->type == RelationType::HAS_MANY) {
                    $relatedData = array_values($relatedData);
                }

                $entry[$relation->name] = $relatedData;
            }
        });

        return $list;
    }

    /**
     * @param array<int|string> $ids
     * @param class-string<DataObject> $model
     * @throws ReflectionException
     */
    private function loadHasOneForList(array $ids, string $model, string $foreignKey, ?array $columns = null)
    {
        return $model::listAll([$foreignKey => $ids], null, $columns);
    }
}
