<?php

// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2025 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);

namespace think;

use ArrayAccess;
use Closure;
use JsonSerializable;
use think\contract\Arrayable;
use think\contract\Jsonable;
use think\db\BaseQuery as Query;
use think\db\Express;
use think\exception\ValidateException;
use think\model\Collection;
use think\model\contract\Modelable;
use think\model\View;
use WeakMap;

/**
 * Class Model.
 * @mixin \think\db\Query
 *
 * @method static void  onAfterRead(Model $model)     after_read事件定义
 * @method static mixed onBeforeInsert(Model $model)  before_insert事件定义
 * @method static void  onAfterInsert(Model $model)   after_insert事件定义
 * @method static mixed onBeforeUpdate(Model $model)  before_update事件定义
 * @method static void  onAfterUpdate(Model $model)   after_update事件定义
 * @method static mixed onBeforeWrite(Model $model)   before_write事件定义
 * @method static void  onAfterWrite(Model $model)    after_write事件定义
 * @method static mixed onBeforeDelete(Model $model)  before_write事件定义
 * @method static void  onAfterDelete(Model $model)   after_delete事件定义
 * @method static void  onBeforeRestore(Model $model) before_restore事件定义
 * @method static void  onAfterRestore(Model $model)  after_restore事件定义
 */
abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable, Modelable
{
    use model\concern\Attribute;
    use model\concern\AutoWriteData;
    use model\concern\Conversion;
    use model\concern\DbConnect;
    use model\concern\ModelEvent;
    use model\concern\RelationShip;

    private static ?WeakMap $weakMap = null;

    /**
     * 服务注入.
     *
     * @var Closure[]
     */
    protected static array $_maker = [];

    /**
     * 设置服务注入.
     */
    public static function maker(Closure $maker): void
    {
        static::$_maker[] = $maker;
    }

    /**
     * 设置容器对象的依赖注入方法.（用于兼容）
     *
     * @param callable $callable 依赖注入方法
     *
     * @return void
     */
    public static function setInvoker(callable $callable): void
    {
    }

    /**
     * 调用反射执行模型方法 支持参数绑定.
     *
     * @param mixed $method
     * @param array $vars   参数
     *
     * @return mixed
     */
    public function invoke($method, array $vars = [])
    {
        if (is_string($method)) {
            $method = [$this, $method];
        }

        $invoker = $this->getOption('invoker');
        if ($invoker) {
            return $invoker($method instanceof Closure ? $method : Closure::fromCallable($method), $vars);
        }

        return call_user_func_array($method, $vars);
    }

    /**
     * 架构函数.
     *
     * @param array|object $data 实体模型数据
     */
    public function __construct(array | object $data = [])
    {
        // 获取实体模型参数
        $options = array_merge($this->getBaseOptions(), $this->getOptions());

        if (!self::$weakMap) {
            self::$weakMap = new WeakMap;
        }

        self::$weakMap[$this] = [
            'get'          => [],
            'data'         => [],
            'origin'       => [],
            'relation'     => [],
            'together'     => [],
            'allow'        => [],
            'withAttr'     => [],
            'schema'       => $options['schema'] ?? [],
            'updateTime'   => $options['updateTime'] ?? 'update_time',
            'createTime'   => $options['createTime'] ?? 'create_time',
            'suffix'       => $options['suffix'] ?? '',
            'validate'     => $options['validate'] ?? $this->parseValidate(),
            'type'         => $options['type'] ?? [],
            'readonly'     => $options['readonly'] ?? [],
            'disuse'       => $options['disuse'] ?? [],
            'hidden'       => $options['hidden'] ?? [],
            'visible'      => $options['visible'] ?? [],
            'append'       => $options['append'] ?? [],
            'mapping'      => $options['mapping'] ?? [],
            'strict'       => $options['strict'] ?? true,
            'bindAttr'     => $options['bindAttr'] ?? [],
            'autoRelation' => $options['autoRelation'] ?? [],
        ];

        // 设置额外参数
        $this->setOptions(array_diff_key($options, self::$weakMap[$this]));

        // 模型初始化
        $this->initialize();

        // 初始化数据
        $this->initializeData($data);        
    }

    protected function initialize()
    {
        if (!empty(static::$_maker)) {
            foreach (static::$_maker as $maker) {
                call_user_func($maker, $this);
            }
        }

        // 初始化模型
        $this->init();
    }

    /**
     *  初始化模型.
     *
     * @return void
     */
    protected function init()
    {}

    /**
     * 定义基础配置参数.
     *
     * @return array
     */
    protected function getBaseOptions(): array
    {
        return [];
    }

    /**
     * 在实体模型中定义 返回相关配置参数.
     *
     * @return array
     */
    protected function getOptions(): array
    {
        return [];
    }

    /**
     * 批量设置模型参数
     * @param array  $options  值
     * @return void
     */
    public function setOptions(array $options): void
    {
        foreach ($options as $name => $value) {
            $this->setOption($name, $value);
        }
    }

    /**
     * 设置模型参数
     *
     * @param string $name  参数名
     * @param mixed  $value  值
     *
     * @return $this
     */
    public function setOption(string $name, $value)
    {
        self::$weakMap[$this][$name] = $value;
        if (property_exists($this, $name)) {
            $this->$name = $value;
        }
        return $this;
    }

    /**
     * 获取模型参数
     *
     * @param string $name  参数名
     * @param mixed  $default  默认值
     *
     * @return mixed
     */
    public function getOption(string $name, $default = null)
    {
        // 兼容读取3.0版本的属性参数定义
        if (property_exists($this, $name) && isset($this->$name)) {
            return $this->$name;
        } 
        return self::$weakMap[$this][$name] ?? $default;
    }

    private function setWeakData(string $key, string $name, $value): void
    {
        self::$weakMap[$this][$key][$name] = $value;
    }

    private function getWeakData(string $key, string $name, $default = null)
    {
        return self::$weakMap[$this][$key][$name] ?? $default;
    }

    /**
     * 创建新的模型实例.
     *
     * @param array|object $data
     * @param array $options
     *
     * @return Model|Entity
     */
    public function newInstance(array | object $data = [], array $options = [])
    {
        $model = new static($data);
        if (!empty($data)) {
            $model->exists(true);
        }

        if ($this->getEntity()) {
            // 存在对应实体模型实例
            return $this->getEntity()->newInstance($model, $options);
        }

        return $this->fetchModel($model, $options);
    }

    /**
     * 获取克隆的模型实例.
     *
     * @return static
     */
    public function clone()
    {
        $model = new static();
        self::$weakMap[$model] = self::$weakMap[$this];
        return $model;
    }

    /**
     * 获取实际模型实例.
     *
     * @param Model $model
     *
     * @return Modelable
     */
    protected function fetchModel(Model $model, array $options = []): Modelable
    {
        $class = $model->getOption('entityClass', str_replace('\\model\\', '\\entity\\', static::class));
        if (class_exists($class) && is_subclass_of($class, Entity::class)) {
            return new $class($model, $options);
        }
        return $model;
    }

    public function entity(Entity $entity): void
    {
        $this->setOption('entity', $entity);
    }

    public function getEntity(): ?Entity
    {
        return $this->getOption('entity');
    }

    /**
     * 解析对应验证类.
     *
     * @return string
     */
    protected function parseValidate(): string
    {
        $auto     = $this->getOption('autoValidate', false);
        $validate = $auto && str_contains(static::class, '\\model\\') ? str_replace('\\model\\', '\\validate\\', static::class) : '';
        return $validate && class_exists($validate) ? $validate : '';
    }

    /**
     * 设置验证场景. 
     *
     * @param string|array $scene 场景名或数组
     * @return $this
     */
    public function scene(string|array $scene)
    {
        return $this->setOption('scene', $scene);
    }

    /**
     * 验证模型数据.
     *
     * @param array $data 数据
     * @param array $allow 需要验证的字段
     *
     * @throws ValidateException
     * @return void
     */
    protected function validate(array $data, array $allow = []): void
    {
        $validater = $this->getOption('validate');
        if (!empty($validater)) {
            validate($validater)
                ->scene($this->getOption('scene') ?: $allow)
                ->check($data);
        }
    }

    /**
     * 保存模型实例数据.
     *
     * @param array|object $data 数据
     * @param mixed $where 更新条件 true为强制新增
     * @param bool  $refresh  是否刷新数据
     * @return bool
     */
    public function save(array | object $data = [], $where = [], bool $refresh = false): bool
    {
        if (!empty($data)) {
            // 初始化模型数据
            $this->initializeData($data, true);
        }

        if (false === $this->trigger('BeforeWrite')) {
            return false;
        }

        if (true === $where) {
            $isUpdate = false;
            $where    = [];
        } elseif (!empty($where)) {
            $isUpdate = true;
        } else {
            $isUpdate = $this->isExists() ? true : false;
        }

        if (false === $this->trigger($isUpdate ? 'BeforeUpdate' : 'BeforeInsert')) {
            return false;
        }

        [$data, $relations, $allow] = $this->validateAndFilterData($isUpdate);

        if (empty($data)) {
            // 保存关联数据
            if ($isUpdate && $this->getOption('together')) {
                $this->relationSave($relations, $isUpdate);
            }
            return true;
        }

        // 自动写入数据
        $this->autoWriteData($data, $isUpdate, $allow);

        $db     = $this->getDbWhere($where);
        $result = $db->field($allow)
            ->removeOption('data')
            ->save($data, !$isUpdate);

        if (!$isUpdate) {
            $this->exists(true);
            // 写入自增键值
            $key = $db->getAutoInc();
            $val = $db->getLastInsID();
            if ($key && $val) {
                $this->setData($key, $val);
            }
        } elseif ($refresh) {
            // 刷新数据
            $this->refresh();
        }
        $this->trigger($isUpdate ? 'AfterUpdate' : 'AfterInsert');
        $this->trigger('AfterWrite');

        // 保存关联数据
        if ($this->getOption('together')) {
            $this->relationSave($relations, $isUpdate);
        }

        // 重置原始数据
        $this->refreshOrigin();
        return true;
    }

    /**
     * 验证和过滤数据
     * @param bool $isUpdate 是否更新
     * @return array [$data, $relations, $allow]
     */
    protected function validateAndFilterData(bool $isUpdate): array
    {
        $data     = $this->getData();
        $origin   = $this->getOrigin();
        $allow    = $this->getOption('allow') ?: array_keys($this->getFields());
        $readonly = $this->getOption('readonly');
        $disuse   = $this->getOption('disuse');
        $allow    = array_diff($allow, $disuse, $isUpdate ? $readonly : []);
        $together = $this->getOption('together');

        // 验证数据
        $this->validate($data, $allow);

        $relations = [];
        foreach ($data as $name => &$val) {
            if ($val instanceof Modelable || in_array($name, $together)) {
                $relations[$name] = $val;
                unset($data[$name]);
            } elseif ($val instanceof Collection || (!empty($allow) && !in_array($name, $allow))) {
                unset($data[$name]);
            } elseif ($isUpdate && !$this->isForce() && $this->isNotRequireUpdate($name, $val, $origin)) {
                unset($data[$name]);
            } else {
                $val = $this->setWithAttr($name, $val);
            }
        }

        return [$data, $relations, $allow];
    }

    /**
     * 数据检查.
     * @param array $data 数据
     * @param bool  $isUpdate 是否更新
     * @return void
     */
    protected function checkData(array &$data, bool $isUpdate): void
    {
    }

    protected function getDbWhere($where = [])
    {
        $db = $this->db();
        // 检查条件
        if (!empty($where)) {
            $db->where($where);
        } elseif ($this->getKey()) {
            $db->setKey($this->getKey());
        } else {
            $db->where($this->getOrigin());
        }

        if ($this->isForce()) {
            $db->removeOption('soft_delete');
        }

        return $db;
    }

    /**
     * 检查字段是否有更新（主键无需更新）.
     *
     * @param string $name 字段
     * @param mixed $val 值
     * @param array $origin 原始数据
     * @return bool
     */
    protected function isNotRequireUpdate(string $name, $val, array $origin): bool
    {
        return (array_key_exists($name, $origin) && $val === $origin[$name]) || $this->getPk() == $name;
    }

    /**
     * 获取更新数据.
     *
     * @return array
     */
    public function getChangedData(): array
    {
        $data   = $this->getData();
        $origin = $this->getOrigin();
        $change = [];
        foreach ($data as $name => $val) {
            if (!array_key_exists($name, $origin) || $val !== $origin[$name]) {
                $change[$name] = $val;
            }
        }
        return $change;
    }

    /**
     * 判断数据是否有更新.
     *
     * @param string $name 字段
     * @return bool
     */
    public function isChange(string $name): bool
    {
        return $this->getData($name) !== $this->getOrigin($name);
    }

    /**
     * 刷新模型数据.
     *
     * @return static
     */
    public function refresh()
    {
        if ($this->isExists() && $this->getKey()) {
            $data = $this->db()->find($this->getKey())->getData();
            $this->data($data);
        }
        return $this;
    }

    /**
     * 保存多个数据到当前数据对象
     *
     * @param iterable $dataSet 数据
     * @param bool     $replace 是否replace
     *
     * @return Collection
     */
    public static function saveAll(iterable $dataSet, bool $replace = true): Collection
    {
        $result = [];
        $model  = new static;
        $pk     = $model->getPk();
        foreach ($dataSet as $key => $data) {
            $model  = new static;
            if ($replace) {
                $exists = true;
                foreach ((array) $pk as $field) {
                    if (is_string($field) && !isset($data[$field])) {
                        $exists = false;
                    }
                }
            } else {
                $exists = false;
            }

            $model->replace($replace)->exists($exists)->save($data);
            $result[$key] = $model->fetchModel($model);
        }
        return $model->toCollection($result);
    }

    /**
     * 删除模型数据.
     *
     * @return bool
     */
    public function delete(): bool
    {
        if ($this->isEmpty() || false === $this->trigger('BeforeDelete')) {
            return false;
        }

        foreach ($this->getData() as $name => $val) {
            if ($val instanceof Model || $val instanceof Collection) {
                $relations[$name] = $val;
            }
        }

        $result = $this->getDbWhere()->delete();

        $this->trigger('AfterDelete');

        if ($result && !empty($relations)) {
            // 删除关联数据
            $this->relationDelete($relations);
        }

        $this->exists(false);
        $this->clear();
        return true;
    }

    /**
     * 写入数据.
     *
     * @param array|object  $data 数据
     * @param array  $allowField  允许字段
     * @param bool   $replace     使用Replace
     * @param string $suffix      数据表后缀
     * @return Modelable
     */
    public static function create(array | object $data, array $allowField = [], bool $replace = false, string $suffix = ''): Modelable
    {
        $model = new static();
        if (!empty($suffix)) {
            $model->setSuffix($suffix);
        }
        $model->allowField($allowField)->replace($replace)->save($data, true);            
        return $model->fetchModel($model);
    }

    /**
     * 更新数据.
     *
     * @param array|object  $data 数据
     * @param mixed  $where       更新条件
     * @param array  $allowField  允许字段
     * @param string $suffix      数据表后缀
     * @param bool   $refresh     是否刷新数据
     * @return Modelable
     */
    public static function update(array | object $data, $where = [], array $allowField = [], string $suffix = '', bool $refresh = false): Modelable
    {
        $model  = new static();
        if (!empty($suffix)) {
            $model->setSuffix($suffix);
        }
        $model->allowField($allowField)->exists(true)->save($data, $where, $refresh);
        return $model->fetchModel($model);
    }

    /**
     * 删除记录.
     *
     * @param mixed $data  主键列表 支持闭包查询条件
     * @param bool  $force 是否强制删除
     * @param array $together 关联删除
     *
     * @return bool
     */
    public static function destroy($data, bool $force = false, array $together = []): bool
    {
        if (empty($data) && 0 !== $data) {
            return false;
        }        
        $db = (new static())->db();

        if (is_array($data) && key($data) !== 0) {
            $db->where($data);
            $data = [];
        } elseif ($data instanceof Closure) {
            $data($db);
            $data = [];
        }

        $resultSet = $db->select((array) $data);

        foreach ($resultSet as $result) {
            $result->force($force)->together($together)->delete();
        }
        return true;
    }

    /**
     * 字段值增长
     *
     * @param string    $field    字段名
     * @param float|int $step     增长值
     * @param int       $lazyTime 延迟时间（秒）
     *
     * @return $this
     */
    public function inc(string $field, float|int $step = 1, int $lazyTime = 0)
    {
        return $this->set($field, new Express('+', $step, $lazyTime));
    }

    /**
     * 字段值减少.
     *
     * @param string    $field    字段名
     * @param float|int $step     增长值
     * @param int       $lazyTime 延迟时间（秒）
     *
     * @return $this
     */
    public function dec(string $field, float|int $step = 1, int $lazyTime = 0)
    {
        return $this->set($field, new Express('-', $step, $lazyTime));
    }

    /**
     * 查询缓存 数据为空不缓存.
     *
     * @param mixed         $key    缓存key
     * @param int|\DateTime $expire 缓存有效期
     * @param string|array  $tag    缓存标签
     *
     * @return $this
     */
    public function setCache($key = true, $expire = null, $tag = null)
    {
        return $this->setOption('cache', [$key, $expire, $tag]);
    }

    /**
     * 允许写入字段.
     *
     * @param array $allow 允许字段
     *
     * @return $this
     */
    public function allowField(array $allow)
    {
        return $this->setOption('allow', $allow);
    }

    /**
     * 动态设置只读字段.
     *
     * @param array $fields 只读字段
     *
     * @return $this
     */
    public function readonly(array $fields)
    {
        return $this->setOption('readonly', $fields);
    }

    /**
     * 强制写入或删除
     *
     * @param bool $force 强制更新
     *
     * @return $this
     */
    public function force(bool $force = true)
    {
        return $this->setOption('force', $force);
    }

    /**
     * 判断数据是否强制写入或删除.
     *
     * @return bool
     */
    public function isForce(): bool
    {
        return $this->getOption('force', false);
    }

    /**
     * 获取属性 支持获取器
     *
     * @param string $name 名称
     *
     * @return mixed
     */
    public function __get(string $name)
    {
        return $this->get($name);
    }

    /**
     * 设置数据 支持类型自动转换
     *
     * @param string $name  名称
     * @param mixed  $value 值
     *
     * @return void
     */
    public function __set(string $name, $value): void
    {
        if ($value instanceof Modelable && $bind = $this->getBindAttr($this->getOption('bindAttr'), $name)) {
            // 关联属性绑定
            $this->bindRelationAttr($value, $bind);
        } else {
            $this->set($name, $value);
        }
    }

    /**
     * 检测数据对象的值
     *
     * @param string $name 名称
     *
     * @return bool
     */
    public function __isset(string $name): bool
    {
        return !is_null($this->get($name));
    }

    /**
     * 销毁数据对象的值
     *
     * @param string $name 名称
     *
     * @return void
     */
    public function __unset(string $name): void
    {
        $name = $this->getRealFieldName($name);

        $this->setWeakData('data', $name, null);
        $this->setWeakData('get', $name, null);
    }

    public function __toString()
    {
        return $this->toJson();
    }

    public function __debugInfo()
    {
        return [
            'data'   => $this->getOption('data'),
            'origin' => $this->getOption('origin'),
            'schema' => $this->getOption('schema'),
        ];
    }

    // JsonSerializable
    public function jsonSerialize(): array
    {
        return $this->toArray();
    }

    /**
     * 序列化模型对象
     * 
     * @return array
     */
    public function __serialize(): array
    {
        $removeKeys = ['invoker', 'db', 'event'];
        return array_diff_key(self::$weakMap[$this], array_flip($removeKeys));
    }

    /**
     * 反序列化模型对象
     * 
     * @param array $data 
     * @return void
     */
    public function __unserialize(array $data) 
    {
        if (!self::$weakMap) {
            self::$weakMap = new WeakMap;
        }

        self::$weakMap[$this] = $data;
        // 重新初始化
        $this->initialize();
    }

    /**
     * 克隆模型实例
     * 
     * @return void
     */
    public function __clone()
    {
        throw new InvalidArgumentException('use $modelObj->clone() replace clone $modelObj');
    }

    // ArrayAccess
    public function offsetSet(mixed $name, mixed $value): void
    {
        $this->set($name, $value);
    }

    public function offsetGet(mixed $name): mixed
    {
        return $this->get($name);
    }

    public function offsetExists(mixed $name): bool
    {
        return $this->__isset($name);
    }

    public function offsetUnset(mixed $name): void
    {
        $this->__unset($name);
    }
}
