<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Versioning;

use CpCreation\VitiCore\Versioning\Model\BusinessVersion;
use CpCreation\VitiCore\Versioning\Repository\BusinessVersionRepository;
use Doctrine\ORM\EntityManagerInterface;

final readonly class BusinessVersionManager
{
    public function __construct(
        private BusinessVersionRepository $businessVersionRepository,
        private EntityManagerInterface $entityManager,
    ) {
    }

    /**
     * @param array<string, mixed> $snapshot
     * @param array<string, mixed> $metadata
     */
    public function create(
        object $subject,
        array $snapshot,
        string $scope = 'default',
        ?string $createdBy = null,
        string $status = BusinessVersion::STATUS_DRAFT,
        ?\DateTimeImmutable $validFrom = null,
        array $metadata = [],
    ): BusinessVersion {
        $objectClass = $subject::class;
        $objectId = $this->resolveObjectId($subject);
        $versionNumber = $this->businessVersionRepository->getNextVersion($objectClass, $objectId, $scope);

        $version = new BusinessVersion($objectClass, $objectId, $versionNumber, $snapshot, $scope);
        $version->setCreatedBy($createdBy);
        $version->setStatus($status);
        $version->setValidFrom($validFrom);
        $version->setMetadata($metadata);

        $this->entityManager->persist($version);

        return $version;
    }

    public function publish(BusinessVersion $version, bool $archiveCurrent = true): void
    {
        if ($archiveCurrent) {
            $current = $this->businessVersionRepository->findPublished(
                $version->getObjectClass(),
                $version->getObjectId(),
                $version->getScope(),
            );

            if (null !== $current && $current !== $version) {
                $current->archive();
            }
        }

        $version->publish();
    }

    private function resolveObjectId(object $subject): string
    {
        if (!method_exists($subject, 'getId')) {
            throw new \InvalidArgumentException(sprintf('Versioned subject "%s" must expose a getId() method.', $subject::class));
        }

        $id = $subject->getId();

        if (null === $id || (!is_scalar($id) && !method_exists($id, '__toString'))) {
            throw new \InvalidArgumentException(sprintf('Versioned subject "%s" has no stringable identifier.', $subject::class));
        }

        return (string) $id;
    }
}
