<?php

declare(strict_types=1);

namespace App\State;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Post;
use ApiPlatform\State\ProcessorInterface;
use App\Entity\Composant;
use App\Entity\ComposantPieceSlot;
use App\Entity\ComposantProductSlot;
use App\Entity\ComposantSubcomponentSlot;
use App\Entity\ModelType;
use App\Entity\Piece;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;

/**
 * Initializes skeleton slots when a Composant is created with a typeComposant,
 * and applies the piece/product/subcomponent selections from the frontend payload.
 */
final class ComposantProcessor implements ProcessorInterface
{
    public function __construct(
        #[Autowire(service: 'api_platform.doctrine.orm.state.persist_processor')]
        private readonly ProcessorInterface $decorated,
        private readonly EntityManagerInterface $entityManager,
    ) {}

    /**
     * @param array<string, mixed> $uriVariables
     * @param array<string, mixed> $context
     */
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof Composant) {
            return $this->decorated->process($data, $operation, $uriVariables, $context);
        }

        $pendingStructure = $data->getPendingStructure();

        // Persist the entity first
        $result = $this->decorated->process($data, $operation, $uriVariables, $context);

        // On creation, scaffold slots from the ModelType skeleton + apply selections
        if ($operation instanceof Post) {
            $this->initializeSlotsFromSkeleton($data, $pendingStructure);
            $data->clearPendingStructure();
        }

        return $result;
    }

    private function initializeSlotsFromSkeleton(Composant $composant, ?array $structure): void
    {
        $modelType = $composant->getTypeComposant();

        if (!$modelType) {
            return;
        }

        $modelType = $this->entityManager->getRepository(ModelType::class)->find($modelType->getId());

        if (!$modelType) {
            return;
        }

        // Index selections from the frontend payload by typePieceId/typeProductId/typeComposantId
        $pieceSelections   = $this->indexSelections($structure['pieces'] ?? [], 'typePieceId', 'selectedPieceId');
        $productSelections = $this->indexSelections($structure['products'] ?? [], 'typeProductId', 'selectedProductId');
        $subSelections     = $this->indexSelections($structure['subcomponents'] ?? [], 'typeComposantId', 'selectedComponentId');

        $hasNewSlots = false;

        // Piece slots
        foreach ($modelType->getSkeletonPieceRequirements() as $req) {
            $typePieceId = $req->getTypePiece()->getId();
            $slot        = new ComposantPieceSlot();
            $slot->setComposant($composant);
            $slot->setTypePiece($req->getTypePiece());
            $slot->setPosition($req->getPosition());

            $selectedId = $this->shiftSelection($pieceSelections, $typePieceId);
            if ($selectedId) {
                $slot->setSelectedPiece($this->entityManager->getReference(Piece::class, $selectedId));
            }

            $this->entityManager->persist($slot);
            $hasNewSlots = true;
        }

        // Product slots
        foreach ($modelType->getSkeletonProductRequirements() as $req) {
            $typeProductId = $req->getTypeProduct()->getId();
            $slot          = new ComposantProductSlot();
            $slot->setComposant($composant);
            $slot->setTypeProduct($req->getTypeProduct());
            $slot->setFamilyCode($req->getFamilyCode());
            $slot->setPosition($req->getPosition());

            $selectedId = $this->shiftSelection($productSelections, $typeProductId);
            if ($selectedId) {
                $slot->setSelectedProduct($this->entityManager->getReference(Product::class, $selectedId));
            }

            $this->entityManager->persist($slot);
            $hasNewSlots = true;
        }

        // Subcomponent slots
        foreach ($modelType->getSkeletonSubcomponentRequirements() as $req) {
            $typeComposantId = $req->getTypeComposant()?->getId() ?? '';
            $slot            = new ComposantSubcomponentSlot();
            $slot->setComposant($composant);
            $slot->setAlias($req->getAlias());
            $slot->setFamilyCode($req->getFamilyCode());
            $slot->setTypeComposant($req->getTypeComposant());
            $slot->setPosition($req->getPosition());

            $selectedId = $this->shiftSelection($subSelections, $typeComposantId);
            if ($selectedId) {
                $slot->setSelectedComposant($this->entityManager->getReference(Composant::class, $selectedId));
            }

            $this->entityManager->persist($slot);
            $hasNewSlots = true;
        }

        if ($hasNewSlots) {
            $this->entityManager->flush();
        }
    }

    /**
     * Build an indexed map of typeId → [selectedId, selectedId, ...] from the payload.
     * Supports both flat format and nested definition format from the frontend.
     *
     * @return array<string, list<string>>
     */
    private function indexSelections(array $items, string $typeKey, string $selectionKey): array
    {
        $map = [];

        foreach ($items as $item) {
            if (!is_array($item)) {
                continue;
            }

            // The typeId can be at top level or inside definition
            $typeId = $item['definition'][$typeKey]
                ?? $item[$typeKey]
                ?? null;
            $selectedId = $item[$selectionKey] ?? null;

            if (!is_string($typeId) || '' === $typeId) {
                continue;
            }

            if (is_string($selectedId) && '' !== $selectedId) {
                $map[$typeId][] = $selectedId;
            }
        }

        return $map;
    }

    /**
     * Pop the first selection for a given typeId (handles multiple slots of the same type).
     */
    private function shiftSelection(array &$selections, string $typeId): ?string
    {
        if (empty($selections[$typeId])) {
            return null;
        }

        return array_shift($selections[$typeId]);
    }
}
