<?php

declare(strict_types=1);

namespace App\Entity;

use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use App\Enum\ModelCategory;
use App\Repository\ModelTypeRepository;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;

#[ORM\Entity(repositoryClass: ModelTypeRepository::class)]
#[ORM\Table(name: 'model_types')]
#[ORM\UniqueConstraint(name: 'unique_category_name', columns: ['category', 'name'])]
#[ORM\HasLifecycleCallbacks]
#[ApiFilter(SearchFilter::class, properties: ['category' => 'exact', 'name' => 'ipartial'])]
#[ApiResource(
    paginationClientItemsPerPage: true,
    paginationMaximumItemsPerPage: 500
)]
class ModelType
{
    #[ORM\Id]
    #[ORM\Column(type: Types::STRING, length: 36)]
    #[Groups(['type_machine:read', 'model_type:read'])]
    private ?string $id = null;

    #[ORM\Column(type: Types::STRING, length: 120)]
    #[Groups(['type_machine:read', 'model_type:read', 'model_type:write'])]
    private string $name;

    #[ORM\Column(type: Types::STRING, length: 60, unique: true)]
    #[Groups(['type_machine:read', 'model_type:read', 'model_type:write'])]
    private string $code;

    #[ORM\Column(enumType: ModelCategory::class)]
    #[Groups(['type_machine:read', 'model_type:read', 'model_type:write'])]
    private ModelCategory $category;

    #[ORM\Column(type: Types::TEXT, nullable: true)]
    #[Groups(['type_machine:read', 'model_type:read', 'model_type:write'])]
    private ?string $notes = null;

    #[ORM\Column(type: Types::TEXT, nullable: true)]
    #[Groups(['type_machine:read', 'model_type:read', 'model_type:write'])]
    private ?string $description = null;

    #[ORM\Column(type: Types::JSON, nullable: true, name: 'componentSkeleton')]
    #[Groups(['model_type:read'])]
    private ?array $componentSkeleton = null;

    #[ORM\Column(type: Types::JSON, nullable: true, name: 'pieceSkeleton')]
    #[Groups(['model_type:read'])]
    private ?array $pieceSkeleton = null;

    #[ORM\Column(type: Types::JSON, nullable: true, name: 'productSkeleton')]
    #[Groups(['model_type:read'])]
    private ?array $productSkeleton = null;

    #[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'createdAt')]
    #[Groups(['model_type:read'])]
    private DateTimeImmutable $createdAt;

    #[ORM\Column(type: Types::DATETIME_IMMUTABLE, name: 'updatedAt')]
    #[Groups(['model_type:read'])]
    private DateTimeImmutable $updatedAt;

    private ?array $pendingStructure = null;

    /**
     * @var Collection<int, Composant>
     */
    #[ORM\OneToMany(mappedBy: 'typeComposant', targetEntity: Composant::class)]
    private Collection $composants;

    /**
     * @var Collection<int, Piece>
     */
    #[ORM\OneToMany(mappedBy: 'typePiece', targetEntity: Piece::class)]
    private Collection $pieces;

    /**
     * @var Collection<int, Product>
     */
    #[ORM\OneToMany(mappedBy: 'typeProduct', targetEntity: Product::class)]
    private Collection $products;

    /**
     * @var Collection<int, TypeMachineComponentRequirement>
     */
    #[ORM\OneToMany(mappedBy: 'typeComposant', targetEntity: TypeMachineComponentRequirement::class)]
    private Collection $componentRequirements;

    /**
     * @var Collection<int, TypeMachinePieceRequirement>
     */
    #[ORM\OneToMany(mappedBy: 'typePiece', targetEntity: TypeMachinePieceRequirement::class)]
    private Collection $pieceRequirements;

    /**
     * @var Collection<int, TypeMachineProductRequirement>
     */
    #[ORM\OneToMany(mappedBy: 'typeProduct', targetEntity: TypeMachineProductRequirement::class)]
    private Collection $productRequirements;

    /**
     * @var Collection<int, CustomField>
     */
    #[ORM\OneToMany(mappedBy: 'typeComposant', targetEntity: CustomField::class)]
    private Collection $customFields;

    /**
     * @var Collection<int, CustomField>
     */
    #[ORM\OneToMany(mappedBy: 'typePiece', targetEntity: CustomField::class)]
    private Collection $pieceCustomFields;

    /**
     * @var Collection<int, CustomField>
     */
    #[ORM\OneToMany(mappedBy: 'typeProduct', targetEntity: CustomField::class)]
    private Collection $productCustomFields;

    public function __construct()
    {
        $this->composants            = new ArrayCollection();
        $this->pieces                = new ArrayCollection();
        $this->products              = new ArrayCollection();
        $this->componentRequirements = new ArrayCollection();
        $this->pieceRequirements     = new ArrayCollection();
        $this->productRequirements   = new ArrayCollection();
        $this->customFields          = new ArrayCollection();
        $this->pieceCustomFields     = new ArrayCollection();
        $this->productCustomFields   = new ArrayCollection();
    }

    #[ORM\PrePersist]
    public function setCreatedAtValue(): void
    {
        $now             = new DateTimeImmutable();
        $this->createdAt = $now;
        $this->updatedAt = $now;

        if (null === $this->id) {
            $this->id = $this->generateCuid();
        }
    }

    #[ORM\PreUpdate]
    public function setUpdatedAtValue(): void
    {
        $this->updatedAt = new DateTimeImmutable();
    }

    public function getId(): ?string
    {
        return $this->id;
    }

    public function setId(string $id): static
    {
        $this->id = $id;

        return $this;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setName(string $name): static
    {
        $this->name = $name;

        return $this;
    }

    public function getCode(): string
    {
        return $this->code;
    }

    public function setCode(string $code): static
    {
        $this->code = $code;

        return $this;
    }

    public function getCategory(): ModelCategory
    {
        return $this->category;
    }

    public function setCategory(ModelCategory $category): static
    {
        $this->category = $category;

        if (null !== $this->pendingStructure) {
            $this->applyStructureForCategory($this->pendingStructure, $category);
            $this->pendingStructure = null;
        }

        return $this;
    }

    public function getNotes(): ?string
    {
        return $this->notes;
    }

    public function setNotes(?string $notes): static
    {
        $this->notes = $notes;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): static
    {
        $this->description = $description;

        return $this;
    }

    public function getComponentSkeleton(): ?array
    {
        return $this->componentSkeleton;
    }

    public function setComponentSkeleton(?array $componentSkeleton): static
    {
        $this->componentSkeleton = $componentSkeleton;

        return $this;
    }

    public function getPieceSkeleton(): ?array
    {
        return $this->pieceSkeleton;
    }

    public function setPieceSkeleton(?array $pieceSkeleton): static
    {
        $this->pieceSkeleton = $pieceSkeleton;

        return $this;
    }

    public function getProductSkeleton(): ?array
    {
        return $this->productSkeleton;
    }

    public function setProductSkeleton(?array $productSkeleton): static
    {
        $this->productSkeleton = $productSkeleton;

        return $this;
    }

    #[Groups(['model_type:read'])]
    public function getStructure(): ?array
    {
        return match ($this->category) {
            ModelCategory::COMPONENT => $this->componentSkeleton,
            ModelCategory::PIECE     => $this->pieceSkeleton,
            ModelCategory::PRODUCT   => $this->productSkeleton,
        };
    }

    #[Groups(['model_type:write'])]
    public function setStructure(?array $structure): static
    {
        if (!isset($this->category)) {
            $this->pendingStructure = $structure;

            return $this;
        }

        $this->applyStructureForCategory($structure, $this->category);

        return $this;
    }

    public function getCreatedAt(): DateTimeImmutable
    {
        return $this->createdAt;
    }

    public function getUpdatedAt(): DateTimeImmutable
    {
        return $this->updatedAt;
    }

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

    private function applyStructureForCategory(?array $structure, ModelCategory $category): void
    {
        if (ModelCategory::COMPONENT === $category) {
            $this->componentSkeleton = $structure;
            $this->pieceSkeleton     = null;
            $this->productSkeleton   = null;

            return;
        }

        if (ModelCategory::PIECE === $category) {
            $this->pieceSkeleton     = $structure;
            $this->componentSkeleton = null;
            $this->productSkeleton   = null;

            return;
        }

        $this->productSkeleton   = $structure;
        $this->componentSkeleton = null;
        $this->pieceSkeleton     = null;
    }
}
