<?php declare(strict_types=1);
/*
 * This file is part of phpunit/php-code-coverage.
 *
 * (c) Sebastian Bergmann <sebastian@phpunit.de>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
namespace SebastianBergmann\CodeCoverage\Test\Target;

use function array_merge;
use function assert;
use function dirname;
use function range;
use function realpath;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Small;
use PHPUnit\Framework\Attributes\Ticket;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use SebastianBergmann\CodeCoverage\Filter;
use SebastianBergmann\CodeCoverage\StaticAnalysis\FileAnalyser;
use SebastianBergmann\CodeCoverage\StaticAnalysis\ParsingSourceAnalyser;
use SebastianBergmann\CodeCoverage\TestFixture\Target\ChildClass;
use SebastianBergmann\CodeCoverage\TestFixture\Target\GrandParentClass;
use SebastianBergmann\CodeCoverage\TestFixture\Target\Issue1066\BaseDummy;
use SebastianBergmann\CodeCoverage\TestFixture\Target\Issue1066\Dummy;
use SebastianBergmann\CodeCoverage\TestFixture\Target\Issue1066\Dummy2;
use SebastianBergmann\CodeCoverage\TestFixture\Target\Issue1066\DummyWithTrait;
use SebastianBergmann\CodeCoverage\TestFixture\Target\Issue1066\SomeTrait;
use SebastianBergmann\CodeCoverage\TestFixture\Target\ParentClass;
use SebastianBergmann\CodeCoverage\TestFixture\Target\T1;
use SebastianBergmann\CodeCoverage\TestFixture\Target\T2;
use SebastianBergmann\CodeCoverage\TestFixture\Target\TargetEnumeration;
use SebastianBergmann\CodeCoverage\TestFixture\Target\TraitOne;
use SebastianBergmann\CodeCoverage\TestFixture\Target\TraitTwo;

/**
 * @phpstan-import-type TargetMap from Mapper
 * @phpstan-import-type TargetMapPart from Mapper
 * @phpstan-import-type ReverseLookup from Mapper
 *
 * @phpstan-type PartialTargetMap array{namespaces: TargetMapPart, traits: TargetMapPart, classes: TargetMapPart, classesThatExtendClass: TargetMapPart, classesThatImplementInterface: TargetMapPart, methods: TargetMapPart, functions: TargetMapPart, reverseLookup: ReverseLookup}
 */
#[CoversClass(MapBuilder::class)]
#[Small]
final class MapBuilderTest extends TestCase
{
    /**
     * @return non-empty-array<non-empty-string, array{0: PartialTargetMap, 1: non-empty-list<non-empty-string>}>
     */
    public static function provider(): array
    {
        $file             = self::realpath(__DIR__ . '/../../../_files/source_with_interfaces_classes_traits_functions.php');
        $traitOne         = self::realpath(__DIR__ . '/../../../_files/Target/TraitOne.php');
        $traitTwo         = self::realpath(__DIR__ . '/../../../_files/Target/TraitTwo.php');
        $twoTraits        = self::realpath(__DIR__ . '/../../../_files/Target/two_traits.php');
        $enum             = self::realpath(__DIR__ . '/../../../_files/Target/TargetEnumeration.php');
        $grandParentClass = self::realpath(__DIR__ . '/../../../_files/Target/GrandParentClass.php');
        $parentClass      = self::realpath(__DIR__ . '/../../../_files/Target/ParentClass.php');
        $childClass       = self::realpath(__DIR__ . '/../../../_files/Target/ChildClass.php');

        return [
            'generic' => [
                [
                    'namespaces' => [
                        'SebastianBergmann' => [
                            $file => array_merge(
                                range(19, 24),
                                range(26, 31),
                                range(33, 52),
                                range(54, 56),
                            ),
                        ],
                        'SebastianBergmann\\CodeCoverage' => [
                            $file => array_merge(
                                range(19, 24),
                                range(26, 31),
                                range(33, 52),
                                range(54, 56),
                            ),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis' => [
                            $file => array_merge(
                                range(19, 24),
                                range(26, 31),
                                range(33, 52),
                                range(54, 56),
                            ),
                        ],
                    ],
                    'traits' => [
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\T' => [
                            $file => range(19, 24),
                        ],
                    ],
                    'classes' => [
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParentClass' => [
                            $file => range(26, 31),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ChildClass' => [
                            $file => array_merge(
                                range(33, 52),
                                range(19, 24),
                                range(26, 31),
                            ),
                        ],
                    ],
                    'classesThatExtendClass' => [
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParentClass' => [
                            $file => range(33, 52),
                        ],
                    ],
                    'classesThatImplementInterface' => [
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\A' => [
                            $file => range(33, 52),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\B' => [
                            $file => range(33, 52),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\C' => [
                            $file => range(26, 31),
                        ],
                    ],
                    'methods' => [
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\T::four' => [
                            $file => range(21, 23),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ParentClass::five' => [
                            $file => range(28, 30),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ChildClass::six' => [
                            $file => range(37, 39),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ChildClass::one' => [
                            $file => range(41, 43),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ChildClass::two' => [
                            $file => range(45, 47),
                        ],
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\ChildClass::three' => [
                            $file => range(49, 51),
                        ],
                    ],
                    'functions' => [
                        'SebastianBergmann\\CodeCoverage\\StaticAnalysis\\f' => [
                            $file => range(54, 56),
                        ],
                    ],
                    'reverseLookup' => [
                        $file . ':21' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\T::four',
                        $file . ':22' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\T::four',
                        $file . ':23' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\T::four',
                        $file . ':28' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ParentClass::five',
                        $file . ':29' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ParentClass::five',
                        $file . ':30' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ParentClass::five',
                        $file . ':37' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::six',
                        $file . ':38' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::six',
                        $file . ':39' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::six',
                        $file . ':41' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::one',
                        $file . ':42' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::one',
                        $file . ':43' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::one',
                        $file . ':45' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::two',
                        $file . ':46' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::two',
                        $file . ':47' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::two',
                        $file . ':49' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::three',
                        $file . ':50' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::three',
                        $file . ':51' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\ChildClass::three',
                        $file . ':54' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\f',
                        $file . ':55' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\f',
                        $file . ':56' => 'SebastianBergmann\CodeCoverage\StaticAnalysis\f',
                    ],
                ],
                [$file],
            ],
            'trait using trait declared in another file' => [
                [
                    'namespaces' => [
                        'SebastianBergmann' => [
                            $traitOne => range(4, 9),
                            $traitTwo => range(4, 11),
                        ],
                        'SebastianBergmann\\CodeCoverage' => [
                            $traitOne => range(4, 9),
                            $traitTwo => range(4, 11),
                        ],
                        'SebastianBergmann\\CodeCoverage\\TestFixture' => [
                            $traitOne => range(4, 9),
                            $traitTwo => range(4, 11),
                        ],
                        'SebastianBergmann\\CodeCoverage\\TestFixture\\Target' => [
                            $traitOne => range(4, 9),
                            $traitTwo => range(4, 11),
                        ],
                    ],
                    'traits' => [
                        TraitOne::class => [
                            $traitOne => range(4, 9),
                        ],
                        TraitTwo::class => [
                            $traitTwo => range(4, 11),
                            $traitOne => range(4, 9),
                        ],
                    ],
                    'classes' => [
                    ],
                    'classesThatExtendClass' => [
                    ],
                    'classesThatImplementInterface' => [
                    ],
                    'methods' => [
                        TraitOne::class . '::one' => [
                            $traitOne => range(6, 8),
                        ],
                        TraitTwo::class . '::two' => [
                            $traitTwo => range(8, 10),
                        ],
                    ],
                    'functions' => [
                    ],
                    'reverseLookup' => [
                        $traitOne . ':6'  => TraitOne::class . '::one',
                        $traitOne . ':7'  => TraitOne::class . '::one',
                        $traitOne . ':8'  => TraitOne::class . '::one',
                        $traitTwo . ':8'  => TraitTwo::class . '::two',
                        $traitTwo . ':9'  => TraitTwo::class . '::two',
                        $traitTwo . ':10' => TraitTwo::class . '::two',
                    ],
                ],
                [
                    $traitOne,
                    $traitTwo,
                ],
            ],
            'trait using trait declared in same file' => [
                [
                    'namespaces' => [
                        'SebastianBergmann' => [
                            $twoTraits => array_merge(
                                range(4, 9),
                                range(11, 18),
                            ),
                        ],
                        'SebastianBergmann\\CodeCoverage' => [
                            $twoTraits => array_merge(
                                range(4, 9),
                                range(11, 18),
                            ),
                        ],
                        'SebastianBergmann\\CodeCoverage\\TestFixture' => [
                            $twoTraits => array_merge(
                                range(4, 9),
                                range(11, 18),
                            ),
                        ],
                        'SebastianBergmann\\CodeCoverage\\TestFixture\\Target' => [
                            $twoTraits => array_merge(
                                range(4, 9),
                                range(11, 18),
                            ),
                        ],
                    ],
                    'traits' => [
                        T1::class => [
                            $twoTraits => range(4, 9),
                        ],
                        T2::class => [
                            $twoTraits => array_merge(
                                range(11, 18),
                                range(4, 9),
                            ),
                        ],
                    ],
                    'classes' => [
                    ],
                    'classesThatExtendClass' => [
                    ],
                    'classesThatImplementInterface' => [
                    ],
                    'methods' => [
                        T1::class . '::one' => [
                            $twoTraits => range(6, 8),
                        ],
                        T2::class . '::two' => [
                            $twoTraits => range(15, 17),
                        ],
                    ],
                    'functions' => [
                    ],
                    'reverseLookup' => [
                        $twoTraits . ':6'  => T1::class . '::one',
                        $twoTraits . ':7'  => T1::class . '::one',
                        $twoTraits . ':8'  => T1::class . '::one',
                        $twoTraits . ':15' => T2::class . '::two',
                        $twoTraits . ':16' => T2::class . '::two',
                        $twoTraits . ':17' => T2::class . '::two',
                    ],
                ],
                [
                    $twoTraits,
                ],
            ],
            'enumeration' => [
                [
                    'namespaces' => [
                        'SebastianBergmann' => [
                            $enum => range(4, 8),
                        ],
                        'SebastianBergmann\\CodeCoverage' => [
                            $enum => range(4, 8),
                        ],
                        'SebastianBergmann\\CodeCoverage\\TestFixture' => [
                            $enum => range(4, 8),
                        ],
                        'SebastianBergmann\\CodeCoverage\\TestFixture\\Target' => [
                            $enum => range(4, 8),
                        ],
                    ],
                    'traits' => [
                    ],
                    'classes' => [
                        TargetEnumeration::class => [
                            $enum => range(4, 8),
                        ],
                    ],
                    'classesThatExtendClass' => [
                    ],
                    'classesThatImplementInterface' => [
                    ],
                    'methods' => [
                    ],
                    'functions' => [
                    ],
                    'reverseLookup' => [
                    ],
                ],
                [
                    $enum,
                ],
            ],
            'class with parent classes' => [
                [
                    'namespaces' => [
                        'SebastianBergmann' => [
                            $grandParentClass => range(4, 9),
                            $parentClass      => range(4, 9),
                            $childClass       => range(4, 9),
                        ],
                        'SebastianBergmann\CodeCoverage' => [
                            $grandParentClass => range(4, 9),
                            $parentClass      => range(4, 9),
                            $childClass       => range(4, 9),
                        ],
                        'SebastianBergmann\CodeCoverage\TestFixture' => [
                            $grandParentClass => range(4, 9),
                            $parentClass      => range(4, 9),
                            $childClass       => range(4, 9),
                        ],
                        'SebastianBergmann\CodeCoverage\TestFixture\Target' => [
                            $grandParentClass => range(4, 9),
                            $parentClass      => range(4, 9),
                            $childClass       => range(4, 9),
                        ],
                    ],
                    'traits' => [
                    ],
                    'classes' => [
                        GrandParentClass::class => [
                            $grandParentClass => range(4, 9),
                        ],
                        ParentClass::class => [
                            $parentClass      => range(4, 9),
                            $grandParentClass => range(4, 9),
                        ],
                        ChildClass::class => [
                            $childClass       => range(4, 9),
                            $parentClass      => range(4, 9),
                            $grandParentClass => range(4, 9),
                        ],
                    ],
                    'classesThatExtendClass' => [
                        GrandParentClass::class => [
                            $parentClass => range(4, 9),
                            $childClass  => range(4, 9),
                        ],
                        ParentClass::class => [
                            $childClass => range(4, 9),
                        ],
                    ],
                    'classesThatImplementInterface' => [
                    ],
                    'methods' => [
                        GrandParentClass::class . '::one' => [
                            $grandParentClass => range(6, 8),
                        ],
                        ParentClass::class . '::two' => [
                            $parentClass => range(6, 8),
                        ],
                        ChildClass::class . '::three' => [
                            $childClass => range(6, 8),
                        ],
                    ],
                    'functions' => [
                    ],
                    'reverseLookup' => [
                        $grandParentClass . ':6' => 'SebastianBergmann\CodeCoverage\TestFixture\Target\GrandParentClass::one',
                        $grandParentClass . ':7' => 'SebastianBergmann\CodeCoverage\TestFixture\Target\GrandParentClass::one',
                        $grandParentClass . ':8' => 'SebastianBergmann\CodeCoverage\TestFixture\Target\GrandParentClass::one',
                        $parentClass . ':6'      => 'SebastianBergmann\CodeCoverage\TestFixture\Target\ParentClass::two',
                        $parentClass . ':7'      => 'SebastianBergmann\CodeCoverage\TestFixture\Target\ParentClass::two',
                        $parentClass . ':8'      => 'SebastianBergmann\CodeCoverage\TestFixture\Target\ParentClass::two',
                        $childClass . ':6'       => 'SebastianBergmann\CodeCoverage\TestFixture\Target\ChildClass::three',
                        $childClass . ':7'       => 'SebastianBergmann\CodeCoverage\TestFixture\Target\ChildClass::three',
                        $childClass . ':8'       => 'SebastianBergmann\CodeCoverage\TestFixture\Target\ChildClass::three',
                    ],
                ],
                [
                    $grandParentClass,
                    $parentClass,
                    $childClass,
                ],
            ],
        ];
    }

    /**
     * @param PartialTargetMap                 $expected
     * @param non-empty-list<non-empty-string> $files
     */
    #[DataProvider('provider')]
    public function testBuildsMap(array $expected, array $files): void
    {
        $this->assertSame($this->withFileAndDirectoryEntries($expected, $files), $this->map($files));
    }

    public function testBuildsMapForFileAndDirectoryTargets(): void
    {
        $top   = self::realpath(__DIR__ . '/../../../_files/Target/file-target-tree/Top.php');
        $inner = self::realpath(__DIR__ . '/../../../_files/Target/file-target-tree/Subdir/Inner.php');

        $topDir = dirname($top);
        $subDir = dirname($inner);

        $map = $this->map([$top, $inner]);

        $topLines   = range(1, 4);
        $innerLines = range(1, 4);

        $this->assertSame(
            [
                $top   => [$top => $topLines],
                $inner => [$inner => $innerLines],
            ],
            $map['files'],
        );

        $this->assertArrayHasKey($topDir, $map['directories']);
        $this->assertSame(
            [$top => $topLines],
            $map['directories'][$topDir],
        );

        $this->assertArrayHasKey($subDir, $map['directories']);
        $this->assertSame(
            [$inner => $innerLines],
            $map['directories'][$subDir],
        );

        $this->assertArrayHasKey($topDir, $map['directoriesRecursively']);
        $this->assertSame(
            [
                $top   => $topLines,
                $inner => $innerLines,
            ],
            $map['directoriesRecursively'][$topDir],
        );

        $this->assertArrayHasKey($subDir, $map['directoriesRecursively']);
        $this->assertSame(
            [$inner => $innerLines],
            $map['directoriesRecursively'][$subDir],
        );
    }

    #[Ticket('https://github.com/sebastianbergmann/php-code-coverage/issues/1066')]
    public function testIssue1066(): void
    {
        $baseDummy      = self::realpath(__DIR__ . '/../../../_files/Target/regression/1066/BaseDummy.php');
        $dummy          = self::realpath(__DIR__ . '/../../../_files/Target/regression/1066/Dummy.php');
        $dummy2         = self::realpath(__DIR__ . '/../../../_files/Target/regression/1066/Dummy2.php');
        $dummyWithTrait = self::realpath(__DIR__ . '/../../../_files/Target/regression/1066/DummyWithTrait.php');
        $someTrait      = self::realpath(__DIR__ . '/../../../_files/Target/regression/1066/SomeTrait.php');

        $files = [
            $baseDummy,
            $dummy,
            $dummy2,
            $dummyWithTrait,
            $someTrait,
        ];

        $this->assertSame(
            $this->withFileAndDirectoryEntries([
                'namespaces' => [
                    'SebastianBergmann' => [
                        $someTrait      => range(4, 6),
                        $baseDummy      => range(4, 6),
                        $dummy          => range(4, 15),
                        $dummy2         => range(4, 15),
                        $dummyWithTrait => range(4, 17),
                    ],
                    'SebastianBergmann\CodeCoverage' => [
                        $someTrait      => range(4, 6),
                        $baseDummy      => range(4, 6),
                        $dummy          => range(4, 15),
                        $dummy2         => range(4, 15),
                        $dummyWithTrait => range(4, 17),
                    ],
                    'SebastianBergmann\CodeCoverage\TestFixture' => [
                        $someTrait      => range(4, 6),
                        $baseDummy      => range(4, 6),
                        $dummy          => range(4, 15),
                        $dummy2         => range(4, 15),
                        $dummyWithTrait => range(4, 17),
                    ],
                    'SebastianBergmann\CodeCoverage\TestFixture\Target' => [
                        $someTrait      => range(4, 6),
                        $baseDummy      => range(4, 6),
                        $dummy          => range(4, 15),
                        $dummy2         => range(4, 15),
                        $dummyWithTrait => range(4, 17),
                    ],
                    'SebastianBergmann\CodeCoverage\TestFixture\Target\Issue1066' => [
                        $someTrait      => range(4, 6),
                        $baseDummy      => range(4, 6),
                        $dummy          => range(4, 15),
                        $dummy2         => range(4, 15),
                        $dummyWithTrait => range(4, 17),
                    ],
                ],
                'traits' => [
                    SomeTrait::class => [
                        $someTrait => range(4, 6),
                    ],
                ],
                'classes' => [
                    BaseDummy::class => [
                        $baseDummy => range(4, 6),
                    ],
                    Dummy::class => [
                        $dummy     => range(4, 15),
                        $baseDummy => range(4, 6),
                    ],
                    Dummy2::class => [
                        $dummy2 => range(4, 15),
                    ],
                    DummyWithTrait::class => [
                        $dummyWithTrait => range(4, 17),
                        $someTrait      => range(4, 6),
                        $baseDummy      => range(4, 6),
                    ],
                ],
                'classesThatExtendClass' => [
                    BaseDummy::class => [
                        $dummy          => range(4, 15),
                        $dummyWithTrait => range(4, 17),
                    ],
                ],
                'classesThatImplementInterface' => [
                ],
                'methods' => [
                    Dummy::class . '::method1' => [
                        $dummy => range(6, 9),
                    ],
                    Dummy::class . '::method2' => [
                        $dummy => range(11, 14),
                    ],
                    Dummy2::class . '::method1' => [
                        $dummy2 => range(6, 9),
                    ],
                    Dummy2::class . '::method2' => [
                        $dummy2 => range(11, 14),
                    ],
                    DummyWithTrait::class . '::method1' => [
                        $dummyWithTrait => range(8, 11),
                    ],
                    DummyWithTrait::class . '::method2' => [
                        $dummyWithTrait => range(13, 16),
                    ],
                ],
                'functions' => [
                ],
                'reverseLookup' => [
                    $dummy . ':6'           => Dummy::class . '::method1',
                    $dummy . ':7'           => Dummy::class . '::method1',
                    $dummy . ':8'           => Dummy::class . '::method1',
                    $dummy . ':9'           => Dummy::class . '::method1',
                    $dummy . ':11'          => Dummy::class . '::method2',
                    $dummy . ':12'          => Dummy::class . '::method2',
                    $dummy . ':13'          => Dummy::class . '::method2',
                    $dummy . ':14'          => Dummy::class . '::method2',
                    $dummy2 . ':6'          => Dummy2::class . '::method1',
                    $dummy2 . ':7'          => Dummy2::class . '::method1',
                    $dummy2 . ':8'          => Dummy2::class . '::method1',
                    $dummy2 . ':9'          => Dummy2::class . '::method1',
                    $dummy2 . ':11'         => Dummy2::class . '::method2',
                    $dummy2 . ':12'         => Dummy2::class . '::method2',
                    $dummy2 . ':13'         => Dummy2::class . '::method2',
                    $dummy2 . ':14'         => Dummy2::class . '::method2',
                    $dummyWithTrait . ':8'  => DummyWithTrait::class . '::method1',
                    $dummyWithTrait . ':9'  => DummyWithTrait::class . '::method1',
                    $dummyWithTrait . ':10' => DummyWithTrait::class . '::method1',
                    $dummyWithTrait . ':11' => DummyWithTrait::class . '::method1',
                    $dummyWithTrait . ':13' => DummyWithTrait::class . '::method2',
                    $dummyWithTrait . ':14' => DummyWithTrait::class . '::method2',
                    $dummyWithTrait . ':15' => DummyWithTrait::class . '::method2',
                    $dummyWithTrait . ':16' => DummyWithTrait::class . '::method2',
                ],
            ], $files),
            $this->map($files),
        );
    }

    /**
     * @param list<string> $files
     *
     * @return TargetMap
     */
    private function map(array $files): array
    {
        $filter = new Filter;

        $filter->includeFiles($files);

        return (new MapBuilder)->build(
            $filter,
            new FileAnalyser(
                new ParsingSourceAnalyser,
                false,
                false,
            ),
        );
    }

    /**
     * @param PartialTargetMap                 $expected
     * @param non-empty-list<non-empty-string> $files
     *
     * @return TargetMap
     */
    private function withFileAndDirectoryEntries(array $expected, array $files): array
    {
        $filter = new Filter;
        $filter->includeFiles($files);

        $analyser = new FileAnalyser(new ParsingSourceAnalyser, false, false);

        $filesMap                  = [];
        $directoriesMap            = [];
        $directoriesRecursivelyMap = [];

        foreach ($filter->files() as $file) {
            $linesOfCode = $analyser->analyse($file)->linesOfCode()->linesOfCode();

            assert($linesOfCode > 0);

            $lines = range(1, $linesOfCode);

            $filesMap[$file] = [$file => $lines];

            $parent                         = dirname($file);
            $directoriesMap[$parent][$file] = $lines;

            $ancestor = $parent;

            while ($ancestor !== dirname($ancestor)) {
                $directoriesRecursivelyMap[$ancestor][$file] = $lines;
                $ancestor                                    = dirname($ancestor);
            }
        }

        return [
            'namespaces'                    => $expected['namespaces'],
            'traits'                        => $expected['traits'],
            'classes'                       => $expected['classes'],
            'classesThatExtendClass'        => $expected['classesThatExtendClass'],
            'classesThatImplementInterface' => $expected['classesThatImplementInterface'],
            'methods'                       => $expected['methods'],
            'functions'                     => $expected['functions'],
            'files'                         => $filesMap,
            'directories'                   => $directoriesMap,
            'directoriesRecursively'        => $directoriesRecursivelyMap,
            'reverseLookup'                 => $expected['reverseLookup'],
        ];
    }

    /**
     * @return non-empty-string
     */
    private static function realpath(string $path): string
    {
        $realpath = realpath($path);

        if ($realpath === false) {
            throw new RuntimeException('Could not resolve real path of ' . $path);
        }

        return $realpath;
    }
}
