<?php

namespace Lucent\Query;

use Lucent\Edge\Edge;
use Lucent\Primitive\Collection;
use Lucent\Record\QueryRecord;

final class Graph
{

    /**
     * @param Collection<QueryRecord> $records
     * @param Collection<Edge> $edges
     * */
    public function __construct(
        public Collection $records,
        public Collection $edges,
        public Collection $parentEdges,
        public QueryOptions $queryOptions,
        public ?int       $total = null,
    )
    {
    }

    /**
     * @return Collection<QueryRecord>
     * */
    public function getRootRecords(): Collection
    {

        return $this->records->where("isRoot", true)->values();
    }

    public function hasResults(): bool
    {
        return !empty($this->records);
    }

    public function tree(): Collection
    {
        return $this->getRootRecords()
            ->map(fn($r) => $this->findParents($r))
            ->map(fn($r) => $this->findChildren($r));

    }

    public function findChildren(QueryRecord $record, int $depth = 1): QueryRecord
    {
        if($this->queryOptions->childrenDepth < $depth){
            return $record;
        }
        $recordEdges = $this->edges
            ->filter(fn(Edge $ed) => $ed->source === $record->id )
            ->unique(fn(Edge $ed) => $ed->targetSchema . $ed->field . $ed->target . $ed->source)
            ->sort(fn($a, $b) => $a->rank <=> $b->rank)->values();
        $groupRecordEdges = [];
        foreach ($recordEdges as $element) {
            $groupRecordEdges[$element->field][] = $element;
        }
        $children = [];
        foreach ($groupRecordEdges as $field => $edges) {

            $children[$field] = [];
            foreach ($edges as $anEdge) {
                $aRecord = $this->records->filter(fn(QueryRecord $rec) => $rec->id == $anEdge->target)->values();
                if (empty($aRecord[0])) {
                    continue;
                }

                $children[$field][] = $this->findChildren($aRecord[0], $depth + 1);
            }
        }
        $record->_children = $children;
        return $record;
    }


    public function findParents(QueryRecord $record, int $depth = 1): QueryRecord
    {
        if($this->queryOptions->parentsDepth < $depth){
            return $record;
        }
        $recordEdges = $this->parentEdges->filter(fn(Edge $ed) => $ed->target === $record->id)->where("depth", $depth)->values()->sort(fn($a, $b) => $a->rank <=> $b->rank)->values();

        $groupRecordEdges = [];
        foreach ($recordEdges as $element) {
            $groupRecordEdges[$element->field][] = $element;
        }
        $parents = [];
        foreach ($groupRecordEdges as $field => $edges) {

            $parents[$field] = [];
            foreach ($edges as $anEdge) {
                $aRecord = $this->records->filter(fn(QueryRecord $rec) => $rec->id == $anEdge->source)->values();
                if (empty($aRecord[0])) {
                    continue;
                }

                $parents[$field][] = $this->findParents($aRecord[0], $depth + 1);
            }
        }
        $record->_parents = $parents;
        return $record;
    }

}
