<?php

/**
 * This file is part of the Nette Framework (https://nette.org)
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
 */
declare (strict_types=1);
namespace Packetery\Latte\Runtime;

use Packetery\Latte;
use Packetery\Nette\PhpGenerator as Php;
/**
 * Generates blueprint of template class.
 * @internal
 */
class Blueprint
{
    use \Packetery\Latte\Strict;
    public function printClass(Template $template, ?string $name = null) : void
    {
        if (!\class_exists(Php\ClassType::class)) {
            throw new \LogicException('Nette PhpGenerator is required to print template, install package `nette/php-generator`.');
        }
        $name = $name ?: 'Template';
        $namespace = new Php\PhpNamespace(Php\Helpers::extractNamespace($name));
        $class = $namespace->addClass(Php\Helpers::extractShortName($name));
        $this->addProperties($class, $template->getParameters(), \true);
        $functions = \array_diff_key((array) $template->global->fn, (new Defaults())->getFunctions());
        $this->addFunctions($class, $functions);
        $end = $this->printCanvas();
        $this->printHeader('Native types');
        $this->printCode((string) $namespace);
        $this->addProperties($class, $template->getParameters(), \false);
        $this->printHeader('phpDoc types');
        $this->printCode((string) $namespace);
        echo $end;
    }
    /**
     * @param  mixed[]  $vars
     */
    public function printVars(array $vars) : void
    {
        if (!\class_exists(Php\Type::class)) {
            throw new \LogicException('Nette PhpGenerator is required to print template, install package `nette/php-generator`.');
        }
        $res = '';
        foreach ($vars as $name => $value) {
            if (\Packetery\Latte\Helpers::startsWith($name, 'ʟ_')) {
                continue;
            }
            $type = $this->getType($value);
            $res .= "{varType {$type} \${$name}}\n";
        }
        $end = $this->printCanvas();
        $this->printHeader('varPrint');
        $this->printCode($res ?: 'No variables', 'latte');
        echo $end;
    }
    /**
     * @param  mixed[]  $props
     */
    public function addProperties(Php\ClassType $class, array $props, ?bool $native = null) : void
    {
        $printer = new Php\Printer();
        $native = $native ?? \PHP_VERSION_ID >= 70400;
        foreach ($props as $name => $value) {
            $class->removeProperty($name);
            $type = $this->getType($value);
            $prop = $class->addProperty($name);
            if ($native) {
                $prop->setType($type);
            } else {
                $doctype = $this->printType($type, \false, $class->getNamespace()) ?: 'mixed';
                $prop->setComment("@var {$doctype}");
            }
        }
    }
    /**
     * @param  callable[]  $funcs
     */
    public function addFunctions(Php\ClassType $class, array $funcs) : void
    {
        $printer = new Php\Printer();
        foreach ($funcs as $name => $func) {
            $method = (new Php\Factory())->fromCallable($func);
            $type = $this->printType($method->getReturnType(), $method->isReturnNullable(), $class->getNamespace()) ?: 'mixed';
            $class->addComment("@method {$type} {$name}" . $this->printParameters($method, $class->getNamespace()));
        }
    }
    private function printType(?string $type, bool $nullable, ?Php\PhpNamespace $namespace) : string
    {
        if ($type === null) {
            return '';
        }
        if ($namespace) {
            $type = @$namespace->unresolveName($type);
            // simplifyName() in v4
        }
        if ($nullable && \strcasecmp($type, 'mixed')) {
            $type = \strpos($type, '|') !== \false ? $type . '|null' : '?' . $type;
        }
        return $type;
    }
    /**
     * @param Closure|GlobalFunction|Method  $function
     */
    public function printParameters($function, ?Php\PhpNamespace $namespace = null) : string
    {
        $params = [];
        $list = $function->getParameters();
        foreach ($list as $param) {
            $variadic = $function->isVariadic() && $param === \end($list);
            $params[] = \ltrim($this->printType($param->getType(), $param->isNullable(), $namespace) . ' ') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') . '$' . $param->getName() . ($param->hasDefaultValue() && !$variadic ? ' = ' . \var_export($param->getDefaultValue(), \true) : '');
        }
        return '(' . \implode(', ', $params) . ')';
    }
    public function printCanvas() : string
    {
        echo '<script src="https://nette.github.io/resources/prism/prism.js"></script>';
        echo '<link rel="stylesheet" href="https://nette.github.io/resources/prism/prism.css">';
        echo "<div style='all:initial;position:fixed;overflow:auto;z-index:1000;left:0;right:0;top:0;bottom:0;color:black;background:white;padding:1em'>\n";
        return "</div>\n";
    }
    public function printHeader(string $string) : void
    {
        echo "<h1 style='all:initial;display:block;font-size:2em;margin:1em 0'>", \htmlspecialchars($string), "</h1>\n";
    }
    public function printCode(string $code, string $lang = 'php') : void
    {
        echo '<pre><code class="language-', \htmlspecialchars($lang), '">', \htmlspecialchars($code), "</code></pre>\n";
    }
    private function getType($value) : string
    {
        if (\is_object($value)) {
            return \get_class($value);
        } elseif (\is_int($value)) {
            return 'int';
        } elseif (\is_float($value)) {
            return 'float';
        } elseif (\is_string($value)) {
            return 'string';
        } elseif (\is_bool($value)) {
            return 'bool';
        } elseif (\is_array($value)) {
            return 'array';
        } else {
            return 'mixed';
        }
    }
}
