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

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./AereMessenger.sol";

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    function balanceOf(address) external view returns (uint256);
    function mint(address to, uint256 amount) external;
    function burn(address from, uint256 amount) external;
}

/**
 * @title AereWarpRoute
 * @notice Typed cross-chain token bridge built on AereMessenger.
 *         One AereWarpRoute per token per chain. Two flavours:
 *
 *           Collateral mode (token has natural liquidity on this chain):
 *             - transferRemote: lock tokens on this chain → emit message → mint on remote
 *             - handle (incoming): receive message → unlock tokens on this chain to recipient
 *
 *           Synthetic mode (token is a bridged representation):
 *             - transferRemote: burn synthetic tokens here → emit message → mint on remote
 *             - handle (incoming): receive message → mint synthetic tokens here to recipient
 *
 *   This contract pattern mirrors Hyperlane Warp Route exactly, so apps built
 *   against Hyperlane's tooling work on AERE with only the contract address change.
 *
 *   For Bank28: USDC.aere, USDT.aere, WETH.aere, cbBTC.aere are all separate
 *   AereWarpRoute deployments in collateral mode (on the origin chain) and
 *   synthetic mode (on AERE) — totalling 8 deployments for 4 assets across
 *   2 chain pairs.
 */
contract AereWarpRoute is IMessageRecipient, Ownable, ReentrancyGuard {
    enum Mode { Collateral, Synthetic }

    AereMessenger public immutable messenger;
    IERC20 public immutable token;
    Mode public immutable mode;

    /// Remote AereWarpRoute deployments per destination domain.
    /// bytes32 because Hyperlane convention is to pad addresses.
    mapping(uint32 => bytes32) public remoteRouter;

    /// Lifetime metrics for transparency.
    uint256 public totalSentOut;
    uint256 public totalReceivedIn;

    event SentTransferRemote(uint32 indexed destDomain, bytes32 indexed recipient, uint256 amount, bytes32 messageId);
    event ReceivedTransferRemote(uint32 indexed origin, address indexed recipient, uint256 amount);
    event RemoteRouterEnrolled(uint32 indexed domain, bytes32 router);

    constructor(AereMessenger _messenger, IERC20 _token, Mode _mode) {
        messenger = _messenger;
        token = _token;
        mode = _mode;
    }

    // ───────────────────── Admin ─────────────────────

    /// Bind a remote AereWarpRoute deployment so it can talk to us.
    function enrollRemoteRouter(uint32 domain, bytes32 router) external onlyOwner {
        remoteRouter[domain] = router;
        emit RemoteRouterEnrolled(domain, router);
    }

    // ───────────────────── Outbound ─────────────────────

    /**
     * @notice Send tokens to a recipient on another chain via Hyperlane-compatible bridging.
     * @dev User must have approved this contract for `amount` of `token` first.
     */
    function transferRemote(
        uint32 destDomain,
        bytes32 recipient,
        uint256 amount
    ) external payable nonReentrant returns (bytes32 messageId) {
        require(remoteRouter[destDomain] != bytes32(0), "WarpRoute: no remote router");
        require(amount > 0, "WarpRoute: zero amount");

        if (mode == Mode.Collateral) {
            // Pull tokens; they remain locked here until a withdrawal arrives.
            require(token.transferFrom(msg.sender, address(this), amount), "WarpRoute: pull failed");
        } else {
            // Burn synthetic tokens from the sender — the remote side will mint the original.
            token.burn(msg.sender, amount);
        }

        bytes memory body = abi.encode(recipient, amount);
        messageId = messenger.dispatch{ value: msg.value }(destDomain, remoteRouter[destDomain], body);

        totalSentOut += amount;
        emit SentTransferRemote(destDomain, recipient, amount, messageId);
    }

    // ───────────────────── Inbound (only callable by Messenger) ─────────────────────

    function handle(uint32 origin, bytes32 sender, bytes calldata body) external payable override {
        require(msg.sender == address(messenger), "WarpRoute: caller not messenger");
        require(sender == remoteRouter[origin], "WarpRoute: unknown remote router");

        (bytes32 recipient32, uint256 amount) = abi.decode(body, (bytes32, uint256));
        address recipient = address(uint160(uint256(recipient32)));

        if (mode == Mode.Collateral) {
            // Release locked tokens to recipient.
            require(token.transfer(recipient, amount), "WarpRoute: release failed");
        } else {
            // Mint synthetic tokens to recipient.
            token.mint(recipient, amount);
        }

        totalReceivedIn += amount;
        emit ReceivedTransferRemote(origin, recipient, amount);
    }
}
