<?php
/**
 * Copyright 2020 Adobe
 * All Rights Reserved.
 *
 * NOTICE: Adobe permits you to use, modify, and distribute this file in
 * accordance with the terms of the Adobe license agreement accompanying
 * it.
 */
declare(strict_types=1);

namespace Magento\Mray\Api;

use Exception;

use Magento\Mray\Package\AbstractTree\Node\Node;
use Magento\Mray\Package\AbstractTree\Scanner;
use Magento\Mray\Index\Scanner\GeneralPurposeScanner;
use Magento\Mray\Index\Scanner\Subject as ScannerSubject;
use Magento\Mray\Index\Scanner\Probe;
use Magento\Mray\Index\Scanner\Tuner;
use Magento\Mray\Index\Traverser\NodeVisitor;
use Magento\Mray\Index\Traverser\VisitingTraverser;
use RuntimeException;
use function array_filter;
use function array_keys;
use function array_map;
use function array_unique;
use function glob;
use function realpath;
use function sprintf;
use const GLOB_ONLYDIR;

class MagentoIndexEngine
{
    /**
     * @var Tuner[]
     */
    private $tuners;

    /**
     * @var NodeVisitor[]
     */
    private $visitors;

    /**
     * @param Tuner[] $tuners
     * @param NodeVisitor[] $visitors
     */
    public function __construct(
        array $tuners = [],
        array $visitors = []
    ) {
        $this->tuners = array_map(function (Tuner $tuner) {
            return $tuner;
        }, $tuners);
        $this->visitors = array_map(function (NodeVisitor $visitor) {
            return $visitor;
        }, $visitors);
    }

    /**
     * @param string $path
     * @return array|Node[]
     */
    public function index(string $path)
    {
        $scanner = new GeneralPurposeScanner(
            [
                new Probe\ComposerDistributionPackage(),
                new Probe\MagentoProject(),
                new Probe\MagentoModule(),
                new Probe\MagentoLibrary()
            ],
            $this->tuners ? new Tuner\Chain($this->tuners) : new Tuner\None()
        );
        $traverser = new VisitingTraverser($this->visitors);

        try {
            return $traverser->traverse(
                $this->findComponents($this->getTargetDirs([$path]), $scanner)
            );
        } catch (Exception $e) {
            throw new RuntimeException(
                sprintf('An error occurred while indexing Magento components: %s', $e->getMessage()),
                $e->getCode(),
                $e
            );
        }
    }

    /**
     * @param array $globs
     * @return array
     */
    private function getTargetDirs(array $globs): array
    {
        $dirs = [];
        foreach ($globs as $glob) {
            if (is_dir($glob)) {
                $matches = [$glob];
            } else {
                $matches = glob($glob, GLOB_ONLYDIR);
                if ($matches === false) {
                    throw new RuntimeException(sprintf('Unable to find paths for "%s".', $glob));
                }
            }

            foreach ($matches as $dir) {
                $dirs[$dir] = realpath($dir);
            }
        }
        $dirs = array_unique(array_filter($dirs));
        return array_keys($dirs);
    }

    /**
     * @param array $dirs
     * @param Scanner $scanner
     * @return iterable
     */
    private function findComponents(array $dirs, Scanner $scanner): iterable
    {
        foreach ($dirs as $dir) {
            yield from $scanner->scan(new ScannerSubject\Local($dir));
        }
    }
}
