<?php
/**
 * Branch remap, extract, insert, and coherence checks for ElementTree.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Elements;

/**
 * Keeps ElementTree under the 600-line budget without changing its public API.
 */
trait ElementTreeBranches
{
    /**
     * @return string[] Coherence error codes.
     */
    public function coherenceErrors(): array
    {
        $byId   = [];
        $errors = [];

        foreach ($this->elements as $element) {
            $id = (string) ($element['id'] ?? '');
            if ($id === '') {
                $errors[] = 'missing_id';
                continue;
            }
            if (isset($byId[$id])) {
                $errors[] = 'duplicate_id';
            }
            $byId[$id] = $element;
        }

        foreach ($this->elements as $element) {
            $id       = (string) ($element['id'] ?? '');
            $parentId = $this->parentId($element);
            if ($parentId !== '' && !isset($byId[$parentId])) {
                $errors[] = 'missing_parent';
            }

            foreach ($this->normalizeChildren($element['children'] ?? []) as $childId) {
                if (!isset($byId[$childId])) {
                    $errors[] = 'missing_child';
                    continue;
                }

                if ($this->parentId($byId[$childId]) !== $id) {
                    $errors[] = 'parent_mismatch';
                }
            }
        }

        foreach ($byId as $id => $element) {
            $id       = (string) $id;
            $parentId = $this->parentId($element);
            if ($parentId === '') {
                continue;
            }

            $parentChildren = $this->normalizeChildren($byId[$parentId]['children'] ?? []);
            if (!in_array($id, $parentChildren, true)) {
                $errors[] = 'orphan';
            }
        }

        if ($this->hasCycle($byId)) {
            $errors[] = 'cycle';
        }

        return array_values(array_unique($errors));
    }

    public function firstCoherenceError(): ?string
    {
        $errors = $this->coherenceErrors();

        return $errors[0] ?? null;
    }

    /**
     * Return a copy of the list with every element ID remapped.
     *
     * @param array<string, string> $forceMap oldId => newId pinned before generation
     * @return array<int, array<string, mixed>>
     */
    public function withRemappedIds(IdGenerator $ids, array $forceMap = []): array
    {
        $reserved = $this->ids();
        foreach ($forceMap as $newId) {
            $reserved[] = (string) $newId;
        }

        $toRemap = [];
        foreach ($this->ids() as $oldId) {
            if (!isset($forceMap[$oldId])) {
                $toRemap[] = $oldId;
            }
        }

        $map  = $forceMap + $ids->remap($toRemap, $reserved);
        $list = [];

        foreach ($this->elements as $element) {
            $oldId   = (string) ($element['id'] ?? '');
            $parent  = $this->parentId($element);
            $element['id'] = $map[$oldId] ?? $oldId;
            if ($parent !== '' && isset($map[$parent])) {
                $element['parent'] = $map[$parent];
            } elseif ($parent === '' || $this->indexOf($parent) === null) {
                $element['parent'] = self::ROOT_PARENT;
            }

            $children = [];
            foreach ($this->normalizeChildren($element['children'] ?? []) as $childId) {
                $children[] = $map[$childId] ?? $childId;
            }
            $element['children'] = $children;
            $list[]              = $element;
        }

        return $list;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function extractBranch(string $id): array
    {
        if ($this->indexOf($id) === null) {
            return [];
        }

        $wanted = array_flip(array_merge([$id], $this->descendantIds($id)));
        $branch = [];
        foreach ($this->elements as $element) {
            $elementId = (string) ($element['id'] ?? '');
            if (isset($wanted[$elementId])) {
                $branch[] = $element;
            }
        }

        return $branch;
    }

    /**
     * Insert a coherent branch at the given placement.
     *
     * @param array<int, array<string, mixed>> $branch
     */
    public function insertBranch(array $branch, string $position, string $referenceId): ?string
    {
        if ($branch === []) {
            return 'element_not_found';
        }

        $root = $branch[0];
        foreach ($branch as $element) {
            if ($this->parentId($element) === '') {
                $root = $element;
                break;
            }
        }

        $rootId = (string) ($root['id'] ?? '');
        $placed = $root;
        $placed['children'] = [];
        $error = $this->insert($placed, $position, $referenceId);
        if ($error !== null) {
            return $error;
        }

        $index = $this->indexOf($rootId);
        if ($index !== null) {
            $this->elements[$index]['children'] = $this->normalizeChildren($root['children'] ?? []);
        }

        foreach ($branch as $element) {
            if ((string) ($element['id'] ?? '') === $rootId) {
                continue;
            }

            $this->elements[] = $element;
        }

        return $this->firstCoherenceError();
    }
}
