<?php

/*
 * This file is part of the Serenity package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Serenity\MediaBundle\Twig\Components;

use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Random\RandomException;
use Serenity\CoreBundle\Command\NotificationCommand;
use Serenity\CoreBundle\Domain\Feature\SortableInterface;
use Serenity\CoreBundle\Notification\PushLevel;
use Serenity\MediaBundle\Doctrine\Repository\AttachmentRepository;
use Serenity\MediaBundle\Entity\Attachment;
use Serenity\MediaBundle\Enum\PathStorage;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Uid\Uuid;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Contracts\Service\Attribute\Required;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\Attribute\LiveAction;
use Symfony\UX\LiveComponent\Attribute\LiveArg;
use Symfony\UX\LiveComponent\Attribute\LiveListener;
use Symfony\UX\LiveComponent\Attribute\LiveProp;
use Symfony\UX\LiveComponent\ComponentToolsTrait;
use Symfony\UX\LiveComponent\DefaultActionTrait;
use Symfony\UX\LiveComponent\ValidatableComponentTrait;
use Symfony\UX\TwigComponent\Attribute\PreMount;

#[AsLiveComponent(template: '@components/SerenityMedia/FilesForm.html.twig')]
class FilesForm extends AbstractController
{
    use ComponentToolsTrait;
    use DefaultActionTrait;
    use ValidatableComponentTrait;

    private string $relativeDefaultLocalDirectory;
    private readonly CacheManager $imagineCacheManager;
    private readonly ValidatorInterface $validator;
    private readonly MessageBusInterface $commandBus;
    private readonly AttachmentRepository $attachmentRepository;

    #[Required]
    public function setLocalDirectory(
        #[Autowire(param: 'relative_default_local_directory')]
        string $relativeDefaultLocalDirectory,
    ): void {
        $this->relativeDefaultLocalDirectory = $relativeDefaultLocalDirectory;
    }

    #[Required]
    public function setCacheManager(CacheManager $imagineCacheManager): void
    {
        $this->imagineCacheManager = $imagineCacheManager;
    }

    #[Required]
    public function setValidator(ValidatorInterface $validator): void
    {
        $this->validator = $validator;
    }

    #[Required]
    public function setMessageBus(MessageBusInterface $commandBus): void
    {
        $this->commandBus = $commandBus;
    }

    #[Required]
    public function setAttachmentRepository(AttachmentRepository $attachmentRepository): void
    {
        $this->attachmentRepository = $attachmentRepository;
    }

    #[LiveProp(writable: true, hydrateWith: 'hydrateFiles', dehydrateWith: 'dehydrateFiles', updateFromParent: true)]
    public array $files = [];

    #[LiveProp(writable: true)]
    public ?string $uploadedFile = null;

    /**
     * @var array<string, array{path: string, originalName: string, mimeType: string, url: string}>
     */
    #[LiveProp(writable: false, updateFromParent: true)]
    public array $uploadedFiles = [];

    #[LiveProp(writable: false)]
    public string $type;

    #[LiveProp(writable: false)]
    public string $classForValidateUpload;

    #[LiveProp(writable: false)]
    public string $formId;

    #[LiveProp(writable: false)]
    public string $name;

    /**
     * @param array<string, mixed> $data
     *
     * @return array<string, mixed>
     */
    #[PreMount]
    public function preMount(array $data): array
    {
        $resolver = new OptionsResolver();
        $resolver->setIgnoreUndefined(true);

        return $resolver->resolve($data) + $data;
    }

    /**
     * @param array<string, array{path: string, originalName: string, mimeType: string, url: string}> $uploadedFiles
     */
    #[LiveListener('filesForm:uploadedFilesChanged')]
    public function uploadedFilesChange(#[LiveArg] array $uploadedFiles): void
    {
        $this->uploadedFiles = $uploadedFiles;
    }

    /**
     * @throws RandomException|\Symfony\Component\Messenger\Exception\ExceptionInterface
     */
    #[LiveAction]
    public function upload(Request $request): void
    {
        $upload = $request->files->all();
        if (!$upload) {
            return;
        }
        $uploadedFiles = $this->findUploadedFiles($upload);
        $addUploadedFiles = [];
        $index = \count($this->files) + \count($this->uploadedFiles);
        foreach ($uploadedFiles as $key => $uploadedFile) {
            $key = preg_replace('/_(\d+)(?!.*\d)/', '_'.$index, $key);
            $file = new $this->classForValidateUpload();

            // ✅ Typage dynamique pour PHPStan
            if (!method_exists($file, 'setUploadedFile')) {
                throw new \LogicException(\sprintf('Class "%s" must implement a setUploadedFile method.', $this->classForValidateUpload));
            }

            $file->setUploadedFile($uploadedFile);
            $constraint = $this->validator->validate($file);

            if ($constraint->count()) {
                $notification = new NotificationCommand(
                    message: $constraint->get(0)->getMessage(),
                    level: PushLevel::ERROR
                );
                $this->commandBus->dispatch($notification);

                return;
            }

            $addUploadedFiles[$key] = $this->serializeUploadedFile($uploadedFile);
            ++$index;
        }

        $this->emitUp('uploadedFilesChanged', [
            'addUploadedFiles' => $addUploadedFiles,
        ]);
    }

    /**
     * @param array<string, mixed> $array
     *
     * @return array<string, UploadedFile>
     */
    private function findUploadedFiles(array $array, string $needle = 'uploadedFile', string $prefix = ''): array
    {
        $result = [];
        foreach ($array as $key => $value) {
            $path = '' === $prefix ? $key : $prefix.'_'.$key;

            if (false !== mb_stripos($key, $needle)) {
                $result[$path] = $value;
            }

            if (\is_array($value)) {
                $result += $this->findUploadedFiles($value, $needle, $path);
            }
        }

        return $result;
    }

    /**
     * @throws RandomException
     *
     * @return array{path: string, originalName: string, mimeType: string}
     */
    private function serializeUploadedFile(UploadedFile $uploadedFile): array
    {
        $random = bin2hex(random_bytes(4));
        $filename = $random.'-'.$uploadedFile->getClientOriginalName();
        $uploadedFile->move($this->relativeDefaultLocalDirectory.PathStorage::TEMP->value, $filename);

        return [
            'path' => $this->relativeDefaultLocalDirectory.PathStorage::pathTemp($filename),
            'originalName' => $uploadedFile->getClientOriginalName(),
            'mimeType' => $uploadedFile->getClientMimeType(),
            'url' => $this->imagineCacheManager->generateUrl($this->relativeDefaultLocalDirectory.PathStorage::pathTemp($filename), 'thumbnail_temp'),
        ];
    }

    #[LiveAction]
    public function sortable(#[LiveArg] int $currentPosition, #[LiveArg] int $newPosition, #[LiveArg] ?string $id = null): void
    {
        if (Uuid::isValid($id)) {
            $object = $this->attachmentRepository->find($id);
            if (!$object instanceof SortableInterface) {
                return;
            }
            $object->setPosition($newPosition);
            $this->attachmentRepository->save($object);
        }
    }

    /**
     * @param array<string> $identifiers
     */
    #[LiveAction]
    public function remove(#[LiveArg] array $identifiers): void
    {
        $removeUploadedFiles = [];
        foreach ($identifiers as $identifier) {
            if (!Uuid::isValid($identifier)) {
                $object = $this->attachmentRepository->find($identifier);
                if (null !== $object) {
                    $this->attachmentRepository->remove($object);
                }
            } else {
                $removeUploadedFiles[] = $identifiers;
            }
        }
        if (!empty($removeUploadedFiles)) {
            $this->emitUp('uploadedFilesChanged', [
                'removeUploadedFiles' => $removeUploadedFiles,
            ]);
        }
    }

    /**
     * @return list<array<string, mixed>>|null
     */
    public function dehydrateFiles(mixed $data): ?array
    {
        $files = [];
        /* @var Attachment $attachment */
        foreach ($data as $attachment) {
            if ($attachment instanceof Attachment) {
                $files[] = [
                    'id' => $attachment->getId(),
                    'position' => $attachment->getPosition(),
                    'name' => $attachment->getFile()->getName(),
                    'originalName' => $attachment->getFile()->getOriginalName(),
                    'mimeType' => $attachment->getFile()->getMimeType(),
                    'size' => $attachment->getFile()->getSize(),
                    'dimensions' => $attachment->getFile()->getDimensions(),
                    'alt' => $attachment->getAlt(),
                    'credit' => $attachment->getCredit(),
                ];
            }
        }

        return $files;
    }

    /**
     * @param list<array{id: string, position: int, name: string, originalName: string, mimeType: string, size: int, dimensions: mixed, alt: ?string, credit: ?string}>|null $data
     *
     * @return list<\Serenity\MediaBundle\Entity\Image>|null
     */
    public function hydrateFiles(mixed $data = null): ?array
    {
        if (null === $data) {
            return null;
        }
        $attachments = [];
        foreach ($data as $item) {
            $file = new \Serenity\MediaBundle\Entity\Image();
            $file->setId(Uuid::fromString($item['id']));
            $file->setPosition($item['position']);
            $file->getFile()?->setName($item['name']);
            $file->getFile()?->setOriginalName($item['name']);
            $file->getFile()?->setMimeType($item['mimeType']);
            $file->getFile()?->setSize($item['size']);
            $file->getFile()?->setDimensions($item['dimensions']);
            $file->setAlt($item['alt']);
            $file->setCredit($item['credit']);
            $attachments[] = $file;
        }

        return $attachments;
    }
}
