<?php

namespace xmvc;

use Exception;
use Closure;
use xmvc\Sqler;
use xmvc\BaseMeta;

/**
 * Dqler 对象查询
 */

class Dqler
{
    private BaseMeta $selectMeta;
    private Sqler $q;
    private array $conditions = [];
    private array $fields = ['*'];
    private array $joinMetas = [];
    private ?array $dbRows = null;
    private ?array $list = null;
    public const VIRTUALLY_FIELD = '_'; //虚拟字段名间隔符
    public function __construct(string $metaClassMame = '')
    {
        if ($metaClassMame) {
            $this->select($metaClassMame);
        }
    }

    public function last(): string
    {
        $this->checkSelectMeta();
        return $this->q->last();
    }

    public function checkSelectMeta(): bool
    {
        if (!isset($this->selectMeta)) {
            throw new Exception("请先 select() 一个元对象", 1);
        }
        return true;
    }

    public function bindDataSet(mixed $dataset): self
    {
        if (get_class($dataset) == 'xmvc\libDx\DxSet') {
            $dataset->dql = $this;
            $this->checkSelectMeta();
            $this->where($dataset->parseWhere());
            $this->orderBy($dataset->parseOrderBy());
            $this->limit($dataset->parseLimit());
        }
        return $this;
    }

    /** 属性名转字段名 */
    public function attrNameToFieldName(string|array $attrName): string|array
    {
        if (is_array($attrName)) {
            $fieldName = $attrName[0];
        } else {
            $fieldName = $attrName;
        }
        $parts = $this->selectMeta::parseAsAttrNameForField($fieldName, $this->joinMetas);
        if (is_array($parts)) {
            $fieldName = $parts['fieldName'];
        } else {
            $fieldName = $parts;
        }
        if (is_array($attrName)) {
            $attrName[0] = $fieldName;
            return $attrName;
        } else {
            return $fieldName;
        }
    }

    /** 批量属性名转字段名, 转数组的值 */
    public function attrNameToFieldNameArrayForValue(array $attrs): array
    {
        $result = [];
        foreach ($attrs as $k => $v) {
            $result[$k] = $this->attrNameToFieldName($v);
        }
        return $result;
    }

    /** 批量属性名转字段名, 转数组的键 */
    public function attrNameToFieldNameArrayFoKey(array $attrs): array
    {
        $result = [];
        foreach ($attrs as $k => $v) {
            $result[$this->attrNameToFieldName($k)] = $v;
        }
        return $result;
    }

    /** 字段名转属性名 */
    public function fieldNameToAttrName(string $fieldName): string
    {
        return $this->selectMeta::parseAsFieldName($fieldName, $this->joinMetas);
    }

    /* 组建查询 */
    private function buildQuery(Closure $callback = null): self
    {
        $this->checkSelectMeta();
        if (!empty($this->conditions)) {
            if (isset($this->conditions['JOIN'])) {
                $this->conditions['JOIN'] = $this->selectMeta::parseJoin($this->joinMetas, $this->conditions['JOIN']);
            }
            if (isset($this->conditions['LEFT JOIN'])) {
                $this->conditions['LEFT JOIN'] = $this->selectMeta::parseJoin($this->joinMetas, $this->conditions['LEFT JOIN']);
            }
            if (isset($this->conditions['FULL JOIN'])) {
                $this->conditions['FULL JOIN'] = $this->selectMeta::parseJoin($this->joinMetas, $this->conditions['FULL JOIN']);
            }
            $conditions = $this->conditions;
            if ($callback) {
                $callback($conditions);
            }
            $conditions = $this->selectMeta::processConditionsToWhere($conditions, $this->joinMetas);
            $fields = [];
            foreach ($this->fields as $i => $field) {
                if (is_numeric($i)) {
                    if (is_array($field)) {
                        continue;
                    }
                    $fields[$i] = $this->selectMeta::parseAsAttrNameForField($field, $this->joinMetas);
                } else {
                    if (is_array($field)) {
                        if (isset($field['fieldName'])) {
                            $fields[$field['fieldName']] = $field['fieldRule'];
                            continue; // 已经转换过了
                        }
                    } else {
                        dump($this->fields);
                        throw new Exception("{$field} 不是一个数组", 1);
                    }
                    $field['attr'] = $i;
                    $tmpArr = $this->selectMeta::parseAsAttrNameForField($field, $this->joinMetas);
                    if (!is_array($tmpArr)) {
                        dump($this->fields);
                        throw new Exception("{$i} 格式有误", 1);
                    }
                    $fields[$tmpArr['fieldName']] = $tmpArr['fieldRule'];
                }
            }
            $this->q->fields($fields);
            $this->q->where($conditions);
        }
        return $this;
    }

    public function parseMetaClassName(string $metaClassName): string
    {
        $metaClassName = str_replace('.', '\\', $metaClassName);
        if (strpos($metaClassName, 'app\\') === false) {
            $metaClassName = '\\app\\meta\\' . ltrim($metaClassName, '\\');
        }
        return $metaClassName;
    }

    public function select(string $metaClassName, string $as = ''): self
    {
        $metaClassName = $this->parseMetaClassName($metaClassName);
        $this->selectMeta = new $metaClassName();
        $as = $as ?: $this->selectMeta::CLASSNAME;
        $this->addMeta($metaClassName, $as, true);
        $this->q = $this->selectMeta->getDB()->newSqler();
        $this->q->table($this->selectMeta::TABLE, $as);
        return $this;
    }

    public function fields(array $fields): self
    {
        foreach ($fields as $key => $value) {
            if (is_array($value)) {
                $value['attr'] = $key;
            }
            $this->fields[$key] = $this->selectMeta::parseAsAttrNameForField($value, $this->joinMetas);
        }
        return $this;
    }

    public function addMeta(string|array $metas, string $as = '', bool $isSelect = false): self
    {
        if (is_array($metas)) {
            $this->joinMetas = array_merge($this->joinMetas, $metas);
        } else {
            $metaClassName = $this->parseMetaClassName($metas);
            if ($as == '') {
                throw new Exception("需要提供别名", 1);
            }
            $item = ['class' => $metaClassName];
            $item['as'] = $as;
            if ($isSelect) {
                $this->joinMetas['SELECT'] = $item;
            } else {
                $this->joinMetas[$as] = $item;
            }
        }
        return $this;
    }

    public function where(string|array $conditions = null, mixed $value = null): self
    {
        if (!empty($conditions)) {
            if (is_string($conditions)) {
                $conditions = [$conditions => $value];
            }
            if (!isset($this->conditions['WHERE'])) {
                $this->conditions['WHERE'] = [];
            }
            $this->conditions['WHERE'] = array_merge($this->conditions['WHERE'], $conditions);
        }
        return $this;
    }

    public function having(string|array $conditions = null, mixed $value = null): self
    {
        if (!empty($conditions)) {
            if (is_string($conditions)) {
                $conditions = [$conditions => $value];
            }
            $this->conditions['HAVING'] = $conditions;
        }
        return $this;
    }

    public function join(array $join): self
    {
        if (!isset($this->conditions['JOIN'])) {
            $this->conditions['JOIN'] = [];
        }
        $this->conditions['JOIN'] = array_merge($this->conditions['JOIN'], $join);
        return $this;
    }

    public function leftJoin(array $join): self
    {
        if (!isset($this->conditions['LEFT JOIN'])) {
            $this->conditions['LEFT JOIN'] = [];
        }
        $this->conditions['LEFT JOIN'] = array_merge($this->conditions['LEFT JOIN'], $join);
        return $this;
    }

    public function fullJoin(array $join): self
    {
        if (!isset($this->conditions['FULL JOIN'])) {
            $this->conditions['FULL JOIN'] = [];
        }
        $this->conditions['FULL JOIN'] = array_merge($this->conditions['FULL JOIN'], $join);
        return $this;
    }

    public function groupBy(array $groups): self
    {
        if ($groups) {
            $this->conditions['GROUP BY'] = $groups;
        }
        return $this;
    }

    public function orderBy(array $orders): self
    {
        if ($orders) {
            $this->conditions['ORDER BY'] = $orders;
        }
        return $this;
    }

    public function limit(int|array $start, int $pagesize = 1000): self
    {
        if (is_array($start)) {
            $this->conditions['LIMIT'] = $start;
        } else {
            $this->conditions['LIMIT'] = [$start, $pagesize];
        }
        return $this;
    }

    public function count(array $extAttrs = null): int
    {
        $this->checkSelectMeta();
        $this->buildQuery();
        return $this->q->count($extAttrs);
    }

    public function allDbRows(): array
    {
        $this->checkSelectMeta();
        $this->buildQuery();
        $this->dbRows = $this->q->all();
        return $this->dbRows;
    }

    public function all(): array
    {
        $this->allDbRows();
        if (is_null($this->list)) {
            $this->list = [];
        }
        if ($this->dbRows) {
            foreach ($this->dbRows as $row) {
                $this->list[] = $this->selectMeta::fromDBRow($row);
            }
        }
        return $this->list;
    }

    public function asArray(\Closure $callback = null): array
    {
        $this->checkSelectMeta();
        if (is_null($this->list)) {
            $this->all();
        }
        $data = [];
        if ($this->list) {
            foreach ($this->list as $i => $item) {
                $itemData = $item->toArray();
                if ($callback) {
                    $callback($itemData, $this->dbRows[$i]);
                }
                $data[] = $itemData;
            }
        }
        return $data;
    }
}
