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

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/**
 * @title EtherlinkRelayReceiver
 * @notice Relay-compatible receiver on Etherlink (chain 42793). Accepts relayMintOrUnlock from off-chain relay.
 * @dev When CCIP does not support Etherlink, custom relay monitors source and calls this contract.
 *      Idempotency via messageId; only RELAYER_ROLE can call.
 */
contract EtherlinkRelayReceiver is AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");

    mapping(bytes32 => bool) public processed;

    event RelayMintOrUnlock(
        bytes32 indexed messageId,
        address indexed token,
        address indexed recipient,
        uint256 amount
    );

    constructor(address admin) {
        _grantRole(DEFAULT_ADMIN_ROLE, admin);
        _grantRole(RELAYER_ROLE, admin);
    }

    /**
     * @notice Mint or unlock tokens to recipient. Only relayer; idempotent per messageId.
     * @param messageId Source chain message id (for idempotency).
     * @param token Token address (address(0) for native).
     * @param recipient Recipient on Etherlink.
     * @param amount Amount to transfer.
     */
    function relayMintOrUnlock(
        bytes32 messageId,
        address token,
        address recipient,
        uint256 amount
    ) external onlyRole(RELAYER_ROLE) nonReentrant {
        require(!processed[messageId], "already processed");
        require(recipient != address(0), "zero recipient");
        require(amount > 0, "zero amount");
        processed[messageId] = true;

        if (token == address(0)) {
            (bool sent,) = payable(recipient).call{value: amount}("");
            require(sent, "transfer failed");
        } else {
            IERC20(token).safeTransfer(recipient, amount);
        }

        emit RelayMintOrUnlock(messageId, token, recipient, amount);
    }

    receive() external payable {}
}
