<?php

namespace Lib\vue;

/**
 * 处理vue的,加载的是element-ui
 * @author:dc
 * @time 2024/12/12 9:53
 * Class VueService
 * @package App\Services
 */
class vue
{
    /**
     * 模板html
     * @var string
     */
    protected $html = '';

    /**
     * 前端加载的数据
     * @var array
     */
    protected $data = [];

    /**
     * 基础模板
     * @var string
     */
    protected $baseHtml = '';

    /**
     * 模板存储的文件路径
     * @var string
     */
    protected static $tempPath = '';

    /**
     * 样式
     * @var string
     */
    protected $css = '';

    /**
     * js
     * @var string
     */
    protected $script = '';

    /**
     * 这个是完整的html
     * @var string
     */
    protected $fullHtml = '';



    /**
     * 全组件模式
     * @var string
     */
    protected static $baseVue = 'index';


    public function __construct(string $html, array $data = [])
    {

        $this->baseHtml = file_get_contents(__DIR__.'/template/'.static::$baseVue.'.vue');

        $this->html = '';

        // 版本2
        $this->baseHtml = str_replace('@component(<--{#component_main#}-->)','@component('.$html.')',$this->baseHtml);

        $this->data = $data;
    }



    /**
     * @param string $name vue 文件相对路径  xx/xx
     * @param array $data 传递到前端的数据
     * @return string
     * @throws \Exception
     * @author:dc
     * @time 2024/12/13 9:22
     */
    public static function view(string $name,array $data = []):string {

        if(!self::$tempPath){
            throw new \Exception('vue view not found. template path');
        }

        if(!is_file(self::$tempPath.'/'.ltrim($name,'/').'.vue')){
            throw new \Exception('vue view not found. path:'.$name);
        }

        return (new static($name,$data))->getHtml();
    }


    /**
     * 设置 模板 放在那个目录
     * @param string $path
     * @author:dc
     * @time 2024/12/12 10:05
     */
    public static function runPath(string $path){
        self::$tempPath = rtrim($path,'/');
    }

    /**
     * 处理样式
     * @author:dc
     * @time 2024/12/12 10:19
     */
    protected function renderStyle(){
        // 找到样式 css
        preg_match_all("/<style([\sa-z=\/\"']+)?>([\S\s]*)<\/style>/iU",$this->fullHtml,$style);
        if(!empty($style[0])){
            // 匹配出来的替换为空
            $this->fullHtml = str_replace($style[0],'',$this->fullHtml);
            $style[0] = array_unique(array_map('trim',$style[0]));
            $this->fullHtml = str_replace('<!--{#style#}-->',implode("\n",$style[0]),$this->fullHtml);
        }


    }

    /**
     * 处理js
     * @author:dc
     * @time 2024/12/12 10:27
     */
    protected function renderScript(){
        // 找到样式 css
        if(preg_match("/<script([\sa-z=\/\"']+)?>([\S\s]*)<\/script>/iU",$this->html,$js)){
            $this->html = str_replace($js[0],'',$this->html);
            $this->script .= $js[0];
        }
        $this->baseHtml = str_replace('<!--{#script#}-->',$this->script,$this->baseHtml);
    }

    /**
     * 解析组件
     * test.vue 内容如下
    <template> 这个是组件 </template>

    <script>
    export default {
    template:"slot#template",
    data(){

    }
    }
    </script>

    <style scoped>

    </style>

     * @author:dc
     * @return array|false
     * @time 2024/12/12 14:43
     */
    protected function parseComponent(&$tempHtml){
        // 处理组件
        preg_match_all("/['\"]@component\(([\sa-z0-9_\-\/]+)\)['\"]/iU",$tempHtml,$components);
        if(!empty($components[0])){
            foreach ($components[0] as $k=>$com){
                $component = $components[1][$k];
                // 找到组件文件
                $file = self::$tempPath.'/'.ltrim($component,'/').'.vue';
                if(!is_file($file)){
                    throw new \Exception('vue component not found: '.$component);
                }

                $html = file_get_contents($file);

                // 这个是组件的名称 以 文件名为名称
                $name = str_replace('/','-',$component);
                $uuid = 'data-'.uniqid(); // 这个是唯一id,每个组件唯一,用来隔离css
                // 找到js
                preg_match("/<script([\sa-z=\/\"']+)?>([\S\s]*)<\/script>/i",$html,$js);
                // 找到css
                preg_match("/<style([\sa-z=\/\"']+)?>([\S\s]*)<\/style>/i",$html,$css);
                // 处理css的限定标记 scoped
                $css[0] = array_map(function ($cs) use ($css,$uuid){
                    // 是否是最后一行,最后一行不处理
                    if(preg_match("/<\s*\/\s*style\s*>/i",$cs)){
                        return $cs;
                    }
                    // 是否存在单独的 scoped
                    if(stripos($cs,'[scoped]')!==false){
                        return str_replace('[scoped]',"[{$uuid}]",$cs);
                    }
                    // 这个是 当前组件下是是否全局 scoped 限定
                    if(stripos($css[1]??'','scoped')!==false){
                        return $cs."[{$uuid}]";
                    }
                    // 不处理
                    return $cs;
                },explode("{",$css[0]??''));
                // 还原css操作
                $css = implode('{',$css[0]);

                // 找到template html
                preg_match("/<template(\s*.*)>([\S\s]*)<\/template>/i",$html,$temp);
                $tempAttr = $temp[1]??'';
                $temp[1] = $temp[2]??'';
                // 为每个标签添加uuid 用来隔离css
                // <a></a> 最后是 <a data-xxxx></a>
                if($temp[1]){
                    preg_match_all("/<([a-z_\-]+)\s?([\S\s]*)>/iU",$temp[1],$tags);
                    foreach ($tags[0] as $tag){
                        // 过滤哪些标记不进行 唯一属性
                        if(preg_match("/^<(v-for|template|slot|v-if|v-else-if|v-else|transition-group|transition|keep-alive|component)[\n\s>\/]/",$tag)){
                            continue;
                        }

                        // 有空格
                        if(strpos($tag,' ')!==false){
                            $tagHtml = explode(' ',$tag);
                            $tagHtml[0] .= " {$uuid} ";
                            $tagHtml = implode(' ',$tagHtml);
                        }else{
                            // 无空格
                            $tagHtml = mb_substr($tag,0,-1)." {$uuid} >";
                        }
                        $temp[1] = str_replace($tag,$tagHtml,$temp[1]);
                    }
                }
                // 有 ${{这种情况}} 这个其实是 钱+价额
                $temp[1] = str_replace('${','\\\${',$temp[1]);
                $temp = " template:`<div {$tempAttr}> {$css} ".addcslashes($temp[1],'`').'</div>`,';

                $js = preg_replace('/export[\s\n\r]+default[\s\n\r]*{/',"{ \n".$temp,$js[2]??'');

                // 替换模板 中用到组件的地方
                $tempHtml = str_replace(
                    $com,
                    $js,
                    $tempHtml
                );

            }
        }else{
            return false;
        }

        return true;

    }
    /**
     * 处理组件 把页面上通过 import导入的html 处理成可用的
     * 类似 @include('xxxx')
     * @author:dc
     * @time 2024/12/12 10:34
     */
    protected function components(){

        $forNum = 0;
        while (1) {
            if ($forNum >= 5) {
                throw new \Exception('vue 组件嵌套过多/无限嵌套');
            }
            $forNum++;

            if(!$this->parseComponent($this->fullHtml)){
                break;
            }
        }

    }

    /**
     * 渲染数据
     * 界面上使用 <{#data#}> 这个是 渲染普通数据
     *          "<[{#data#}]>" 这个是渲染对象使用的
     * @author:dc
     * @time 2024/12/12 14:29
     */
    protected function renderData(){
        foreach ($this->data as $key=>$datum){
            $datum = is_array($datum) ? json_encode($datum,JSON_UNESCAPED_UNICODE) : $datum;

            $this->fullHtml = str_replace(['<{#'.$key.'#}>','"<[{#'.$key.'#}]>"',"'<[{#'.$key.'#}]>'"],$datum,$this->fullHtml);
        }
    }

    /**
     * 返回模板代码 html
     * @return string
     * @author:dc
     * @time 2024/12/12 9:58
     */
    public function getHtml():string {
        $this->renderScript();


        // 找到样式 link
        if(preg_match_all("/<link(.*)\/?>/iU",$this->html,$links)){
            foreach ($links[0] as $link){
                $this->html = str_replace($link,'',$this->html);
            }
            $this->baseHtml = str_replace('<!--{#link#}-->',implode("\r\n",$links[0]),$this->baseHtml);
        }

        $this->fullHtml = str_replace('<!--{#body#}-->',$this->html,$this->baseHtml);

        $this->components();

        $this->renderStyle();

        // 模板2 组件模式 <!--{#script.src#}-->
        /**
         * 组件中 用 @import("js/css") 导入的外链资源
         */
        preg_match_all('/\@import\((["\'a-z0-9._\-\/?&=:]{5,})\)/iU',$this->fullHtml,$sc);
        if(!empty($sc[1])){
            $this->fullHtml = str_replace($sc[0],'',$this->fullHtml);
            $this->fullHtml = str_replace('<!--{#script.src#}-->',implode("\n",array_map(function ($v){
                $v = str_replace(['"',"'"],'',$v);
                if(stripos($v,'.js')){
                    return '<script type="application/javascript" src="'.$v.'"></script>';
                }elseif (stripos($v,'.css')){
                    return '<link rel="stylesheet" href="'.$v.'" />';
                }else{
                    return '';
                }
            },$sc[1])),$this->fullHtml);

        }

        $this->renderData();

        return $this->fullHtml;
    }

}