<?php

namespace Spatie\MediaLibrary\Conversions;

use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use RuntimeException;
use Spatie\MediaLibrary\Conversions\Actions\PerformConversionAction;
use Spatie\MediaLibrary\Conversions\ImageGenerators\ImageGeneratorFactory;
use Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob;
use Spatie\MediaLibrary\MediaCollections\Filesystem;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob;
use Spatie\MediaLibrary\Support\TemporaryDirectory;

class FileManipulator
{
    public function createDerivedFiles(
        Media $media,
        array $onlyConversionNames = [],
        bool $onlyMissing = false,
        bool $withResponsiveImages = false,
        bool $queueAll = false,
    ): void {
        if (! $this->canConvertMedia($media)) {
            return;
        }

        $allConversions = ConversionCollection::createForMedia($media)
            ->filter(function (Conversion $conversion) use ($onlyConversionNames) {
                if (count($onlyConversionNames) === 0) {
                    return true;
                }

                return in_array($conversion->getName(), $onlyConversionNames);
            })
            ->filter(fn (Conversion $conversion) => $conversion->shouldBePerformedOn($media->collection_name));

        if ($queueAll) {
            $this
                ->dispatchQueuedConversions($media, $allConversions, $onlyMissing)
                ->generateResponsiveImages($media, $withResponsiveImages);

            return;
        }

        [$deferredConversions, $remaining] = $allConversions->partition(
            fn (Conversion $conversion) => $conversion->shouldBeDeferred()
        );

        [$queuedConversions, $conversions] = $remaining->partition(
            fn (Conversion $conversion) => $conversion->shouldBeQueued()
        );

        $this
            ->performConversions($conversions, $media, $onlyMissing)
            ->performDeferredConversions($deferredConversions, $media, $onlyMissing)
            ->dispatchQueuedConversions($media, $queuedConversions, $onlyMissing)
            ->generateResponsiveImages($media, $withResponsiveImages);
    }

    public function performConversions(
        ConversionCollection $conversions,
        Media $media,
        bool $onlyMissing = false
    ): self {
        $conversions = $conversions
            ->when(
                $onlyMissing,
                fn (ConversionCollection $conversions) => $conversions->reject(function (Conversion $conversion) use ($media) {
                    $relativePath = $media->getPath($conversion->getName());

                    if ($rootPath = config("filesystems.disks.{$media->disk}.root")) {
                        $relativePath = str_replace($rootPath, '', $relativePath);
                    }

                    return Storage::disk($media->disk)->exists($relativePath);
                })
            );

        if ($conversions->isEmpty()) {
            return $this;
        }

        $temporaryDirectory = TemporaryDirectory::create();

        $copiedOriginalFile = app(Filesystem::class)->copyFromMediaLibrary(
            $media,
            $temporaryDirectory->path(Str::random(32).'.'.$media->extension)
        );

        // Check if the file exists and has content to avoid issues with missing files
        if (! file_exists($copiedOriginalFile) || filesize($copiedOriginalFile) === 0) {
            return $this;
        }

        $conversions->each(fn (Conversion $conversion) => (new PerformConversionAction)->execute($conversion, $media, $copiedOriginalFile));

        $temporaryDirectory->delete();

        return $this;
    }

    protected function performDeferredConversions(
        ConversionCollection $conversions,
        Media $media,
        bool $onlyMissing = false
    ): self {
        if ($conversions->isEmpty()) {
            return $this;
        }

        if (! function_exists('defer')) {
            throw new RuntimeException(
                'Deferred conversions require Laravel 11.23 or higher. Use queued() or nonQueued() instead.',
            );
        }

        defer(fn () => $this->performConversions($conversions, $media, $onlyMissing));

        return $this;
    }

    /**
     * @return $this
     */
    protected function dispatchQueuedConversions(
        Media $media,
        ConversionCollection $conversions,
        bool $onlyMissing = false
    ): self {
        if ($conversions->isEmpty()) {
            return $this;
        }

        $performConversionsJobClass = config(
            'media-library.jobs.perform_conversions',
            PerformConversionsJob::class
        );

        /** @var PerformConversionsJob $job */
        $job = (new $performConversionsJobClass($conversions, $media, $onlyMissing))
            ->onConnection(config('media-library.queue_connection_name'))
            ->onQueue(config('media-library.queue_name'));

        config('media-library.queue_conversions_after_database_commit')
            ? dispatch($job)->afterCommit()
            : dispatch($job);

        return $this;
    }

    /**
     * @return $this
     */
    protected function generateResponsiveImages(Media $media, bool $withResponsiveImages): self
    {
        if (! $withResponsiveImages) {
            return $this;
        }

        if (! count($media->responsive_images)) {
            return $this;
        }

        $generateResponsiveImagesJobClass = config(
            'media-library.jobs.generate_responsive_images',
            GenerateResponsiveImagesJob::class
        );

        /** @var GenerateResponsiveImagesJob $job */
        $job = (new $generateResponsiveImagesJobClass($media))
            ->onConnection(config('media-library.queue_connection_name'))
            ->onQueue(config('media-library.queue_name'));

        config('media-library.queue_conversions_after_database_commit')
            ? dispatch($job)->afterCommit()
            : dispatch($job);

        return $this;
    }

    protected function canConvertMedia(Media $media): bool
    {
        $imageGenerator = ImageGeneratorFactory::forMedia($media);

        return $imageGenerator ? true : false;
    }
}
