<?php

namespace Kylin\Activity;

use Closure;
use Laravel\SerializableClosure\SerializableClosure;

/**
 * 日志选项配置
 *
 * 控制模型活动日志的行为，通过模型的 getActivitylogOptions() 方法返回。
 * 使用流畅接口（Fluent API）链式配置日志策略。
 *
 * 配置项：
 *  - logAll/logOnly/logExcept：控制记录哪些属性
 *  - logOnlyDirty：仅记录变更的属性
 *  - logFillable：记录所有 $fillable 属性
 *  - logUnguarded：记录所有非 $guarded 属性
 *  - dontSubmitEmptyLogs：忽略无变更的日志
 *  - dontLogIfAttributesChangedOnly：仅指定属性变更时忽略
 *  - useLogName：指定日志名称
 *  - setDescriptionForEvent：自定义事件描述
 */
class LogOptions
{
    /** 日志名称 */
    public ?string $logName = null;

    /** 是否提交空日志（无变更时仍记录） */
    public bool $submitEmptyLogs = true;

    /** 是否仅记录 $fillable 属性 */
    public bool $logFillable = false;

    /** 是否仅记录变更的属性（Dirty） */
    public bool $logOnlyDirty = false;

    /** 是否记录所有非 $guarded 属性 */
    public bool $logUnguarded = false;

    /** 仅记录这些属性 */
    public array $logAttributes = [];

    /** 排除这些属性 */
    public array $logExceptAttributes = [];

    /** 仅这些属性变更时不记录日志 */
    public array $dontLogIfAttributesChangedOnly = [];

    /** 属性原始值（用于对比） */
    public array $attributeRawValues = [];

    /** 自定义事件描述闭包 */
    public ?Closure $descriptionForEvent = null;

    /**
     * 使用默认选项开始配置
     */
    public static function defaults(): self
    {
        return new static();
    }

    /**
     * 记录所有属性
     */
    public function logAll(): self
    {
        return $this->logOnly(['*']);
    }

    /**
     * 记录所有非 $guarded 属性
     */
    public function logUnguarded(): self
    {
        $this->logUnguarded = true;

        return $this;
    }

    /**
     * 记录所有 $fillable 属性
     */
    public function logFillable(): self
    {
        $this->logFillable = true;

        return $this;
    }

    /**
     * Stop logging $fillable attributes of the model.
     */
    public function dontLogFillable(): self
    {
        $this->logFillable = false;

        return $this;
    }

    /**
     * Log changes that has actually changed after the update.
     */
    public function logOnlyDirty(): self
    {
        $this->logOnlyDirty = true;

        return $this;
    }

    /**
     * Log changes only if these attributes changed.
     */
    public function logOnly(array $attributes): self
    {
        $this->logAttributes = $attributes;

        return $this;
    }

    /**
     * Exclude these attributes from being logged.
     */
    public function logExcept(array $attributes): self
    {
        $this->logExceptAttributes = $attributes;

        return $this;
    }

    /**
     * Don't trigger an activity if these attributes changed logged.
     */
    public function dontLogIfAttributesChangedOnly(array $attributes): self
    {
        $this->dontLogIfAttributesChangedOnly = $attributes;

        return $this;
    }

    /**
     * Don't store empty logs. Storing empty logs can happen when you only
     * want to log a certain attribute but only another changes.
     */
    public function dontSubmitEmptyLogs(): self
    {
        $this->submitEmptyLogs = false;

        return $this;
    }

    /**
     * Allow storing empty logs. Storing empty logs can happen when you only
     * want to log a certain attribute but only another changes.
     */
    public function submitEmptyLogs(): self
    {
        $this->submitEmptyLogs = true;

        return $this;
    }

    /**
     * Customize log name.
     */
    public function useLogName(?string $logName): self
    {
        $this->logName = $logName;

        return $this;
    }

    /**
     * Customize log description using callback.
     */
    public function setDescriptionForEvent(Closure $callback): self
    {
        $this->descriptionForEvent = $callback;

        return $this;
    }

    /**
     * Exclude these attributes from being casted.
     */
    public function useAttributeRawValues(array $attributes): self
    {
        $this->attributeRawValues = $attributes;

        return $this;
    }

    public function __serialize(): array
    {
        $data = get_object_vars($this);

        if ($data['descriptionForEvent'] !== null) {
            $data['descriptionForEvent'] = new SerializableClosure($data['descriptionForEvent']);
        }

        return $data;
    }

    public function __unserialize(array $data): void
    {
        foreach ($data as $key => $value) {
            if ($key === 'descriptionForEvent' && $value instanceof SerializableClosure) {
                $value = $value->getClosure();
            }

            $this->$key = $value;
        }
    }
}
