#!/usr/bin/env php
<?php 
namespace SdgScoped;

// pdep is a tool to help explore dependencies of classes.
// @phan-file-suppress PhanPluginRemoveDebugEcho
use SdgScoped\Phan\CLIBuilder;
use SdgScoped\Phan\Phan;
\define("PDEP_IGNORE_STATIC", 1 << 0);
\define("PDEP_HIDE_LABELS", 1 << 1);
/** Prints a usage message for 'pdep' and exits. */
function pdep_usage(int $status) : void
{
    global $argv;
    echo <<<EOT
Usage: {$argv[0]} [options] [files or classes...]

 -c, --find-classes  Find classes that depend on the passed files or classes
 -f, --find-files    Find files that depend on the passed files or classes
 -i, --import        Import class graph from json file previously generated
 -j, --json          JSON output of entire class and file graphs plus metadata
                     unless a depth and starting node is given, then you get
                     a json file with just the dependency list for that node
 -g, --graph         Graphviz dot output
 -m, --graphml       GraphML output
     --hide-labels   Labels are hidden - hover in yEd to see them
     --ignore-static Don't include static calls and static var dependencies
 -d, --depth <depth_level>
    When walking the dependency graph, limit it to this depth. For
    example,
      {$argv[0]} -f -d 1 MyClass
    would show only the files that directly depend on MyClass.
 -l, --file-list <filelist.txt>
 -q, --quick         Uses Phan's --quick mode
 -p, --progress-bar  Show progress bar
 -h, --help          This help

If no filenames or classnames are provided, it will generate the
full dependency tree.

Note that this tool will read your local .phan/config.php and pick out
the list of files to scan/not scan from there. Or, you can provide it
with a file list.

Examples:
    {$argv[0]} -f src/Phan/PluginV3/PluginAwarePreAnalysisVisitor.php
    {$argv[0]} -c -g '\\Phan\\PluginV3\\PluginAwarePreAnalysisVisitor' | dot -Tpng > graph.png
    {$argv[0]} -c -d 2 -g '\\Phan\\Language\\Type\\ClassStringType' | dot -Kfdp -Tpng > graph.png

EOT;
    exit($status);
}
\call_user_func(static function () : void {
    global $argv;
    $tool_dir = \dirname($argv[0]);
    $depth = 0;
    $cmd = '';
    $graph_flags = 0;
    $graph_file = '';
    $options = \getopt("cfjgmhpqd:l:i:", ['import', 'json', 'graph', 'graphml', 'ignore-static', 'hide-labels', 'find-classes', 'find-files', 'file-list:', 'progress-bar', 'depth:', 'help'], $optind);
    if (isset($options['find-classes'])) {
        $options['c'] = \false;
    }
    if (isset($options['find-files'])) {
        $options['f'] = \false;
    }
    if (isset($options['depth'])) {
        $options['d'] = \false;
    }
    if (isset($options['json'])) {
        $options['j'] = \false;
    }
    if (isset($options['import'])) {
        $options['i'] = \false;
    }
    $mode = null;
    if (isset($options['c'])) {
        if (isset($options['f'])) {
            echo "ERROR: Cannot pass both -c and -f\n";
            pdep_usage(1);
        }
        $mode = 'class';
    } elseif (isset($options['f'])) {
        $mode = 'file';
    }
    if (isset($options['d'])) {
        if (empty($mode)) {
            echo "ERROR: You must specify either -c or -f\n";
            pdep_usage(1);
        }
    }
    if (isset($options['h']) || isset($options['help'])) {
        pdep_usage(0);
        return;
    }
    require_once __DIR__ . '/../src/Phan/Bootstrap.php';
    $cli_builder = new CLIBuilder();
    foreach ($options as $opt => $value) {
        switch ($opt) {
            case 'j':
            case 'json':
                $cmd = 'json';
                break;
            case 'g':
            case 'graph':
                $cmd = "graph";
                break;
            case 'm':
            case 'graphml':
                $cmd = "graphml";
                break;
            case 'ignore-static':
                $graph_flags |= \PDEP_IGNORE_STATIC;
                break;
            case 'hide-labels':
                $graph_flags |= \PDEP_HIDE_LABELS;
                break;
            case 'p':
            case 'progress-bar':
                $cli_builder->setOption('progress-bar');
                break;
            case 'q':
            case 'quick':
                $cli_builder->setOption('quick');
                break;
            case 'd':
            case 'depth':
                $depth = \filter_var($value, \FILTER_VALIDATE_INT);
                if ($depth === \false) {
                    echo "ERROR: Invalid depth '{$value}' (expected int)\n";
                    pdep_usage(1);
                }
                break;
            case 'l':
            case 'file-list':
                // @phan-suppress-next-line PhanPossiblyNullTypeArgument
                $cli_builder->setOption('file-list', $value);
                break;
            case 'i':
            case 'import':
                $cli_builder->setOption('no-progress-bar');
                $graph_file = (string) $value;
                break;
        }
    }
    if ($cmd === 'json' && $depth === 0) {
        $mode = '';
    }
    // Args for PDEP
    $arg_string = \implode(' ', \array_slice($argv, $optind));
    if (empty($arg_string) && $depth) {
        echo "ERROR: You must specify a starting node when specifying a depth\n";
        pdep_usage(1);
    }
    $cwd = \getcwd();
    // @phan-suppress-next-line PhanThrowTypeAbsentForCall
    $cli_builder->setOption('allow-polyfill-parser');
    $cli_builder->setOption('processes', '1');
    $cli_builder->setOption('plugin', \dirname($tool_dir) . '/src/Phan/Plugin/Internal/DependencyGraphPlugin.php');
    $cli_builder->setOption('config-file', "{$cwd}/.phan/pdep_config.php");
    // @phan-suppress-next-line PhanThrowTypeAbsentForCall
    $cli = $cli_builder->build();
    $putenv = static function (string $key, string $value) : void {
        \putenv("{$key}={$value}");
        $_ENV[$key] = $value;
    };
    $putenv("PDEP_CMD", $cmd);
    $putenv("PDEP_MODE", (string) $mode);
    $putenv("PDEP_DEPTH", (string) $depth);
    $putenv("PDEP_ARGS", $arg_string);
    $putenv("PDEP_GRAPH_FLAGS", (string) $graph_flags);
    // Generate codebase info after parsing configs (e.g. included_extension_subset)
    $code_base = (require __DIR__ . '/../src/codebase.php');
    if (isset($options['i'])) {
        $data = @\file_get_contents($graph_file);
        if (!\is_string($data)) {
            echo "Unable to read graph file '{$graph_file}'\n";
            exit(1);
        }
        $cached_graph = @\json_decode($data, \true);
        if (!\is_array($cached_graph)) {
            echo "Invalid JSON contents of graph file '{$graph_file}': " . (\json_last_error() !== \JSON_ERROR_NONE ? \json_last_error_msg() : 'expected array, got ' . \gettype($cached_graph)) . "\n";
            exit(1);
        }
        require_once __DIR__ . '/../src/Phan/Plugin/Internal/DependencyGraphPlugin.php';
        (new \SdgScoped\Phan\Plugin\Internal\DependencyGraphPlugin())->processGraph($cached_graph);
    } else {
        // @phan-suppress-next-line PhanThrowTypeAbsentForCall
        Phan::analyzeFileList(
            $code_base,
            /** @return string[] */
            static function () use($cli) : array {
                return $cli->getFileList();
            }
        );
    }
});
