<?php

declare (strict_types=1);
namespace OCA\Talk\Vendor\CuyZ\Valinor\Cache;

use OCA\Talk\Vendor\CuyZ\Valinor\Cache\Exception\CacheDirectoryNotWritable;
use OCA\Talk\Vendor\CuyZ\Valinor\Cache\Exception\CompiledPhpCacheFileNotWritten;
use OCA\Talk\Vendor\CuyZ\Valinor\Cache\Exception\CorruptedCompiledPhpCacheFile;
use Error;
use FilesystemIterator;
use function bin2hex;
use function chmod;
use function file_exists;
use function file_put_contents;
use function is_dir;
use function mkdir;
use function random_bytes;
use function rename;
use function rmdir;
use function str_contains;
use function umask;
use function unlink;
/**
 * @api
 *
 * @template EntryType
 * @implements Cache<EntryType>
 */
final class FileSystemCache implements Cache
{
    private const GENERATED_MESSAGE = 'Generated by ' . self::class;
    public function __construct(private string $cacheDir)
    {
    }
    /** @internal */
    public function get(string $key, mixed ...$arguments): mixed
    {
        $filename = $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php';
        if (!file_exists($filename)) {
            return null;
        }
        try {
            return (require $filename)(...$arguments);
            // @phpstan-ignore callable.nonCallable
        } catch (Error) {
            throw new CorruptedCompiledPhpCacheFile($filename);
        }
    }
    /** @internal */
    public function set(string $key, CacheEntry $entry): void
    {
        $tmpDir = $this->cacheDir . DIRECTORY_SEPARATOR . '.valinor.tmp';
        $filename = $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php';
        /**
         * The third `is_dir()` check confirms the directory truly doesn't exist
         * after `mkdir()` failure, distinguishing a genuine error (permissions,
         * disk full) from a concurrent creation by another process.
         *
         * @infection-ignore-all
         */
        if (!is_dir($tmpDir) && !@mkdir($tmpDir, 0777, true) && !is_dir($tmpDir)) {
            throw new CacheDirectoryNotWritable($this->cacheDir);
        }
        /** @infection-ignore-all */
        $tmpFilename = $tmpDir . DIRECTORY_SEPARATOR . bin2hex(random_bytes(16));
        try {
            $code = '<?php // ' . self::GENERATED_MESSAGE . PHP_EOL . "return {$entry->code};" . PHP_EOL;
            if (!@file_put_contents($tmpFilename, $code)) {
                throw new CompiledPhpCacheFileNotWritten($tmpFilename);
            }
            if (!@rename($tmpFilename, $filename)) {
                throw new CompiledPhpCacheFileNotWritten($filename);
            }
            @chmod($filename, 0666 & ~umask());
        } finally {
            if (file_exists($tmpFilename)) {
                unlink($tmpFilename);
            }
        }
    }
    public function clear(): void
    {
        if (!is_dir($this->cacheDir)) {
            return;
        }
        $shouldDeleteRootDir = true;
        /** @var FilesystemIterator $file */
        foreach (new FilesystemIterator($this->cacheDir) as $file) {
            if ($file->getFilename() === '.valinor.tmp') {
                @rmdir($file->getPathname());
                continue;
            }
            if (!$file->isFile()) {
                $shouldDeleteRootDir = false;
                continue;
            }
            $line = $file->openFile()->getCurrentLine();
            if (!$line || !str_contains($line, self::GENERATED_MESSAGE)) {
                $shouldDeleteRootDir = false;
                continue;
            }
            @unlink($file->getPathname());
        }
        if ($shouldDeleteRootDir) {
            @rmdir($this->cacheDir);
        }
    }
}