<?php

declare(strict_types=1);

namespace App\Framework\Filesystem;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Filesystem\ValueObjects\FilePath;

/**
 * Repräsentiert ein Verzeichnis im Dateisystem mit Lazy-Loading-Unterstützung.
 *
 * @property-read FilePath|string $path Pfad zum Verzeichnis
 * @property-read Storage $storage Storage-Implementierung
 * @property-read array $contents Verzeichnisinhalt (lazy geladen)
 */
final readonly class Directory
{
    public function __construct(
        public FilePath|string $path,
        public Storage $storage,
        public array $contents = []
    ) {
    }

    /**
     * Get path as FilePath object
     */
    public function getPath(): FilePath
    {
        return $this->path instanceof FilePath ? $this->path : FilePath::create($this->path);
    }

    /**
     * Get path as string
     */
    public function getPathString(): string
    {
        return $this->path instanceof FilePath ? $this->path->toString() : $this->path;
    }

    /**
     * Prüft, ob das Verzeichnis existiert
     */
    public function exists(): bool
    {
        return is_dir($this->getPathString());
    }

    /**
     * Erstellt das Verzeichnis, falls es nicht existiert
     */
    public function create(int $permissions = 0755, bool $recursive = true): void
    {
        $this->storage->createDirectory($this->getPathString(), $permissions, $recursive);
    }

    /**
     * Gibt alle Dateien im Verzeichnis zurück
     *
     * @return File[] Liste von Dateiobjekten
     */
    public function getFiles(): array
    {
        $paths = $this->storage->listDirectory($this->getPathString());
        $files = [];

        foreach ($paths as $path) {
            if (is_file($path)) {
                $files[] = FilesystemFactory::createFile($path, $this->storage);
            }
        }

        return $files;
    }

    /**
     * Gibt alle Unterverzeichnisse zurück
     *
     * @return Directory[] Liste von Verzeichnisobjekten
     */
    public function getDirectories(): array
    {
        $paths = $this->storage->listDirectory($this->getPathString());
        $directories = [];

        foreach ($paths as $path) {
            if (is_dir($path)) {
                $directories[] = FilesystemFactory::createDirectory($path, $this->storage);
            }
        }

        return $directories;
    }

    /**
     * Gibt alle Dateien und Unterverzeichnisse in einem Durchgang zurück
     *
     * @return array{files: File[], directories: Directory[]}
     */
    public function getContents(): array
    {
        $paths = $this->storage->listDirectory($this->getPathString());
        $files = [];
        $directories = [];

        foreach ($paths as $path) {
            if (is_file($path)) {
                $files[] = FilesystemFactory::createFile($path, $this->storage);
            } elseif (is_dir($path)) {
                $directories[] = FilesystemFactory::createDirectory($path, $this->storage);
            }
        }

        return ['files' => $files, 'directories' => $directories];
    }

    /**
     * Gibt eine Datei im Verzeichnis zurück
     */
    public function getFile(string $filename): File
    {
        $filePath = $this->getPath()->join($filename);

        return FilesystemFactory::createFile($filePath, $this->storage);
    }

    /**
     * Gibt ein Unterverzeichnis zurück
     */
    public function getDirectory(string $name): Directory
    {
        $dirPath = $this->getPath()->join($name);

        return FilesystemFactory::createDirectory($dirPath, $this->storage);
    }

    /**
     * Lädt den Verzeichnisinhalt neu
     */
    public function refresh(): Directory
    {
        return FilesystemFactory::createDirectory($this->path, $this->storage);
    }

    /**
     * Get directory name
     */
    public function getName(): string
    {
        return $this->getPath()->getFilename();
    }

    /**
     * Get parent directory
     */
    public function getParent(): Directory
    {
        $parentPath = $this->getPath()->getDirectory();

        return FilesystemFactory::createDirectory($parentPath, $this->storage);
    }

    /**
     * Get total size of all files in directory
     */
    public function getTotalSize(): Byte
    {
        $totalBytes = 0;
        $files = $this->getFiles();

        foreach ($files as $file) {
            $totalBytes += $file->getSize()->toBytes();
        }

        return Byte::fromBytes($totalBytes);
    }

    /**
     * Check if directory is empty
     */
    public function isEmpty(): bool
    {
        $contents = $this->getContents();

        return empty($contents['files']) && empty($contents['directories']);
    }

    /**
     * Count files in directory
     */
    public function countFiles(): int
    {
        return count($this->getFiles());
    }

    /**
     * Count subdirectories
     */
    public function countDirectories(): int
    {
        return count($this->getDirectories());
    }

    /**
     * Lädt Verzeichnisbaum rekursiv mit optimierter Performance
     * Nutzt intern Fiber-basierte Parallelisierung
     */
    public function getTree(int $maxDepth = 3): array
    {
        return $this->buildTree($this, 0, $maxDepth);
    }

    /**
     * Berechnet die Gesamtgröße mit verbesserter Performance
     * Nutzt Batch-Operationen für paralleles Laden
     */
    public function getTotalSizeOptimized(): Byte
    {
        $files = $this->getFiles();

        if (empty($files)) {
            return Byte::fromBytes(0);
        }

        // Nutze Batch-Operation für paralleles Laden aller Dateigrößen
        $operations = [];
        foreach ($files as $index => $file) {
            $operations["size_$index"] = fn () => $file->getSize()->toBytes();
        }

        $sizes = $this->storage->batch($operations);
        $totalBytes = 0;

        foreach ($sizes as $size) {
            if (! ($size instanceof \Throwable)) {
                $totalBytes += $size;
            }
        }

        return Byte::fromBytes($totalBytes);
    }

    /**
     * Lädt alle Inhalte mit verbesserter Performance
     * Nutzt intern Batch-Operationen für parallele Verarbeitung
     */
    public function getContentsOptimized(): array
    {
        $paths = $this->storage->listDirectory($this->getPathString());

        if (empty($paths)) {
            return ['files' => [], 'directories' => []];
        }

        // Nutze Batch-Operation für parallele Typ-Prüfung
        $operations = [];
        foreach ($paths as $path) {
            $operations["check_$path"] = fn () => [
                'path' => $path,
                'is_file' => is_file($path),
                'is_dir' => is_dir($path),
            ];
        }

        $results = $this->storage->batch($operations);

        $files = [];
        $directories = [];

        foreach ($results as $result) {
            if ($result instanceof \Throwable) {
                continue;
            }

            if ($result['is_file']) {
                $files[] = FilesystemFactory::createFile($result['path'], $this->storage);
            } elseif ($result['is_dir']) {
                $directories[] = FilesystemFactory::createDirectory($result['path'], $this->storage);
            }
        }

        return ['files' => $files, 'directories' => $directories];
    }

    /**
     * Hilfsmethode für rekursives Laden mit Fiber-Optimierung
     */
    private function buildTree(Directory $directory, int $currentDepth, int $maxDepth): array
    {
        if ($currentDepth >= $maxDepth) {
            return ['files' => [], 'directories' => []];
        }

        $contents = $directory->getContentsOptimized();

        $tree = [
            'files' => $contents['files'],
            'directories' => [],
        ];

        if (! empty($contents['directories'])) {
            // Nutze Batch-Operation für paralleles rekursives Laden
            $operations = [];
            foreach ($contents['directories'] as $index => $subDir) {
                $operations["tree_$index"] = fn () => [
                    'directory' => $subDir,
                    'subtree' => $this->buildTree($subDir, $currentDepth + 1, $maxDepth),
                ];
            }

            $results = $this->storage->batch($operations);

            foreach ($results as $result) {
                if (! ($result instanceof \Throwable)) {
                    $tree['directories'][] = [
                        'directory' => $result['directory'],
                        'contents' => $result['subtree'],
                    ];
                }
            }
        }

        return $tree;
    }
}
