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

/**
 * @title RLPReader — minimal Recursive-Length-Prefix decoder for MPT node parsing.
 *
 * @dev   Operates on `bytes memory`. Just enough to walk Ethereum trie nodes:
 *        decode an item header, split a list into its raw element items, and read
 *        a string item's content. Rejects malformed / truncated input.
 */
library RLPReader {
    struct Item {
        uint256 offset; // byte offset of the item header within `data`
        uint256 headerLen; // length of the length-prefix header
        uint256 payloadLen; // length of the payload
        bool isList;
    }

    /// @notice Decode the RLP item header starting at `pos` in `data`.
    function itemAt(bytes memory data, uint256 pos) internal pure returns (Item memory it) {
        require(pos < data.length, "RLP:oob");
        uint256 b = uint8(data[pos]);
        if (b < 0x80) {
            // single byte, itself the payload
            it = Item(pos, 0, 1, false);
        } else if (b < 0xb8) {
            it = Item(pos, 1, b - 0x80, false);
        } else if (b < 0xc0) {
            uint256 lenOfLen = b - 0xb7;
            uint256 len = _readLen(data, pos + 1, lenOfLen);
            it = Item(pos, 1 + lenOfLen, len, false);
        } else if (b < 0xf8) {
            it = Item(pos, 1, b - 0xc0, true);
        } else {
            uint256 lenOfLen = b - 0xf7;
            uint256 len = _readLen(data, pos + 1, lenOfLen);
            it = Item(pos, 1 + lenOfLen, len, true);
        }
        require(it.offset + it.headerLen + it.payloadLen <= data.length, "RLP:truncated");
    }

    function _readLen(bytes memory data, uint256 pos, uint256 lenOfLen) private pure returns (uint256 len) {
        require(lenOfLen >= 1 && lenOfLen <= 8, "RLP:lenoflen");
        require(pos + lenOfLen <= data.length, "RLP:len oob");
        for (uint256 i = 0; i < lenOfLen; i++) {
            len = (len << 8) | uint8(data[pos + i]);
        }
    }

    /// @notice Total encoded length (header + payload) of the item at `pos`.
    function totalLen(bytes memory data, uint256 pos) internal pure returns (uint256) {
        Item memory it = itemAt(data, pos);
        return it.headerLen + it.payloadLen;
    }

    /// @notice Copy out the raw RLP bytes (header + payload) of the item at `pos`.
    function rawItemAt(bytes memory data, uint256 pos) internal pure returns (bytes memory out) {
        Item memory it = itemAt(data, pos);
        uint256 n = it.headerLen + it.payloadLen;
        out = new bytes(n);
        for (uint256 i = 0; i < n; i++) out[i] = data[pos + i];
    }

    /// @notice Read the CONTENT (payload) of a string item; reverts if it is a list.
    function readBytes(bytes memory item) internal pure returns (bytes memory out) {
        Item memory it = itemAt(item, 0);
        require(!it.isList, "RLP:not string");
        uint256 start = it.headerLen; // for single-byte, headerLen=0 so start=0 => the byte itself
        out = new bytes(it.payloadLen);
        for (uint256 i = 0; i < it.payloadLen; i++) out[i] = item[start + i];
    }

    /// @notice True iff the first byte marks a list (>= 0xc0).
    function isList(bytes memory item) internal pure returns (bool) {
        return item.length != 0 && uint8(item[0]) >= 0xc0;
    }

    /// @notice Split a list item into the RAW rlp bytes of each of its elements.
    function splitList(bytes memory listItem) internal pure returns (bytes[] memory items) {
        Item memory head = itemAt(listItem, 0);
        require(head.isList, "RLP:not list");
        uint256 end = head.headerLen + head.payloadLen;
        // First pass: count.
        uint256 count = 0;
        uint256 p = head.headerLen;
        while (p < end) {
            p += totalLen(listItem, p);
            count++;
        }
        items = new bytes[](count);
        p = head.headerLen;
        for (uint256 i = 0; i < count; i++) {
            uint256 n = totalLen(listItem, p);
            bytes memory el = new bytes(n);
            for (uint256 j = 0; j < n; j++) el[j] = listItem[p + j];
            items[i] = el;
            p += n;
        }
    }
}
