<?php

declare(strict_types=1);

namespace App\Controller;

use App\Entity\Composant;
use App\Entity\Machine;
use App\Entity\MachineComponentLink;
use App\Entity\MachinePieceLink;
use App\Entity\MachineProductLink;
use App\Entity\ModelType;
use App\Entity\Piece;
use App\Entity\Product;
use App\Entity\TypeMachineComponentRequirement;
use App\Entity\TypeMachinePieceRequirement;
use App\Entity\TypeMachineProductRequirement;
use App\Repository\ComposantRepository;
use App\Repository\MachineComponentLinkRepository;
use App\Repository\MachinePieceLinkRepository;
use App\Repository\MachineProductLinkRepository;
use App\Repository\MachineRepository;
use App\Repository\PieceRepository;
use App\Repository\ProductRepository;
use App\Repository\TypeMachineComponentRequirementRepository;
use App\Repository\TypeMachinePieceRequirementRepository;
use App\Repository\TypeMachineProductRequirementRepository;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/api/machines')]
class MachineSkeletonController extends AbstractController
{
    public function __construct(
        private readonly EntityManagerInterface $entityManager,
        private readonly MachineRepository $machineRepository,
        private readonly MachineComponentLinkRepository $machineComponentLinkRepository,
        private readonly MachinePieceLinkRepository $machinePieceLinkRepository,
        private readonly MachineProductLinkRepository $machineProductLinkRepository,
        private readonly ComposantRepository $composantRepository,
        private readonly PieceRepository $pieceRepository,
        private readonly ProductRepository $productRepository,
        private readonly TypeMachineComponentRequirementRepository $componentRequirementRepository,
        private readonly TypeMachinePieceRequirementRepository $pieceRequirementRepository,
        private readonly TypeMachineProductRequirementRepository $productRequirementRepository,
    ) {
    }

    #[Route('/{id}/skeleton', name: 'machine_skeleton_get', methods: ['GET'])]
    public function getSkeleton(string $id): JsonResponse
    {
        $machine = $this->machineRepository->find($id);
        if (!$machine instanceof Machine) {
            return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
        }

        $componentLinks = $this->machineComponentLinkRepository->findBy(['machine' => $machine]);
        $pieceLinks = $this->machinePieceLinkRepository->findBy(['machine' => $machine]);
        $productLinks = $this->machineProductLinkRepository->findBy(['machine' => $machine]);

        return $this->json($this->normalizeMachineSkeletonResponse(
            $machine,
            $componentLinks,
            $pieceLinks,
            $productLinks
        ));
    }

    #[Route('/{id}/skeleton', name: 'machine_skeleton_update', methods: ['PATCH'])]
    public function updateSkeleton(string $id, Request $request): JsonResponse
    {
        $machine = $this->machineRepository->find($id);
        if (!$machine instanceof Machine) {
            return $this->json(['success' => false, 'error' => 'Machine not found.'], 404);
        }

        $payload = json_decode($request->getContent(), true);
        if (!is_array($payload)) {
            return $this->json(['success' => false, 'error' => 'Invalid JSON payload.'], 400);
        }

        $componentLinksPayload = $this->normalizePayloadList($payload['componentLinks'] ?? []);
        $pieceLinksPayload = $this->normalizePayloadList($payload['pieceLinks'] ?? []);
        $productLinksPayload = $this->normalizePayloadList($payload['productLinks'] ?? []);

        $componentLinks = $this->applyComponentLinks($machine, $componentLinksPayload);
        if ($componentLinks instanceof JsonResponse) {
            return $componentLinks;
        }

        $pieceLinks = $this->applyPieceLinks($machine, $pieceLinksPayload, $componentLinks);
        if ($pieceLinks instanceof JsonResponse) {
            return $pieceLinks;
        }

        $productLinks = $this->applyProductLinks($machine, $productLinksPayload, $componentLinks, $pieceLinks);
        if ($productLinks instanceof JsonResponse) {
            return $productLinks;
        }

        $this->entityManager->flush();

        return $this->json($this->normalizeMachineSkeletonResponse(
            $machine,
            $componentLinks,
            $pieceLinks,
            $productLinks
        ));
    }

    private function normalizePayloadList(mixed $value): array
    {
        if (!is_array($value)) {
            return [];
        }
        return array_values(array_filter($value, static fn ($item) => is_array($item)));
    }

    private function applyComponentLinks(Machine $machine, array $payload): array|JsonResponse
    {
        $existing = $this->indexLinksById($this->machineComponentLinkRepository->findBy(['machine' => $machine]));
        $keepIds = [];
        $pendingParents = [];
        $links = [];

        foreach ($payload as $entry) {
            $linkId = $this->resolveIdentifier($entry, ['id', 'linkId']);
            $link = $linkId && isset($existing[$linkId]) ? $existing[$linkId] : new MachineComponentLink();
            if (!$linkId) {
                $linkId = $this->generateCuid();
            }
            if (!$link->getId()) {
                $link->setId($linkId);
            }

            $composantId = $this->resolveIdentifier($entry, ['composantId', 'componentId', 'idComposant']);
            if (!$composantId) {
                return $this->json(['success' => false, 'error' => 'Composant requis pour le squelette.'], 400);
            }
            $composant = $this->composantRepository->find($composantId);
            if (!$composant instanceof Composant) {
                return $this->json(['success' => false, 'error' => 'Composant introuvable.'], 404);
            }

            $link->setMachine($machine);
            $link->setComposant($composant);

            $requirementId = $this->resolveIdentifier($entry, ['requirementId', 'typeMachineComponentRequirementId']);
            if ($requirementId) {
                $requirement = $this->componentRequirementRepository->find($requirementId);
                if ($requirement instanceof TypeMachineComponentRequirement) {
                    $link->setTypeMachineComponentRequirement($requirement);
                }
            }

            $this->applyOverrides($link, $entry['overrides'] ?? null);

            $pendingParents[$linkId] = $this->resolveIdentifier($entry, [
                'parentComponentLinkId',
                'parentLinkId',
                'parentMachineComponentLinkId',
            ]);

            $this->entityManager->persist($link);
            $links[$linkId] = $link;
            $keepIds[] = $linkId;
        }

        foreach ($pendingParents as $linkId => $parentId) {
            if (!$parentId) {
                continue;
            }
            if (!isset($links[$linkId])) {
                continue;
            }
            $parent = $links[$parentId] ?? $existing[$parentId] ?? null;
            if ($parent instanceof MachineComponentLink) {
                $links[$linkId]->setParentLink($parent);
            }
        }

        $this->removeMissingLinks($existing, $keepIds);

        return array_values($links);
    }

    private function applyPieceLinks(Machine $machine, array $payload, array $componentLinks): array|JsonResponse
    {
        $existing = $this->indexLinksById($this->machinePieceLinkRepository->findBy(['machine' => $machine]));
        $componentIndex = $this->indexLinksById($componentLinks);
        $keepIds = [];
        $pendingParents = [];
        $links = [];

        foreach ($payload as $entry) {
            $linkId = $this->resolveIdentifier($entry, ['id', 'linkId']);
            $link = $linkId && isset($existing[$linkId]) ? $existing[$linkId] : new MachinePieceLink();
            if (!$linkId) {
                $linkId = $this->generateCuid();
            }
            if (!$link->getId()) {
                $link->setId($linkId);
            }

            $pieceId = $this->resolveIdentifier($entry, ['pieceId']);
            if (!$pieceId) {
                return $this->json(['success' => false, 'error' => 'Pièce requise pour le squelette.'], 400);
            }
            $piece = $this->pieceRepository->find($pieceId);
            if (!$piece instanceof Piece) {
                return $this->json(['success' => false, 'error' => 'Pièce introuvable.'], 404);
            }

            $link->setMachine($machine);
            $link->setPiece($piece);

            $requirementId = $this->resolveIdentifier($entry, ['requirementId', 'typeMachinePieceRequirementId']);
            if ($requirementId) {
                $requirement = $this->pieceRequirementRepository->find($requirementId);
                if ($requirement instanceof TypeMachinePieceRequirement) {
                    $link->setTypeMachinePieceRequirement($requirement);
                }
            }

            $this->applyOverrides($link, $entry['overrides'] ?? null);

            $pendingParents[$linkId] = $this->resolveIdentifier($entry, [
                'parentComponentLinkId',
                'parentLinkId',
                'parentMachineComponentLinkId',
            ]);

            $this->entityManager->persist($link);
            $links[$linkId] = $link;
            $keepIds[] = $linkId;
        }

        foreach ($pendingParents as $linkId => $parentId) {
            if (!$parentId) {
                continue;
            }
            if (!isset($links[$linkId])) {
                continue;
            }
            $parent = $componentIndex[$parentId] ?? null;
            if ($parent instanceof MachineComponentLink) {
                $links[$linkId]->setParentLink($parent);
            }
        }

        $this->removeMissingLinks($existing, $keepIds);

        return array_values($links);
    }

    private function applyProductLinks(
        Machine $machine,
        array $payload,
        array $componentLinks,
        array $pieceLinks,
    ): array|JsonResponse {
        $existing = $this->indexLinksById($this->machineProductLinkRepository->findBy(['machine' => $machine]));
        $componentIndex = $this->indexLinksById($componentLinks);
        $pieceIndex = $this->indexLinksById($pieceLinks);
        $keepIds = [];
        $pendingParents = [];
        $links = [];

        foreach ($payload as $entry) {
            $linkId = $this->resolveIdentifier($entry, ['id', 'linkId']);
            $link = $linkId && isset($existing[$linkId]) ? $existing[$linkId] : new MachineProductLink();
            if (!$linkId) {
                $linkId = $this->generateCuid();
            }
            if (!$link->getId()) {
                $link->setId($linkId);
            }

            $productId = $this->resolveIdentifier($entry, ['productId']);
            if (!$productId) {
                return $this->json(['success' => false, 'error' => 'Produit requis pour le squelette.'], 400);
            }
            $product = $this->productRepository->find($productId);
            if (!$product instanceof Product) {
                return $this->json(['success' => false, 'error' => 'Produit introuvable.'], 404);
            }

            $link->setMachine($machine);
            $link->setProduct($product);

            $requirementId = $this->resolveIdentifier($entry, ['requirementId', 'typeMachineProductRequirementId']);
            if ($requirementId) {
                $requirement = $this->productRequirementRepository->find($requirementId);
                if ($requirement instanceof TypeMachineProductRequirement) {
                    $link->setTypeMachineProductRequirement($requirement);
                }
            }

            $pendingParents[$linkId] = [
                'parentComponentLinkId' => $this->resolveIdentifier($entry, ['parentComponentLinkId']),
                'parentPieceLinkId' => $this->resolveIdentifier($entry, ['parentPieceLinkId']),
                'parentLinkId' => $this->resolveIdentifier($entry, ['parentLinkId']),
            ];

            $this->entityManager->persist($link);
            $links[$linkId] = $link;
            $keepIds[] = $linkId;
        }

        foreach ($pendingParents as $linkId => $parentIds) {
            if (!isset($links[$linkId])) {
                continue;
            }
            if (!empty($parentIds['parentComponentLinkId']) && isset($componentIndex[$parentIds['parentComponentLinkId']])) {
                $links[$linkId]->setParentComponentLink($componentIndex[$parentIds['parentComponentLinkId']]);
            }
            if (!empty($parentIds['parentPieceLinkId']) && isset($pieceIndex[$parentIds['parentPieceLinkId']])) {
                $links[$linkId]->setParentPieceLink($pieceIndex[$parentIds['parentPieceLinkId']]);
            }
            if (!empty($parentIds['parentLinkId']) && isset($links[$parentIds['parentLinkId']])) {
                $links[$linkId]->setParentLink($links[$parentIds['parentLinkId']]);
            }
        }

        $this->removeMissingLinks($existing, $keepIds);

        return array_values($links);
    }

    private function normalizeMachineSkeletonResponse(
        Machine $machine,
        array $componentLinks,
        array $pieceLinks,
        array $productLinks,
    ): array {
        $normalizedComponentLinks = $this->normalizeComponentLinks($componentLinks);
        $componentIndex = $this->indexNormalizedLinks($normalizedComponentLinks);
        $normalizedPieceLinks = $this->normalizePieceLinks($pieceLinks);

        // Build component hierarchy
        foreach ($normalizedComponentLinks as &$link) {
            $parentId = $link['parentComponentLinkId'] ?? null;
            if ($parentId && isset($componentIndex[$parentId])) {
                $componentIndex[$parentId]['childLinks'][] = &$link;
            }
        }
        unset($link);

        // Add pieces to components recursively
        $this->attachPiecesToComponents($componentIndex, $normalizedPieceLinks);

        return [
            'machine' => $this->normalizeMachine($machine),
            'componentLinks' => array_values($componentIndex),
            'pieceLinks' => $normalizedPieceLinks,
            'productLinks' => $this->normalizeProductLinks($productLinks),
        ];
    }

    private function attachPiecesToComponents(array &$componentIndex, array $pieceLinks): void
    {
        foreach ($pieceLinks as $pieceLink) {
            $parentId = $pieceLink['parentComponentLinkId'] ?? null;
            if ($parentId && isset($componentIndex[$parentId])) {
                $componentIndex[$parentId]['pieceLinks'][] = $pieceLink;
            }
        }

        // Recursively attach to child components
        foreach ($componentIndex as &$component) {
            if (!empty($component['childLinks'])) {
                $this->attachPiecesToChildComponents($component['childLinks'], $pieceLinks);
            }
        }
    }

    private function attachPiecesToChildComponents(array &$childLinks, array $pieceLinks): void
    {
        foreach ($childLinks as &$child) {
            $childId = $child['id'] ?? $child['linkId'] ?? null;
            if ($childId) {
                foreach ($pieceLinks as $pieceLink) {
                    $parentId = $pieceLink['parentComponentLinkId'] ?? null;
                    if ($parentId === $childId) {
                        $child['pieceLinks'][] = $pieceLink;
                    }
                }
            }

            // Recursively process nested children
            if (!empty($child['childLinks'])) {
                $this->attachPiecesToChildComponents($child['childLinks'], $pieceLinks);
            }
        }
    }

    private function normalizeMachine(Machine $machine): array
    {
        $site = $machine->getSite();
        $typeMachine = $machine->getTypeMachine();

        return [
            'id' => $machine->getId(),
            'name' => $machine->getName(),
            'reference' => $machine->getReference(),
            'prix' => $machine->getPrix(),
            'siteId' => $site->getId(),
            'site' => [
                'id' => $site->getId(),
                'name' => $site->getName(),
            ],
            'typeMachineId' => $typeMachine?->getId(),
            'typeMachine' => $typeMachine ? [
                'id' => $typeMachine->getId(),
                'name' => $typeMachine->getName(),
                'category' => $typeMachine->getCategory(),
                'description' => $typeMachine->getDescription(),
                'customFields' => $this->normalizeCustomFields($typeMachine->getCustomFields()),
                'componentRequirements' => $typeMachine->getComponentRequirements()
                    ->map(fn (TypeMachineComponentRequirement $req) => $this->normalizeComponentRequirement($req))
                    ->toArray(),
                'pieceRequirements' => $typeMachine->getPieceRequirements()
                    ->map(fn (TypeMachinePieceRequirement $req) => $this->normalizePieceRequirement($req))
                    ->toArray(),
                'productRequirements' => $typeMachine->getProductRequirements()
                    ->map(fn (TypeMachineProductRequirement $req) => $this->normalizeProductRequirement($req))
                    ->toArray(),
            ] : null,
            'constructeurs' => $this->normalizeConstructeurs($machine->getConstructeurs()),
            'documents' => null,
            'customFieldValues' => null,
        ];
    }

    private function normalizeCustomFields(Collection $customFields): array
    {
        $items = [];
        foreach ($customFields as $customField) {
            if (!$customField instanceof CustomField) {
                continue;
            }
            $items[] = [
                'id' => $customField->getId(),
                'name' => $customField->getName(),
                'type' => $customField->getType(),
                'required' => $customField->isRequired(),
                'options' => $customField->getOptions(),
                'defaultValue' => $customField->getDefaultValue(),
                'orderIndex' => $customField->getOrderIndex(),
            ];
        }

        return $items;
    }

    private function normalizeComponentLinks(array $links): array
    {
        return array_map(function (MachineComponentLink $link): array {
            $composant = $link->getComposant();
            $requirement = $link->getTypeMachineComponentRequirement();
            $parentLink = $link->getParentLink();
            $parentRequirementId = $parentLink?->getTypeMachineComponentRequirement()?->getId();

            return [
                'id' => $link->getId(),
                'linkId' => $link->getId(),
                'machineId' => $link->getMachine()->getId(),
                'composantId' => $composant->getId(),
                'composant' => $this->normalizeComposant($composant),
                'typeMachineComponentRequirementId' => $requirement?->getId(),
                'typeMachineComponentRequirement' => $requirement ? $this->normalizeComponentRequirement($requirement) : null,
                'parentLinkId' => $parentLink?->getId(),
                'parentComponentLinkId' => $parentLink?->getId(),
                'parentComponentId' => $parentLink?->getComposant()->getId(),
                'parentMachineComponentRequirementId' => $parentRequirementId,
                'overrides' => $this->normalizeOverrides($link),
                'childLinks' => [],
                'pieceLinks' => [],
            ];
        }, $links);
    }

    private function normalizePieceLinks(array $links): array
    {
        return array_map(function (MachinePieceLink $link): array {
            $piece = $link->getPiece();
            $requirement = $link->getTypeMachinePieceRequirement();
            $parentLink = $link->getParentLink();
            $parentRequirementId = $parentLink?->getTypeMachineComponentRequirement()?->getId();

            return [
                'id' => $link->getId(),
                'linkId' => $link->getId(),
                'machineId' => $link->getMachine()->getId(),
                'pieceId' => $piece->getId(),
                'piece' => $this->normalizePiece($piece),
                'typeMachinePieceRequirementId' => $requirement?->getId(),
                'typeMachinePieceRequirement' => $requirement ? $this->normalizePieceRequirement($requirement) : null,
                'parentLinkId' => $parentLink?->getId(),
                'parentComponentLinkId' => $parentLink?->getId(),
                'parentComponentId' => $parentLink?->getComposant()->getId(),
                'parentMachineComponentRequirementId' => $parentRequirementId,
                'overrides' => $this->normalizeOverrides($link),
            ];
        }, $links);
    }

    private function normalizeProductLinks(array $links): array
    {
        return array_map(function (MachineProductLink $link): array {
            $product = $link->getProduct();
            $requirement = $link->getTypeMachineProductRequirement();

            return [
                'id' => $link->getId(),
                'linkId' => $link->getId(),
                'machineId' => $link->getMachine()->getId(),
                'productId' => $product->getId(),
                'product' => $this->normalizeProduct($product),
                'typeMachineProductRequirementId' => $requirement?->getId(),
                'typeMachineProductRequirement' => $requirement ? $this->normalizeProductRequirement($requirement) : null,
                'parentLinkId' => $link->getParentLink()?->getId(),
                'parentComponentLinkId' => $link->getParentComponentLink()?->getId(),
                'parentPieceLinkId' => $link->getParentPieceLink()?->getId(),
            ];
        }, $links);
    }

    private function normalizeComposant(Composant $composant): array
    {
        return [
            'id' => $composant->getId(),
            'name' => $composant->getName(),
            'reference' => $composant->getReference(),
            'prix' => $composant->getPrix(),
            'typeComposantId' => $composant->getTypeComposant()?->getId(),
            'typeComposant' => $this->normalizeModelType($composant->getTypeComposant()),
            'productId' => $composant->getProduct()?->getId(),
            'product' => $composant->getProduct() ? $this->normalizeProduct($composant->getProduct()) : null,
            'constructeurs' => $this->normalizeConstructeurs($composant->getConstructeurs()),
            'documents' => [],
            'customFields' => [],
        ];
    }

    private function normalizePiece(Piece $piece): array
    {
        return [
            'id' => $piece->getId(),
            'name' => $piece->getName(),
            'reference' => $piece->getReference(),
            'prix' => $piece->getPrix(),
            'typePieceId' => $piece->getTypePiece()?->getId(),
            'typePiece' => $this->normalizeModelType($piece->getTypePiece()),
            'productId' => $piece->getProduct()?->getId(),
            'product' => $piece->getProduct() ? $this->normalizeProduct($piece->getProduct()) : null,
            'constructeurs' => $this->normalizeConstructeurs($piece->getConstructeurs()),
            'documents' => [],
            'customFields' => [],
        ];
    }

    private function normalizeProduct(Product $product): array
    {
        return [
            'id' => $product->getId(),
            'name' => $product->getName(),
            'reference' => $product->getReference(),
            'supplierPrice' => $product->getSupplierPrice(),
            'typeProductId' => $product->getTypeProduct()?->getId(),
            'typeProduct' => $this->normalizeModelType($product->getTypeProduct()),
            'constructeurs' => $this->normalizeConstructeurs($product->getConstructeurs()),
            'documents' => [],
            'customFields' => [],
        ];
    }

    private function normalizeModelType(?ModelType $type): ?array
    {
        if (!$type instanceof ModelType) {
            return null;
        }

        return [
            'id' => $type->getId(),
            'name' => $type->getName(),
            'code' => $type->getCode(),
            'category' => $type->getCategory()->value,
        ];
    }

    private function normalizeComponentRequirement(TypeMachineComponentRequirement $requirement): array
    {
        return [
            'id' => $requirement->getId(),
            'label' => $requirement->getLabel(),
            'minCount' => $requirement->getMinCount(),
            'maxCount' => $requirement->getMaxCount(),
            'required' => $requirement->isRequired(),
            'typeComposantId' => $requirement->getTypeComposant()->getId(),
            'typeComposant' => $this->normalizeModelType($requirement->getTypeComposant()),
        ];
    }

    private function normalizePieceRequirement(TypeMachinePieceRequirement $requirement): array
    {
        return [
            'id' => $requirement->getId(),
            'label' => $requirement->getLabel(),
            'minCount' => $requirement->getMinCount(),
            'maxCount' => $requirement->getMaxCount(),
            'required' => $requirement->isRequired(),
            'typePieceId' => $requirement->getTypePiece()->getId(),
            'typePiece' => $this->normalizeModelType($requirement->getTypePiece()),
        ];
    }

    private function normalizeProductRequirement(TypeMachineProductRequirement $requirement): array
    {
        return [
            'id' => $requirement->getId(),
            'label' => $requirement->getLabel(),
            'minCount' => $requirement->getMinCount(),
            'maxCount' => $requirement->getMaxCount(),
            'required' => $requirement->isRequired(),
            'typeProductId' => $requirement->getTypeProduct()->getId(),
            'typeProduct' => $this->normalizeModelType($requirement->getTypeProduct()),
        ];
    }

    private function normalizeConstructeurs(Collection $constructeurs): array
    {
        $items = [];
        foreach ($constructeurs as $constructeur) {
            $items[] = [
                'id' => $constructeur->getId(),
                'name' => $constructeur->getName(),
                'email' => $constructeur->getEmail(),
                'phone' => $constructeur->getPhone(),
            ];
        }

        return $items;
    }

    private function normalizeOverrides(object $link): ?array
    {
        $name = method_exists($link, 'getNameOverride') ? $link->getNameOverride() : null;
        $reference = method_exists($link, 'getReferenceOverride') ? $link->getReferenceOverride() : null;
        $prix = method_exists($link, 'getPrixOverride') ? $link->getPrixOverride() : null;

        if ($name === null && $reference === null && $prix === null) {
            return null;
        }

        return [
            'name' => $name,
            'reference' => $reference,
            'prix' => $prix,
        ];
    }

    private function applyOverrides(object $link, mixed $overrides): void
    {
        if (!is_array($overrides)) {
            return;
        }

        if (array_key_exists('name', $overrides) && method_exists($link, 'setNameOverride')) {
            $link->setNameOverride($this->stringOrNull($overrides['name']));
        }
        if (array_key_exists('reference', $overrides) && method_exists($link, 'setReferenceOverride')) {
            $link->setReferenceOverride($this->stringOrNull($overrides['reference']));
        }
        if (array_key_exists('prix', $overrides) && method_exists($link, 'setPrixOverride')) {
            $link->setPrixOverride($this->stringOrNull($overrides['prix']));
        }
    }

    private function stringOrNull(mixed $value): ?string
    {
        if ($value === null) {
            return null;
        }
        $string = trim((string) $value);

        return $string === '' ? null : $string;
    }

    private function resolveIdentifier(array $entry, array $keys): ?string
    {
        foreach ($keys as $key) {
            if (!array_key_exists($key, $entry)) {
                continue;
            }
            $value = $entry[$key];
            if ($value === null || $value === '') {
                continue;
            }
            return (string) $value;
        }

        return null;
    }

    /**
     * @param array<array-key, object> $links
     * @return array<string, object>
     */
    private function indexLinksById(array $links): array
    {
        $indexed = [];
        foreach ($links as $link) {
            if (method_exists($link, 'getId') && $link->getId()) {
                $indexed[$link->getId()] = $link;
            }
        }

        return $indexed;
    }

    private function indexNormalizedLinks(array $links): array
    {
        $indexed = [];
        foreach ($links as $link) {
            if (is_array($link) && isset($link['id'])) {
                $indexed[$link['id']] = $link;
            }
        }

        return $indexed;
    }

    private function removeMissingLinks(array $existing, array $keepIds): void
    {
        $keep = array_flip($keepIds);
        foreach ($existing as $link) {
            if (!method_exists($link, 'getId')) {
                continue;
            }
            $id = $link->getId();
            if ($id && !isset($keep[$id])) {
                $this->entityManager->remove($link);
            }
        }
    }

    private function generateCuid(): string
    {
        return 'cl' . bin2hex(random_bytes(12));
    }
}
