<?php

declare(strict_types=1);

namespace Apexl\Vault\Connections;

use Apexl\Vault\Interfaces\ConnectionInterface;
use PDO;
use Throwable;

final class SqlConnection implements ConnectionInterface
{
    /**
     * How many transaction() calls deep we are. PDO itself cannot answer this —
     * inTransaction() is a yes/no — and that gap is the whole bug: bookkeeping kept
     * anywhere other than alongside the handle cannot tell "I opened this" from
     * "someone else did".
     */
    private int $depth = 0;

    public function __construct(private readonly PDO $pdo) {}

    public function handle(): PDO
    {
        return $this->pdo;
    }

    public function lastInsertId(): int
    {
        return (int) $this->pdo->lastInsertId();
    }

    public function transaction(callable $work): mixed
    {
        // Only the outermost call touches the handle. MySQL has no true nested
        // transactions, and emitting BEGIN inside one silently commits it, so a nested
        // call joins rather than nests.
        $outermost = $this->depth === 0;

        if ($outermost) {
            $this->pdo->beginTransaction();
        }
        $this->depth++;

        try {
            $result = $work();
        } catch (Throwable $throwable) {
            $this->depth--;
            // An inner failure must not commit the outer work, so the rollback is left
            // to the outermost frame and the exception carries the decision up to it.
            if ($this->depth === 0 && $this->pdo->inTransaction()) {
                $this->pdo->rollBack();
            }

            throw $throwable;
        }

        $this->depth--;
        if ($this->depth === 0 && $this->pdo->inTransaction()) {
            $this->pdo->commit();
        }

        return $result;
    }

    public function inTransaction(): bool
    {
        return $this->pdo->inTransaction();
    }
}
