<?php

namespace app\common\exception;

use DateTime;
use think\facade\Request;
use think\facade\Log;
use think\helper\Arr;
use think\facade\Db;
use app\common\arw\adjfut\src\Exception\ErrorMsg;

class Tool
{
    /**
     * 生成配置项
     *
     * @param array $config
     * @param callable|string $cb 自定义合并策略
     * @return callable
     * @date 2022-07-29
     * @example
     * @author arw
     * @since 1.0.0
     */
    public static function generateConfig(array $config, $cb = 'array_merge'): callable
    {
        return function (...$argv) use ($cb, $config) {
            array_unshift($argv, $config);
            return call_user_func_array($cb, $argv);
        };
    }

    /**
     * 构建url
     *
     * @param string $url
     * @param array $query
     * @param boolean $decode
     * @return string
     * @date 2023-01-04
     * @example
     * @author arw
     * @since 1.0.0
     */
    public static function buildUrl(string $url, array $query = [], bool $decode = true): string
    {
        $parse = parse_url($url);
        $parse_query = Arr::get($parse, 'query', '');
        $_query = [];
        parse_str($parse_query, $_query);
        if (!is_array($_query)) {
            $_query = [];
        }
        $_query = array_merge($_query, $query);
        $_url = str_replace('?' . $parse_query, '', $url);
        $_url .= '?';
        $_url .= http_build_query($_query);
        if ($decode) {
            $_url = urldecode($_url);
        }
        return $_url;
    }

    /**
     * 写入通道日志
     *
     * @param string $channel
     * @param string $type
     * @param array $data
     * @return void
     * @date 2022-07-29
     * @example
     * @author arw
     * @since 1.0.0
     */
    public static function writeChannelLog(string $channel, string $type, array $data): void
    {
        Log::channel($channel)->$type(json_encode($data, JSON_UNESCAPED_UNICODE));
    }

    /**
     * 字段映射
     *
     * @param array $data
     * @param array $map
     * @param array $options
     * @return array
     * @date 2022-06-23
     * @example
     * @author arw
     * @since 1.0.0
     */
    public static function fieldMap(array $data, array $map, array $options = []): array
    {
        $prefix = Arr::get($options, 'prefix', '');
        $result = [];
        foreach ($data as $key => $value) {
            if (isset($map[$key])) {
                $key = $map[$key];
            }
            if ($prefix) {
                $key = $prefix . $key;
            }
            $result[$key] = $value;
        }
        return $result;
    }

    /**
     * 转换日期格式
     *
     * @param string $datetime
     * @param string $format
     * @return string
     * @date 2022-04-20
     * @example
     * @author arw
     * @since 1.0.0
     */
    public static function conversionDateTime($datetime, string $format): string
    {
        try {
            $date = new DateTime($datetime);
            return $date->format($format);
        } catch (\Throwable $th) {
            throw new ErrorMsg('日期格式非法', 1);
        }
    }

    /**
     * 生成唯一id
     *
     * @param string|array $value
     * @return string
     * @date 2022-03-24
     * @example
     * @author arw
     * @since 1.0.0
     */
    public static function generateGuid($value = ''): string
    {
        $str = '';
        if (is_array($value)) {
            $str = json_encode($value);
        }
        $charid = strtolower(md5($str . uniqid('', true)));
        $hyphen = chr(45); // '-'
        $uuid = //chr(123)// '{'
            substr($charid, 0, 8) . $hyphen
            . substr($charid, 8, 4) . $hyphen
            . substr($charid, 12, 4) . $hyphen
            . substr($charid, 16, 4) . $hyphen
            . substr($charid, 20, 12);
        //.chr(125);// '}'
        return $uuid;
    }


    /**
     * 清空（擦除）缓冲区并关闭输出缓冲
     *
     * @return void
     */
    public static function obEndClean(): void
    {
        try {
            while (ob_get_level() > 0) {
                ob_end_clean();
            }
        } catch (\Throwable $th) {
            //throw $th;
        }
    }

    /**
     * 自动创建文件夹
     *
     * @param string $path 文件夹路径
     * @param integer $mode 权限
     * @return boolean
     */
    public static function mkdir(string $path, int $mode = 0755): bool
    {
        $path = str_replace('/', DIRECTORY_SEPARATOR, $path);
        $temp = explode(DIRECTORY_SEPARATOR, $path);
        if (preg_match("/\./", end($temp))) {
            // pathinfo 不支持中文
            // $path = pathinfo($path, PATHINFO_DIRNAME);
            array_splice($temp, count($temp) - 1, 1);
            $path = join(DIRECTORY_SEPARATOR, $temp);
        }
        if (!file_exists($path)) {
            mkdir($path, $mode, true);
        }
        chmod($path, $mode);
        return true;
    }

    /**
     * 下划线转驼峰
     * @param string $uncamelized_words 下划线单词
     * @param bool $size true:大驼峰 false:小驼峰 默认为false
     */
    public static function camelize(string $uncamelized_words, bool $size = false): string
    {
        //下划线替换为空格
        $uncamelized_words = str_replace('_', ' ', $uncamelized_words);

        if ($size) {
            //大驼峰: 1:进行单词首字母大写转换 2:将空格替换为空
            return str_replace(" ", "", ucwords($uncamelized_words));
        } else {
            //小驼峰: 1:下划线替换为空格,并在单词首部添加下划线(防止被首单词首字母后续被转为大写)
            //        2:进行单词首字母大写转换
            //        3:将空格替换为空
            //        4:将首单词多余的下划线去除
            $uncamelized_words = '_' . $uncamelized_words;
            return ltrim(str_replace(" ", "", ucwords($uncamelized_words)), '_');
        }
    }

    /**
     * 驼峰命名转下划线命名
     * @param string $uncamelize 驼峰单词
     */
    public static function uncamelize($camelCaps): string
    {
        return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . '_' . "$2", $camelCaps));
    }

    /**
     * 获取excel验证数组
     * 
     * @author xjh
     * @param array $field_data 字段数组 ['user_name'=>'张三']
     */
    public static function getExcelRule(array $field_data): array
    {
        $data = [];
        foreach ($field_data as $key => $val) {
            $data2 = ['title' => $val, 'field' => $key];
            if (stripos($val, '*') !== false) $data2['validate'] = 'require';
            $data[] = $data2;
        }
        return $data;
    }

    /**
     * 获取选填查询条件数组
     * @author xjh
     */
    public static function getOptionalQuery(array ...$data)
    {
        $params = [];
        $con = [];

        //请求参数获取
        if (isset($data[0]['params']) && $data[0]['params']) {
            //自定义
            $params = $data[0]['params'];
            unset($data[0]);
            $data = array_merge($data);
        } else {
            //自动获取
            $params = Request::param();
        }

        //处理联表字段
        $dealJoinField = function (string $field_name): string {
            $stripos_val = stripos($field_name, '.');
            return $stripos_val !== false ? substr($field_name, $stripos_val + 1) : $field_name;
        };

        //获取查询条件数组
        $getQueryData = function (string $field_name, string $op, $value) {
            switch (strtoupper($op)) {
                case 'LIKE':
                    return [$field_name, 'LIKE', '%' . $value . '%'];
                case 'BETWEEN':
                    return [$field_name, 'BETWEEN', implode(',', $value)];
                case 'IN':
                    return [$field_name, 'IN', $value];
                case 'NULL':
                    return [$field_name, 'NULL', null];
                default:
                    return [$field_name, $op, $value];
            }
        };

        //批量混合查询数组构造
        foreach ($data as $key => $val) {
            switch (count($val)) {
                case 1:
                    if (!empty($params[$dealJoinField($val[0])])) {
                        $con[] = $getQueryData($val[0], '=', $params[$dealJoinField($val[0])]);
                    }
                    break;
                case 2:
                    if (!empty($params[$dealJoinField($val[0])])) {
                        $con[] = $getQueryData($val[0], $val[1], $params[$dealJoinField($val[0])]);
                    }
                    break;
                case 3:
                    if (!empty($params[$dealJoinField($val[2])])) {
                        $con[] = $getQueryData($val[0], $val[1], $params[$val[2]]);
                    }
                    break;
            }
        }
        return $con;
    }

    /**
     * 上下个数据返回
     *
     * @author xjh
     * @param string $model 模型层命名空间地址
     * @param int|array $sort_field_info 排序字段信息 例:当前排序值(排序字段默认走模型层定义好的) | [排序字段字段名,当前排序值]
     * @param array $extends 扩展
     * @param array $extends["field"] 数据结果集标识要返回的字段(默认全部字段返回)
     * @param string $extends["type"] 返回类型 all(默认):[上个数据,下个数据] | last:上个数据 | next:下个数据
     * @param array $extends["extraWhere"] 额外查询条件（批量混合查询格式: [ ['user_name','=','name'],... ]）
     * @return string|array
     */
    public static function getLastNextData(string $model, $sort_field_info, array $extends =[])
    {
        //模型对象初始化
        $model = new $model();

        //排序字段信息初始化
        $order_field_name = null;
        $order_field_val = null;
        if (is_array($sort_field_info)) {
            $order_field_name = $sort_field_info[0];
            $order_field_val = $sort_field_info[1];
        } else  if (is_int($sort_field_info)) {
            if (!isset($model->order_field)) {
                throwErrorMsg(__METHOD__ . "若排序字段信息为字符串，则必须要在模型层定义排序字段");
            }
            $order_field_name = $model->order_field;
            $order_field_val = $sort_field_info;
        }

        //非必传参数初始化
        $extra_where = isset($extends['extraWhere']) ? $extends['extraWhere'] : [];
        $field = isset($extends['field']) ? $extends['field'] : null;
        $type = isset($extends['type']) ? $extends['type'] : 'all';


        //闭包查询函数
        $query = function ($op) use ($model, $order_field_name, $order_field_val, $field, $extra_where) {
            $order = $op == '<' ? 'desc' : 'asc';
            if ($field) $model = $model->field($field);
            return $model->where($order_field_name, $op, $order_field_val)
                ->where($extra_where)
                ->order($order_field_name, $order)
                ->find();
        };

        //获取指定类型数据
        switch ($type) {
            case 'all':
                return [$query('<'), $query('>')];
            case 'last':
                return $query('<');
            case 'next':
                return $query('>');
            default:
                throwErrorMsg('Tool::getLastNextId() : type无此类型', 444);
        }
    }

    /**
     * 排序号更换处理
     * 
     * 注意：使用该方法时候要启动事务!!
     * @param string $model 模型层命名空间地址
     * @param string $guid 主键
     * @param int $order 新排序号
     * @param array $extra_wheres 关联类型 例:["xxx_type" => 1]
     */
    public static function sortEditProc(string $model, string $guid, int $order, array $extra_wheres = []): void
    {
        //模型层实例化
        $model = new $model;
        //表名
        $table_name = $model->db()->getTable();
        //获取当前的主键字段名
        $guld_field = $model->db()->getPk();
        //排序字段名
        if (!isset($model->order_field)) throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field');
        $order_field_name = $model->order_field;
        //当前数据原信息
        $original_oreder_find = $model->where($guld_field, $guid)->find();
        if (!$original_oreder_find) throwErrorMsg('Tool::sortEditProc() : 找不到该数据原排序号', 444);
        //查找当前数据所想更换的新排序号是否已有数据占用(排除自己)
        //已被占用，则将它们的排序号互换
        if ($model->where($order_field_name, $order)->where($guld_field, '<>', $guid)->where($extra_wheres)->find()) {
            $update_data = [$order_field_name => $original_oreder_find[$order_field_name]];

            //检查是否有父祖级主键，有则也加入互换
            if (isset($model->parent_guid_field) && isset($model->ancestors_guid_field)) {
                $update_data[$model->parent_guid_field] = $original_oreder_find[$model->parent_guid_field];
                $update_data[$model->ancestors_guid_field] = $original_oreder_find[$model->ancestors_guid_field];
            }

            //关联类型也加入互换
            foreach ($extra_wheres as $key => $value) {
                $update_data[$key] = $original_oreder_find[$key];
            };
            Db::name($table_name)->where($order_field_name, $order)->where($extra_wheres)->update($update_data);
        }
    }

    /**
     * 排序号腾位处理
     * 
     * 注意：使用该方法时候要启动事务!!
     * @param string $model 模型层命名空间地址
     * @param int $order 当前排序号
     * @param array $wheres  当前指定数据的额外查询条件(tp批量查询数组) 例:["xxx_type" => 1,...]
     */
    public static function sortInsertProc(string $model, int $order, array $wheres = []): void
    {
        //模型层实例化
        $model = new $model;
        //表名
        $table_name = $model->db()->getTable();
        //排序字段名
        if (!isset($model->order_field)) throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field');
        $order_field_name = $model->order_field;
        //新增数据的所属排序号已有数据占用，则腾位处理(已占用的数据及其后面所有数据一起排序号腾位+1)
        if ($model->where($order_field_name, $order)->where($wheres)->value($order_field_name) !== null) {
            Db::name($table_name)
                ->where($wheres)
                ->where($order_field_name, '>=', $order)
                ->inc($order_field_name)
                ->update();
        }
    }

    /**
     * 排序号删除处理
     * 注意：使用该方法时候要启动事务!!
     * @param string $model 模型层命名空间地址
     * @param array $guid 主键
     * @param array $correl_fields 关联类型字段 例:["xxx_guid",...] 代表删除排序号时会根据这些字段来关联(例:所属xxx类型、所属xxx系列)来进行排序处理
     */
    public static function sortDeleteProc(string $model, string $guid, array $correl_fields = []): void
    {
        //模型层实例化
        $model = new $model;
        //获取当前的主键字段名
        $guld_field_name = $model->db()->getPk();
        //获取当前表排序字段名
        if (!isset($model->order_field)) throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field');
        $order_field_name = $model->order_field;
        //在所删除的数据之后的数据所属排序号将进行减位处理减位-1
        if ($find = $model->where($guld_field_name, $guid)->find()) {
            $con = [];
            foreach ($correl_fields as $correl_field) {
                $con[$correl_field] = $find[$correl_field];
            }
            $model->where($order_field_name, '>', $find[$order_field_name])
                ->where($con)
                ->dec($order_field_name)
                ->save();
        }
    }

    /**
     * 处理(树形数据父子)伦理关系
     * 
     * @param \think\Model $model 模型层对象
     */
    public static function handleEthicalRel(\think\Model $model): void
    {
        if (!isset($model->parent_guid_field) || !isset($model->ancestors_guid_field)) {
            throwErrorMsg(__METHOD__
                . "方法:"
                . get_class($model)
                . "模型层必须定义public \$parent_guid_field,public \$works_type_ancestors_guid");
        }
        //获取当前的主键字段名
        $guld_field = $model->db()->getPk();
        //当前父级主键字段名
        $parent_guid_field = $model->parent_guid_field;
        //当前祖级级主键字段名
        $ancestors_guid_field = $model->ancestors_guid_field;
        //处理一 
        if ($model[$guld_field] == $model[$parent_guid_field]) {
            throwErrorMsg("不可以当自己的子级!");
        }
        //处理二 
        $is_children =    $model->where([
            [$guld_field, '=', $model[$parent_guid_field]],
            [$ancestors_guid_field, 'REGEXP', $model[$guld_field]],
        ])->find();
        if ($is_children) {
            throwErrorMsg("不可以当自己孩子们的子级!");
        }
    }

    /**
     * 祖级guid构建
     * @param \think\Model $model 模型层命名空间地址
     * @param bool $is_unipolar 是否为单极结构
     */
    public static function buildAncestorsGuid(\think\Model &$model, bool $is_unipolar = false): void
    {
        //获取最大父级guid
        $first_parent_guid = isset($model->first_parent_guid) ? $model->first_parent_guid : "0";

        //获取当前的主键字段名
        $guld_field_name = $model->db()->getPk();
        //获取当前父级主键集字段名
        $parent_guid_field = $model->parent_guid_field;
        $parent_guid = $model[$parent_guid_field];
        //获取当前祖级主键集字段名
        $ancestors_guid_field = $model->ancestors_guid_field;

        //单极结构或父级guid已经为最大父级guid时直接返回最大父级guid
        if ($is_unipolar || $parent_guid == $first_parent_guid) {
            $model[$ancestors_guid_field] = $first_parent_guid;
            return;
        }

        //开始构建祖级guid
        $parent = $model->where($guld_field_name, $parent_guid)->find();
        if (!$parent) throwErrorMsg('该父级数据不存在!');
        $model[$ancestors_guid_field] =  $parent[$ancestors_guid_field] . ',' . $parent_guid;
    }

    /**
     * 初始化模型字段值
     * 
     * @param \think\Model &$model 模型层对象(引用传递)
     * @param array $field_values 初始化字段信息 例: ['user_name'=>'张三',...]
     */
    public static function initModelFieldValue(\think\Model &$model, array $field_values): void
    {
        foreach ($field_values as $field => $value) {
            $model[$field] = $value;
        }
    }

    /**
     * 字符串替换优化版
     * 
     * @param string $subject 执行替换字符串
     * @param array $search_and_replace 替换信息 例:[替换目标=>替换值,替换目标=>替换值,...]
     * @return string 该函数返回替换后的字符串。
     */
    public static function strReplacePlus(string $subject, array $search_and_replace): string
    {
        $search = [];
        $replace = [];
        foreach ($search_and_replace as $key => $val) {
            $search[] = $key;
            $replace[] = $val;
        };
        return str_replace($search, $replace, $subject);
    }
}
