<?php

declare(strict_types=1);

namespace App\Framework\AsyncExamples\Assets;

use App\Framework\Async\FiberManager;
use App\Framework\Filesystem\Storage;

/**
 * Asynchroner Asset-Processor für CSS, JS und Bilder
 */
final class AsyncAssetProcessor
{
    public function __construct(
        private readonly Storage $storage,
        private readonly FiberManager $fiberManager = new FiberManager(),
        /** @var array<string, mixed> */
        private readonly array $config = []
    ) {
    }

    /**
     * Verarbeitet alle Assets parallel
     *
     * @return array<string, mixed>
     */
    public function processAll(string $sourceDir, string $outputDir): array
    {
        /** @var array<string, \Closure> */
        $operations = [
            'css' => fn () => $this->processCss($sourceDir . '/css', $outputDir . '/css'),
            'js' => fn () => $this->processJs($sourceDir . '/js', $outputDir . '/js'),
            'images' => fn () => $this->processImages($sourceDir . '/images', $outputDir . '/images'),
        ];

        return $this->fiberManager->batch($operations);
    }

    /**
     * Verarbeitet CSS-Dateien
     *
     * @return array<string, string>
     */
    public function processCss(string $sourceDir, string $outputDir): array
    {
        $cssFiles = $this->findFiles($sourceDir, '*.css');
        $scssFiles = $this->findFiles($sourceDir, '*.scss');

        /** @var array<string, \Closure> */
        $operations = [];

        // Verarbeite CSS-Dateien
        foreach ($cssFiles as $file) {
            $operations["css_{$file}"] = fn () => $this->minifyCss($sourceDir . '/' . $file, $outputDir);
        }

        // Verarbeite SCSS-Dateien
        foreach ($scssFiles as $file) {
            $operations["scss_{$file}"] = fn () => $this->compileSass($sourceDir . '/' . $file, $outputDir);
        }

        return $this->fiberManager->batch($operations);
    }

    /**
     * Verarbeitet JavaScript-Dateien
     *
     * @return array<string, string>
     */
    public function processJs(string $sourceDir, string $outputDir): array
    {
        $jsFiles = $this->findFiles($sourceDir, '*.js');

        /** @var array<string, \Closure> */
        $operations = [];
        foreach ($jsFiles as $file) {
            $operations["js_{$file}"] = fn () => $this->minifyJs($sourceDir . '/' . $file, $outputDir);
        }

        return $this->fiberManager->batch($operations);
    }

    /**
     * Verarbeitet Bilder
     *
     * @return array<string, string>
     */
    public function processImages(string $sourceDir, string $outputDir): array
    {
        $imageFiles = array_merge(
            $this->findFiles($sourceDir, '*.jpg'),
            $this->findFiles($sourceDir, '*.jpeg'),
            $this->findFiles($sourceDir, '*.png'),
            $this->findFiles($sourceDir, '*.gif'),
            $this->findFiles($sourceDir, '*.svg')
        );

        /** @var array<string, \Closure> */
        $operations = [];
        foreach ($imageFiles as $file) {
            $operations["img_{$file}"] = fn () => $this->optimizeImage($sourceDir . '/' . $file, $outputDir);
        }

        return $this->fiberManager->batch($operations);
    }

    /**
     * Bündelt JavaScript-Dateien
     *
     * @param array<int, string> $files
     */
    public function bundleJs(array $files, string $outputFile): string
    {
        /** @var array<string, \Closure> */
        $operations = [];
        foreach ($files as $file) {
            $operations[$file] = fn () => $this->storage->get($file);
        }

        $contents = $this->fiberManager->batch($operations);
        $bundled = implode("\n\n", array_filter($contents, fn ($c) => ! ($c instanceof \Throwable)));

        $minified = $this->minifyJsContent($bundled);
        $this->storage->put($outputFile, $minified);

        return $outputFile;
    }

    /**
     * Bündelt CSS-Dateien
     *
     * @param array<int, string> $files
     */
    public function bundleCss(array $files, string $outputFile): string
    {
        /** @var array<string, \Closure> */
        $operations = [];
        foreach ($files as $file) {
            $operations[$file] = fn () => $this->storage->get($file);
        }

        $contents = $this->fiberManager->batch($operations);
        $bundled = implode("\n\n", array_filter($contents, fn ($c) => ! ($c instanceof \Throwable)));

        $minified = $this->minifyCssContent($bundled);
        $this->storage->put($outputFile, $minified);

        return $outputFile;
    }

    /**
     * Generiert verschiedene Bildgrößen parallel
     *
     * @param array<string, array<string, mixed>> $sizes
     * @return array<string, string>
     */
    public function generateImageSizes(string $sourceImage, array $sizes, string $outputDir): array
    {
        /** @var array<string, \Closure> */
        $operations = [];
        foreach ($sizes as $sizeName => $dimensions) {
            $operations[$sizeName] = fn () => $this->resizeImage(
                $sourceImage,
                $outputDir . '/' . $sizeName . '_' . basename($sourceImage),
                $dimensions['width'],
                $dimensions['height']
            );
        }

        return $this->fiberManager->batch($operations);
    }

    private function minifyCss(string $sourceFile, string $outputDir): string
    {
        $content = $this->storage->get($sourceFile);
        $minified = $this->minifyCssContent($content);

        $outputFile = $outputDir . '/' . basename($sourceFile);
        $this->storage->put($outputFile, $minified);

        return $outputFile;
    }

    private function minifyCssContent(string $content): string
    {
        // Vereinfachte CSS-Minifizierung
        $content = preg_replace('/\s+/', ' ', $content);
        $content = str_replace(['; ', ' {', '{ ', ' }', '} ', ': '], [';', '{', '{', '}', '}', ':'], $content);
        $content = preg_replace('/\/\*.*?\*\//', '', $content);

        return trim($content);
    }

    private function compileSass(string $sourceFile, string $outputDir): string
    {
        // Vereinfachte SCSS-Kompilierung
        // In Produktion würde man eine echte SCSS-Library verwenden
        $content = $this->storage->get($sourceFile);

        // Basis-Variable-Ersetzung
        $content = $this->processSassVariables($content);

        $minified = $this->minifyCssContent($content);
        $outputFile = $outputDir . '/' . str_replace('.scss', '.css', basename($sourceFile));
        $this->storage->put($outputFile, $minified);

        return $outputFile;
    }

    private function minifyJs(string $sourceFile, string $outputDir): string
    {
        $content = $this->storage->get($sourceFile);
        $minified = $this->minifyJsContent($content);

        $outputFile = $outputDir . '/' . basename($sourceFile);
        $this->storage->put($outputFile, $minified);

        return $outputFile;
    }

    private function minifyJsContent(string $content): string
    {
        // Vereinfachte JS-Minifizierung
        $content = preg_replace('/\s+/', ' ', $content);
        $content = str_replace(['; ', ' {', '{ ', ' }', '} '], [';', '{', '{', '}', '}'], $content);
        $content = preg_replace('/\/\*.*?\*\//', '', $content);
        $content = preg_replace('/\/\/.*$/m', '', $content);

        return trim($content);
    }

    private function optimizeImage(string $sourceFile, string $outputDir): string
    {
        // Vereinfachte Bildoptimierung
        // In Produktion würde man Bibliotheken wie Imagick verwenden
        $outputFile = $outputDir . '/' . basename($sourceFile);

        // Kopiere erstmal nur
        $content = $this->storage->get($sourceFile);
        $this->storage->put($outputFile, $content);

        return $outputFile;
    }

    private function resizeImage(string $sourceFile, string $outputFile, int $width, int $height): string
    {
        // Vereinfachte Bildgrößenänderung
        // In Produktion würde man GD oder Imagick verwenden
        $content = $this->storage->get($sourceFile);
        $this->storage->put($outputFile, $content);

        return $outputFile;
    }

    private function processSassVariables(string $content): string
    {
        // Vereinfachte Variable-Verarbeitung
        preg_match_all('/\$([a-zA-Z0-9_-]+)\s*:\s*([^;]+);/', $content, $matches);

        /** @var array<string, string> */
        $variables = [];
        for ($i = 0; $i < count($matches[0]); $i++) {
            $variables['$' . $matches[1][$i]] = $matches[2][$i];
        }

        foreach ($variables as $var => $value) {
            $content = str_replace($var, $value, $content);
        }

        // Entferne Variable-Definitionen
        $content = preg_replace('/\$[a-zA-Z0-9_-]+\s*:\s*[^;]+;\s*/', '', $content);

        return $content;
    }

    /**
     * @return array<int, string>
     */
    private function findFiles(string $directory, string $pattern): array
    {
        if (! is_dir($directory)) {
            return [];
        }

        $files = glob($directory . '/' . $pattern);

        return array_map('basename', $files ?: []);
    }

    /**
     * Überwacht Dateien auf Änderungen und verarbeitet sie automatisch
     */
    public function watch(string $sourceDir, string $outputDir): void
    {
        $this->fiberManager->async(function () use ($sourceDir, $outputDir) {
            /** @var array<string, int|false> */
            $lastCheck = [];

            while (true) {
                /** @var array<int, string> */
                $files = array_merge(
                    glob($sourceDir . '/**/*.css') ?: [],
                    glob($sourceDir . '/**/*.scss') ?: [],
                    glob($sourceDir . '/**/*.js') ?: []
                );

                foreach ($files as $file) {
                    $mtime = filemtime($file);
                    if (! isset($lastCheck[$file]) || $lastCheck[$file] !== $mtime) {
                        $this->processFile($file, $sourceDir, $outputDir);
                        $lastCheck[$file] = $mtime;
                    }
                }

                sleep(1); // Prüfe jede Sekunde
            }
        });
    }

    private function processFile(string $file, string $sourceDir, string $outputDir): void
    {
        $extension = pathinfo($file, PATHINFO_EXTENSION);
        $relativePath = str_replace($sourceDir . '/', '', $file);

        switch ($extension) {
            case 'css':
                $this->minifyCss($file, dirname($outputDir . '/' . $relativePath));

                break;
            case 'scss':
                $this->compileSass($file, dirname($outputDir . '/' . $relativePath));

                break;
            case 'js':
                $this->minifyJs($file, dirname($outputDir . '/' . $relativePath));

                break;
        }
    }
}
