<?php

use xmvc\MVC;
use xmvc\Dump;
use xmvc\Valid;
use xmvc\Viewer;
use xmvc\Encoder;
use xmvc\ErrorClass;
use xmvc\ReturnClass;
use xmvc\Session;

/**
 * XMVC函数库
 */


/**
 * 语言文本切换
 * lg('abc')
 * lg('abc{:1}def{:2}',$var1,$var2)
 * @return string
 */
function lg(string $str): string
{
    $str = addslashes(trim($str));
    if (isset(MVC::$langData[$str])) {
        $str = MVC::$langData[$str];
    }
    $argsCount = func_num_args();
    if ($argsCount > 1) {
        $args = func_get_args();
        for ($i = 1; $i < $argsCount; $i++) {
            $str = str_replace('{:' . $i . '}', (string)$args[$i], $str);
        }
    }
    return $str;
}

/**
 * 构建网址
 */
function url(string $route, array|string $params = null): string
{
    if ($route == 'index') {
        return HOST ?: '/';
    } elseif ($route == 'self') {
        if (IS_DOMIAN_SITE) {
            $route = substr(MVC::$controller, strlen(SITE)) . '-' . MVC::$function;
        } else {
            $route = MVC::$controller . '-' . MVC::$function;
        }
        $params = $params ? array_merge(MVC::$params, $params) : MVC::$params;
    }
    $url = HOST . '/' . trim($route, '/');
    if ($params) {
        if (is_array($params)) {
            array_walk($params, function (&$value, $key) {
                $value = $key . '-' . urlencode($value ?: '');
            });
            $url .= '~' . implode('~', $params);
        } else {
            $url .= '~' . ltrim($params, '~');
        }
    }
    return $url;
}
/**
 * 创建一个对象
 * @param string $className 类名 action.admin.demo
 * @param ... $params 可变参数
 * @return object
 */
function C(string $className, ...$params)
{
    $className = '\\' . ltrim(str_replace('.', '\\', $className), '\\');
    return MVC::createObject($className, $params);
}

/**
 * 创建一个单列对象
 * @param object $obj 创建的对象 D(new valid(), 'func_i()');
 * @param string $guidKey 前面会加上类名 get_class($obj) . $guidKey, 如果 $guidKey 传空, 那么全局就只能创建一个对象
 * @return object
 */
function D(object $obj, string $guidKey = ''): object
{
    $guidKey = get_class($obj) . $guidKey;
    if (!isset(MVC::$objectList[$guidKey])) {
        MVC::$objectList[$guidKey] = $obj;
    }
    return MVC::$objectList[$guidKey];
}

/**
 * 创建一个 action 对象
 * @param string $className 类名
 * @param ... $params 可变参数
 * @return object
 */
function A(string $className, ...$params): \action\base
{
    $className = '\\action\\' . ltrim(str_replace('.', '\\', $className), '\\');
    return C($className, ...$params);
}

/**
 * 创建一个 action 单例对象
 */
function DA(string $className, ...$params): \action\base
{
    $className = '\\action\\' . ltrim(str_replace('.', '\\', $className), '\\');
    return D($className, ...$params);
}

/**
 * 创建一个 model 对象
 * @param string $className 类名
 * @param ... $params 可变参数
 * @return \model\base
 */
function M(string $className, ...$params): \model\base
{
    $className = '\\model\\' . ltrim(str_replace('.', '\\', $className), '\\');
    return C($className, ...$params);
}

/**
 * 创建一个 model 单例对象
 */
function DM(string $className, ...$params): \model\base
{
    $className = '\\model\\' . ltrim(str_replace('.', '\\', $className), '\\');
    return D($className, ...$params);
}


/**
 * 抛出一个错误
 * @param string $message 错误信息
 * @param array $exts 扩展信息，例  ['sql'=>$sql]
 */
function error(string $message, array $exts = null, array $filters = [], array $customCaller = [])
{
    ErrorClass::errorMake(errorInfo($message, $exts, $filters, $customCaller));
    exit();
}


/**
 * 构造一个错误信息体
 * @param string $message 错误信息
 * @param array $exts 扩展信息，例  ['sql'=>$sql]
 */
function errorInfo(string $message, array $exts = null, array $filters = [], array $customCaller = [])
{
    $backtrace = defined(DEBUG_BACKTRACE) ? debug_backtrace(DEBUG_BACKTRACE) : debug_backtrace();
    $filters = array_merge([__FILE__], $filters);
    $caller = getTraceCaller($backtrace, $filters);
    if (!empty($customCaller)) {
        $caller = $customCaller;
    }
    $error = [
        'type' => E_ERROR,
        'file' => $caller['file'],
        'line' => $caller['line'],
        'message' => $message,
        'backtrace' => $backtrace,
    ];

    if (!is_null($exts)) {
        $error = array_merge($error, $exts);
    }
    return $error;
}

/**
 * 取 GET 或 POST 或 php://input 的值
 * @param string $reqKey
 * @return mixed
 */
function I(string $reqKey, mixed $default = null, array $rules = null): mixed
{
    if ($reqKey == '') {
        return MVC::$req;
    } elseif (is_null($rules)) {
        if (isset(MVC::$req[$reqKey])) {
            return MVC::$req[$reqKey];
        } else {
            return $default;
        }
    } else {
        /** @var Valid $valid */
        $valid = D(new Valid(), 'I()');
        $isValided = $valid->check(MVC::$req, $reqKey, $rules);
        if ($isValided) {
            if (isset(MVC::$req[$reqKey])) {
                return MVC::$req[$reqKey];
            } else {
                return $default; // 非必须则返回默认值
            }
        } else {
            return null;
        }
    }
}

/**
 * 提交参数的验证错误信息, 如果没有错误返回 null
 *
 * @return array|null
 */
function IError(): array|null
{
    /** @var Valid $valid */
    $valid = D(new Valid(), 'I()');
    return $valid->errorInfo();
}

/** 提交参数的是否有 GET */
function hasGet(): bool
{
    return !empty(MVC::$params);
}

/** 提交参数的是否有 POST */
function hasPost(): bool
{
    return !empty(MVC::$postKeys);
}

/** 提交参数的是否是 php://input */
function hasPhpinput(): bool
{
    return
        !empty(MVC::$phpinputKeys);
}

/** GET 提交的所有的键 */
function getKeys(): array
{
    return array_keys(MVC::$params);
}

/** POST 提交的所有的键 */
function postKeys(): array
{
    return MVC::$postKeys;
}

/** php://input 提交的所有的键 */
function phpinputKeys(): array
{
    return MVC::$phpinputKeys;
}

/**
 * 取配置文件中的具体的项的值
 * @param string $dns 路径
 * cfg('net.db.main') 取 MVC::$cfg['net']['db']['main']的值
 * @return mixed
 */
function cfg(string $dns)
{
    return getArrayFromStrLink($dns, MVC::$cfg);
}

/**
 * a.b.c 取 数组
 *
 * @param string $dns 例 net.redis.main
 * @param array|stdClass $array
 * @return mixed
 */
function getArrayFromStrLink(string $dns, array|stdClass $array)
{
    if ($dns == '.') {
        return $array;
    }
    $arr = explode('.', $dns);
    if (is_object($array)) {
        $array = get_object_vars($array);
    }
    foreach ($arr as $v) {
        if (isset($array[$v])) {
            $array = $array[$v];
            if (is_object($array)) {
                $array = get_object_vars($array);
            }
        } else {
            return null; //trigger_error(lg('未找到指定项').': '.$dns, E_USER_ERROR);
        }
    }
    return $array;
}

/**
 * a.b.c 写 数组
 *
 * @param string $dns 例 net.redis.main
 * @param mixed $value
 * @param array $array
 */
function setArrayFromStrLink(string|array $dns, mixed $value, array &$array)
{
    if (!$dns) {
        return;
    }
    if (is_string($dns)) {
        $dns =  explode('.', $dns);
    }
    $key = array_shift($dns);
    // 如果当前 key 存在且还有子 key 需要处理
    if (isset($array[$key]) && $dns) {
        if (is_array($array[$key])) {
            setArrayFromStrLink($dns, $value, $array[$key]);
        } else {
            error(lg('dns 寻址错误'), null, [[__FILE__, __LINE__]]);
        }
    } else {
        $array[$key] = $value;
    }
}

/**
 * 加载配置文件
 * 如果不是开发模式则会取 remote 目录下的配置, 如果 remote 下没有，则取开发模式下的
 *
 * @param string $filename  配置的相对目录和文件名
 * @return mixed 配置
 */
function loadConfig(string $filename): array
{
    $filename .= '.php';
    if (CONFIG_USE_APCU) {
        $prefix = SITE_GUID . '_';
    }
    if (CONFIG_USE_APCU && apcu_exists($prefix . ':cfg.' . $filename)) {
        return apcu_fetch($prefix . ':cfg.' . $filename);
    } else {
        if (IS_DEVELOP) {
            $path = DIR_CONFIG . '/' . $filename;
        } else {
            $path = DIR_CONFIG . '/remote/' . $filename;
            if (!is_file($path)) {
                $path = DIR_CONFIG . '/' . $filename;
            }
        }
        if (! is_file($path)) {
            exit(lg('不存在的配置文件') . ' ' . $path);
        } else {
            $cfg = require($path);
            if (CONFIG_USE_APCU) {
                apcu_store($prefix . ':cfg.' . $filename, $cfg, 315360000);
            }
            return $cfg;
        }
    }
}

/**
 * 使用指定变量格式化字符串
 *
 * qgprintf("sum({:1}) sum_{:2}",$var1,$var2);
 * qgprintf("sum({:a}) sum_{:b}",$arr); //$arr['a'=>1,'b'=>2]
 * qgprintf("sum({:a}) sum_{:b} in({:sets})",$arr); //$arr['a'=>1,'b'=>2,sets=[1,2,3,4]]
 * qgprintf("{:1} sum({:a}) sum_{:b}",HOST $arr); //HOST='http://xxx',$arr['a'=>1,'b'=>2]
 * @param mixed $var
 * @return string
 */
function qgprintf($rulestr, ...$args): string
{
    $count = count($args);
    for ($i = $count - 1; $i > -1; $i--) {
        $arg = $args[$i];
        if (is_array($arg)) {
            foreach ($arg as $k => $v) {
                $type = gettype($v);
                if ($type != 'string') {
                    if (is_object($v)) {
                        $v = get_object_vars($v);
                    }
                    if (is_array($v)) {
                        foreach ($v as $k2 => $v2) {
                            $type = gettype($v2);
                            if ($type == 'array') {
                                $v2 = 'Array';
                            } elseif ($type == 'object') {
                                $v2 = get_class($v2);
                            }
                            $v[$k2] = $v2;
                        }
                        $v = implode(',', $v);
                    } elseif (is_null($v)) {
                        $v = 'NULL';
                    }
                }
                $rulestr = str_replace('{:' . $k . '}', $v, $rulestr);
            }
        } else {
            $arg = is_null($arg) ? 'NULL' : $arg;
            $rulestr = str_replace('{:' . ($i + 1) . '}', $arg, $rulestr);
        }
    }
    return $rulestr;
}

/**
 * 将任意值转成字符串
 * @param mixed $mixedValue
 * @return string
 */
function toString($mixedValue): string
{
    if (is_string($mixedValue) || is_numeric($mixedValue)) {
        return $mixedValue;
    }
    $result = '';
    $type = gettype($mixedValue);
    switch ($type) {
        case 'NULL':
            $result = '';
            break;
        case 'array':
            $result = json_encode($mixedValue);
            break;
        case 'object':
            $result = get_class($mixedValue);
            break;
        default:
            dd($type);
    }
    return $result;
}

/**
* 生成唯一ID
* @return string 32位
*/
function guid(): string
{
    return md5(uniqid((string)rand(0, PHP_INT_MAX), true) . serialize($_SERVER));
}

/**
 * 取时间的毫秒数, 默认0 为当前时间
 * @return int
 */
function msec($microtime = 0): int
{
    if (!$microtime) {
        $microtime = microtime();
    }
    // 获取当前时间戳和微秒数
    list($usec, $sec) = explode(" ", $microtime);
    return round(((float)$usec + (float)$sec) * 1000);
}

/**
 * 将时间戳转成可阅读
 * @param int $sec 秒
 * @return array
 */
function formatTimeArr(int $sec): array
{
    $t = [];
    $t['d'] = floor($sec / (3600 * 24));
    $t['h'] = floor(($sec % (3600 * 24)) / 3600);
    $t['i'] = floor(($sec % 3600) / 60);
    $t['s'] = $sec % 60;
    return $t;
}

/**
 * 将时间戳转成可阅读
 * @param int $sec 秒
 * @return string
 */
function formatTimeString(array $t, array $lgs = null): string
{
    if (is_null($lgs)) {
        $lgs = ['d' => ' 天 ', 'h' => ' 小时 ', 'i' => ' 分钟 ', 's' => ' 秒 ', 'm' => ' 毫秒'];
    }
    foreach ($t as $k => $v) {
        if ($v == 0) {
            unset($t[$k]);
        } else {
            $t[$k] = $v . $lgs[$k];
        }
    }
    return implode('', $t);
}

/**
 * 将时间戳转成可阅读
 * @param int $sec 秒
 * @return string
 */
function formatTime(int $sec, array $lgs = null): string
{
    return formatTimeString(formatTimeArr($sec), $lgs);
}


/**
 * 将毫秒时间戳转成可阅读
 * @param int $msec 毫秒
 * @param int $precision 返回结果的小数位数
 * @return string
 */
function formatMsecTime(int $msec, array $lgs = null): string
{
    $t = formatTimeArr(floor($msec / 1000), $lgs);
    $t['m'] = $msec % 1000;
    return formatTimeString($t, $lgs);
}

/**
 * 将字节数转成 TB,GB,MB,KB 便宜阅读
 * @param int $bytes 字节数
 * @param int $precision 返回结果的小数位数
 * @return string
 */
function formatBytes(int $bytes, int $precision = 2): string
{
    $units = array('TB', 'GB', 'MB', 'KB','B');
    $unitIndex = 4;
    while ($bytes >= 1024 && $unitIndex > 0) {
        $bytes /= 1024;
        $unitIndex--;
    }
    return round($bytes, $precision) . ' ' . $units[$unitIndex];
}

/**
 * 返回安全字符串
 *
 * @return string|array
 */
function escape(string|array|\stdClass|null $variables): string|array
{
    // 避免不必要的双重编码 以及 忽略 htmlspecialchars 的 $double_encode = false 产生的错误警告
    $oldErrorReporting = error_reporting();
    error_reporting(E_ALL & ~E_WARNING);
    $oldErrorHandler = set_error_handler(function () {
        ;
    });
    $escapedStr = escapeRecursive($variables);
    set_error_handler($oldErrorHandler);
    error_reporting($oldErrorReporting);
    return $escapedStr;
}

function escapeRecursive(string|array|\stdClass|null $variables): string|array
{
    if (is_object($variables)) {
        $variables = get_object_vars($variables);
    }
    if (is_array($variables)) {
        return array_map('escapeRecursive', $variables); // 递归
    }

    if (is_numeric($variables)) {
        return (string)$variables;
    }

    if (is_null($variables)) {
        return '';
    }

    if (is_bool($variables)) {
        return $variables ? '1' : '0';
    }

    if (strtotime($variables) !== false) {
        return $variables;
    }

    // $double_encode = false 避免不必要的双重编码, 但它会对非法字符产生警告, 在上级中关闭 PHP E_WARNING
    return htmlspecialchars($variables, ENT_QUOTES, 'UTF-8', false) ?: '';
}

/** md5的63进制值 */
function md6(string $string): string
{
    return Encoder::md6($string);
}

/** 取csrf token */
function getCsrfToken(): string
{
    $csrfToken = bin2hex(random_bytes(16));
    Session::set('csrfToken', $csrfToken);
    return $csrfToken;
}


/** 取 用户 token */
function getUserToken(): string
{
    if (Session::get(SITE_USER_LOGIN_KEY)) {
        $info = Session::get(SITE_USER_INFO_KEY);
        if (!isset($info['token']) || empty($info['token'])) {
            error(lg('没有找到 用户 token'), [[__FILE__, __LINE__]]);
        } else {
            return $info['token'];
        }
    }
    return '';
}


/**
 * 重构返回结果数组
 *
 * @param array $data 源,一维或都二维数组
 * @param array|string $keyField 指定哪个/组字段做为数组的键
 * @param array|string|null $valueField 指定哪个/组字段做为数组的值, null 表示全部字段
 * @param Closure $func 回调数据处理,返回 false 则忽略, func($key,$value,$allkey,$row), 级联键 $allkey 为 一级.二级.三级
 * @return self
 * @example map('id')
 * @example map('id','name')
 * @example map('id','@','name') 不改变数组下标
 * @example map('id',['name','email'])
 * @example map(['id1','id2'],['name','email']) 多个字段做键时用 点 连接 id1.id2
 * @example map('username',['email','depa','items'=>['ck1','ck2']]) 支持嵌套
 * @example map('username',true) 第二个参数 true，表示有相同键的使用追加模式将变成三维数组 ['user001'=>[  ['a','b'],['c','d'] ], 'user002'=>...]
 */
function map(array $data, $keyField, $valueField = null, \Closure $func = null): array
{
    if (!is_array(current($data))) {
        $data = [$data];
    }
    $result = [];
    foreach ($data as $i => $row) {
        $key = '';
        if ($keyField == '@') {
            $key = $i;
        } elseif (is_string($keyField)) {
            $key = is_object($row) ? $row->{$keyField} : $row[$keyField];
        } else {
            foreach ($keyField as $field) {
                if ($key != '') {
                    $key .= '.';
                }
                $key .= is_object($row) ? $row->{$field} : $row[$field];
            }
        }
        if (is_null($valueField)) {
            if ($func && false === $func($key, $row, $key, $row)) {
                continue;
            }
            $result[$key] = $row;
        } elseif ($valueField === true) {
            if ($func && false === $func($key, $row, $key, $row)) {
                continue;
            }
            $result[$key][] = $row;
        } elseif (is_string($valueField)) {
            if (is_object($row)) {
                $aTemp = (array)$row;
                $value = $aTemp[$valueField];
            } else {
                $value = $row[$valueField];
            }
            if ($func && false === $func($valueField, $value, $key, $row)) {
                continue;
            }
            $result[$key] = $value;
        } else {
            $result[$key] = _mapRecursionRow($key, $valueField, $row, $func);
        }
    }
    return $result;
}

function _mapRecursionRow($parentKey, $fields, $row, \Closure $func = null)
{
    $data = [];
    foreach ($fields as $k => $field) {
        if (is_string($field)) {
            if (is_object($row)) {
                $aTemp = (array)$row;
                $value = $aTemp[$field];
            } else {
                $value = $row[$field];
            }
            if ($func && false === $func($field, $value, $parentKey . '.' . $field, $k, $row)) {
                continue;
            }
            if (is_numeric($k)) {
                $data[$field] = $value;
            } else {
                $data[$k] = $value;
            }
        } else {
            $data[$k] = _mapRecursionRow($parentKey . '.' . $k, $field, $row, $func);
        }
    }
    return $data;
}

/*************************************************************************************************************/
/** 目录与文件 */
/*************************************************************************************************************/

/**
 * 格式化目录
 *
 * @param string $dir
 * @return string
 */
function fdir(string $dir): string
{
    if (IS_WIN) {
        $dir = str_replace('\\', '/', $dir);
    }
    $dir = rtrim($dir, '/');
    return $dir;
}

/**
 * 取相对目录
 *
 * @param string|null $dir 目录或文件名
 * @return string
 */
function relDir($dir): string
{
    if (is_null($dir)) {
        return '';
    }
    $result = fdir($dir);
    $result = str_replace(DIR_MVC . '/', '[MVC]/', $result);
    $result = str_replace(ROOT . '/', '', $result);
    return rtrim($result, '/');
}

/**
 * 取绝对 对目录
 *
 * @param string $dir 目录或文件名
 * @return string
 */
function absDir(string $dir): string
{
    $relDir = relDir($dir);
    $fix = substr($relDir, 0, 5);
    if ($fix == '[MVC]') {
        $result = DIR_MVC . '/' . substr($relDir, 6);
    } else {
        $result = ROOT . '/' . $relDir;
    }
    return $result;
}

/**
 * 文件扩展名
 *
 * @return string
 */
function extname($filename)
{
    return pathinfo($filename, PATHINFO_EXTENSION);
}

/**
 * 文件名不带扩展
 *
 * @return string
 */
function filenameNotExt($filename)
{
    return pathinfo($filename, PATHINFO_FILENAME);
}

/**
 * 递归创建目录
 * @param string $dir 要创建的目录
 * @param int $mode 权限，默认 0777
 * @return  bool
 */
function mkdirs($dir, $mode = 0777)
{
    if (is_null($dir) || $dir === "") {
        return false;
    }
    if (is_dir($dir) || $dir === "/") {
        return true;
    }
    if (mkdirs(dirname($dir), $mode)) {
        return mkdir($dir, $mode);
    }
    return false;
}

/**
 * 以锁方式写文件，带重试机制
 * @param string $filename
 * @param string $data
 * @param int $retry 最大重试次数，默认 10 次
 * @param int $wait 每次重试的等待时间（秒），默认1 秒
 * @return bool
 */
function file_put_contents_lock(string $filename, string $data, int $retry = 10, int $wait = 1): bool
{
    $fp = fopen($filename, 'c');  // 'c' 模式在文件不存在时创建文件
    if ($fp === false) {
        return false;
    }

    $result = false;
    $attempts = 0;

    while ($attempts < $retry) {
        if (flock($fp, LOCK_EX | LOCK_NB)) {  // 尝试获取独占锁（非阻塞模式）
            ftruncate($fp, 0);  // 清空文件
            if (fwrite($fp, $data) !== false) {
                $result = true;
            }
            flock($fp, LOCK_UN);  // 释放锁
            break;  // 成功获取锁并写入数据后跳出循环
        } else {
            $attempts++;
            sleep($wait);  // 等待一段时间后重试
        }
    }
    if (!$result) {
        error(lg('写文件失败' . ':' . $filename), null, [[__FILE__,__LINE__]]);
    }
    fclose($fp);
    return $result;
}

/**
 * 将数组内容写入到一个php文件中
 * @param string $filename
 * @param array $data
 * @param int $mode 权限，默认 0777
 * @return bool
 */
function arrayToFile(string $filename, array $data, $mode = 0777): bool
{
    $code = var_export($data, true);
    $code = "<?php\r\n return " . $code . ";\r\n?>";
    $back = file_put_contents_lock($filename, $code);
    if ($back !== false) {
        chmod($filename, $mode);
    }
    return $back !== false;
}

/**
 * 递归遍历目录与文件
 * @param string $dir 要遍历的目录
 * @param \Closure $func 逻辑代码 function (string $path, string $basename,string 'dir'|'file')
 */
function forEachDir(string $dir, \Closure $func)
{
    if (is_dir($dir)) {
        if ($handle = opendir($dir)) {
            while (false !== ($file = readdir($handle))) {
                if ($file != "." && $file != "..") {
                    $path = $dir . "/" . $file;
                    if (is_dir($path)) {
                        forEachDir($path, $func);
                        $func($path, $file, 'dir');
                    } else {
                        $func($path, $file, 'file');
                    }
                }
            }
            closedir($handle);
        }
    }
}

/**
 * 递归删除目录
 * @param string $dir 要删除的目录
 * @param bool $deleteSelf 是否删除自已, 如果为 false 则只删除目录下的子目录及文件
 * @return  bool
 */
function rmdirs($dir, $deleteSelf = false)
{
    $dh = opendir($dir);
    while ($file = readdir($dh)) {
        if ($file != "." && $file != "..") {
            $fullpath = $dir . "/" . $file;
            if (!is_dir($fullpath)) {
                unlink($fullpath);
            } else {
                rmdirs($fullpath, true);
            }
        }
    }
    closedir($dh);
    return $deleteSelf ? rmdir($dir) : true;
}

/**
 * 通过目录名取站点名称
 *
 * @return string
 */
function getSiteNameByDir(string $dir): string
{
    if (strpos($dir, DIR_ACTION) !== false) {
        $tmp = substr($dir, strlen(DIR_ACTION));
    } elseif (strpos($dir, DIR_VIEW) !== false) {
        $tmp = substr($dir, strlen(DIR_VIEW));
    } else {
        return '';
    }
    $pos = strpos($tmp, '/');
    return $pos ? substr($tmp, 0, $pos) : '';
}

/*************************************************************************************************************/
/** 输出相关,视图,JSON */
/*************************************************************************************************************/
/**
 * 输出视图
 * - view(__METHOD__) //明确指定当前类当前方法命名的视图文件, 可稍稍节省一点点性能
 * - view('admin/user::login') //action类与方法
 * - view('admin/public/header') //视图路径
 * - view('admin/public/header',['a'=>'1']) //视图变量与值
 * - view('admin/public/header',['a'=>'1'],['a'=>'b']) //第二视图变量与值，相同key将覆盖第一个
 * - 如果 $variables 是一个数字下标的二维数组， 则数组会给变量 $data
 *
 * @param array|\stdClass $variables 要转成变量的数组或是 stdClass
 * @param array $variables2 要转成变量的数组2
 * @param bool  $require 是否加载, 如果为 false 就只是生成模板缓存
 */
function view(string $filename, array|\stdClass $variables = null, array $variables2 = null, bool $require = true)
{
    if (MVC::$req) {
        $request = escape(MVC::$req);
        extract($request);
    }
    if ($variables) {
        $variables = escape($variables);
        reset($variables);
        if (is_numeric(key($variables))) {
            $variables = ['data' => $variables];
        }
        extract($variables);
    }
    if ($variables2) {
        extract($variables2);
    }

    if (substr($filename, -8) != '.tpl.php') {
        $filename .= '.tpl.php';
    }
    if (!is_file($filename)) {
        error(relDir($filename) . ' 模板文件不存在', [
            'cmd' => [
                'title' => '创建此模板',
                'cmdFunction' => 'newTpl',
                'newClassFile' => $filename,
                'newClassName' => '',
                'newClassFunction' => ''
            ],
        ]);
    } else {
        $cacheFilename = Viewer::getViewCacheFilename($filename);
        $viewName = Viewer::getViewName($filename);
        if (! Viewer::isViewCacheValid($viewName, $cacheFilename)) {
            (new Viewer())->load($filename, true, $viewName, isset($loop) && $loop); // {{x.table loop=true}}
        }
        if ($require) {
            require($cacheFilename);
            //ob_start();
            //require($cacheFilename);
            //$html = ob_get_contents();
            //ob_end_clean();
        }
    }
}

/*************************************************************************************************************/
/**  */
/*************************************************************************************************************/

/**
 * 返回一个action值对象
 *
 * @param mixed $data
 * @param mixed ...$dataArgs 多值返回
 * @return ReturnClass
 */
function R(mixed $data = null, mixed ...$dataArgs): ReturnClass
{
    if ($dataArgs) {
        $result = [];
        $result[0] = $data;
        foreach ($dataArgs as $value) {
            $result[] = $value;
        }
    } else {
        $result = $data;
    }
    return (new ReturnClass())->data($result);
}

function dd(...$args)
{
    MVC::echo(Dump::dumpResult(true, ...$args));
    exit();
}

function ddRN(...$args)
{
    MVC::echo(Dump::dumpResult(false, ...$args));
    exit();
}

function dump(...$args)
{
    MVC::echo(Dump::dumpResult(true, ...$args));
}

function dumpRN(...$args)
{
    MVC::echo(Dump::dumpResult(false, ...$args));
}

function ddPrint(...$args)
{
    MVC::echo(Dump::printResult(true, ...$args));
    exit();
}

function ddPrintRN(...$args)
{
    MVC::echo(Dump::printResult(false, ...$args));
    exit();
}

function dumpPrint(...$args)
{
    MVC::echo(Dump::printResult(true, ...$args));
}

function dumpPrintRN(...$args)
{
    MVC::echo(Dump::printResult(false, ...$args));
}

function ddTable($rows)
{
    dumpTable($rows);
    exit();
}
function dumpTable($rows, $styles = [])
{
    if (!IS_CLI) {
        MVC::echo(dumpTableResult($rows, $styles));
    } else {
        print_r($rows);
    }
}
function dumpTableResult($rows, $styles = [])
{
    if (empty($rows)) {
        return '';
    }
    $html = '<table class="mvc-blue-table"  cellspacing="0" cellpadding="0"><thead><tr>';
    $firstRow = reset($rows);
    foreach ($firstRow as $k => $v) {
        $style = !isset($styles[$k]) ? '' : $styles[$k];
        if ($style) {
            $html .= '<th style="' . $style . '">' . $k . '</th>';
        } else {
            $html .= '<th>' . $k . '</th>';
        }
    }
    $html .= '</tr></thead><tbody>';
    foreach ($rows as $i => $row) {
        $html .= '<tr>';
        foreach ($row as $k => $v) {
            $v = toString($v);
            $title = strtr($v, ['"' => "'", '<' => '《', '>' => '》']);
            $html .= '<td title="' . $title . '">' . $v . '</td>';
        }
        $html .= '</tr>';
    }
    $html .= '</tbody></table>';
    return $html;
}

/**
 * 转义单引号，但已转义过的不重复转义
 *
 * @param string $str
 */
function addslashesOnce(string $str): string
{
    $result = '';
    $length = strlen($str);
    $escaped = false;
    for ($i = 0; $i < $length; $i++) {
        $char = $str[$i];
        if ($char === '\\') {
            if ($i + 1 < $length && $str[$i + 1] === '\'') {
                $result .= '\\\'';
                $i++;
                $escaped = false;
            } else {
                $result .= '\\';
            }
        } elseif ($char === '\'') {
            if (!$escaped) {
                $result .= '\\\'';
            } else {
                $result .= '\'';
                $escaped = false;
            }
        } else {
            $result .= $char;
            $escaped = false;
        }
    }
    return $result;
}

/**
 * 匿名函数转字符串
 * @param object $closureObject
 * @return string 匿名函数代码
 */
function closureToString($closureObject)
{
    $reflection = new \ReflectionFunction($closureObject);
    $file = new \SplFileObject($reflection->getFileName());
    $file->seek($reflection->getStartLine() - 1);
    $code = '';
    while ($file->key() < $reflection->getEndLine()) {
        $code .= $file->current();
        $file->next();
    }
    $begin = strpos($code, 'function');
    $end = strrpos($code, '}');
    $code = substr($code, $begin, $end - $begin + 1);
    return $code;
}

/**
 * 取跟踪记录中的真实调用者
 *
 * @param array $traceList
 * @param array $filters 二维数组, 下面的例子是二维里的一维值
 * - ['', 'onShutdownDebug'], //空文件来自php自己触发
 * - ['/index.php',  'execute'], //[文件名(含有.点号),方法名]
 * - ['dump',  'dumpResult'], //[类名,方法名]
 * - ['/MVC.php', 388],  //文件, 行号(整型数字)
 * - 'ErrorClass.php',  //整个文件过滤(含有.点号)
 * - 'dump',  //整类过滤
 * @return array
 */
function getTraceCaller(array &$traceList, array $filters = []): array|null
{
    $caller = null;
    if (empty($filters)) {
        $caller  = array_shift($traceList);
    } else {
        $newTraceList = [];
        foreach ($traceList as $k => $trace) {
            $isFilter = false;
            if (!isset($trace['line']) || $trace['line'] == '') {
                $isFilter = true; //定位不到文件与文件行的
            }
            $trace = array_merge(['file' => '', 'line' => '', 'class' => '', 'type' => '',
                                  'function' => '', 'object' => '', 'args' => ''], $trace);
            if (IS_WIN) {
                $trace['file'] = str_replace('\\', '/', $trace['file']);
            }
            //过滤哪些类和方法
            if (!$isFilter) {
                foreach ($filters as $filter) {
                    if (is_string($filter)) {
                        $filter = [$filter];
                    }
                    if (isset($filter[1])) {
                        if (
                            (strpos($filter[0], '.') > 0 || $filter[0] == '')
                            && strpos($trace['file'], $filter[0]) !== false
                            && $trace['function'] == $filter[1]
                        ) {
                            $isFilter = true; //文件中的方法, 或者空文件中的系统触发的方法
                            break;
                        } elseif (is_int($filter[1]) && $trace['file'] == $filter[0] && $trace['line'] == $filter[1]) {
                            $isFilter = true; //文件 + 行号
                            break;
                        } elseif ($trace['class'] == $filter[0] && $trace['function'] == $filter[1]) {
                            $isFilter = true; //类名 + 方法
                            break;
                        }
                    } elseif (strpos($filter[0], '.') > 0 && (strpos($trace['file'], $filter[0]) !== false)) {
                        $isFilter = true; //纯文件
                        break;
                    } elseif ($trace['class'] == $filter[0]) {
                        $isFilter = true; //纯类名
                        break;
                    }
                }
            }
            if ($isFilter) {
                continue;
            } else {
                if (is_null($caller)) {
                    $caller = $trace;
                }
                $newTraceList[] = $trace;
            }
        }
    }
    $traceList = $newTraceList;
    return $caller;
}

/**
 * 写错误日志
 *
 * 日志将保存到 config/站点.php['dir']['log'] 配置的目录下的 error下，
 * 日志文件按日分隔
 *
 * @param string|array|object $msg 错误内容,
 * @param string $type 类型,如 program 或 database
 * @param string|array $alone 单独日志, "Y-m-d" 时间格式 或者 ['Y-m','Y-m-d'] 第一个为目录，第二个为文件名
 * @return string 日志文件名
 */
function tolog(string|array|object $msg, string $type, string|array $alone = ''): string
{
    $msg = json_encode($msg, JSON_UNESCAPED_UNICODE);
    $message = sprintf("[%s] %s\t.\r\n", date("Y-m-d H:i:s"), $msg);
    if (!is_dir(DIR_LOG)) {
        mkdir(DIR_LOG, 0777);
    }
    if (empty($alone)) {
        $logPath = DIR_LOG . '/' . date("Y-m");
        if (!is_dir($logPath)) {
            mkdir($logPath, 0777);
        }
        $logFile = $logPath . "/" . $type . "-" . date("Y-m-d") . ".log";
    } else {
        $logPath = DIR_LOG . '/' . $type;
        if (!is_dir($logPath)) {
            mkdir($logPath, 777);
        }
        if (is_array($alone)) {
            $logPath = $logPath . '/' . date($alone[0]);
            if (!is_dir($logPath)) {
                mkdir($logPath, 0777);
            }
            $logFile = $logPath . "/" . date($alone[1]) . ".log";
        } else {
            $logFile = $logPath . "/" . date($alone) . ".log";
        }
    }
    error_log($message, 3, $logFile);
    return $logFile;
}
