<?php

declare(strict_types=1);

namespace App\Service\Utils;

use App\Entity\Core\File;
use Mimey\MimeTypes;
use Path\Exception\FileExistsException;
use Path\Exception\FileNotFoundException;
use Path\Exception\IOException;
use Path\Path;
use Symfony\Component\Config\FileLocator;

class FileUtils
{
    public function __construct()
    {
    }

    /**
     * Return a Path object
     * > Method is for testing purposes.
     *
     * @see https://path-php.net/api
     */
    protected function makePath(string $path): Path
    {
        return new Path($path);
    }

    /**
     * Return a FileLocator object
     * > Method is for testing purposes.
     *
     * @param array<string> $directories
     */
    protected function makeFileLocator(array $directories): FileLocator
    {
        return new FileLocator($directories);
    }

    /**
     * Return the mimetype corresponding to the given file extension.
     */
    public function getMimeTypeFromExt(string $ext): ?string
    {
        return (new MimeTypes())->getMimeType(ltrim($ext, '.'));
    }

    /**
     * Try to guess the mimetype from the filename.
     *
     * Return null if it did not manage to guess it.
     */
    public function guessMimeTypeFromFilename(string $filename): ?string
    {
        $ext = pathinfo($filename, PATHINFO_EXTENSION);
        if (empty($ext)) {
            return null;
        }

        return $this->getMimeTypeFromExt($ext);
    }

    /**
     * Test si le fichier passé en paramètre est une image.
     */
    public function isImage(File $file): bool
    {
        $mimetype = $file->getMimeType() ?? $this->guessMimeTypeFromFilename($file->getName());

        return boolval(preg_match('#^image#', $mimetype ?? ''));
    }

    /**
     * Génère un nom de fichier temporaire situé dans le répertoire var/tmp,
     * avec l'extension et le préfixe donnés.
     *
     * @throws \RuntimeException
     */
    public function getTempFilename(string $ext = 'tmp', string $prefix = ''): string
    {
        if (empty($ext)) {
            throw new \RuntimeException('Extension can not be empty');
        }
        $tempDir = PathUtils::getProjectDir().'/var/tmp';
        $this->makePath($tempDir)->mkdir(0777, true);

        return PathUtils::join($tempDir, uniqid($prefix).'.'.$ext);
    }

    /**
     * @throws IOException
     * @throws FileNotFoundException
     * @throws FileExistsException
     */
    public function rmIfExist(string $path): void
    {
        $this->makePath($path)->remove_p();
    }

    /**
     * Reads the content of a file.
     *
     * @param string $path the path to the file to be read
     *
     * @return string the content of the file
     *
     * @throws FileNotFoundException
     * @throws IOException
     */
    public function getFileContent(string $path): string
    {
        return $this->makePath($path)->getContent();
    }

    /**
     * Locate a file in the given directories and return its absolute path, or
     * null if no file were found.
     *
     * @param array<string> $directories
     */
    public function locate(array $directories, string $filename): ?string
    {
        $fileLocator = $this->makeFileLocator($directories);

        return $fileLocator->locate($filename, null, false)[0] ?? null;
    }

    /**
     * Recursively removes a directory and all its contents.
     *
     * @param string $path the path to the directory to be removed
     *
     * @throws FileNotFoundException
     * @throws IOException
     */
    public function rmTree(string $path): void
    {
        $this->makePath($path)->rmdir(true);
    }

    /**
     * List the files located in the given directory.
     *
     * @return list<string>
     *
     * @throws FileNotFoundException
     * @throws IOException
     */
    public function list(string $path, string $glob = '*'): array
    {
        $paths = $this->makePath($path)->glob($glob);

        return array_map('strval', $paths);
    }

    /**
     * Recursively remove a directory.
     */
    public static function rrmDir(string $path): void
    {
        if (!is_dir($path)) {
            throw new \RuntimeException(sprintf('Path %s is not a directory', $path));
        }

        $files = glob($path.'/*');
        foreach ($files as $file) {
            is_dir($file) ? self::rrmDir($file) : unlink($file);
        }
        rmdir($path);
    }
}
