<?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;

use think\admin\extend\VirtualModel;
use think\App;
use think\Container;
use think\db\BaseQuery;
use think\db\Mongo;
use think\db\Query;
use think\Model;

/**
 * 控制器助手.
 * @class Helper
 */
abstract class Helper
{
    /**
     * 应用容器.
     * @var App
     */
    public $app;

    /**
     * 控制器实例.
     * @var Controller
     */
    public $class;

    /**
     * 当前请求方式.
     * @var string
     */
    public $method;

    /**
     * 自定输出格式.
     * @var string
     */
    public $output;

    /**
     * Helper constructor.
     */
    public function __construct(App $app, Controller $class)
    {
        $this->app = $app;
        $this->class = $class;
        // 计算指定输出格式
        $output = $app->request->request('output', 'default');
        $method = $app->request->method() ?: ($app->runningInConsole() ? 'cli' : 'nil');
        $this->output = strtolower("{$method}.{$output}");
    }

    /**
     * 实例对象反射.
     * @param array $args
     * @return static
     */
    public static function instance(...$args): Helper
    {
        return Container::getInstance()->invokeClass(static::class, $args);
    }

    /**
     * 获取数据库查询对象
     * @param BaseQuery|Model|string $query
     * @return BaseQuery|Mongo|Query
     */
    public static function buildQuery($query)
    {
        if (is_string($query)) {
            if (self::isSubquery($query)) {
                $query = Library::$sapp->db->table($query);
            } else {
                return self::triggerBeforeEvent(static::buildModel($query)->db());
            }
        }
        if ($query instanceof Model) {
            return self::triggerBeforeEvent($query->db());
        }
        if ($query instanceof BaseQuery && !$query->getModel()) {
            // 如果是子查询,不需要挂载模型对象
            if (!self::isSubquery($query->getTable())) {
                $name = $query->getConfig('name') ?: '';
                if (is_string($name) && strlen($name) > 0) {
                    $name = config("database.connections.{$name}") ? $name : '';
                }
                $query->model(static::buildModel($query->getName(), [], $name));
            }
        }
        return self::triggerBeforeEvent($query);
    }

    /**
     * 动态创建模型对象
     * @param mixed $name 模型名称
     * @param array $data 初始数据
     * @param mixed $conn 指定连接
     */
    public static function buildModel(string $name, array $data = [], string $conn = ''): Model
    {
        if (strpos($name, '\\') !== false) {
            if (class_exists($name)) {
                $model = new $name($data);
                if ($model instanceof Model) {
                    return $model;
                }
            }
            $name = basename(str_replace('\\', '/', $name));
        }
        return VirtualModel::mk($name, $data, $conn);
    }

    /**
     * 触发查询对象执行前事件.
     * @param BaseQuery|mixed|Model $query
     * @return BaseQuery|mixed|Model
     */
    private static function triggerBeforeEvent($query)
    {
        Library::$sapp->db->trigger('think_before_event', $query);
        return $query;
    }

    /**
     * 判断是否为子查询.
     */
    private static function isSubquery(string $sql): bool
    {
        return preg_match('/^\(?\s*select\s+/i', $sql) > 0;
    }
}