<?php

declare(strict_types=1);
/**
 * +----------------------------------------------------------------------
 * | ThinkAdmin Plugin for ThinkAdmin
 * +----------------------------------------------------------------------
 * | 版权所有 2014~2026 ThinkAdmin [ thinkadmin.top ]
 * +----------------------------------------------------------------------
 * | 官方网站: https://thinkadmin.top
 * +----------------------------------------------------------------------
 * | 开源协议 ( https://mit-license.org )
 * | 免责声明 ( https://thinkadmin.top/disclaimer )
 * | 会员特权 ( https://thinkadmin.top/vip-introduce )
 * +----------------------------------------------------------------------
 * | gitee 代码仓库:https://gitee.com/zoujingli/ThinkAdmin
 * | github 代码仓库:https://github.com/zoujingli/ThinkAdmin
 * +----------------------------------------------------------------------
 */

namespace think\admin\helper;

use think\admin\Helper;
use think\admin\service\SystemService;
use think\Container;
use think\db\BaseQuery;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use think\db\Query;
use think\Model;

/**
 * 搜索条件处理器.
 * @see Query
 * @mixin \think\db\Query
 * @class QueryHelper
 * @method bool mSave(array $data = [], string $field = '', mixed $where = []) 快捷更新
 * @method bool mDelete(string $field = '', mixed $where = []) 快捷删除
 * @method array|bool mForm(string $tpl = '', string $field = '', mixed $where = [], array $data = []) 快捷表单
 * @method bool|int mUpdate(array $data = [], string $field = '', mixed $where = []) 快捷保存
 */
class QueryHelper extends Helper
{
    /**
     * 分页助手工具.
     * @var PageHelper
     */
    protected $page;

    /**
     * 当前数据操作.
     * @var Query
     */
    protected $query;

    /**
     * 初始化默认数据.
     * @var array
     */
    protected $input;

    /**
     * 克隆属性复制.
     */
    public function __clone()
    {
        $this->page = clone $this->page;
        $this->query = clone $this->query;
    }

    /**
     * QueryHelper call.
     * @param string $name 调用方法名称
     * @param array $args 调用参数内容
     * @return $this|mixed
     */
    public function __call(string $name, array $args)
    {
        return static::make($this->query, $name, $args, function ($name, $args) {
            if (is_callable($callable = [$this->query, $name])) {
                $value = call_user_func_array($callable, $args);
                if ($name[0] === '_' || $value instanceof $this->query) {
                    return $this;
                }
                return $value;
            }
            return $this;
        });
    }

    /**
     * 获取当前Db操作对象
     */
    public function db(): Query
    {
        return $this->query;
    }

    /**
     * 逻辑器初始化.
     * @param BaseQuery|Model|string $dbQuery
     * @param null|array|string $input 输入数据
     * @param null|callable $callable 初始回调
     * @return $this
     */
    public function init($dbQuery, $input = null, ?callable $callable = null): QueryHelper
    {
        $this->page = PageHelper::instance();
        $this->input = $this->getInputData($input);
        $this->query = $this->page->autoSortQuery($dbQuery);
        is_callable($callable) && call_user_func($callable, $this, $this->query);
        return $this;
    }

    /**
     * 设置 Like 查询条件.
     * @param array|string $fields 查询字段
     * @param string $split 前后分割符
     * @param null|array|string $input 输入数据
     * @param string $alias 别名分割符
     * @return $this
     */
    public function like($fields, string $split = '', $input = null, string $alias = '#'): QueryHelper
    {
        $data = $this->getInputData($input ?: $this->input);
        foreach (is_array($fields) ? $fields : explode(',', $fields) as $field) {
            [$dk, $qk] = [$field, $field];
            if (stripos($field, $alias) !== false) {
                [$dk, $qk] = explode($alias, $field);
            }
            if (isset($data[$qk]) && $data[$qk] !== '') {
                $this->query->whereLike($dk, "%{$split}{$data[$qk]}{$split}%");
            }
        }
        return $this;
    }

    /**
     * 设置 Equal 查询条件.
     * @param array|string $fields 查询字段
     * @param null|array|string $input 输入类型
     * @param string $alias 别名分割符
     * @return $this
     */
    public function equal($fields, $input = null, string $alias = '#'): QueryHelper
    {
        $data = $this->getInputData($input ?: $this->input);
        foreach (is_array($fields) ? $fields : explode(',', $fields) as $field) {
            [$dk, $qk] = [$field, $field];
            if (stripos($field, $alias) !== false) {
                [$dk, $qk] = explode($alias, $field);
            }
            if (isset($data[$qk]) && $data[$qk] !== '') {
                $this->query->where($dk, strval($data[$qk]));
            }
        }
        return $this;
    }

    /**
     * 设置 IN 区间查询.
     * @param array|string $fields 查询字段
     * @param string $split 输入分隔符
     * @param null|array|string $input 输入数据
     * @param string $alias 别名分割符
     * @return $this
     */
    public function in($fields, string $split = ',', $input = null, string $alias = '#'): QueryHelper
    {
        $data = $this->getInputData($input ?: $this->input);
        foreach (is_array($fields) ? $fields : explode(',', $fields) as $field) {
            [$dk, $qk] = [$field, $field];
            if (stripos($field, $alias) !== false) {
                [$dk, $qk] = explode($alias, $field);
            }
            if (isset($data[$qk]) && $data[$qk] !== '') {
                $this->query->whereIn($dk, explode($split, strval($data[$qk])));
            }
        }
        return $this;
    }

    /**
     * 两字段范围查询.
     * @example field1:field2#field,field11:field22#field00
     * @param array|string $fields 查询字段
     * @param null|array|string $input 输入数据
     * @param string $alias 别名分割符
     * @return $this
     */
    public function valueRange($fields, $input = null, string $alias = '#'): QueryHelper
    {
        $data = $this->getInputData($input ?: $this->input);
        foreach (is_array($fields) ? $fields : explode(',', $fields) as $field) {
            if (strpos($field, ':') !== false) {
                if (stripos($field, $alias) !== false) {
                    [$dk0, $qk0] = explode($alias, $field);
                    [$dk1, $dk2] = explode(':', $dk0);
                } else {
                    [$qk0] = [$dk1, $dk2] = explode(':', $field, 2);
                }
                if (isset($data[$qk0]) && $data[$qk0] !== '') {
                    $this->query->where([[$dk1, '<=', $data[$qk0]], [$dk2, '>=', $data[$qk0]]]);
                }
            }
        }
        return $this;
    }

    /**
     * 设置内容区间查询.
     * @param array|string $fields 查询字段
     * @param string $split 输入分隔符
     * @param null|array|string $input 输入数据
     * @param string $alias 别名分割符
     * @return $this
     */
    public function valueBetween($fields, string $split = ' ', $input = null, string $alias = '#'): QueryHelper
    {
        return $this->setBetweenWhere($fields, $split, $input, $alias);
    }

    /**
     * 设置日期时间区间查询.
     * @param array|string $fields 查询字段
     * @param string $split 输入分隔符
     * @param null|array|string $input 输入数据
     * @param string $alias 别名分割符
     * @return $this
     */
    public function dateBetween($fields, string $split = ' - ', $input = null, string $alias = '#'): QueryHelper
    {
        return $this->setBetweenWhere($fields, $split, $input, $alias, static function ($value, $type) {
            if (preg_match('#^\d{4}(-\d\d){2}\s+\d\d(:\d\d){2}$#', $value)) {
                return $value;
            }
            return $type === 'after' ? "{$value} 23:59:59" : "{$value} 00:00:00";
        });
    }

    /**
     * 设置时间戳区间查询.
     * @param array|string $fields 查询字段
     * @param string $split 输入分隔符
     * @param null|array|string $input 输入数据
     * @param string $alias 别名分割符
     * @return $this
     */
    public function timeBetween($fields, string $split = ' - ', $input = null, string $alias = '#'): QueryHelper
    {
        return $this->setBetweenWhere($fields, $split, $input, $alias, static function ($value, $type) {
            if (preg_match('#^\d{4}(-\d\d){2}\s+\d\d(:\d\d){2}$#', $value)) {
                return strtotime($value);
            }
            return $type === 'after' ? strtotime("{$value} 23:59:59") : strtotime("{$value} 00:00:00");
        });
    }

    /**
     * 实例化分页管理器.
     * @param bool|int $page 是否启用分页
     * @param bool $display 是否渲染模板
     * @param bool|int $total 集合分页记录数
     * @param int $limit 集合每页记录数
     * @param string $template 模板文件名称
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function page($page = true, bool $display = true, $total = false, int $limit = 0, string $template = ''): array
    {
        return $this->page->init($this->query, $page, $display, $total, $limit, $template);
    }

    /**
     * 清空数据并保留表结构.
     * @return $this
     */
    public function empty(): QueryHelper
    {
        $table = $this->query->getTable();
        $ctype = strtolower($this->query->getConfig('type'));
        if ($ctype === 'mysql') {
            $this->query->getConnection()->execute("truncate table `{$table}`");
        } elseif (in_array($ctype, ['sqlsrv', 'oracle', 'pgsql'])) {
            $this->query->getConnection()->execute("truncate table {$table}");
        } else {
            try {
                $this->query->newQuery()->whereRaw('1=1')->delete();
            } catch (\Throwable $exception) {
                trace_file($exception);
            }
        }
        return $this;
    }

    /**
     * 中间回调处理.
     * @return $this
     */
    public function filter(callable $after): QueryHelper
    {
        call_user_func($after, $this, $this->query);
        return $this;
    }

    /**
     * Layui.Table 组件数据.
     * @param ?callable $befor 表单前置操作
     * @param ?callable $after 表单后置操作
     * @param string $template 视图模板文件
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function layTable(?callable $befor = null, ?callable $after = null, string $template = '')
    {
        if (in_array($this->output, ['get.json', 'get.layui.table'])) {
            if (is_callable($after)) {
                call_user_func($after, $this, $this->query);
            }
            $this->page->layTable($this->query, $template);
        } else {
            if (is_callable($befor)) {
                call_user_func($befor, $this, $this->query);
            }
            $this->class->fetch($template);
        }
    }

    /**
     * 快捷助手调用勾子.
     * @param Model|Query|string $model
     * @return false|int|mixed|QueryHelper
     */
    public static function make($model, string $method = 'init', array $args = [], ?callable $nohook = null)
    {
        $hooks = [
            'mForm' => [FormHelper::class, 'init'],
            'mSave' => [SaveHelper::class, 'init'],
            'mQuery' => [QueryHelper::class, 'init'],
            'mDelete' => [DeleteHelper::class, 'init'],
            'mUpdate' => [SystemService::class, 'update'],
        ];
        if (isset($hooks[$method])) {
            [$class, $method] = $hooks[$method];
            return Container::getInstance()->invokeClass($class)->{$method}($model, ...$args);
        }
        return is_callable($nohook) ? $nohook($method, $args) : false;
    }

    /**
     * 设置区域查询条件.
     * @param array|string $fields 查询字段
     * @param string $split 输入分隔符
     * @param null|array|string $input 输入数据
     * @param string $alias 别名分割符
     * @param null|callable $callback 回调函数
     * @return $this
     */
    private function setBetweenWhere($fields, string $split = ' ', $input = null, string $alias = '#', ?callable $callback = null): QueryHelper
    {
        $data = $this->getInputData($input ?: $this->input);
        foreach (is_array($fields) ? $fields : explode(',', $fields) as $field) {
            [$dk, $qk] = [$field, $field];
            if (stripos($field, $alias) !== false) {
                [$dk, $qk] = explode($alias, $field);
            }
            if (isset($data[$qk]) && $data[$qk] !== '') {
                [$begin, $after] = explode($split, strval($data[$qk]));
                if (is_callable($callback)) {
                    $after = call_user_func($callback, $after, 'after');
                    $begin = call_user_func($callback, $begin, 'begin');
                }
                $this->query->whereBetween($dk, [$begin, $after]);
            }
        }
        return $this;
    }

    /**
     * 获取输入数据.
     * @param null|array|string $input
     */
    private function getInputData($input): array
    {
        if (is_array($input)) {
            return $input;
        }
        $input = $input ?: 'request';
        return $this->app->request->{$input}();
    }
}