<?php

declare (strict_types=1);
namespace Rector\CustomRules;

use PhpParser\Modifiers;
use PhpParser\Node;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\UseItem;
/**
 * Inspired by @see \PhpParser\NodeDumper
 */
final class SimpleNodeDumper
{
    /**
     * @var array<int, string>
     */
    private const INCLUDE_TYPE_MAP = [Include_::TYPE_INCLUDE => 'TYPE_INCLUDE', Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE', Include_::TYPE_REQUIRE => 'TYPE_REQUIRE', Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE'];
    /**
     * @param Node[]|Node|mixed[] $node
     */
    public static function dump($node, bool $rootNode = \true): string
    {
        // display single root node directly to avoid useless nesting in output
        if (is_array($node) && count($node) === 1 && $rootNode) {
            $node = $node[0];
        }
        if ($node instanceof Node) {
            return self::dumpSingleNode($node);
        }
        if (self::isStringList($node)) {
            return json_encode($node, \JSON_THROW_ON_ERROR);
        }
        $result = '[';
        foreach ($node as $key => $value) {
            $result .= "\n    " . $key . ': ';
            if ($value === null) {
                $result .= 'null';
            } elseif ($value === \false) {
                $result .= 'false';
            } elseif ($value === \true) {
                $result .= 'true';
            } elseif (is_string($value)) {
                $result .= '"' . $value . '"';
            } elseif (is_scalar($value)) {
                $result .= $value;
            } else {
                $result .= str_replace("\n", "\n    ", self::dump($value, \false));
            }
        }
        if (count($node) === 0) {
            $result .= ']';
        } else {
            $result .= "\n]";
        }
        return $result;
    }
    /**
     * @param mixed[] $items
     */
    private static function isStringList(array $items): bool
    {
        foreach ($items as $item) {
            if (!is_string($item)) {
                return \false;
            }
        }
        return \true;
    }
    /**
     * @param mixed $flags
     */
    private static function dumpFlags($flags): string
    {
        $strs = [];
        if (($flags & Modifiers::PUBLIC) !== 0) {
            $strs[] = 'MODIFIER_PUBLIC';
        }
        if (($flags & Modifiers::PROTECTED) !== 0) {
            $strs[] = 'MODIFIER_PROTECTED';
        }
        if (($flags & Modifiers::PRIVATE) !== 0) {
            $strs[] = 'MODIFIER_PRIVATE';
        }
        if (($flags & Modifiers::ABSTRACT) !== 0) {
            $strs[] = 'MODIFIER_ABSTRACT';
        }
        if (($flags & Modifiers::STATIC) !== 0) {
            $strs[] = 'MODIFIER_STATIC';
        }
        if (($flags & Modifiers::FINAL) !== 0) {
            $strs[] = 'MODIFIER_FINAL';
        }
        if (($flags & Modifiers::READONLY) !== 0) {
            $strs[] = 'MODIFIER_READONLY';
        }
        if ($strs !== []) {
            return implode(' | ', $strs) . ' (' . $flags . ')';
        }
        return (string) $flags;
    }
    /**
     * @param int|float|string $type
     */
    private static function dumpIncludeType($type): string
    {
        if (!isset(self::INCLUDE_TYPE_MAP[$type])) {
            return (string) $type;
        }
        return self::INCLUDE_TYPE_MAP[$type] . ' (' . $type . ')';
    }
    /**
     * @param mixed $type
     */
    private static function dumpUseType($type): string
    {
        $map = [Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN', Use_::TYPE_NORMAL => 'TYPE_NORMAL', Use_::TYPE_FUNCTION => 'TYPE_FUNCTION', Use_::TYPE_CONSTANT => 'TYPE_CONSTANT'];
        if (!isset($map[$type])) {
            return (string) $type;
        }
        return $map[$type] . ' (' . $type . ')';
    }
    private static function dumpSingleNode(Node $node): string
    {
        $result = get_class($node);
        // print simple nodes on same line, to make output more readable
        if ($node instanceof Variable && is_string($node->name)) {
            $result .= '( name: "' . $node->name . '" )';
        } elseif ($node instanceof Identifier) {
            $result .= '( name: "' . $node->name . '" )';
        } elseif ($node instanceof Name) {
            $result .= '( parts: ' . json_encode($node->getParts(), \JSON_THROW_ON_ERROR) . ' )';
        } elseif ($node instanceof Scalar && $node->getSubNodeNames() === ['value']) {
            if (is_string($node->value)) {
                $result .= '( value: "' . $node->value . '" )';
            } else {
                $result .= '( value: ' . $node->value . ' )';
            }
        } else {
            $result .= '(';
            foreach ($node->getSubNodeNames() as $key) {
                $result .= "\n    " . $key . ': ';
                $value = $node->{$key};
                if ($value === null) {
                    $result .= 'null';
                } elseif ($value === \false) {
                    $result .= 'false';
                } elseif ($value === \true) {
                    $result .= 'true';
                } elseif (is_scalar($value)) {
                    if ($key === 'flags' || $key === 'newModifier') {
                        $result .= self::dumpFlags($value);
                    } elseif ($key === 'type' && $node instanceof Include_) {
                        $result .= self::dumpIncludeType($value);
                    } elseif ($key === 'type' && ($node instanceof Use_ || $node instanceof UseItem || $node instanceof GroupUse)) {
                        $result .= self::dumpUseType($value);
                    } elseif (is_string($value)) {
                        $result .= '"' . $value . '"';
                    } else {
                        $result .= $value;
                    }
                } else {
                    $result .= str_replace("\n", "\n    ", self::dump($value, \false));
                }
            }
            $result .= "\n)";
        }
        return $result;
    }
}
