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

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

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

/**
 * @title TwoWayTokenBridgeL1
 * @notice L1/LMain chain side: locks canonical tokens and triggers CCIP message to mint on L2
 * @dev Uses escrow for locked tokens; release on inbound messages
 */
contract TwoWayTokenBridgeL1 {
    IRouterClient public immutable ccipRouter;
    address public immutable canonicalToken;
    address public feeToken; // LINK
    address public admin;

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

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

    mapping(bytes32 => bool) public processed; // replay protection

    event Locked(address indexed user, uint256 amount);
    event Released(address indexed recipient, uint256 amount);
    event CcipSend(bytes32 indexed messageId, uint64 destChain, address recipient, uint256 amount);
    event DestinationAdded(uint64 chainSelector, address l2Bridge);
    event DestinationUpdated(uint64 chainSelector, address l2Bridge);
    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);
        canonicalToken = _token;
        feeToken = _feeToken;
        admin = msg.sender;
    }

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

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

    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;
    }

    // User-facing: lock canonical tokens and send CCIP to mint on L2
    function lockAndSend(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");

        // Pull tokens into escrow
        require(IERC20(canonicalToken).transferFrom(msg.sender, address(this), amount), "pull fail");
        emit Locked(msg.sender, amount);

        // Encode payload
        bytes memory data = abi.encode(recipient, amount);

        // Build message
        IRouterClient.EVM2AnyMessage memory m = IRouterClient.EVM2AnyMessage({
            receiver: abi.encode(dest.l2Bridge),
            data: data,
            tokenAmounts: new IRouterClient.TokenAmount[](0),
            feeToken: feeToken,
            extraArgs: ""
        });

        // Get fee and pay in LINK held by user: bridge expects to have LINK pre-funded by admin or via separate topup
        uint256 fee = ccipRouter.getFee(destSelector, m);
        if (fee > 0) {
            // Expect admin has prefunded LINK to this contract; otherwise approvals/pull pattern can be added
            require(IERC20(feeToken).approve(address(ccipRouter), fee), "fee approve");
        }

        (messageId, ) = ccipRouter.ccipSend(destSelector, m);
        emit CcipSend(messageId, destSelector, recipient, amount);
        return messageId;
    }

    // Inbound from L2: release canonical 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");
        require(IERC20(canonicalToken).transfer(recipient, amount), "release fail");
        emit Released(recipient, amount);
    }
}


