<?php

namespace xmvc\libDx;

use Closure;
use stdClass;
use xmvc\Cache;
use xmvc\Dqler;
use xmvc\libDx\DxUtils;
use xmvc\libDx\DxSqlExpr;
use xmvc\libDx\DxSqlerExpr;

/**
 * DxSet 类 For xmvc\Sqler
 */
class DxSet
{
    private $groupSettings;
    public stdClass $params;
    public Dqler $dql;
    public $conditions = [];
    /** 条件分析回调
     * - 字段名,操作符,值,递归层级,当前条件组索引
     * - 操作符返回 '' 则废弃当前条件
     * - function(&$fieldName, &$op, &$value)
     * - function(&$fieldName, &$op, &$value, &$deep, &$index)
     */
    public ?Closure $whereCallback = null;
    public ?Closure $groupByCallback = null;
    public ?Closure $orderByCallback = null;

    public function __construct(array|object $params)
    {
        if (is_array($params)) {
            $this->params = DxUtils::formatRequest($params);
        } else {
            $this->params = $params;
        }
        if ($params->take >= DxUtils::MAX_ROW) {
            ini_set('memory_limit', '4096M');
        }
    }

    /**
     * 虚拟字段名转真实字段
     * - bigcate_id 转 bigcate.id AS bigcate_id
     * @param string $vfieldName
     */
    public function virtuallyFieldNameToTrueName(string $vfieldName): string
    {
        if (strpos($vfieldName, $this->dql::VIRTUALLY_FIELD) > 0) {
            list($table, $fieldName) = explode($this->dql::VIRTUALLY_FIELD, $vfieldName);
            $fieldName = $this->dql->attrNameToFieldName($table . '.' . $fieldName);
            if (strpos($fieldName, '.') === false) {
                $fieldName = $table . '.' . $fieldName;
            }
            $vfieldName = sprintf("%s AS %s", $fieldName, $vfieldName);
        } else {
            $vfieldName = $this->dql->attrNameToFieldName($vfieldName);
        }
        return $vfieldName;
    }

    /**
     * 虚拟字段名转真实字段 - 用于查询条件
     * bigcate_id 转 bigcate.id AS bigcate_id
     * @param string $vfieldName
     */
    public function virtuallyFieldNameToTrueNameForWhere(string $vfieldName): string
    {
        if (strpos($vfieldName, $this->dql::VIRTUALLY_FIELD) > 0) {
            list($table, $fieldName) = explode($this->dql::VIRTUALLY_FIELD, $vfieldName);
            $vfieldName = $this->dql->attrNameToFieldName($table . '.' . $fieldName);
            if (strpos($vfieldName, '.') === false) {
                $vfieldName = $table . '.' . $vfieldName;
            }
        } else {
            $vfieldName = $this->dql->attrNameToFieldName($vfieldName);
        }
        return $vfieldName;
    }

    /**
     * 虚拟字段名转真实字段 - 用于查询条件 - 通过数组
     * bigcate_id 转 bigcate.id AS bigcate_id
     * @param string $vfieldName
     */
    public function virtuallyFieldNameToTrueNameForWhereByArray(array $vfieldNames): array
    {
        $result = [];
        foreach ($vfieldNames as $i => $fieldName) {
            $result[$i] = $this->virtuallyFieldNameToTrueNameForWhere($fieldName);
        }
        return $result;
    }

    public function parseWhere(\Closure $callback = null): array
    {
        $result = [];
        if ($this->params->filter) {
            $result = DxSqlerExpr::getWhere(
                $this->params->filter,
                function (&$fieldName, &$op, &$val, &$deep, &$index) use ($callback) {
                    $fieldName = $this->virtuallyFieldNameToTrueNameForWhere($fieldName);
                    $callback = $callback ?: $this->whereCallback;
                    if ($callback) {
                        $callback($fieldName, $op, $val, $deep, $index);
                    }
                }
            );
        }
        return $result;
    }

    public function parseOrderBy(\Closure $callback = null): array
    {
        $result = [];
        if ($this->params->sort) {
            $result = DxSqlerExpr::getSort($this->params->sort, $callback ?: $this->orderByCallback);
        }
        return $result;
    }

    public function getGroupData(int $cacheTime = 0): array
    {
        $result = [
            'groupCount' => -1,
            'summary' => [],
        ];
        $dql = $this->dql;
        if ($this->params->group) {
            if (!DEBUG && $cacheTime) {
                $cacheKey = md5(serialize($this->params->group));
                $cacheFile = new Cache();
                $cacheFile->setCacheFileDir('dql/group');
                $result = $cacheFile->getCacheFile($cacheKey, $cacheTime);
                if (!is_null($result)) {
                    return  $result;
                }
            }
            $fields = [];
            $groupFields = [];
            $summaryFields = [];
            $groupBy = [];
            $orderBy = [];
            $groupByOne = null;
            $groupIntervalArr = [];
            foreach ($this->params->group as $item) {
                $groupByOne = $item->selector;
                if ($item->isExpanded) {
                    $groupByOne = $item->selector;
                }
                if (isset($item->groupInterval) && $item->isExpanded) {
                    $groupIntervalArr[$item->groupInterval] = true;
                }
                $sortType = isset($item->desc) && $item->desc ? 'DESC' : 'ASC';
                $orderBy[$item->selector] = $sortType;
            }
            $groupFieldName = '';
            if ($groupIntervalArr) {
                //year,month,day
                $fieldName = $dql->attrNameToFieldName($groupByOne);
                if (isset($groupIntervalArr['year'])) {
                    $groupBy[] = "YEAR({$fieldName})";
                    $groupFields[$fieldName . "#year"] = ["YEAR(~)", 'GROUP_YEAR'];
                }
                if (isset($groupIntervalArr['month'])) {
                    $groupBy[] = "MONTH({$fieldName})";
                    $groupFields[$fieldName . "#month"] = ["MONTH(~)", 'GROUP_MONTH'];
                }
            } else {
                $groupBy = [$this->virtuallyFieldNameToTrueNameForWhere($groupByOne)];
                $groupFieldName = $this->virtuallyFieldNameToTrueName($groupByOne);
                $groupFieldName = $dql->getAsField($groupFieldName);
                $groupFields = [$groupFieldName];
            }
            if ($this->params->groupSummary) {
                foreach ($this->params->groupSummary as $i => $item) {
                    $summaryType = strtoupper($item->summaryType ?? 'count');
                    $fieldName = $dql->attrNameToFieldName($item->selector);
                    $summaryFields[$fieldName . "#{$i}"] = ["{$summaryType}(~)", "SUMMARY_{$i}"];
                }
            }
            $summaryFields["1#COUNT"] = ["COUNT(~)", "COUNT_TOTAL"];
            $fields = array_merge([], $groupFields, $summaryFields);
            $dql->where($this->parseWhere());
            $dql->orderBy($this->parseOrderBy());
            $dql->limit($this->parseLimit());
            $dql->bindDataSet($this);
            $dql->groupBy($groupBy);
            $dql->orderBy($orderBy);
            $dql->fields($fields);
            $countFieldName = $this->virtuallyFieldNameToTrueName($groupByOne);
            $countFieldName = $dql->attrNameToFieldName($countFieldName);
            $countFieldName = $dql->getAsField($countFieldName);
            $count = $dql->count(['countFieldName' => $countFieldName]);
            $rows = $dql->allDbRows();
            //ddSql($dql->last());
            $items = [];
            if ($groupIntervalArr) {
                if (isset($groupIntervalArr['year']) && isset($groupIntervalArr['month'])) {
                    $tmpArr = [];
                    foreach ($rows as $key => $row) {
                        $year = $row['GROUP_YEAR'];
                        $month = $row['GROUP_MONTH'];
                        $tmpArr[$year][] = [
                            'key' => $month,
                            'count' => null,
                            'items' => [],
                        ];
                    }
                    foreach ($tmpArr as $year => $months) {
                        $item = [];
                        $item['key'] = $year;
                        $item['count'] = null;
                        $item['items'] = $months;
                        $items[] = $item;
                    }
                }
            } else {
                foreach ($rows as $i => $row) {
                    $item = [];
                    if (strpos($groupFieldName, ' AS ') !== false) {
                        $groupFieldName = explode(' AS ', $groupFieldName)[1];
                    }
                    foreach ($row as $fieldName => $value) {
                        if (strpos($fieldName, 'SUMMARY_') !== false) {
                            $item['summary'][] = $value;
                        } elseif (strpos($fieldName, 'COUNT_TOTAL') !== false) {
                            $item['count'] = $value;
                        } elseif ($groupByOne && $fieldName == $groupFieldName) {
                            $item['key'] = $value;
                        } elseif ($groupFieldName == '' || $fieldName == trim($groupFieldName, '`')) {
                            $item['key'] = $value;
                        }
                        $item['items'] = null;
                    }
                    $items[] = $item;
                }
            }
            $result = [
                'data' => $items,
                'groupCount' => $count,
                'totalCount' => $count,
            ];
            if (isset($cacheFile)) {
                $cacheFile->saveCacheFile($cacheKey, $result);
            }
        }
        return $result;
    }

    public function parseLimit()
    {
        return [$this->params->skip, $this->params->take];
    }
}
