<?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\Notification\Command\NotificationCommand;
use Serenity\CoreBundle\Notification\Enum\PushLevel;
use Serenity\CoreBundle\Shared\Feature\SortableInterface;
use Serenity\MediaBundle\Doctrine\Repository\AttachmentRepository;
use Serenity\MediaBundle\Entity\Attachment;
use Serenity\MediaBundle\Entity\Image;
use Serenity\MediaBundle\Enum\PathStorage;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\DependencyInjection\Attribute\Target;
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(#[Target] MessageBusInterface $commandBus): void
    {
        $this->commandBus = $commandBus;
    }

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

    /** @var list<Attachment> */
    #[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) {
            // preg_replace peut retourner null => on force une string
            $key = preg_replace('/_(\d+)(?!.*\d)/', '_'.$index, $key) ?? (string) $key;

            $file = new $this->classForValidateUpload();

            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)) {
                if ($value instanceof UploadedFile) {
                    $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, url: string}
     */
    private function serializeUploadedFile(UploadedFile $uploadedFile): array
    {
        $random = bin2hex(random_bytes(4));
        $filename = $random.'-'.$uploadedFile->getClientOriginalName();
        $uploadedFile->move($this->relativeDefaultLocalDirectory.PathStorage::TEMP->value, $filename);

        $path = $this->relativeDefaultLocalDirectory.PathStorage::pathTemp($filename);

        return [
            'path' => $path,
            'originalName' => $uploadedFile->getClientOriginalName(),
            'mimeType' => (string) $uploadedFile->getClientMimeType(),
            'url' => $this->imagineCacheManager->generateUrl($path, 'thumbnail_temp'),
        ];
    }

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

    /**
     * @param list<string> $identifiers
     */
    #[LiveAction]
    public function remove(#[LiveArg] array $identifiers): void
    {
        $removeUploadedFiles = [];

        foreach ($identifiers as $identifier) {
            // UUID => suppression en base
            if (Uuid::isValid($identifier)) {
                $object = $this->attachmentRepository->find($identifier);
                if (null !== $object) {
                    $this->attachmentRepository->remove($object);
                }
                continue;
            }

            // sinon => suppression côté uploadedFiles (temp key)
            $removeUploadedFiles[] = $identifier;
        }

        if ([] !== $removeUploadedFiles) {
            $this->emitUp('uploadedFilesChanged', [
                'removeUploadedFiles' => $removeUploadedFiles,
            ]);
        }
    }

    /**
     * @return list<array{
     *     id: string,
     *     position: int,
     *     name: string|null,
     *     originalName: string|null,
     *     mimeType: string|null,
     *     size: int|null,
     *     dimensions: mixed,
     *     alt: ?string,
     *     credit: ?string
     * }>|null
     */
    public function dehydrateFiles(mixed $data): ?array
    {
        $files = [];

        foreach ($data as $attachment) {
            if (!$attachment instanceof Attachment) {
                continue;
            }

            $id = $attachment->getId();
            if (null === $id) {
                continue; // entité pas persistée, on ne peut pas l'hydrater correctement
            }

            $embedded = $attachment->getFile();

            $files[] = [
                'id' => $id->toRfc4122(),
                'position' => $attachment->getPosition(),
                'name' => $embedded?->getName(),
                'originalName' => $embedded?->getOriginalName(),
                'mimeType' => $embedded?->getMimeType(),
                'size' => $embedded?->getSize(),
                'dimensions' => $embedded?->getDimensions(),
                'alt' => $attachment->getAlt(),
                'credit' => $attachment->getCredit(),
            ];
        }

        return $files;
    }

    /**
     * @param list<array{
     *     id: string,
     *     position: int,
     *     name: string|null,
     *     originalName: string|null,
     *     mimeType: string|null,
     *     size: int|null,
     *     dimensions: mixed,
     *     alt: ?string,
     *     credit: ?string
     * }>|null $data
     *
     * @return list<Attachment>|null
     */
    public function hydrateFiles(mixed $data = null): ?array
    {
        if (null === $data) {
            return null;
        }

        $attachments = [];

        foreach ($data as $item) {
            $file = new Image();
            $file->setId(Uuid::fromString($item['id']));
            $file->setPosition($item['position']);

            $embedded = $file->getFile();
            if (null !== $embedded) {
                $embedded->setName($item['name']);
                $embedded->setOriginalName($item['originalName']); // ✅ fix
                $embedded->setMimeType($item['mimeType']);
                $embedded->setSize($item['size']);
                $embedded->setDimensions($item['dimensions']);
            }

            $file->setAlt($item['alt']);
            $file->setCredit($item['credit']);

            $attachments[] = $file;
        }

        return $attachments;
    }
}
