// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import "../ccip/IRouterClient.sol";

interface IMintableERC20 {
    function mint(address to, uint256 amount) external;
    function burnFrom(address from, uint256 amount) external;
    function balanceOf(address account) external view returns (uint256);
}

/**
 * @title TwoWayTokenBridgeL2
 * @notice L2/secondary chain side: mints mirrored tokens on inbound and burns on outbound
 */
contract TwoWayTokenBridgeL2 {
    IRouterClient public immutable ccipRouter;
    address public immutable mirroredToken;
    address public feeToken; // LINK
    address public admin;

    struct DestinationConfig {
        uint64 chainSelector;
        address l1Bridge;
        bool enabled;
    }

    mapping(uint64 => DestinationConfig) public destinations;
    uint64[] public destinationChains;
    mapping(bytes32 => bool) public processed;

    event Minted(address indexed recipient, uint256 amount);
    event Burned(address indexed user, uint256 amount);
    event CcipSend(bytes32 indexed messageId, uint64 destChain, address recipient, uint256 amount);
    event DestinationAdded(uint64 chainSelector, address l1Bridge);
    event DestinationUpdated(uint64 chainSelector, address l1Bridge);
    event DestinationRemoved(uint64 chainSelector);

    modifier onlyAdmin() {
        require(msg.sender == admin, "only admin");
        _;
    }

    modifier onlyRouter() {
        require(msg.sender == address(ccipRouter), "only router");
        _;
    }

    constructor(address _router, address _token, address _feeToken) {
        require(_router != address(0) && _token != address(0) && _feeToken != address(0), "zero addr");
        ccipRouter = IRouterClient(_router);
        mirroredToken = _token;
        feeToken = _feeToken;
        admin = msg.sender;
    }

    function addDestination(uint64 chainSelector, address l1Bridge) external onlyAdmin {
        require(l1Bridge != address(0), "zero l1");
        require(!destinations[chainSelector].enabled, "exists");
        destinations[chainSelector] = DestinationConfig(chainSelector, l1Bridge, true);
        destinationChains.push(chainSelector);
        emit DestinationAdded(chainSelector, l1Bridge);
    }

    function updateDestination(uint64 chainSelector, address l1Bridge) external onlyAdmin {
        require(destinations[chainSelector].enabled, "missing");
        require(l1Bridge != address(0), "zero l1");
        destinations[chainSelector].l1Bridge = l1Bridge;
        emit DestinationUpdated(chainSelector, l1Bridge);
    }

    function removeDestination(uint64 chainSelector) external onlyAdmin {
        require(destinations[chainSelector].enabled, "missing");
        destinations[chainSelector].enabled = false;
        for (uint256 i = 0; i < destinationChains.length; i++) {
            if (destinationChains[i] == chainSelector) {
                destinationChains[i] = destinationChains[destinationChains.length - 1];
                destinationChains.pop();
                break;
            }
        }
        emit DestinationRemoved(chainSelector);
    }

    function updateFeeToken(address newFee) external onlyAdmin {
        require(newFee != address(0), "zero");
        feeToken = newFee;
    }

    function changeAdmin(address newAdmin) external onlyAdmin {
        require(newAdmin != address(0), "zero");
        admin = newAdmin;
    }

    function getDestinationChains() external view returns (uint64[] memory) {
        return destinationChains;
    }

    // Inbound from L1: mint mirrored tokens to recipient
    function ccipReceive(IRouterClient.Any2EVMMessage calldata message) external onlyRouter {
        require(!processed[message.messageId], "replayed");
        processed[message.messageId] = true;
        (address recipient, uint256 amount) = abi.decode(message.data, (address, uint256));
        require(recipient != address(0) && amount > 0, "bad msg");
        IMintableERC20(mirroredToken).mint(recipient, amount);
        emit Minted(recipient, amount);
    }

    // Outbound to L1: burn mirrored tokens and signal release on L1
    function burnAndSend(uint64 destSelector, address recipient, uint256 amount) external returns (bytes32 messageId) {
        require(amount > 0 && recipient != address(0), "bad args");
        DestinationConfig memory dest = destinations[destSelector];
        require(dest.enabled, "dest disabled");

        IMintableERC20(mirroredToken).burnFrom(msg.sender, amount);
        emit Burned(msg.sender, amount);

        bytes memory data = abi.encode(recipient, amount);
        IRouterClient.EVM2AnyMessage memory m = IRouterClient.EVM2AnyMessage({
            receiver: abi.encode(dest.l1Bridge),
            data: data,
            tokenAmounts: new IRouterClient.TokenAmount[](0),
            feeToken: feeToken,
            extraArgs: ""
        });
        uint256 fee = ccipRouter.getFee(destSelector, m);
        if (fee > 0) {
            require(IERC20(feeToken).approve(address(ccipRouter), fee), "fee approve");
        }
        (messageId, ) = ccipRouter.ccipSend(destSelector, m);
        emit CcipSend(messageId, destSelector, recipient, amount);
        return messageId;
    }
}


