// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title CheckpointLeaf
 * @notice Canonical payment leaves for Merkle proofs (versioned).
 */
library CheckpointLeaf {
    bytes1 internal constant PAYMENT_LEAF_V1 = 0x01;
    bytes1 internal constant PAYMENT_LEAF_V2 = 0x02;

    struct PaymentLeafV1 {
        bytes32 txHash;
        address from;
        address to;
        uint256 value;
        uint256 blockNumber;
        uint64 blockTimestamp;
        uint256 gasUsed;
        bool success;
    }

    /// @notice V2 adds optional ERC-20 token + log index for transfer-only batches
    struct PaymentLeafV2 {
        bytes32 txHash;
        address from;
        address to;
        address token;
        uint256 value;
        uint256 blockNumber;
        uint64 blockTimestamp;
        uint256 gasUsed;
        bool success;
        uint32 logIndex;
    }

    function paymentLeafV1(
        uint64 chainId,
        PaymentLeafV1 memory leaf
    ) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                PAYMENT_LEAF_V1,
                chainId,
                leaf.txHash,
                leaf.from,
                leaf.to,
                leaf.value,
                leaf.blockNumber,
                leaf.blockTimestamp,
                leaf.gasUsed,
                leaf.success
            )
        );
    }

    function paymentLeafV2(uint64 chainId, PaymentLeafV2 memory leaf) internal pure returns (bytes32) {
        return keccak256(
            abi.encode(
                PAYMENT_LEAF_V2,
                chainId,
                leaf.txHash,
                leaf.from,
                leaf.to,
                leaf.token,
                leaf.value,
                leaf.blockNumber,
                leaf.blockTimestamp,
                leaf.gasUsed,
                leaf.success,
                leaf.logIndex
            )
        );
    }

  function buildMerkleRoot(bytes32[] memory leaves) internal pure returns (bytes32) {
        require(leaves.length > 0, "empty");
        if (leaves.length == 1) return leaves[0];
        bytes32[] memory layer = leaves;
        while (layer.length > 1) {
            uint256 nextLen = (layer.length + 1) / 2;
            bytes32[] memory next = new bytes32[](nextLen);
            for (uint256 i = 0; i < layer.length; i += 2) {
                bytes32 a = layer[i];
                bytes32 b = i + 1 < layer.length ? layer[i + 1] : a;
                next[i / 2] = _hashPair(a, b);
            }
            layer = next;
        }
        return layer[0];
    }

    function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
        return a < b ? keccak256(abi.encodePacked(a, b)) : keccak256(abi.encodePacked(b, a));
    }

    function verifyMerkle(bytes32 root, bytes32 leaf, bytes32[] memory proof) internal pure returns (bool) {
        bytes32 computed = leaf;
        for (uint256 i = 0; i < proof.length; i++) {
            bytes32 p = proof[i];
            computed = computed < p ? keccak256(abi.encodePacked(computed, p)) : keccak256(abi.encodePacked(p, computed));
        }
        return computed == root;
    }
}
