// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title AereCryptoRegistry, a governed cryptographic-agility registry for AERE (chain 2800)
 *
 * @notice AERE has FIVE live native cryptographic precompiles on mainnet 2800
 *         (activated at block 9,189,161): four post-quantum SIGNATURE verifiers and one
 *         post-quantum HASH function. This registry turns the flat fact "we verify N
 *         schemes" into a governed table:
 *
 *              algorithmId  ->  { verifier address, wire format, status, gas, successor }
 *
 *         so a consumer integrates against ONE stable surface (this registry) instead of
 *         hardcoding a precompile address. When a scheme is deprecated, broken, or
 *         superseded, the Foundation (later the Timelock) flips its status and points it
 *         at a successor. Consumers that route through {resolveActive} follow the upgrade
 *         automatically, WITHOUT any consumer contract being redeployed.
 *
 * @dev    HONEST SCOPE. This is an application-layer routing + policy registry. It does
 *         NOT change AERE consensus (blocks are still produced and signed by Besu QBFT
 *         validators with classical ECDSA / secp256k1). It calls the SAME live native
 *         precompiles that {AerePQCAttestation} calls, and it builds the SAME wire-format
 *         input those precompiles parse. No funds are ever held. There is no payable
 *         function, no selfdestruct, and no fund custody of any kind.
 *
 * @dev    THE FIVE LIVE PRECOMPILES (chain 2800):
 *           scheme 1 = Falcon-512     at 0x...0AE1  (signature)  pk 897,  esig header 0x29
 *           scheme 2 = Falcon-1024    at 0x...0AE2  (signature)  pk 1793, esig header 0x2A
 *           scheme 3 = ML-DSA-44      at 0x...0AE3  (signature)  pk 1312, sig 2420  (FIPS 204)
 *           scheme 4 = SLH-DSA-128s   at 0x...0AE4  (signature)  pk 32,   sig 7856  (FIPS 205)
 *           scheme 5 = SHAKE256       at 0x...0AE5  (HASH, NOT a signature scheme)
 *
 * @dev    PRECOMPILE INPUT ENCODING (mirrors AerePQCAttestation._buildInput exactly):
 *
 *         Falcon family (envelope format, selected iff esigHeader != 0):
 *           signature envelope = nonce(40) || esig     (esig[0] == esigHeader)
 *           input = pk || sm
 *           sm    = esigLen(uint16 big-endian) || nonce(40) || message(32) || esig
 *
 *         Fixed-concatenation family (selected iff esigHeader == 0):
 *           signature = the raw fixed-length signature (sigLen bytes)
 *           input = pk || signature || message(32)
 *
 *         Every verifier returns a 32-byte word: 0x..01 == valid, anything else invalid.
 *         An empty/short/zero return (e.g. a pre-fork address with no precompile) is
 *         treated as INVALID, so a stale route can never report a false positive.
 *
 * @dev    WIRE-FORMAT DISCRIMINATOR. The registry selects the envelope (Falcon) format
 *         when the algorithm's `esigHeader` is non-zero, and the fixed-concatenation
 *         format otherwise. This keeps the registry agile across future schemes that
 *         share one of the two families (e.g. a new Falcon-like or a new fixed-length
 *         lattice scheme) without any code change: only a new row is added. A genuinely
 *         NEW wire family would require a registry upgrade, but consumers still never
 *         change their integration (they keep calling verify(id, ...)).
 */
contract AereCryptoRegistry is Ownable {
    // ==========================================================================
    //                               Types
    // ==========================================================================

    /// @notice Lifecycle of a registered algorithm.
    /// UNKNOWN   = never registered (the zero value; no stored row ever has this).
    /// ACTIVE    = registered and recommended for new use; verify() runs.
    /// DEPRECATED= verifiable for legacy signatures but NOT recommended for new use;
    ///             verify() still runs, but isActive()==false and isUsable()==false.
    /// REVOKED   = broken/withdrawn; verify() always returns false. Terminal by policy.
    enum Status {
        UNKNOWN,
        ACTIVE,
        DEPRECATED,
        REVOKED
    }

    /// @param name        human-readable identifier, e.g. "Falcon-512".
    /// @param scheme       the AERE scheme id (1..5 for the live set; informational).
    /// @param verifier     the address staticcalled to verify (a precompile or contract).
    /// @param pubKeyLen    exact public-key length in bytes the verifier expects.
    /// @param sigLen       exact signature length for the fixed-concatenation family
    ///                     (0 for the variable-length Falcon envelope family).
    /// @param esigHeader   Falcon esig header byte; NON-ZERO selects the Falcon envelope
    ///                     wire format, ZERO selects the fixed-concatenation format.
    /// @param status       lifecycle status (see {Status}).
    /// @param gasEstimate  ADVISORY metadata only: a rough gas budget hint for consumers.
    ///                     It does NOT cap or influence the actual staticcall gas.
    /// @param successorId  the algorithm id that supersedes this one (0 == none).
    /// @param addedBlock   block number the row was added at.
    /// @param isSignature  true for signature schemes; false for hash-only entries
    ///                     (e.g. SHAKE256), which verify() refuses to route.
    struct Algorithm {
        string name;
        uint8 scheme;
        address verifier;
        uint16 pubKeyLen;
        uint32 sigLen;
        uint8 esigHeader;
        Status status;
        uint256 gasEstimate;
        uint256 successorId;
        uint64 addedBlock;
        bool isSignature;
    }

    // ==========================================================================
    //                              Constants
    // ==========================================================================

    uint256 internal constant FALCON_NONCE_LEN = 40;
    uint256 internal constant NAME_MAX_LEN = 64;

    // Live precompile addresses on chain 2800 (used only by the seed function).
    address internal constant PRECOMPILE_FALCON512 = address(0x0AE1);
    address internal constant PRECOMPILE_FALCON1024 = address(0x0AE2);
    address internal constant PRECOMPILE_MLDSA44 = address(0x0AE3);
    address internal constant PRECOMPILE_SLHDSA128S = address(0x0AE4);
    address internal constant PRECOMPILE_SHAKE256 = address(0x0AE5);

    // Advisory gas-budget PLACEHOLDERS for the seeded live schemes. These are hints for
    // consumers, not gas caps; the operator can refine them by adding a new row. The
    // magnitudes reflect the native precompile costs on the Besu PQC fork, not the older
    // pure-EVM verifiers.
    uint256 internal constant GAS_FALCON512 = 120_000;
    uint256 internal constant GAS_FALCON1024 = 145_000;
    uint256 internal constant GAS_MLDSA44 = 351_000;
    uint256 internal constant GAS_SLHDSA128S = 400_000;
    uint256 internal constant GAS_SHAKE256 = 30_000;

    // ==========================================================================
    //           Canonical scheme identifiers (crypto-agility, ADDITIVE)
    // ==========================================================================
    //
    // The `scheme` field of an Algorithm row is an informational uint8 tag naming the NIST
    // parameter set a row implements (see the struct doc). These named constants pin the
    // CANONICAL scheme-ID space so governance, consumers, and off-chain tooling all agree
    // on the number. Ids 1..4 match AerePQCKeyRegistry's SCHEME_* exactly; id 5 is this
    // registry's SHAKE256 hash entry; ids 6..14 RESERVE future NIST parameter sets.
    // Numbering rules and the full rationale live in docs/PQ-AGILITY-HARDENING-2026-07-18.md.
    //
    // HONESTY: declaring a scheme id here does NOT register, seed, activate, or route
    // anything, and does NOT assert a verifier exists. Ids 1..5 are the only schemes with a
    // live precompile on chain 2800 today (0x0AE1..0x0AE5, activated block 9,189,161). Ids
    // 6..14 are RESERVED identifiers; a scheme becomes usable only when governance adds a
    // row via addAlgorithm pointing at a real, conformance-gated verifier. These constants
    // are purely additive reference values: seedLiveSchemes, verify, resolveActive, and
    // every other function are unchanged, and the constants occupy no storage.

    // Genesis block (ids 1..5): the five live schemes seedLiveSchemes registers.
    uint8 public constant SCHEME_FALCON512 = 1; // Falcon-512 (0x0AE1, live, signature)
    uint8 public constant SCHEME_FALCON1024 = 2; // Falcon-1024 (0x0AE2, live, signature)
    uint8 public constant SCHEME_MLDSA44 = 3; // ML-DSA-44 FIPS 204 (0x0AE3, live, signature)
    uint8 public constant SCHEME_SLHDSA128S = 4; // SLH-DSA-128s FIPS 205 (0x0AE4, live, signature)
    uint8 public constant SCHEME_SHAKE256 = 5; // SHAKE256 FIPS 202 (0x0AE5, live, hash/XOF)

    // Reserved future schemes (ids 6..14): NO live verifier yet, reserved numbers only.
    uint8 public constant SCHEME_MLDSA65 = 6; // ML-DSA-65 FIPS 204 (signature)
    uint8 public constant SCHEME_MLDSA87 = 7; // ML-DSA-87 FIPS 204 (signature)
    uint8 public constant SCHEME_FALCON512_PADDED = 8; // Falcon-512 padded FN-DSA draft (signature, fixed-len)
    uint8 public constant SCHEME_FALCON1024_PADDED = 9; // Falcon-1024 padded FN-DSA draft (signature, fixed-len)
    uint8 public constant SCHEME_SLHDSA192S = 10; // SLH-DSA-192s FIPS 205 (signature)
    uint8 public constant SCHEME_SLHDSA256S = 11; // SLH-DSA-256s FIPS 205 (signature)
    uint8 public constant SCHEME_MLKEM512 = 12; // ML-KEM-512 FIPS 203 (KEM, non-signature, discovery-only)
    uint8 public constant SCHEME_MLKEM768 = 13; // ML-KEM-768 FIPS 203 (KEM, non-signature, discovery-only)
    uint8 public constant SCHEME_MLKEM1024 = 14; // ML-KEM-1024 FIPS 203 (KEM, non-signature, discovery-only)

    // Auxiliary primitive (id 15): Falcon HashToPoint. Like SHAKE256 (id 5) this names a
    // HASH / auxiliary primitive, NOT a signature or KEM parameter set, so any future
    // registry row for it would be added isSignature == false and is never routed by the
    // signature-only verify() path. Id 15 is the next append-only number after the reserved
    // block (ids 1..14); the earlier soft "recommendation" in PQ-AGILITY-HARDENING-2026-07-18
    // to earmark ids 15+ for SLH-DSA fast variants was non-binding (no constant was ever
    // declared for those), so those parameter sets take the next free ids (16+) when a
    // concrete verifier is proposed. Append-only integrity is intact: no existing id moves.
    uint8 public constant SCHEME_FALCON_HASHTOPOINT = 15; // Falcon HashToPoint (SHAKE256 rejection sampler)

    // ==========================================================================
    //       NEW native precompiles - TESTNET ONLY (chain 2801), NOT mainnet 2800
    // ==========================================================================
    //
    // Two additional native precompiles were built on the AERE Besu PQC fork and are live on
    // the AERE TESTNET ONLY. They are NOT activated on mainnet 2800 as of 2026-07-18: mainnet
    // still has exactly the FIVE precompiles 0x0AE1..0x0AE5 (activated block 9,189,161). Their
    // gated mainnet-activation plan is docs/PQC-PRECOMPILES-MAINNET-ACTIVATION-PLAN-2026-07-18.md
    // (founder + external-audit gated). Wire formats (see AereMLKEM768.sol and AerePQCPrecompiles.sol):
    //
    //   0x0AE6  ML-KEM-768 (FIPS 203) deterministic Encaps:
    //             input  = ek(1184) || m(32)          (1216 bytes)
    //             output = ct(1088) || ss(32)         (1120 bytes); EMPTY on malformed input
    //           KEM, NOT a signature. Canonical scheme id = SCHEME_MLKEM768 (13).
    //   0x0AE7  Falcon HashToPoint (SHAKE256 rejection sampler):
    //             input  = logn(1) || nonce(40) || message(rest), logn = 9 (n=512) or 10 (n=1024)
    //             output = 2n bytes = n big-endian uint16 coefficients in [0, q), q=12289; EMPTY on malformed
    //           Auxiliary hash/lattice primitive, NOT a signature. Canonical scheme id = SCHEME_FALCON_HASHTOPOINT (15).
    //
    // HONESTY / ADDITIVE: these are reference constants ONLY. They are NOT seeded by
    // seedLiveSchemes, NOT routed by verify / resolveActive / precompileFor, and add no storage
    // and no code path. Declaring them makes NO on-chain claim that either precompile exists on
    // mainnet 2800; on mainnet a staticcall to 0x0AE6/0x0AE7 returns empty today, so every
    // consumer wrapper fail-closes until a governance-gated activation. A registry row for either
    // is created only after the activation + conformance gate in the plan doc passes (addAlgorithm,
    // onlyOwner). Address-literal style matches the seeded PRECOMPILE_* constants above.

    /// @notice TESTNET-ONLY address of the ML-KEM-768 (FIPS 203) deterministic-Encaps precompile
    ///         on the AERE Besu PQC fork. NOT on mainnet 2800. Reference value only: never seeded
    ///         or routed by this registry.
    address public constant PRECOMPILE_MLKEM768_TESTNET = address(0x0AE6);

    /// @notice TESTNET-ONLY address of the Falcon HashToPoint precompile on the AERE Besu PQC
    ///         fork. NOT on mainnet 2800. Reference value only: never seeded or routed here.
    address public constant PRECOMPILE_FALCON_HASHTOPOINT_TESTNET = address(0x0AE7);

    // ==========================================================================
    //                               Storage
    // ==========================================================================

    // Algorithm ids are 1-based and monotonic. Id 0 is the "none"/unknown sentinel
    // (also used as the "no successor" value), so it is never a valid row.
    mapping(uint256 => Algorithm) private _algorithms;
    uint256 private _count;
    bool private _seeded;

    // ==========================================================================
    //                                Events
    // ==========================================================================

    event AlgorithmAdded(
        uint256 indexed id, uint8 indexed scheme, address indexed verifier, string name, bool isSignature
    );
    event StatusChanged(uint256 indexed id, Status oldStatus, Status newStatus);
    event SuccessorSet(uint256 indexed id, uint256 indexed successorId);
    event LiveSchemesSeeded(uint256 count);

    // ==========================================================================
    //                                Errors
    // ==========================================================================

    error UnknownAlgorithm(uint256 id);
    error InvalidAlgorithmParams();
    error InvalidStatusTarget(); // attempt to set UNKNOWN
    error AlgorithmRevoked(uint256 id); // REVOKED is terminal
    error InvalidSuccessor(); // self-successor
    error NoActiveSuccessor(uint256 id); // resolveActive dead-ends with no ACTIVE row
    error SuccessorCycle(uint256 id); // resolveActive detected a cycle
    error UnknownScheme(uint8 scheme);
    error AlreadySeeded();

    // ==========================================================================
    //                           Owner: registration
    // ==========================================================================

    /**
     * @notice Register a new algorithm row. Owner only. Returns the fresh monotonic id.
     *         The row is created ACTIVE with no successor.
     * @dev    Length sanity: name in (0, 64]; verifier != 0; for a signature scheme
     *         pubKeyLen != 0, and for the fixed-concatenation family (esigHeader == 0)
     *         sigLen != 0. Hash-only entries (isSignature == false) skip the sig checks.
     */
    function addAlgorithm(
        string calldata name,
        uint8 scheme,
        address verifier,
        uint16 pubKeyLen,
        uint32 sigLen,
        uint8 esigHeader,
        uint256 gasEstimate,
        bool isSignature
    ) external onlyOwner returns (uint256 id) {
        return _addAlgorithm(name, scheme, verifier, pubKeyLen, sigLen, esigHeader, gasEstimate, isSignature);
    }

    function _addAlgorithm(
        string memory name,
        uint8 scheme,
        address verifier,
        uint16 pubKeyLen,
        uint32 sigLen,
        uint8 esigHeader,
        uint256 gasEstimate,
        bool isSignature
    ) internal returns (uint256 id) {
        uint256 nameLen = bytes(name).length;
        if (nameLen == 0 || nameLen > NAME_MAX_LEN) revert InvalidAlgorithmParams();
        if (verifier == address(0)) revert InvalidAlgorithmParams();
        if (isSignature) {
            if (pubKeyLen == 0) revert InvalidAlgorithmParams();
            // Fixed-concatenation family needs an exact signature length.
            if (esigHeader == 0 && sigLen == 0) revert InvalidAlgorithmParams();
        }

        _count += 1;
        id = _count;
        _algorithms[id] = Algorithm({
            name: name,
            scheme: scheme,
            verifier: verifier,
            pubKeyLen: pubKeyLen,
            sigLen: sigLen,
            esigHeader: esigHeader,
            status: Status.ACTIVE,
            gasEstimate: gasEstimate,
            successorId: 0,
            addedBlock: uint64(block.number),
            isSignature: isSignature
        });

        emit AlgorithmAdded(id, scheme, verifier, name, isSignature);
    }

    /**
     * @notice Change an algorithm's lifecycle status. Owner only.
     * @dev    May set ACTIVE (reactivate a DEPRECATED row), DEPRECATED, or REVOKED.
     *         Cannot set UNKNOWN (the sentinel). REVOKED is TERMINAL: a revoked row can
     *         never be changed again. To bring back a fixed variant of a revoked scheme,
     *         add a NEW algorithm id and point the revoked row's successor at it.
     */
    function setStatus(uint256 id, Status newStatus) external onlyOwner {
        if (!_exists(id)) revert UnknownAlgorithm(id);
        if (newStatus == Status.UNKNOWN) revert InvalidStatusTarget();
        Algorithm storage a = _algorithms[id];
        if (a.status == Status.REVOKED) revert AlgorithmRevoked(id);
        Status old = a.status;
        a.status = newStatus;
        emit StatusChanged(id, old, newStatus);
    }

    /**
     * @notice Point `id`'s successor at `successorId` (the row that supersedes it). Owner
     *         only. Both must exist and must differ. Longer cycles are tolerated at write
     *         time but are caught and reverted by {resolveActive}'s bounded walk.
     */
    function setSuccessor(uint256 id, uint256 successorId) external onlyOwner {
        if (!_exists(id)) revert UnknownAlgorithm(id);
        if (!_exists(successorId)) revert UnknownAlgorithm(successorId);
        if (successorId == id) revert InvalidSuccessor();
        _algorithms[id].successorId = successorId;
        emit SuccessorSet(id, successorId);
    }

    /**
     * @notice Seed the five live chain-2800 schemes. Owner only, one-shot (idempotent
     *         guard). Registers Falcon-512/1024, ML-DSA-44 and SLH-DSA-128s as signature
     *         schemes pointing at their live precompiles, plus SHAKE256 as a hash-only
     *         entry (isSignature == false) that {verify} refuses to route.
     */
    function seedLiveSchemes() external onlyOwner {
        if (_seeded) revert AlreadySeeded();
        _seeded = true;

        _addAlgorithm("Falcon-512", 1, PRECOMPILE_FALCON512, 897, 0, 0x29, GAS_FALCON512, true);
        _addAlgorithm("Falcon-1024", 2, PRECOMPILE_FALCON1024, 1793, 0, 0x2A, GAS_FALCON1024, true);
        _addAlgorithm("ML-DSA-44", 3, PRECOMPILE_MLDSA44, 1312, 2420, 0, GAS_MLDSA44, true);
        _addAlgorithm("SLH-DSA-128s", 4, PRECOMPILE_SLHDSA128S, 32, 7856, 0, GAS_SLHDSA128S, true);
        _addAlgorithm("SHAKE256", 5, PRECOMPILE_SHAKE256, 0, 0, 0, GAS_SHAKE256, false);

        emit LiveSchemesSeeded(_count);
    }

    // ==========================================================================
    //                             Verification
    // ==========================================================================

    /**
     * @notice Verify a signature for `algorithmId` against the routed verifier.
     *
     *         POLICY (fail-closed): returns FALSE, never reverts, when the id is unknown,
     *         the row is a hash-only entry (isSignature == false), the status is UNKNOWN
     *         or REVOKED, or the supplied lengths are malformed. A DEPRECATED row still
     *         verifies (legacy support); check {isUsable} before adopting a scheme for new
     *         work. Returns TRUE iff the routed verifier answers 0x..01.
     *
     * @param algorithmId the registry id to verify under.
     * @param pubKey      the raw public key for the scheme (exactly pubKeyLen bytes).
     * @param messageHash the 32-byte message that was signed.
     * @param signature   the signature envelope for the scheme's wire family.
     */
    function verify(uint256 algorithmId, bytes calldata pubKey, bytes32 messageHash, bytes calldata signature)
        external
        view
        returns (bool)
    {
        if (!_exists(algorithmId)) return false;
        Algorithm storage a = _algorithms[algorithmId];
        if (!a.isSignature) return false;
        Status st = a.status;
        if (st == Status.UNKNOWN || st == Status.REVOKED) return false; // ACTIVE or DEPRECATED proceed
        if (pubKey.length != a.pubKeyLen) return false;

        bytes memory input;
        if (a.esigHeader != 0) {
            // Falcon envelope family: signature = nonce(40) || esig.
            if (signature.length <= FALCON_NONCE_LEN) return false;
            uint256 esigLen = signature.length - FALCON_NONCE_LEN;
            if (esigLen > type(uint16).max) return false;
            bytes calldata nonce = signature[0:FALCON_NONCE_LEN];
            bytes calldata esig = signature[FALCON_NONCE_LEN:];
            // sm = esigLen(uint16 BE) || nonce(40) || message(32) || esig
            bytes memory sm = abi.encodePacked(uint16(esigLen), nonce, messageHash, esig);
            input = abi.encodePacked(pubKey, sm);
        } else {
            // Fixed-concatenation family: input = pk || sig || message.
            if (signature.length != a.sigLen) return false;
            input = abi.encodePacked(pubKey, signature, messageHash);
        }

        (bool ok, bytes memory ret) = a.verifier.staticcall(input);
        return ok && ret.length >= 32 && ret[31] == 0x01;
    }

    /**
     * @notice Follow the successor chain from `algorithmId` to the first ACTIVE row and
     *         return its id. Reverts {NoActiveSuccessor} if the chain dead-ends without an
     *         ACTIVE row, and {SuccessorCycle} if a cycle is detected. The walk is bounded
     *         by the number of registered algorithms, so it always terminates.
     */
    function resolveActive(uint256 algorithmId) external view returns (uint256) {
        uint256 id = algorithmId;
        // At most _count distinct rows exist; a walk longer than that is a cycle.
        for (uint256 hops = 0; hops <= _count; hops++) {
            if (!_exists(id)) revert UnknownAlgorithm(id);
            Algorithm storage a = _algorithms[id];
            if (a.status == Status.ACTIVE) return id;
            uint256 next = a.successorId;
            if (next == 0) revert NoActiveSuccessor(algorithmId);
            id = next;
        }
        revert SuccessorCycle(algorithmId);
    }

    // ==========================================================================
    //                                 Views
    // ==========================================================================

    /// @notice The full row for `id`. Reverts if `id` does not exist.
    function getAlgorithm(uint256 id) external view returns (Algorithm memory) {
        if (!_exists(id)) revert UnknownAlgorithm(id);
        return _algorithms[id];
    }

    /// @notice Number of registered algorithms. Ids run 1..algorithmCount().
    function algorithmCount() external view returns (uint256) {
        return _count;
    }

    /// @notice Lifecycle status of `id`; UNKNOWN for any id that was never registered.
    function statusOf(uint256 id) external view returns (Status) {
        if (!_exists(id)) return Status.UNKNOWN;
        return _algorithms[id].status;
    }

    /// @notice True iff `id` exists and is ACTIVE.
    function isActive(uint256 id) external view returns (bool) {
        return _exists(id) && _algorithms[id].status == Status.ACTIVE;
    }

    /**
     * @notice True iff `id` is a signature scheme that is safe to adopt for NEW work:
     *         it exists, isSignature == true, and status == ACTIVE. A DEPRECATED row is
     *         still verifiable (see {verify}) but is NOT usable, and a hash-only entry is
     *         never a usable signature verifier.
     */
    function isUsable(uint256 id) external view returns (bool) {
        if (!_exists(id)) return false;
        Algorithm storage a = _algorithms[id];
        return a.isSignature && a.status == Status.ACTIVE;
    }

    /// @notice The verifier address currently routing an ACTIVE algorithm for `scheme`.
    ///         Reverts {UnknownScheme} if no ACTIVE row serves that scheme.
    function precompileFor(uint8 scheme) external view returns (address) {
        uint256 n = _count;
        for (uint256 id = 1; id <= n; id++) {
            Algorithm storage a = _algorithms[id];
            if (a.scheme == scheme && a.status == Status.ACTIVE) return a.verifier;
        }
        revert UnknownScheme(scheme);
    }

    /// @notice Whether the one-shot {seedLiveSchemes} has already run.
    function seeded() external view returns (bool) {
        return _seeded;
    }

    // ==========================================================================
    //                                Internal
    // ==========================================================================

    function _exists(uint256 id) internal view returns (bool) {
        return id != 0 && id <= _count;
    }
}
