<?php

declare(strict_types=1);

namespace Plugin\tnt_mcp\src\Mcp\Backend;

use Throwable;

/**
 * The undo token returned by {@see GlobalStateSandbox::enter()}. Call {@see restore()} in a
 * `finally` — always, on every path.
 *
 * Two properties matter more than anything else here, because a missed restore leaks an
 * authenticated admin identity into the rest of the PHP process (design §5 R6):
 *
 * 1. **Idempotent.** A second `restore()` is a no-op. The `finally` of an inner helper and the
 *    `finally` of the caller may both fire; the second must not put back a *stale* snapshot over
 *    state that has legitimately moved on since.
 * 2. **Never throws.** Every undo step is individually guarded. One failing step (say, a shop that
 *    unloaded itself mid-invocation) must not prevent the remaining steps from running — the
 *    superglobal restores are the ones that matter and they must always be reached.
 *
 * Undo steps run in LIFO order — exact mirror image of the install order in `enter()`. The output
 * buffer unwind is therefore *last*, so that anything a restore step unexpectedly echoes is still
 * swallowed by the invocation's own buffers rather than corrupting the surrounding JSON response.
 */
final class Restore
{
    private bool $done = false;

    /**
     * @param list<callable():void> $undo install order; executed in reverse
     * @internal constructed only by {@see GlobalStateSandbox::enter()}
     */
    public function __construct(private array $undo)
    {
    }

    public function restore(): void
    {
        if ($this->done) {
            return;
        }
        // Flip *before* running, not after: an undo step that re-enters restore() (or a fatal in
        // one of them) must not be able to replay the snapshot a second time.
        $this->done = true;
        $steps      = $this->undo;
        $this->undo = [];

        foreach (\array_reverse($steps) as $step) {
            try {
                $step();
            } catch (Throwable) {
                // Deliberately swallowed: see the class docblock. Nothing here may abort the
                // remaining restores, and there is no logger this class is allowed to depend on.
            }
        }
    }
}
