<?php

namespace xmvc;

/**
* 数据库类
*
* @version 2.0.0
*/

class DB extends PdoHelper
{
    /**当前 Sqler*/
    protected Sqler|null $currentSqler = null;

    /**
     * 构造
     *
     * @param string  $key 配置项中的database.php <key>
     */
    public function __construct(string $cfgKey = 'main')
    {
        if (!isset(MVC::$cfg['database'])) {
            MVC::$cfg['database'] = loadConfig('database');
        }
        if ($cfgKey == 'main') {
            $cfgKey = MVC::$cfg['database']['main'];
        }
        if (!isset(MVC::$cfg['database'][$cfgKey])) {
            error('未定义的数据库配置 ' . $cfgKey);
        }
        parent::__construct($cfgKey, MVC::$cfg['database'][$cfgKey]);
    }

    /**
     * 创建一个 Sqler 对象
     *
     * @return Sqler
     */
    public function newSqler(): Sqler
    {
        $this->currentSqler = new Sqler($this);
        return $this->currentSqler;
    }

    /**
     * 返回当前 Sqler 对象
     *
     * @return Sqler|null
     */
    public function getSqler(): Sqler|null
    {
        return $this->currentSqler;
    }

    /**
     * 改变当前 currentSqler 的 Sqler 对象
     *
     * @return self
     */
    public function changeSqler(Sqler $helper = null): self
    {
        if (!is_null($helper)) {
            $this->currentSqler = $helper;
        } elseif (!isset($this->currentSqler)) {
            error('db->currentSqler 为 null, 无法构建 SQL 语句');
        }
        return $this;
    }

    /**
     * 事务 Transaction
     *
     * @param Closure $func,  function (Sqler $q, DB $db);
     * - $q = 当前调用的 db 类->createSqler()
     * - $db = 当前调用的 DB 类
     * @return mixed $func() 返回的结果, 如果返回 false 则主动回滚事务
     */
    public function transaction(\Closure $func): mixed
    {
        $this->begin();
        $result = $func($this->newSqler(), $this);
        if ($result === false) {
            $this->rollBack();
        } else {
            $this->end();
        }
        return $result;
    }

    /**
     * 绑定查询构建器 Sqler
     *
     * - 绑定查询Helper并执行查询语句,准备好 statement 供 all,row,one 使用。
     * @param Sqler $helper 默认为null, 为当前 currentSqler
     * @return self
     */
    public function select(Sqler $helper = null): self
    {
        $this->changeSqler($helper);
        $this->query($this->currentSqler->getSelectSQL(), $this->currentSqler->getParams());
        return $this;
    }

    /**
     * 取查询结果所有行
     *
     * @param bool $sameName 是否有相同的字段名称, 默认 false, 如果为 true 则会自动给相同名称加 _num 返回
     * @return array
     */
    public function all(bool $sameName = false): array
    {
        $rows = parent::all($sameName);
        if (!is_null($this->currentSqler) && !is_null($this->currentSqler->mapFetchKeyValue)) {
            $data = [];
            if (!empty($rows)) {
                list($keyField, $valueField, $func) = $this->currentSqler->mapFetchKeyValue;
                $data = map($rows, $keyField, $valueField, $func);
            }
            return $data;
        } else {
            return $rows;
        }
    }

    public function has(Sqler $helper = null): bool
    {
        $this->changeSqler($helper);
        return !empty($this->query($this->currentSqler->getHasSQL(), $this->currentSqler->getParams())->one());
    }

    public function count(Sqler $helper = null): int
    {
        $this->changeSqler($helper);
        return $this->query($this->currentSqler->getCountSQL(), $this->currentSqler->getParams())->one();
    }


    /**
     * 插入或指插入
     * - 当 Sqler 有调用 push() 方法时为批量插入
     * @param Sqler $helper 如果为空则默认当前(也就是最近一次操作的) Sqler
     * @param int $chunkQty array_chunk 按多少条数来分割
     * @return int 返回受影响的行数
     */
    public function insert(Sqler $helper = null, int $chunkQty = 200): int
    {
        $this->changeSqler($helper);
        if ($this->currentSqler->hasLots()) {
            return $this->insertLots($this->currentSqler, $chunkQty);  //批量
        } else {
            return $this->exec($this->currentSqler->getInsertSQL(), $this->currentSqler->getParams());
        }
    }

    /**
     * 批量插入
     *
     * @param int $chunkQty array_chunk 按多少条数来分割
     * @return int 返回插入的记录数量
     */
    public function insertLots(Sqler $helper = null, int $chunkQty = 200): int
    {
        $this->changeSqler($helper);
        $sqlArr = $this->currentSqler->getInsertLotsSQL($chunkQty);
        $num = 0;
        if ($sqlArr) {
            foreach ($sqlArr as $sql) {
                $num += $this->exec($sql, $this->currentSqler->getParams());
            }
        }
        return $num;
    }

    /**
     * 原生替换或插入语句(原生 REPLACE INTO), 返回受影响的行数
     */
    public function replaceInto(Sqler $helper = null): int
    {
        $this->changeSqler($helper);
        return $this->exec($this->currentSqler->getReplaceIntoSQL(), $this->currentSqler->getParams());
    }

    /**
     * 检测更新或插入语句, 没有找到则新增, 返回受影响的行数
     */
    public function replaceCheck(Sqler $helper = null): int
    {
        $this->changeSqler($helper);
        if (!$this->currentSqler->hasWhere()) {
            throw new \Exception("构建SQL失败,缺失条件语句，须调用 Sqler->where() 方法");
        }
        if ($this->has($this->currentSqler)) {
            return $this->exec($this->currentSqler->getUpdateSQL(), $this->currentSqler->getParams());
        } else {
            return $this->exec($this->currentSqler->getInsertSQL(), $this->currentSqler->getParams());
        }
    }

    /**
     * 更新 返回受影响的行数
     */
    public function update(Sqler $helper = null): int
    {
        $this->changeSqler($helper);
        return $this->exec($this->currentSqler->getUpdateSQL(), $this->currentSqler->getParams());
    }

    /**
     * 删除 返回受影响的行数
     */
    public function delete(Sqler $helper = null): int
    {
        $this->changeSqler($helper);
        return $this->exec($this->currentSqler->getDeleteSQL(), $this->currentSqler->getParams());
    }

    /**
     * 清空 返回受影响的行数
     */
    public function truncate(Sqler $helper = null): int
    {
        $this->changeSqler($helper);
        return $this->exec($this->currentSqler->getTruncateSQL(), $this->currentSqler->getParams());
    }
}
