<?php

declare(strict_types=1);

namespace NoahVet\Reef\Hasher;

use NoahVet\Reef\Domain\Tool\ArrayTool;
use Symfony\Component\Finder\Finder;

class DirectoryHasher implements DirectoryHasherInterface
{
    private const ALGO = 'xxh3';

    public function __construct(
        protected readonly string $kernelProjectDir,
    ) {
    }

    /**
     * @param string[] $relativeFixtureDirs
     *
     * @return array{
     *     fixtures: string,
     *     fixtureCommands: string,
     *     migrations: string,
     *     sync: string,
     *     syncCommands: string,
     *     global: string
     * }
     */
    public function computeHashes(array $relativeFixtureDirs): array
    {
        if ([] === $relativeFixtureDirs) {
            throw new \LogicException('$relativeDirs should not be empty.');
        }

        $fixturePaths = [];

        foreach ($relativeFixtureDirs as $relativeDir) {
            $path = $this->kernelProjectDir.'/data/fixture/doctrine';
            $fixturePaths[] = \rtrim($path, \DIRECTORY_SEPARATOR)
                .\DIRECTORY_SEPARATOR
                .\trim($relativeDir, \DIRECTORY_SEPARATOR);
        }

        $fixturesHash = $this->hashDirectories(
            $fixturePaths,
            ['*.yaml'],
        );

        $fixtureCommandsHash = $this->hashDirectories(
            [$this->kernelProjectDir.'/src/DataFixtures'],
            ['*.php'],
        );

        $migrationsHash = $this->hashDirectories(
            [$this->kernelProjectDir.'/migrations'],
            ['*.php'],
        );

        $syncHash = $this->hashDirectories(
            [$this->kernelProjectDir.'/data/sync/doctrine'],
            ['*.yaml'],
        );

        $syncCommandsHash = $this->hashDirectories(
            [$this->kernelProjectDir.'/src/Command/Sync'],
            ['*.php'],
        );

        $globalHash = \hash(
            self::ALGO,
            'fixtures='.$fixturesHash
            .'::fixtureCommandsHash='.$fixtureCommandsHash
            .'::migrations='.$migrationsHash
            .'::sync='.$syncHash
            .'::syncCommands='.$syncCommandsHash,
        );

        return [
            'fixtures' => $fixturesHash,
            'fixtureCommands' => $fixtureCommandsHash,
            'migrations' => $migrationsHash,
            'sync' => $syncHash,
            'syncCommands' => $syncCommandsHash,
            'global' => $globalHash,
        ];
    }

    /**
     * @param string[] $paths    Absolute directory paths
     * @param string[] $patterns Glob patterns for Finder::name(), empty = all files
     */
    private function hashDirectories(
        array $paths,
        array $patterns,
    ): string {
        $paths = \array_values(
            \array_filter(
                $paths,
                static fn (string $path): bool => \is_dir($path),
            ),
        );

        if ([] === $paths) {
            return 'no-paths';
        }

        $finder = (new Finder())
            ->files()
            ->in($paths)
        ;

        foreach ($patterns as $pattern) {
            $finder->name($pattern);
        }

        $parts = [];

        foreach ($finder as $file) {
            $pathname = $file->getPathname();
            $contents = $file->getContents();

            $parts[] = $pathname.'::'.\hash(self::ALGO, $contents);
        }

        if ([] === $parts) {
            return 'no-files';
        }

        ArrayTool::sortMultiDimensionalByKey($parts);

        return \hash(self::ALGO, \implode("\n", $parts));
    }
}
