<?php
// +----------------------------------------------------------------------
// | saiadmin [ saiadmin快速开发框架 ]
// +----------------------------------------------------------------------
// | Author: sai <1430792918@qq.com>
// +----------------------------------------------------------------------
namespace plugin\saiadmin\utils\code;

use Twig\TwigFilter;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use plugin\saiadmin\exception\ApiException;

// 定义目录分隔符常量
defined('DS') or define('DS', DIRECTORY_SEPARATOR);

/**
 * 代码生成引擎
 */
class CodeEngine
{
    /**
     * @var array 值栈
     */
    private array $value = [];

    /**
     * 模板名称
     * @var string
     */
    private string $stub = 'saiadmin';

    /**
     * 获取配置文件
     * @return string[]
     */
    private static function _getConfig(): array
    {
        return [
            'template_path' => base_path() . DS . 'plugin' . DS . 'saiadmin' . DS . 'utils' . DS . 'code' . DS . 'stub',
            'generate_path' => runtime_path() . DS . 'code_engine' . DS . 'saiadmin',
        ];
    }

    /**
     * 初始化
     * @param array $data 数据
     */
    public function __construct(array $data)
    {
        // 读取配置文件
        $config = self::_getConfig();

        // 判断模板是否存在
        if (!is_dir($config['template_path'])) {
            throw new ApiException('Template directory not found');
        }
        // 判断文件生成目录是否存在
        if (!is_dir($config['generate_path'])) {
            mkdir($config['generate_path'], 0770, true);
        }
        // 赋值
        $this->value = $data;
    }

    /**
     * 设置模板名称
     * @param $stub
     * @return void
     */
    public function setStub($stub): void
    {
        $this->stub = $stub;
    }

    /**
     * 渲染文件内容
     */
    public function renderContent($path, $filename): string
    {
        $config = self::_getConfig();

        $path = $config['template_path'] . DS . $this->stub . DS . $path;

        $loader = new FilesystemLoader($path);
        $twig = new Environment($loader);
        $camelFilter = new TwigFilter('camel', function ($value) {
            static $cache = [];
            $key = $value;
            if (isset($cache[$key])) {
                return $cache[$key];
            }
            $value = ucwords(str_replace(['-', '_'], ' ', $value));
            return $cache[$key] = str_replace(' ', '', $value);
        });
        $boolFilter = new TwigFilter('bool', function ($value) {
            if ($value == 1) {
                return 'true';
            } else {
                return 'false';
            }
        });
        $formatFilter = new TwigFilter('formatNumber', function ($value) {
            if (ctype_digit((string) $value)) {
                return $value;
            } else {
                return '1';
            }
        });
        $defaultFilter = new TwigFilter('parseNumber', function ($value) {
            if ($value) {
                return $value;
            } else {
                return 'null';
            }
        });
        $containsFilter = new TwigFilter('str_contains', function ($haystack, $needle) {
            return str_contains($haystack ?? '', $needle ?? '');
        });

        $twig->addFilter($camelFilter);
        $twig->addFilter($boolFilter);
        $twig->addFilter($containsFilter);
        $twig->addFilter($formatFilter);
        $twig->addFilter($defaultFilter);

        return $twig->render($filename, $this->value);
    }

    /**
     * 生成后端文件
     */
    public function generateBackend($action, $content): void
    {
        $outPath = '';
        if ($this->value['template'] == 'app') {
            $rootPath = base_path() . DS . 'app' . DS . $this->value['namespace'];
            $adminPath = '';

        } else {
            $rootPath = base_path() . DS . 'plugin' . DS . $this->value['namespace'] . DS . 'app';
            $adminPath = DS . 'admin';
        }
        $subPath = DS . $this->value['package_name'];
        switch ($action) {
            case 'controller':
                $outPath = $rootPath . $adminPath . DS . 'controller' . $subPath . DS . $this->value['class_name'] . 'Controller.php';
                break;
            case 'logic':
                $outPath = $rootPath . $adminPath . DS . 'logic' . $subPath . DS . $this->value['class_name'] . 'Logic.php';
                break;
            case 'validate':
                $outPath = $rootPath . $adminPath . DS . 'validate' . $subPath . DS . $this->value['class_name'] . 'Validate.php';
                break;
            case 'model':
                $outPath = $rootPath . DS . 'model' . $subPath . DS . $this->value['class_name'] . '.php';
                break;
            default:
                break;
        }

        if (empty($outPath)) {
            throw new ApiException('Invalid file type, cannot generate file');
        }
        if (!is_dir(dirname($outPath))) {
            mkdir(dirname($outPath), 0777, true);
        }

        file_put_contents($outPath, $content);
    }

    /**
     * 生成前端文件
     */
    public function generateFrontend($action, $content): void
    {
        $rootPath = dirname(base_path()) . DS . $this->value['generate_path'];
        if (!is_dir($rootPath)) {
            throw new ApiException('Frontend directory not found, must be same level as backend');
        }

        $rootPath = $rootPath . DS . 'src' . DS . 'views' . DS . 'plugin' . DS . $this->value['namespace'];
        $subPath = DS . $this->value['package_name'];
        switch ($action) {
            case 'index':
                $outPath = $rootPath . $subPath . DS . $this->value['business_name'] . DS . 'index.vue';
                break;
            case 'edit-dialog':
                $outPath = $rootPath . $subPath . DS . $this->value['business_name'] . DS . 'modules' . DS . 'edit-dialog.vue';
                break;
            case 'table-search':
                $outPath = $rootPath . $subPath . DS . $this->value['business_name'] . DS . 'modules' . DS . 'table-search.vue';
                break;
            case 'api':
                $outPath = $rootPath . DS . 'api' . $subPath . DS . $this->value['business_name'] . '.ts';
                break;
            default:
                break;
        }

        if (empty($outPath)) {
            throw new ApiException('Invalid file type, cannot generate file');
        }
        if (!is_dir(dirname($outPath))) {
            mkdir(dirname($outPath), 0777, true);
        }

        file_put_contents($outPath, $content);
    }

    /**
     * 生成临时文件
     */
    public function generateTemp(): void
    {
        $config = self::_getConfig();
        $rootPath = $config['generate_path'];

        $vuePath = $rootPath . DS . 'vue' . DS . 'src' . DS . 'views' . DS . 'plugin' . DS . $this->value['namespace'];
        $phpPath = $rootPath . DS . 'php';
        $sqlPath = $rootPath . DS . 'sql';
        if ($this->value['template'] == 'app') {
            $phpPath = $phpPath . DS . 'app' . DS . $this->value['namespace'];
            $adminPath = '';
        } else {
            $phpPath = $phpPath . DS . 'plugin' . DS . $this->value['namespace'] . DS . 'app';
            $adminPath = DS . 'admin';
        }
        $subPath = DS . $this->value['package_name'];

        $indexOutPath = $vuePath . $subPath . DS . $this->value['business_name'] . DS . 'index.vue';
        $this->checkPath($indexOutPath);
        $indexContent = $this->renderContent('vue', 'index.stub');
        file_put_contents($indexOutPath, $indexContent);

        $editOutPath = $vuePath . $subPath . DS . $this->value['business_name'] . DS . 'modules' . DS . 'edit-dialog.vue';
        $this->checkPath($editOutPath);
        $editContent = $this->renderContent('vue', 'edit-dialog.stub');
        file_put_contents($editOutPath, $editContent);

        $searchOutPath = $vuePath . $subPath . DS . $this->value['business_name'] . DS . 'modules' . DS . 'table-search.vue';
        $this->checkPath($searchOutPath);
        $searchContent = $this->renderContent('vue', 'table-search.stub');
        file_put_contents($searchOutPath, $searchContent);

        $viewOutPath = $vuePath . DS . 'api' . $subPath . DS . $this->value['business_name'] . '.ts';
        $this->checkPath($viewOutPath);
        $viewContent = $this->renderContent('ts', 'api.stub');
        file_put_contents($viewOutPath, $viewContent);

        $controllerOutPath = $phpPath . $adminPath . DS . 'controller' . $subPath . DS . $this->value['class_name'] . 'Controller.php';
        $this->checkPath($controllerOutPath);
        $controllerContent = $this->renderContent('php', 'controller.stub');
        file_put_contents($controllerOutPath, $controllerContent);

        $logicOutPath = $phpPath . $adminPath . DS . 'logic' . $subPath . DS . $this->value['class_name'] . 'Logic.php';
        $this->checkPath($logicOutPath);
        $logicContent = $this->renderContent('php', 'logic.stub');
        file_put_contents($logicOutPath, $logicContent);

        $validateOutPath = $phpPath . $adminPath . DS . 'validate' . $subPath . DS . $this->value['class_name'] . 'Validate.php';
        $this->checkPath($validateOutPath);
        $validateContent = $this->renderContent('php', 'validate.stub');
        file_put_contents($validateOutPath, $validateContent);

        $modelOutPath = $phpPath . DS . 'model' . $subPath . DS . $this->value['class_name'] . '.php';
        $this->checkPath($modelOutPath);
        $modelContent = $this->renderContent('php', 'model.stub');
        file_put_contents($modelOutPath, $modelContent);

        $sqlOutPath = $sqlPath . DS . 'sql.sql';
        $this->checkPath($sqlOutPath);
        $sqlContent = $this->renderContent('sql', 'sql.stub');
        file_put_contents($sqlOutPath, $sqlContent);
    }

    /**
     * 检查并生成路径
     * @param $path
     * @return void
     */
    protected function checkPath($path): void
    {
        if (!is_dir(dirname($path))) {
            mkdir(dirname($path), 0777, true);
        }
    }

}