<?php

namespace app\common\exception;

use DateTime;
use think\facade\Request;
use think\facade\Log;
use think\helper\Arr;
use think\facade\Db;
use app\common\arw\adjfut\src\Exception\ErrorMsg;

/**
 * 排序类(调用此类的方法需要进行事务与锁表操作)
 */
class Sort
{
    /**
     * 模型层实例
     * @var \think\model
     */
    private $model;

    /**
     * 主键字段名
     * @var string
     */
    private $pk;

    /**
     * 排序字段名
     * @var string
     */
    private $sort_field;

    /**
     * 表名
     * @var string
     */
    private $table_name;

    /**
     * 构造器
     * 
     * @param string $model 模型命名空间
     */
    public function __construct(string $model)
    {
        //模型实例化
        $this->model = new $model();
        //主键字段名初始化
        $this->pk = $this->model->db()->getPk();
        //表名初始化
        $this->table_name = $this->model->db()->getTable();
        //排序字段名初始化
        $this->initSortField();
    }

    /**
     * 获取排序字段名
     */
    public function getSortField(): string
    {
        return $this->sort_field;
    }

    /**
     * 获取主键字段名
     */
    public function getPk(): string
    {
        return $this->pk;
    }

    /**
     * 排序号新增处理
     * 
     * @param array $extra_wheres 关联类型 例:["xxx_type" => 1]
     * @return int 返回新增所需的最新排序号
     */
    public function add(array $extra_wheres = []): int
    {
        $sort_field = $this->sort_field;
        $max_sort = $this->model->where($extra_wheres)->order($sort_field, 'desc')->value($sort_field) ?? 0;
        return ++$max_sort;
    }

    /**
     * 排序号互换处理(数据A <=> 数据B)
     * 
     * @param string $guid 主键值
     * @param int $sort 新排序号
     * @param array $extra_wheres 关联类型 例:["xxx_type" => 1]
     */
    public function swap(string $guid, int $sort, array $extra_wheres = []): void
    {
        $model = $this->model;
        $table_name = $this->table_name;
        $pk = $this->pk;
        $sort_field = $this->sort_field;

        //数据A
        $data_a = $model->where($pk, $guid)->find();
        if (!$data_a) {
            throwErrorMsg('Tool::sortEditProc() : 找不到该数据原排序号', 444);
        }

        //数据B
        //查找当前数据所想更换的新排序号是否已有数据占用(排除自己)，这个占用数据则视为数据B
        $data_b = $model->where($sort_field, $sort)->where($pk, '<>', $guid)->where($extra_wheres)->find();

        //数据B存在，开始进行互换处理
        if ($data_b) {
            //【互换内容初始化】
            $update_data = [];
            //检查模型是否有父祖级主键，有则也加入互换
            if ($this->isExistFathersAndAncestors()) {
                $update_data[$model->parent_guid_field] = $data_a[$model->parent_guid_field];
                $update_data[$model->ancestors_guid_field] = $data_a[$model->ancestors_guid_field];
            }
            //原字段排序加入互换
            $update_data = [$sort_field => $data_a[$sort_field]];
            //关联类型也加入互换
            foreach ($extra_wheres as $key => $value) {
                $update_data[$key] = $data_a[$key];
            };

            //【数据逻辑处理】
            //互换数据A与B的关联类型不相同时候,则代表为数据互换跨类型情况
            if (self::isAssociationTypeDataSame($data_a, $data_b, $extra_wheres)) {
                //数据互换不同类型情况-正常互换操作
                Db::name($table_name)->where($sort_field, $sort)->where($extra_wheres)->update($update_data);
            } else {
                //数据互换跨类型情况-腾位操作
                $this->vacate($sort, $extra_wheres);
            }
        }
    }

    /**
     * 排序号腾位处理
     * 
     * @param int|null $sort 当前排序号
     * @param array $wheres  当前指定数据的额外查询条件(tp批量查询数组) 例:["xxx_type" => 1,...]
     */
    public function vacate($sort, array $wheres = []): void
    {
        $sort_field = $this->sort_field;
        $table_name = $this->table_name;
        //新增数据的所属排序号已有数据占用，则腾位处理(已占用的数据及其后面所有数据一起排序号腾位+1)
        if ($this->model->where($sort_field, $sort)->where($wheres)->value($sort_field) !== null) {
            Db::name($table_name)->where($wheres)->where($sort_field, '>=', $sort)->inc($sort_field)->update();
        }
    }

    /**
     * 排序号退位处理
     * 
     * @param string $guid 当前主键值
     * @param array $correl_fields 关联类型字段 例:["xxx_guid",...] 代表删除排序号时会根据这些字段来关联(例:所属xxx类型、所属xxx系列)来进行排序处理
     */
    public function back(string $guid, array $correl_fields): void
    {
        $model = $this->model;
        $sort_field = $this->sort_field;
        //在所删除的数据之后的数据所属排序号将进行减位处理减位-1
        $deleted_find = $model->where($this->pk, $guid)->find();
        if ($deleted_find) {
            $con = [];
            foreach ($correl_fields as $correl_field) {
                $con[$correl_field] = $deleted_find[$correl_field];
            }
            $model->where($sort_field, '>', $deleted_find[$sort_field])->where($con)->dec($sort_field)->save();
        }
    }

    /**
     * 对比数据关联类型是否相同【排序号互换处理】
     * 
     * @param object $data_a 互换数据A
     * @param object $data_b 互换数据B
     * @param array $extra_wheres 关联类型数组 例:["xxx_type" => 1]
     */
    private static function isAssociationTypeDataSame(object $data_a, object $data_b, array $extra_wheres): bool
    {
        foreach ($extra_wheres as $key => $val) {
            if ($data_a[$key] != $data_b[$key]) {
                return false;
            }
        };
        return true;
    }

    /**
     * 检查模型实例父祖级主键字段是否存在【排序号互换处理】
     */
    private function isExistFathersAndAncestors(): bool
    {
        return isset($this->model->parent_guid_field) && isset($this->model->ancestors_guid_field);
    }

    /**
     *初始化排序字段名
     */
    private function initSortField(): void
    {
        if (!isset($this->model->order_field)) {
            throwErrorMsg('排序处理失败!模型层未定义排序字段$order_field');
        }
        $this->sort_field = $this->model->order_field;
    }
}
