<?php

declare(strict_types=1);

namespace Builder\Core\Models;

use Builder\Core\Foundation\Revisions\RevisionDiff;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Foundation\Auth\User;
use Illuminate\Support\Carbon;

/**
 * @property int $id
 * @property string $revisable_type
 * @property int|string $revisable_id
 * @property int|null $user_id
 * @property array<string, mixed> $revision_data
 * @property bool $is_restore
 * @property int|null $restored_from
 * @property Carbon $created_at
 * @property Carbon $updated_at
 */
final class Revision extends Model
{
    protected $guarded = [];

    protected function casts(): array
    {
        return [
            'revision_data' => 'array',
        ];
    }

    /** @return MorphTo<Model, $this> */
    public function revisable(): MorphTo
    {
        return $this->morphTo();
    }

    /** @return BelongsTo<User, $this> */
    public function user(): BelongsTo
    {
        /** @var class-string<User> $userModel */
        $userModel = config('builder.auth.user_model', User::class);

        return $this->belongsTo($userModel);
    }

    /**
     * Compute a field-by-field diff against a previous revision.
     * Returns only fields that changed, with 'old' and 'new' values.
     *
     * @param  array<string, mixed>  $previousData
     * @return array<string, array{old: mixed, new: mixed}>
     */
    public function diffFrom(array $previousData): array
    {
        return $this->diff($previousData)->fields();
    }

    /**
     * Compute a per-locale diff for translatable models.
     *
     * @param  array<string, mixed>  $previousData
     * @return array<string, array<string, array{old: mixed, new: mixed}>>
     */
    public function translationDiffFrom(array $previousData): array
    {
        return $this->diff($previousData)->translations();
    }

    /**
     * Compute a media diff for records with media attachments.
     * Returns changed collections with old/new media ID arrays.
     *
     * @param  array<string, mixed>  $previousData
     * @return array<string, array{old: list<int>, new: list<int>}>
     */
    public function mediaDiffFrom(array $previousData): array
    {
        return $this->diff($previousData)->media();
    }

    /**
     * Compute a block diff for non-translatable records.
     * Returns changed collections with old/new normalized block arrays.
     *
     * @param  array<string, mixed>  $previousData
     * @return array<string, array{old: list<array{key: string, data: array<string, mixed>}>, new: list<array{key: string, data: array<string, mixed>}>}>
     */
    public function blockDiffFrom(array $previousData): array
    {
        return $this->diff($previousData)->blocks();
    }

    /**
     * Compute per-locale block diffs for translatable records.
     *
     * @param  array<string, mixed>  $previousData
     * @return array<string, array<string, array{old: list<array{key: string, data: array<string, mixed>}>, new: list<array{key: string, data: array<string, mixed>}>}>>
     */
    public function translationBlockDiffFrom(array $previousData): array
    {
        return $this->diff($previousData)->translationBlocks();
    }

    /**
     * Compute a per-block granular diff for non-translatable records.
     * Position-based: index i in old is compared to index i in new.
     * Only changed blocks are included (added/removed/modified).
     *
     * @param  array<string, mixed>  $previousData
     * @return array<string, list<array{action: 'added'|'removed'|'modified', position: int, key: string, data_diff?: array<string, array{old: mixed, new: mixed}>, media_diff?: array<string, array{old: list<int>, new: list<int>}>, data?: array<string, mixed>, media?: array<string, mixed>}>>
     */
    public function blockGranularDiffFrom(array $previousData): array
    {
        return $this->diff($previousData)->blockGranular();
    }

    /**
     * Compute per-locale granular block diffs for translatable records.
     *
     * @param  array<string, mixed>  $previousData
     * @return array<string, array<string, list<array{action: 'added'|'removed'|'modified', position: int, key: string, data_diff?: array<string, array{old: mixed, new: mixed}>, media_diff?: array<string, array{old: list<int>, new: list<int>}>, data?: array<string, mixed>, media?: array<string, mixed>}>>>
     */
    public function translationBlockGranularDiffFrom(array $previousData): array
    {
        return $this->diff($previousData)->translationBlockGranular();
    }

    /** @param  array<string, mixed>  $previousData */
    private function diff(array $previousData): RevisionDiff
    {
        return new RevisionDiff($this->revision_data ?? [], $previousData);
    }
}
