// 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";
import "./interfaces/IZedxionTransport.sol";

interface ICWMintBurnToken is IERC20 {
    function mint(address to, uint256 amount) external;
    function burnFrom(address from, uint256 amount) external;
}

/**
 * @title ZedxionCustomBridge
 * @notice GRU custom transport for Chain 138 ↔ ZEDXION (83872). No CCIP.
 * @dev Deploy at same address on 138 and 83872 via CREATE2 (salt keccak256("ZedxionCustomBridge")).
 *      On 138: lock canonical c* and emit LockForZedxion.
 *      On 83872: relayer calls releaseOnZedxion to mint cW* or transfer escrowed assets.
 *      Reverse: lock on 83872 (LockFor138), relayer completes releaseOn138 on 138.
 */
contract ZedxionCustomBridge is IZedxionTransport, AccessControl, ReentrancyGuard {
    using SafeERC20 for IERC20;

    bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
    uint256 public constant ZEDXION_CHAIN_ID = 83872;
    uint256 public constant HUB_CHAIN_ID = 138;

    mapping(bytes32 => LockRecord) public locks;
    mapping(bytes32 => bool) public releasedOnDestination;
    mapping(address => address) public canonicalToMirrored;
    mapping(address => address) public mirroredToCanonical;
    mapping(address => bool) public mintableMirroredToken;
    mapping(address => uint256) public nonces;
    bool private _hasRelayer;

    struct LockRecord {
        address sender;
        address token;
        uint256 amount;
        address recipient;
        uint256 createdAt;
        bool released;
    }

    event LockForZedxion(
        bytes32 indexed requestId,
        address indexed sender,
        address indexed token,
        uint256 amount,
        address recipient,
        uint256 sourceChainId
    );

    event UnlockOnZedxion(
        bytes32 indexed requestId,
        address indexed recipient,
        address indexed token,
        uint256 amount
    );

    event LockFor138(
        bytes32 indexed requestId,
        address indexed sender,
        address indexed token,
        uint256 amount,
        address recipient,
        uint256 sourceChainId
    );

    event UnlockOn138(
        bytes32 indexed requestId,
        address indexed recipient,
        address indexed token,
        uint256 amount
    );

    event TokenPairConfigured(address indexed canonicalToken, address indexed mirroredToken);

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

    function zedxionChainId() external pure override returns (uint256) {
        return ZEDXION_CHAIN_ID;
    }

    function configureTokenPair(address canonicalToken, address mirroredToken) external onlyRole(DEFAULT_ADMIN_ROLE) {
        require(canonicalToken != address(0) && mirroredToken != address(0), "zero token");
        canonicalToMirrored[canonicalToken] = mirroredToken;
        mirroredToCanonical[mirroredToken] = canonicalToken;
        emit TokenPairConfigured(canonicalToken, mirroredToken);
    }

    function setMintableMirroredToken(address mirroredToken, bool enabled) external onlyRole(DEFAULT_ADMIN_ROLE) {
        mintableMirroredToken[mirroredToken] = enabled;
    }

    function bridgeToZedxion(
        address token,
        uint256 amount,
        address recipientOn83872
    ) external payable override nonReentrant returns (bytes32 requestId) {
        require(block.chainid == HUB_CHAIN_ID, "ZedxionCustomBridge: hub only");
        return _lock(token, amount, recipientOn83872, ZEDXION_CHAIN_ID);
    }

    function bridgeFromZedxion(
        address tokenOn83872,
        uint256 amount,
        address recipientOn138
    ) external payable override nonReentrant returns (bytes32 requestId) {
        require(block.chainid == ZEDXION_CHAIN_ID, "ZedxionCustomBridge: zedxion only");
        return _lock(tokenOn83872, amount, recipientOn138, HUB_CHAIN_ID);
    }

    function _lock(
        address token,
        uint256 amount,
        address recipient,
        uint256 destinationChainId
    ) internal returns (bytes32 requestId) {
        require(recipient != address(0), "zero recipient");
        require(amount > 0, "zero amount");

        requestId = keccak256(abi.encodePacked(
            msg.sender,
            token,
            amount,
            recipient,
            nonces[msg.sender]++,
            block.chainid,
            block.timestamp
        ));

        if (token == address(0)) {
            require(msg.value >= amount, "insufficient value");
        } else {
            IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
        }

        locks[requestId] = LockRecord({
            sender: msg.sender,
            token: token,
            amount: amount,
            recipient: recipient,
            createdAt: block.timestamp,
            released: false
        });

        if (destinationChainId == ZEDXION_CHAIN_ID) {
            emit LockForZedxion(requestId, msg.sender, token, amount, recipient, block.chainid);
            emit ZedxionBridgeInitiated(requestId, msg.sender, token, amount, recipient, ZEDXION_CHAIN_ID);
        } else {
            emit LockFor138(requestId, msg.sender, token, amount, recipient, block.chainid);
            emit ZedxionBridgeInitiated(requestId, msg.sender, token, amount, recipient, HUB_CHAIN_ID);
        }
    }

    /**
     * @notice On 83872: mint cW* or release escrow after 138 lock. On 138: release canonical c* after 83872 burn/lock.
     */
    function releaseOnZedxion(
        bytes32 requestId,
        address token,
        uint256 amount,
        address recipient
    ) external onlyRole(RELAYER_ROLE) nonReentrant {
        require(block.chainid == ZEDXION_CHAIN_ID, "ZedxionCustomBridge: zedxion only");
        _release(requestId, token, amount, recipient, true);
    }

    function releaseOn138(
        bytes32 requestId,
        address token,
        uint256 amount,
        address recipient
    ) external onlyRole(RELAYER_ROLE) nonReentrant {
        require(block.chainid == HUB_CHAIN_ID, "ZedxionCustomBridge: hub only");
        _release(requestId, token, amount, recipient, false);
    }

    function _release(
        bytes32 requestId,
        address token,
        uint256 amount,
        address recipient,
        bool onZedxion
    ) internal {
        require(!releasedOnDestination[requestId], "already released");
        require(recipient != address(0), "zero recipient");
        require(amount > 0, "zero amount");
        releasedOnDestination[requestId] = true;

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

        emit ZedxionBridgeCompleted(requestId, bytes32(0));

        if (onZedxion) {
            emit UnlockOnZedxion(requestId, recipient, token, amount);
        } else {
            emit UnlockOn138(requestId, recipient, token, amount);
        }
    }

    receive() external payable {}
}
