<?php

/*
 *
 *  ____            _        _   __  __ _                  __  __ ____
 * |  _ \ ___   ___| | _____| |_|  \/  (_)_ __   ___      |  \/  |  _ \
 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
 * |  __/ (_) | (__|   <  __/ |_| |  | | | | | |  __/_____| |  | |  __/
 * |_|   \___/ \___|_|\_\___|\__|_|  |_|_|_| |_|\___|     |_|  |_|_|
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * @author PocketMine Team
 * @link http://www.pocketmine.net/
 *
 *
 */

declare(strict_types=1);

namespace pocketmine\build\update_registry_annotations;

use pocketmine\utils\Utils;
use function array_diff;
use function array_map;
use function array_unshift;
use function basename;
use function class_exists;
use function count;
use function dirname;
use function file_get_contents;
use function file_put_contents;
use function fwrite;
use function implode;
use function is_dir;
use function ksort;
use function mb_strtoupper;
use function preg_match;
use function sleep;
use function sprintf;
use function str_ends_with;
use function str_replace;
use const PHP_EOL;
use const SORT_STRING;
use const STDERR;

if(!isset($argv) || count($argv) !== 2){
	fwrite(STDERR, "Provide a path to process\n");
	exit(1);
}

/**
 * @param object[] $members
 * @phpstan-param array<string, object> $members
 */
function generateMethodAnnotations(string $namespaceName, array $members) : string{
	$selfName = basename(__FILE__);
	$lines = ["/**"];
	$lines[] = " * This doc-block is generated automatically, do not modify it manually.";
	$lines[] = " * This must be regenerated whenever registry members are added, removed or changed.";
	$lines[] = " * @see build/codegen/$selfName";
	$lines[] = " * @generate-registry-docblock";
	$lines[] = " *";

	static $lineTmpl = " * @method static %2\$s %s()";
	$memberLines = [];
	foreach(Utils::stringifyKeys($members) as $name => $member){
		$reflect = new \ReflectionClass($member);
		$types = $reflect->getInterfaceNames();
		$concreteClass = $reflect;
		while($concreteClass !== false && $concreteClass->isAnonymous()){
			$concreteClass = $concreteClass->getParentClass();
		}

		if($concreteClass === false){
			$typehint = "object";
		}else{
			$types = array_diff($types, $concreteClass->getInterfaceNames());
			array_unshift($types, $concreteClass->getName());
			$typehint = implode("&", array_map(function(string $class) use ($namespaceName) : string{
				$reflect = new \ReflectionClass($class);
				return $reflect->getNamespaceName() === $namespaceName ?
					$reflect->getShortName() :
					'\\' . $reflect->getName();
			}, $types));
		}
		$accessor = mb_strtoupper($name);
		$memberLines[$accessor] = sprintf($lineTmpl, $accessor, $typehint);
	}
	ksort($memberLines, SORT_STRING);

	foreach($memberLines as $line){
		$lines[] = $line;
	}
	$lines[] = " */";
	return implode("\n", $lines);
}

function processFile(string $file) : void{
	$contents = file_get_contents($file);
	if($contents === false){
		throw new \RuntimeException("Failed to get contents of $file");
	}

	if(preg_match("/(*ANYCRLF)^namespace (.+);$/m", $contents, $matches) !== 1 || preg_match('/(*ANYCRLF)^((final|abstract)\s+)?class /m', $contents) !== 1){
		return;
	}
	$shortClassName = basename($file, ".php");
	$className = $matches[1] . "\\" . $shortClassName;
	if(!class_exists($className)){
		return;
	}
	$reflect = new \ReflectionClass($className);
	$docComment = $reflect->getDocComment();
	if($docComment === false || preg_match("/(*ANYCRLF)^\s*\*\s*@generate-registry-docblock$/m", $docComment) !== 1){
		return;
	}
	echo "Found registry in $file\n";

	$replacement = generateMethodAnnotations($matches[1], $className::getAll());

	$newContents = str_replace($docComment, $replacement, $contents);
	if($newContents !== $contents){
		echo "Writing changed file $file\n";
		file_put_contents($file, $newContents);
	}else{
		echo "No changes made to file $file\n";
	}
}

require dirname(__DIR__, 2) . '/vendor/autoload.php';

fwrite(STDERR, "DEPRECATED: Consider using the newer RegistrySource and build/codegen/registry-interface.php instead" . PHP_EOL);
fwrite(STDERR, "The newer approach allows generating actual code, which is substantially faster and more robust than the __callStatic magic used by RegistryTrait" . PHP_EOL);
sleep(5);

if(is_dir($argv[1])){
	/** @var string $file */
	foreach(new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($argv[1], \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME)) as $file){
		if(!str_ends_with($file, ".php")){
			continue;
		}

		processFile($file);
	}
}else{
	processFile($argv[1]);
}