<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Journal;

/**
 * One journal entry between `open()` and `close()` — the header ids plus the before-snapshot it is
 * holding in memory while the invocation runs.
 *
 * It exists as an object rather than a handful of locals in {@see ChangeJournal} because the
 * before-snapshot is taken OUTSIDE the invocation's transaction (design §4.4) and the after-snapshot
 * outside it again on the far side of the commit; between those two moments a backend controller
 * runs, and nothing about that is a single call stack.
 *
 * `$notUndoable` is set the moment a snapshot exceeds `maxRows` or covers a keyless table. It does
 * NOT abort the action: the entry refuses the *promise* of an undo, never the action itself
 * (§4.3) — and `invoke_admin_action` repeats the reason back to the caller.
 */
final class ChangeDraft
{
    /**
     * @param list<array{table:string,keys:list<string>,columns:list<string>,narrowed:bool,where:array<string,mixed>,rows:array<string,array<string,mixed>>}> $snapshots
     */
    public function __construct(
        public readonly string $changeId,
        public readonly int $kChange,
        public array $snapshots = [],
        public ?string $notUndoable = null,
    ) {
    }

    /** First reason wins: the earliest limit hit is the one that explains the rest. */
    public function refusePromise(string $reason): void
    {
        $this->notUndoable ??= $reason;
    }
}
